关于 alpaca-broker-reconciliation-idempotency
面向 Alpaca Broker API 开发者的集成技能,说明如何通过幂等写入、事件去重和定期对账维护本地状态。重点涵盖异步结算、事件遗漏与状态更正等生产场景。
主要功能
- 01可重试的订单与账本写入
- 02SSE 事件去重和状态保护
- 03按时间窗口重新拉取并对账
- 04无事件资金通道的状态轮询
- 05状态映射与终态处理
适用场景
- 01设计可安全重试的资金或订单操作
- 02从重复及遗漏事件中恢复本地数据
- 03定期校准账户、账本和转账记录
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ alpacahq/ alpaca-skills --skill alpaca-broker-reconciliation-idempotency安装后在 Claude Code 中输入/alpaca-broker-reconciliation-idempotency确认已加载。
附带文件reference.md
详细介绍
Alpaca — 对账与幂等性
这项能力是区分演示系统与生产系统的关键。Alpaca 是一个异步、最终一致的系统:写入会在稍后完成,事件可能遗漏或重放,有些通道根本不会发出事件,而且“已执行”仍可能被撤销。你的任务是让本地数据库成为 Alpaca 状态的忠实、自我修复镜像。
请先阅读
alpaca-broker-integration,了解基础 URL 和身份验证(客户端凭据 Bearer 令牌;旧版 Basic 仍可用),然后阅读alpaca-broker-sse-events和相关领域技能。本技能将这些内容整合为一个架构。
核心原则
将 Alpaca 视为事实来源,将你的数据库视为必须与之最终收敛的缓存。 每次写入都是一个请求,而非事实。每个事件都是提示,而非保证。正确性来自幂等处理与对账循环,而绝不能来自假设任何单次调用或事件都恰好成功一次。
1. 三层防护
Layer 1 — Idempotent writes : never create a duplicate when you retry
Layer 2 — Idempotent event intake: never double-process a replayed/duplicate event
Layer 3 — Reconciliation sweep : re-pull authoritative state and fix any drift
三层都不可或缺。第 1 层和第 2 层确保系统在正常及重试情况下正确;第 3 层则能捕获仍然遗漏的所有问题(停机、错误、事件缺失、更正)。
2. 第 1 层 — 幂等写入
所有资金或订单写入都必须支持安全重试,因为你无法区分超时与成功。
- **订单:**设置自己的
client_order_id(≤128 chars),由你的交易 ID 派生。如果响应丢失,在重试前通过orders:by_client_order_id查询订单。 - **账簿分录:**发送
Idempotency-Key标头。同一个键和同一个请求体会返回原始账簿分录;同一个键配不同请求体则返回422。(参见alpaca-broker-journals。) - **本地优先排序:**在网络调用之前写入意图记录(附带生成的键),这样调用中途崩溃时仍有记录可供对账;切勿留下无法查找的孤立 Alpaca 对象。
- 立即持久化 Alpaca 返回的 ID(
account_id、order_id、journal_id、transfer_id)。这是你关联事件和进行对账的唯一键。
3. 第 2 层 — 幂等事件接收
事件采用至少一次投递:游标重放、重新连接和更正都会导致同一事件抵达多次。
- 先保存快照,并以去重 ID 为键。使用 upsert 或重复时跳过的方式插入原始事件,使重复事件成为无操作。仔细选择键:
event_id(ULID)是重放游标;在 Activity SSE 中,去重键是ref_id,这是文档规定的键,用于重放时至少一次重新投递,以及通过previous_id关联更正或撤销。在旧版状态流中,event_id同时承担这两种作用。 - 以 Alpaca ID 而非本地自增 ID 标识业务记录,并采用 upsert 语义。
- **根据当前状态防护状态转换。**执行操作前,检查记录是否已处于终态,避免重复的“已执行”或“已成交”事件再次触发付款或通知。
- **按记录串行化变更。**按
account_id对消费者分区是最简单的默认方案,因为 Alpaca 保证每个账户内的事件顺序;也可以使用SELECT … FOR UPDATE行锁或乐观版本列。 - 仅在成功后推进游标(至少一次投递,通过上述措施确保安全)。
4. 第 3 层 — 对账/修复任务
定时任务,从 Alpaca 重新拉取权威状态并在本地执行插入或更新。这能让系统具备自我修复能力。
规范的窗口式修复(经验):
- 通过 Activity SSE(
GET /v2beta1/events/activities)重新读取活动,并使用since/until设置边界。有界重放是批处理任务,不是订阅:流到达until时会以200结束,since/until根据业务时间(at)筛选。(GET /v1/accounts/activities已标记为完全替代,但它仍是获取 2026-02-11 之前入账活动的唯一来源;对于任何回溯到该日期之前的时间窗口,都应使用它。) - 对同一时间窗口内的日记账(
GET /v1/journals)和转账进行分页读取。 - 使用 Alpaca ID 作为键,将每条记录插入或更新到你的表中。重复运行安全,最终会收敛到一致状态。
- 限制并发(使用小型工作线程池),并遵守速率限制(
alpaca-broker-rate-limits-resilience)。
为什么要用时间窗口,而不是只查“距上次运行以来”的数据:这样可以涵盖更正、延迟结算,以及部署期间遗漏的事件,而不必每晚重新扫描所有历史记录。选择一个长于最长合理故障时长的窗口。
5. 轮询没有事件的业务渠道
并非所有内容都会发出 SSE。没有事件时,你必须轮询。
- 每笔转账的资金钱包状态(v1beta)不会主动推送——应定时轮询
GET /v1beta/.../funding_wallet/transfers/{id}。 - 将轮询器错开安排在整个时间间隔内,避免所有业务渠道同时触发,导致 API 负载激增。
- 只轮询非终态记录。 将查询筛选为
status IN (pending, processing, …);记录达到终态后,将其从轮询集合中移除。这样可以限制工作量,并避免重复通知。 - 注意:经典电汇没有按 ID 查询的 GET 接口: 必须调用
GET /v1/accounts/{id}/transfers?direction=OUTGOING(一个列表),并在客户端匹配 ID;在一次运行中按账户缓存列表。
经验: 轮询意味着延迟。请说明预期延迟(例如“取款状态会在约 1 小时内更新”),以便产品和支持团队设定合理预期。
6. 状态状态机映射
Alpaca 提供多种状态枚举(账户、订单、日记账、转账、资金钱包),每种都有自己的词汇。不要在应用中到处散落 Alpaca 原始状态字符串。
- 为每个领域定义一张明确的映射表,将 Alpaca 状态映射为你的内部状态(例如,转账状态
COMPLETE→COMPLETED;REJECTED/CANCELED/RETURNED→CANCELLED)。 - 大小写和状态集合并不统一。 转账状态为大写,且包含
RETURNED;没有failed。日记账状态为小写。REST 的TransferStatus枚举和资金事件枚举甚至不是同一组状态,因此要映射两者的并集。 - 妥善处理未知状态——记录日志并跳过,绝不要崩溃。Alpaca 会随时间增加新的状态值。
- 了解哪些状态是终态(不同枚举各不相同),以便停止轮询/处理这些状态。
7. 设计时需考虑的最终一致性隐患
executed/COMPLETE并不总是最终状态——日记账可能会被资金处理流程冲正;转账看似完成后也可能变为RETURNED。即使超过“正常”终态,也要在一段时间内继续对账。- 更正会生成新的 ID。 日记账
correct会取消原记录,并创建一个携带实际资金的新日记账 ID。基于事件快照和 Alpaca ID 的对账可以处理这种情况;原地修改原记录的逻辑则无法处理。 200表示已接受,而非已结算。 绝不要仅凭创建请求的响应就向用户确认资金已转移——应根据终态事件/轮询结果进行确认。- 事件乱序和重复属于正常情况(参见
alpaca-broker-sse-events§5)。幂等性可以吸收这些情况。
8. 反模式(实际案例)
- ❌ 重新连接 SSE 流时未提供 since 游标(v2 中为
since_id,v1 中为since_ulid)→ 间隔期间的所有事件都会被静默丢弃。(修复方法:持久化并重放游标。) - ❌ 将 SSE 流视为唯一数据源 → 漏掉的事件没有补救机制。(修复方法:添加修复任务。)
- ❌ 在没有幂等键的情况下盲目重试 journal/order → 导致重复资金转移。
- ❌ 将
executed视为不可逆 → 发生冲正/更正时账目会出错。 - ❌ 使用
confirmed集合而非seen集合进行去重 → judge/reject 变动会导致重复处理永无止境。对所有已见事件进行去重,并使用 Alpaca/event ID 作为键。
9. 整合起来
WRITE: local intent row (idempotency key) → Alpaca call → store Alpaca ID
LIVE: SSE consumer (cursor-replay, snapshot-keyed dedup, status-guarded upsert)
POLL: schedulers for rails with no events (non-terminal records only)
HEAL: windowed re-pull of activities/journals/transfers → upsert
MAP: Alpaca status → internal status, terminal-aware, unknown-tolerant
相关技能: 事件消费机制 → alpaca-broker-sse-events;按领域使用幂等键 → alpaca-broker-journals、alpaca-broker-trading-orders;轮询通道 → alpaca-broker-funding-transfers;修复任务中的退避与速率限制 → alpaca-broker-rate-limits-resilience。