simulator_wqb.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. """
  2. Enhanced Alpha Template Generator Script
  3. This script generates alpha templates with interactive user input for:
  4. - JSON file path selection
  5. - User authentication
  6. - Simulation parameters
  7. - Multi-simulation mode support
  8. - Real-time log monitoring
  9. """
  10. import asyncio
  11. import wqb
  12. import json
  13. import os
  14. import getpass
  15. import threading
  16. import time
  17. import sys
  18. import msvcrt # For Windows password input with asterisks
  19. from pathlib import Path
  20. def get_password_with_asterisks(prompt):
  21. """Get password input with asterisks shown for each character"""
  22. print(prompt, end='', flush=True)
  23. password = ""
  24. while True:
  25. char = msvcrt.getch()
  26. # Handle Enter key (carriage return)
  27. if char == b'\r':
  28. print() # New line
  29. break
  30. # Handle Backspace
  31. elif char == b'\x08':
  32. if len(password) > 0:
  33. password = password[:-1]
  34. # Move cursor back, print space, move cursor back again
  35. print('\b \b', end='', flush=True)
  36. # Handle Ctrl+C
  37. elif char == b'\x03':
  38. print()
  39. raise KeyboardInterrupt
  40. # Handle regular characters
  41. else:
  42. try:
  43. # Convert bytes to string
  44. char_str = char.decode('utf-8')
  45. if char_str.isprintable():
  46. password += char_str
  47. print('*', end='', flush=True)
  48. except UnicodeDecodeError:
  49. pass # Ignore non-printable characters
  50. return password
  51. def get_json_filepath():
  52. """Ask user to input the directory/filepath of expressions_with_settings.json"""
  53. while True:
  54. print("\n" + "="*60)
  55. print("JSON 文件配置")
  56. print("="*60)
  57. filepath = input("请复制粘贴 expressions_with_settings.json (即以json格式储存的带有setting的表达式列表)的目录或完整路径: ").strip()
  58. # Remove quotes if user copied with quotes
  59. filepath = filepath.strip('"').strip("'")
  60. # Check if it's a directory and try to find the file
  61. if os.path.isdir(filepath):
  62. json_path = os.path.join(filepath, "expressions_with_settings.json")
  63. else:
  64. json_path = filepath
  65. # Verify file exists
  66. if os.path.exists(json_path):
  67. try:
  68. with open(json_path, 'r') as f:
  69. data = json.load(f)
  70. print(f"✓ 成功加载 JSON 文件: {json_path}")
  71. return json_path, data
  72. except json.JSONDecodeError:
  73. print("❌ 错误: JSON 文件格式无效,请检查文件。")
  74. except Exception as e:
  75. print(f"❌ 读取文件错误: {e}")
  76. else:
  77. print("❌ 错误: 文件未找到,请检查路径后重试。")
  78. def get_user_credentials():
  79. """Ask user for brain username and password with asterisk password input"""
  80. print("\n" + "="*60)
  81. print("BRAIN 身份验证")
  82. print("="*60)
  83. username = input("请输入您的 BRAIN 用户名: ").strip()
  84. password = get_password_with_asterisks("请输入您的 BRAIN 密码 (显示为 *): ")
  85. return username, password
  86. def test_authentication(username, password):
  87. """Test authentication and return session if successful"""
  88. print("\n" + "="*60)
  89. print("API连通验证")
  90. print("="*60)
  91. try:
  92. logger = wqb.wqb_logger()
  93. wqbs = wqb.WQBSession((username, password), logger=logger)
  94. # Test connection
  95. resp = wqbs.locate_field('open')
  96. print(f"连接测试结果: resp.ok = {resp.ok}")
  97. if resp.ok:
  98. print("✓ 身份验证成功!")
  99. return wqbs, logger
  100. else:
  101. print("❌ 身份验证失败,请检查您的用户名和密码。")
  102. return None, None
  103. except Exception as e:
  104. print(f"❌ 身份验证错误: {e}")
  105. return None, None
  106. def get_simulation_parameters(expressions_count, json_path):
  107. """Get simulation parameters from user with validation"""
  108. print("\n" + "="*60)
  109. print("回测参数设置")
  110. print("="*60)
  111. print(f"JSON 中的表达式总数: {expressions_count}")
  112. # Get starting position
  113. while True:
  114. try:
  115. where_to_start = int(input(f"从列表中第几个表达式开始 (0 到 {expressions_count-1}): "))
  116. if 0 <= where_to_start < expressions_count:
  117. if where_to_start > 0:
  118. print(f"\n⚠️ 警告: 原始 JSON 文件将被直接覆盖!")
  119. print(f"📝 原始文件: {expressions_count} 个表达式")
  120. print(f"🔪 切割后: {expressions_count - where_to_start} 个表达式")
  121. print(f"📂 文件位置: {json_path}")
  122. print(f"\n🚨 重要提示: 如果您不希望覆盖原始文件,请立即关闭终端并手动备份文件!")
  123. print(f"⏰ 5秒后将继续执行覆盖操作...")
  124. # Give user 5 seconds to think/close terminal
  125. import time
  126. for i in range(5, 0, -1):
  127. print(f"倒计时: {i} 秒...", end='\r')
  128. time.sleep(1)
  129. print(" ") # Clear countdown line
  130. confirm = input("(继续程序,开始回测y/返回并重选列表起始位置n): ").lower().strip()
  131. if confirm != 'y':
  132. print("请重新选择表达式列表起始位置。")
  133. continue
  134. break
  135. else:
  136. print(f"❌ 起始位置无效,必须在 0 到 {expressions_count-1} 之间")
  137. except ValueError:
  138. print("❌ 请输入有效数字。")
  139. # Get concurrent count
  140. while True:
  141. try:
  142. concurrent_count = int(input("请输入并发回测数量 (最小值 1): "))
  143. if concurrent_count >= 1:
  144. break
  145. else:
  146. print("❌ 并发数量必须大于等于 1。")
  147. except ValueError:
  148. print("❌ 请输入有效数字。")
  149. return where_to_start, concurrent_count
  150. def cut_json_file(json_path, expressions_with_settings, where_to_start):
  151. """Cut the JSON file from the starting point and overwrite the original file"""
  152. if where_to_start == 0:
  153. return expressions_with_settings # No cutting needed
  154. # Cut the expressions list
  155. cut_expressions = expressions_with_settings[where_to_start:]
  156. # Overwrite the original JSON file
  157. try:
  158. with open(json_path, 'w', encoding='utf-8') as f:
  159. json.dump(cut_expressions, f, ensure_ascii=False, indent=2)
  160. print(f"✓ 原始 JSON 文件已被覆盖")
  161. print(f"📊 新文件包含 {len(cut_expressions)} 个表达式")
  162. return cut_expressions
  163. except Exception as e:
  164. print(f"❌ 覆盖 JSON 文件失败: {e}")
  165. print(f"⚠️ 将使用原始数据继续运行")
  166. return expressions_with_settings
  167. def shuffle_json_file(json_path, expressions_with_settings):
  168. """Randomly shuffle the JSON elements and overwrite the file"""
  169. import random
  170. # Create a copy and shuffle it
  171. shuffled_expressions = expressions_with_settings.copy()
  172. random.shuffle(shuffled_expressions)
  173. # Overwrite the JSON file with shuffled data
  174. try:
  175. with open(json_path, 'w', encoding='utf-8') as f:
  176. json.dump(shuffled_expressions, f, ensure_ascii=False, indent=2)
  177. print(f"✓ JSON 文件已随机打乱并覆盖")
  178. print(f"🔀 已打乱 {len(shuffled_expressions)} 个表达式的顺序")
  179. return shuffled_expressions
  180. except Exception as e:
  181. print(f"❌ 打乱 JSON 文件失败: {e}")
  182. print(f"⚠️ 将使用原始顺序继续运行")
  183. return expressions_with_settings
  184. def get_random_shuffle_choice():
  185. """Ask user if they want to randomly shuffle the expressions"""
  186. print("\n" + "="*60)
  187. print("随机模式选择")
  188. print("="*60)
  189. print("是否要随机打乱表达式顺序?")
  190. print("💡 这将改变表达式在文件中的排列顺序,以达到随机回测的目的")
  191. while True:
  192. choice = input("选择随机模式? (y/n): ").lower().strip()
  193. if choice in ['y', 'n']:
  194. return choice == 'y'
  195. else:
  196. print("❌ 请输入 y 或 n")
  197. def get_multi_simulation_choice():
  198. """Ask user if they want to use multi-simulation mode"""
  199. print("\n" + "="*60)
  200. print("多重回测(multi-simulatioin)模式选择")
  201. print("="*60)
  202. print("是否要使用多重回测(multi-simulatioin)模式?")
  203. print("💡 多重回测(multi-simulatioin)可以将多个alpha组合在一个回测槽中运行")
  204. while True:
  205. choice = input("使用多重回测(multi-simulatioin)模式? (y/n): ").lower().strip()
  206. if choice in ['y', 'n']:
  207. return choice == 'y'
  208. else:
  209. print("❌ 请输入 y 或 n")
  210. def get_alpha_count_per_slot():
  211. """Ask user how many alphas to put in one multi-simulation slot"""
  212. print("\n" + "="*60)
  213. print("多重回测(multi-simulatioin)槽配置")
  214. print("="*60)
  215. print("每个多重回测(multi-simulatioin)槽中放置多少个alpha?")
  216. print("💡 范围: 2-10 个alpha")
  217. while True:
  218. try:
  219. alpha_count = int(input("每个槽的alpha数量 (2-10): "))
  220. if 2 <= alpha_count <= 10:
  221. return alpha_count
  222. else:
  223. print("❌ 数量必须在 2 到 10 之间")
  224. except ValueError:
  225. print("❌ 请输入有效数字。")
  226. def monitor_log_file(logger, stop_event, use_multi_sim=False, alpha_count_per_slot=None):
  227. """Monitor log file and print new lines in real-time"""
  228. print("\n📊 开始监控日志文件...")
  229. # Get current directory to look for log files
  230. current_dir = os.getcwd()
  231. log_file_path = None
  232. # First, try to find any existing wqb log files (including older ones)
  233. print("🔍 查找 WQB 日志文件...")
  234. # Look for any wqb*.log files in current directory
  235. wqb_files = []
  236. try:
  237. for file in os.listdir(current_dir):
  238. if file.startswith('wqb') and file.endswith('.log'):
  239. file_path = os.path.join(current_dir, file)
  240. wqb_files.append((file_path, os.path.getmtime(file_path)))
  241. except Exception as e:
  242. print(f"⚠️ 扫描目录失败: {e}")
  243. return
  244. if wqb_files:
  245. # Sort by modification time, get the newest one
  246. log_file_path = sorted(wqb_files, key=lambda x: x[1], reverse=True)[0][0]
  247. print(f"✓ 监控已找到的最新日志文件: {log_file_path}")
  248. else:
  249. # Wait for new log file to be created
  250. print("等待新的 WQB 日志文件创建...")
  251. start_time = time.time()
  252. while not stop_event.is_set() and (time.time() - start_time) < 30: # Wait max 30 seconds
  253. try:
  254. for file in os.listdir(current_dir):
  255. if file.startswith('wqb') and file.endswith('.log'):
  256. file_path = os.path.join(current_dir, file)
  257. # Check if file was created recently (within last 120 seconds)
  258. if os.path.getctime(file_path) > (time.time() - 120):
  259. log_file_path = file_path
  260. break
  261. except Exception:
  262. pass
  263. if log_file_path:
  264. break
  265. time.sleep(1)
  266. if not log_file_path:
  267. print("⚠️ 未找到 WQB 日志文件,日志监控已禁用。")
  268. print("💡 提示: 日志文件通常在开始回测后才会创建")
  269. return
  270. else:
  271. print(f"✓ 找到新日志文件: {log_file_path}")
  272. if stop_event.is_set():
  273. return
  274. print("="*60)
  275. # Display multi-simulation information if applicable
  276. if use_multi_sim and alpha_count_per_slot:
  277. print("📌 重要提示:")
  278. print(f"以下是multi simulation的记录,你的设计是1个multi simulation中有{alpha_count_per_slot}个alpha,")
  279. print(f"因此需将实际回测数乘以该乘数,才得到实际已完成的Alpha个数。")
  280. print("="*60)
  281. try:
  282. # Start monitoring from current end of file
  283. with open(log_file_path, 'r', encoding='utf-8') as f:
  284. # Go to end of file
  285. f.seek(0, 2)
  286. while not stop_event.is_set():
  287. line = f.readline()
  288. if line:
  289. # Clean up the log line and print it
  290. clean_line = line.rstrip()
  291. if clean_line: # Only print non-empty lines
  292. print(f"[日志] {clean_line}")
  293. else:
  294. time.sleep(0.2)
  295. except Exception as e:
  296. print(f"⚠️ 监控日志文件时出错: {e}")
  297. async def automated_main(json_file_content, username, password, start_position=0, concurrent_count=3,
  298. random_shuffle=False, use_multi_sim=False, alpha_count_per_slot=3):
  299. """Automated main function for web interface - takes all parameters at once"""
  300. try:
  301. print("🧠 BRAIN Alpha 模板生成器 (自动模式)")
  302. print("="*60)
  303. # Parse JSON content directly
  304. import json
  305. expressions_with_settings = json.loads(json_file_content)
  306. expressions_count = len(expressions_with_settings)
  307. print(f"📊 已加载 {expressions_count} 个 alpha 配置")
  308. # Setup logger and session
  309. logger = wqb.wqb_logger()
  310. wqbs = wqb.WQBSession((username, password), logger=logger)
  311. # Test connection
  312. resp = wqbs.locate_field('open')
  313. print(f"连接测试结果: resp.ok = {resp.ok}")
  314. if not resp.ok:
  315. print("❌ 身份验证失败")
  316. return {"success": False, "error": "Authentication failed"}
  317. print("✅ 身份验证成功!")
  318. # Process expressions based on parameters
  319. if start_position > 0:
  320. expressions_with_settings = expressions_with_settings[start_position:]
  321. print(f"🔪 已从位置 {start_position} 开始切割,剩余 {len(expressions_with_settings)} 个表达式")
  322. if random_shuffle:
  323. import random
  324. random.shuffle(expressions_with_settings)
  325. print(f"🔀 已随机打乱 {len(expressions_with_settings)} 个表达式的顺序")
  326. if use_multi_sim:
  327. # Convert to multi-alphas format
  328. original_count = len(expressions_with_settings)
  329. expressions_with_settings = wqb.to_multi_alphas(expressions_with_settings, alpha_count_per_slot)
  330. print(f"✓ 已转换为多重回测(multi-simulatioin)格式")
  331. print(f"📊 原始表达式数: {original_count}")
  332. print(f"🎯 每槽alpha数: {alpha_count_per_slot}")
  333. # Write multi-simulation info to log
  334. multi_sim_msg = (f"[MULTI-SIMULATION MODE] 以下是multi simulation的记录,"
  335. f"你的设计是1个multi simulation中有{alpha_count_per_slot}个alpha,"
  336. f"因此需将实际回测数乘以该乘数,才得到实际已完成的Alpha个数。")
  337. logger.info("="*80)
  338. logger.info(multi_sim_msg)
  339. logger.info("="*80)
  340. print(f"🔄 使用 {concurrent_count} 个并发回测")
  341. print("\n" + "="*60)
  342. print("运行回测")
  343. print("="*60)
  344. if use_multi_sim:
  345. print("开始多重回测(multi-simulatioin)并发回测...")
  346. else:
  347. print("开始并发回测...")
  348. # Run simulations
  349. resps = await wqbs.concurrent_simulate(
  350. expressions_with_settings,
  351. concurrent_count,
  352. log_gap=10
  353. )
  354. # Collect results
  355. alpha_ids = []
  356. successful_count = 0
  357. failed_count = 0
  358. print("\n" + "="*60)
  359. print("回测结果")
  360. print("="*60)
  361. if use_multi_sim:
  362. print(f"成功完成 {len(resps)} 个多重回测(multi-simulatioin)槽的回测")
  363. else:
  364. print(f"成功完成 {len(resps)} 个回测")
  365. print("\nAlpha IDs:")
  366. for i, resp in enumerate(resps):
  367. try:
  368. alpha_id = resp.json()['alpha']
  369. alpha_ids.append(alpha_id)
  370. successful_count += 1
  371. print(f" {i+1:4d}. {alpha_id}")
  372. except Exception as e:
  373. failed_count += 1
  374. print(f" {i+1:4d}. 错误: {e}")
  375. print("\n✅ 处理完成!")
  376. return {
  377. "success": True,
  378. "results": {
  379. "total": len(resps),
  380. "successful": successful_count,
  381. "failed": failed_count,
  382. "alphaIds": alpha_ids,
  383. "use_multi_sim": use_multi_sim,
  384. "alpha_count_per_slot": alpha_count_per_slot if use_multi_sim else None
  385. }
  386. }
  387. except Exception as e:
  388. print(f"\n❌ 错误: {e}")
  389. return {"success": False, "error": str(e)}
  390. async def main():
  391. """Main function with interactive workflow"""
  392. print("🧠 BRAIN Alpha 模板生成器")
  393. print("="*60)
  394. # Step 1: Get JSON file and load expressions
  395. json_path, expressions_with_settings = get_json_filepath()
  396. expressions_count = len(expressions_with_settings)
  397. print(f"\n📊 已从以下位置加载 {expressions_count} 个 alpha 配置:")
  398. print(f" {json_path}")
  399. # Step 2: Get credentials and test authentication
  400. wqbs = None
  401. logger = None
  402. while wqbs is None:
  403. username, password = get_user_credentials()
  404. wqbs, logger = test_authentication(username, password)
  405. if wqbs is None:
  406. retry = input("\n是否要重试? (y/n): ").lower().strip()
  407. if retry != 'y':
  408. print("正在退出...")
  409. return
  410. # Step 3: Get simulation parameters
  411. where_to_start, concurrent_count = get_simulation_parameters(expressions_count, json_path)
  412. # Step 3.5: Cut JSON file if needed
  413. if where_to_start > 0:
  414. print(f"\n🔪 正在切割 JSON 文件...")
  415. expressions_with_settings = cut_json_file(json_path, expressions_with_settings, where_to_start)
  416. where_to_start = 0 # Reset to 0 since we cut the file
  417. # Step 3.6: Ask for random shuffle option
  418. if get_random_shuffle_choice():
  419. print(f"\n🔀 正在随机打乱表达式顺序...")
  420. expressions_with_settings = shuffle_json_file(json_path, expressions_with_settings)
  421. # Step 3.7: Ask for multi-simulation mode
  422. use_multi_sim = get_multi_simulation_choice()
  423. alpha_count_per_slot = None
  424. if use_multi_sim:
  425. alpha_count_per_slot = get_alpha_count_per_slot()
  426. # Convert to multi-alphas format
  427. original_count = len(expressions_with_settings)
  428. expressions_with_settings = wqb.to_multi_alphas(expressions_with_settings, alpha_count_per_slot)
  429. print(f"\n✓ 已转换为多重回测(multi-simulatioin)格式")
  430. print(f"📊 原始表达式数: {original_count}")
  431. print(f"🎯 每槽alpha数: {alpha_count_per_slot}")
  432. # Calculate how many expressions will be processed
  433. print(f"🔄 使用 {concurrent_count} 个并发回测")
  434. # Step 4: Write multi-simulation info to log if applicable
  435. if use_multi_sim and alpha_count_per_slot and logger:
  436. multi_sim_msg = (f"[MULTI-SIMULATION MODE] 以下是multi simulation的记录,"
  437. f"你的设计是1个multi simulation中有{alpha_count_per_slot}个alpha,"
  438. f"因此需将实际回测数乘以该乘数,才得到实际已完成的Alpha个数。")
  439. logger.info("="*80)
  440. logger.info(multi_sim_msg)
  441. logger.info("="*80)
  442. # Step 5: Start log monitoring in background
  443. stop_log_monitor = threading.Event()
  444. log_thread = threading.Thread(
  445. target=monitor_log_file,
  446. args=(logger, stop_log_monitor, use_multi_sim, alpha_count_per_slot),
  447. daemon=True
  448. )
  449. log_thread.start()
  450. # Step 6: Run simulations
  451. print("\n" + "="*60)
  452. print("运行回测")
  453. print("="*60)
  454. if use_multi_sim:
  455. print("开始多重回测(multi-simulatioin)并发回测...")
  456. else:
  457. print("开始并发回测...")
  458. try:
  459. resps = await wqbs.concurrent_simulate(
  460. expressions_with_settings,
  461. concurrent_count,
  462. log_gap=10
  463. )
  464. # Stop log monitoring
  465. stop_log_monitor.set()
  466. # Print results
  467. print("\n" + "="*60)
  468. print("回测结果")
  469. print("="*60)
  470. if use_multi_sim:
  471. print(f"成功完成 {len(resps)} 个多重回测(multi-simulatioin)槽的回测")
  472. else:
  473. print(f"成功完成 {len(resps)} 个回测")
  474. print("\nAlpha IDs:")
  475. for i, resp in enumerate(resps):
  476. try:
  477. alpha_id = resp.json()['alpha']
  478. print(f" {i+1:4d}. {alpha_id}")
  479. except Exception as e:
  480. print(f" {i+1:4d}. 错误: {e}")
  481. except KeyboardInterrupt:
  482. print("\n\n⚠️ 回测被用户中断")
  483. stop_log_monitor.set()
  484. except Exception as e:
  485. print(f"\n❌ 回测错误: {e}")
  486. stop_log_monitor.set()
  487. print("\n✅ 处理完成!")
  488. if __name__ == '__main__':
  489. asyncio.run(main())