diagnose_sell_logic.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 深入诊断 - 检查卖出逻辑和移动止损问题
  5. """
  6. import pandas as pd
  7. import numpy as np
  8. import sys
  9. sys.path.insert(0, '/root/.openclaw/workspace/quant')
  10. # 加载数据
  11. df = pd.read_csv('/root/.openclaw/workspace/quant/cyb50_baostock.csv')
  12. df['date'] = pd.to_datetime(df['date'])
  13. df = df.set_index('date').sort_index()
  14. for col in ['open', 'high', 'low', 'close', 'volume']:
  15. df[col] = pd.to_numeric(df[col], errors='coerce')
  16. # 计算指标
  17. df['ma10'] = df['close'].rolling(window=10).mean()
  18. df['ma30'] = df['close'].rolling(window=30).mean()
  19. df['high_20'] = df['high'].rolling(window=20).max()
  20. df['low_20'] = df['low'].rolling(window=20).min()
  21. df['ret_10'] = df['close'].pct_change(periods=10)
  22. # 买入条件
  23. buy_cond = (
  24. (df['close'] > df['ma10']) &
  25. (df['ma10'] > df['ma30']) &
  26. (df['close'] >= df['high_20'] * 0.995) &
  27. (df['ret_10'] > 0.02)
  28. )
  29. # 卖出条件1: 跌破MA30
  30. sell_cond1 = df['close'] < df['ma30']
  31. # 卖出条件2: 创20日新低
  32. sell_cond2 = df['close'] <= df['low_20'] * 1.005
  33. print("="*60)
  34. print("🔍 卖出条件分析")
  35. print("="*60)
  36. print(f"\n卖出条件1 (跌破MA30): {sell_cond1.sum()} 天")
  37. print(f"卖出条件2 (创20日新低): {sell_cond2.sum()} 天")
  38. print(f"任一条件触发: {(sell_cond1 | sell_cond2).sum()} 天")
  39. # 检查卖出条件1和2的重叠
  40. both_sell = sell_cond1 & sell_cond2
  41. print(f"两个条件同时触发: {both_sell.sum()} 天")
  42. # 模拟交易,详细检查每次卖出原因
  43. print("\n📈 详细交易分析:")
  44. df['signal'] = 0
  45. df.loc[buy_cond, 'signal'] = 1
  46. df.loc[sell_cond1 | sell_cond2, 'signal'] = -1
  47. position = 0
  48. entry_price = 0
  49. peak_price = 0
  50. capital = 1000000
  51. trades = []
  52. for i in range(30, len(df)):
  53. date = df.index[i]
  54. price = df['close'].iloc[i]
  55. signal = df['signal'].iloc[i]
  56. # 检查各种卖出原因
  57. sell_ma30 = price < df['ma30'].iloc[i]
  58. sell_low20 = price <= df['low_20'].iloc[i] * 1.005
  59. # 移动止损检查
  60. stop_loss_triggered = False
  61. stop_loss_price = None
  62. if position > 0:
  63. if price > peak_price:
  64. peak_price = price
  65. stop_loss_price = peak_price * 0.90
  66. if price < stop_loss_price:
  67. signal = -1
  68. stop_loss_triggered = True
  69. # 买入
  70. if signal == 1 and position == 0:
  71. position = 1
  72. entry_price = price
  73. peak_price = price
  74. trades.append({
  75. 'date': date,
  76. 'action': 'BUY',
  77. 'price': price,
  78. 'capital': capital
  79. })
  80. # 卖出
  81. elif signal == -1 and position == 1:
  82. pnl = (price / entry_price - 1) * capital
  83. capital += pnl
  84. hold_days = (date - trades[-1]['date']).days
  85. exit_reasons = []
  86. if stop_loss_triggered:
  87. exit_reasons.append(f"移动止损({peak_price:.2f}→{stop_loss_price:.2f})")
  88. if sell_ma30:
  89. exit_reasons.append("跌破MA30")
  90. if sell_low20:
  91. exit_reasons.append("创20日新低")
  92. trades.append({
  93. 'date': date,
  94. 'action': 'SELL',
  95. 'price': price,
  96. 'capital': capital,
  97. 'pnl': pnl,
  98. 'return_pct': (price / entry_price - 1) * 100,
  99. 'reasons': ' + '.join(exit_reasons),
  100. 'hold_days': hold_days
  101. })
  102. position = 0
  103. # 显示最近10次卖出的详细原因
  104. print("\n最近10次卖出详情:")
  105. sell_trades = [t for t in trades if t['action'] == 'SELL']
  106. for t in sell_trades[-10:]:
  107. print(f" {t['date'].strftime('%Y-%m-%d')}: {t['return_pct']:+.2f}% ({t['hold_days']}天)")
  108. print(f" 原因: {t['reasons']}")
  109. # 统计各卖出原因的占比
  110. print("\n📊 卖出原因统计:")
  111. reasons_count = {}
  112. for t in sell_trades:
  113. for reason in t['reasons'].split(' + '):
  114. reasons_count[reason] = reasons_count.get(reason, 0) + 1
  115. for reason, count in sorted(reasons_count.items(), key=lambda x: x[1], reverse=True):
  116. print(f" {reason}: {count}次 ({count/len(sell_trades)*100:.1f}%)")
  117. print("\n" + "="*60)
  118. # 检查移动止损的触发情况
  119. print("\n📉 移动止损检查:")
  120. stop_loss_trades = [t for t in sell_trades if '移动止损' in t['reasons']]
  121. print(f" 移动止损触发次数: {len(stop_loss_trades)}")
  122. if stop_loss_trades:
  123. avg_return = np.mean([t['return_pct'] for t in stop_loss_trades])
  124. print(f" 移动止损平均收益: {avg_return:.2f}%")
  125. print("\n 最近5次移动止损:")
  126. for t in stop_loss_trades[-5:]:
  127. print(f" {t['date'].strftime('%Y-%m-%d')}: {t['return_pct']:+.2f}%")