Fix bounded S/R training portfolio calendars
This commit is contained in:
@@ -33,7 +33,7 @@ import statistics
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from datetime import date, datetime, timezone
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
@@ -1376,8 +1376,17 @@ def _simulate_portfolio(
|
||||
qualified_fn = _default_qualified
|
||||
|
||||
entries_by_ord: dict[int, list[dict]] = defaultdict(list)
|
||||
start_ord = start_date.toordinal() if start_date is not None else None
|
||||
end_ord = end_date.toordinal() if end_date is not None else None
|
||||
configured_start, configured_end = _backtest_entry_bounds()
|
||||
effective_start = start_date if start_date is not None else configured_start
|
||||
start_ord = effective_start.toordinal() if effective_start is not None else None
|
||||
if end_date is not None:
|
||||
# Explicit simulator/holdout end dates are exclusive split boundaries.
|
||||
end_ord = end_date.toordinal()
|
||||
elif configured_end is not None:
|
||||
# BACKTEST_ENTRY_END is documented and applied as an inclusive bound.
|
||||
end_ord = (configured_end + timedelta(days=1)).toordinal()
|
||||
else:
|
||||
end_ord = None
|
||||
for c in candidates:
|
||||
if not qualified_fn(c) or c.get("direction") != "long":
|
||||
continue
|
||||
|
||||
@@ -429,6 +429,13 @@ Choose one arm and record that lock before running exactly control and that arm:
|
||||
Replace `confirmed_rounds` with the recorded winner. The validation command also
|
||||
calls `scripts/compare_sr_variants.py` to produce the paired cohort CSV and JSON.
|
||||
|
||||
> Validation result: `confirmed_rounds` is rejected and is no longer a lockable
|
||||
> arm. It remains in the corrected training matrix only to preserve the causal
|
||||
> experiment record. The first training reports used an entry end bound without
|
||||
> forwarding it to the portfolio calendar, leaving each book in flat cash through
|
||||
> the test period. The simulator now treats `BACKTEST_ENTRY_END` as an inclusive
|
||||
> entry bound and truncates the calendar after the final position can resolve.
|
||||
|
||||
The post-2024 interval has informed earlier research, so this is validation rather
|
||||
than a pristine holdout; do not sweep variants on it. No deployment follows
|
||||
automatically. A lower validation Sharpe or higher drawdown remains a no-ship
|
||||
|
||||
@@ -23,7 +23,11 @@ TRAINING_ARMS = (
|
||||
"confirmed_rounds",
|
||||
"gate_v2",
|
||||
)
|
||||
LOCKABLE_ARMS = TRAINING_ARMS[1:]
|
||||
# confirmed_rounds failed the post-2024 validation decisively and must not be
|
||||
# selected again merely because a corrected training curve looks attractive.
|
||||
LOCKABLE_ARMS = tuple(
|
||||
arm for arm in TRAINING_ARMS[1:] if arm != "confirmed_rounds"
|
||||
)
|
||||
|
||||
|
||||
def _add_common(parser: argparse.ArgumentParser) -> None:
|
||||
|
||||
@@ -604,6 +604,34 @@ class TestSimulatePortfolio:
|
||||
def test_nothing_qualified_returns_none(self):
|
||||
assert bt._simulate_portfolio([], {}, None, "hold", 30) is None
|
||||
|
||||
def test_configured_entry_end_truncates_flat_calendar_tail(self, monkeypatch):
|
||||
closes = [100.0 + i for i in range(100)]
|
||||
prices = {"AAA": _sim_prices(self.ORD, closes)}
|
||||
cand = _sim_cand("AAA", self.ORD, entry=100.0, stop=95.0, target=130.0)
|
||||
monkeypatch.setenv(
|
||||
"BACKTEST_ENTRY_END", date.fromordinal(self.ORD).isoformat()
|
||||
)
|
||||
|
||||
sim = bt._simulate_portfolio([cand], prices, None, "hold", 3)
|
||||
|
||||
assert sim is not None
|
||||
assert sim["end_date"] == date.fromordinal(self.ORD + 3).isoformat()
|
||||
|
||||
def test_configured_entry_start_aligns_book_calendar(self, monkeypatch):
|
||||
closes = [100.0 + i for i in range(10)]
|
||||
prices = {"AAA": _sim_prices(self.ORD, closes)}
|
||||
cand = _sim_cand(
|
||||
"AAA", self.ORD + 1, entry=101.0, stop=96.0, target=130.0
|
||||
)
|
||||
monkeypatch.setenv(
|
||||
"BACKTEST_ENTRY_START", date.fromordinal(self.ORD).isoformat()
|
||||
)
|
||||
|
||||
sim = bt._simulate_portfolio([cand], prices, None, "hold", 3)
|
||||
|
||||
assert sim is not None
|
||||
assert sim["start_date"] == date.fromordinal(self.ORD).isoformat()
|
||||
|
||||
|
||||
def test_bucket_stats_counts_and_expectancy():
|
||||
cands = [
|
||||
|
||||
Reference in New Issue
Block a user