++
This commit is contained in:
+583
@@ -0,0 +1,583 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user