diff --git a/core/biz/__pycache__/huge_volume_chart.cpython-312.pyc b/core/biz/__pycache__/huge_volume_chart.cpython-312.pyc new file mode 100644 index 0000000..038ea1f Binary files /dev/null and b/core/biz/__pycache__/huge_volume_chart.cpython-312.pyc differ diff --git a/core/biz/huge_volume_chart.py b/core/biz/huge_volume_chart.py new file mode 100644 index 0000000..fcab219 --- /dev/null +++ b/core/biz/huge_volume_chart.py @@ -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}") diff --git a/huge_volume_main.py b/huge_volume_main.py index 6479ee5..cefa1ec 100644 --- a/huge_volume_main.py +++ b/huge_volume_main.py @@ -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, + ) diff --git a/requirements.txt b/requirements.txt index 9b4c77c..4e58085 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ requests>=2.25.0 sqlalchemy >= 2.0.41 pymysql >= 1.1.1 wechatpy >= 1.8.18 +seaborn >= 0.13.2 \ No newline at end of file diff --git a/sql/query/sql_playground.sql b/sql/query/sql_playground.sql index 558dfac..f57bcc7 100644 --- a/sql/query/sql_playground.sql +++ b/sql/query/sql_playground.sql @@ -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'