trading_training_tool.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. # 交易训练工具
  2. # 用于从交易记录CSV文件中随机选择交易,显示部分K线图让用户判断是否开仓,然后显示完整结果并记录
  3. from jqdata import *
  4. import pandas as pd
  5. import numpy as np
  6. import matplotlib.pyplot as plt
  7. import matplotlib.patches as patches
  8. from datetime import datetime, timedelta, date
  9. import re
  10. import os
  11. import random
  12. import warnings
  13. warnings.filterwarnings('ignore')
  14. # 中文字体设置
  15. plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
  16. plt.rcParams['axes.unicode_minus'] = False
  17. # ========== 参数配置区域(硬编码参数都在这里) ==========
  18. CONFIG = {
  19. # CSV文件名
  20. 'csv_filename': 'transaction1.csv',
  21. # 结果记录文件名
  22. 'result_filename': 'training_results.csv',
  23. # 历史数据天数
  24. 'history_days': 100,
  25. # 未来数据天数
  26. 'future_days': 20,
  27. # 输出目录
  28. 'output_dir': 'training_images',
  29. # 是否显示图片(在某些环境下可能需要设置为False)
  30. 'show_plots': True,
  31. # 图片DPI
  32. 'plot_dpi': 150,
  33. # 随机种子(设置为None表示不固定种子)
  34. 'random_seed': 42
  35. }
  36. # =====================================================
  37. def _get_current_directory():
  38. """
  39. 获取当前文件所在目录
  40. """
  41. try:
  42. current_dir = os.path.dirname(os.path.abspath(__file__))
  43. except NameError:
  44. current_dir = os.getcwd()
  45. if not os.path.exists(os.path.join(current_dir, CONFIG['csv_filename'])):
  46. parent_dir = os.path.dirname(current_dir)
  47. future_dir = os.path.join(parent_dir, 'future')
  48. if os.path.exists(os.path.join(future_dir, CONFIG['csv_filename'])):
  49. current_dir = future_dir
  50. return current_dir
  51. def read_transaction_data(csv_path):
  52. """
  53. 读取交易记录CSV文件
  54. """
  55. encodings = ['utf-8-sig', 'utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1']
  56. for encoding in encodings:
  57. try:
  58. df = pd.read_csv(csv_path, encoding=encoding)
  59. print(f"成功使用 {encoding} 编码读取CSV文件,共 {len(df)} 条记录")
  60. return df
  61. except UnicodeDecodeError:
  62. continue
  63. except Exception as e:
  64. if encoding == encodings[-1]:
  65. print(f"读取CSV文件时出错: {str(e)}")
  66. raise
  67. return pd.DataFrame()
  68. def extract_contract_info(row):
  69. """
  70. 从交易记录中提取合约信息和交易信息
  71. """
  72. try:
  73. # 提取合约编号
  74. target_str = str(row['标的'])
  75. match = re.search(r'\(([^)]+)\)', target_str)
  76. if match:
  77. contract_code = match.group(1)
  78. else:
  79. return None, None, None, None, None, None, None, None, None
  80. # 提取日期
  81. date_str = str(row['日期']).strip()
  82. date_formats = ['%Y-%m-%d', '%d/%m/%Y', '%Y/%m/%d', '%d-%m-%Y', '%Y%m%d']
  83. trade_date = None
  84. for date_format in date_formats:
  85. try:
  86. trade_date = datetime.strptime(date_str, date_format).date()
  87. break
  88. except ValueError:
  89. continue
  90. if trade_date is None:
  91. return None, None, None, None, None, None, None, None, None
  92. # 提取委托时间
  93. order_time_str = str(row['委托时间']).strip()
  94. try:
  95. time_parts = order_time_str.split(':')
  96. hour = int(time_parts[0])
  97. # 如果委托时间 >= 21:00,需要找到下一个交易日
  98. if hour >= 21:
  99. try:
  100. trade_days = get_trade_days(start_date=trade_date, count=2)
  101. if len(trade_days) >= 2:
  102. next_trade_day = trade_days[1]
  103. if isinstance(next_trade_day, datetime):
  104. actual_trade_date = next_trade_day.date()
  105. elif isinstance(next_trade_day, date):
  106. actual_trade_date = next_trade_day
  107. else:
  108. actual_trade_date = trade_date
  109. else:
  110. actual_trade_date = trade_date
  111. except:
  112. actual_trade_date = trade_date
  113. else:
  114. actual_trade_date = trade_date
  115. except:
  116. actual_trade_date = trade_date
  117. # 提取成交价
  118. try:
  119. trade_price = float(row['成交价'])
  120. except:
  121. return None, None, None, None, None, None, None, None, None
  122. # 提取交易方向和类型
  123. trade_type = str(row['交易类型']).strip()
  124. if '开多' in trade_type:
  125. direction = 'long'
  126. action = 'open'
  127. elif '开空' in trade_type:
  128. direction = 'short'
  129. action = 'open'
  130. elif '平多' in trade_type or '平空' in trade_type:
  131. action = 'close'
  132. direction = 'long' if '平多' in trade_type else 'short'
  133. else:
  134. return None, None, None, None, None, None, None, None, None
  135. # 提取交易对ID和连续交易对ID
  136. trade_pair_id = row.get('交易对ID', 'N/A')
  137. continuous_pair_id = row.get('连续交易对ID', 'N/A')
  138. return contract_code, actual_trade_date, trade_price, direction, action, order_time_str, trade_type, trade_pair_id, continuous_pair_id
  139. except Exception as e:
  140. print(f"提取合约信息时出错: {str(e)}")
  141. return None, None, None, None, None, None, None, None, None
  142. def get_trade_day_range(trade_date, days_before, days_after):
  143. """
  144. 获取交易日范围
  145. """
  146. try:
  147. # 获取历史交易日
  148. trade_days_before = get_trade_days(end_date=trade_date, count=days_before + 1)
  149. if len(trade_days_before) < days_before + 1:
  150. return None, None
  151. first_day = trade_days_before[0]
  152. if isinstance(first_day, datetime):
  153. start_date = first_day.date()
  154. elif isinstance(first_day, date):
  155. start_date = first_day
  156. else:
  157. start_date = first_day
  158. # 获取未来交易日
  159. trade_days_after = get_trade_days(start_date=trade_date, count=days_after + 1)
  160. if len(trade_days_after) < days_after + 1:
  161. return None, None
  162. last_day = trade_days_after[-1]
  163. if isinstance(last_day, datetime):
  164. end_date = last_day.date()
  165. elif isinstance(last_day, date):
  166. end_date = last_day
  167. else:
  168. end_date = last_day
  169. return start_date, end_date
  170. except Exception as e:
  171. print(f"计算交易日范围时出错: {str(e)}")
  172. return None, None
  173. def get_kline_data_with_future(contract_code, trade_date, days_before=100, days_after=20):
  174. """
  175. 获取包含历史和未来数据的K线数据
  176. """
  177. try:
  178. # 获取完整的数据范围
  179. start_date, end_date = get_trade_day_range(trade_date, days_before, days_after)
  180. if start_date is None or end_date is None:
  181. return None, None, None
  182. # 获取K线数据
  183. price_data = get_price(
  184. contract_code,
  185. start_date=start_date,
  186. end_date=end_date,
  187. frequency='1d',
  188. fields=['open', 'close', 'high', 'low']
  189. )
  190. if price_data is None or len(price_data) == 0:
  191. return None, None, None
  192. # 计算均线
  193. price_data['ma5'] = price_data['close'].rolling(window=5).mean()
  194. price_data['ma10'] = price_data['close'].rolling(window=10).mean()
  195. price_data['ma20'] = price_data['close'].rolling(window=20).mean()
  196. price_data['ma30'] = price_data['close'].rolling(window=30).mean()
  197. # 找到交易日在数据中的位置
  198. trade_date_normalized = pd.Timestamp(trade_date)
  199. trade_idx = None
  200. for i, idx in enumerate(price_data.index):
  201. if isinstance(idx, pd.Timestamp):
  202. if idx.date() == trade_date:
  203. trade_idx = i
  204. break
  205. return price_data, trade_idx
  206. except Exception as e:
  207. print(f"获取K线数据时出错: {str(e)}")
  208. return None, None, None
  209. def plot_partial_kline(data, trade_idx, trade_price, direction, contract_code, trade_date, order_time, save_path=None):
  210. """
  211. 绘制部分K线图(仅显示历史数据和当天)
  212. """
  213. try:
  214. # 截取历史数据和当天数据
  215. partial_data = data.iloc[:trade_idx + 1].copy()
  216. # 修改当天的收盘价为成交价
  217. partial_data.iloc[-1, partial_data.columns.get_loc('close')] = trade_price
  218. fig, ax = plt.subplots(figsize=(16, 10))
  219. # 准备数据
  220. dates = partial_data.index
  221. opens = partial_data['open']
  222. highs = partial_data['high']
  223. lows = partial_data['low']
  224. closes = partial_data['close']
  225. # 绘制K线
  226. for i in range(len(partial_data)):
  227. color = 'red' if closes.iloc[i] > opens.iloc[i] else 'green'
  228. edge_color = 'darkred' if closes.iloc[i] > opens.iloc[i] else 'darkgreen'
  229. # 影线
  230. ax.plot([i, i], [lows.iloc[i], highs.iloc[i]], color='black', linewidth=1)
  231. # 实体
  232. body_height = abs(closes.iloc[i] - opens.iloc[i])
  233. if body_height == 0:
  234. body_height = 0.01
  235. bottom = min(opens.iloc[i], closes.iloc[i])
  236. rect = patches.Rectangle((i-0.4, bottom), 0.8, body_height,
  237. linewidth=1, edgecolor=edge_color,
  238. facecolor=color, alpha=0.8)
  239. ax.add_patch(rect)
  240. # 绘制均线
  241. ax.plot(range(len(partial_data)), partial_data['ma5'], label='MA5', color='blue', linewidth=1.5, alpha=0.8)
  242. ax.plot(range(len(partial_data)), partial_data['ma10'], label='MA10', color='orange', linewidth=1.5, alpha=0.8)
  243. ax.plot(range(len(partial_data)), partial_data['ma20'], label='MA20', color='purple', linewidth=1.5, alpha=0.8)
  244. ax.plot(range(len(partial_data)), partial_data['ma30'], label='MA30', color='brown', linewidth=1.5, alpha=0.8)
  245. # 标注开仓位置
  246. ax.plot(trade_idx, trade_price, marker='*', markersize=20,
  247. color='yellow', markeredgecolor='black', markeredgewidth=2,
  248. label='Open Position', zorder=10)
  249. # 添加垂直线
  250. ax.axvline(x=trade_idx, color='yellow', linestyle='--',
  251. linewidth=2, alpha=0.7, zorder=5)
  252. # 标注信息
  253. date_label = trade_date.strftime('%Y-%m-%d')
  254. price_label = f'Price: {trade_price:.2f}'
  255. direction_label = f'Direction: {"Long" if direction == "long" else "Short"}'
  256. time_label = f'Time: {order_time}'
  257. # 计算文本位置
  258. price_range = highs.max() - lows.min()
  259. y_offset = max(price_range * 0.08, (highs.max() - trade_price) * 0.3)
  260. text_y = trade_price + y_offset
  261. if text_y > highs.max():
  262. text_y = trade_price - price_range * 0.08
  263. annotation_text = f'{date_label}\n{price_label}\n{direction_label}\n{time_label}'
  264. ax.text(trade_idx, text_y, annotation_text,
  265. fontsize=10, ha='center', va='bottom',
  266. bbox=dict(boxstyle='round,pad=0.6', facecolor='yellow', alpha=0.9, edgecolor='black', linewidth=1.5),
  267. zorder=11, weight='bold')
  268. # 设置标题和标签
  269. contract_simple = contract_code.split('.')[0]
  270. direction_text = "Long" if direction == "long" else "Short"
  271. ax.set_title(f'{contract_simple} - {direction_text} Position Decision\n'
  272. f'Historical Data + Trade Day Only',
  273. fontsize=14, fontweight='bold', pad=20)
  274. ax.set_xlabel('Time', fontsize=12)
  275. ax.set_ylabel('Price', fontsize=12)
  276. ax.grid(True, alpha=0.3)
  277. ax.legend(loc='lower left', fontsize=10)
  278. # 设置x轴标签
  279. step = max(1, len(partial_data) // 10)
  280. tick_positions = range(0, len(partial_data), step)
  281. tick_labels = []
  282. for pos in tick_positions:
  283. date_val = dates[pos]
  284. if isinstance(date_val, (date, datetime)):
  285. tick_labels.append(date_val.strftime('%Y-%m-%d'))
  286. else:
  287. tick_labels.append(str(date_val))
  288. ax.set_xticks(tick_positions)
  289. ax.set_xticklabels(tick_labels, rotation=45, ha='right')
  290. plt.tight_layout()
  291. if save_path:
  292. plt.savefig(save_path, dpi=CONFIG['plot_dpi'], bbox_inches='tight')
  293. if CONFIG['show_plots']:
  294. plt.show()
  295. plt.close(fig)
  296. except Exception as e:
  297. print(f"绘制部分K线图时出错: {str(e)}")
  298. plt.close('all')
  299. raise
  300. def plot_full_kline(data, trade_idx, trade_price, direction, contract_code, trade_date, order_time, profit_loss, save_path=None):
  301. """
  302. 绘制完整K线图(包含未来数据)
  303. """
  304. try:
  305. fig, ax = plt.subplots(figsize=(16, 10))
  306. # 准备数据
  307. dates = data.index
  308. opens = data['open']
  309. highs = data['high']
  310. lows = data['low']
  311. closes = data['close']
  312. # 绘制K线
  313. for i in range(len(data)):
  314. color = 'red' if closes.iloc[i] > opens.iloc[i] else 'green'
  315. edge_color = 'darkred' if closes.iloc[i] > opens.iloc[i] else 'darkgreen'
  316. # 影线
  317. ax.plot([i, i], [lows.iloc[i], highs.iloc[i]], color='black', linewidth=1)
  318. # 实体
  319. body_height = abs(closes.iloc[i] - opens.iloc[i])
  320. if body_height == 0:
  321. body_height = 0.01
  322. bottom = min(opens.iloc[i], closes.iloc[i])
  323. rect = patches.Rectangle((i-0.4, bottom), 0.8, body_height,
  324. linewidth=1, edgecolor=edge_color,
  325. facecolor=color, alpha=0.8)
  326. ax.add_patch(rect)
  327. # 绘制均线
  328. ax.plot(range(len(data)), data['ma5'], label='MA5', color='blue', linewidth=1.5, alpha=0.8)
  329. ax.plot(range(len(data)), data['ma10'], label='MA10', color='orange', linewidth=1.5, alpha=0.8)
  330. ax.plot(range(len(data)), data['ma20'], label='MA20', color='purple', linewidth=1.5, alpha=0.8)
  331. ax.plot(range(len(data)), data['ma30'], label='MA30', color='brown', linewidth=1.5, alpha=0.8)
  332. # 标注开仓位置
  333. ax.plot(trade_idx, trade_price, marker='*', markersize=20,
  334. color='yellow', markeredgecolor='black', markeredgewidth=2,
  335. label='Open Position', zorder=10)
  336. # 添加垂直线分隔历史和未来
  337. ax.axvline(x=trade_idx, color='yellow', linestyle='--',
  338. linewidth=2, alpha=0.7, zorder=5)
  339. # 添加未来区域背景
  340. ax.axvspan(trade_idx + 0.5, len(data) - 0.5, alpha=0.1, color='gray', label='Future Data')
  341. # 标注信息
  342. date_label = trade_date.strftime('%Y-%m-%d')
  343. price_label = f'Price: {trade_price:.2f}'
  344. direction_label = f'Direction: {"Long" if direction == "long" else "Short"}'
  345. time_label = f'Time: {order_time}'
  346. profit_label = f'P&L: {profit_loss:+.2f}'
  347. # 计算文本位置
  348. price_range = highs.max() - lows.min()
  349. y_offset = max(price_range * 0.08, (highs.max() - trade_price) * 0.3)
  350. text_y = trade_price + y_offset
  351. if text_y > highs.max():
  352. text_y = trade_price - price_range * 0.08
  353. annotation_text = f'{date_label}\n{price_label}\n{direction_label}\n{time_label}\n{profit_label}'
  354. ax.text(trade_idx, text_y, annotation_text,
  355. fontsize=10, ha='center', va='bottom',
  356. bbox=dict(boxstyle='round,pad=0.6', facecolor='yellow', alpha=0.9, edgecolor='black', linewidth=1.5),
  357. zorder=11, weight='bold')
  358. # 设置标题和标签
  359. contract_simple = contract_code.split('.')[0]
  360. direction_text = "Long" if direction == "long" else "Short"
  361. ax.set_title(f'{contract_simple} - {direction_text} Position Result\n'
  362. f'Complete Data with Future {CONFIG["future_days"]} Days',
  363. fontsize=14, fontweight='bold', pad=20)
  364. ax.set_xlabel('Time', fontsize=12)
  365. ax.set_ylabel('Price', fontsize=12)
  366. ax.grid(True, alpha=0.3)
  367. ax.legend(loc='lower left', fontsize=10)
  368. # 设置x轴标签
  369. step = max(1, len(data) // 15)
  370. tick_positions = range(0, len(data), step)
  371. tick_labels = []
  372. for pos in tick_positions:
  373. date_val = dates[pos]
  374. if isinstance(date_val, (date, datetime)):
  375. tick_labels.append(date_val.strftime('%Y-%m-%d'))
  376. else:
  377. tick_labels.append(str(date_val))
  378. ax.set_xticks(tick_positions)
  379. ax.set_xticklabels(tick_labels, rotation=45, ha='right')
  380. plt.tight_layout()
  381. if save_path:
  382. plt.savefig(save_path, dpi=CONFIG['plot_dpi'], bbox_inches='tight')
  383. if CONFIG['show_plots']:
  384. plt.show()
  385. plt.close(fig)
  386. except Exception as e:
  387. print(f"绘制完整K线图时出错: {str(e)}")
  388. plt.close('all')
  389. raise
  390. def load_processed_results(result_path):
  391. """
  392. 加载已处理的结果文件
  393. """
  394. if not os.path.exists(result_path):
  395. return pd.DataFrame(), set()
  396. try:
  397. df = pd.read_csv(result_path)
  398. # 获取已处理的交易对ID
  399. processed_pairs = set(df['交易对ID'].unique())
  400. return df, processed_pairs
  401. except Exception as e:
  402. print(f"加载结果文件时出错: {str(e)}")
  403. return pd.DataFrame(), set()
  404. def calculate_profit_loss(df, trade_pair_id, continuous_pair_id):
  405. """
  406. 计算平仓盈亏
  407. """
  408. try:
  409. if continuous_pair_id != 'N/A' and pd.notna(continuous_pair_id):
  410. # 合并所有同一连续交易对ID的平仓盈亏
  411. close_trades = df[
  412. (df['连续交易对ID'] == continuous_pair_id) &
  413. (df['交易类型'].str[0] == '平')
  414. ]
  415. total_profit = close_trades['平仓盈亏'].sum()
  416. else:
  417. # 只查找当前交易对ID的平仓交易
  418. close_trades = df[
  419. (df['交易对ID'] == trade_pair_id) &
  420. (df['交易类型'].str[0] == '平')
  421. ]
  422. if len(close_trades) > 0:
  423. total_profit = close_trades['平仓盈亏'].iloc[0]
  424. else:
  425. total_profit = 0
  426. return total_profit
  427. except Exception as e:
  428. print(f"计算盈亏时出错: {str(e)}")
  429. return 0
  430. def record_result(result_data, result_path):
  431. """
  432. 记录训练结果
  433. """
  434. try:
  435. # 创建结果DataFrame
  436. result_df = pd.DataFrame([result_data])
  437. # 如果文件已存在,追加写入;否则创建新文件
  438. if os.path.exists(result_path):
  439. result_df.to_csv(result_path, mode='a', header=False, index=False, encoding='utf-8-sig')
  440. else:
  441. result_df.to_csv(result_path, mode='w', header=True, index=False, encoding='utf-8-sig')
  442. print(f"结果已记录到: {result_path}")
  443. except Exception as e:
  444. print(f"记录结果时出错: {str(e)}")
  445. def get_user_decision():
  446. """
  447. 获取用户的开仓决策
  448. """
  449. while True:
  450. decision = input("\n是否开仓?请输入 'y' (开仓) 或 'n' (不开仓): ").strip().lower()
  451. if decision in ['y', 'yes', '是', '开仓']:
  452. return True
  453. elif decision in ['n', 'no', '否', '不开仓']:
  454. return False
  455. else:
  456. print("请输入有效的选项: 'y' 或 'n'")
  457. def main():
  458. """
  459. 主函数
  460. """
  461. print("=" * 60)
  462. print("交易训练工具")
  463. print("=" * 60)
  464. # 设置随机种子
  465. if CONFIG['random_seed'] is not None:
  466. random.seed(CONFIG['random_seed'])
  467. np.random.seed(CONFIG['random_seed'])
  468. # 获取当前目录
  469. current_dir = _get_current_directory()
  470. csv_path = os.path.join(current_dir, CONFIG['csv_filename'])
  471. result_path = os.path.join(current_dir, CONFIG['result_filename'])
  472. output_dir = os.path.join(current_dir, CONFIG['output_dir'])
  473. # 创建输出目录
  474. os.makedirs(output_dir, exist_ok=True)
  475. # 1. 读取交易数据
  476. print("\n=== 步骤1: 读取交易数据 ===")
  477. transaction_df = read_transaction_data(csv_path)
  478. if len(transaction_df) == 0:
  479. print("未能读取交易数据,退出")
  480. return
  481. # 2. 加载已处理的结果
  482. print("\n=== 步骤2: 加载已处理记录 ===")
  483. existing_results, processed_pairs = load_processed_results(result_path)
  484. print(f"已处理 {len(processed_pairs)} 个交易对")
  485. # 3. 提取所有开仓交易
  486. print("\n=== 步骤3: 提取开仓交易 ===")
  487. open_trades = []
  488. for idx, row in transaction_df.iterrows():
  489. contract_code, trade_date, trade_price, direction, action, order_time, trade_type, trade_pair_id, continuous_pair_id = extract_contract_info(row)
  490. if contract_code is None or action != 'open':
  491. continue
  492. # 跳过已处理的交易对
  493. if trade_pair_id in processed_pairs:
  494. continue
  495. # 查找对应的平仓交易
  496. profit_loss = calculate_profit_loss(transaction_df, trade_pair_id, continuous_pair_id)
  497. open_trades.append({
  498. 'index': idx,
  499. 'contract_code': contract_code,
  500. 'trade_date': trade_date,
  501. 'trade_price': trade_price,
  502. 'direction': direction,
  503. 'order_time': order_time,
  504. 'trade_type': trade_type,
  505. 'trade_pair_id': trade_pair_id,
  506. 'continuous_pair_id': continuous_pair_id,
  507. 'profit_loss': profit_loss,
  508. 'original_row': row
  509. })
  510. print(f"找到 {len(open_trades)} 个未处理的开仓交易")
  511. if len(open_trades) == 0:
  512. print("没有未处理的开仓交易,退出")
  513. return
  514. # 4. 随机选择一个交易(完全随机)
  515. print("\n=== 步骤4: 随机选择交易 ===")
  516. # 打乱交易列表顺序确保完全随机
  517. random.shuffle(open_trades)
  518. selected_trade = random.choice(open_trades)
  519. print(f"选中交易: {selected_trade['contract_code']} - {selected_trade['trade_date']} - {selected_trade['direction']}")
  520. print(f"剩余未处理交易: {len(open_trades) - 1} 个")
  521. # 5. 获取K线数据
  522. print("\n=== 步骤5: 获取K线数据 ===")
  523. kline_data, trade_idx = get_kline_data_with_future(
  524. selected_trade['contract_code'],
  525. selected_trade['trade_date'],
  526. CONFIG['history_days'],
  527. CONFIG['future_days']
  528. )
  529. if kline_data is None or trade_idx is None:
  530. print("获取K线数据失败,退出")
  531. return
  532. # 6. 显示部分K线图
  533. print("\n=== 步骤6: 显示部分K线图 ===")
  534. partial_image_path = os.path.join(output_dir, f"partial_{selected_trade['trade_pair_id']}.png")
  535. plot_partial_kline(
  536. kline_data, trade_idx, selected_trade['trade_price'],
  537. selected_trade['direction'], selected_trade['contract_code'],
  538. selected_trade['trade_date'], selected_trade['order_time'],
  539. partial_image_path
  540. )
  541. # 7. 获取用户决策
  542. print(f"\n交易信息:")
  543. print(f"合约: {selected_trade['contract_code']}")
  544. print(f"日期: {selected_trade['trade_date']}")
  545. print(f"方向: {'多头' if selected_trade['direction'] == 'long' else '空头'}")
  546. print(f"成交价: {selected_trade['trade_price']}")
  547. user_decision = get_user_decision()
  548. # 8. 显示完整K线图
  549. print("\n=== 步骤7: 显示完整K线图 ===")
  550. full_image_path = os.path.join(output_dir, f"full_{selected_trade['trade_pair_id']}.png")
  551. plot_full_kline(
  552. kline_data, trade_idx, selected_trade['trade_price'],
  553. selected_trade['direction'], selected_trade['contract_code'],
  554. selected_trade['trade_date'], selected_trade['order_time'],
  555. selected_trade['profit_loss'],
  556. full_image_path
  557. )
  558. # 9. 记录结果
  559. print("\n=== 步骤8: 记录结果 ===")
  560. # 计算判定收益
  561. decision_profit = selected_trade['profit_loss'] if user_decision else -selected_trade['profit_loss']
  562. result_data = {
  563. '日期': selected_trade['original_row']['日期'],
  564. '委托时间': selected_trade['original_row']['委托时间'],
  565. '标的': selected_trade['original_row']['标的'],
  566. '交易类型': selected_trade['original_row']['交易类型'],
  567. '成交数量': selected_trade['original_row']['成交数量'],
  568. '成交价': selected_trade['original_row']['成交价'],
  569. '平仓盈亏': selected_trade['profit_loss'],
  570. '用户判定': '开仓' if user_decision else '不开仓',
  571. '判定收益': decision_profit,
  572. '交易对ID': selected_trade['trade_pair_id'],
  573. '连续交易对ID': selected_trade['continuous_pair_id']
  574. }
  575. record_result(result_data, result_path)
  576. print(f"\n=== 训练完成 ===")
  577. print(f"用户判定: {'开仓' if user_decision else '不开仓'}")
  578. print(f"实际盈亏: {selected_trade['profit_loss']:+.2f}")
  579. print(f"判定收益: {decision_profit:+.2f}")
  580. print(f"结果已保存到: {result_path}")
  581. if __name__ == "__main__":
  582. main()