analyze_deep_mining.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 创业板50 T+1 高级算法深挖 - 第四层
  5. 探索: 动量细节、机器学习特征、市场情绪、资金流向、参数敏感度
  6. """
  7. import pandas as pd
  8. import numpy as np
  9. from datetime import datetime, timedelta
  10. import warnings
  11. import sys
  12. warnings.filterwarnings('ignore')
  13. # Redirect stdout
  14. from io import StringIO
  15. old_stdout = sys.stdout
  16. sys.stdout = StringIO()
  17. from cyb50_30min_dual_direction import ConfigManager, IntradayDataFetcher, DualDirectionSignalGenerator, DualDirectionExecutor
  18. from t1_converter import simulate_t1_trades
  19. def load_local_data(csv_file='cyb50_30min_2023_to_20260325.csv'):
  20. df = pd.read_csv(csv_file)
  21. df['DateTime'] = pd.to_datetime(df['DateTime'])
  22. df.set_index('DateTime', inplace=True)
  23. df.sort_index(inplace=True)
  24. if 'Open' not in df.columns and 'o' in df.columns:
  25. df.rename(columns={'o':'Open','h':'High','l':'Low','c':'Close','v':'Volume','a':'Amount'}, inplace=True)
  26. for col in ['Open', 'High', 'Low', 'Close', 'Volume']:
  27. if col in df.columns:
  28. df[col] = pd.to_numeric(df[col], errors='coerce')
  29. df['Returns'] = df['Close'].pct_change()
  30. df['High_Low_Pct'] = (df['High'] - df['Low']) / df['Close'].shift(1)
  31. df['Close_Open_Pct'] = (df['Close'] - df['Open']) / df['Open']
  32. df.ffill(inplace=True)
  33. df.dropna(inplace=True)
  34. return df
  35. # 运行回测
  36. initial_capital = 1000000
  37. raw_data = load_local_data('cyb50_30min_2023_to_20260325.csv')
  38. config_manager = ConfigManager('config.json')
  39. fetcher = IntradayDataFetcher(config_manager)
  40. data_with_indicators = fetcher.calculate_intraday_indicators(raw_data)
  41. signal_generator = DualDirectionSignalGenerator()
  42. signals_df = signal_generator.generate_dual_direction_signals(data_with_indicators)
  43. executor = DualDirectionExecutor(initial_capital=initial_capital)
  44. results_df, trades_df = executor.execute_dual_direction_trades(signals_df)
  45. long_trades = trades_df[trades_df['交易方向'] == '做多'].copy()
  46. t1_trades = simulate_t1_trades(data_with_indicators, long_trades, initial_capital)
  47. # Restore stdout
  48. sys.stdout = old_stdout
  49. print('='*80)
  50. print('创业板50 T+1 高级算法深挖')
  51. print('='*80)
  52. # 准备数据
  53. t1_trades['开仓时间'] = pd.to_datetime(t1_trades['开仓时间'])
  54. t1_trades['平仓时间'] = pd.to_datetime(t1_trades['平仓时间'])
  55. t1_trades['是否盈利'] = t1_trades['盈亏金额'] > 0
  56. t1_trades['开仓年份'] = t1_trades['开仓时间'].dt.year
  57. t1_trades['开仓小时'] = t1_trades['开仓时间'].dt.hour
  58. # ========== 深挖1: 动量指标精细化分析 ==========
  59. print('\n' + '='*80)
  60. print('【深挖1】动量指标精细化分析')
  61. print('='*80)
  62. # 计算更详细的动量指标
  63. def calculate_advanced_momentum(df):
  64. """计算高级动量指标"""
  65. # RSI多种周期
  66. for period in [6, 14, 21]:
  67. delta = df['Close'].diff()
  68. gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
  69. loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
  70. rs = gain / loss
  71. df[f'RSI_{period}'] = 100 - (100 / (1 + rs))
  72. # MACD多种参数
  73. for fast, slow, signal in [(12, 26, 9), (8, 21, 5), (5, 35, 5)]:
  74. exp1 = df['Close'].ewm(span=fast, adjust=False).mean()
  75. exp2 = df['Close'].ewm(span=slow, adjust=False).mean()
  76. df[f'MACD_{fast}_{slow}'] = exp1 - exp2
  77. df[f'MACD_Signal_{fast}_{slow}'] = df[f'MACD_{fast}_{slow}'].ewm(span=signal, adjust=False).mean()
  78. df[f'MACD_Hist_{fast}_{slow}'] = df[f'MACD_{fast}_{slow}'] - df[f'MACD_Signal_{fast}_{slow}']
  79. # 价格动量 (不同周期)
  80. for period in [3, 5, 10, 20]:
  81. df[f'Momentum_{period}'] = (df['Close'] / df['Close'].shift(period) - 1) * 100
  82. # 成交量动量
  83. df['Volume_MA5'] = df['Volume'].rolling(5).mean()
  84. df['Volume_MA20'] = df['Volume'].rolling(20).mean()
  85. df['Volume_Ratio'] = df['Volume'] / df['Volume_MA20']
  86. return df
  87. data_advanced = calculate_advanced_momentum(data_with_indicators.copy())
  88. # 合并高级动量数据
  89. for idx, row in t1_trades.iterrows():
  90. try:
  91. mask = data_advanced.index <= row['开仓时间']
  92. if mask.any():
  93. current_data = data_advanced.loc[data_advanced.index[mask][-1]]
  94. t1_trades.loc[idx, 'RSI_6'] = current_data.get('RSI_6', 50)
  95. t1_trades.loc[idx, 'RSI_14'] = current_data.get('RSI_14', 50)
  96. t1_trades.loc[idx, 'RSI_21'] = current_data.get('RSI_21', 50)
  97. t1_trades.loc[idx, 'Momentum_3'] = current_data.get('Momentum_3', 0)
  98. t1_trades.loc[idx, 'Momentum_5'] = current_data.get('Momentum_5', 0)
  99. t1_trades.loc[idx, 'Momentum_10'] = current_data.get('Momentum_10', 0)
  100. t1_trades.loc[idx, 'Volume_Ratio'] = current_data.get('Volume_Ratio', 1)
  101. except:
  102. pass
  103. # RSI精细化分析
  104. print('\nRSI_14精细化分析:')
  105. t1_trades['RSI_Level'] = pd.cut(t1_trades['RSI_14'],
  106. bins=[0, 30, 40, 50, 60, 70, 100],
  107. labels=['超卖(<30)', '偏弱(30-40)', '中性偏弱(40-50)', '中性偏强(50-60)', '偏强(60-70)', '超买(>70)'])
  108. rsi_analysis = t1_trades.groupby('RSI_Level', observed=False).agg({
  109. '盈亏金额': ['count', 'sum', 'mean'],
  110. '是否盈利': 'sum'
  111. }).round(2)
  112. rsi_analysis.columns = ['交易次数', '总盈亏', '平均盈亏', '盈利次数']
  113. rsi_analysis['胜率'] = (rsi_analysis['盈利次数'] / rsi_analysis['交易次数'] * 100).round(1)
  114. print(rsi_analysis.to_string())
  115. # 最佳动量组合搜索
  116. print('\n最佳动量指标组合搜索:')
  117. momentum_results = []
  118. for rsi_low in [30, 35, 40, 45]:
  119. for rsi_high in [55, 60, 65, 70]:
  120. for mom_min in [-2, -1, 0, 1]:
  121. mask = (t1_trades['RSI_14'] >= rsi_low) & \
  122. (t1_trades['RSI_14'] <= rsi_high) & \
  123. (t1_trades['Momentum_5'] >= mom_min) & \
  124. (t1_trades['T+1调整'] != '是(T0→T1)')
  125. filtered = t1_trades[mask]
  126. if len(filtered) >= 10:
  127. win_rate = (filtered['盈亏金额'] > 0).mean() * 100
  128. total_pnl = filtered['盈亏金额'].sum()
  129. avg_pnl = filtered['盈亏金额'].mean()
  130. momentum_results.append({
  131. 'RSI范围': f'{rsi_low}-{rsi_high}',
  132. '动量≥': mom_min,
  133. '交易数': len(filtered),
  134. '胜率': win_rate,
  135. '总盈亏': total_pnl,
  136. '平均盈亏': avg_pnl
  137. })
  138. mom_df = pd.DataFrame(momentum_results)
  139. if len(mom_df) > 0:
  140. print('\n动量组合TOP10 (按总盈亏):')
  141. print(mom_df.nlargest(10, '总盈亏').to_string(index=False))
  142. # ========== 深挖2: 成交量与价格关系 ==========
  143. print('\n' + '='*80)
  144. print('【深挖2】量价关系分析')
  145. print('='*80)
  146. # 量价关系分类
  147. def classify_price_volume(row):
  148. """分类量价关系"""
  149. price_change = row.get('Momentum_3', 0)
  150. volume_ratio = row.get('Volume_Ratio', 1)
  151. if price_change > 1 and volume_ratio > 1.2:
  152. return '价涨量增(健康)'
  153. elif price_change > 1 and volume_ratio < 0.8:
  154. return '价涨量缩(背离)'
  155. elif price_change < -1 and volume_ratio > 1.2:
  156. return '价跌量增(恐慌)'
  157. elif price_change < -1 and volume_ratio < 0.8:
  158. return '价跌量缩(疲软)'
  159. else:
  160. return '震荡整理'
  161. t1_trades['Price_Volume_Pattern'] = t1_trades.apply(classify_price_volume, axis=1)
  162. pv_analysis = t1_trades.groupby('Price_Volume_Pattern').agg({
  163. '盈亏金额': ['count', 'sum', 'mean'],
  164. '是否盈利': 'sum'
  165. }).round(2)
  166. pv_analysis.columns = ['交易次数', '总盈亏', '平均盈亏', '盈利次数']
  167. pv_analysis['胜率'] = (pv_analysis['盈利次数'] / pv_analysis['交易次数'] * 100).round(1)
  168. print('\n量价关系分析:')
  169. print(pv_analysis.to_string())
  170. # ========== 深挖3: 市场环境分类 ==========
  171. print('\n' + '='*80)
  172. print('【深挖3】市场环境自适应策略')
  173. print('='*80)
  174. # 定义市场环境
  175. def classify_market_environment(row):
  176. """分类市场环境"""
  177. trend = row.get('Trend_Score', 0)
  178. vol = row.get('Vol_Regime', '正常')
  179. rsi = row.get('RSI_14', 50)
  180. if trend >= 2 and vol != '高波动' and rsi > 50:
  181. return '牛市趋势'
  182. elif trend <= -2 and vol != '高波动' and rsi < 50:
  183. return '熊市趋势'
  184. elif vol == '高波动':
  185. return '高波动震荡'
  186. elif abs(trend) <= 1:
  187. return '横盘震荡'
  188. else:
  189. return '趋势转折'
  190. t1_trades['Market_Environment'] = t1_trades.apply(classify_market_environment, axis=1)
  191. env_analysis = t1_trades.groupby('Market_Environment').agg({
  192. '盈亏金额': ['count', 'sum', 'mean'],
  193. '是否盈利': 'sum'
  194. }).round(2)
  195. env_analysis.columns = ['交易次数', '总盈亏', '平均盈亏', '盈利次数']
  196. env_analysis['胜率'] = (env_analysis['盈利次数'] / env_analysis['交易次数'] * 100).round(1)
  197. print('\n不同市场环境表现:')
  198. print(env_analysis.to_string())
  199. # 各环境下的最优策略
  200. print('\n各市场环境下的最优过滤条件:')
  201. for env in t1_trades['Market_Environment'].unique():
  202. if pd.isna(env):
  203. continue
  204. env_data = t1_trades[t1_trades['Market_Environment'] == env]
  205. # 测试不同条件
  206. best_condition = None
  207. best_pnl = -999999
  208. # 条件1: 仅非T+1
  209. c1 = env_data[env_data['T+1调整'] != '是(T0→T1)']
  210. if len(c1) > 5 and c1['盈亏金额'].sum() > best_pnl:
  211. best_pnl = c1['盈亏金额'].sum()
  212. best_condition = ('非T+1', len(c1), (c1['盈亏金额']>0).mean()*100, c1['盈亏金额'].sum())
  213. # 条件2: RSI过滤
  214. c2 = env_data[(env_data['RSI_14'] >= 40) & (env_data['RSI_14'] <= 60)]
  215. if len(c2) > 5 and c2['盈亏金额'].sum() > best_pnl:
  216. best_pnl = c2['盈亏金额'].sum()
  217. best_condition = ('RSI 40-60', len(c2), (c2['盈亏金额']>0).mean()*100, c2['盈亏金额'].sum())
  218. # 条件3: 动量过滤
  219. c3 = env_data[env_data['Momentum_5'] >= 0]
  220. if len(c3) > 5 and c3['盈亏金额'].sum() > best_pnl:
  221. best_pnl = c3['盈亏金额'].sum()
  222. best_condition = ('动量≥0', len(c3), (c3['盈亏金额']>0).mean()*100, c3['盈亏金额'].sum())
  223. if best_condition:
  224. print(f' {env}: {best_condition[0]} -> {best_condition[1]}笔, 胜率{best_condition[2]:.1f}%, 盈亏{best_condition[3]:+,.0f}元')
  225. # ========== 深挖4: 参数敏感度分析 ==========
  226. print('\n' + '='*80)
  227. print('【深挖4】参数敏感度与稳健性分析')
  228. print('='*80)
  229. # 止损止盈参数敏感度
  230. print('\n止损止盈参数敏感度分析:')
  231. stop_loss_range = [0.5, 0.8, 1.0, 1.2, 1.5]
  232. take_profit_range = [1.0, 1.5, 2.0, 2.5, 3.0]
  233. sensitivity_results = []
  234. for sl in stop_loss_range:
  235. for tp in take_profit_range:
  236. # 模拟不同止损止盈下的盈亏
  237. simulated_pnl = []
  238. for idx, row in t1_trades.iterrows():
  239. actual_pnl_pct = row['盈亏金额'] / initial_capital * 100
  240. # 如果原交易是止损出局且亏损接近-0.8%
  241. if -1.0 < actual_pnl_pct < -0.6:
  242. # 假设放宽止损后可能盈利
  243. if row['Momentum_5'] > 0: # 有动量支撑
  244. simulated_pnl.append(abs(row['盈亏金额']) * 0.5) # 假设能挽回50%
  245. else:
  246. simulated_pnl.append(row['盈亏金额'] * (sl / 0.8))
  247. # 如果原交易是止盈出局
  248. elif actual_pnl_pct > 1.5:
  249. # 收紧止盈可能减少盈利
  250. simulated_pnl.append(row['盈亏金额'] * (tp / 2.0))
  251. else:
  252. simulated_pnl.append(row['盈亏金额'])
  253. total_pnl = sum(simulated_pnl)
  254. sensitivity_results.append({
  255. '止损': sl,
  256. '止盈': tp,
  257. '总盈亏': total_pnl,
  258. '原盈亏': t1_trades['盈亏金额'].sum()
  259. })
  260. sens_df = pd.DataFrame(sensitivity_results)
  261. print('\n止损止盈参数TOP10组合:')
  262. print(sens_df.nlargest(10, '总盈亏')[['止损', '止盈', '总盈亏']].to_string(index=False))
  263. # ========== 深挖5: 交易频率优化 ==========
  264. print('\n' + '='*80)
  265. print('【深挖5】交易频率与机会成本分析')
  266. print('='*80)
  267. # 分析交易密度
  268. trade_density = t1_trades.groupby(t1_trades['开仓时间'].dt.date).size()
  269. print(f'\n交易密度统计:')
  270. print(f' 平均每日交易: {trade_density.mean():.2f}笔')
  271. print(f' 最大单日交易: {trade_density.max()}笔')
  272. print(f' 有交易日占比: {(trade_density > 0).mean()*100:.1f}%')
  273. # 单日多笔交易的影响
  274. daily_pnl = t1_trades.groupby(t1_trades['开仓时间'].dt.date)['盈亏金额'].sum()
  275. daily_count = t1_trades.groupby(t1_trades['开仓时间'].dt.date).size()
  276. daily_analysis = pd.DataFrame({
  277. '交易次数': daily_count,
  278. '日盈亏': daily_pnl
  279. })
  280. print('\n单日交易次数与盈亏关系:')
  281. single_trade_days = daily_analysis[daily_analysis['交易次数'] == 1]
  282. multi_trade_days = daily_analysis[daily_analysis['交易次数'] > 1]
  283. print(f' 单日1笔: {len(single_trade_days)}天, 平均日盈亏{single_trade_days["日盈亏"].mean():+,.0f}元')
  284. print(f' 单日多笔: {len(multi_trade_days)}天, 平均日盈亏{multi_trade_days["日盈亏"].mean():+,.0f}元')
  285. # 最优每日交易限制
  286. print('\n不同每日交易上限的效果:')
  287. for max_daily in [1, 2, 3, 5]:
  288. # 模拟限制每日交易次数
  289. limited_trades = []
  290. for date, group in t1_trades.groupby(t1_trades['开仓时间'].dt.date):
  291. limited_trades.extend(group.head(max_daily).to_dict('records'))
  292. if limited_trades:
  293. limited_df = pd.DataFrame(limited_trades)
  294. total_pnl = limited_df['盈亏金额'].sum()
  295. win_rate = (limited_df['盈亏金额'] > 0).mean() * 100
  296. print(f' 每日最多{max_daily}笔: {len(limited_df)}笔, 胜率{win_rate:.1f}%, 总盈亏{total_pnl:+,.0f}元')
  297. # ========== 深挖6: 高级复合策略 ==========
  298. print('\n' + '='*80)
  299. print('【深挖6】高级复合策略 - 动态自适应')
  300. print('='*80)
  301. def advanced_filter(row):
  302. """高级复合过滤逻辑"""
  303. score = 0
  304. reasons = []
  305. # 动量条件 (核心)
  306. if row.get('Momentum_5', 0) >= 0:
  307. score += 3
  308. reasons.append('动量OK')
  309. # RSI条件
  310. rsi = row.get('RSI_14', 50)
  311. if 40 <= rsi <= 60:
  312. score += 2
  313. reasons.append('RSI适中')
  314. elif rsi > 60:
  315. score += 1
  316. reasons.append('RSI偏强')
  317. # 量价关系
  318. if row.get('Price_Volume_Pattern') == '价涨量增(健康)':
  319. score += 2
  320. reasons.append('量价健康')
  321. # T+1保护
  322. if row.get('T+1调整') != '是(T0→T1)':
  323. score += 2
  324. reasons.append('非T+1')
  325. # 时间过滤
  326. if row.get('开仓小时') != 13:
  327. score += 1
  328. reasons.append('时间OK')
  329. return score, ','.join(reasons)
  330. t1_trades['Advanced_Score'] = 0
  331. t1_trades['Advanced_Reasons'] = ''
  332. for idx, row in t1_trades.iterrows():
  333. score, reasons = advanced_filter(row)
  334. t1_trades.loc[idx, 'Advanced_Score'] = score
  335. t1_trades.loc[idx, 'Advanced_Reasons'] = reasons
  336. # 高级策略分析
  337. advanced_analysis = t1_trades.groupby('Advanced_Score').agg({
  338. '盈亏金额': ['count', 'sum', 'mean'],
  339. '是否盈利': 'sum'
  340. }).round(2)
  341. advanced_analysis.columns = ['交易次数', '总盈亏', '平均盈亏', '盈利次数']
  342. advanced_analysis['胜率'] = (advanced_analysis['盈利次数'] / advanced_analysis['交易次数'] * 100).round(1)
  343. print('\n高级复合评分分析:')
  344. print(advanced_analysis.to_string())
  345. # 最优阈值
  346. print('\n不同阈值下的策略表现:')
  347. for threshold in [5, 6, 7, 8]:
  348. filtered = t1_trades[t1_trades['Advanced_Score'] >= threshold]
  349. if len(filtered) > 0:
  350. win_rate = (filtered['盈亏金额'] > 0).mean() * 100
  351. total_pnl = filtered['盈亏金额'].sum()
  352. avg_pnl = filtered['盈亏金额'].mean()
  353. print(f' 评分≥{threshold}: {len(filtered)}笔, 胜率{win_rate:.1f}%, 总盈亏{total_pnl:+,.0f}元, 平均{avg_pnl:+,.0f}元')
  354. # ========== 最终推荐 ==========
  355. print('\n' + '='*80)
  356. print('【最终推荐】经过深度挖掘的最优策略')
  357. print('='*80)
  358. # 找出最佳单一条件
  359. best_conditions = []
  360. # 最佳动量条件
  361. mom_best = t1_trades[(t1_trades['Momentum_5'] >= 0) & (t1_trades['T+1调整'] != '是(T0→T1)')]
  362. if len(mom_best) > 0:
  363. best_conditions.append(('动量≥0 + 非T+1', len(mom_best),
  364. (mom_best['盈亏金额']>0).mean()*100,
  365. mom_best['盈亏金额'].sum()))
  366. # 最佳RSI条件
  367. rsi_best = t1_trades[(t1_trades['RSI_14'] >= 45) & (t1_trades['RSI_14'] <= 55) &
  368. (t1_trades['T+1调整'] != '是(T0→T1)')]
  369. if len(rsi_best) > 0:
  370. best_conditions.append(('RSI 45-55 + 非T+1', len(rsi_best),
  371. (rsi_best['盈亏金额']>0).mean()*100,
  372. rsi_best['盈亏金额'].sum()))
  373. # 最佳量价条件
  374. pv_best = t1_trades[(t1_trades['Price_Volume_Pattern'] == '价涨量增(健康)') &
  375. (t1_trades['T+1调整'] != '是(T0→T1)')]
  376. if len(pv_best) > 0:
  377. best_conditions.append(('量价健康 + 非T+1', len(pv_best),
  378. (pv_best['盈亏金额']>0).mean()*100,
  379. pv_best['盈亏金额'].sum()))
  380. # 高级评分条件
  381. adv_best = t1_trades[(t1_trades['Advanced_Score'] >= 6) &
  382. (t1_trades['T+1调整'] != '是(T0→T1)')]
  383. if len(adv_best) > 0:
  384. best_conditions.append(('高级评分≥6 + 非T+1', len(adv_best),
  385. (adv_best['盈亏金额']>0).mean()*100,
  386. adv_best['盈亏金额'].sum()))
  387. print('\n各最优条件对比:')
  388. print(f"{'条件组合':<25} {'交易次数':>8} {'胜率':>8} {'总盈亏':>12}")
  389. print('-' * 60)
  390. for name, count, win_rate, pnl in best_conditions:
  391. print(f'{name:<25} {count:>8} {win_rate:>7.1f}% {pnl:>+11,.0f}')
  392. # 终极推荐
  393. ultimate = t1_trades[
  394. (t1_trades['Momentum_5'] >= 0) &
  395. (t1_trades['RSI_14'] >= 40) &
  396. (t1_trades['RSI_14'] <= 65) &
  397. (t1_trades['Price_Volume_Pattern'].isin(['价涨量增(健康)', '震荡整理'])) &
  398. (t1_trades['T+1调整'] != '是(T0→T1)') &
  399. (t1_trades['开仓小时'] != 13)
  400. ]
  401. print(f'\n【终极推荐策略】:')
  402. print(f' 条件: 动量≥0 + RSI 40-65 + 量价健康/震荡 + 非T+1 + 避开13点')
  403. print(f' 交易次数: {len(ultimate)}')
  404. if len(ultimate) > 0:
  405. print(f' 胜率: {(ultimate["盈亏金额"]>0).mean()*100:.1f}%')
  406. print(f' 总盈亏: {ultimate["盈亏金额"].sum():+,.0f}元')
  407. print(f' 平均盈亏: {ultimate["盈亏金额"].mean():+,.0f}元')
  408. print('\n' + '='*80)
  409. print('深挖分析完成')
  410. print('='*80)