Files
quant_engine/tests/test_daily_decision_policy.py
2026-10-04 11:10:01 +08:00

59 lines
1.9 KiB
Python

"""Policies observe actual post-fill holdings and only schedule the next open."""
import pytest
from quant_engine.execution import ExecutionConfig, simulate_daily_ledger_with_audit
def test_policy_next_open_actual_holdings_and_immutable_past_snapshots():
seen = []
def decide(position):
seen.append((position.date, dict(position.holdings), position.cash))
position.holdings.clear()
return {"A": 0.5} if position.date == "d1" else None
result = simulate_daily_ledger_with_audit(
[],
[("d1", {"A": 10}), ("d2", {"A": 20}), ("d3", {"A": 30})],
[("d1", {"A": 10}), ("d2", {"A": 25}), ("d3", {"A": 40})],
1000,
ExecutionConfig(commission_bps=0, stamp_tax_bps=0, slippage_bps=0, min_trade_amount=0),
decision_policy=decide,
)
assert seen[0][1] == {}
assert seen[1][1] == {"A": 25}
assert result.nav_series.tolist() == [1000, 1125, 1500]
assert result.positions[1].holdings == {"A": 25}
assert len(result.trades_frame) == 1
def test_rejected_entry_does_not_create_a_position_for_policy():
holdings = []
def decide(position):
holdings.append(dict(position.holdings))
return {"A": 1} if position.date == "d1" else None
result = simulate_daily_ledger_with_audit(
[],
[("d1", {"A": 10}), ("d2", {"A": 10})],
[("d1", {"A": 10}), ("d2", {"A": 10})],
1000,
ExecutionConfig(min_trade_amount=2000),
decision_policy=decide,
)
assert holdings == [{}, {}]
assert result.trades_frame.empty
def test_policy_and_fixed_schedule_cannot_be_mixed():
with pytest.raises(ValueError, match="fixed"):
simulate_daily_ledger_with_audit(
[("d1", {"A": 1})],
[("d1", {"A": 10})],
[("d1", {"A": 10})],
1000,
decision_policy=lambda p: None,
)