fix(live): runtime副本孤立加载ImportError根治——2026-08-20晨12引擎崩溃循环事故(6实盘+6影子自建队07:10起每60s崩,当日0成交,9:05/9:30/9:35全未触发)——根因=live_strategy.py台账/B2两行相对导入(from . import live_instance_ledger/from .live_portfolio_proxy):文件被_instance_adapter逐字节复制到runtime/<实例>/后bullet_trade以顶层模块加载(无包上下文)→ImportError;测试全绿盲区=测试以包成员导入相对导入合法,生产孤立加载才崩,nas-verify不启引擎——修=改绝对导入from sanguo_portfolio import(引擎进程项目根在sys.path,同文件其它绝对导入生产已验证可跑);抽_ledger_tools()单函数两种加载上下文单测直打;+3测试(孤立副本顶层import_module复现事故点/包内上下文/静态禁from .回潮);_instance_adapter启动时内容比对自动重拷→VPS落地后下轮引擎重启自愈免手工清runtime;顺手修2个日期硬编码测试(proxy fixture与snapshot的trade_date=2026-08-19→动态今天,当日买入跨日T+1断言必翻红,CI不跑此套件故昨日未暴露);portfolio 398绿 [vps]

This commit is contained in:
2026-08-20 11:41:47 +08:00
parent 9fa2f933ee
commit e7c5defe64
4 changed files with 78 additions and 4 deletions
+3 -1
View File
@@ -167,7 +167,9 @@ class TestAttributionAndSnapshot:
db = str(tmp_path / "live.db")
init_db(db)
led = LiveInstanceLedger(initial_cash=1_000_000)
led.apply_trade(True, "000001.XSHE", 10.0, 100, "t1", "2026-08-19")
# trade_date=今天(动态):快照按 now 判 T+1,写死日期跨日必翻红(frozen 归 0)
led.apply_trade(True, "000001.XSHE", 10.0, 100, "t1",
datetime.now().strftime("%Y-%m-%d"))
engine = _fake_engine()
_snapshot_once(engine, db, 44, led)
+4 -1
View File
@@ -4,6 +4,7 @@
全账户污染);现价从真 portfolio 透传(市场数据非所有权);未覆盖属性
fail-fast;无账本(回测/影子)零改动。
"""
from datetime import date
from types import SimpleNamespace
import pytest
@@ -29,8 +30,10 @@ class _RealPos:
def ledger():
led = LiveInstanceLedger(initial_cash=1_000_000)
# 买入 510300@4.74×4200(费用=est) → cash 减,持仓进账本
# trade_date=今天(动态):T+1 断言语义=「当日买入锁定」,写死日期跨日必翻红
led.apply_trade(is_buy=True, symbol="510300.XSHG", price=4.74,
volume=4200, trade_id="t1", trade_date="2026-08-19")
volume=4200, trade_id="t1",
trade_date=date.today().isoformat())
return led
@@ -0,0 +1,54 @@
"""runtime 副本孤立加载回归(2026-08-20 事故)。
生产路径 = runner_live/_shadow 的 _instance_adapter 把
sanguo_portfolio/live_strategy.py 逐字节复制到 runtime/<实例>/live_strategy.py,
bullet_trade 以**顶层模块**加载(无包上下文)。历史上 ``from . import`` 相对导入
在该上下文直接 ImportError → 12 引擎(6实盘+6影子)自建队起崩溃循环 4 小时、
当日 0 成交;测试全绿是因为测试以包成员导入,相对导入合法——加载方式不一致
就是盲区。本测试按生产方式加载副本并直接调用 _ledger_tools(事故行),
相对导入复发会在本地测试就红(注:CI 只跑 tests/data_platform,本文件靠
本地 venv310 会话验收,属已知 CI 覆盖缺口)。
"""
from __future__ import annotations
import importlib
import shutil
import sys
from pathlib import Path
import pytest
ADAPTER = Path(__file__).resolve().parents[2] / "sanguo_portfolio" / "live_strategy.py"
@pytest.fixture
def standalone_module(tmp_path, monkeypatch):
"""按生产方式加载:拷副本到独立目录 → sys.path 指向该目录 → 顶层 import。"""
dst = tmp_path / "live_strategy.py"
shutil.copyfile(ADAPTER, dst)
monkeypatch.syspath_prepend(str(tmp_path))
monkeypatch.delitem(sys.modules, "live_strategy", raising=False)
return importlib.import_module("live_strategy")
def test_ledger_tools_under_standalone_load(standalone_module):
"""孤立加载(无包上下文)下 _ledger_tools 必须可用——2026-08-20 事故复现点。"""
lil, wrap = standalone_module._ledger_tools()
assert hasattr(lil, "get_active")
assert callable(wrap)
def test_ledger_tools_under_package_load():
"""包内上下文(测试/回测)同样可用——两种加载方式都必须成立。"""
from sanguo_portfolio import live_strategy as pkg_mod
lil, wrap = pkg_mod._ledger_tools()
assert hasattr(lil, "get_active")
assert callable(wrap)
def test_adapter_has_no_relative_imports():
"""适配文件禁止 ``from .`` 相对导入(复制出去必崩,静态兜底防回潮)。"""
for ln in ADAPTER.read_text(encoding="utf-8").splitlines():
s = ln.strip()
assert not s.startswith("from ."), f"相对导入残留: {s}"