This commit is contained in:
jack
2025-11-26 18:06:29 +08:00
parent 0b84cb8222
commit d4e55e9aca
32 changed files with 1303 additions and 1886 deletions
-9
View File
@@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
"""
核心模块 - 包含API客户端和数据模型
"""
from .api_client import WorldQuantBrainSimulate
from .models import AlphaMetrics, SimulationResult
__all__ = ['WorldQuantBrainSimulate', 'AlphaMetrics', 'SimulationResult']
-158
View File
@@ -1,158 +0,0 @@
# -*- coding: utf-8 -*-
import os.path
import httpx
import time
from httpx import BasicAuth
from typing import Dict, Any, Optional, Tuple
class WorldQuantBrainSimulate:
def __init__(self, credentials_file='account.txt'):
self.credentials_file = credentials_file
self.client = None
self.brain_api_url = 'https://api.worldquantbrain.com'
"""读取本地账号密码"""
def load_credentials(self) -> Tuple[str, str]:
if not os.path.exists(self.credentials_file):
print("未找到 account.txt 文件")
with open(self.credentials_file, 'w') as f:
f.write("")
print("account.txt 文件已创建,请填写账号密码, 格式: ['username', 'password]")
exit(1)
with open(self.credentials_file) as f:
credentials = eval(f.read())
return credentials[0], credentials[1]
"""登录认证"""
def login(self) -> bool:
username, password = self.load_credentials()
self.client = httpx.Client(auth=BasicAuth(username, password))
response = self.client.post(f'{self.brain_api_url}/authentication')
print(f"登录状态: {response.status_code}")
if response.status_code == 201:
print("登录成功!")
print(f"账户信息: {response.json()}")
return True
else:
print(f"登录失败: {response.json()}")
return False
"""模拟Alpha因子"""
def simulate_alpha(self, expression: str, settings: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
if self.client is None:
raise Exception("请先登录")
default_settings = {
'instrumentType': 'EQUITY',
'region': 'USA',
'universe': 'TOP3000',
'delay': 1,
'decay': 0,
'neutralization': 'INDUSTRY',
'truncation': 0.08,
'pasteurization': 'ON',
'unitHandling': 'VERIFY',
'nanHandling': 'OFF',
'language': 'FASTEXPR',
'visualization': False,
}
if settings:
default_settings.update(settings)
simulation_data = {
'type': 'REGULAR',
'settings': default_settings,
'regular': expression
}
sim_resp = self.client.post(f'{self.brain_api_url}/simulations', json=simulation_data)
print(f"模拟提交状态: {sim_resp.status_code}")
sim_progress_url = sim_resp.headers['location']
print(f"进度URL: {sim_progress_url}")
while True:
sim_progress_resp = self.client.get(sim_progress_url)
retry_after_sec = float(sim_progress_resp.headers.get("Retry-After", 0))
if retry_after_sec == 0:
break
if sim_progress_resp.json():
result = sim_progress_resp.json()
progress = result['progress']
if progress:
print(f"模拟进度: {float(progress)*100}%")
print(f"等待 {retry_after_sec} 秒...")
time.sleep(retry_after_sec)
# 如果因子模拟不通过, 获取一下失败信息
if sim_progress_resp.json()["status"] == "ERROR":
result = sim_progress_resp.json()["message"]
print(f"因子模拟失败: {result}")
# 返回一个特殊标识,表示模拟失败
return {"status": "error", "message": result}
alpha_id = sim_progress_resp.json()["alpha"]
print(f"生成的Alpha ID: {alpha_id}")
# 获取详细的性能指标
metrics = self.get_alpha_metrics(alpha_id)
return {"status": "success", "alpha_id": alpha_id, "metrics": metrics}
"""获取Alpha因子的详细指标"""
def get_alpha_metrics(self, alpha_id: str) -> Dict[str, Any]:
if self.client is None:
raise Exception("请先登录")
try:
# 获取Alpha的基本信息和指标
alpha_url = f'{self.brain_api_url}/alphas/{alpha_id}'
alpha_resp = self.client.get(alpha_url)
if alpha_resp.status_code in [200, 201]:
alpha_data = alpha_resp.json()
# 以后可能需要获取其他参数
if alpha_data.get('metrics'):
alpha_data = alpha_data.get('metrics')
return alpha_data or {}
else:
print(f"获取Alpha指标失败: {alpha_resp.status_code}")
# 返回一个空的字典结构
return {
"train": {},
"is": {},
"test": {},
"grade": None,
"stage": None,
"status": None,
"dateCreated": None,
"id": alpha_id
}
except Exception as e:
print(f"获取指标时出错: {str(e)}")
# 返回一个空的字典结构
return {
"train": {},
"is": {},
"test": {},
"grade": None,
"stage": None,
"status": None,
"dateCreated": None,
"id": alpha_id
}
def close(self):
"""关闭连接"""
if self.client:
self.client.close()
+176
View File
@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
"""数据库管理层"""
import psycopg2
from typing import List, Optional
from config.settings import settings
from models.entities import SimulationResult, AlphaExpression
class DatabaseManager:
"""数据库管理类"""
def __init__(self):
self.connection = None
self.init_database()
def create_database(self) -> None:
"""创建数据库(如果不存在)"""
try:
# 先连接到默认的postgres数据库来创建alpha数据库
conn = psycopg2.connect(
host=settings.DATABASE_CONFIG["host"],
port=settings.DATABASE_CONFIG["port"],
database="postgres",
user=settings.DATABASE_CONFIG["user"],
password=settings.DATABASE_CONFIG["password"]
)
conn.autocommit = True
cursor = conn.cursor()
# 检查数据库是否存在
cursor.execute("SELECT 1 FROM pg_catalog.pg_database WHERE datname = %s",
(settings.DATABASE_CONFIG["database"],))
exists = cursor.fetchone()
if not exists:
cursor.execute(f"CREATE DATABASE {settings.DATABASE_CONFIG['database']}")
print(f"数据库 {settings.DATABASE_CONFIG['database']} 创建成功")
else:
print(f"数据库 {settings.DATABASE_CONFIG['database']} 已存在")
cursor.close()
conn.close()
except Exception as e:
print(f"创建数据库时出错: {e}")
raise
def get_connection(self) -> psycopg2.extensions.connection:
"""获取数据库连接"""
if self.connection is None or self.connection.closed:
self.connection = psycopg2.connect(
host=settings.DATABASE_CONFIG["host"],
port=settings.DATABASE_CONFIG["port"],
database=settings.DATABASE_CONFIG["database"],
user=settings.DATABASE_CONFIG["user"],
password=settings.DATABASE_CONFIG["password"]
)
return self.connection
def init_database(self) -> None:
"""初始化数据库和表结构"""
# 先创建数据库
self.create_database()
# 然后连接到此数据库创建表
conn = self.get_connection()
cursor = conn.cursor()
# 创建 alpha_prepare 表
cursor.execute('''
CREATE TABLE IF NOT EXISTS alpha_prepare
(
id
SERIAL
PRIMARY
KEY,
alpha
TEXT
NOT
NULL
UNIQUE,
unused
BOOLEAN
NOT
NULL
DEFAULT
TRUE,
created_time
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
)
''')
# 创建 simulation 表
cursor.execute('''
CREATE TABLE IF NOT EXISTS simulation
(
id
SERIAL
PRIMARY
KEY,
expression
TEXT
NOT
NULL,
time_consuming
REAL
NOT
NULL,
status
TEXT
NOT
NULL,
timestamp
TEXT
NOT
NULL,
alpha_id
TEXT,
message
TEXT,
created_time
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
)
''')
conn.commit()
print(f"数据库 {settings.DATABASE_CONFIG['database']} 表结构初始化完成")
def get_unused_alpha(self) -> List[str]:
"""获取所有未使用的alpha表达式"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("SELECT alpha FROM alpha_prepare WHERE unused = TRUE")
results = cursor.fetchall()
alpha_list = [result[0] for result in results]
return alpha_list
def mark_alpha_used(self, alpha: str) -> None:
"""将alpha标记为已使用"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("UPDATE alpha_prepare SET unused = FALSE WHERE alpha = %s", (alpha,))
conn.commit()
def insert_simulation_result(self, result: SimulationResult) -> None:
"""插入模拟结果到simulation表"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO simulation
(expression, time_consuming, status, timestamp, alpha_id, message)
VALUES (%s, %s, %s, %s, %s, %s)
''', (
result.expression,
result.time_consuming,
result.status,
result.timestamp,
result.alpha_id,
result.message or ""
))
conn.commit()
def close_connection(self) -> None:
"""关闭数据库连接"""
if self.connection and not self.connection.closed:
self.connection.close()
-62
View File
@@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
from dataclasses import dataclass
from typing import Dict, Any, Optional
@dataclass
class TrainMetrics:
"""训练集指标"""
sharpe_ratio: Optional[float] = None
annual_return: Optional[float] = None
max_drawdown: Optional[float] = None
turnover: Optional[float] = None
fitness: Optional[float] = None
pnl: Optional[float] = None
book_size: Optional[float] = None
long_count: Optional[float] = None
short_count: Optional[float] = None
margin: Optional[float] = None
@dataclass
class TestMetrics:
"""测试集指标"""
sharpe_ratio: Optional[float] = None
annual_return: Optional[float] = None
max_drawdown: Optional[float] = None
turnover: Optional[float] = None
fitness: Optional[float] = None
pnl: Optional[float] = None
@dataclass
class AlphaInfo:
"""Alpha基本信息"""
grade: Optional[str] = None
stage: Optional[str] = None
status: Optional[str] = None
date_created: Optional[str] = None
checks: Optional[Dict[str, Any]] = None
@dataclass
class AlphaMetrics:
"""Alpha因子完整指标"""
train_metrics: TrainMetrics
is_metrics: TestMetrics
test_metrics: TestMetrics
alpha_info: AlphaInfo
alpha_id: Optional[str] = None
@dataclass
class SimulationResult:
"""模拟结果"""
expression: str
time_consuming: float
formatted_time: str
alpha_id: str
status: str
description: str
simulation_timestamp: str
metrics: Optional[Dict[str, Any]] = None
+126
View File
@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
"""Alpha模拟器核心类"""
import time
import httpx
from datetime import datetime
from typing import List, Tuple
from config.settings import settings
from models.entities import SimulationResult, TokenInfo
from core.database import DatabaseManager
from services.auth_service import AuthService
from services.alpha_service import AlphaService
from services.notification_service import NotificationService
class AlphaSimulator:
"""Alpha模拟器主类"""
def __init__(self):
self.db_manager = DatabaseManager()
self.auth_service = AuthService()
self.client = None
self.token_info = None
self.alpha_service = None
def __del__(self):
"""析构函数,确保数据库连接被关闭"""
if hasattr(self, 'db_manager'):
self.db_manager.close_connection()
def initialize(self) -> None:
"""初始化模拟器"""
self.client = httpx.Client()
self.login()
def login(self) -> TokenInfo:
"""登录并初始化alpha服务"""
self.token_info = self.auth_service.login(self.client)
self.alpha_service = AlphaService(self.client)
return self.token_info
def needs_token_refresh(self) -> bool:
"""检查是否需要刷新token"""
if not self.token_info:
return True
return self.token_info.expiry < settings.TOKEN_REFRESH_THRESHOLD
def load_alpha_list(self) -> List[str]:
"""从数据库加载未使用的alpha表达式"""
return self.db_manager.get_unused_alpha()
def run_batch_simulation(self, alpha_list: List[str]) -> Tuple[int, int]:
"""运行批量模拟"""
success_count = 0
fail_count = 0
for i in range(0, len(alpha_list), settings.BATCH_SIZE):
batch = alpha_list[i:i + settings.BATCH_SIZE]
print(f"\n开始处理第 {i // settings.BATCH_SIZE + 1} 批因子,共 {len(batch)} 个")
for expression in batch:
result = self._simulate_single_alpha(expression)
if result.status == "ok":
success_count += 1
else:
fail_count += 1
print(f"第 {i // settings.BATCH_SIZE + 1} 批处理完成")
self._print_summary(success_count, fail_count)
return success_count, fail_count
def _simulate_single_alpha(self, expression: str) -> SimulationResult:
"""模拟单个Alpha表达式"""
print(f"\n模拟因子: {expression}")
start_time = time.time()
try:
result = self.alpha_service.simulate_alpha(expression)
end_time = time.time()
time_consuming = round(end_time - start_time, 2)
simulation_result = SimulationResult(
expression=expression,
time_consuming=time_consuming,
status=result["status"],
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
alpha_id=result.get("alpha_id"),
message=result.get("message", "")
)
if result["status"] == "ok":
print(f"✅ 模拟成功 - Alpha ID: {result['alpha_id']} - 耗时: {time_consuming}秒")
else:
print(f"❌ 模拟失败 - {result.get('message', '未知错误')}")
except Exception as e:
end_time = time.time()
time_consuming = round(end_time - start_time, 2)
simulation_result = SimulationResult(
expression=expression,
time_consuming=time_consuming,
status="err",
message=str(e),
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
print(f"❌ 模拟异常 - {str(e)}")
# 保存结果并标记为已使用
self._save_simulation_result(simulation_result)
return simulation_result
def _save_simulation_result(self, result: SimulationResult) -> None:
"""保存模拟结果到数据库"""
self.db_manager.mark_alpha_used(result.expression)
self.db_manager.insert_simulation_result(result)
def _print_summary(self, success_count: int, fail_count: int) -> None:
"""打印总结信息"""
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"\n总计: 成功 {success_count} 个, 失败 {fail_count} 个")
print(f"完成时间: {now}")
print(f"所有结果已保存到 PostgreSQL 数据库 {settings.DATABASE_CONFIG['database']} 的 simulation 表中")
# 发送通知
NotificationService.send_to_gotify(success_count, fail_count)