从 Claude Code 源码
学习构建交易机器人
CC 本质上是一个感知 → 决策 → 执行 → 观察的循环系统。 交易机器人的架构需求高度相似。从源码中提炼 12 个可复用的架构模式。
核心循环架构
The Agent Loop → Trading Loop
源码洞察
transition 字段记录"为什么要继续"——这不是日志,而是状态机的一部分。
- 构建上下文(多级 compact)
- 调用模型 API(streaming)
- 解析响应,提取 tool_use
- 执行 tools(并发或串行)
- 收集结果,更新消息历史
- 检查 7 个终止条件
- 生成 next State,循环
- 采集市场数据
- 生成交易信号
- 风控管道检查
- 执行交易
- 更新持仓状态
- 检查终止条件
- 生成 next State,循环
设计原则:把整个循环的状态打包成一个对象,每轮循环生成一个新的。好处是随时可以拍快照——出了问题直接回看"当时仓位多少、信号是什么、为什么没止损",不用从日志里拼凑。每次循环还要记录"为什么继续":是因为收到了新信号?还是在做风控再平衡?复盘时这比"这一轮做了什么"有用得多。
工具系统
Tool Registry + Dispatch
源码洞察
isConcurrencySafe()、isReadOnly()、isDestructive()。这些不是文档标注——它们直接决定运行时行为:并发调度器根据 isConcurrencySafe 自动分区,权限系统根据 isDestructive 触发额外确认。每个 tool 还带 inputSchema(Zod),在调用前强制校验参数。
设计原则:每个操作自带"能否并发""是否只读""有没有破坏性"三个标签。系统根据标签自动决定怎么执行——查余额自动并发跑,下单自动排队串行,平仓自动触发二次确认。不用到处写 if-else 判断"这个操作要不要等上一个完成"。加一个新操作只需要填三个标签,调度、权限、监控全自动适配。
并发工具执行
Tool Orchestration
源码洞察
partitionToolCalls() 把一组调用按连续的 isConcurrencySafe 分区成批次。并发批次中的 state 修改会被排队,等整个批次结束后才统一应用;串行批次中的 state 修改则立即生效,影响后续工具。并发批次中如果一个工具出错,会通过 siblingAbortController 级联取消所有兄弟——不是静默忽略。
设计原则:同时查 5 个交易对的行情,结果回来后不能每个立刻改 state——否则第 3 个回来时看到的 state 已经被第 1、2 个改过了,数据不一致。正确做法:5 个结果全收齐,一次性合并更新。但下单不一样:先检查余额 → 再下单 → 再确认成交,每一步必须立刻看到上一步的结果。另外,5 个并发查询中如果一个交易所断了,其余 4 个也应该立刻取消,不要出现"查了一半"的状态。
重试与错误恢复
Error Classification + Graduated Retry
源码洞察
设计原则:不是所有错误都该重试。网络抖动?等一下重试。交易所限流?遵守它告诉你的等待时间。连续被限流 3 次?别重试了,切到备用交易所。余额不足?重试 100 次也没用,直接报错。定时任务的限流错误更不能重试——10 个定时任务同时重试,只会让限流更严重。先搞清楚是什么错误,再决定怎么处理。
CC 没有面对但交易必须解决的问题:幂等性
clientOrderId 确保幂等,并在重试前先查询上一次请求是否已成功。
状态管理
Immutable Snapshots + Pure Updates
源码洞察
getState、setState((prev) => next)、subscribe。子 agent 拿到同一个 store 引用但 setState 被替换成 no-op——子 agent 可以读全局状态,但不能写。唯一的例外是 setAppStateForTasks,它绕过 no-op 直达 root store,因为任务注册必须全局可见。这种"默认只读 + 白名单可写"很精巧。
设计原则:趋势策略想看当前持仓?可以看。想直接下单?不行——必须把下单请求提交到统一通道,通道会自动过风控。这样不管你跑多少个策略,所有订单都必经同一个风控管道,不存在"策略 A 绕过检查直接下了一笔"的可能。状态也永远不会被某个策略的 bug 搞乱,因为没人能直接改它。
权限与风控系统
Multi-layer Decision Pipeline
源码洞察
设计原则:风控不是一个函数,是一条流水线。订单先过"金额上限",再过"仓位检查",再过"频率检查",再过"回撤检查",每一关都能拦。更妙的是:如果一个策略连续 5 次被风控拦截,系统不会傻傻地继续拦——它会判定"这个策略本身可能出了问题",自动暂停策略并告警。这不是在检查某笔交易,而是在检查系统行为是否正常。
上下文压缩
Multi-tier Data Lifecycle
源码洞察
| CC 层级 | 交易机器人映射 | 触发时机 |
|---|---|---|
| Snip | 丢弃 N 天前的 tick 数据 | 每轮循环 |
| Microcompact | 原始 K 线 → 预计算 MA/RSI | 数据窗口滑动时 |
| Autocompact | 交易记录 → 统计摘要 | 定期 / token 超限 |
| Reactive | 紧急丢弃非活跃交易对数据 | 内存压力 |
| 恢复关键数据 | 始终保留当前持仓 + 活跃订单 | 每次压缩后 |
设计原则:历史数据越来越多怎么办?不要纠结"这条能不能删",直接大刀砍掉旧数据,然后把最重要的东西(当前持仓、活跃订单)完整地补回来。听起来粗暴,但比小心翼翼地挑选"哪些可以删"简单得多,而且关键数据永远是完整的。CC 里每次压缩后都会把最重要的 5 个文件重新注入,预算 50K tokens——先砍后补,简单可靠。
流式处理
Streaming Execution
源码洞察
StreamingToolExecutor 不等响应结束就开始执行——一旦解析出完整的 tool_use block 就立即调度。并发安全的工具可以与尚未完成的流式响应并行执行。如果流中断,已调度但未完成的工具会被标记为 tombstone(墓碑),不是简单丢弃,而是保留标记让下一轮循环知道"这里有未完成的操作"。
设计原则:行情数据来一条处理一条,不用等"这一秒的数据都到齐了"再算。但断线时要小心:WebSocket 断了,你刚提交的订单到底成交了没有?CC 的做法是给未完成的操作打一个"墓碑"标记——重连后系统看到墓碑,就知道"这里有个操作状态未知,先查清楚再继续",而不是当作没发生过。
插件与集成
Dynamic Tool Discovery
源码洞察
list_tools() 在运行时动态发现外部工具,转换成统一的内部 Tool 格式后注入 registry。工具的元数据(readOnlyHint、destructiveHint)直接映射为运行时行为标记。LRU 缓存限制最多 20 个 server 连接。认证失败缓存 15 分钟避免重复 OAuth。会话过期时自动清除缓存并重试(最多 1 次)。
设计原则:加一个新交易所不应该改策略代码。每个交易所是一个"插件",启动时自动注册自己支持的操作(下单、撤单、查余额…),策略只管调用统一接口。Binance 挂了?切到 Bybit,策略代码一行不改。连接数也要管——CC 限制最多 20 个连接,用 LRU 淘汰最久没用的;认证失败缓存 15 分钟,避免反复触发 OAuth 登录流程。
Hook 系统
Lifecycle Interception
源码洞察
pre_order 风控检查、滑点保护
post_order 日志、通知、状态更新
order_filled 更新持仓、触发止盈止损
order_rejected 告警、策略自动调整
position_closed 计算 PnL、更新统计
exchange_disconnect 紧急风控、tombstone 标记
设计原则:普通的事件系统只能"通知"——订单提交了,发个消息。CC 的 hook 能"拦截":下单前跑 pre_order hook,hook 说"不行,滑点超限",这笔单直接不下。甚至可以"挂起":hook 说"等一下,我去查外部风控系统",整个流程暂停等结果回来再继续。这比"先下了再说,出了问题再撤"安全得多。
子 Agent 模式
Multi-Strategy Isolation
源码洞察
query() 函数——不是另起进程。子 agent 继承父 agent 的 state store 引用,但 setState 被替换成 no-op(只读)。唯一的例外是任务注册,它有一条专用通道直达 root store。子 agent 的 fork 模式更精巧:所有 fork 子 agent 共享字节级一致的 API 请求前缀,确保 prompt cache 可复用。
多策略并行架构:
主循环 ── 全局状态管理 + 风控守卫
├── 子策略:BTC 趋势跟踪(独立循环、只读全局状态)
├── 子策略:ETH 均值回归
├── 子策略:跨交易所套利
└── 风控守卫:全局监控(唯一有写权限的子进程)
设计原则:跑 5 个策略,每个策略都能看到全局持仓和行情,但没有一个能直接下单。想下单?提交到统一通道,过风控后才执行。BTC 趋势策略出 bug 了?直接关掉,ETH 策略和套利策略完全不受影响,全局状态也不会被搞乱。CC 里子 agent 的 setState 就是被换成了空函数——代码层面就不可能"偷偷"改全局状态。
可观测性
Structured Telemetry
源码洞察
queryChainId 和 depth 用于关联主 agent 和子 agent 的调用链。
设计原则:不要在"发了一个请求"的地方打日志,要在"做了一个决定"的地方打。"切换到备用交易所"比"发了一个 HTTP 请求"有用一万倍。每条日志带一个 traceId,把整条链路串起来——出了问题可以追溯:这笔亏损的单是哪个策略、在什么行情下、经过哪几层风控、最终在哪个交易所成交的。没有 traceId,5 个策略并行跑的时候,日志就是一锅粥。
总结映射表
| CC 架构模式 | 设计原则 |
|---|---|
| Query Loop | 状态打包成对象,每轮记录"为什么继续" |
| Tool Interface | 三个标签自动决定:并发跑、排队跑、还是要确认 |
| Batch Partitioning | 并发结果收齐后一次性合并,串行每步立即生效 |
| withRetry | 先分类再处理:重试、降级、报错、还是先做别的 |
| Immutable Store | 策略能看不能改,想下单必须走统一通道过风控 |
| Permission Pipeline | 流水线逐层过,连续被拦 = 策略可能出故障 |
| 5-tier Compaction | 大刀砍旧数据,然后把持仓和活跃订单补回来 |
| Streaming + Tombstone | 来一条处理一条,断线的操作打墓碑标记 |
| Dynamic Discovery | 交易所是插件,加新所不改策略代码 |
| Lifecycle Hooks | 不只通知,能拦截、能挂起等外部条件 |
| Sub-agents | 5 个策略并行跑,关掉一个不影响其余 |
| Chain Telemetry | 在"做决定"的地方打日志,traceId 串起全链路 |