docs: distinguish signal execution and holding times #2
@@ -11,6 +11,7 @@ import pytest
|
||||
from quant_engine.execution import (
|
||||
ExecutionConfig,
|
||||
ExecutionResult,
|
||||
ExecutionSimulationResult,
|
||||
apply_bid_ask_spread,
|
||||
apply_volume_constraint,
|
||||
check_price_limit,
|
||||
@@ -20,6 +21,7 @@ from quant_engine.execution import (
|
||||
run_end_to_end_poc,
|
||||
simulate_execution,
|
||||
simulate_multi_day,
|
||||
simulate_multi_day_with_audit,
|
||||
simulate_with_daily_data,
|
||||
total_costs,
|
||||
total_turnover,
|
||||
@@ -357,6 +359,73 @@ def test_simulate_multi_day_holdings_evolution():
|
||||
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) ─────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user