cyb50_30min_intraday_reversa1111l.bak 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. import pandas as pd
  2. import numpy as np
  3. import akshare as ak
  4. import warnings
  5. from datetime import datetime, timedelta
  6. warnings.filterwarnings('ignore')
  7. # ==================== 数据获取模块 ====================
  8. class IntradayDataFetcher:
  9. """30分钟K线数据获取类"""
  10. def __init__(self):
  11. self.symbol = "399673" # 创业板50指数
  12. def fetch_30min_data(self, start_date=None, end_date=None) -> pd.DataFrame:
  13. """获取指定时间范围的30分钟K线数据"""
  14. try:
  15. if start_date is None:
  16. start_date = datetime.now() - timedelta(days=60)
  17. if end_date is None:
  18. end_date = datetime.now()
  19. print(f"正在获取创业板50指数的30分钟K线数据...")
  20. print(f"时间范围: {start_date.strftime('%Y-%m-%d')} 至 {end_date.strftime('%Y-%m-%d')}")
  21. # 使用数据源连接方式获取更多数据
  22. # 首先尝试获取分钟级数据
  23. try:
  24. data = ak.index_zh_a_hist_min_em(symbol=self.symbol, period="30")
  25. except Exception as e:
  26. print(f"获取30分钟数据失败: {e}")
  27. # 如果30分钟数据获取失败,尝试获取日线数据作为备选
  28. print("尝试获取日线数据作为备选方案...")
  29. data = ak.index_zh_a_hist_em(symbol=self.symbol)
  30. if data.empty:
  31. raise ValueError("获取的数据为空")
  32. # 重命名列
  33. data.rename(columns={
  34. '时间': 'DateTime', '开盘': 'Open', '收盘': 'Close',
  35. '最高': 'High', '最低': 'Low', '成交量': 'Volume',
  36. '成交额': 'Amount', '振幅': 'Amplitude', '涨跌幅': 'Change_Pct',
  37. '涨跌额': 'Change_Amount', '换手率': 'Turnover',
  38. '日期': 'DateTime' # 备用字段名
  39. }, inplace=True)
  40. # 设置时间索引
  41. data['DateTime'] = pd.to_datetime(data['DateTime'])
  42. data.set_index('DateTime', inplace=True)
  43. data.sort_index(inplace=True)
  44. # 筛选指定时间范围的数据(使用宽松的开始时间,确保有预热数据)
  45. buffer_start = start_date - timedelta(days=60) # 增加60天缓冲
  46. filtered_data = data[(data.index >= buffer_start) & (data.index <= end_date)].copy()
  47. if filtered_data.empty:
  48. print(f"警告:指定时间范围没有数据,使用所有可用数据")
  49. filtered_data = data.copy()
  50. # 检查数据量
  51. print(f"获取数据总量: {len(data)}条")
  52. print(f"筛选后数据量: {len(filtered_data)}条")
  53. if len(filtered_data) < 20: # 放宽最低要求
  54. raise ValueError(f"数据量严重不足:只获取到{len(filtered_data)}条数据,无法进行有效回测")
  55. # 计算基础指标
  56. filtered_data['Returns'] = filtered_data['Close'].pct_change()
  57. filtered_data['High_Low_Pct'] = (filtered_data['High'] - filtered_data['Low']) / filtered_data['Close'].shift(1)
  58. filtered_data['Close_Open_Pct'] = (filtered_data['Close'] - filtered_data['Open']) / filtered_data['Open']
  59. # 处理缺失值
  60. filtered_data.fillna(method='ffill', inplace=True)
  61. filtered_data.dropna(inplace=True)
  62. print(f"最终可用数据: {len(filtered_data)}条")
  63. print(f"数据范围: {filtered_data.index[0]} 到 {filtered_data.index[-1]}")
  64. return filtered_data
  65. except Exception as e:
  66. print(f"获取数据时出错: {str(e)}")
  67. raise
  68. def calculate_intraday_indicators(self, data: pd.DataFrame) -> pd.DataFrame:
  69. """计算30分钟技术指标"""
  70. print("正在计算30分钟技术指标...")
  71. df = data.copy()
  72. # 短期移动平均线
  73. df['MA6'] = df['Close'].rolling(window=6).mean() # 3小时
  74. df['MA12'] = df['Close'].rolling(window=12).mean() # 6小时
  75. df['MA24'] = df['Close'].rolling(window=24).mean() # 12小时(一天)
  76. # RSI
  77. delta = df['Close'].diff()
  78. gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
  79. loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
  80. rs = gain / loss
  81. df['RSI'] = 100 - (100 / (1 + rs))
  82. # 布林带
  83. df['BB_middle'] = df['Close'].rolling(window=20).mean()
  84. bb_std = df['Close'].rolling(window=20).std()
  85. df['BB_upper'] = df['BB_middle'] + (bb_std * 2)
  86. df['BB_lower'] = df['BB_middle'] - (bb_std * 2)
  87. df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / df['BB_middle']
  88. # MACD
  89. exp1 = df['Close'].ewm(span=12, adjust=False).mean()
  90. exp2 = df['Close'].ewm(span=26, adjust=False).mean()
  91. df['MACD'] = exp1 - exp2
  92. df['MACD_signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
  93. df['MACD_hist'] = df['MACD'] - df['MACD_signal']
  94. # KDJ
  95. low_9 = df['Low'].rolling(window=9).min()
  96. high_9 = df['High'].rolling(window=9).max()
  97. rsv = (df['Close'] - low_9) / (high_9 - low_9) * 100
  98. df['K'] = rsv.ewm(com=2, adjust=False).mean()
  99. df['D'] = df['K'].ewm(com=2, adjust=False).mean()
  100. df['J'] = 3 * df['K'] - 2 * df['D']
  101. # ATR
  102. high_low = df['High'] - df['Low']
  103. high_close = abs(df['High'] - df['Close'].shift())
  104. low_close = abs(df['Low'] - df['Close'].shift())
  105. true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
  106. df['ATR'] = true_range.rolling(window=14).mean()
  107. df['ATR_Pct'] = df['ATR'] / df['Close']
  108. # 动量指标
  109. df['Momentum'] = df['Close'] / df['Close'].shift(4) - 1 # 2小时动量
  110. # 成交量变化
  111. df['Volume_MA'] = df['Volume'].rolling(window=12).mean()
  112. df['Volume_Ratio'] = df['Volume'] / df['Volume_MA']
  113. # 价格动量
  114. df['Price_Momentum'] = (df['Close'] - df['Close'].shift(6)) / df['Close'].shift(6)
  115. print("技术指标计算完成")
  116. return df
  117. # ==================== 翻转信号生成器 ====================
  118. class ReversalSignalGenerator:
  119. """日内翻转信号生成器"""
  120. def __init__(self):
  121. self.signal_count = 0
  122. def generate_reversal_signals(self, data: pd.DataFrame) -> pd.DataFrame:
  123. """生成日内翻转信号"""
  124. print("正在生成日内翻转信号...")
  125. signals = []
  126. df = data.copy()
  127. for i in range(24, len(df)): # 至少需要12小时(24个30分钟)的历史数据
  128. current_bar = df.iloc[i]
  129. current_time = df.index[i]
  130. # 跳过不适合交易的时间段(如午休时间等)
  131. hour = current_time.hour
  132. if hour < 9 or hour > 15: # 只在交易时间内
  133. continue
  134. # 生成信号
  135. signal = {
  136. 'DateTime': current_time,
  137. 'Open': current_bar['Open'],
  138. 'High': current_bar['High'],
  139. 'Low': current_bar['Low'],
  140. 'Close': current_bar['Close'],
  141. 'Volume': current_bar['Volume'],
  142. 'RSI': current_bar['RSI'],
  143. 'MACD': current_bar['MACD'],
  144. 'MACD_hist': current_bar['MACD_hist'],
  145. 'K': current_bar['K'],
  146. 'D': current_bar['D'],
  147. 'J': current_bar['J'],
  148. 'ATR_Pct': current_bar['ATR_Pct'],
  149. 'Volume_Ratio': current_bar['Volume_Ratio'],
  150. 'Price_Momentum': current_bar['Price_Momentum'],
  151. 'Close_Open_Pct': current_bar['Close_Open_Pct']
  152. }
  153. # 计算各种翻转信号
  154. reversal_score = 0
  155. reversal_signals = []
  156. # 1. RSI超卖翻转
  157. if current_bar['RSI'] < 30:
  158. reversal_score += 2
  159. reversal_signals.append("RSI超卖")
  160. elif current_bar['RSI'] < 35:
  161. reversal_score += 1
  162. reversal_signals.append("RSI偏弱")
  163. # 2. KDJ超卖翻转
  164. if current_bar['K'] < 20 and current_bar['D'] < 20:
  165. reversal_score += 2
  166. reversal_signals.append("KDJ超卖")
  167. elif current_bar['J'] < 0:
  168. reversal_score += 2
  169. reversal_signals.append("KDJ极端超卖")
  170. # 3. MACD金叉
  171. if current_bar['MACD_hist'] > 0 and df.iloc[i-1]['MACD_hist'] <= 0:
  172. reversal_score += 2
  173. reversal_signals.append("MACD金叉")
  174. elif current_bar['MACD_hist'] > df.iloc[i-1]['MACD_hist']:
  175. reversal_score += 1
  176. reversal_signals.append("MACD改善")
  177. # 4. 价格触及布林带下轨
  178. bb_width = current_bar['BB_width']
  179. if current_bar['Close'] <= current_bar['BB_lower'] * 1.005:
  180. reversal_score += 2
  181. reversal_signals.append("触及下轨")
  182. elif current_bar['Close'] <= current_bar['BB_lower'] * 1.01:
  183. reversal_score += 1
  184. reversal_signals.append("接近下轨")
  185. # 5. 连续下跌后的反转
  186. recent_returns = df.iloc[i-6:i]['Returns']
  187. if recent_returns.min() < -0.015: # 最近2小时内有超过1.5%的下跌
  188. consecutive_decline = sum(recent_returns < 0)
  189. if consecutive_decline >= 4: # 连续4个周期下跌
  190. reversal_score += 2
  191. reversal_signals.append("连续下跌反转")
  192. # 6. 价格动量反转
  193. if current_bar['Price_Momentum'] < -0.02: # 3小时下跌超过2%
  194. reversal_score += 1
  195. reversal_signals.append("动量超卖")
  196. # 7. 成交量配合
  197. if current_bar['Volume_Ratio'] > 1.2: # 放量
  198. reversal_score += 1
  199. reversal_signals.append("放量配合")
  200. # 8. 当日开盘价格关系
  201. daily_high = df[df.index.date == current_time.date()]['High'].max()
  202. daily_low = df[df.index.date == current_time.date()]['Low'].min()
  203. daily_range = daily_high - daily_low
  204. if daily_range > 0:
  205. position_in_day = (current_bar['Close'] - daily_low) / daily_range
  206. if position_in_day < 0.3: # 在当日低位区域
  207. reversal_score += 1
  208. reversal_signals.append("日内低位")
  209. # 设置信号
  210. signal['Reversal_Score'] = reversal_score
  211. signal['Reversal_Signals'] = ', '.join(reversal_signals) if reversal_signals else ''
  212. # 生成买入信号(阈值降低以增加交易频率)
  213. if reversal_score >= 4:
  214. signal['Signal'] = 1
  215. signal['Signal_Type'] = '做多翻转'
  216. self.signal_count += 1
  217. else:
  218. signal['Signal'] = 0
  219. signal['Signal_Type'] = ''
  220. signals.append(signal)
  221. signals_df = pd.DataFrame(signals)
  222. signals_df.set_index('DateTime', inplace=True)
  223. print(f"信号生成完成,共产生{self.signal_count}个翻转信号")
  224. print(f"信号密度: {self.signal_count/len(signals_df)*100:.2f}%")
  225. return signals_df
  226. # ==================== 日内交易执行器 ====================
  227. class IntradayReversalExecutor:
  228. """日内翻转交易执行器"""
  229. def __init__(self, initial_capital=1000000):
  230. self.initial_capital = initial_capital
  231. self.params = {
  232. 'commission_rate': 0.0001, # 万分之一
  233. 'slippage_rate': 0.0, # 无滑点
  234. 'position_size_pct': 1.0, # 每次开仓100%仓位(满仓)
  235. 'stop_loss_pct': 0.008, # 0.8%止损
  236. 'take_profit_pct': 0.015, # 1.5%止盈
  237. 'max_hold_bars': 16, # 最多持有8小时(16个30分钟)
  238. 'min_signal_strength': 4 # 最小信号强度
  239. }
  240. def execute_intraday_trades(self, signals_df: pd.DataFrame) -> tuple:
  241. """执行日内翻转交易"""
  242. print("正在执行日内翻转交易...")
  243. df = signals_df.copy()
  244. # 初始化
  245. trades = []
  246. capital = self.initial_capital
  247. position = 0
  248. entry_price = 0
  249. entry_time = None
  250. holding_bars = 0
  251. entry_signals = ''
  252. # 添加资金列
  253. df = df.copy()
  254. df['capital'] = capital
  255. df['position'] = 0
  256. df['net_value'] = capital
  257. for i in range(len(df)):
  258. current_time = df.index[i]
  259. current_bar = df.iloc[i]
  260. price = current_bar['Close']
  261. # 更新当前净值
  262. if position > 0:
  263. current_value = capital + position * price
  264. df.iloc[i, df.columns.get_loc('net_value')] = current_value
  265. else:
  266. df.iloc[i, df.columns.get_loc('net_value')] = capital
  267. # 开仓逻辑
  268. if position == 0 and current_bar['Signal'] == 1:
  269. # 开仓
  270. position_size = int((capital * self.params['position_size_pct']) / price)
  271. if position_size > 0:
  272. cost = position_size * price * (1 + self.params['commission_rate'] + self.params['slippage_rate'])
  273. if cost <= capital:
  274. position = position_size
  275. entry_price = price
  276. entry_time = current_time
  277. entry_signals = current_bar.get('Reversal_Signals', '')
  278. holding_bars = 0
  279. capital -= cost
  280. df.iloc[i, df.columns.get_loc('position')] = position
  281. # 平仓逻辑
  282. elif position > 0:
  283. holding_bars += 1
  284. # 计算止损止盈价格
  285. stop_loss = entry_price * (1 - self.params['stop_loss_pct'])
  286. take_profit = entry_price * (1 + self.params['take_profit_pct'])
  287. exit_signal = False
  288. exit_reason = ''
  289. exit_price = price
  290. # 止损
  291. if price <= stop_loss:
  292. exit_signal = True
  293. exit_reason = "止损"
  294. exit_price = stop_loss
  295. # 止盈
  296. elif price >= take_profit:
  297. exit_signal = True
  298. exit_reason = "止盈"
  299. exit_price = take_profit
  300. # 最大持仓时间
  301. elif holding_bars >= self.params['max_hold_bars']:
  302. exit_signal = True
  303. exit_reason = "时间止损"
  304. # 翻转信号消失
  305. elif current_bar['RSI'] > 70: # RSI超买
  306. exit_signal = True
  307. exit_reason = "RSI超买平仓"
  308. # 执行平仓
  309. if exit_signal:
  310. # 计算盈亏 - 修复:包含开仓和平仓的总成本
  311. gross_pnl = (exit_price - entry_price) * position
  312. # 开仓成本(已经在开仓时扣除)
  313. open_cost = position * entry_price * (self.params['commission_rate'] + self.params['slippage_rate'])
  314. # 平仓成本
  315. close_revenue = position * exit_price
  316. close_cost = close_revenue * (self.params['commission_rate'] + self.params['slippage_rate'])
  317. # 净盈亏 = 价差收益 - 开仓成本 - 平仓成本
  318. pnl = gross_pnl - open_cost - close_cost
  319. # 更新资金
  320. capital += close_revenue - close_cost
  321. # 记录交易
  322. trade = {
  323. '买入时间': entry_time,
  324. '卖出时间': current_time,
  325. '买入价格': entry_price,
  326. '卖出价格': exit_price,
  327. '仓位': position,
  328. '盈亏金额': pnl,
  329. '盈亏百分比': (exit_price - entry_price) / entry_price * 100,
  330. '退出原因': exit_reason,
  331. '持仓周期数': holding_bars,
  332. '持仓小时数': holding_bars * 0.5,
  333. '入场信号': entry_signals,
  334. '卖出时资金': capital,
  335. '开仓市值': position * entry_price
  336. }
  337. trades.append(trade)
  338. # 重置
  339. position = 0
  340. entry_price = 0
  341. entry_time = None
  342. holding_bars = 0
  343. # 更新资金
  344. df.iloc[i, df.columns.get_loc('capital')] = capital
  345. df.iloc[i, df.columns.get_loc('position')] = position
  346. # 强制平仓剩余持仓 - 修复:包含开仓和平仓的总成本
  347. if position > 0:
  348. final_price = df.iloc[-1]['Close']
  349. # 计算总盈亏
  350. gross_pnl = (final_price - entry_price) * position
  351. open_cost = position * entry_price * (self.params['commission_rate'] + self.params['slippage_rate'])
  352. close_revenue = position * final_price
  353. close_cost = close_revenue * (self.params['commission_rate'] + self.params['slippage_rate'])
  354. pnl = gross_pnl - open_cost - close_cost
  355. capital += close_revenue - close_cost
  356. trade = {
  357. '买入时间': entry_time,
  358. '卖出时间': df.index[-1],
  359. '买入价格': entry_price,
  360. '卖出价格': final_price,
  361. '仓位': position,
  362. '盈亏金额': pnl,
  363. '盈亏百分比': (final_price - entry_price) / entry_price * 100,
  364. '退出原因': '强制平仓',
  365. '持仓周期数': holding_bars,
  366. '持仓小时数': holding_bars * 0.5,
  367. '入场信号': entry_signals,
  368. '卖出时资金': capital,
  369. '开仓市值': position * entry_price
  370. }
  371. trades.append(trade)
  372. trades_df = pd.DataFrame(trades)
  373. if len(trades_df) > 0:
  374. trades_df['买入时间'] = pd.to_datetime(trades_df['买入时间'])
  375. trades_df['卖出时间'] = pd.to_datetime(trades_df['卖出时间'])
  376. trades_df = trades_df.sort_values('买入时间')
  377. print(f"交易执行完成,共{len(trades_df)}笔交易")
  378. return df, trades_df
  379. # ==================== 验证分析模块 ====================
  380. def validate_intraday_results(results_df, trades_df, initial_capital):
  381. """验证日内交易结果"""
  382. print("\n" + "=" * 80)
  383. print("日内翻转交易结果验证")
  384. print("=" * 80)
  385. print(f"\n【基础数据验证】")
  386. final_capital = results_df['net_value'].iloc[-1]
  387. total_return = (final_capital - initial_capital) / initial_capital * 100
  388. print(f"初始资金: {initial_capital:,.2f}元")
  389. print(f"最终资金: {final_capital:,.2f}元")
  390. print(f"总收益率: {total_return:.2f}%")
  391. print(f"交易次数: {len(trades_df)}笔")
  392. if len(trades_df) > 0:
  393. print(f"\n【交易统计】")
  394. win_trades = trades_df[trades_df['盈亏金额'] > 0]
  395. lose_trades = trades_df[trades_df['盈亏金额'] < 0]
  396. print(f"盈利交易: {len(win_trades)}笔 ({len(win_trades)/len(trades_df)*100:.1f}%)")
  397. print(f"亏损交易: {len(lose_trades)}笔 ({len(lose_trades)/len(trades_df)*100:.1f}%)")
  398. print(f"平均持仓时间: {trades_df['持仓小时数'].mean():.1f}小时")
  399. print(f"平均收益率: {trades_df['盈亏百分比'].mean():.2f}%")
  400. # 按退出原因统计
  401. print(f"\n【退出原因统计】")
  402. for reason, count in trades_df['退出原因'].value_counts().items():
  403. percentage = count / len(trades_df) * 100
  404. reason_pnl = trades_df[trades_df['退出原因'] == reason]['盈亏金额'].sum()
  405. print(f" {reason}: {count}次 ({percentage:.1f}%) - 总盈亏: {reason_pnl:+,.2f}元")
  406. # ==================== 主程序 ====================
  407. def main():
  408. """主程序 - 运行30分钟日内翻转策略"""
  409. print("=" * 80)
  410. print("创业板50 30分钟日内翻转策略")
  411. print("=" * 80)
  412. # 策略参数
  413. # 时间配置(调整为akshare数据可用的最近时间范围)
  414. BACKTEST_START_DATE = "2026-01-05" # 回测开始日期(调整为最近可用数据)
  415. BACKTEST_END_DATE = "2026-01-19" # 回测结束日期(使用当前日期)
  416. PREWARMP_DAYS = 10 # 指标预热期天数(调整为10天)
  417. INITIAL_CAPITAL = 100000
  418. # 转换日期格式
  419. start_date = datetime.strptime(BACKTEST_START_DATE, "%Y-%m-%d")
  420. end_date = datetime.strptime(BACKTEST_END_DATE, "%Y-%m-%d")
  421. # 计算数据获取开始时间(回测开始时间 - 预热期)
  422. data_start_date = start_date - timedelta(days=PREWARMP_DAYS)
  423. print(f"\n策略参数:")
  424. print(f" 回测期间: {BACKTEST_START_DATE} 至 {BACKTEST_END_DATE}")
  425. print(f" 数据获取期间: {data_start_date.strftime('%Y-%m-%d')} 至 {BACKTEST_END_DATE}")
  426. print(f" 指标预热期: {PREWARMP_DAYS}天")
  427. print(f" K线周期: 30分钟")
  428. print(f" 初始资金: {INITIAL_CAPITAL:,}元")
  429. print(f" 标的指数: 创业板50 (399673)")
  430. try:
  431. # Phase 1: 数据获取
  432. print(f"\n【Phase 1: 30分钟数据获取】")
  433. fetcher = IntradayDataFetcher()
  434. # 获取包含预热期的完整数据
  435. full_data = fetcher.fetch_30min_data(start_date=data_start_date, end_date=end_date)
  436. full_data = fetcher.calculate_intraday_indicators(full_data)
  437. # 筛选回测期间的数据
  438. original_len = len(full_data)
  439. backtest_data = full_data[(full_data.index >= start_date) & (full_data.index <= end_date)].copy()
  440. print(f"筛选回测数据: {original_len} -> {len(backtest_data)} 条")
  441. print(f"回测数据范围: {backtest_data.index[0]} 到 {backtest_data.index[-1]}")
  442. # Phase 2: 信号生成
  443. print(f"\n【Phase 2: 翻转信号生成】")
  444. signal_gen = ReversalSignalGenerator()
  445. signals_df = signal_gen.generate_reversal_signals(backtest_data)
  446. # Phase 3: 交易执行
  447. print(f"\n【Phase 3: 日内交易执行】")
  448. executor = IntradayReversalExecutor(initial_capital=INITIAL_CAPITAL)
  449. results_df, trades_df = executor.execute_intraday_trades(signals_df)
  450. # Phase 4: 验证分析
  451. print(f"\n【Phase 4: 结果验证与分析】")
  452. validate_intraday_results(results_df, trades_df, INITIAL_CAPITAL)
  453. # Phase 5: 导出数据
  454. if len(trades_df) > 0:
  455. print(f"\n【Phase 5: 导出交易数据】")
  456. # 确保时间戳格式精确到分钟
  457. trades_df['买入时间'] = pd.to_datetime(trades_df['买入时间']).dt.strftime('%Y-%m-%d %H:%M:%S')
  458. trades_df['卖出时间'] = pd.to_datetime(trades_df['卖出时间']).dt.strftime('%Y-%m-%d %H:%M:%S')
  459. # 生成带时间戳的文件名
  460. timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
  461. output_file = f'cyb50_30min_intraday_reversal_trades_{timestamp}.csv'
  462. trades_df.to_csv(output_file, index=False, encoding='utf-8-sig')
  463. print(f"交易记录已保存到: {output_file}")
  464. print(f"时间戳格式: YYYY-MM-DD HH:MM:SS")
  465. # 策略总结
  466. print(f"\n" + "=" * 80)
  467. print("策略运行总结")
  468. print("=" * 80)
  469. if len(trades_df) > 0:
  470. final_capital = results_df['net_value'].iloc[-1]
  471. total_return = (final_capital - INITIAL_CAPITAL) / INITIAL_CAPITAL * 100
  472. print(f"初始资金: {INITIAL_CAPITAL:,.2f}元")
  473. print(f"最终资金: {final_capital:,.2f}元")
  474. print(f"总收益率: {total_return:.2f}%")
  475. print(f"交易次数: {len(trades_df)}笔")
  476. print(f"胜率: {(trades_df['盈亏金额'] > 0).sum() / len(trades_df) * 100:.1f}%")
  477. print(f"平均收益率: {trades_df['盈亏百分比'].mean():.2f}%")
  478. print(f"最大单笔盈利: {trades_df['盈亏金额'].max():+,.2f}元")
  479. print(f"最大单笔亏损: {trades_df['盈亏金额'].min():+,.2f}元")
  480. print(f"\n[SUCCESS] 策略运行成功!")
  481. else:
  482. print("未产生任何交易信号")
  483. except Exception as e:
  484. print(f"\n[ERROR] 策略运行出错: {str(e)}")
  485. import traceback
  486. traceback.print_exc()
  487. finally:
  488. print(f"\n" + "=" * 80)
  489. if __name__ == "__main__":
  490. main()