support draw chart
This commit is contained in:
parent
4ee8658d89
commit
5a45449b87
Binary file not shown.
|
|
@ -0,0 +1,459 @@
|
|||
from core.db.db_huge_volume_data import DBHugeVolumeData
|
||||
from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.drawing.image import Image
|
||||
from PIL import Image as PILImage
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
import os
|
||||
import re
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
sns.set_theme(style="whitegrid")
|
||||
# 设置中文
|
||||
plt.rcParams["font.sans-serif"] = ["SimHei"]
|
||||
plt.rcParams["axes.unicode_minus"] = False
|
||||
|
||||
|
||||
class HugeVolumeChart:
|
||||
def __init__(
|
||||
self,
|
||||
data: pd.DataFrame,
|
||||
output_folder: str = "./output/huge_volume_statistics/",
|
||||
):
|
||||
"""
|
||||
初始化
|
||||
data: 数据
|
||||
data中的列名为:
|
||||
symbol: 币种
|
||||
bar: 周期
|
||||
window_size: 窗口大小
|
||||
huge_volume: 是否巨量
|
||||
volume_ratio_percentile_10: 10%分位数
|
||||
volume_ratio_percentile_10_mean: 10%分位数平均值
|
||||
price_type: 价格类型
|
||||
next_period: 下一个周期
|
||||
average_return: 平均回报
|
||||
max_return: 最大回报
|
||||
min_return: 最小回报
|
||||
rise_count: 上涨次数
|
||||
rise_ratio: 上涨比例
|
||||
fall_count: 下跌次数
|
||||
fall_ratio: 下跌比例
|
||||
draw_count: 持平次数
|
||||
draw_ratio: 持平比例
|
||||
total_count: 总次数
|
||||
output_folder: 输出文件夹
|
||||
"""
|
||||
self.data = data
|
||||
# remove 1D bar
|
||||
self.data = self.data[self.data["bar"] != "1D"]
|
||||
self.data.reset_index(drop=True, inplace=True)
|
||||
self.output_folder = output_folder
|
||||
os.makedirs(self.output_folder, exist_ok=True)
|
||||
self.temp_dir = os.path.join(self.output_folder, "temp")
|
||||
os.makedirs(self.temp_dir, exist_ok=True)
|
||||
self.symbol_list = self.data["symbol"].unique().tolist()
|
||||
# sort symbol_list
|
||||
self.symbol_list.sort()
|
||||
self.bar_list = self.data["bar"].unique().tolist()
|
||||
self.bar_list.sort()
|
||||
self.window_size_list = self.data["window_size"].unique().tolist()
|
||||
self.window_size_list.sort()
|
||||
self.next_period_list = self.data["next_period"].unique().tolist()
|
||||
self.next_period_list.sort()
|
||||
self.volume_ratio_percentile_10_list = (
|
||||
self.data["volume_ratio_percentile_10"].unique().tolist()
|
||||
)
|
||||
self.volume_ratio_percentile_10_list.sort()
|
||||
self.price_type_list = self.data["price_type"].unique().tolist()
|
||||
self.price_type_list.sort()
|
||||
|
||||
def plot_entrance(self, include_heatmap: bool = True, include_line: bool = True):
|
||||
"""
|
||||
绘制上涨下跌图入口
|
||||
"""
|
||||
charts_dict = {}
|
||||
if include_heatmap:
|
||||
heatmap_plot_dict = self.plot_heatmap_entrance()
|
||||
if include_line:
|
||||
line_plot_dict = self.plot_line_chart_entrance()
|
||||
|
||||
if include_line:
|
||||
charts_dict.update(line_plot_dict)
|
||||
if include_heatmap:
|
||||
charts_dict.update(heatmap_plot_dict)
|
||||
return charts_dict
|
||||
|
||||
def plot_line_chart_entrance(self):
|
||||
"""
|
||||
绘制折线图入口
|
||||
"""
|
||||
charts_dict = {}
|
||||
# 根据price_type_list,得到各个price_type的平均rise_ratio,平均fall_ratio,平均draw_ratio, 平均average_return
|
||||
total_chart_path = self.plot_pice_rise_fall(data=self.data, prefix="总体")
|
||||
charts_dict["总体"] = {"总体": total_chart_path}
|
||||
self.plot_window_size_rise_fall(charts_dict=charts_dict)
|
||||
self.plot_window_size_bar_rise_fall(charts_dict=charts_dict)
|
||||
self.plot_window_size_bar_next_period_rise_fall(charts_dict=charts_dict)
|
||||
self.plot_symbol_rise_fall(charts_dict=charts_dict)
|
||||
self.plot_symbol_bar_rise_fall(charts_dict=charts_dict)
|
||||
self.plot_symbol_bar_window_size_rise_fall(charts_dict=charts_dict)
|
||||
self.plot_symbol_bar_window_size_next_period_rise_fall(charts_dict=charts_dict)
|
||||
|
||||
# self.plot_symbol_bar_window_size_volume_ratio_percentile_10_mean_rise_fall(charts_dict=charts_dict)
|
||||
# self.plot_symbol_bar_window_size_volume_ratio_percentile_10_mean_next_period_rise_fall(charts_dict=charts_dict)
|
||||
|
||||
self.output_excel(chart_type="line_chart", charts_dict=charts_dict)
|
||||
return charts_dict
|
||||
|
||||
def plot_heatmap_entrance(self):
|
||||
"""
|
||||
绘制热力图入口
|
||||
"""
|
||||
charts_dict = {}
|
||||
|
||||
self.plot_symbol_heatmap(charts_dict=charts_dict, ratio_column="rise_ratio", title=f"Rise Ratio Heatmap by Window Size and Bar")
|
||||
self.plot_symbol_heatmap(charts_dict=charts_dict, ratio_column="fall_ratio", title=f"Fall Ratio Heatmap by Window Size and Bar")
|
||||
self.plot_symbol_heatmap(charts_dict=charts_dict, ratio_column="average_return", title=f"Average Return Heatmap by Window Size and Bar")
|
||||
|
||||
self.output_excel(chart_type="heatmap_chart", charts_dict=charts_dict)
|
||||
return charts_dict
|
||||
|
||||
def plot_symbol_heatmap(self,
|
||||
charts_dict: dict,
|
||||
ratio_column: str = "rise_ratio",
|
||||
title: str = "Rise Ratio Heatmap by Window Size and Bar"
|
||||
):
|
||||
"""
|
||||
绘制symbol热力图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_{ratio_column}_heatmap"] = {}
|
||||
for price_type in self.price_type_list:
|
||||
logging.info(f"绘制{symbol} {price_type} {ratio_column}热力图")
|
||||
df = self.data[(self.data["symbol"] == symbol) & (self.data["price_type"] == price_type)]
|
||||
pivot_table = df.pivot_table(values=ratio_column, index='window_size', columns='bar', aggfunc='mean')
|
||||
plt.figure(figsize=(10, 6))
|
||||
# 热力图以红色渐变为主,红色表示高,绿色表示低
|
||||
sns.heatmap(pivot_table, annot=True, cmap='RdYlGn_r', fmt='.3f')
|
||||
plt.xlabel('Period')
|
||||
plt.ylabel('Window Size')
|
||||
plt.title(f"{title} {price_type}")
|
||||
# plt.show()
|
||||
chart_path = os.path.join(self.temp_dir, f'{symbol}_{price_type}_{ratio_column}_heatmap.png')
|
||||
plt.savefig(chart_path, bbox_inches='tight', dpi=100)
|
||||
plt.close()
|
||||
charts_dict[f"{symbol}_{ratio_column}_heatmap"][f"{symbol}_{price_type}_{ratio_column}_heatmap"] = chart_path
|
||||
|
||||
def plot_window_size_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
不区分symbol, 绘制window_size上涨下跌图
|
||||
"""
|
||||
charts_dict["window_size"] = {}
|
||||
for window_size in self.window_size_list:
|
||||
data = self.data[self.data["window_size"] == window_size]
|
||||
chart_path = self.plot_pice_rise_fall(data, prefix=f"window_size_{window_size}")
|
||||
charts_dict["window_size"][f"window_size_{window_size}"] = chart_path
|
||||
|
||||
def plot_window_size_bar_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
不区分symbol, 根据window_size绘制bar上涨下跌图
|
||||
"""
|
||||
charts_dict["window_size_bar"] = {}
|
||||
for window_size in self.window_size_list:
|
||||
for bar in self.bar_list:
|
||||
data = self.data[
|
||||
(self.data["window_size"] == window_size)
|
||||
& (self.data["bar"] == bar)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(
|
||||
data, prefix=f"window_size_{window_size}_bar_{bar}"
|
||||
)
|
||||
charts_dict["window_size_bar"][f"window_size_{window_size}_bar_{bar}"] = chart_path
|
||||
|
||||
def plot_window_size_bar_next_period_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
不区分symbol, 根据window_size, bar, next_period上涨下跌图
|
||||
"""
|
||||
charts_dict["window_size_bar_period"] = {}
|
||||
for window_size in self.window_size_list:
|
||||
for bar in self.bar_list:
|
||||
for next_period in self.next_period_list:
|
||||
data = self.data[
|
||||
(self.data["window_size"] == window_size)
|
||||
& (self.data["bar"] == bar)
|
||||
& (self.data["next_period"] == next_period)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(
|
||||
data,
|
||||
prefix=f"window_size_{window_size}_bar_{bar}_next_period_{next_period}"
|
||||
)
|
||||
charts_dict["window_size_bar_period"][f"window_size_{window_size}_bar_{bar}_next_period_{next_period}"] = chart_path
|
||||
|
||||
def plot_symbol_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
区分symbol, 绘制symbol上涨下跌图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_总体"] = {}
|
||||
data = self.data[self.data["symbol"] == symbol]
|
||||
chart_path = self.plot_pice_rise_fall(data, prefix=symbol)
|
||||
charts_dict[f"{symbol}_总体"][symbol] = chart_path
|
||||
|
||||
def plot_symbol_bar_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
区分symbol, bar, 绘制symbol上涨下跌图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_bar"] = {}
|
||||
for bar in self.bar_list:
|
||||
data = self.data[
|
||||
(self.data["symbol"] == symbol) & (self.data["bar"] == bar)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(data, prefix=f"{symbol}_{bar}")
|
||||
charts_dict[f"{symbol}_bar"][f"{symbol}_{bar}"] = chart_path
|
||||
|
||||
def plot_symbol_bar_window_size_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
区分symbol, bar, window_size, 绘制symbol上涨下跌图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_bar_window_size"] = {}
|
||||
for bar in self.bar_list:
|
||||
for window_size in self.window_size_list:
|
||||
data = self.data[
|
||||
(self.data["symbol"] == symbol)
|
||||
& (self.data["bar"] == bar)
|
||||
& (self.data["window_size"] == window_size)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(
|
||||
data, prefix=f"{symbol}_{bar}_ws_{window_size}"
|
||||
)
|
||||
charts_dict[f"{symbol}_bar_window_size"][f"{symbol}_{bar}_ws_{window_size}"] = chart_path
|
||||
|
||||
def plot_symbol_bar_window_size_next_period_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
区分symbol, bar, window_size, next_period, 绘制symbol上涨下跌图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_bar_ws_period"] = {}
|
||||
for bar in self.bar_list:
|
||||
for window_size in self.window_size_list:
|
||||
for next_period in self.next_period_list:
|
||||
data = self.data[
|
||||
(self.data["symbol"] == symbol)
|
||||
& (self.data["bar"] == bar)
|
||||
& (self.data["window_size"] == window_size)
|
||||
& (self.data["next_period"] == next_period)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(
|
||||
data,
|
||||
prefix=f"{symbol}_{bar}_ws_{window_size}_next_period_{next_period}"
|
||||
)
|
||||
charts_dict[f"{symbol}_bar_ws_period"][f"{symbol}_{bar}_ws_{window_size}_next_period_{next_period}"] = chart_path
|
||||
|
||||
def plot_symbol_bar_window_size_volume_ratio_percentile_10_mean_rise_fall(self, charts_dict: dict):
|
||||
"""
|
||||
区分symbol, bar, window_size, volume_ratio_percentile_10_mean, 绘制symbol上涨下跌图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_bar_window_size_vol_per"] = {}
|
||||
for bar in self.bar_list:
|
||||
for window_size in self.window_size_list:
|
||||
for (
|
||||
volume_ratio_percentile_10
|
||||
) in self.volume_ratio_percentile_10_list:
|
||||
data = self.data[
|
||||
(self.data["symbol"] == symbol)
|
||||
& (self.data["bar"] == bar)
|
||||
& (self.data["window_size"] == window_size)
|
||||
& (
|
||||
self.data["volume_ratio_percentile_10"]
|
||||
== volume_ratio_percentile_10
|
||||
)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(
|
||||
data,
|
||||
prefix=f"{symbol}_{bar}_ws_{window_size}_vol_per_{volume_ratio_percentile_10}"
|
||||
)
|
||||
charts_dict[f"{symbol}_bar_window_size_vol_per"][f"{symbol}_{bar}_ws_{window_size}_vol_per_{volume_ratio_percentile_10}"] = chart_path
|
||||
|
||||
def plot_symbol_bar_window_size_volume_ratio_percentile_10_mean_next_period_rise_fall(
|
||||
self,
|
||||
charts_dict: dict,
|
||||
):
|
||||
"""
|
||||
区分symbol, bar, window_size, volume_ratio_percentile_10_mean, next_period, 绘制symbol上涨下跌图
|
||||
"""
|
||||
for symbol in self.symbol_list:
|
||||
charts_dict[f"{symbol}_bar_ws_vol_period"] = {}
|
||||
for bar in self.bar_list:
|
||||
for window_size in self.window_size_list:
|
||||
for (
|
||||
volume_ratio_percentile_10
|
||||
) in self.volume_ratio_percentile_10_list:
|
||||
for next_period in self.next_period_list:
|
||||
data = self.data[
|
||||
(self.data["symbol"] == symbol)
|
||||
& (self.data["bar"] == bar)
|
||||
& (self.data["window_size"] == window_size)
|
||||
& (
|
||||
self.data["volume_ratio_percentile_10"]
|
||||
== volume_ratio_percentile_10
|
||||
)
|
||||
& (self.data["next_period"] == next_period)
|
||||
]
|
||||
chart_path = self.plot_pice_rise_fall(
|
||||
data,
|
||||
prefix=f"{symbol}_{bar}_ws_{window_size}_vol_per_{volume_ratio_percentile_10}_next_period_{next_period}"
|
||||
)
|
||||
charts_dict[f"{symbol}_bar_ws_vol_period"][f"{symbol}_{bar}_ws_{window_size}_vol_per_{volume_ratio_percentile_10}_next_period_{next_period}"] = chart_path
|
||||
|
||||
def plot_pice_rise_fall(self, data: pd.DataFrame, prefix: str = ""):
|
||||
"""
|
||||
绘制价格上涨下跌图
|
||||
"""
|
||||
logging.info(f"绘制价格上涨下跌图: {prefix}")
|
||||
# 根据price_type_list,得到各个price_type的平均rise_ratio,平均fall_ratio,平均draw_ratio, 平均average_return
|
||||
price_type_data_dict = {}
|
||||
for price_type in self.price_type_list:
|
||||
filtered_data = data[data["price_type"] == price_type]
|
||||
average_rise_ratio = filtered_data["rise_ratio"].mean()
|
||||
average_fall_ratio = filtered_data["fall_ratio"].mean()
|
||||
average_draw_ratio = filtered_data["draw_ratio"].mean()
|
||||
average_average_return = filtered_data["average_return"].mean()
|
||||
price_type_data_dict[price_type] = {
|
||||
"average_rise_ratio": average_rise_ratio,
|
||||
"average_fall_ratio": average_fall_ratio,
|
||||
"average_draw_ratio": average_draw_ratio,
|
||||
"average_average_return": average_average_return,
|
||||
}
|
||||
|
||||
# 准备数据用于绘图
|
||||
price_types = list(price_type_data_dict.keys())
|
||||
rise_ratios = [
|
||||
price_type_data_dict[pt]["average_rise_ratio"] for pt in price_types
|
||||
]
|
||||
fall_ratios = [
|
||||
price_type_data_dict[pt]["average_fall_ratio"] for pt in price_types
|
||||
]
|
||||
draw_ratios = [
|
||||
price_type_data_dict[pt]["average_draw_ratio"] for pt in price_types
|
||||
]
|
||||
avg_returns = [
|
||||
price_type_data_dict[pt]["average_average_return"] for pt in price_types
|
||||
]
|
||||
|
||||
# 创建子图,保持2x2布局
|
||||
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
|
||||
|
||||
# 绘制上涨比例
|
||||
bars1 = ax1.bar(price_types, rise_ratios, color="green", alpha=0.7)
|
||||
ax1.set_title(f"{prefix}总体平均上涨比例")
|
||||
ax1.set_ylabel("上涨比例")
|
||||
ax1.tick_params(axis="x", rotation=0)
|
||||
ax1.bar_label(bars1, fmt="%.2f")
|
||||
|
||||
# 绘制下跌比例
|
||||
bars2 = ax2.bar(price_types, fall_ratios, color="red", alpha=0.7)
|
||||
ax2.set_title(f"{prefix}平均下跌比例")
|
||||
ax2.set_ylabel("下跌比例")
|
||||
ax2.tick_params(axis="x", rotation=0)
|
||||
ax2.bar_label(bars2, fmt="%.2f")
|
||||
|
||||
# 绘制持平比例
|
||||
bars3 = ax3.bar(price_types, draw_ratios, color="gray", alpha=0.7)
|
||||
ax3.set_title(f"{prefix}平均持平比例")
|
||||
ax3.set_ylabel("持平比例")
|
||||
ax3.tick_params(axis="x", rotation=0)
|
||||
ax3.bar_label(bars3, fmt="%.2f")
|
||||
|
||||
# 绘制平均回报
|
||||
bars4 = ax4.bar(price_types, avg_returns, color="blue", alpha=0.7)
|
||||
ax4.set_title(f"{prefix}平均回报")
|
||||
ax4.set_ylabel("平均回报")
|
||||
ax4.tick_params(axis="x", rotation=0)
|
||||
ax4.bar_label(bars4, fmt="%.2f")
|
||||
|
||||
# 调整布局,增加底部空间和垂直间距以显示完整的x轴标签
|
||||
plt.tight_layout()
|
||||
plt.subplots_adjust(bottom=0.15, hspace=0.4)
|
||||
# plt.show()
|
||||
chart_path = os.path.join(self.temp_dir, f'{prefix}.png')
|
||||
plt.savefig(chart_path, bbox_inches='tight', dpi=100)
|
||||
plt.close()
|
||||
return chart_path
|
||||
|
||||
def output_excel(self, chart_type: str, charts_dict: dict):
|
||||
"""
|
||||
输出Excel文件,包含所有图表
|
||||
charts_dict: 图表数据字典,格式为:
|
||||
{
|
||||
"sheet_name": {
|
||||
"chart_name": "chart_path"
|
||||
}
|
||||
}
|
||||
"""
|
||||
logging.info(f"输出Excel文件,包含所有{chart_type}图表")
|
||||
file_name = f"huge_volume_{chart_type}_{datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"
|
||||
file_path = os.path.join(self.output_folder, file_name)
|
||||
|
||||
# Create Excel file and worksheet
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active) # Remove default sheet
|
||||
|
||||
for sheet_name, chart_data_dict in charts_dict.items():
|
||||
try:
|
||||
ws = wb.create_sheet(title=sheet_name)
|
||||
row_offset = 1
|
||||
for chart_name, chart_path in chart_data_dict.items():
|
||||
# Load image to get dimensions
|
||||
with PILImage.open(chart_path) as img:
|
||||
width_px, height_px = img.size
|
||||
|
||||
# Convert pixel height to Excel row height (approximate: 1 point = 1.333 pixels, 1 row ≈ 15 points for 20 pixels)
|
||||
pixels_per_point = 1.333
|
||||
points_per_row = 15 # Default row height in points
|
||||
pixels_per_row = (
|
||||
points_per_row * pixels_per_point
|
||||
) # ≈ 20 pixels per row
|
||||
chart_rows = max(
|
||||
10, int(height_px / pixels_per_row)
|
||||
) # Minimum 10 rows for small charts
|
||||
|
||||
# Add chart title
|
||||
# 支持中文标题
|
||||
ws[f"A{row_offset}"] = chart_name.encode("utf-8").decode("utf-8")
|
||||
ws[f"A{row_offset}"].font = openpyxl.styles.Font(bold=True, size=12)
|
||||
row_offset += 2 # Add 2 rows for title and spacing
|
||||
|
||||
# Insert chart image
|
||||
img = Image(chart_path)
|
||||
ws.add_image(img, f"A{row_offset}")
|
||||
|
||||
# Update row offset (chart height + padding)
|
||||
row_offset += chart_rows + 5 # Add 5 rows for padding between charts
|
||||
except Exception as e:
|
||||
logging.error(f"输出Excel Sheet {sheet_name} 失败: {e}")
|
||||
continue
|
||||
|
||||
# Save Excel file
|
||||
wb.save(file_path)
|
||||
print(f"Excel file saved as {file_path}")
|
||||
|
||||
for sheet_name, chart_data_dict in charts_dict.items():
|
||||
for chart_name, chart_path in chart_data_dict.items():
|
||||
try:
|
||||
os.remove(chart_path)
|
||||
except Exception as e:
|
||||
logging.error(f"删除临时文件失败: {e}")
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
from core.biz.huge_volume import HugeVolume
|
||||
from core.biz.huge_volume_chart import HugeVolumeChart
|
||||
from core.db.db_market_data import DBMarketData
|
||||
from core.db.db_huge_volume_data import DBHugeVolumeData
|
||||
from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp
|
||||
|
|
@ -35,7 +36,9 @@ class HugeVolumeMain:
|
|||
self.output_folder = "./output/huge_volume_statistics/"
|
||||
os.makedirs(self.output_folder, exist_ok=True)
|
||||
|
||||
def batch_initial_detect_volume_spike(self, window_size: int = 50, start: str = None):
|
||||
def batch_initial_detect_volume_spike(
|
||||
self, window_size: int = 50, start: str = None
|
||||
):
|
||||
for symbol in self.market_data_main.symbols:
|
||||
for bar in self.market_data_main.bars:
|
||||
if start is None:
|
||||
|
|
@ -43,7 +46,12 @@ class HugeVolumeMain:
|
|||
"initial_date", "2025-05-01 00:00:00"
|
||||
)
|
||||
data = self.detect_volume_spike(
|
||||
symbol, bar, window_size, start, only_output_huge_volume=False, is_update=False
|
||||
symbol,
|
||||
bar,
|
||||
window_size,
|
||||
start,
|
||||
only_output_huge_volume=False,
|
||||
is_update=False,
|
||||
)
|
||||
if data is not None and len(data) > 0:
|
||||
logging.info(f"此次初始化巨量交易数据: {len(data)}条")
|
||||
|
|
@ -66,16 +74,22 @@ class HugeVolumeMain:
|
|||
)
|
||||
if end is None:
|
||||
end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
logging.info(f"开始处理巨量交易数据: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
||||
logging.info(
|
||||
f"开始处理巨量交易数据: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||
)
|
||||
data = self.db_market_data.query_market_data_by_symbol_bar(
|
||||
symbol, bar, start, end
|
||||
)
|
||||
if data is None:
|
||||
logging.warning(f"获取行情数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
||||
logging.warning(
|
||||
f"获取行情数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
if len(data) == 0:
|
||||
logging.warning(f"获取行情数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
||||
logging.warning(
|
||||
f"获取行情数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
if isinstance(data, list):
|
||||
|
|
@ -93,10 +107,8 @@ class HugeVolumeMain:
|
|||
if data is not None:
|
||||
if is_update:
|
||||
for index, row in data.iterrows():
|
||||
exist_huge_volume_data = (
|
||||
self.db_huge_volume_data.query_data_by_symbol_bar_window_size_timestamp(
|
||||
symbol, bar, window_size, row["timestamp"]
|
||||
)
|
||||
exist_huge_volume_data = self.db_huge_volume_data.query_data_by_symbol_bar_window_size_timestamp(
|
||||
symbol, bar, window_size, row["timestamp"]
|
||||
)
|
||||
if exist_huge_volume_data is not None:
|
||||
# remove the exist_huge_volume_data from data
|
||||
|
|
@ -152,7 +164,9 @@ class HugeVolumeMain:
|
|||
else:
|
||||
logging.info(f"此次更新巨量交易数据为空")
|
||||
except Exception as e:
|
||||
logging.error(f"更新巨量交易数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {earliest_date_time} 到 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {e}")
|
||||
logging.error(
|
||||
f"更新巨量交易数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {earliest_date_time} 到 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}: {e}"
|
||||
)
|
||||
|
||||
def get_seconds_by_bar(self, bar: str):
|
||||
"""
|
||||
|
|
@ -214,26 +228,29 @@ class HugeVolumeMain:
|
|||
if end is None:
|
||||
end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
periods_text = ", ".join([str(period) for period in periods])
|
||||
logging.info(f"开始计算巨量出现后,之后{periods_text}个周期,上涨或下跌的比例: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
||||
logging.info(
|
||||
f"开始计算巨量出现后,之后{periods_text}个周期,上涨或下跌的比例: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||
)
|
||||
volume_statistics_data = (
|
||||
self.db_huge_volume_data.query_huge_volume_data_by_symbol_bar_window_size(
|
||||
symbol, bar, window_size, start, end
|
||||
)
|
||||
)
|
||||
if volume_statistics_data is None or len(volume_statistics_data) == 0:
|
||||
logging.warning(f"获取巨量交易数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
||||
logging.warning(
|
||||
f"获取巨量交易数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
if isinstance(volume_statistics_data, list):
|
||||
volume_statistics_data = pd.DataFrame(volume_statistics_data)
|
||||
elif isinstance(volume_statistics_data, dict):
|
||||
volume_statistics_data = pd.DataFrame([volume_statistics_data])
|
||||
if (
|
||||
volume_statistics_data is not None
|
||||
and len(volume_statistics_data) > 0
|
||||
):
|
||||
if volume_statistics_data is not None and len(volume_statistics_data) > 0:
|
||||
# 根据timestamp排序
|
||||
volume_statistics_data = volume_statistics_data.sort_values(by="timestamp", ascending=True)
|
||||
volume_statistics_data = volume_statistics_data.sort_values(
|
||||
by="timestamp", ascending=True
|
||||
)
|
||||
volume_statistics_data["window_size"] = window_size
|
||||
volume_statistics_data = volume_statistics_data[
|
||||
[
|
||||
|
|
@ -279,7 +296,11 @@ class HugeVolumeMain:
|
|||
huge_volume_data_list = []
|
||||
result_data_list = []
|
||||
window_size_list = WINDOW_SIZE.get("window_sizes", None)
|
||||
if window_size_list is None or not isinstance(window_size_list, list) or len(window_size_list) == 0:
|
||||
if (
|
||||
window_size_list is None
|
||||
or not isinstance(window_size_list, list)
|
||||
or len(window_size_list) == 0
|
||||
):
|
||||
window_size_list = [50, 80, 100, 120]
|
||||
|
||||
for symbol in self.market_data_main.symbols:
|
||||
|
|
@ -296,14 +317,14 @@ class HugeVolumeMain:
|
|||
total_huge_volume_data = total_huge_volume_data.reset_index(drop=True)
|
||||
total_result_data = total_result_data.reset_index(drop=True)
|
||||
current_date = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
file_name = (
|
||||
f"next_periods_rise_or_fall_{current_date}.xlsx"
|
||||
)
|
||||
file_name = f"next_periods_rise_or_fall_{current_date}.xlsx"
|
||||
try:
|
||||
with pd.ExcelWriter(
|
||||
os.path.join(self.output_folder, file_name)
|
||||
) as writer:
|
||||
total_huge_volume_data.to_excel(writer, sheet_name="details", index=False)
|
||||
total_huge_volume_data.to_excel(
|
||||
writer, sheet_name="details", index=False
|
||||
)
|
||||
total_result_data.to_excel(
|
||||
writer, sheet_name="next_periods_statistics", index=False
|
||||
)
|
||||
|
|
@ -311,10 +332,29 @@ class HugeVolumeMain:
|
|||
logging.error(f"导出Excel文件失败: {e}")
|
||||
return total_huge_volume_data, total_result_data
|
||||
|
||||
def plot_huge_volume_data(
|
||||
self,
|
||||
data_file_path: str,
|
||||
sheet_name: str = "next_periods_statistics",
|
||||
output_folder: str = "./output/huge_volume_statistics/",
|
||||
):
|
||||
os.makedirs(output_folder, exist_ok=True)
|
||||
huge_volume_data = pd.read_excel(data_file_path, sheet_name=sheet_name)
|
||||
huge_volume_chart = HugeVolumeChart(huge_volume_data)
|
||||
include_heatmap = True
|
||||
include_line = False
|
||||
huge_volume_chart.plot_entrance(
|
||||
include_heatmap=include_heatmap, include_line=include_line
|
||||
)
|
||||
|
||||
|
||||
def batch_initial_detect_volume_spike(threshold: float = 2.0):
|
||||
window_sizes = WINDOW_SIZE.get("window_sizes", None)
|
||||
if window_sizes is None or not isinstance(window_sizes, list) or len(window_sizes) == 0:
|
||||
if (
|
||||
window_sizes is None
|
||||
or not isinstance(window_sizes, list)
|
||||
or len(window_sizes) == 0
|
||||
):
|
||||
window_sizes = [50, 80, 100, 120]
|
||||
huge_volume_main = HugeVolumeMain(threshold)
|
||||
for window_size in window_sizes:
|
||||
|
|
@ -326,7 +366,11 @@ def batch_initial_detect_volume_spike(threshold: float = 2.0):
|
|||
|
||||
def batch_update_volume_spike(threshold: float = 2.0):
|
||||
window_sizes = WINDOW_SIZE.get("window_sizes", None)
|
||||
if window_sizes is None or not isinstance(window_sizes, list) or len(window_sizes) == 0:
|
||||
if (
|
||||
window_sizes is None
|
||||
or not isinstance(window_sizes, list)
|
||||
or len(window_sizes) == 0
|
||||
):
|
||||
window_sizes = [50, 80, 100, 120]
|
||||
huge_volume_main = HugeVolumeMain(threshold)
|
||||
for window_size in window_sizes:
|
||||
|
|
@ -337,4 +381,12 @@ if __name__ == "__main__":
|
|||
# batch_initial_detect_volume_spike(threshold=2.0)
|
||||
# batch_update_volume_spike(threshold=2.0)
|
||||
huge_volume_main = HugeVolumeMain(threshold=2.0)
|
||||
huge_volume_main.batch_next_periods_rise_or_fall(output_excel=True)
|
||||
# huge_volume_main.batch_next_periods_rise_or_fall(output_excel=True)
|
||||
data_file_path = "./output/huge_volume_statistics/next_periods_rise_or_fall_stat_20250731200304.xlsx"
|
||||
sheet_name = "next_periods_statistics"
|
||||
output_folder = "./output/huge_volume_statistics/"
|
||||
huge_volume_main.plot_huge_volume_data(
|
||||
data_file_path=data_file_path,
|
||||
sheet_name=sheet_name,
|
||||
output_folder=output_folder,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@ requests>=2.25.0
|
|||
sqlalchemy >= 2.0.41
|
||||
pymysql >= 1.1.1
|
||||
wechatpy >= 1.8.18
|
||||
seaborn >= 0.13.2
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
select * from crypto_market_data
|
||||
WHERE symbol='XCH-USDT-SWAP' and bar='5m' #and date_time > '2025-07-01'
|
||||
WHERE symbol='XCH-USDT' and bar='5m' #and date_time > '2025-07-01'
|
||||
order by timestamp desc;
|
||||
|
||||
delete FROM crypto_market_data where symbol != 'XCH-USDT';
|
||||
|
||||
select * from crypto_trade_data
|
||||
where symbol='BTC-USDT'
|
||||
where symbol='XCH-USDT'
|
||||
order by ts desc;
|
||||
|
||||
select count(1) from crypto_trade_data;
|
||||
|
||||
select * from crypto_huge_volume
|
||||
WHERE symbol='XCH-USDT' and bar='5m' and date_time > '2025-07-26'
|
||||
order by timestamp desc;
|
||||
WHERE symbol='XCH-USDT' and bar='5m' and window_size = 80 and date_time > '2025-07-26'
|
||||
order by timestamp;
|
||||
|
||||
select * from crypto_huge_volume
|
||||
WHERE symbol='XCH-USDT-SWAP' and bar='5m' and date_time > '2025-07-26'
|
||||
|
|
|
|||
Loading…
Reference in New Issue