test: reproduce multi-day execution audit gaps
This commit is contained in:
@@ -11,6 +11,7 @@ import pytest
|
|||||||
from quant_engine.execution import (
|
from quant_engine.execution import (
|
||||||
ExecutionConfig,
|
ExecutionConfig,
|
||||||
ExecutionResult,
|
ExecutionResult,
|
||||||
|
ExecutionSimulationResult,
|
||||||
apply_bid_ask_spread,
|
apply_bid_ask_spread,
|
||||||
apply_volume_constraint,
|
apply_volume_constraint,
|
||||||
check_price_limit,
|
check_price_limit,
|
||||||
@@ -20,6 +21,7 @@ from quant_engine.execution import (
|
|||||||
run_end_to_end_poc,
|
run_end_to_end_poc,
|
||||||
simulate_execution,
|
simulate_execution,
|
||||||
simulate_multi_day,
|
simulate_multi_day,
|
||||||
|
simulate_multi_day_with_audit,
|
||||||
simulate_with_daily_data,
|
simulate_with_daily_data,
|
||||||
total_costs,
|
total_costs,
|
||||||
total_turnover,
|
total_turnover,
|
||||||
@@ -357,6 +359,73 @@ def test_simulate_multi_day_holdings_evolution():
|
|||||||
assert "A" in positions[2].holdings
|
assert "A" in positions[2].holdings
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_multi_day_with_audit_rebalances_target_weights_by_delta():
|
||||||
|
"""相同目标权重不应在每个交易日重复买入。"""
|
||||||
|
config = ExecutionConfig(
|
||||||
|
commission_bps=0,
|
||||||
|
stamp_tax_bps=0,
|
||||||
|
slippage_bps=0,
|
||||||
|
min_trade_amount=0,
|
||||||
|
)
|
||||||
|
targets = [(date, {"A": 1.0}) for date in ("d1", "d2", "d3")]
|
||||||
|
prices = [(date, {"A": 10.0}) for date in ("d1", "d2", "d3")]
|
||||||
|
|
||||||
|
result = simulate_multi_day_with_audit(targets, prices, 1_000.0, config)
|
||||||
|
|
||||||
|
assert isinstance(result, ExecutionSimulationResult)
|
||||||
|
assert [len(day.executions) for day in result.daily_executions] == [1, 0, 0]
|
||||||
|
assert result.total_turnover == pytest.approx(1_000.0)
|
||||||
|
assert [position.cash for position in result.positions] == pytest.approx([0.0, 0.0, 0.0])
|
||||||
|
assert [position.holdings["A"] for position in result.positions] == pytest.approx(
|
||||||
|
[100.0, 100.0, 100.0]
|
||||||
|
)
|
||||||
|
assert [position.portfolio_value for position in result.positions] == pytest.approx(
|
||||||
|
[1_000.0, 1_000.0, 1_000.0]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_multi_day_with_audit_records_costs_without_replay():
|
||||||
|
"""成交成本与日末 NAV 应来自同一次状态推进。"""
|
||||||
|
targets = [("d1", {"A": 1.0}), ("d2", {"A": 1.0})]
|
||||||
|
prices = [("d1", {"A": 10.0}), ("d2", {"A": 10.0})]
|
||||||
|
|
||||||
|
result = simulate_multi_day_with_audit(targets, prices, 1_000.0)
|
||||||
|
|
||||||
|
first_day = result.daily_executions[0]
|
||||||
|
assert first_day.nav_before == pytest.approx(1_000.0)
|
||||||
|
assert first_day.nav_after == pytest.approx(result.positions[0].portfolio_value)
|
||||||
|
assert result.total_costs == pytest.approx(sum(r.total_cost for r in first_day.executions))
|
||||||
|
assert result.final_portfolio_value == pytest.approx(1_000.0 - result.total_costs)
|
||||||
|
assert result.daily_executions[1].executions == ()
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_multi_day_with_audit_records_unpriced_target_rejection():
|
||||||
|
"""缺失价格的目标不能吞掉现金,且必须留下拒绝原因。"""
|
||||||
|
result = simulate_multi_day_with_audit(
|
||||||
|
[("d1", {"A": 1.0})],
|
||||||
|
[("d1", {"B": 10.0})],
|
||||||
|
1_000.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
rejection = result.daily_executions[0].executions[0]
|
||||||
|
assert rejection.stock_code == "A"
|
||||||
|
assert rejection.executed_value == 0.0
|
||||||
|
assert rejection.partial_fill_pct == 0.0
|
||||||
|
assert rejection.blocked_reason == "missing_price"
|
||||||
|
assert result.positions[0].cash == 1_000.0
|
||||||
|
assert result.positions[0].holdings == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_multi_day_with_audit_requires_matching_dates():
|
||||||
|
"""权重与价格日期错位必须显式失败,不能按位置静默配对。"""
|
||||||
|
with pytest.raises(ValueError, match="dates must match"):
|
||||||
|
simulate_multi_day_with_audit(
|
||||||
|
[("d1", {"A": 1.0})],
|
||||||
|
[("d2", {"A": 10.0})],
|
||||||
|
1_000.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ── v1.2.0 Phase 1:端到端 POC(run_end_to_end_poc) ─────
|
# ── v1.2.0 Phase 1:端到端 POC(run_end_to_end_poc) ─────
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user