"""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, )