Repository navigation
Expand file tree
/
Copy pathbrain_batch_alpha.py
More file actions
398 lines (314 loc) · 13.7 KB
/
Copy pathbrain_batch_alpha.py
File metadata and controls
398 lines (314 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
"""WorldQuant Brain API 批量处理模块"""
import json
import os
from datetime import datetime
from os.path import expanduser
from time import sleep
import pandas as pd
import requests
from requests.auth import HTTPBasicAuth
from alpha_strategy import AlphaStrategy
from dataset_config import get_api_settings, get_dataset_config
class BrainBatchAlpha:
API_BASE_URL = 'https://api.worldquantbrain.com'
def __init__(self, credentials_file='brain_credentials.txt'):
"""初始化 API 客户端"""
self.session = requests.Session()
self._setup_authentication(credentials_file)
def _setup_authentication(self, credentials_file):
"""设置认证"""
try:
with open(expanduser(credentials_file)) as f:
credentials = json.load(f)
username, password = credentials
self.session.auth = HTTPBasicAuth(username, password)
response = self.session.post(f"{self.API_BASE_URL}/authentication")
if response.status_code not in [200, 201]:
raise Exception(f"认证失败: HTTP {response.status_code}")
print("✅ 认证成功!")
except Exception as e:
print(f"❌ 认证错误: {str(e)}")
raise
def simulate_alphas(self, datafields=None, strategy_mode=1, dataset_name=None):
"""模拟 Alpha 列表"""
try:
datafields = self._get_datafields_if_none(datafields, dataset_name)
if not datafields:
return []
alpha_list = self._generate_alpha_list(datafields, strategy_mode)
if not alpha_list:
return []
print(f"\n🚀 开始模拟 {len(alpha_list)} 个 Alpha 表达式...")
results = []
for i, alpha in enumerate(alpha_list, 1):
print(f"\n[{i}/{len(alpha_list)}] 正在模拟 Alpha...")
result = self._simulate_single_alpha(alpha)
if result and result.get('passed_all_checks'):
results.append(result)
self._save_alpha_id(result['alpha_id'], result)
if i < len(alpha_list):
sleep(5)
return results
except Exception as e:
print(f"❌ 模拟过程出错: {str(e)}")
return []
def _simulate_single_alpha(self, alpha):
"""模拟单个 Alpha"""
try:
print(f"表达式: {alpha.get('regular', 'Unknown')}")
# 发送模拟请求
sim_resp = self.session.post(
f"{self.API_BASE_URL}/simulations",
json=alpha
)
if sim_resp.status_code != 201:
print(f"❌ 模拟请求失败 (状态码: {sim_resp.status_code})")
return None
try:
sim_progress_url = sim_resp.headers['Location']
start_time = datetime.now()
total_wait = 0
while True:
sim_progress_resp = self.session.get(sim_progress_url)
retry_after_sec = float(sim_progress_resp.headers.get("Retry-After", 0))
if retry_after_sec == 0: # simulation done!
alpha_id = sim_progress_resp.json()['alpha']
print(f"✅ 获得 Alpha ID: {alpha_id}")
# 等待一下让指标计算完成
sleep(3)
# 获取 Alpha 详情
alpha_url = f"{self.API_BASE_URL}/alphas/{alpha_id}"
alpha_detail = self.session.get(alpha_url)
alpha_data = alpha_detail.json()
# 检查是否有 is 字段
if 'is' not in alpha_data:
print("❌ 无法获取指标数据")
return None
is_qualified = self.check_alpha_qualification(alpha_data)
return {
'expression': alpha.get('regular'),
'alpha_id': alpha_id,
'passed_all_checks': is_qualified,
'metrics': alpha_data.get('is', {}),
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
# 更新等待时间和进度
total_wait += retry_after_sec
elapsed = (datetime.now() - start_time).total_seconds()
progress = min(95, (elapsed / 30) * 100) # 假设通常需要 30 秒完成
print(f"⏳ 等待模拟结果... ({elapsed:.1f} 秒 | 进度约 {progress:.0f}%)")
sleep(retry_after_sec)
except KeyError:
print("❌ 无法获取模拟进度 URL")
return None
except Exception as e:
print(f"⚠️ Alpha 模拟失败: {str(e)}")
return None
def check_alpha_qualification(self, alpha_data):
"""检查 Alpha 是否满足所有提交条件"""
try:
# 从 'is' 字段获取指标
is_data = alpha_data.get('is', {})
if not is_data:
print("❌ 无法获取指标数据")
return False
# 获取指标值
sharpe = float(is_data.get('sharpe', 0))
fitness = float(is_data.get('fitness', 0))
turnover = float(is_data.get('turnover', 0))
ic_mean = float(is_data.get('margin', 0)) # margin 对应 IC Mean
# 获取子宇宙 Sharpe
sub_universe_check = next(
(
check for check in is_data.get('checks', [])
if check['name'] == 'LOW_SUB_UNIVERSE_SHARPE'
),
{}
)
subuniverse_sharpe = float(sub_universe_check.get('value', 0))
required_subuniverse_sharpe = float(sub_universe_check.get('limit', 0))
# 打印指标
print("\n📊 Alpha 指标详情:")
print(f" Sharpe: {sharpe:.3f} (>1.5)")
print(f" Fitness: {fitness:.3f} (>1.0)")
print(f" Turnover: {turnover:.3f} (0.1-0.9)")
print(f" IC Mean: {ic_mean:.3f} (>0.02)")
print(f" 子宇宙 Sharpe: {subuniverse_sharpe:.3f} (>{required_subuniverse_sharpe:.3f})")
print("\n📝 指标评估结果:")
# 检查每个指标并输出结果
is_qualified = True
if sharpe < 1.5:
print("❌ Sharpe ratio 不达标")
is_qualified = False
else:
print("✅ Sharpe ratio 达标")
if fitness < 1.0:
print("❌ Fitness 不达标")
is_qualified = False
else:
print("✅ Fitness 达标")
if turnover < 0.1 or turnover > 0.9:
print("❌ Turnover 不在合理范围")
is_qualified = False
else:
print("✅ Turnover 达标")
if ic_mean < 0.02:
print("❌ IC Mean 不达标")
is_qualified = False
else:
print("✅ IC Mean 达标")
if subuniverse_sharpe < required_subuniverse_sharpe:
print(f"❌ 子宇宙 Sharpe 不达标 ({subuniverse_sharpe:.3f} < {required_subuniverse_sharpe:.3f})")
is_qualified = False
else:
print(f"✅ 子宇宙 Sharpe 达标 ({subuniverse_sharpe:.3f} > {required_subuniverse_sharpe:.3f})")
print("\n🔍 检查项结果:")
checks = is_data.get('checks', [])
for check in checks:
name = check.get('name')
result = check.get('result')
value = check.get('value', 'N/A')
limit = check.get('limit', 'N/A')
if result == 'PASS':
print(f"✅ {name}: {value} (限制: {limit})")
elif result == 'FAIL':
print(f"❌ {name}: {value} (限制: {limit})")
is_qualified = False
elif result == 'PENDING':
print(f"⚠️ {name}: 检查尚未完成")
is_qualified = False
print("\n📋 最终评判:")
if is_qualified:
print("✅ Alpha 满足所有条件,可以提交!")
else:
print("❌ Alpha 未达到提交标准")
return is_qualified
except Exception as e:
print(f"❌ 检查 Alpha 资格时出错: {str(e)}")
return False
def submit_alpha(self, alpha_id):
"""提交单个 Alpha"""
submit_url = f"{self.API_BASE_URL}/alphas/{alpha_id}/submit"
for attempt in range(5):
print(f"🔄 第 {attempt + 1} 次尝试提交 Alpha {alpha_id}")
# POST 请求
res = self.session.post(submit_url)
if res.status_code == 201:
print("✅ POST: 成功,等待提交完成...")
elif res.status_code in [400, 403]:
print(f"❌ 提交被拒绝 ({res.status_code})")
return False
else:
sleep(3)
continue
# 检查提交状态
while True:
res = self.session.get(submit_url)
retry = float(res.headers.get('Retry-After', 0))
if retry == 0:
if res.status_code == 200:
print("✅ 提交成功!")
return True
return False
sleep(retry)
return False
def submit_multiple_alphas(self, alpha_ids):
"""批量提交 Alpha"""
successful = []
failed = []
for alpha_id in alpha_ids:
if self.submit_alpha(alpha_id):
successful.append(alpha_id)
else:
failed.append(alpha_id)
if alpha_id != alpha_ids[-1]:
sleep(10)
return successful, failed
def _get_datafields_if_none(self, datafields=None, dataset_name=None):
"""获取数据字段列表"""
try:
if datafields is not None:
return datafields
if dataset_name is None:
print("❌ 未指定数据集")
return None
config = get_dataset_config(dataset_name)
if not config:
print(f"❌ 无效的数据集: {dataset_name}")
return None
# 获取数据字段
search_scope = {
'instrumentType': 'EQUITY',
'region': 'USA',
'delay': '1',
'universe': config['universe']
}
url_template = (
f"{self.API_BASE_URL}/data-fields?"
f"instrumentType={search_scope['instrumentType']}"
f"®ion={search_scope['region']}"
f"&delay={search_scope['delay']}"
f"&universe={search_scope['universe']}"
f"&dataset.id={config['id']}"
"&limit=50&offset={offset}"
)
# 获取总数
initial_resp = self.session.get(url_template.format(offset=0))
if initial_resp.status_code != 200:
print("❌ 获取数据字段失败")
return None
total_count = initial_resp.json()['count']
# 获取所有数据字段
all_fields = []
for offset in range(0, total_count, 50):
resp = self.session.get(url_template.format(offset=offset))
if resp.status_code != 200:
continue
all_fields.extend(resp.json()['results'])
# 过滤矩阵类型字段
matrix_fields = [
field['id'] for field in all_fields
if field.get('type') == 'MATRIX'
]
if not matrix_fields:
print("❌ 未找到可用的数据字段")
return None
print(f"✅ 获取到 {len(matrix_fields)} 个数据字段")
return matrix_fields
except Exception as e:
print(f"❌ 获取数据字段时出错: {str(e)}")
return None
def _generate_alpha_list(self, datafields, strategy_mode):
"""生成 Alpha 表达式列表"""
try:
# 初始化策略生成器
strategy_generator = AlphaStrategy()
# 生成策略列表
strategies = strategy_generator.get_simulation_data(datafields, strategy_mode)
print(f"生成了 {len(strategies)} 个Alpha表达式")
# 转换为 API 所需的格式
alpha_list = []
for strategy in strategies:
simulation_data = {
'type': 'REGULAR',
'settings': {
'instrumentType': 'EQUITY',
'region': 'USA',
'universe': 'TOP3000',
'delay': 1,
'decay': 0,
'neutralization': 'SUBINDUSTRY',
'truncation': 0.08,
'pasteurization': 'ON',
'unitHandling': 'VERIFY',
'nanHandling': 'ON',
'language': 'FASTEXPR',
'visualization': False,
},
'regular': strategy
}
alpha_list.append(simulation_data)
return alpha_list
except Exception as e:
print(f"❌ 生成 Alpha 列表失败: {str(e)}")
return []