From 5700e8e7e1db090537a4f6270bcf019f45be468e Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Sun, 24 Aug 2025 01:44:33 +0800 Subject: [PATCH] optimize strategy statistics --- .../ma_break_statistics.cpython-312.pyc | Bin 21727 -> 24055 bytes core/trade/ma_break_statistics.py | 135 +++++++++++++++--- trade_ma_strategy_main.py | 22 ++- 3 files changed, 137 insertions(+), 20 deletions(-) diff --git a/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc b/core/trade/__pycache__/ma_break_statistics.cpython-312.pyc index 22ba36c6a542aa3ec4b24828509a7813e21a43e5..e04149372c9c4ae873a625a83ef1afae7b1c54cd 100644 GIT binary patch delta 5643 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 NsM*E`3{rX~{15#k#7zJI delta 3624 zcmZ`+drX_x75}aupWnt{8$*o4!w_SPd4xwO0TK$7B&4Y*NeT(6Yx5amV{CG5N}x7L zq)BTlw!&_z)v>HRWp`k0K1<)+LyE-qO>DK4mo+c|DVg5@!5S{uw`lMM#7oH`8V zlT!wxStDqN9?a5#t6(8$EP5eBFbJ7KmS7Z2i{=GI$`83@G?B{&m7M>3F zS>PY_pi#u-LJqSoA?Hk1oigUw%C(eqAIdI@xzd8TGQG5|swnAnE1AtK$V#ZcKDeJFq`ZMGFaCD`Ihx+&Y^DvJ4WM6uvaMM52uq$82{ zvP_cTE>_q#bY^{Ay|AfEXi^(%E{tm~a&K{+pK1Mh!$3qV81Ah zj`=9WFn*eNty3t2+_RRHE-Mm`Cu&yrOzDsz(?kZ|9wt zWl&SH*Y-?r8$C;BxwWFMm8VzsB##|W7WBU?yP>q*R;pjqyrP+Dzh!c+nw(4RH9ZTjP2dlh?tV&l+rAxIpl?`jfJCe#m@*i8-8M&$pz5LXg#rpch?8KLx zOxJ~Gq_+*(uWM#C7ee#(^QB9LOKnSa$p&9?U^MAFmCVC84AC`*>z1Q&)zNsR{#wn7 z?z-b>(ono+bgUX{)=chOrrK3gZL)6vP1Awm=wCSf%PFd6+3gOo%bt-TzePAa*0CaLc#u1E1KoACjs-4#QDeN8blbw#gsD4ak z(M$?tZ@q<|q-4@gKGbHD=29Jcb-Anbuc&Zq;|63@pZ;vM$nSa5Qg#t>S)Y@{M%Jd zTFOi4yENspkeAB!3Im&9fi|JbT+B)zzF;nRtfkY9O*B>dWQH(?36nJKEv{kQv}tD| z-mts)0mNq>d2I&aaouvznjF>Z{a>8Svh zphrH=3)@-R63X%!Q&VOo&sSRe3fNdf$Aye(DJx=3N12^5J!Qp=Wl&ban1Qk#jAc^h zU@VKWQee}c#5s0hX zd&!;ZdabC61xEbQn0I9KFnJ@#juwb(=dby9Y1;T#6MPrdd@hip+7fh(?5jN?ahl1;wT>!w_*pkljC*l75FTQ!0RJ33 zX`&Gv^StN{jrpS(L+~KUtTWl30VO{e2!+C4Q5lFq9~qB|^1)DqHvGMH1$iNA;mpQ$ zFy}#%9WeZWoU7Z@HU#uTfcF930k{J28-U9IO91at5LF}I0l^~#1_woDiu%3yDv{RP zRcEO_noWWHt;0l~saK+5(pz7mfXUHsW#mWoeG+Zf^4NAn=UE%X%PH`Ty86D zU}>9S{C_e|E#hfE-Tw}(V3r7?o>Y3knwr2V4YYNh=#p*490iBbhDJH z0)mv4?jvBopdjk$Wh(??fk>E^X&{y=>JKLd*C+lvz&`-4lFJ>rmN&tXB7!}k0KK2z zqN7I(dzXCMVMngzf~Wqa-q8c|jsn1~eb^}x6$6pb*hn~v7eG1>ut?n9RtxOEsCW`y zB9GvdiRiS5Ch!-;(|sso19UR_fKum#~TMJh{_24jW#-QIwb1oQ58Jn5j^aRM24}JNc!C9JgMr-$ta>T zv7Le_8Ta8q@?2jNnj+`>$|ZNu^2dErM4Qhi$C`JsxARr-4L=R=FaW%~souf&_;G-? zjk_R8P!MJ8&72~Q$Ln`@B6-gIzy=4;`_D>>fST;OxP5De&sr4S258{?uQN0;HvX8w!)Y{xZ877NUsyy}k$z zV)*XaIOOg>RHiBN1UN 0 + and market_data_pct_chg is not None + ): ma_break_market_data_list.append(ma_break_market_data) + logger.info( + f"{symbol} {bar} 的市场价格变化, {market_data_pct_chg.get('pct_chg', 0)}%" + ) + market_data_pct_chg_list.append(market_data_pct_chg) if len(ma_break_market_data_list) > 0: ma_break_market_data = pd.concat(ma_break_market_data_list) + market_data_pct_chg_df = pd.DataFrame(market_data_pct_chg_list) # 依据symbol和bar分组,统计每个symbol和bar的pct_chg的max, min, mean, std, median, count pct_chg_df = ( ma_break_market_data.groupby(["symbol", "bar"])["pct_chg"] @@ -92,6 +110,66 @@ class MaBreakStatistics: ) .reset_index() ) + pct_chg_df["strategy_name"] = strategy_name + pct_chg_df["pct_chg_total"] = 0 + pct_chg_df["market_pct_chg"] = 0 + # 将pct_chg_total与market_pct_chg的值类型转换为float + pct_chg_df["pct_chg_total"] = pct_chg_df["pct_chg_total"].astype(float) + pct_chg_df["market_pct_chg"] = pct_chg_df["market_pct_chg"].astype(float) + # 统计pct_chg_total + # 算法要求,ma_break_market_data,然后pct_chg/100 + 1 + ma_break_market_data["pct_chg_total"] = ( + ma_break_market_data["pct_chg"] / 100 + 1 + ) + # 遍历symbol和bar,按照end_timestamp排序,计算pct_chg_total的值,然后相乘 + for symbol in pct_chg_df["symbol"].unique(): + for bar in pct_chg_df["bar"].unique(): + symbol_bar_data = ma_break_market_data[ + (ma_break_market_data["symbol"] == symbol) + & (ma_break_market_data["bar"] == bar) + ] + if len(symbol_bar_data) > 0: + symbol_bar_data.sort_values( + by="end_timestamp", ascending=True, inplace=True + ) + symbol_bar_data.reset_index(drop=True, inplace=True) + symbol_bar_data["pct_chg_total"] = symbol_bar_data[ + "pct_chg_total" + ].cumprod() + last_pct_chg_total = symbol_bar_data["pct_chg_total"].iloc[-1] + last_pct_chg_total = (last_pct_chg_total - 1) * 100 + pct_chg_df.loc[ + (pct_chg_df["symbol"] == symbol) + & (pct_chg_df["bar"] == bar), + "pct_chg_total", + ] = last_pct_chg_total + market_pct_chg = market_data_pct_chg_df.loc[ + (market_data_pct_chg_df["symbol"] == symbol) + & (market_data_pct_chg_df["bar"] == bar), + "pct_chg", + ].values[0] + pct_chg_df.loc[ + (pct_chg_df["symbol"] == symbol) + & (pct_chg_df["bar"] == bar), + "market_pct_chg", + ] = market_pct_chg + + pct_chg_df = pct_chg_df[ + [ + "strategy_name", + "symbol", + "bar", + "market_pct_chg", + "pct_chg_total", + "pct_chg_sum", + "pct_chg_max", + "pct_chg_min", + "pct_chg_mean", + "pct_chg_std", + "pct_chg_median", + "pct_chg_count", + ] + ] # 依据symbol和bar分组,统计每个symbol和bar的interval_minutes的max, min, mean, std, median, count interval_minutes_df = ( ma_break_market_data.groupby(["symbol", "bar"])["interval_minutes"] @@ -132,9 +210,10 @@ class MaBreakStatistics: chart_dict = self.draw_pct_chg_mean_chart(pct_chg_df, strategy_name) self.output_chart_to_excel(output_file_path, chart_dict) + return pct_chg_df else: return None - + def get_strategy_info(self, strategy_name: str = "全均线策略"): strategy_config = self.main_strategy.get(strategy_name, None) if strategy_config is None: @@ -179,7 +258,7 @@ class MaBreakStatistics: ) if market_data is None or len(market_data) == 0: logger.warning(f"获取{symbol} {bar} 数据失败") - return + return None, None else: market_data = pd.DataFrame(market_data) market_data.sort_values(by="timestamp", ascending=True, inplace=True) @@ -291,10 +370,20 @@ class MaBreakStatistics: if len(ma_break_market_data_pair_list) > 0: ma_break_market_data = pd.DataFrame(ma_break_market_data_pair_list) - logger.info(f"获取{symbol} {bar} 的买卖记录明细成功, 买卖次数: {len(ma_break_market_data)}") - return ma_break_market_data + logger.info( + f"获取{symbol} {bar} 的买卖记录明细成功, 买卖次数: {len(ma_break_market_data)}" + ) + # 将market_data(最后一条数据的close - 第一条数据的open) / 第一条数据的open * 100 + pct_chg = ( + (market_data["close"].iloc[-1] - market_data["open"].iloc[0]) + / market_data["open"].iloc[0] + * 100 + ) + pct_chg = round(pct_chg, 4) + market_data_pct_chg = {"symbol": symbol, "bar": bar, "pct_chg": pct_chg} + return ma_break_market_data, market_data_pct_chg else: - return None + return None, None def fit_strategy( self, @@ -438,10 +527,7 @@ class MaBreakStatistics: plt.rcParams["font.size"] = 11 # 设置字体大小 plt.rcParams["axes.unicode_minus"] = False # 解决负号显示问题 chart_dict = {} - column_name_dict = { - "pct_chg_sum": "涨跌总和", - "pct_chg_mean": "涨跌均值", - } + column_name_dict = {"pct_chg_total": "涨跌总和", "pct_chg_mean": "涨跌均值"} for column_name, column_name_text in column_name_dict.items(): for bar in data["bar"].unique(): bar_data = data[data["bar"] == bar].copy() # 一次筛选即可 @@ -453,20 +539,31 @@ class MaBreakStatistics: bar_data.reset_index(drop=True, inplace=True) plt.figure(figsize=(10, 6)) - ax = sns.barplot(x="symbol", y=column_name_text, data=bar_data, palette="Blues_d") + ax = sns.barplot( + x="symbol", y=column_name_text, data=bar_data, palette="Blues_d" + ) plt.title(f"{bar}趋势{column_name_text}(%)") plt.xlabel("symbol") plt.ylabel(column_name_text) plt.xticks(rotation=45, ha="right") - + # 在柱状图上添加数值标签 for i, v in enumerate(bar_data[column_name_text]): - ax.text(i, v, f'{v:.3f}', ha='center', va='bottom', fontsize=10, fontweight='bold') - + ax.text( + i, + v, + f"{v:.3f}", + ha="center", + va="bottom", + fontsize=10, + fontweight="bold", + ) + plt.tight_layout() save_path = os.path.join( - self.stats_chart_dir, f"{bar}_ma_break_{column_name}_{strategy_name}.png" + self.stats_chart_dir, + f"{bar}_ma_break_{column_name}_{strategy_name}.png", ) plt.savefig(save_path, dpi=150) plt.close() diff --git a/trade_ma_strategy_main.py b/trade_ma_strategy_main.py index 430d5a2..01174a6 100644 --- a/trade_ma_strategy_main.py +++ b/trade_ma_strategy_main.py @@ -34,8 +34,28 @@ class TradeMaStrategyMain: """ logger.info("开始批量计算MA突破统计") strategy_dict = self.ma_break_statistics.main_strategy + pct_chg_df_list = [] for strategy_name, strategy_info in strategy_dict.items(): - self.ma_break_statistics.batch_statistics(strategy_name=strategy_name) + pct_chg_df = self.ma_break_statistics.batch_statistics(strategy_name=strategy_name) + pct_chg_df_list.append(pct_chg_df) + pct_chg_df = pd.concat(pct_chg_df_list) + + def statistics_pct_chg(self, pct_chg_df: pd.DataFrame): + """ + 1. 将各个symbol, 各个bar, 各个策略的pct_chg_total构建为新的数据结构,如: + symbol, bar, stratege_name_1, stratege_name_2, stratege_name_3, ... + stratege_name_1的值, 为该策略的pct_chg_total的值 + 2. 构建新的数据结构: symbol, bar, max_pct_chg_total_strategy_name, min_pct_chg_total_strategy_name + 如: BCT-USDT, 15m, 均线macd结合策略2, 全均线策略 + 3. 构建新的数据结构, bar, max_pct_chg_total_strategy_name, min_pct_chg_total_strategy_name + 如: 15m, 均线macd结合策略2, 全均线策略 + 4. 构建新的数据结构, symbol, max_pct_chg_total_strategy_name, min_pct_chg_total_strategy_name + 如: BCT-USDT, 均线macd结合策略2, 全均线策略 + """ + logger.info("开始统计pct_chg") + + + if __name__ == "__main__": trade_ma_strategy_main = TradeMaStrategyMain()