feat(paper): 模拟盘现金/仓位体系——购买力守门、run 额度、资金流水与风控收口#131
Conversation
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>
commit `8003cb0`
必修(无)未发现 critical / major 级问题。 可选优化(medium)
其余:并发下单锁序统一(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>
Deploying inalpha-web with
|
| 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 |
背景
用户 dashboard 实锤:USD 1 万开户,SOL/USDT runner 满仓买入后 USDT 桶 -9,498、折算现金仅剩 $501——spot 买入在 live/HTTP 写路径没有任何购买力检查,跨币种 cash 只做了分桶记账,每个 runner 还各自假装有固定 1 万。本 PR 系统性修复模拟盘的现金/仓位体系,并补齐账户资金管理与多项风控缺口。
主要改动
购买力与额度
perp 风控
账户资金管理(agent 交互)
估值与可视化
测试
注意
feat/multi-user-login分支的 0024 同 down 0023(双头):后并入 main 的分支需把迁移链修直alembic upgrade head(引用新列,不跑服务启动即崩)Closes #114
🤖 Generated with Claude Code