Files
sanguo_vnpy_v2/tests/trader/test_portfolio_paper.py
T

121 lines
4.7 KiB
Python

"""组合策略模拟盘实走 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