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:
@@ -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,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}"
|
||||
Reference in New Issue
Block a user