From efdbcd0899b0c45fc2248d94c3376f7eea0ec97e Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Wed, 4 Feb 2026 10:06:38 -0700 Subject: [PATCH] Fix critical bugs: RSI division by zero, API price validation, order timeout, and crossover race condition --- alpaca_trader/api.py | 6 +++++- alpaca_trader/engine.py | 29 ++++++++++++++++------------- alpaca_trader/indicators.py | 3 ++- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/alpaca_trader/api.py b/alpaca_trader/api.py index d1be18f..9d9b21a 100644 --- a/alpaca_trader/api.py +++ b/alpaca_trader/api.py @@ -64,7 +64,7 @@ class AlpacaClient: price_source = limit_price else: price_source = bid_price if side == "buy" else ask_price - if price_source is None: + if price_source is None or price_source <= 0: return None shares = int(notional / price_source) if shares == 0: @@ -83,7 +83,11 @@ class AlpacaClient: return None order = self.submit_order(symbol=symbol, qty=shares, side=side, type="market", time_in_force="day") status = self.get_order(order.id) + timeout = 30 + start_time = time.time() while status.status not in {"filled", "cancelled", "expired", "rejected"}: + if time.time() - start_time > timeout: + return None time.sleep(0.5) status = self.get_order(order.id) if status.status == "filled": diff --git a/alpaca_trader/engine.py b/alpaca_trader/engine.py index cf02bfb..33c9ed6 100644 --- a/alpaca_trader/engine.py +++ b/alpaca_trader/engine.py @@ -206,6 +206,17 @@ api = AlpacaClient( api_version='v2' ) +class SignalState: + def __init__(self): + self.last_bullish_crossover_bar = -999 + self.last_bearish_crossover_bar = -999 + + def reset(self): + self.last_bullish_crossover_bar = -999 + self.last_bearish_crossover_bar = -999 + +signal_state = SignalState() + def debug_print(message): if DEBUG_MODE: debug_logger.debug(f"🔎 {message}") @@ -426,28 +437,23 @@ def advanced_signal_generator(symbol): bearish_crossover = False if REQUIRE_MA_CROSSOVER and len(bars) >= LONG_WINDOW + CROSSOVER_LOOKBACK: - if not hasattr(advanced_signal_generator, 'last_bullish_crossover_bar'): - advanced_signal_generator.last_bullish_crossover_bar = -999 - if not hasattr(advanced_signal_generator, 'last_bearish_crossover_bar'): - advanced_signal_generator.last_bearish_crossover_bar = -999 - current_bar_index = len(bars) - 1 for i in range(1, CROSSOVER_LOOKBACK + 1): bar_index = current_bar_index - i if short_ma_series.iloc[-i-1] <= long_ma_series.iloc[-i-1] and short_ma_series.iloc[-i] > long_ma_series.iloc[-i]: - if bar_index > advanced_signal_generator.last_bullish_crossover_bar: + if bar_index > signal_state.last_bullish_crossover_bar: bullish_crossover = True - advanced_signal_generator.last_bullish_crossover_bar = bar_index + signal_state.last_bullish_crossover_bar = bar_index debug_print(f"Bullish crossover detected {i} bars ago") break for i in range(1, CROSSOVER_LOOKBACK + 1): bar_index = current_bar_index - i if short_ma_series.iloc[-i-1] >= long_ma_series.iloc[-i-1] and short_ma_series.iloc[-i] < long_ma_series.iloc[-i]: - if bar_index > advanced_signal_generator.last_bearish_crossover_bar: + if bar_index > signal_state.last_bearish_crossover_bar: bearish_crossover = True - advanced_signal_generator.last_bearish_crossover_bar = bar_index + signal_state.last_bearish_crossover_bar = bar_index debug_print(f"Bearish crossover detected {i} bars ago") break @@ -647,10 +653,7 @@ def main(): trades_today = 0 total_pnl = 0 - if hasattr(advanced_signal_generator, 'last_bullish_crossover_bar'): - delattr(advanced_signal_generator, 'last_bullish_crossover_bar') - if hasattr(advanced_signal_generator, 'last_bearish_crossover_bar'): - delattr(advanced_signal_generator, 'last_bearish_crossover_bar') + signal_state.reset() try: existing_position = api.get_position(SYMBOL) diff --git a/alpaca_trader/indicators.py b/alpaca_trader/indicators.py index e341318..241b35f 100644 --- a/alpaca_trader/indicators.py +++ b/alpaca_trader/indicators.py @@ -10,7 +10,8 @@ def rsi(data, window=14): delta = data.diff() gain = (delta.where(delta > 0, 0)).rolling(window=window).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean() - loss = loss.replace(0, 0.0001) + loss = loss.replace(0, 1e-10) + loss = loss.clip(lower=1e-10) rs = gain / loss rsi_val = 100 - (100 / (1 + rs)) return rsi_val