11from __future__ import annotations
22
33from datetime import date , datetime , time
4+ from math import ceil
45from math import isnan
6+ import re
57from typing import Any , Callable
68
79from quant_platform_kit .common .models import PricePoint , PriceSeries , QuoteSnapshot
@@ -32,6 +34,49 @@ def _build_stock_contract(
3234 return stock_factory (symbol , exchange , currency )
3335
3436
37+ def _normalize_duration_for_ibkr (duration : str ) -> str :
38+ text = str (duration or "" ).strip ()
39+ match = re .fullmatch (r"(\d+)\s*([A-Za-z]+)" , text )
40+ if not match :
41+ return text
42+
43+ quantity = int (match .group (1 ))
44+ unit = match .group (2 ).upper ()
45+ if unit == "D" and quantity > 365 :
46+ return f"{ ceil (quantity / 365 )} Y"
47+ return f"{ quantity } { unit } "
48+
49+
50+ def _request_historical_bars (
51+ ib : Any ,
52+ contract : Any ,
53+ * ,
54+ duration : str ,
55+ bar_size : str ,
56+ ) -> Any :
57+ normalized_duration = _normalize_duration_for_ibkr (duration )
58+ last_error : Exception | None = None
59+ for what_to_show in ("ADJUSTED_LAST" , "TRADES" ):
60+ try :
61+ bars = ib .reqHistoricalData (
62+ contract ,
63+ endDateTime = "" ,
64+ durationStr = normalized_duration ,
65+ barSizeSetting = bar_size ,
66+ whatToShow = what_to_show ,
67+ useRTH = True ,
68+ formatDate = 1 ,
69+ )
70+ except Exception as exc : # pragma: no cover - exercised by live broker adapters.
71+ last_error = exc
72+ continue
73+ if bars :
74+ return bars
75+ if last_error is not None :
76+ raise last_error
77+ return ()
78+
79+
3580def fetch_historical_price_series (
3681 ib : Any ,
3782 symbol : str ,
@@ -49,14 +94,11 @@ def fetch_historical_price_series(
4994 stock_factory = stock_factory ,
5095 )
5196 ib .qualifyContracts (contract )
52- bars = ib .reqHistoricalData (
97+ bars = _request_historical_bars (
98+ ib ,
5399 contract ,
54- endDateTime = "" ,
55- durationStr = duration ,
56- barSizeSetting = bar_size ,
57- whatToShow = "ADJUSTED_LAST" ,
58- useRTH = True ,
59- formatDate = 1 ,
100+ duration = duration ,
101+ bar_size = bar_size ,
60102 )
61103 points = tuple (
62104 PricePoint (as_of = _coerce_as_of (bar .date ), close = float (bar .close ))
@@ -82,14 +124,11 @@ def fetch_historical_price_candles(
82124 stock_factory = stock_factory ,
83125 )
84126 ib .qualifyContracts (contract )
85- bars = ib .reqHistoricalData (
127+ bars = _request_historical_bars (
128+ ib ,
86129 contract ,
87- endDateTime = "" ,
88- durationStr = duration ,
89- barSizeSetting = bar_size ,
90- whatToShow = "ADJUSTED_LAST" ,
91- useRTH = True ,
92- formatDate = 1 ,
130+ duration = duration ,
131+ bar_size = bar_size ,
93132 )
94133 return [
95134 {
0 commit comments