121 lines
4.7 KiB
Python
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
|