From 357bcaee6779c6d8afdb360b97e8ae6f80969e89 Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Thu, 19 Feb 2026 09:05:02 -0700 Subject: [PATCH] fix: retry on requests ConnectionError in AlpacaClient --- alpaca_trader/api.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/alpaca_trader/api.py b/alpaca_trader/api.py index 37916b8..e1fc1f3 100644 --- a/alpaca_trader/api.py +++ b/alpaca_trader/api.py @@ -2,9 +2,12 @@ import time import logging import backoff import alpaca_trade_api as tradeapi +import requests.exceptions logging.getLogger('backoff').setLevel(logging.CRITICAL) +_RETRYABLE_ERRORS = (tradeapi.rest.APIError, ConnectionError, requests.exceptions.ConnectionError) + def _is_position_not_found(e): return isinstance(e, tradeapi.rest.APIError) and "position does not exist" in str(e) @@ -14,50 +17,50 @@ class AlpacaClient: def __init__(self, api_key_id, api_secret_key, base_url, api_version="v2"): self.api = tradeapi.REST(api_key_id, api_secret_key, base_url, api_version=api_version) - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def get_account(self): return self.api.get_account() - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def get_clock(self): return self.api.get_clock() - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def get_bars(self, symbol, timeframe, **kwargs): bars = self.api.get_bars(symbol, timeframe, **kwargs) if bars is None: return None return bars.df - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def get_latest_quote(self, symbol): return self.api.get_latest_quote(symbol) - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def submit_order(self, **kwargs): return self.api.submit_order(**kwargs) - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def get_order(self, order_id): return self.api.get_order(order_id) - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def cancel_order(self, order_id): return self.api.cancel_order(order_id) - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def list_positions(self): return self.api.list_positions() - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def list_orders(self, **kwargs): return self.api.list_orders(**kwargs) - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter) def close_all_positions(self): return self.api.close_all_positions() - @backoff.on_exception(backoff.expo, (tradeapi.rest.APIError, ConnectionError), max_tries=5, jitter=backoff.full_jitter, giveup=_is_position_not_found) + @backoff.on_exception(backoff.expo, _RETRYABLE_ERRORS, max_tries=5, jitter=backoff.full_jitter, giveup=_is_position_not_found) def get_position(self, symbol): return self.api.get_position(symbol)