Skip to content

feat(paper): 模拟盘现金/仓位体系——购买力守门、run 额度、资金流水与风控收口#131

Open
mirror29 wants to merge 10 commits into
mainfrom
feat/paper-buying-power-allocation
Open

feat(paper): 模拟盘现金/仓位体系——购买力守门、run 额度、资金流水与风控收口#131
mirror29 wants to merge 10 commits into
mainfrom
feat/paper-buying-power-allocation

Conversation

@mirror29

@mirror29 mirror29 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

背景

用户 dashboard 实锤:USD 1 万开户,SOL/USDT runner 满仓买入后 USDT 桶 -9,498、折算现金仅剩 $501——spot 买入在 live/HTTP 写路径没有任何购买力检查,跨币种 cash 只做了分桶记账,每个 runner 还各自假装有固定 1 万。本 PR 系统性修复模拟盘的现金/仓位体系,并补齐账户资金管理与多项风控缺口。

主要改动

购买力与额度

  • spot BUY 账户折算购买力守门(live/HTTP 双路径):各币种桶按 FX 折算成 base 总可用现金,超过即拒;桶允许为负(隐式借计价货币),总现金池不可能再被买穿。事务内恒锁账户行,统一全局锁序 accounts→positions,消除死锁窗口
  • per-run allocation:每个 runner 的资金额度 start 时落库(默认 min(10000, 可用 − 其他 run 已分配)),sizing 与 run 级购买力以它为上限;resume 回灌净已实现盈亏,重启不"回血"
  • 同账户同 venue/symbol 第二个 run start 即 409(缓解 [paper] 同账户同 venue/symbol 多 run 共享一行持仓,互相打架 #108,短期方案 1;per-run 持仓隔离留后续)

perp 风控

账户资金管理(agent 交互)

  • account_cash_flows 流水表:充值/重置一律先留痕再改余额(改钱=改绩效口径,必须可审计还原)
  • POST /accounts/me/deposit|reset + GET cash_flows;reset 删持仓、现金回基准、历史订单/复盘全保留,有 running run 时 409
  • agent 工具 paper.deposit_cash / paper.reset_account(permission ask 气泡审批)/ paper.list_cash_flows
  • reset 纪元收口:快照 realized_pnl 改 closed_trades + reset epoch 单一口径;风控成交窗口按 reset epoch 收口;快照新增 net_external_flows(真实收益 = 权益 − 基准 − 净充值)

估值与可视化

  • /accounts/me 持仓估值改 mark-to-market(spot qty×最新价含浮盈;perp 计未实现盈亏),总权益随行情浮动
  • dashboard 持仓/runner 显式「现货/合约」徽标 + 杠杆/保证金列 + run 额度展示;修 runs list 漏查 trading_mode/leverage(列表恒返 spot;重启 resume 会把 perp run 静默降级 spot/1×)

测试

注意

  • migration 0025/0026 与 feat/multi-user-login 分支的 0024 同 down 0023(双头):后并入 main 的分支需把迁移链修直
  • 合并后 dev/线上库需 alembic upgrade head(引用新列,不跑服务启动即崩)

Closes #114

🤖 Generated with Claude Code

mirror29 and others added 9 commits July 2, 2026 12:15
live run 的仓位测算此前用固定 1 万虚拟现金,脱离账户真实余额。加 nullable
allocation 列(老行为空=旧语义),start 时确定并落库,使 sizing 可复现、可审计。

注:与并行登录分支的 0024 同 down 0023,后并入 main 者需把迁移链修直。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
四个互相咬合的缺口一并修(共享同一批文件,拆开会破坏各自的可编译性):

1. spot BUY 账户折算购买力守门(此前 live/HTTP 写路径无现金检查,USD 开户买
   USDT 计价对把 USDT 桶买成 -9,498 实锤):各币种桶按 FX 折算成 base 总可用
   现金,notional+fee 超过可用×0.99 拒单;桶仍可为负(隐式借计价货币),总折算
   现金不允许被买穿。HTTP 409 INSUFFICIENT_CASH;live 记 risk_rejected 决策行
   不杀 run。事务内账户行 FOR UPDATE 防 TOCTOU,锁内复用预取汇率零网络。
2. per-run allocation:POST /strategy_runs 可选 allocation,默认
   min(10000, 账户折算可用现金) 落库;live session 以它为虚拟钱包做 sizing 与
   run 级购买力,替代固定 1 万;账户可用 ≤0 时 422 拒 start。
3. 同标的守门(issue #108 短期方案):同账户同 (venue, symbol) 已有 running
   run 再 start → 409 SYMBOL_RUN_CONFLICT,堵住多 run 共享一行持仓互相打架。
4. /accounts/me 持仓估值改 mark-to-market(data /ticker 缓存价):spot 计
   qty×最新价、perp 计未实现盈亏;拿不到最新价降级开仓均价/0 并入 fx_warnings
   不静默。此前按开仓均价估值,总权益恒≈初始资金、不反映浮盈。

顺带修:strategy_runs list/list_all_running SELECT 漏 trading_mode/leverage
(列表端点恒返 spot;重启 resume 会把 perp run 静默降级 spot/1×);/positions
响应加派生 trading_mode 字段供前端显式标注现货/合约。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Record 类型补全

- start_strategy tool/client 加可选 allocation(run 资金额度,省略走服务端默认),
  tool 描述补同标的 409 与额度不足 422 两个坑
- TS StrategyRunRecord 镜像补 trading_mode / leverage / allocation;
  AccountSnapshot 注释对齐 mark-to-market 新口径

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
此前持仓表靠强平价非空隐式判 perp、杠杆徽标仅 >1× 显示、保证金藏 tooltip,
runner 列表完全不显示交易模式——用户分不清跑的是现货还是合约:

- 持仓表:每行「现货/合约」徽标(用后端派生 trading_mode,不再隐式推断),
  perp 杠杆徽标恒显示(含 1×),保证金提为可见列
- 总览 runner 面板 / runner 卡片 / run 详情页:标的旁「合约 N×」/「现货」徽标,
  详情与卡片展示 allocation(资金额度)
- zh/en 文案同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
充值/提取/重置一律先留痕再改余额(同事务):模拟盘改钱=改绩效口径,无流水的
余额变更会让收益率/榜单/审计链失信。成交现金变动仍由 orders/closed_trades
承载,不重复记账。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
一、用户可改可用资金,全程留痕:
- POST /accounts/me/deposit:入指定币种桶(默认 base),流水 kind=deposit;
  不改 initial_cash(充值≠赚钱,绩效口径由流水可还原)
- POST /accounts/me/reset:删全部持仓行 + 现金回 {base: initial_cash}(可传
  新基准);有 running run 时 409(runner 会立刻把仓开回来);orders/
  closed_trades/strategy_runs 历史保留(审计不可抹);流水 note 记旧桶明细
- GET /accounts/me/cash_flows:外生资金事件列表
- 事务内账户行 FOR UPDATE,与购买力守门/并发充值串行化

二、perp 保证金守门跨仓聚合(issue #114):
- HTTP 与 live 两条写路径从「单笔目标 IM vs 全钱包」改为「其他活跃仓已占 IM
  (positions.margin_used 权威值,同计价货币) + 本笔目标 IM + fee ≤ 钱包」,
  堵住多仓合计超钱包的洞;拒单 details 带 others_im 供排查
- 与回测引擎 Portfolio free_margin 口径对齐

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
对账后复查发现的 5 个问题一并修(前两个是上一批引入的锁粒度不对称):

1. perp 跨仓聚合 TOCTOU(高):守门只锁 per-symbol 持仓行,两笔并发开仓在
   **不同 symbol** 同一钱包时互不阻塞,各读旧 others_im/钱包双双过闸 → 合计
   IM 超钱包——恰是聚合要堵的洞。改为下单事务恒锁账户行,live 路径补事务内
   保证金权威复检(新 InsufficientMarginError,竞态拒单不杀 run)。
2. 锁序统一 accounts → positions(中):此前 spot SELL/perp 先锁持仓行再扣
   cash,与 deposit/reset(先锁账户)反序,并发有死锁窗口;恒锁账户行后消除。
3. 账户快照加 net_external_flows(中):自最近一次 reset 以来的净充值(折
   base)。真实收益 = equity − initial_cash − net_external_flows——此前消费者
   按 initial_cash 算收益率会把充值当成盈利(1 万户充 1 万显示 +100%)。
4. 自动 allocation 扣减已分配额度(中):min(10000, 可用 − Σrunning run 额度),
   防 N 个 run 集体超额认领资本(∑allocation ≫ 现金,per-run 钱包虚高)。
5. 充值币种白名单(低):KNOWN_CASH_CURRENCIES(各市场本币+USD 稳定币),
   任意字符串会建出 FX 永远折算不了的垃圾桶 → 422 UNSUPPORTED_CURRENCY。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
用户改资金走 agent 对话而非前端页面:

- paper.deposit_cash / paper.reset_account:permission ask(前端气泡确认,与
  promote_candidate 同门)——改钱=改绩效口径必须人点头,流水留痕是第二道防线;
  tool 描述三段式,写明 reset 前须停 running run、调前先报告将被清的持仓
- paper.list_cash_flows:命中 paper.list_* allow,解释收益率口径用
- paper.get_account 描述更新:mark-to-market 新口径 + net_external_flows
  (报告收益率必须减净充值);start_strategy 描述补自动额度扣减语义
- AccountSnapshot/StartStrategyParams 类型对齐;permissions YAML 与
  defaults.ts 同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reset = 绩效新纪元,三处此前不感知它的口径统一收口:

1. 快照 realized_pnl 改从 closed_trades(成交审计源)按最近一次 reset 起算:
   此前从 positions 行汇总,reset 删行后快照凭空归零而 closed_trades 仍在,
   两套"已实现盈亏"互相矛盾;现在单一口径,重置后从 0 起、新平仓正常累计。
2. 风控成交窗口按 reset epoch 收口(PostgresTradeRepository.refresh):
   否则 lookback 窗口内的旧亏损会在重置后的"干净"账户重新触发
   MaxDrawdown/LowProfit 锁。已存在的 risk_locks 行无 account 维度暂无法按户
   清理,到期自然失效(结构性多租户项另行处理)。
3. live run resume 回灌净已实现盈亏(毛已实现 − 手续费,自 started_at)到
   session 钱包(新 Portfolio.adjust_cash):此前重启后钱包从 allocation 满额
   重建——亏损 run 一重启就"回血",盈利 run 赚到的额度凭空消失。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
commit `8003cb0`

必修(无)

未发现 critical / major 级问题。

可选优化(medium)

  1. [medium] services/paper/src/inalpha_paper/storage/positions.py:322-329 (sum_other_margin_used) —— 跨仓保证金聚合的 SQL 用 currency = %s 精确匹配,漏了 currency IS NULL 的老持仓行。
    0009_multicurrency_cash.py 的 migration 说明本身写明「positions.currency NULL = 0008 之前的旧行,读取层按 (venue, symbol) 用 currency_resolver 再解析兜底」,api/orders.py_decimal_to_float / get_my_account 都遵循了这个约定(p.get("currency") or resolve_currency(...)),但本次新加的 sum_other_margin_used 没有走这条兜底路径。
    失败场景:账户上有一个 0009 之前建仓、此后再没有成交过的老 perp 持仓行(currency IS NULL,margin_used > 0),此时在另一个 symbol上开新 perp 仓,others_im 会漏掉这笔老仓的保证金,跨仓聚合守门(本 PR 主打的 perp 跨仓保证金聚合校验(替代单笔 IM gate) #114 修复)就可能放过一笔实际已经超钱包的加仓——正是这个聚合要堵的洞。建议 SQL 里对 currency IS NULL 的行按 venue/symbol fallback(或直接 COALESCE)。

  2. [medium] services/paper/src/inalpha_paper/api/orders.py:176-228 (spot BUY 购买力守门) —— 在已经 FOR UPDATE 锁住 accounts 行、事务未提交的情况下,为取 FX 汇率发起对 data-service 的 HTTP 调用(DataClient 默认 30s 超时)。data-service 变慢或不可用时,这笔订单会把该账户的行锁和一条 DB 连接持有到 30s,期间该账户上的所有其他下单 / 充值 / 重置请求都会被阻塞在锁等待上;如果多个账户同时触发(如 data-service 大面积降级),会在短时间内占满连接池,影响面不再局限于单账户。commit message 里已经标注这是"模拟盘低并发可接受"的取舍,但既然是持锁期间的外部网络调用,建议至少确认这条路径有熔断 / 降级预案,或后续把汇率预取挪到锁外(FOR UPDATE 前)。

其余:并发下单锁序统一(accounts→positions)、perp 跨仓 TOCTOU 权威复检、reset 纪元下 realized_pnl / 风控成交窗口口径收口、resume 净已实现盈亏回灌等改动逻辑自洽,测试覆盖了边界情况(安全垫、fail-closed、跨币种、并发竞态拒单)。migration 链双头(0025 vs 并行分支 0024)已在 PR 描述里显式说明,后续合并需修直。

两处 auto-review 指出的修复:

1. 持锁期间零 HTTP(medium):spot BUY 购买力守门的 FX 汇率预取从事务内移到
   事务**外**(LOCK 之前),锁内复用 offline_copy(零网络)。data 慢时不会占住
   DB 连接与账户行锁——模拟盘低并发也要设这个下限。
   追加:BaseCurrencyConverter.base property 供锁内核对 base 一致性。

2. sum_other_margin_used 漏 currency IS NULL 老仓(medium):0009 迁移前的持仓
   行 currency=NULL,SQL 精确匹配 currency=%s 会漏掉它们的保证金;改为 Python
   侧用 currency_resolver 兜底解析,与账户快照读取层同约定。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploying inalpha-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: b1b7a41
Status: ✅  Deploy successful!
Preview URL: https://71c2d44c.inalpha-web.pages.dev
Branch Preview URL: https://feat-paper-buying-power-allo.inalpha-web.pages.dev

View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perp 跨仓保证金聚合校验(替代单笔 IM gate)

1 participant