@ -32,6 +32,19 @@ INTERVAL_MAP = {
}
_DAILY_TIMEFRAMES = { " 1d " , " 1Day " , " 1w " , " 1Week " , " 1mo " , " 1Month " }
def _default_feed ( timeframe : str ) - > Optional [ str ] :
""" Return ' iex ' for intraday timeframes (free plan), None for daily+.
Daily / weekly / monthly SIP data is accessible on the free plan .
Intraday SIP data requires a paid subscription — use IEX instead .
"""
alpaca_tf = INTERVAL_MAP . get ( timeframe , timeframe )
return None if alpaca_tf in _DAILY_TIMEFRAMES else " iex "
def normalize_ticker ( symbol : str ) - > str :
""" Normalize ticker symbol for Alpaca API.
@ -152,6 +165,7 @@ class AlpacaClient:
start : Optional [ str ] = None ,
end : Optional [ str ] = None ,
limit : int = 10000 ,
feed : Optional [ str ] = None ,
) - > List [ Dict ] :
"""
Fetch bars for a single symbol with automatic pagination .
@ -162,6 +176,8 @@ class AlpacaClient:
start : RFC - 3339 date / datetime ( e . g . " 2024-01-01 " )
end : RFC - 3339 date / datetime
limit : Max bars per page ( Alpaca max 10000 )
feed : Data feed ( " iex " = free real - time , " sip " = paid consolidated ) .
Defaults to " iex " for intraday , no feed param for daily + .
Returns :
List of bar dicts with keys : t , o , h , l , c , v , n , vw
@ -172,6 +188,10 @@ class AlpacaClient:
params [ " start " ] = start
if end :
params [ " end " ] = end
# Default to iex feed for intraday intervals (free plan compatible)
effective_feed = feed or ( _default_feed ( timeframe ) )
if effective_feed :
params [ " feed " ] = effective_feed
all_bars : List [ Dict ] = [ ]
path = f " /v2/stocks/ { normalize_ticker ( symbol ) . upper ( ) } /bars "
@ -196,6 +216,7 @@ class AlpacaClient:
end : Optional [ str ] = None ,
limit : int = 10000 ,
batch_size : int = 200 ,
feed : Optional [ str ] = None ,
) - > Dict [ str , List [ Dict ] ] :
"""
Fetch bars for multiple symbols with auto - pagination and transparent batching .
@ -222,6 +243,8 @@ class AlpacaClient:
normalized = [ normalize_ticker ( s ) . upper ( ) for s in symbols ]
result : Dict [ str , List [ Dict ] ] = { s : [ ] for s in normalized }
effective_feed = feed or _default_feed ( timeframe )
# Process in batches to stay within URL length limits
for batch_start in range ( 0 , len ( normalized ) , batch_size ) :
batch = normalized [ batch_start : batch_start + batch_size ]
@ -234,6 +257,8 @@ class AlpacaClient:
params [ " start " ] = start
if end :
params [ " end " ] = end
if effective_feed :
params [ " feed " ] = effective_feed
while True :
data = await self . _request ( " GET " , path , params = params )