"""组合策略模拟盘实走 step 单测(E1):mock run_backtest_json 验证落库/幂等/分流。""" import json import pytest from sanguo_trader import portfolio_paper from sanguo_trader.persistence import ( init_db, load_last_balance, load_positions, list_trades, save_account, update_account_status, ) def _mk_account(db, strategy_type="portfolio", status="running"): aid = save_account(db, { "name": "pf1", "mode": "live", "strategy_type": strategy_type, "symbols": ["hs300_subset"], "strategies": [{"name": "all_weather", "params": {"max_pool": 30, "benchmark": "000300.XSHG"}}], "initial_capital": 1_000_000, "start": "2026-01-01", "end": "2026-12-31", }) update_account_status(db, aid, status) return aid def _fake_result(date="2026-08-13", equity=1_050_000.0): return { "strategy": "all_weather", "equity_curve": [{"date": "2026-08-12", "equity": 1_040_000.0}, {"date": date, "equity": equity}], "stocks_selected": [ {"code": "600000", "amount": 1000, "avg_cost": 10.0, "price": 10.5, "value": 10500.0}, ], "trades": [ {"date": f"{date} 14:50:00", "code": "600000", "side": "buy", "filled_amount": 1000, "filled_price": 10.5, "commission": 5.0}, {"date": "2026-08-12 14:50:00", "code": "000001", "side": "buy", "filled_amount": 500, "filled_price": 11.0}, ], "metrics": {}, } def test_step_writes_balance_positions_today_trades(tmp_path, monkeypatch): db = str(tmp_path / "paper.db") init_db(db) aid = _mk_account(db) monkeypatch.setattr("sanguo_portfolio.runner_backtest.run_backtest_json", lambda params: _fake_result()) out = portfolio_paper.run_portfolio_live_step(db, aid, today="2026-08-13") assert out["date"] == "2026-08-13" assert out["trades"] == 1 # 只落当日成交 bal = load_last_balance(db, aid) assert bal["date"] == "2026-08-13" assert bal["total_equity"] == pytest.approx(1_050_000.0) assert bal["cash"] == pytest.approx(1_050_000.0 - 10500.0) pos = load_positions(db, aid, "account") assert pos["600000"]["volume"] == 1000 trades = list_trades(db, aid) assert len(trades) == 1 assert trades[0]["symbol"] == "600000" assert trades[0]["strategy_id"] == "all_weather" def test_step_idempotent_same_day(tmp_path, monkeypatch): db = str(tmp_path / "paper.db") init_db(db) aid = _mk_account(db) calls = [] monkeypatch.setattr("sanguo_portfolio.runner_backtest.run_backtest_json", lambda p: calls.append(1) or _fake_result()) r1 = portfolio_paper.run_portfolio_live_step(db, aid, today="2026-08-13") r2 = portfolio_paper.run_portfolio_live_step(db, aid, today="2026-08-13") assert r1.get("date") == "2026-08-13" assert r2.get("skipped") == "already stepped today" assert len(calls) == 1 def test_step_skips_no_new_trading_day(tmp_path, monkeypatch): db = str(tmp_path / "paper.db") init_db(db) aid = _mk_account(db) # 数据滞后:回放末日仍 08-12(账户已结算到 08-13 前先结算到 12) monkeypatch.setattr("sanguo_portfolio.runner_backtest.run_backtest_json", lambda p: _fake_result(date="2026-08-11", equity=1_030_000.0)) portfolio_paper.run_portfolio_live_step(db, aid, today="2026-08-12") monkeypatch.setattr("sanguo_portfolio.runner_backtest.run_backtest_json", lambda p: _fake_result(date="2026-08-11", equity=1_030_000.0)) r = portfolio_paper.run_portfolio_live_step(db, aid, today="2026-08-13") assert r.get("skipped") == "no new trading day" def test_step_stopped_account_skipped(tmp_path, monkeypatch): db = str(tmp_path / "paper.db") init_db(db) aid = _mk_account(db, status="stopped") monkeypatch.setattr("sanguo_portfolio.runner_backtest.run_backtest_json", lambda p: (_ for _ in ()).throw(AssertionError("不应跑引擎"))) r = portfolio_paper.run_portfolio_live_step(db, aid, today="2026-08-13") assert r.get("skipped") == "status=stopped" def test_step_rejects_cta_account(tmp_path): db = str(tmp_path / "paper.db") init_db(db) aid = _mk_account(db, strategy_type="cta") with pytest.raises(ValueError): portfolio_paper.run_portfolio_live_step(db, aid) def test_is_portfolio_branch(tmp_path): from sanguo_trader.live_orchestrator import _is_portfolio_account db = str(tmp_path / "paper.db") init_db(db) p_aid = _mk_account(db, strategy_type="portfolio") c_aid = _mk_account(db, strategy_type="cta") assert _is_portfolio_account(db, p_aid) is True assert _is_portfolio_account(db, c_aid) is False