Files
alpha_tools/backup_code/wqb-server/simulator/simulator_wqb.py
T
2026-02-05 10:18:53 +08:00

583 lines
22 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Enhanced Alpha Template Generator Script
This script generates alpha templates with interactive user input for:
- JSON file path selection
- User authentication
- Simulation parameters
- Multi-simulation mode support
- Real-time log monitoring
"""
import asyncio
import wqb
import json
import os
import getpass
import threading
import time
import sys
import msvcrt # For Windows password input with asterisks
from pathlib import Path
def get_password_with_asterisks(prompt):
"""Get password input with asterisks shown for each character"""
print(prompt, end='', flush=True)
password = ""
while True:
char = msvcrt.getch()
# Handle Enter key (carriage return)
if char == b'\r':
print() # New line
break
# Handle Backspace
elif char == b'\x08':
if len(password) > 0:
password = password[:-1]
# Move cursor back, print space, move cursor back again
print('\b \b', end='', flush=True)
# Handle Ctrl+C
elif char == b'\x03':
print()
raise KeyboardInterrupt
# Handle regular characters
else:
try:
# Convert bytes to string
char_str = char.decode('utf-8')
if char_str.isprintable():
password += char_str
print('*', end='', flush=True)
except UnicodeDecodeError:
pass # Ignore non-printable characters
return password
def get_json_filepath():
"""Ask user to input the directory/filepath of expressions_with_settings.json"""
while True:
print("\n" + "="*60)
print("JSON 文件配置")
print("="*60)
filepath = input("请复制粘贴 expressions_with_settings.json (即以json格式储存的带有setting的表达式列表)的目录或完整路径: ").strip()
# Remove quotes if user copied with quotes
filepath = filepath.strip('"').strip("'")
# Check if it's a directory and try to find the file
if os.path.isdir(filepath):
json_path = os.path.join(filepath, "expressions_with_settings.json")
else:
json_path = filepath
# Verify file exists
if os.path.exists(json_path):
try:
with open(json_path, 'r') as f:
data = json.load(f)
print(f"✓ 成功加载 JSON 文件: {json_path}")
return json_path, data
except json.JSONDecodeError:
print("❌ 错误: JSON 文件格式无效,请检查文件。")
except Exception as e:
print(f"❌ 读取文件错误: {e}")
else:
print("❌ 错误: 文件未找到,请检查路径后重试。")
def get_user_credentials():
"""Ask user for brain username and password with asterisk password input"""
print("\n" + "="*60)
print("BRAIN 身份验证")
print("="*60)
username = input("请输入您的 BRAIN 用户名: ").strip()
password = get_password_with_asterisks("请输入您的 BRAIN 密码 (显示为 *): ")
return username, password
def test_authentication(username, password):
"""Test authentication and return session if successful"""
print("\n" + "="*60)
print("API连通验证")
print("="*60)
try:
logger = wqb.wqb_logger()
wqbs = wqb.WQBSession((username, password), logger=logger)
# Test connection
resp = wqbs.locate_field('open')
print(f"连接测试结果: resp.ok = {resp.ok}")
if resp.ok:
print("✓ 身份验证成功!")
return wqbs, logger
else:
print("❌ 身份验证失败,请检查您的用户名和密码。")
return None, None
except Exception as e:
print(f"❌ 身份验证错误: {e}")
return None, None
def get_simulation_parameters(expressions_count, json_path):
"""Get simulation parameters from user with validation"""
print("\n" + "="*60)
print("回测参数设置")
print("="*60)
print(f"JSON 中的表达式总数: {expressions_count}")
# Get starting position
while True:
try:
where_to_start = int(input(f"从列表中第几个表达式开始 (0 到 {expressions_count-1}): "))
if 0 <= where_to_start < expressions_count:
if where_to_start > 0:
print(f"\n⚠️ 警告: 原始 JSON 文件将被直接覆盖!")
print(f"📝 原始文件: {expressions_count} 个表达式")
print(f"🔪 切割后: {expressions_count - where_to_start} 个表达式")
print(f"📂 文件位置: {json_path}")
print(f"\n🚨 重要提示: 如果您不希望覆盖原始文件,请立即关闭终端并手动备份文件!")
print(f"⏰ 5秒后将继续执行覆盖操作...")
# Give user 5 seconds to think/close terminal
import time
for i in range(5, 0, -1):
print(f"倒计时: {i} 秒...", end='\r')
time.sleep(1)
print(" ") # Clear countdown line
confirm = input("(继续程序,开始回测y/返回并重选列表起始位置n): ").lower().strip()
if confirm != 'y':
print("请重新选择表达式列表起始位置。")
continue
break
else:
print(f"❌ 起始位置无效,必须在 0 到 {expressions_count-1} 之间")
except ValueError:
print("❌ 请输入有效数字。")
# Get concurrent count
while True:
try:
concurrent_count = int(input("请输入并发回测数量 (最小值 1): "))
if concurrent_count >= 1:
break
else:
print("❌ 并发数量必须大于等于 1。")
except ValueError:
print("❌ 请输入有效数字。")
return where_to_start, concurrent_count
def cut_json_file(json_path, expressions_with_settings, where_to_start):
"""Cut the JSON file from the starting point and overwrite the original file"""
if where_to_start == 0:
return expressions_with_settings # No cutting needed
# Cut the expressions list
cut_expressions = expressions_with_settings[where_to_start:]
# Overwrite the original JSON file
try:
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(cut_expressions, f, ensure_ascii=False, indent=2)
print(f"✓ 原始 JSON 文件已被覆盖")
print(f"📊 新文件包含 {len(cut_expressions)} 个表达式")
return cut_expressions
except Exception as e:
print(f"❌ 覆盖 JSON 文件失败: {e}")
print(f"⚠️ 将使用原始数据继续运行")
return expressions_with_settings
def shuffle_json_file(json_path, expressions_with_settings):
"""Randomly shuffle the JSON elements and overwrite the file"""
import random
# Create a copy and shuffle it
shuffled_expressions = expressions_with_settings.copy()
random.shuffle(shuffled_expressions)
# Overwrite the JSON file with shuffled data
try:
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(shuffled_expressions, f, ensure_ascii=False, indent=2)
print(f"✓ JSON 文件已随机打乱并覆盖")
print(f"🔀 已打乱 {len(shuffled_expressions)} 个表达式的顺序")
return shuffled_expressions
except Exception as e:
print(f"❌ 打乱 JSON 文件失败: {e}")
print(f"⚠️ 将使用原始顺序继续运行")
return expressions_with_settings
def get_random_shuffle_choice():
"""Ask user if they want to randomly shuffle the expressions"""
print("\n" + "="*60)
print("随机模式选择")
print("="*60)
print("是否要随机打乱表达式顺序?")
print("💡 这将改变表达式在文件中的排列顺序,以达到随机回测的目的")
while True:
choice = input("选择随机模式? (y/n): ").lower().strip()
if choice in ['y', 'n']:
return choice == 'y'
else:
print("❌ 请输入 y 或 n")
def get_multi_simulation_choice():
"""Ask user if they want to use multi-simulation mode"""
print("\n" + "="*60)
print("多重回测(multi-simulatioin)模式选择")
print("="*60)
print("是否要使用多重回测(multi-simulatioin)模式?")
print("💡 多重回测(multi-simulatioin)可以将多个alpha组合在一个回测槽中运行")
while True:
choice = input("使用多重回测(multi-simulatioin)模式? (y/n): ").lower().strip()
if choice in ['y', 'n']:
return choice == 'y'
else:
print("❌ 请输入 y 或 n")
def get_alpha_count_per_slot():
"""Ask user how many alphas to put in one multi-simulation slot"""
print("\n" + "="*60)
print("多重回测(multi-simulatioin)槽配置")
print("="*60)
print("每个多重回测(multi-simulatioin)槽中放置多少个alpha?")
print("💡 范围: 2-10 个alpha")
while True:
try:
alpha_count = int(input("每个槽的alpha数量 (2-10): "))
if 2 <= alpha_count <= 10:
return alpha_count
else:
print("❌ 数量必须在 2 到 10 之间")
except ValueError:
print("❌ 请输入有效数字。")
def monitor_log_file(logger, stop_event, use_multi_sim=False, alpha_count_per_slot=None):
"""Monitor log file and print new lines in real-time"""
print("\n📊 开始监控日志文件...")
# Get current directory to look for log files
current_dir = os.getcwd()
log_file_path = None
# First, try to find any existing wqb log files (including older ones)
print("🔍 查找 WQB 日志文件...")
# Look for any wqb*.log files in current directory
wqb_files = []
try:
for file in os.listdir(current_dir):
if file.startswith('wqb') and file.endswith('.log'):
file_path = os.path.join(current_dir, file)
wqb_files.append((file_path, os.path.getmtime(file_path)))
except Exception as e:
print(f"⚠️ 扫描目录失败: {e}")
return
if wqb_files:
# Sort by modification time, get the newest one
log_file_path = sorted(wqb_files, key=lambda x: x[1], reverse=True)[0][0]
print(f"✓ 监控已找到的最新日志文件: {log_file_path}")
else:
# Wait for new log file to be created
print("等待新的 WQB 日志文件创建...")
start_time = time.time()
while not stop_event.is_set() and (time.time() - start_time) < 30: # Wait max 30 seconds
try:
for file in os.listdir(current_dir):
if file.startswith('wqb') and file.endswith('.log'):
file_path = os.path.join(current_dir, file)
# Check if file was created recently (within last 120 seconds)
if os.path.getctime(file_path) > (time.time() - 120):
log_file_path = file_path
break
except Exception:
pass
if log_file_path:
break
time.sleep(1)
if not log_file_path:
print("⚠️ 未找到 WQB 日志文件,日志监控已禁用。")
print("💡 提示: 日志文件通常在开始回测后才会创建")
return
else:
print(f"✓ 找到新日志文件: {log_file_path}")
if stop_event.is_set():
return
print("="*60)
# Display multi-simulation information if applicable
if use_multi_sim and alpha_count_per_slot:
print("📌 重要提示:")
print(f"以下是multi simulation的记录,你的设计是1个multi simulation中有{alpha_count_per_slot}个alpha,")
print(f"因此需将实际回测数乘以该乘数,才得到实际已完成的Alpha个数。")
print("="*60)
try:
# Start monitoring from current end of file
with open(log_file_path, 'r', encoding='utf-8') as f:
# Go to end of file
f.seek(0, 2)
while not stop_event.is_set():
line = f.readline()
if line:
# Clean up the log line and print it
clean_line = line.rstrip()
if clean_line: # Only print non-empty lines
print(f"[日志] {clean_line}")
else:
time.sleep(0.2)
except Exception as e:
print(f"⚠️ 监控日志文件时出错: {e}")
async def automated_main(json_file_content, username, password, start_position=0, concurrent_count=3,
random_shuffle=False, use_multi_sim=False, alpha_count_per_slot=3):
"""Automated main function for web interface - takes all parameters at once"""
try:
print("🧠 BRAIN Alpha 模板生成器 (自动模式)")
print("="*60)
# Parse JSON content directly
import json
expressions_with_settings = json.loads(json_file_content)
expressions_count = len(expressions_with_settings)
print(f"📊 已加载 {expressions_count} 个 alpha 配置")
# Setup logger and session
logger = wqb.wqb_logger()
wqbs = wqb.WQBSession((username, password), logger=logger)
# Test connection
resp = wqbs.locate_field('open')
print(f"连接测试结果: resp.ok = {resp.ok}")
if not resp.ok:
print("❌ 身份验证失败")
return {"success": False, "error": "Authentication failed"}
print("✅ 身份验证成功!")
# Process expressions based on parameters
if start_position > 0:
expressions_with_settings = expressions_with_settings[start_position:]
print(f"🔪 已从位置 {start_position} 开始切割,剩余 {len(expressions_with_settings)} 个表达式")
if random_shuffle:
import random
random.shuffle(expressions_with_settings)
print(f"🔀 已随机打乱 {len(expressions_with_settings)} 个表达式的顺序")
if use_multi_sim:
# Convert to multi-alphas format
original_count = len(expressions_with_settings)
expressions_with_settings = wqb.to_multi_alphas(expressions_with_settings, alpha_count_per_slot)
print(f"✓ 已转换为多重回测(multi-simulatioin)格式")
print(f"📊 原始表达式数: {original_count}")
print(f"🎯 每槽alpha数: {alpha_count_per_slot}")
# Write multi-simulation info to log
multi_sim_msg = (f"[MULTI-SIMULATION MODE] 以下是multi simulation的记录,"
f"你的设计是1个multi simulation中有{alpha_count_per_slot}个alpha,"
f"因此需将实际回测数乘以该乘数,才得到实际已完成的Alpha个数。")
logger.info("="*80)
logger.info(multi_sim_msg)
logger.info("="*80)
print(f"🔄 使用 {concurrent_count} 个并发回测")
print("\n" + "="*60)
print("运行回测")
print("="*60)
if use_multi_sim:
print("开始多重回测(multi-simulatioin)并发回测...")
else:
print("开始并发回测...")
# Run simulations
resps = await wqbs.concurrent_simulate(
expressions_with_settings,
concurrent_count,
log_gap=10
)
# Collect results
alpha_ids = []
successful_count = 0
failed_count = 0
print("\n" + "="*60)
print("回测结果")
print("="*60)
if use_multi_sim:
print(f"成功完成 {len(resps)} 个多重回测(multi-simulatioin)槽的回测")
else:
print(f"成功完成 {len(resps)} 个回测")
print("\nAlpha IDs:")
for i, resp in enumerate(resps):
try:
alpha_id = resp.json()['alpha']
alpha_ids.append(alpha_id)
successful_count += 1
print(f" {i+1:4d}. {alpha_id}")
except Exception as e:
failed_count += 1
print(f" {i+1:4d}. 错误: {e}")
print("\n✅ 处理完成!")
return {
"success": True,
"results": {
"total": len(resps),
"successful": successful_count,
"failed": failed_count,
"alphaIds": alpha_ids,
"use_multi_sim": use_multi_sim,
"alpha_count_per_slot": alpha_count_per_slot if use_multi_sim else None
}
}
except Exception as e:
print(f"\n❌ 错误: {e}")
return {"success": False, "error": str(e)}
async def main():
"""Main function with interactive workflow"""
print("🧠 BRAIN Alpha 模板生成器")
print("="*60)
# Step 1: Get JSON file and load expressions
json_path, expressions_with_settings = get_json_filepath()
expressions_count = len(expressions_with_settings)
print(f"\n📊 已从以下位置加载 {expressions_count} 个 alpha 配置:")
print(f" {json_path}")
# Step 2: Get credentials and test authentication
wqbs = None
logger = None
while wqbs is None:
username, password = get_user_credentials()
wqbs, logger = test_authentication(username, password)
if wqbs is None:
retry = input("\n是否要重试? (y/n): ").lower().strip()
if retry != 'y':
print("正在退出...")
return
# Step 3: Get simulation parameters
where_to_start, concurrent_count = get_simulation_parameters(expressions_count, json_path)
# Step 3.5: Cut JSON file if needed
if where_to_start > 0:
print(f"\n🔪 正在切割 JSON 文件...")
expressions_with_settings = cut_json_file(json_path, expressions_with_settings, where_to_start)
where_to_start = 0 # Reset to 0 since we cut the file
# Step 3.6: Ask for random shuffle option
if get_random_shuffle_choice():
print(f"\n🔀 正在随机打乱表达式顺序...")
expressions_with_settings = shuffle_json_file(json_path, expressions_with_settings)
# Step 3.7: Ask for multi-simulation mode
use_multi_sim = get_multi_simulation_choice()
alpha_count_per_slot = None
if use_multi_sim:
alpha_count_per_slot = get_alpha_count_per_slot()
# Convert to multi-alphas format
original_count = len(expressions_with_settings)
expressions_with_settings = wqb.to_multi_alphas(expressions_with_settings, alpha_count_per_slot)
print(f"\n✓ 已转换为多重回测(multi-simulatioin)格式")
print(f"📊 原始表达式数: {original_count}")
print(f"🎯 每槽alpha数: {alpha_count_per_slot}")
# Calculate how many expressions will be processed
print(f"🔄 使用 {concurrent_count} 个并发回测")
# Step 4: Write multi-simulation info to log if applicable
if use_multi_sim and alpha_count_per_slot and logger:
multi_sim_msg = (f"[MULTI-SIMULATION MODE] 以下是multi simulation的记录,"
f"你的设计是1个multi simulation中有{alpha_count_per_slot}个alpha,"
f"因此需将实际回测数乘以该乘数,才得到实际已完成的Alpha个数。")
logger.info("="*80)
logger.info(multi_sim_msg)
logger.info("="*80)
# Step 5: Start log monitoring in background
stop_log_monitor = threading.Event()
log_thread = threading.Thread(
target=monitor_log_file,
args=(logger, stop_log_monitor, use_multi_sim, alpha_count_per_slot),
daemon=True
)
log_thread.start()
# Step 6: Run simulations
print("\n" + "="*60)
print("运行回测")
print("="*60)
if use_multi_sim:
print("开始多重回测(multi-simulatioin)并发回测...")
else:
print("开始并发回测...")
try:
resps = await wqbs.concurrent_simulate(
expressions_with_settings,
concurrent_count,
log_gap=10
)
# Stop log monitoring
stop_log_monitor.set()
# Print results
print("\n" + "="*60)
print("回测结果")
print("="*60)
if use_multi_sim:
print(f"成功完成 {len(resps)} 个多重回测(multi-simulatioin)槽的回测")
else:
print(f"成功完成 {len(resps)} 个回测")
print("\nAlpha IDs:")
for i, resp in enumerate(resps):
try:
alpha_id = resp.json()['alpha']
print(f" {i+1:4d}. {alpha_id}")
except Exception as e:
print(f" {i+1:4d}. 错误: {e}")
except KeyboardInterrupt:
print("\n\n⚠️ 回测被用户中断")
stop_log_monitor.set()
except Exception as e:
print(f"\n❌ 回测错误: {e}")
stop_log_monitor.set()
print("\n✅ 处理完成!")
if __name__ == '__main__':
asyncio.run(main())