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 import HugeVolume
|
||||||
|
from core.biz.huge_volume_chart import HugeVolumeChart
|
||||||
from core.db.db_market_data import DBMarketData
|
from core.db.db_market_data import DBMarketData
|
||||||
from core.db.db_huge_volume_data import DBHugeVolumeData
|
from core.db.db_huge_volume_data import DBHugeVolumeData
|
||||||
from core.utils import timestamp_to_datetime, transform_date_time_to_timestamp
|
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/"
|
self.output_folder = "./output/huge_volume_statistics/"
|
||||||
os.makedirs(self.output_folder, exist_ok=True)
|
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 symbol in self.market_data_main.symbols:
|
||||||
for bar in self.market_data_main.bars:
|
for bar in self.market_data_main.bars:
|
||||||
if start is None:
|
if start is None:
|
||||||
|
|
@ -43,7 +46,12 @@ class HugeVolumeMain:
|
||||||
"initial_date", "2025-05-01 00:00:00"
|
"initial_date", "2025-05-01 00:00:00"
|
||||||
)
|
)
|
||||||
data = self.detect_volume_spike(
|
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:
|
if data is not None and len(data) > 0:
|
||||||
logging.info(f"此次初始化巨量交易数据: {len(data)}条")
|
logging.info(f"此次初始化巨量交易数据: {len(data)}条")
|
||||||
|
|
@ -66,16 +74,22 @@ class HugeVolumeMain:
|
||||||
)
|
)
|
||||||
if end is None:
|
if end is None:
|
||||||
end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
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(
|
data = self.db_market_data.query_market_data_by_symbol_bar(
|
||||||
symbol, bar, start, end
|
symbol, bar, start, end
|
||||||
)
|
)
|
||||||
if data is None:
|
if data is None:
|
||||||
logging.warning(f"获取行情数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
logging.warning(
|
||||||
|
f"获取行情数据失败: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
logging.warning(f"获取行情数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}")
|
logging.warning(
|
||||||
|
f"获取行情数据为空: {symbol} {bar} 窗口大小: {window_size} 从 {start} 到 {end}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if isinstance(data, list):
|
if isinstance(data, list):
|
||||||
|
|
@ -93,10 +107,8 @@ class HugeVolumeMain:
|
||||||
if data is not None:
|
if data is not None:
|
||||||
if is_update:
|
if is_update:
|
||||||
for index, row in data.iterrows():
|
for index, row in data.iterrows():
|
||||||
exist_huge_volume_data = (
|
exist_huge_volume_data = self.db_huge_volume_data.query_data_by_symbol_bar_window_size_timestamp(
|
||||||
self.db_huge_volume_data.query_data_by_symbol_bar_window_size_timestamp(
|
symbol, bar, window_size, row["timestamp"]
|
||||||
symbol, bar, window_size, row["timestamp"]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if exist_huge_volume_data is not None:
|
if exist_huge_volume_data is not None:
|
||||||
# remove the exist_huge_volume_data from data
|
# remove the exist_huge_volume_data from data
|
||||||
|
|
@ -152,7 +164,9 @@ class HugeVolumeMain:
|
||||||
else:
|
else:
|
||||||
logging.info(f"此次更新巨量交易数据为空")
|
logging.info(f"此次更新巨量交易数据为空")
|
||||||
except Exception as e:
|
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):
|
def get_seconds_by_bar(self, bar: str):
|
||||||
"""
|
"""
|
||||||
|
|
@ -214,26 +228,29 @@ class HugeVolumeMain:
|
||||||
if end is None:
|
if end is None:
|
||||||
end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
periods_text = ", ".join([str(period) for period in periods])
|
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 = (
|
volume_statistics_data = (
|
||||||
self.db_huge_volume_data.query_huge_volume_data_by_symbol_bar_window_size(
|
self.db_huge_volume_data.query_huge_volume_data_by_symbol_bar_window_size(
|
||||||
symbol, bar, window_size, start, end
|
symbol, bar, window_size, start, end
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if volume_statistics_data is None or len(volume_statistics_data) == 0:
|
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
|
return None
|
||||||
else:
|
else:
|
||||||
if isinstance(volume_statistics_data, list):
|
if isinstance(volume_statistics_data, list):
|
||||||
volume_statistics_data = pd.DataFrame(volume_statistics_data)
|
volume_statistics_data = pd.DataFrame(volume_statistics_data)
|
||||||
elif isinstance(volume_statistics_data, dict):
|
elif isinstance(volume_statistics_data, dict):
|
||||||
volume_statistics_data = pd.DataFrame([volume_statistics_data])
|
volume_statistics_data = pd.DataFrame([volume_statistics_data])
|
||||||
if (
|
if volume_statistics_data is not None and len(volume_statistics_data) > 0:
|
||||||
volume_statistics_data is not None
|
|
||||||
and len(volume_statistics_data) > 0
|
|
||||||
):
|
|
||||||
# 根据timestamp排序
|
# 根据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["window_size"] = window_size
|
||||||
volume_statistics_data = volume_statistics_data[
|
volume_statistics_data = volume_statistics_data[
|
||||||
[
|
[
|
||||||
|
|
@ -279,7 +296,11 @@ class HugeVolumeMain:
|
||||||
huge_volume_data_list = []
|
huge_volume_data_list = []
|
||||||
result_data_list = []
|
result_data_list = []
|
||||||
window_size_list = WINDOW_SIZE.get("window_sizes", None)
|
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]
|
window_size_list = [50, 80, 100, 120]
|
||||||
|
|
||||||
for symbol in self.market_data_main.symbols:
|
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_huge_volume_data = total_huge_volume_data.reset_index(drop=True)
|
||||||
total_result_data = total_result_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")
|
current_date = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
file_name = (
|
file_name = f"next_periods_rise_or_fall_{current_date}.xlsx"
|
||||||
f"next_periods_rise_or_fall_{current_date}.xlsx"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
with pd.ExcelWriter(
|
with pd.ExcelWriter(
|
||||||
os.path.join(self.output_folder, file_name)
|
os.path.join(self.output_folder, file_name)
|
||||||
) as writer:
|
) 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(
|
total_result_data.to_excel(
|
||||||
writer, sheet_name="next_periods_statistics", index=False
|
writer, sheet_name="next_periods_statistics", index=False
|
||||||
)
|
)
|
||||||
|
|
@ -311,10 +332,29 @@ class HugeVolumeMain:
|
||||||
logging.error(f"导出Excel文件失败: {e}")
|
logging.error(f"导出Excel文件失败: {e}")
|
||||||
return total_huge_volume_data, total_result_data
|
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):
|
def batch_initial_detect_volume_spike(threshold: float = 2.0):
|
||||||
window_sizes = WINDOW_SIZE.get("window_sizes", None)
|
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]
|
window_sizes = [50, 80, 100, 120]
|
||||||
huge_volume_main = HugeVolumeMain(threshold)
|
huge_volume_main = HugeVolumeMain(threshold)
|
||||||
for window_size in window_sizes:
|
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):
|
def batch_update_volume_spike(threshold: float = 2.0):
|
||||||
window_sizes = WINDOW_SIZE.get("window_sizes", None)
|
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]
|
window_sizes = [50, 80, 100, 120]
|
||||||
huge_volume_main = HugeVolumeMain(threshold)
|
huge_volume_main = HugeVolumeMain(threshold)
|
||||||
for window_size in window_sizes:
|
for window_size in window_sizes:
|
||||||
|
|
@ -337,4 +381,12 @@ if __name__ == "__main__":
|
||||||
# batch_initial_detect_volume_spike(threshold=2.0)
|
# batch_initial_detect_volume_spike(threshold=2.0)
|
||||||
# batch_update_volume_spike(threshold=2.0)
|
# batch_update_volume_spike(threshold=2.0)
|
||||||
huge_volume_main = HugeVolumeMain(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
|
sqlalchemy >= 2.0.41
|
||||||
pymysql >= 1.1.1
|
pymysql >= 1.1.1
|
||||||
wechatpy >= 1.8.18
|
wechatpy >= 1.8.18
|
||||||
|
seaborn >= 0.13.2
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
select * from crypto_market_data
|
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;
|
order by timestamp desc;
|
||||||
|
|
||||||
delete FROM crypto_market_data where symbol != 'XCH-USDT';
|
delete FROM crypto_market_data where symbol != 'XCH-USDT';
|
||||||
|
|
||||||
select * from crypto_trade_data
|
select * from crypto_trade_data
|
||||||
where symbol='BTC-USDT'
|
where symbol='XCH-USDT'
|
||||||
order by ts desc;
|
order by ts desc;
|
||||||
|
|
||||||
select count(1) from crypto_trade_data;
|
select count(1) from crypto_trade_data;
|
||||||
|
|
||||||
select * from crypto_huge_volume
|
select * from crypto_huge_volume
|
||||||
WHERE symbol='XCH-USDT' and bar='5m' and date_time > '2025-07-26'
|
WHERE symbol='XCH-USDT' and bar='5m' and window_size = 80 and date_time > '2025-07-26'
|
||||||
order by timestamp desc;
|
order by timestamp;
|
||||||
|
|
||||||
select * from crypto_huge_volume
|
select * from crypto_huge_volume
|
||||||
WHERE symbol='XCH-USDT-SWAP' and bar='5m' and date_time > '2025-07-26'
|
WHERE symbol='XCH-USDT-SWAP' and bar='5m' and date_time > '2025-07-26'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue