quant-ai / scripts /report_conditional_research.py
Ypeng12's picture
feat(ai): add audited alpha research and remove unverified prediction displays
03b76a5
Raw History Blame Contribute Delete
8.6 kB
#!/usr/bin/env python3
"""Explain the role of ML and the limits of a paper-inspired pilot."""
import argparse,json,sys
from pathlib import Path
import pandas as pd
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT/'backend'))
from app.research.artifacts import load_platform_results,digest
def report(bundle,output):
bundle=Path(bundle);output=Path(output);result=load_platform_results(bundle)
if result['status']!='complete':raise ValueError(result.get('reason'))
r=result['research'];output.mkdir(parents=True,exist_ok=False)
trials={t['candidate']['name']:t for t in r['trials']}
baseline=trials['conditional_ridge_control']['summary']['net_pnl'];equal=trials['conditional_equal']['summary']['net_pnl']
rows=[]
for name,t in trials.items():
d=pd.read_csv(bundle/f'{name}_cost5/daily.csv')
rows.append(dict(candidate=name,**t['summary'],versus_ridge=t['summary']['net_pnl']-baseline,
versus_equal=t['summary']['net_pnl']-equal,first_five=d.net_pnl.iloc[:5].sum(),last_four=d.net_pnl.iloc[5:].sum()))
training=json.loads((bundle/f'{name}_cost5/training.json').read_text())
assert all(x['last_train']<day['day'] for day in training for x in (day['training'] or []))
pd.DataFrame(rows).to_csv(output/'comparison.csv',index=False)
best=trials['conditional_state_h1'];pnl=best['summary']['net_pnl']
lines=['# 学习论文后的小规模应用:ML 是否增加价值','',
'**本报告全部金额为历史模拟,未发生真实账户盈利。** 2026-08-31—09-11,共九个交易日;交易三股SNDK、TSLA、NVDA,参考SPY、QQQ、SOXX。起始资金$100,000,单边成本5 bps。', '',
f'五分钟条件Ridge净盈亏 ${pnl:,.2f},比原Ridge改善 ${pnl-baseline:,.2f},比不使用ML的日内等权改善 ${pnl-equal:,.2f}。短期探索不能证明这点增益会延续。', '',
'| 方法 | 净盈亏 | 成本 | 最大回撤 | 相对原Ridge | 相对等权 | 后四日净盈亏 |', '|---|---:|---:|---:|---:|---:|---:|']
for x in rows:lines.append(f'| {x["candidate"]} | ${x["net_pnl"]:,.2f} | ${x["costs"]:,.2f} | {x["max_drawdown"]:.2%} | ${x["versus_ridge"]:+,.2f} | ${x["versus_equal"]:+,.2f} | ${x["last_four"]:,.2f} |')
lines+=['','## 学习了什么,实际应用了什么','',
'[Gao、Han、Li、Zhou:Market Intraday Momentum](https://ssrn.com/abstract=2440866)研究ETF中从前收盘到开盘半小时的收益与尾盘半小时收益,并考察量能、波动等条件。本次读的是2017年工作论文版本。它没有证明任意个股、任意日内时点放量后都持续上涨。',
'本轮是迁移假说的小样本试验,不是原论文复现:交易的是三只股票,预测五分钟或三十分钟收益,日内持续更新;没有重建论文的1993—2013年TAQ样本、ETF交易方式、宏观事件与尾盘时段。',
'新增连续输入为:截至当时的价格路径效率、波动相对过去相同时段的变化、成交量相对过去相同时段的变化;将它们与15/60分钟动量及市场相对强弱相乘形成交互项。Ridge只用此前交易日学习系数,没有按事后趋势标签强制交易。零成交量的对数不可用时作为缺失值,按训练集均值处理,不能当真实订单流。',
'路径效率使用当日开盘至当前收盘的净位移与五分钟收盘采样路径之比,无法还原K线内部路径。波动和量能参照只使用之前日期的同一时段。临近收盘缩放长期预测仍是研究近似。',
'保留原Ridge作为同执行/组合条件的代码对照,实际净盈亏严格复现+$1,169.245405823。增加每日开盘等权、收盘前平仓作为无ML基准。等权的方向和敞口路径与ML不同,因此两者差额是整套策略比较,不是单纯模型因果贡献。', '',
'## Ridge、树和模拟分别做什么','',
'- 模拟按当时已知的信息、下一根开盘价格、整数股数、成本和每日平仓规则计算结果。它能暴露策略问题,不能证明真实订单必然按模拟价成交。',
'- Ridge是带L2系数惩罚的线性回归,从历史学习各特征的权重;正则用于抑制过大系数。本项目输出收益预测,不是胜率。参见[官方文档](https://scikit-learn.org/stable/modules/linear_model.html#ridge-regression-and-classification)。',
'- 回归树从训练数据学习一系列条件分支,每个叶子给出平均收益估计,能够表达非线性。本项目旧研究使用深度3、叶子至少40条记录;这些是实验参数,不是机构标准。参见[官方文档](https://scikit-learn.org/stable/modules/tree.html#regression)。',
'- ML学习的是预测误差目标;是否增加扣成本后的利润必须另做组合回测。模型输出偏多不必然等于立即买入,因为现有仓位、风险与交易成本也参与配仓。', '',
'## 旧界面的百分比为什么不能当胜率','',
'审计发现旧ML助手在接口success=false时填入65.4%/58.4%的固定数据;旧轨迹图请求失败时随机生成55%—70%的胜率及虚构价格/成交。已删除这些兜底生成逻辑,显示不可用。',
'旧trajectory接口还用K线位置和成交量计算代理OFI、手工0.35/0.35/0.30权重组合,并对分类输出做赔率变换和35%—88%裁剪。它没有给出可靠的样本外校准证据。本轮已将该旧接口改为明确不可用,不调用券商或模型,不返回假预测;/api/research/platform仍展示经过产物校验的研究结果。',
'这次修复针对上述两个组件及旧trajectory接口,不代表仓库全部历史可视化已经审计完毕。不能追溯断言某张旧截图恰好走了哪条分支。',
'概率必须先定义事件与期限(例如未来15分钟扣成本后为正),再检验历史未见样本中同一概率区间的实际命中率。64%不是64%的确定获利机会,也不是当前Ridge给出的数字。', '',
'## LOB 中的量与 Alpha','',
'成交量是已发生的交易;买卖一挂单量是当前可见报价;OFI衡量报价价格与数量变动带来的不平衡。每笔成交都有买卖双方,总成交量不能直接当净买盘。队列不平衡比不是胜率。',
'[Cont、Kukanov、Stoikov](https://arxiv.org/abs/1011.6402)的真实买卖一OFI公式已在app/alpha/real_l1_alpha.py中实现;输入必须是真实报价/成交事件。公式已实现不等于增益已验证,本次四组试验均未使用真实L1。', '',
'## 下一步验收','',
'冻结条件五分钟Ridge作为未来模拟候选,同时保留原Ridge和无ML等权。重点检验净收益差、回撤、交易成本、趋势机会参与情况和假突破损失,不按这九天继续无限调参。三十分钟变体结果也完整保留。',
'更长历史、预先定义的股票池及真正未来模拟仍然必要。不得把本次+$4,702附近的探索结果写成稳定Alpha或实盘成绩。']+['- '+a for a in r['unverified_assumptions']]
lines+=['','## 逐股净贡献','','| 方法 | SNDK | TSLA | NVDA |','|---|---:|---:|---:|']
for name,t in trials.items():lines.append('| '+name+' | '+' | '.join(f'${t["per_symbol"][s]["net_pnl"]:,.2f}' for s in r['traded_symbols'])+' |')
lines+=['','## 复现','','```bash',
'OPENBLAS_NUM_THREADS=1 OMP_NUM_THREADS=1 python3 scripts/run_conditional_research.py --output reports/conditional_research_NEW',
'python3 scripts/report_conditional_research.py --bundle reports/conditional_research_NEW --output reports/conditional_report_NEW',
'python3 scripts/shadow_conditional_policy.py register --bundle reports/conditional_research_NEW --directory reports/conditional_forward_NEW',
'```','','Lab新增“论文启发实验”入口;live_runner未修改或切换。']
(output/'REPORT.md').write_text('\n'.join(lines)+'\n')
(output/'validation.json').write_text(json.dumps(dict(bundle_sha256=digest(bundle/'registry.json'),checks='artifact integrity, cost and PnL reconciliation, prior-session training',orders_submitted=0),indent=2)+'\n')
print(json.dumps(dict(net_pnl=pnl,improvement_vs_ridge=pnl-baseline,improvement_vs_equal=pnl-equal),indent=2))
if __name__=='__main__':
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--bundle',required=True);p.add_argument('--output',required=True)
a=p.parse_args();report(a.bundle,a.output)