diff --git a/sanguo_portfolio/runner_live.py b/sanguo_portfolio/runner_live.py index 72e68da..cedefd4 100644 --- a/sanguo_portfolio/runner_live.py +++ b/sanguo_portfolio/runner_live.py @@ -96,6 +96,25 @@ def _snapshot_loop(engine: Any, db: str, account_id: int, logger.warning("[live-snapshot] 落库失败 (account=%s): %s", account_id, e) +def _instance_adapter(account_id: str) -> Path: + """按账户复制一份策略适配文件。 + + bullet_trade 实例锁判重键=主机+strategy_path+broker_type+account_identity; + 多实盘共用同一 QMT 账号(合法场景:同账号跑多策略)时 strategy_path 相同会被 + 误判"重复实例"拒启 → 每账户一份副本(内容同、路径异)即视为不同逻辑实例。 + """ + if not account_id: + return ADAPTER_FILE + dst = (Path(__file__).resolve().parent.parent / "runtime" + / f"live_{account_id}" / ADAPTER_FILE.name) + dst.parent.mkdir(parents=True, exist_ok=True) + if not dst.exists() or dst.read_text(encoding="utf-8") != \ + ADAPTER_FILE.read_text(encoding="utf-8"): + import shutil + shutil.copyfile(ADAPTER_FILE, dst) + return dst + + def run_live(provider_config: Dict[str, Any] | None = None) -> None: """装配 LiveEngine(strategy_file=适配文件 + QmtBroker)并 run(阻塞)。""" from bullet_trade.core.live_engine import LiveEngine # type: ignore @@ -116,7 +135,7 @@ def run_live(provider_config: Dict[str, Any] | None = None) -> None: logger.info("QmtBroker 装配 account=%s data_path=%s", cfg["account"], cfg["mini_path"]) engine = LiveEngine( - ADAPTER_FILE, + _instance_adapter(cfg["account_id"]), broker_factory=lambda: broker, # bullet_trade 每实例锁 runtime 目录(单实例设计);多实盘并行须各用独立目录 live_config={"runtime_dir": str( diff --git a/sanguo_trader/shadow/runner.py b/sanguo_trader/shadow/runner.py index 3287aeb..cfc1032 100644 --- a/sanguo_trader/shadow/runner.py +++ b/sanguo_trader/shadow/runner.py @@ -121,6 +121,24 @@ def _snapshot_loop(broker: Any, db: str, account_id: int, logger.warning("[shadow-snapshot] 落库失败 (account=%s): %s", account_id, exc) +def _instance_adapter(account_id: str) -> Path: + """按账户复制一份策略适配文件(同 runner_live._instance_adapter)。 + + bullet_trade 实例锁按 strategy_path+broker_type+account_identity 判重; + 多影子账户共用 ShadowBroker(同 identity)时须路径互异才不被判重复实例。 + """ + if not account_id: + return ADAPTER_FILE + dst = (Path(__file__).resolve().parents[2] / "runtime" + / f"shadow_{account_id}" / ADAPTER_FILE.name) + dst.parent.mkdir(parents=True, exist_ok=True) + if not dst.exists() or dst.read_text(encoding="utf-8") != \ + ADAPTER_FILE.read_text(encoding="utf-8"): + import shutil + shutil.copyfile(ADAPTER_FILE, dst) + return dst + + def run_shadow(provider_config: Optional[Dict[str, Any]] = None) -> None: """装配 LiveEngine(影子 broker)并 run(阻塞)。""" from bullet_trade.core.live_engine import LiveEngine # type: ignore @@ -153,7 +171,7 @@ def run_shadow(provider_config: Optional[Dict[str, Any]] = None) -> None: ) engine = LiveEngine( - ADAPTER_FILE, + _instance_adapter(cfg["account_id"]), broker_factory=lambda: broker, # 独立 runtime 目录:bullet_trade 单实例锁默认 ./runtime, # 影子与实盘并行(双轨§8)会互抢锁,按账户分目录隔离