Skip to content

Commit 6f1c9a6

Browse files
authored
Merge pull request #972 from Lumiwealth/version/4.4.53
v4.4.53 - Routed IBKR daily cadence + options MTM/delta fixes
2 parents cf58e8c + 79c8374 commit 6f1c9a6

21 files changed

+883
-97
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Changelog
22

3+
## 4.4.53 - 2026-03-06
4+
5+
### Added
6+
- Regression tests for daily-cadence datasource seeding in `StrategyExecutor`, routed `1D` timestep normalization, put-delta normalization/model-path strike selection, and IBKR equity corporate-action cache reuse.
7+
- Regression tests for IBKR paged-history retention when later pages are empty, plus option valuation fallback coverage for off-session stale mark scenarios.
8+
9+
### Changed
10+
- Daily-cadence backtests now seed datasource cadence to `day` during strategy initialization to avoid first-lookup minute prefetch blowups.
11+
- `Strategy.get_last_price()` now consistently prefers daily bars for stock/index assets in daily backtest cadence, including routed IBKR stock/index paths.
12+
- Routed backtesting now treats day-like timestep aliases (`1D`, `1day`, etc.) as daily cadence for non-Theta last-price/quote reads.
13+
- ThetaData daily option fetches now prefetch forward in bounded chunks (capped by expiration/end) to reduce repeated downloader round-trips during long runs.
14+
- Option helper strike selection now normalizes absolute delta inputs by option side and uses a fast model-based strike pick for Theta daily option backtests.
15+
- IBKR equity corporate-action enrichment now uses Yahoo history with coverage hints (`last_needed_datetime`) and date-bucket cache keys for stable reuse.
16+
- Backtest artifact export now always writes CSV/parquet outputs for trades/stats/indicators/trade-events regardless of `show_plot` mode.
17+
18+
### Fixed
19+
- Guarded option MTM valuation against off-session stale marks that could cause transient portfolio-value drops in backtests.
20+
- Fixed IBKR history pagination to preserve already-fetched chunks when a subsequent page returns empty.
21+
- Refreshed acceptance baseline metrics for `aapl_deep_dip_calls` and `leaps_alpha_picks_short` to match current deterministic CI outputs.
22+
- Updated `test_classic_60_40` drift-rebalancer expectations to the corrected daily-cadence fill quantities.
23+
324
## 4.4.52 - 2026-03-03
425

526
### Added

lumibot/backtesting/routed_backtesting.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@ def _align_timestamp_to_index_tz(ts_value: datetime, ref_index_ts: pd.Timestamp)
102102
return ts
103103

104104

105+
def _is_day_like_timestep(value: str) -> bool:
106+
"""Return True when a timestep string represents daily cadence.
107+
108+
Accepts aliases like `day`, `1d`, `1day`, and normalized quantities returned by
109+
`parse_timestep_qty_and_unit`.
110+
"""
111+
raw = str(value or "").strip().lower()
112+
if not raw:
113+
return False
114+
try:
115+
_qty, unit = parse_timestep_qty_and_unit(raw)
116+
return str(unit).strip().lower() == "day"
117+
except Exception:
118+
return raw in {"day", "1d", "1day", "d"}
119+
120+
105121
class _RoutingAdapter:
106122
provider_key: str
107123

@@ -976,7 +992,7 @@ def get_last_price(self, asset, timestep="minute", quote=None, exchange=None, **
976992

977993
if spec.provider != "thetadata" and timestep == "minute":
978994
source_timestep = str(getattr(self, "_timestep", "") or "").strip().lower()
979-
if source_timestep == "day":
995+
if _is_day_like_timestep(source_timestep):
980996
timestep = "day"
981997
elif not bool(getattr(self, "_observed_intraday_cadence", False)) and bool(
982998
getattr(self, "_effective_day_mode", False)
@@ -1005,7 +1021,7 @@ def get_quote(self, asset, quote=None, exchange=None, timestep="minute", **kwarg
10051021

10061022
if spec.provider != "thetadata" and timestep == "minute":
10071023
source_timestep = str(getattr(self, "_timestep", "") or "").strip().lower()
1008-
if source_timestep == "day":
1024+
if _is_day_like_timestep(source_timestep):
10091025
timestep = "day"
10101026
elif not bool(getattr(self, "_observed_intraday_cadence", False)) and bool(
10111027
getattr(self, "_effective_day_mode", False)

lumibot/backtesting/thetadata_backtesting_pandas.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,18 @@ def _update_pandas_data(
10551055
expiration_dt = self._option_expiration_end(asset_separated)
10561056
if expiration_dt is not None and end_requirement is not None and expiration_dt < end_requirement:
10571057
end_requirement = expiration_dt
1058+
end_for_fetch = end_requirement
1059+
# Perf: daily option mark-to-market paths can otherwise extend cache one trading day at a time,
1060+
# causing one downloader call per day per open contract in long runs.
1061+
# Fetch forward in chunks (capped to expiration) so subsequent days hit warm cache.
1062+
if is_option_asset and ts_unit == "day" and end_for_fetch is not None:
1063+
prefetch_horizon = end_for_fetch + timedelta(days=45)
1064+
if expiration_dt is not None:
1065+
end_for_fetch = min(prefetch_horizon, expiration_dt)
1066+
else:
1067+
end_for_fetch = prefetch_horizon
1068+
if self.datetime_end is not None and end_for_fetch is not None and end_for_fetch > self.datetime_end:
1069+
end_for_fetch = self.datetime_end
10581070

10591071
canonical_key, legacy_key = self._build_dataset_keys(asset_separated, quote_asset, ts_unit)
10601072
dataset_key = canonical_key
@@ -1813,7 +1825,7 @@ def _fetch_ohlc():
18131825
frame = thetadata_helper.get_price_data(
18141826
fetch_asset,
18151827
start_for_fetch,
1816-
end_requirement,
1828+
end_for_fetch,
18171829
timespan=ts_unit,
18181830
quote_asset=quote_asset,
18191831
dt=date_time_now,
@@ -1839,7 +1851,7 @@ def _fetch_quote():
18391851
frame = thetadata_helper.get_price_data(
18401852
fetch_asset,
18411853
start_for_fetch,
1842-
end_requirement,
1854+
end_for_fetch,
18431855
timespan=ts_unit,
18441856
quote_asset=quote_asset,
18451857
dt=date_time_now,

0 commit comments

Comments
 (0)