Cloudflare Durable Object SQLite(每所有者一实例) · STRATEGY_OWNER → StrategyOwner(apps/strategy/wrangler.jsonc) · contexts/strategy

StrategyOwner DO

每个所有者一间「策略工作室」:写过的策略脚本与不可改的版本、运行配置、回测记录(摘要),以及模拟部署的逐根账本、事件与权益曲线。

不存钱:回测与模拟部署只算模拟账本,不对应任何 HL 订单,订单 / 仓位 / 资产权威在 Hyperliquid L1(ADR-0083);行情归 MARKET_ARCHIVE 行情归档(采集 DO 写);回测大块结果归 R2 BACKTEST_RESULTS;所有者身份与最近活跃时间归 USER_DB(apps/server)。

10 张表 67 个字段 4 个聚合 5 个迁移 语义层已对齐 · sha256:034db57c84906f34

板块颜色贯穿全文

脚本与配置

「写了什么策略、保存过哪些版本、配了哪些运行参数」

run_configs
运行配置
scripts
策略脚本
versions
策略版本(不可改)

回测

「这个版本在过去会怎样交易、今天还能跑几次」

backtests
回测记录(摘要)

模拟部署

「正在跑的部署走到哪根了、发生了什么、权益怎么变」

deployment_equity
部署权益曲线
deployment_events
部署事件流(只追加)
deployments
模拟部署

运行时簿记

「沙箱今天装了哪些版本、休眠什么时候查过、库迁到第几版」

owner_state
所有者级键值
sandbox_bundle_versions
当天沙箱包
schema_migrations
迁移版本

全库约定

业务流程跟着流程看表怎么变

✍️ 写一份策略并保存版本

  1. 所有者(或 AI 代写)建脚本 → 插一行 scripts
  2. 保存:官方模板或用户源码先 compileScript 静态校验 + 转译,失败直接返回全部问题,不落库
    Worker 里不执行脚本,只在沙箱里执行产物
  3. 通过 → versions 追加一行,版本号 = 该脚本 MAX(number)+1,源码、产物、参数声明一起存
    之后任何 UPDATE 都被 versions_immutable 挡下
  4. 建一份运行配置 → run_configs;之后改它只更新这一行,不产生新版本

📈 跑一次回测

  1. 取版本与运行配置(必须同脚本),读行情归档,按归档实际数据定数据段与数据修订号,算出 reuse_key
  2. backtests 里有同键的 → 直接返回最早那条(不重算、不计配额)
  3. 否则数今天(UTC)已新跑几次(created_at),满 100 次返回 quota_exceeded 与重置时刻
  4. 把版本记进 sandbox_bundle_versions 当天包,在沙箱里跑引擎
  5. 插一行 backtests(摘要),再把大块结果写到 R2 backtests/<id>.json;R2 失败就删掉这行
    同一所有者的回测排成一队,复用与配额判定不会被并发绕过

🤖 启动模拟部署并逐根推进

  1. 检查活跃部署不足 10 个 → 用归档里启动前已收盘的 K 线在沙箱里预热 → 插一行 deployments(运行配置快照 + 引擎快照),alarm 拨到 next_run_at
  2. alarm 触发:对每个到点的部署取游标之后已收盘的 K 线逐根推进;归档没跟上就 reschedule 到 30 秒后
    发版重启、alarm 晚到时一次补跑完漏掉的几根
  3. 每根一个 transactionSync:改 deployments(游标、收盘价、最高权益、出错计数、快照)+ 追加 deployment_events + upsert deployment_equity
  4. 连续 3 根出错 → 同一事务里转 paused(errors);回撤到上限 → 平仓、转 stopped(drawdown),推送照发
    静音只压开平仓与出错推送,自动暂停 / 停止照常推
  5. 推进完把活跃部署的币与触发区间报给采集 DO,盘中检查碰到区间时再唤醒本 DO 走 save

😴 所有者 30 天没打开 App

  1. alarm 推进完,若距 owner_state.dormancy_checked_at 已满一天,经 service binding 问 server 最近活跃时间
    server 不可用 → 不算查过、不暂停
  2. 拿到了 → 写 dormancy_checked_at;超过 30 天就把运行中的部署全部转 paused(dormant)并推送
  3. 所有者回来一键恢复:因休眠暂停的部署逐个重新预热到游标、转回 running,写 resumed 事件

设计要点

🏠 一个所有者一个库DO 名就是 user_id,库里没有 owner 列,也不可能查到别人的策略;隔离由 DO 边界给,不靠 WHERE。
🔒 版本不可改,用快照解耦版本由触发器 versions_immutable 兜底不可改;运行配置可改,所以回测与部署各自存 config_json 快照,改配置不追溯。
♻️ 复用键代替重算reuse_key 把「同一次回测」的全部身份规范化成一个串;命中就返回最早那条、不计配额。没有 UNIQUE,靠 DO 内排队串行。
🪣 摘要进库,大块进 R2DO SQLite 只放报告摘要;权益曲线、逐笔交易等大块在 R2 BACKTEST_RESULTS。先落库后写 R2,R2 失败补偿删行,不留没有大块的 completed 回测。
⚛️ 一次转换一个事务部署的每次状态转换(行 + 快照 + 事件 + 权益点)在 transactionSync 里一起写;转换本身是 domain 纯函数,仓储只负责落盘。
⏱️ 毫秒整数时间(已接受的例外)全库时间存 INTEGER epoch ms,便于和 K 线开盘 / 收盘对齐与 alarm 比较;这是对全仓 ISO TEXT 约定的已接受例外,不打算迁移。
🧩 旧行靠「读时默认值」兼容迁移只加列不回填:v1/v2 版本无产物 / 来源、v4 部署无回撤与引擎版本等,都在仓储读出时按注明的默认值解释(compiledOf / originOf / deploymentOf)。