quantlab.acquisition.alpaca.AlpacaAcquisition#
- class quantlab.acquisition.alpaca.AlpacaAcquisition(config)[source]#
Bases:
SymbolBatchAcquisitionDownload Alpaca Market Data through the shared
SymbolBatchAcquisitionbase.Alpaca serves many symbols per request and splits large answers into pages, so
_fetch_pagesends one request and returns(rows, next_page_token). The base class loops over the pages, writes the shards and keeps the page ledger.Data types. One value, returned by
_data_type, selects the endpoint, the field names and the shard columns.frequency="1d"and"1m"fetch bars.frequency="tick"fetchesquotesortradesand requireskwargs["data_type"]to say which, with no default. Tick rows are written exactly as received: nothing between the request and the shard resamples, groups or deduplicates them.Subscription tiers. Alpaca’s free (Basic) plan and its paid Algo Trader Plus plan both serve history since 2016 with the same fields. The free plan withholds the latest 15 minutes, which a historical backfill never asks for. The difference that matters is the historical request rate: 200 requests per minute on the free plan against 10,000 on the paid one, a factor of 50. At 200 per minute a full-market daily backfill takes about 8 minutes and a full-market minute backfill about 50 hours and 358 GB. The practical question is therefore how much data a run asks for, and the volume check in
quantlab.universemakes the user answer it before a run starts.Whether the free plan may use SIP data is unresolved. SIP (Securities Information Processor) is the consolidated feed covering every US exchange; IEX is a single exchange’s feed. Alpaca’s own documentation does not settle whether the free plan can request historical SIP data, and this class takes no position. One reading is yes, for data older than 15 minutes: the FAQ says
endmust be at least 15 minutes old to query SIP data without a subscription, and the plan table reads the same way. The other reading is IEX only: the data-sources table says IEX is the only feed available without a subscription. Three vendor pages support the first reading and one the second; that is a vote count, not evidence. The difference is material, because IEX carries roughly 2.5% of consolidated volume, and tick data limited to IEX would be unusable for research. Sofeedis aconfig.kwargsoption with no in-code default: when it is unset the parameter is left out of the request and the vendor picks the best feed the account allows. Only a request made with a real key can settle the question.Corporate actions. This class has no corporate-actions method, endpoint or constant. If one is ever added it must not replace the delisting information in Tiingo’s
supported_tickers.csv: Alpaca’s Corporate Actions API leaves out delistings and reorganisations, and relying on it would bring back survivorship bias.Credentials. Only the market-data endpoints are used; there is no trading or broker API and no paper/live switch, because the market-data API does not distinguish the two.
APCA_API_KEY_IDandAPCA_API_SECRET_KEYare read from the environment inside_AlpacaMarketDataClientand are never put on a config, becauseAcquisitionConfigis saved as JSON beside model checkpoints and a key stored there would end up on disk. Their values are removed from every captured message.- Parameters:
- configAcquisitionConfig
The acquisition config. Alpaca-specific options go in
config.kwargs:data_type(required forfrequency="tick"),feed,adjustment(default"raw"),asofandpage_limit(default 10,000), plus the base class’s tuning parameters.
- Raises:
- ValueError
If
data_type,feedoradjustmentis not an accepted value.- RuntimeError
If the credentials are missing from the environment.
Examples
Needs
APCA_API_KEY_IDandAPCA_API_SECRET_KEYexported:from quantlab.acquisition.config import AcquisitionConfig cfg = AcquisitionConfig( market="us_equity", frequency="1d", vendor="alpaca", raw_data_dir_path="downloads/nasdaq_data/alpaca", watermark_path="downloads/nasdaq_data/_watermarks/alpaca", symbols=("AAPL",), start_date="2024-01-02", end_date="2024-01-03", ) acq = AlpacaAcquisition(cfg).download() print(acq.coverage_report())
Trades at full resolution use
frequency="tick"together withkwargs={"data_type": "trades"}.- ASOF_NO_MAPPING = '-'#
Copy of the module-level constant; see its comment.
- CREDENTIAL_ENV_VARS = ('APCA_API_KEY_ID', 'APCA_API_SECRET_KEY')#
The two credentials the request policy hides (as
<NAME REDACTED>) before a message reaches a log line or the failure manifest. Built from the module-level constants, not from the client class that tests replace. Alpaca sends credentials in headers rather than in the URL, but arequestsexception can still exposeexc.request.headers.
- DEFAULT_BATCH_SIZE = 100#
Symbols per request. Alpaca does not document a maximum, so this is a cautious practical value rather than a verified limit; override it through
config.kwargs["batch_size"].
- ENDPOINT_MAP = {'bars': '/stocks/bars', 'quotes': '/stocks/quotes', 'trades': '/stocks/trades'}#
Data type to endpoint path. The response keys its rows by the same name, so one value selects both the request and the response field.
- FEED_VALUES = frozenset({'boats', 'delayed_sip', 'iex', 'otc', 'overnight', 'sip'})#
Accepted values for the request options a caller may set. An unknown value raises instead of being sent, because the vendor rejects some and silently ignores others, and an ignored value means a run that asks for split-adjusted bars and stores raw ones. The lists come from
alpaca-py0.44.0.
- FIELD_MAP = {'c': 'close', 'h': 'high', 'l': 'low', 'n': 'trade_count', 'o': 'open', 't': 'timestamp', 'v': 'volume', 'vw': 'vwap'}#
Alpaca’s single-letter bar fields to this project’s column names. A mistake here is easy to miss: swapping
oandcwould produce plausible-looking data forever.
- FIELD_MAP_BY_DATA_TYPE = {'bars': {'c': 'close', 'h': 'high', 'l': 'low', 'n': 'trade_count', 'o': 'open', 't': 'timestamp', 'v': 'volume', 'vw': 'vwap'}, 'quotes': {'ap': 'ask_price', 'as': 'ask_size', 'ax': 'ask_exchange', 'bp': 'bid_price', 'bs': 'bid_size', 'bx': 'bid_exchange', 'c': 'conditions', 't': 'timestamp', 'z': 'tape'}, 'trades': {'c': 'conditions', 'i': 'trade_id', 'p': 'price', 's': 'size', 't': 'timestamp', 'x': 'exchange', 'z': 'tape'}}#
Data type to its field map.
FIELD_MAPis the bars entry.
- INTRADAY_START_SUFFIX = 'T00:00:00Z'#
Suffixes that turn an intraday window’s dates into full timestamps; see
_window_bounds.Alpaca documents
startandendas RFC 3339 timestamps. A bareYYYY-MM-DDis clear for daily bars but not for minute or tick data, where a bareendmost likely means00:00:00Z. That would leave out the whole final session while the watermark still recorded the date as covered. Nine fractional digits match the nanosecond tick timestamps.
- OPTIONAL_COLUMNS_BY_DATA_TYPE = {'bars': (), 'quotes': ('conditions',), 'trades': ('conditions',)}#
Per data type, the columns the vendor may leave out of every row of a page;
conditionsis optional in the response. Any other missing column means the field map no longer matches the response, and_fetch_pageraises rather than filling it with nulls: an all-nullpricecolumn would look like symbols that never traded.
- QUOTE_FIELD_MAP = {'ap': 'ask_price', 'as': 'ask_size', 'ax': 'ask_exchange', 'bp': 'bid_price', 'bs': 'bid_size', 'bx': 'bid_exchange', 'c': 'conditions', 't': 'timestamp', 'z': 'tape'}#
Alpaca’s quote fields to this project’s column names.
cmeans conditions on a quote and close on a bar, which is why each data type has its own map.
- RATE_LIMIT_HEADERS = ('X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset')#
Headers Alpaca may send with a 429, read for logging only. A missing header is recorded as missing, never as a default.
- property RAW_COLUMNS#
Return the shard columns, in order, for this run’s data type.
A property rather than a class attribute because the columns depend on which endpoint the run reads. The values come from
RAW_COLUMNS_BY_DATA_TYPE; for bars they aretimestamp,symbol,vendor,open,high,low,close,volume,trade_countandvwap.
- RAW_COLUMNS_BY_DATA_TYPE = {'bars': ('timestamp', 'symbol', 'vendor', 'open', 'high', 'low', 'close', 'volume', 'trade_count', 'vwap'), 'quotes': ('timestamp', 'symbol', 'vendor', 'bid_exchange', 'bid_price', 'bid_size', 'ask_exchange', 'ask_price', 'ask_size', 'conditions', 'tape'), 'trades': ('timestamp', 'symbol', 'vendor', 'exchange', 'price', 'size', 'trade_id', 'conditions', 'tape')}#
Shard columns, in order, per data type. Each begins with
("timestamp", "symbol", "vendor")followed by only that endpoint’s own fields, so a directory scan always sees one schema and a trade never carries empty bid/ask columns.
- property RAW_SCHEMA#
Return the column dtypes for this run’s data type.
For bars
timestampispl.Datetime("us"); for quotes and trades it ispl.Datetime("ns").
- RAW_SCHEMA_BY_DATA_TYPE = {'bars': {'close': Float64, 'high': Float64, 'low': Float64, 'open': Float64, 'symbol': String, 'timestamp': Datetime(time_unit='us', time_zone=None), 'trade_count': Float64, 'vendor': String, 'volume': Float64, 'vwap': Float64}, 'quotes': {'ask_exchange': String, 'ask_price': Float64, 'ask_size': Float64, 'bid_exchange': String, 'bid_price': Float64, 'bid_size': Float64, 'conditions': List(String), 'symbol': String, 'tape': String, 'timestamp': Datetime(time_unit='ns', time_zone=None), 'vendor': String}, 'trades': {'conditions': List(String), 'exchange': String, 'price': Float64, 'size': Float64, 'symbol': String, 'tape': String, 'timestamp': Datetime(time_unit='ns', time_zone=None), 'trade_id': Int64, 'vendor': String}}#
Explicit column dtypes per data type. They build an empty page (so a response with no rows still has a readable
symbolcolumn), type columns a sparse response leaves out, and cast a full page so every shard has the same schema.The
timestampunit matters. Bars use microseconds; quotes and trades use nanoseconds because that is what Alpaca sends. Parsing them at microseconds would lose their ordering below one microsecond and create false(timestamp, symbol)ties in tick data, which is never deduplicated.
- SESSION_TIME_ZONE = 'America/New_York'#
The time zone whose calendar date names the intraday
date=directories.Timestamps are stored as UTC without a time zone attached, like every other timestamp in the project; only the directory key is converted. The US regular session runs 14:30 to 21:00 UTC and extended hours cross UTC midnight, so a UTC date would file the last hours of each session under the next day and make a one-day query wrong at both edges.
- SORT = 'asc'#
Always
asc, never read from an option; see_fetch_page.
- TICK_DATA_TYPES = ('quotes', 'trades')#
The values
kwargs["data_type"]accepts underfrequency="tick". There is no default: quotes and trades share one vendor directory and differ only by their data-type directory beneath it, so a guess would file one as the other with the wrong columns.
- TIMEFRAME_MAP = {'1d': '1Day', '1m': '1Min'}#
Maps the project’s frequency value to Alpaca’s
timeframeparameter, using the vendor’s documented spelling. Adding a bar size is a one-line change here.
- TRADE_FIELD_MAP = {'c': 'conditions', 'i': 'trade_id', 'p': 'price', 's': 'size', 't': 'timestamp', 'x': 'exchange', 'z': 'tape'}#
Alpaca’s trade fields to this project’s column names.
- VENDOR = 'alpaca'#
Which vendor this subclass fetches from. Set by every subclass. It is written into every shard as a literal
vendorcolumn and hashed into everyPageLedger.batch_key, so changing it changes the files.