如何读懂ATLAS janus.py源码:JANUS元层把群体加权变成涌现市场制度检测器
【免费下载链接】atlas-gicATLAS by General Intelligence Capital — Self-improving AI trading agents using Karpathy-style autoresearch项目地址: https://gitcode.com/gh_mirrors/at/atlas-gic
ATLAS 是一个自进化 AI 交易系统,25 个以上的 AI 交易代理每日辩论市场,并用市场反馈持续改进自身提示词。其中的 janus.py 实现了位于所有代理群体之上的JANUS 元层:它根据近期准确率对各 cohort(不同训练区间的代理群体)做动态加权,而权重差本身还"涌现"成了一个市场制度(Regime)检测器。下面用新手友好的方式,带你读懂这个不到 570 行的 Python 文件里的核心设计。
🧭 先搞懂背景:JANUS 在 ATLAS 四层架构中的位置
读懂 janus.py 之前,先看一眼整体架构。ATLAS 是一个 4 层多智能体系统,信号从宏观层逐层过滤,最终由决策层执行:
| 层级 | 角色 | 说明 |
|---|---|---|
| Layer 1 | 宏观(10 个代理) | 判断风险偏好:RISK_ON / RISK_OFF / NEUTRAL |
| Layer 2 | 行业台(7 个代理) | 在宏观背景下选出板块多空头 |
| Layer 3 | 超级投资者(4 个代理) | 用不同投资哲学过滤标的 |
| Layer 4 | 决策层(4 个代理) | CRO 风险审查 → CIO 最终拍板 |
架构图和逐层输出格式可以看 architecture/overview.md 与 architecture/layers.md。
而 JANUS 站在这些群体之上。ATLAS 用不同的市场区间训练出不同的代理群体(All Seasons / PRISM 方法,详见 README.md 中的 "All Seasons" 一节),比如18monthcohort 在 18 个月数据上训练,10yearcohort 在更长历史上训练。不同 cohort 给出的建议可能互相矛盾——谁来裁判?就是 JANUS。
🔍 第一步:从 3 个类常量看懂设计意图
打开 src/janus.py#L36-L40,文件开头就藏着整个系统的设计哲学:
MIN_WEIGHT = 0.2:任何 cohort 的影响力不得低于 20%——永远保留"少数派"声音;MAX_WEIGHT = 0.8:任何 cohort 不得独占 80% 以上话语权——防止某个群体"一家独大";ROLLING_WINDOW = 30:只用最近 30 天的表现打分数——只信近期战绩;REGIME_THRESHOLD = 0.15:两组权重差超过 ±15% 才发出明确的制度信号。
💡 新手读源码技巧:先读常量和注释,再读函数。ATLAS 的开发者把"没有 cohort 会被彻底静音、也不会有一个 cohort 独裁"这条风控原则直接写在了常量里,这比任何文字文档都直白。
初始化时(src/janus.py#L42-L58),所有 cohort 从平等权重(各 1/2)开始,随后完全靠战绩说话——这就是"涌现"的前提:没有人预先规定制度切换规则。
⚖️ 第二步:权重是如何算出来的?三步走
JANUS 每天跑一次的权重更新逻辑在update_weights()里(src/janus.py#L241-L267),可以拆成三步:
① 打两个分:命中率 + Sharpe
calculate_cohort_metrics()(src/janus.py#L162-L200)回看最近 30 天的打分记录,算出每个 cohort 的:
hit_rate:预测方向正确的比例(做多且涨了、做空且跌了就算"命中");sharpe:按置信度加权的日收益的年化夏普比率。
② 合成原始分数
每个 cohort 的原始分 = 命中率与归一化 Sharpe 各占一半(各 50%)。
③ Softmax + 双边界约束
_softmax_with_constraints()(src/janus.py#L202-L239)把原始分数通过 softmax 变成一组和为 1 的权重,然后依次执行:低于 0.2 的拉到 0.2 → 重新归一化 → 高于 0.8 的压到 0.8 → 再次归一化。
🧠 为什么用 softmax 而不是简单归一化?softmax 是"赢家通吃但有温度"的平滑放大:分数差距越大,领先者权重升得越猛,但永远不会让落后者归零。配合上下界,形成一个"能拉开差距、但不会消灭对手"的动态天平。
🌉 第三步:最妙的设计——权重差 = 涌现制度检测器
真正让这篇源码"出圈"的是regime_signal()(src/janus.py#L269-L289)。它的判断逻辑只有一行核心比较:短窗口 cohort 权重 − 长窗口 cohort 权重:
| 权重差 | 含义 | 输出信号 |
|---|---|---|
| 短窗口显著占优(> +0.15) | 市场出现了历史经验没覆盖过的新行为 | NOVEL_REGIME(新奇制度) |
| 长窗口显著占优(< −0.15) | 经典历史规律正在重演 | HISTORICAL_REGIME(历史制度) |
| 两者势均力敌 | 无明确倾向 | MIXED(混合) |
为什么说它是"涌现"的?因为开发者从未写过任何制度识别规则——没有 VIX 阈值、没有波动率过滤。它只是忠实地记录"哪个 cohort 最近猜得对",而"市场变新了 vs 市场在重复历史"这一区分,从权重差中自己浮现出来。短窗口代理擅长捕捉当下,长窗口代理擅长匹配历史模式,谁近期更准,谁就获得更高权重——这个差值天然就是制度仪表盘。
🧪 建议怎么融合:争议标的与置信度惩罚
blend_recommendations()(src/janus.py#L291-L336)按当前权重把各 cohort 的建议混成最终推荐,单标的融合规则在_blend_ticker_recommendations()(src/janus.py#L338-L421):
- 对每只股票,把各 cohort 的多头意见和空头意见分开,按权重 × 置信度求和;
- 哪边加权和更高,最终方向就是哪边;
- 如果多空双方同时存在(
contested = True),用对方的一半加权和做惩罚,压低最终置信度——分歧越大,仓位越保守; - 所有争议标的会被单独列进
contested_tickers,供人工或 CRO 重点审查。
这一步的思想可以概括为一句话:让群体吵架,但按"谁更靠谱"决定听谁的话;吵得越凶,出手越轻。
🚀 每日循环:一条命令跑完整流程
run_daily()(src/janus.py#L423-L471)把整件事串成流水线:加载打分结果 → 更新权重 → 融合建议 → 输出制度信号 → 落盘。产物有两份 JSON:当日完整输出janus_daily.json和只保留摘要的滚动历史janus_history.json(自动裁剪到最近 365 天),方便后续画权重与制度随时间演变的曲线(get_history()就是为此提供数据,src/janus.py#L512-L531)。
文件末尾的main()(src/janus.py#L534-L567)还做了命令行入口,支持用逗号分隔传入任意 cohort 组合。
📌 读完这篇源码,你能带走什么?
三条可复用的架构思想:
- 裁判层与选手层分离:25 个交易代理负责"猜",JANUS 只负责"按战绩分话语权"。选手水平再高,也需要一个只认近期战绩的裁判。
- 边界约束比精确算法更重要:0.2 / 0.8 的上下界让系统在"灵活加权"和"防止独裁/消音"之间取得了平衡——这是比 softmax 本身更值得抄的设计。
- 监测涌现指标,而不是硬编码规则:与其写满"什么时候算风格切换"的 if-else,不如先让系统按准确率加权,再从权重分布里"读"出市场状态。制度检测器不是被写出来的,是被权重动态"养"出来的。
延伸阅读路径(都在仓库内):
- 四层架构全景:architecture/overview.md
- 各层代理职责与输出格式:architecture/layers.md
- autoresearch 自改进循环与 Darwinian 权重:architecture/autoresearch.md
- 源码目录结构说明:src/README.md
- 18 个月回测的最终代理权重表:results/summary.json
- 回测过程日志样例:results/autoresearch_log.json
一个有趣的交叉印证:在 results/summary.json 中,CIO(组合经理代理)的 Darwinian 权重被压到了最低值 0.3——系统自己"发现"了组合管理才是瓶颈,与 JANUS 元层的判断逻辑异曲同工:权重会自己说出问题所在。这正是把群体加权做成元层之后,ATLAS 最迷人的地方。
【免费下载链接】atlas-gicATLAS by General Intelligence Capital — Self-improving AI trading agents using Karpathy-style autoresearch项目地址: https://gitcode.com/gh_mirrors/at/atlas-gic
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考