t1_converter.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. CYB50 T+1 转换器 - 基于多空版本的做多交易转换为T+1规则
  5. 规则:
  6. 1. 提取多空版本中的所有做多交易
  7. 2. 买入当天不能卖出(T+1限制)
  8. 3. 如果原交易是T0(当天买卖),则延期到T+1开盘卖出
  9. 4. 重新计算延期后的盈亏(基于实际价格变化)
  10. """
  11. import pandas as pd
  12. import numpy as np
  13. from datetime import datetime, timedelta
  14. import sys
  15. # 调试版本:所有依赖文件在同一目录下,无需额外路径设置
  16. # sys.path.insert(0, '/root/.openclaw/workspace/cat-fly')
  17. from cyb50_30min_dual_direction import (
  18. ConfigManager, IntradayDataFetcher,
  19. DualDirectionSignalGenerator, DualDirectionExecutor
  20. )
  21. def get_next_trading_session_open(data_df, current_time):
  22. """获取下一个交易日的第一个开盘时间(注意:不是当天,是下一天)"""
  23. current_date = current_time.date()
  24. # 查找当前日期之后的所有数据
  25. future_data = data_df[data_df.index > current_time]
  26. if future_data.empty:
  27. return None, None
  28. # 获取所有日期,找到第一个不同于current_date的日期
  29. future_dates = future_data.index.date
  30. next_date = None
  31. for d in future_dates:
  32. if d != current_date:
  33. next_date = d
  34. break
  35. if next_date is None:
  36. return None, None
  37. # 获取下一个交易日的所有数据
  38. next_day_data = data_df[data_df.index.date == next_date]
  39. if next_day_data.empty:
  40. return None, None
  41. open_time = next_day_data.index[0]
  42. open_price = next_day_data.iloc[0]['Open'] # 使用开盘价
  43. return open_time, open_price
  44. def simulate_t1_trades(data_df, long_trades_df, initial_capital=1000000):
  45. """模拟T+1规则下的交易
  46. 规则:
  47. - 买入当天不能卖出
  48. - 如果原T0交易(当天买卖),延期到T+1开盘卖出
  49. - 卖出后当天可以再买(这是关键特性)
  50. """
  51. print("\n" + "="*80)
  52. print("T+1规则转换 - 基于多空版本做多交易")
  53. print("="*80)
  54. if len(long_trades_df) == 0:
  55. print("没有做多交易记录")
  56. return pd.DataFrame()
  57. # 按开仓时间排序
  58. long_trades_df = long_trades_df.sort_values('开仓时间').reset_index(drop=True)
  59. t1_trades = []
  60. capital = initial_capital
  61. for idx, trade in long_trades_df.iterrows():
  62. entry_time = trade['开仓时间']
  63. entry_price = trade['开仓价格']
  64. original_exit_time = trade['平仓时间']
  65. original_exit_price = trade['平仓价格']
  66. position_size = int(trade['仓位'])
  67. entry_signals = trade.get('入场信号', '')
  68. entry_date = entry_time.date()
  69. exit_date = original_exit_time.date()
  70. # 判断是否是T0交易
  71. is_t0 = (entry_date == exit_date)
  72. if is_t0:
  73. # T0交易需要延期到T+1开盘
  74. new_exit_time, new_exit_price = get_next_trading_session_open(data_df, entry_time)
  75. if new_exit_time is None:
  76. print(f"⚠️ 交易 #{idx+1}: 无法找到T+1开盘时间,使用原平仓价格")
  77. new_exit_time = original_exit_time
  78. new_exit_price = original_exit_price
  79. t1_adjusted = False
  80. else:
  81. # 计算新的盈亏
  82. # 假设使用开盘价卖出
  83. original_pnl = trade['盈亏金额']
  84. # 计算手续费(万分之一)
  85. commission_rate = 0.0001
  86. open_cost = position_size * entry_price * commission_rate
  87. close_cost = position_size * new_exit_price * commission_rate
  88. # 新的盈亏
  89. gross_pnl = (new_exit_price - entry_price) * position_size
  90. new_pnl = gross_pnl - open_cost - close_cost
  91. new_pnl_pct = (new_exit_price - entry_price) / entry_price * 100
  92. # 判断新的退出原因
  93. stop_loss = entry_price * 0.992 # 0.8%止损
  94. take_profit = entry_price * 1.02 # 2%止盈
  95. if new_exit_price <= stop_loss:
  96. exit_reason = f"T+1延期止损(价格{new_exit_price:.2f}触及止损线{stop_loss:.2f},亏损{abs(new_pnl_pct):.2f}%)"
  97. elif new_exit_price >= take_profit:
  98. exit_reason = f"T+1延期止盈(价格{new_exit_price:.2f}触及止盈线{take_profit:.2f},盈利{new_pnl_pct:.2f}%)"
  99. else:
  100. exit_reason = f"T+1延期平仓(价格{new_exit_price:.2f},盈亏{new_pnl_pct:+.2f}%)"
  101. # 计算持仓时长(小时)
  102. hold_hours = (new_exit_time - entry_time).total_seconds() / 3600
  103. print(f"\n[T0→T1调整] 交易 #{idx+1}")
  104. print(f" 原交易: {entry_time.strftime('%m-%d %H:%M')} 买 → {original_exit_time.strftime('%m-%d %H:%M')} 卖")
  105. print(f" 新交易: {entry_time.strftime('%m-%d %H:%M')} 买 → {new_exit_time.strftime('%m-%d %H:%M')} 卖")
  106. print(f" 原盈亏: {original_pnl:+.2f}元")
  107. print(f" 新盈亏: {new_pnl:+.2f}元 (基于T+1开盘{new_exit_price:.2f})")
  108. print(f" 盈亏变化: {(new_pnl - original_pnl):+.2f}元")
  109. t1_adjusted = True
  110. # 更新交易记录
  111. trade_record = {
  112. '交易方向': '做多',
  113. '开仓时间': entry_time,
  114. '平仓时间': new_exit_time,
  115. '开仓价格': entry_price,
  116. '平仓价格': new_exit_price,
  117. '仓位': position_size,
  118. '盈亏金额': new_pnl,
  119. '盈亏百分比': new_pnl_pct,
  120. '退出原因': exit_reason,
  121. '持仓周期数': int(hold_hours * 2), # 30分钟周期数
  122. '持仓小时数': hold_hours,
  123. 'T+1调整': '是(T0→T1)',
  124. '原平仓时间': original_exit_time,
  125. '原平仓价格': original_exit_price,
  126. '原盈亏': original_pnl,
  127. '盈亏变化': new_pnl - original_pnl,
  128. '入场信号': entry_signals,
  129. '开仓市值': position_size * entry_price,
  130. }
  131. capital += new_pnl
  132. trade_record['平仓时资金'] = capital
  133. t1_trades.append(trade_record)
  134. continue
  135. # 非T0交易,保持原样
  136. hold_hours = trade['持仓小时数']
  137. # 计算盈亏(使用原始数据)
  138. commission_rate = 0.0001
  139. gross_pnl = (original_exit_price - entry_price) * position_size
  140. open_cost = position_size * entry_price * commission_rate
  141. close_cost = position_size * original_exit_price * commission_rate
  142. pnl = gross_pnl - open_cost - close_cost
  143. pnl_pct = (original_exit_price - entry_price) / entry_price * 100
  144. capital += pnl
  145. trade_record = {
  146. '交易方向': '做多',
  147. '开仓时间': entry_time,
  148. '平仓时间': original_exit_time,
  149. '开仓价格': entry_price,
  150. '平仓价格': original_exit_price,
  151. '仓位': position_size,
  152. '盈亏金额': pnl,
  153. '盈亏百分比': pnl_pct,
  154. '退出原因': trade['退出原因'],
  155. '持仓周期数': trade['持仓周期数'],
  156. '持仓小时数': hold_hours,
  157. 'T+1调整': '否',
  158. '原平仓时间': original_exit_time,
  159. '原平仓价格': original_exit_price,
  160. '原盈亏': trade['盈亏金额'],
  161. '盈亏变化': 0,
  162. '入场信号': entry_signals,
  163. '开仓市值': position_size * entry_price,
  164. '平仓时资金': capital,
  165. }
  166. t1_trades.append(trade_record)
  167. t1_trades_df = pd.DataFrame(t1_trades)
  168. return t1_trades_df
  169. def compare_results(original_trades, t1_trades, initial_capital=1000000):
  170. """对比原始交易和T+1转换后的结果"""
  171. print("\n" + "="*80)
  172. print("T+1转换前后对比")
  173. print("="*80)
  174. # 原始统计
  175. original_total_pnl = original_trades['盈亏金额'].sum()
  176. original_final = initial_capital + original_total_pnl
  177. original_return = (original_final / initial_capital - 1) * 100
  178. original_win_rate = (original_trades['盈亏金额'] > 0).sum() / len(original_trades) * 100
  179. # T+1统计
  180. t1_total_pnl = t1_trades['盈亏金额'].sum()
  181. t1_final = initial_capital + t1_total_pnl
  182. t1_return = (t1_final / initial_capital - 1) * 100
  183. t1_win_rate = (t1_trades['盈亏金额'] > 0).sum() / len(t1_trades) * 100
  184. # T0交易统计
  185. t0_adjusted = t1_trades[t1_trades['T+1调整'] == '是(T0→T1)']
  186. print(f"\n【原始交易(T0规则)】")
  187. print(f" 交易次数: {len(original_trades)}")
  188. print(f" 总盈亏: {original_total_pnl:+,.2f}元")
  189. print(f" 最终资金: {original_final:,.2f}元")
  190. print(f" 收益率: {original_return:+.2f}%")
  191. print(f" 胜率: {original_win_rate:.1f}%")
  192. print(f"\n【T+1转换后】")
  193. print(f" 交易次数: {len(t1_trades)}")
  194. print(f" 总盈亏: {t1_total_pnl:+,.2f}元")
  195. print(f" 最终资金: {t1_final:,.2f}元")
  196. print(f" 收益率: {t1_return:+.2f}%")
  197. print(f" 胜率: {t1_win_rate:.1f}%")
  198. print(f"\n【T+1调整统计】")
  199. print(f" T0→T1调整交易数: {len(t0_adjusted)}笔")
  200. if len(t0_adjusted) > 0:
  201. print(f" 调整后盈亏变化: {t0_adjusted['盈亏变化'].sum():+,.2f}元")
  202. print(f" 平均每笔变化: {t0_adjusted['盈亏变化'].mean():+,.2f}元")
  203. print(f" 调整交易明细:")
  204. for _, row in t0_adjusted.iterrows():
  205. print(f" {row['开仓时间'].strftime('%m-%d %H:%M')} - "
  206. f"原盈亏{row['原盈亏']:+.0f} → 新盈亏{row['盈亏金额']:+.0f} "
  207. f"({row['盈亏变化']:+.0f})")
  208. print(f"\n【收益差异】")
  209. print(f" 收益率变化: {(t1_return - original_return):+.2f}%")
  210. print(f" 绝对盈亏差: {(t1_total_pnl - original_total_pnl):+,.2f}元")
  211. def main():
  212. """主程序"""
  213. print("="*80)
  214. print("CYB50 T+1 交易转换器")
  215. print("基于多空版本的做多交易,应用T+1规则")
  216. print("="*80)
  217. initial_capital = 1000000
  218. # 1. 运行多空版本获取原始交易数据
  219. print("\n【步骤1】运行多空版本获取原始交易...")
  220. config_manager = ConfigManager('config.json')
  221. fetcher = IntradayDataFetcher(config_manager)
  222. end_date = datetime.now()
  223. start_date = end_date - timedelta(days=70)
  224. raw_data = fetcher.fetch_30min_data(start_date, end_date)
  225. data_with_indicators = fetcher.calculate_intraday_indicators(raw_data)
  226. signal_generator = DualDirectionSignalGenerator()
  227. signals_df = signal_generator.generate_dual_direction_signals(data_with_indicators)
  228. executor = DualDirectionExecutor(initial_capital=initial_capital)
  229. results_df, trades_df = executor.execute_dual_direction_trades(signals_df)
  230. # 提取做多交易
  231. long_trades = trades_df[trades_df['交易方向'] == '做多'].copy()
  232. print(f"✅ 获取到 {len(long_trades)} 笔做多交易")
  233. # 2. 应用T+1规则
  234. print("\n【步骤2】应用T+1规则转换...")
  235. t1_trades = simulate_t1_trades(data_with_indicators, long_trades, initial_capital)
  236. # 3. 对比结果
  237. print("\n【步骤3】对比分析...")
  238. compare_results(long_trades, t1_trades, initial_capital)
  239. # 4. 导出结果
  240. if len(t1_trades) > 0:
  241. print("\n【步骤4】导出T+1交易记录...")
  242. # 格式化时间
  243. export_df = t1_trades.copy()
  244. for col in ['开仓时间', '平仓时间', '原平仓时间']:
  245. if col in export_df.columns:
  246. export_df[col] = export_df[col].dt.strftime('%Y-%m-%d %H:%M:%S')
  247. timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
  248. output_file = f'cyb50_t1_converted_trades_{timestamp}.csv'
  249. export_df.to_csv(output_file, index=False, encoding='utf-8-sig')
  250. print(f"✅ T+1交易记录已保存: {output_file}")
  251. print("\n" + "="*80)
  252. print("转换完成!")
  253. print("="*80)
  254. if __name__ == "__main__":
  255. main()