fix(live): 卖出闸门无价透传改fail-closed拒单——issue#38修复一A收尾 [vps]
CI/CD / test (push) Failing after 11m5s
CI/CD / nas-deploy (push) Has been skipped
CI/CD / nas-verify (push) Has been skipped

08-24 300059 跨实例互卖定罪(issue#38):闸门虽有台账硬顶,但两处「无有效价格」
路径透传引擎原函数=数据源退化回全账户视图(引擎按账户算target,账户有量会替
别家卖到目标值/按账户卖出)——正是「账户可卖视图放行18归属4400股」的变体洞。

改动(_instance_order_wrappers):
- otv value>0 无价(市价拿不到且账本无此票无成本可回退)→ 拒单fail-closed,
  不再透传bt_otv引擎target语义(宁可少买不可互卖,策略session定夺=拒单语义)
- 负order_value无价 → 拒单,不再透传bt_ov按全账户卖出
- 事故主洞(同窗双卖吃到60s归因间隙)已由191270c即时归因关闭:新增钉死测试
  「双卖窗口阻断」——第一笔4400即时归因(on_order_done钩子)→第二笔闸门读
  账本可卖0拒单;闸门每调用现读positions_view的B链路行为从此有回归锚

测试:test_live_instance_orders +3(双卖窗口阻断/otv无价拒单/负ov无价拒单),
17/17绿;portfolio+data_platform+api全量819绿。B2 closeable口径已走账本
(InstancePosition直读账本info,无改动);账本bootstrap/重归=15:10恒等式
reconcile既有runbook。issue#38验收(恒等式连续3日PASS)按issue标准观察。
This commit is contained in:
2026-08-25 21:30:49 +08:00
parent 894c547297
commit 41495b0c08
2 changed files with 55 additions and 5 deletions
+14 -5
View File
@@ -158,8 +158,14 @@ def _instance_order_wrappers(ledger, bt_otv, bt_ov):
return _done(bt_order(security, -sell, *args, **kwargs))
price = _price(security, own)
if price <= 0:
logger.warning("[instance-order] %s 无有效价格 → 透传引擎 target 语义", security)
return _done(bt_otv(security, value, *args, **kwargs))
# issue#38 fail-closed:绝不透传引擎 target 语义——引擎按全账户
# 视图算 target,账户有量会替别家卖到目标值(300059 账户级放行的
# 变体)。无价=市价拿不到且账本无此票无成本可回退 → 拒单,
# 宁可少买不可互卖。
logger.warning(
"[instance-order] %s 无有效价格(账本无成本可回退) → 拒单 "
"fail-closed,不透传引擎账户级 target 语义", security)
return None
target = int(value / price) // lot * lot
diff = target - int(own["amount"])
if diff >= lot:
@@ -181,9 +187,12 @@ def _instance_order_wrappers(ledger, bt_otv, bt_ov):
own = _own(security)
price = _price(security, own)
if price <= 0:
logger.warning("[instance-order] %s 无有效价格 → 负 order_value 透传引擎",
security)
return _done(bt_ov(security, value, *args, **kwargs))
# issue#38 fail-closed:负 order_value 无价折算不出股数,透传引擎
# =按全账户卖出,可卖别家归属——拒单。
logger.warning(
"[instance-order] %s 无有效价格(账本无成本可回退) → 负 order_value "
"拒单 fail-closed,不透传引擎账户级卖出", security)
return None
want = int(abs(value) / price + 0.999) # 向上取整再由可卖量硬顶
sell = _sell_shares(security, want, own, "按价值卖出")
if sell <= 0:
@@ -200,3 +200,44 @@ def test_order_done_hook_silent_when_no_order(fakes):
ret = otv("513030.XSHG", 0)
assert ret is None and orders == []
assert fired == []
# ------------------ 修复一A(issue#38):闸门认账本,账户级语义绝不放行 ------------------
def test_double_sell_within_attribution_window_blocked(fakes):
"""08-24 300059 事故形态:同窗两次清仓,第一笔 4400 已即时归因(191270c)→
第二笔闸门读到账本可卖 0 → 拒单,绝不碰 18 归属的另 4400(B 修法链路闸门侧钉死)。"""
orders, _, _ = fakes
led = _ledger_with("300059.XSHE", 4400, price=2.0, trade_date="2026-08-20")
# 模拟 191270c 即时归因:下单返回 → 同步把本笔卖出落账本
led.on_order_done = lambda: led.apply_trade(
False, "300059.XSHE", 2.0, 4400, "t-sync", "2026-08-25")
otv, _ov = _instance_order_wrappers(led, None, None)
first = otv("300059.XSHE", 0)
second = otv("300059.XSHE", 0)
assert orders == [("300059.XSHE", -4400)] # 只有一笔,第二笔不再下
assert first is not None and second is None
def test_otv_no_price_no_ledger_holding_fail_closed(fakes, monkeypatch):
"""issue#38:无价且账本无此票(无成本可回退)→ 拒单 fail-closed。旧版透传
引擎 target 语义按全账户视图算 target,账户有量会替别家卖到目标值——
闸门数据源退化到账户级=300059 账户级放行的变体,必须堵死。"""
orders, _, otv_calls = fakes
import bullet_trade.data.api as bt_data
monkeypatch.setattr(bt_data, "get_current_data", lambda: _FakeCurrentData(0.0))
led = _ledger_with("ANY.XSHG", 0) # 账本无 NEW.XSHG
otv, _ov = _instance_order_wrappers(led, None, None)
out = otv("NEW.XSHG", 60000)
assert out is None and orders == [] and otv_calls == []
def test_negative_order_value_no_price_fail_closed(fakes, monkeypatch):
"""issue#38:负 order_value 无价(账本无成本可折算)→ 拒单,不透传引擎
按全账户卖出。"""
orders, _, ov_calls = fakes
import bullet_trade.data.api as bt_data
monkeypatch.setattr(bt_data, "get_current_data", lambda: _FakeCurrentData(0.0))
led = _ledger_with("ANY.XSHG", 0)
_otv, ov = _instance_order_wrappers(led, None, None)
out = ov("OTHER.XSHG", -30000)
assert out is None and orders == [] and ov_calls == []