- name
- wq-alpha-research
- description
- Use for WorldQuant BRAIN alpha research: designing WQ Alpha expressions, selecting fields/operators, diagnosing simulation and IS check failures, tuning Sharpe/Fitness/Turnover, submitting alphas, and building low-correlation alpha portfolios. Also use for 中文 requests about WorldQuant、BRAIN、WQ Alpha、因子表达式、回测、提交、换手、Fitness、Sharpe.
# WQ Alpha 研究 Skill
> 结构化 playbook:字段 → 表达式 → 回测 → 检查 → 提交 → 组合。融合 WorldQuant BRAIN 文档知识与 USA TOP3000 实证经验。
---
## 1. 快速决策树
```
开始
├── 拉取所有 alpha 列表 ──→ 只看 ACTIVE;算 **日收益** 相关,>0.7 则修改或放弃
├── 设计新因子
│ ├── 字段已验证? ──否──→ 查第 2 节(本地字段文件搜索 / 模拟 rank(field))
│ └── 是
│ ├── 基本面 ──→ group_rank + ts_rank, SUBINDUSTRY, decay=0
│ ├── 分析师 ──→ group_rank + ts_rank, INDUSTRY/SUBINDUSTRY, decay=0–4
│ ├── 技术 ────→ 高 decay(10–30) 或混合基本面降低换手
│ └── 情绪 ────→ nanHandling=ON, 小窗口谨慎
└── 提交后 ──→ 验证 status == ACTIVE,否则检查 SELF_CORRELATION
```
---
## 2. 字段速查(本地数据集)
本 SKILL 已内置 USA TOP3000 delay=1 的完整字段列表(共 4367 个),无需每次从网页/ API 拉取:
- `references/wq_usa_top3000_delay1_data_fields.json`:完整字段元数据数组
- `references/wq_usa_top3000_delay1_data_fields.csv`:CSV 版,方便 Excel/ pandas 查看
- `references/wq_usa_top3000_delay1_data_fields_summary.json`:分类统计与示例字段
字段分布:
| 类别 | 数量 | 说明 |
|------|------|------|
| fundamental | 1652 | 财务报表、附注科目 |
| analyst | 1324 | 分析师预期、一致预期 |
| news | 996 | 新闻、财报事件 |
| pv | 195 | 价量、ADV、VWAP 等 |
| option | 138 | 期权隐含波动、Put/Call 等 |
| model | 40 | 模型因子 |
| socialmedia | 22 | 社交媒体情绪 |
| univ1 | 6 | Universe 相关 |
### 2.1 本地搜索字段
```python
import json
from pathlib import Path
# 假设在 skill 目录下运行;如在其他位置,改为实际路径
skill_dir = Path(".")
field_dir = skill_dir / "references"
data = json.loads((field_dir / "wq_usa_top3000_delay1_data_fields.json").read_text(encoding="utf-8"))
keyword = "operating_income"
matches = [
f for f in data
if keyword.lower() in f["id"].lower()
or (f.get("description") and keyword.lower() in f["description"].lower())
]
for f in matches[:10]:
print(f"{f['id']} | {f.get('category',{}).get('name')} | {f.get('dataset',{}).get('name')} | coverage={f.get('coverage')} | alphaCount={f.get('alphaCount')}")
```
### 2.2 按类别筛选
```python
category = "pv" # 或 fundamental / analyst / news / option / model / socialmedia
fields = [f for f in data if f.get("category", {}).get("id") == category]
print(f"{category}: {len(fields)} fields")
for f in sorted(fields, key=lambda x: x.get("alphaCount", 0), reverse=True)[:10]:
print(f" {f['id']} | alphaCount={f.get('alphaCount')} | coverage={f.get('coverage')}")
```
### 2.3 字段验证
拿到候选字段后,**先用简单表达式模拟验证**字段是否真的可用:
```python
payload = {
"type": "REGULAR",
"settings": {
"instrumentType": "EQUITY", "region": "USA", "universe": "TOP3000",
"delay": 1, "decay": 0, "neutralization": "MARKET",
"truncation": 0.08, "pasteurization": "ON", "unitHandling": "VERIFY",
"nanHandling": "ON", "language": "FASTEXPR", "visualization": False,
},
"regular": "rank(my_candidate_field)",
}
resp = session.post("https://api.worldquantbrain.com/simulations", json=payload)
# 201 表示字段可用;非 201 通常表示字段不存在或参数不匹配
```
### 2.4 何时需要重新拉取
本地字段集已覆盖 USA TOP3000 delay=1。以下情况才需要重新从 BRAIN 拉取:
- 换 Region(如 CHN、EUR)
- 换 Universe(如 TOP500、TOP1000)
- 换 Delay(如 0)
- BRAIN 平台字段列表明显更新(可对比 `dateCreated` 与本地)
---
## 3. 运算符速查表
| 类型 | 算子 | 作用 |
|------|------|------|
| 截面 | `rank(x)`, `zscore(x)`, `normalize(x)`, `scale(x)`, `winsorize(x, std=4)` | 每天对所有股票标准化 |
| 时序 | `ts_mean`, `ts_std_dev`, `ts_delta`, `ts_rank`, `ts_corr`, `ts_decay_linear`, `ts_backfill`, `ts_zscore` | 单只股票历史窗口计算 |
| 分组 | `group_rank(x, group)`, `group_neutralize(x, group)`, `group_zscore(x, group)`, `group_backfill(x, group, N)` | 组内中性化 |
| 条件 | `if_else(cond, a, b)`, `trade_when(x, cond, delay)` | 条件暴露 |
| 向量 | `vec_avg(a, b, c)`, `vec_sum(a, b, c)` | 多字段逐元素平均/求和 |
**黄金组合**:`group_rank(ts_rank(signal, N), subindustry)`
---
## 4. 因子模板库
### 4.1 高胜率模板
```fastexpr
-- 模板 A:ROE 趋势(通过率最高)
group_rank(ts_rank(operating_income / equity, 126), subindustry)
-- 模板 B:EPS 收益率修正
group_rank(ts_rank(est_eps / close, 126), industry)
-- 模板 C:FCF 收益率
group_rank(ts_rank(free_cash_flow_reported_value / equity, 126), industry)
-- 模板 D:多因子混合(高 Fitness)
0.5 * group_rank(ts_rank(operating_income / equity, 126), subindustry)
+ 0.5 * group_rank(ts_rank(est_eps / close, 126), industry)
-- 模板 E:低相关技术+基本面混合
0.5 * rank(-(close / open - 1)) + 0.5 * rank(ts_rank(operating_income / equity, 126))
-- 模板 F:资产周转 × 利润率
rank(ts_rank(operating_income / sales * sales / assets, 126))
```
### 4.2 推荐默认设置
| 因子类型 | Decay | Neutralization | Truncation | nanHandling | 预期 TO |
|----------|-------|----------------|------------|-------------|---------|
| 基本面质量 | 0 | SUBINDUSTRY | 0.08 | ON | 2–8% |
| 分析师预期 | 0–4 | INDUSTRY/SUBINDUSTRY | 0.08 | ON | 9–16% |
| 技术反转 | 10–30 | INDUSTRY | 0.08 | OFF | 15–35% |
| 混合因子 | 4–20 | INDUSTRY/SUBINDUSTRY | 0.08 | ON | 10–20% |
| 情绪 | 4–10 | INDUSTRY | 0.05–0.08 | ON | 8–30% |
---
## 5. 指标与检查
### 5.1 核心指标
| 指标 | 公式/含义 | 目标 |
|------|-----------|------|
| Sharpe | 日 IR × √252 | ≥ 1.5(最低 1.25) |
| Fitness | Sharpe × √(|Returns| / max(TO, 0.125)) | ≥ 1.1(最低 1.0) |
| Returns | 年化收益 / $10M | ≥ 7% |
| Turnover | 日交易额 / Book Size | 1%–20% |
| Drawdown | 峰值到谷值最大回撤 | < 15% |
| Margin | PnL / 总交易额 | 越高越好 |
### 5.2 IS 检查清单
| 检查项 | 阈值 | 失败原因 | 修复方法 |
|--------|------|----------|----------|
| LOW_SHARPE | ≥ 1.25 | 信号弱 | 换字段/窗口/加 group_rank |
| LOW_FITNESS | ≥ 1.0 | 换手过高 | 增大 decay、混合稳定信号 |
| LOW_TURNOVER | ≥ 1% | 信号太稳定 | 缩短窗口、换更活跃字段 |
| HIGH_TURNOVER | ≤ 70% | 换手爆炸 | 增大 decay、trade_when、混合 |
| CONCENTRATED_WEIGHT | 单股 < 10% 且分散 | 权重集中 | 用 rank()、降低 truncation、ts_backfill |
| LOW_SUB_UNIVERSE_SHARPE | TOP1000 也有效 | 小票依赖 | 用基本面、SUBINDUSTRY、避免市值倾斜 |
| SELF_CORRELATION | **日收益** 相关系数 < 0.7 | 与已有因子太像 | 换信号簇、加过滤、换 Universe;不要只调参数 |
| MATCHES_COMPETITION | 信息性 | — | 无影响 |
### 5.3 失败统计
| 失败原因 | 占比 | 结论 |
|----------|------|------|
| LOW_SHARPE | 90.7% | 信号质量是最大瓶颈 |
| LOW_FITNESS | 66.2% | 通常是 HIGH_TURNOVER 的软性版本 |
| LOW_SUB_UNIVERSE_SHARPE | 51.0% | 避免小票/流动性倾斜 |
**按数据类型通过率**:基本面 40% > 混合 12.7% > 纯技术 5.3% > 其他 0%
---
## 6. 问题诊断与修复
| 症状 | 可能原因 | 修复 |
|------|----------|------|
| Fitness < 1.0 | 换手 > 30% | 增大 decay、混合基本面、ts_decay_linear |
| Sharpe < 1.25 | 信号弱 | 拉长窗口、group_rank、换字段 |
| TO > 50% | 信号变化太快 | decay 10–30、trade_when、混合 |
| DD > 15% | 波动大/杠杆高 | 增大 decay、降 truncation、混合低波信号 |
| CONCENTRATED_WEIGHT FAIL | 稀疏/极值 | rank()、truncation 0.05、ts_backfill |
| Sub-Universe FAIL | 小票依赖 | 避免 `rank(-assets)`,用 group_rank、加流动性过滤 |
| simulation_error | 字段不存在/算子参数错误 | 先 rank(field) 验证字段,检查算子参数个数 |
| trade_when 零交易 | 条件过严 | 放宽条件或用 if_else |
---
## 7. BRAIN API 自动化
### 7.1 认证(请填写账号)
**使用前必须准备凭据**。推荐使用环境变量;也可以在本地放置未跟踪的 `credential.txt`(已被 `.gitignore` 忽略),内容为 JSON 数组:
```json
["your_username", "your_password"]
```
⚠️ **提醒**:不要把真实账号密码写入仓库。优先使用 `WQ_BRAIN_USERNAME` / `WQ_BRAIN_PASSWORD` 环境变量。
```python
import json
import requests
from requests.auth import HTTPBasicAuth
API_BASE = "https://api.worldquantbrain.com"
# 1. 读取 credential.txt
import os
username = os.getenv("WQ_BRAIN_USERNAME")
password = os.getenv("WQ_BRAIN_PASSWORD")
if not (username and password):
with open("credential.txt") as f:
username, password = json.load(f)
# 2. 创建会话并认证
session = requests.Session()
session.auth = HTTPBasicAuth(username, password)
session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json",
})
resp = session.post(f"{API_BASE}/authentication")
assert resp.status_code == 201, f"认证失败: {resp.status_code} {resp.text}"
print("认证成功")
```
### 7.2 获取已提交 Alpha 并计算相关性
**目的**:在新因子提交前,避免与已有因子 PnL 高度相关(相关系数 ≥ 0.7)。
```python
import numpy as np
def fetch_pnl(session, alpha_id):
"""获取 Alpha 累计 PnL 序列;schema.properties 可能是 list 或 dict。"""
r = session.get(f"{API_BASE}/alphas/{alpha_id}/recordsets/pnl")
if r.status_code != 200 or not r.text.strip():
return []
data = r.json()
props = data.get("schema", {}).get("properties", [])
if isinstance(props, list):
date_idx = next((i for i, p in enumerate(props) if p.get("name", "").lower() == "date"), 0)
pnl_idx = next((i for i, p in enumerate(props) if p.get("name", "").lower() in ("pnl", "cum_pnl", "returns", "ret")), 1)
else:
date_idx = next((v["index"] for k, v in props.items() if k.lower() == "date"), 0)
pnl_idx = next((v["index"] for k, v in props.items() if k.lower() in ("pnl", "cum_pnl", "returns", "ret")), 1)
records = sorted(data.get("records", []), key=lambda r: r[date_idx])
out = []
for row in records:
rec = row[0] if isinstance(row, list) and len(row) == 1 and isinstance(row[0], list) else row
try:
out.append(float(rec[pnl_idx]))
except Exception:
continue
return out
View on GitHub