diff --git a/daytrader.py b/daytrader.py index a910ae0..bda6662 100644 --- a/daytrader.py +++ b/daytrader.py @@ -16,17 +16,12 @@ from pathlib import Path from dotenv import load_dotenv import alpaca_trade_api as tradeapi -# ----------------------------------------------------------------------------- -# Configuration -# ----------------------------------------------------------------------------- - -# Path configuration SCRIPT_DIR = Path(__file__).parent CONFIG_PATH = SCRIPT_DIR / "daytrader.json" ENV_PATH = SCRIPT_DIR / ".env" -# Advanced configuration DEFAULT_CONFIG = { + "DEBUG_MODE": True, "SYMBOL": "SPY", "RISK_PER_TRADE": 0.005, "SHORT_WINDOW": 20, @@ -69,11 +64,9 @@ DEFAULT_CONFIG = { "PULLBACK_PERCENTAGE": 0.382 } -# Load environment variables if ENV_PATH.exists(): load_dotenv(ENV_PATH) else: - # Create placeholder .env file with open(ENV_PATH, "w") as f: f.write('APCA_API_KEY_ID="YOUR_API_KEY_HERE"\n') f.write('APCA_API_SECRET_KEY="YOUR_SECRET_KEY_HERE"\n') @@ -82,18 +75,16 @@ else: print(" Please add your Alpaca API keys to .env file") sys.exit(1) -# Load configuration if CONFIG_PATH.exists(): with open(CONFIG_PATH, "r") as f: config = json.load(f) else: - # Create default config with open(CONFIG_PATH, "w") as f: json.dump(DEFAULT_CONFIG, f, indent=4) config = DEFAULT_CONFIG.copy() print(f"✅ Created default config file at {CONFIG_PATH}") -# Extract configuration values +DEBUG_MODE = bool(config.get("DEBUG_MODE", False)) SYMBOL = config["SYMBOL"] RISK_PER_TRADE = float(config["RISK_PER_TRADE"]) SHORT_WINDOW = int(config["SHORT_WINDOW"]) @@ -135,7 +126,6 @@ REQUIRE_MACD_CONFIRMATION = bool(config["REQUIRE_MACD_CONFIRMATION"]) MIN_RISK_REWARD = float(config["MIN_RISK_REWARD"]) PULLBACK_PERCENTAGE = float(config["PULLBACK_PERCENTAGE"]) -# Initialize Alpaca API api = tradeapi.REST( os.getenv('APCA_API_KEY_ID'), os.getenv('APCA_API_SECRET_KEY'), @@ -143,20 +133,34 @@ api = tradeapi.REST( api_version='v2' ) -# ----------------------------------------------------------------------------- -# Technical Analysis Functions -# ----------------------------------------------------------------------------- +LOG_PATH = SCRIPT_DIR / "daytrader.log" + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(LOG_PATH, mode='a'), + logging.StreamHandler(sys.stdout) + ] +) + +logger = logging.getLogger(__name__) + +def debug_print(message): + if DEBUG_MODE: + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S,%f')[:-3] + print(f"{timestamp} - DEBUG - 🔎 {message}", flush=True) def calculate_sma(data, window): - # Simple Moving Average + debug_print(f"Calculating SMA with window={window}") return data.rolling(window=window).mean() def calculate_ema(data, window): - # Exponential Moving Average + debug_print(f"Calculating EMA with window={window}") return data.ewm(span=window, adjust=False).mean() def calculate_rsi(data, window=14): - # Relative Strength Index + debug_print(f"Calculating RSI with window={window}") delta = data.diff() gain = (delta.where(delta > 0, 0)).rolling(window=window).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean() @@ -165,7 +169,7 @@ def calculate_rsi(data, window=14): return rsi def calculate_atr(high, low, close, window=14): - # Average True Range + debug_print(f"Calculating ATR with window={window}") high_low = high - low high_close_prev = abs(high - close.shift()) low_close_prev = abs(low - close.shift()) @@ -174,7 +178,7 @@ def calculate_atr(high, low, close, window=14): return atr def calculate_adx(high, low, close, window=14): - # Average Directional Index for trend strength + debug_print(f"Calculating ADX with window={window}") tr1 = high - low tr2 = abs(high - close.shift()) tr3 = abs(low - close.shift()) @@ -199,7 +203,7 @@ def calculate_adx(high, low, close, window=14): return adx, plus_di, minus_di def calculate_macd(close, fast=12, slow=26, signal=9): - # MACD indicator + debug_print(f"Calculating MACD (fast={fast}, slow={slow}, signal={signal})") ema_fast = calculate_ema(close, fast) ema_slow = calculate_ema(close, slow) macd_line = ema_fast - ema_slow @@ -209,7 +213,7 @@ def calculate_macd(close, fast=12, slow=26, signal=9): return macd_line, signal_line, histogram def calculate_bollinger_bands(close, window=20, num_std=2): - # Bollinger Bands for mean reversion + debug_print(f"Calculating Bollinger Bands (window={window}, std={num_std})") if USE_EMA: middle = calculate_ema(close, window) else: @@ -222,18 +226,23 @@ def calculate_bollinger_bands(close, window=20, num_std=2): return upper, middle, lower def check_volume_confirmation(bars): - # Check if current volume exceeds threshold + debug_print("Checking volume confirmation...") if 'volume' not in bars.columns or len(bars) < 20: + debug_print("Volume check: insufficient data, returning True") return True avg_volume = bars['volume'].rolling(window=20).mean().iloc[-1] current_volume = bars['volume'].iloc[-1] + ratio = current_volume / avg_volume if avg_volume > 0 else 0 - return current_volume >= (avg_volume * VOLUME_MULTIPLIER) + result = current_volume >= (avg_volume * VOLUME_MULTIPLIER) + debug_print(f"Volume: current={current_volume:.0f}, avg={avg_volume:.0f}, ratio={ratio:.2f}, pass={result}") + return result def detect_market_regime(bars): - # Detect market regime: trending, ranging, high_vol, low_vol + debug_print("Detecting market regime...") if len(bars) < 50: + debug_print("Regime: insufficient data, returning 'unknown'") return 'unknown' closes = bars['close'] @@ -247,23 +256,32 @@ def detect_market_regime(bars): current_atr = atr.iloc[-1] atr_percentile = (atr <= current_atr).sum() / len(atr) * 100 + debug_print(f"Regime indicators: ADX={current_adx:.2f}, ATR_percentile={atr_percentile:.1f}%") + if atr_percentile > 70: + debug_print("Regime: HIGH_VOL") return 'high_vol' elif atr_percentile < 30: + debug_print("Regime: LOW_VOL") return 'low_vol' elif current_adx > ADX_THRESHOLD: + debug_print("Regime: TREND") return 'trend' else: + debug_print("Regime: RANGE") return 'range' def check_multiframe_confluence(symbol): - # Check hourly timeframe for trend alignment + debug_print(f"Checking multiframe confluence for {symbol}...") if not MULTIFRAME_FILTER: + debug_print("Multiframe filter disabled, returning 'neutral'") return 'neutral' try: + debug_print("Fetching hourly bars...") hourly_bars = api.get_bars(symbol, "1Hour", limit=50).df if len(hourly_bars) < 50: + debug_print(f"Insufficient hourly data: {len(hourly_bars)} bars") return 'neutral' closes = hourly_bars['close'] @@ -279,26 +297,32 @@ def check_multiframe_confluence(symbol): current_long = ema_long.iloc[-1] current_price = closes.iloc[-1] + debug_print(f"Hourly: price={current_price:.2f}, short_MA={current_short:.2f}, long_MA={current_long:.2f}") + if current_short > current_long and current_price > current_short: + debug_print("Multiframe: BULLISH") return 'bullish' elif current_short < current_long and current_price < current_short: + debug_print("Multiframe: BEARISH") return 'bearish' else: + debug_print("Multiframe: NEUTRAL") return 'neutral' except Exception as e: logger.warning(f"âš ī¸ Could not check multiframe confluence: {e}") + debug_print(f"Multiframe check failed: {e}") return 'neutral' def check_candle_pattern(bars): - # Check for bullish/bearish engulfing patterns + debug_print("Checking candle patterns...") if len(bars) < 2: + debug_print("Candle pattern: insufficient data") return False, False last = bars.iloc[-1] prev = bars.iloc[-2] - # Bullish engulfing bullish_engulfing = ( last['close'] > last['open'] and prev['close'] < prev['open'] and @@ -306,7 +330,6 @@ def check_candle_pattern(bars): last['open'] < prev['close'] ) - # Bearish engulfing bearish_engulfing = ( last['close'] < last['open'] and prev['close'] > prev['open'] and @@ -314,16 +337,20 @@ def check_candle_pattern(bars): last['open'] > prev['close'] ) + debug_print(f"Candle pattern: bullish_engulfing={bullish_engulfing}, bearish_engulfing={bearish_engulfing}") return bullish_engulfing, bearish_engulfing def calculate_pivot_points(symbol): - # Calculate yesterday's pivot points for support/resistance + debug_print(f"Calculating pivot points for {symbol}...") if not USE_PIVOT_POINTS: + debug_print("Pivot points disabled") return None, None, None, None, None try: + debug_print("Fetching yesterday's daily bars...") yesterday_bars = api.get_bars(symbol, "1Day", limit=2).df if len(yesterday_bars) < 2: + debug_print(f"Insufficient daily data: {len(yesterday_bars)} bars") return None, None, None, None, None h = yesterday_bars['high'].iloc[-2] @@ -336,15 +363,18 @@ def calculate_pivot_points(symbol): s1 = 2 * pivot - h s2 = pivot - (h - l) + debug_print(f"Pivots: S2={s2:.2f}, S1={s1:.2f}, P={pivot:.2f}, R1={r1:.2f}, R2={r2:.2f}") return pivot, r1, r2, s1, s2 except Exception as e: logger.warning(f"âš ī¸ Could not calculate pivot points: {e}") + debug_print(f"Pivot calculation failed: {e}") return None, None, None, None, None def calculate_fibonacci_levels(bars, lookback=20): - # Calculate Fibonacci retracement levels + debug_print(f"Calculating Fibonacci levels (lookback={lookback})...") if not USE_FIBONACCI or len(bars) < lookback: + debug_print("Fibonacci disabled or insufficient data") return None, None, None, None, None recent_bars = bars.tail(lookback) @@ -357,57 +387,75 @@ def calculate_fibonacci_levels(bars, lookback=20): fib_500 = swing_high - (diff * 0.500) fib_618 = swing_high - (diff * 0.618) + debug_print(f"Fibonacci: high={swing_high:.2f}, low={swing_low:.2f}, 38.2%={fib_382:.2f}, 50%={fib_500:.2f}, 61.8%={fib_618:.2f}") return fib_382, fib_500, fib_618, swing_high, swing_low def get_vix_level(): - # Get current VIX (fear index) level + debug_print("Getting VIX level...") if not USE_VIX_FILTER: + debug_print("VIX filter disabled, returning 0") return 0 try: + debug_print("Fetching VIX data...") vix_bars = api.get_bars("VIX", "1Day", limit=5).df if len(vix_bars) > 0: - return vix_bars['close'].iloc[-1] + vix = vix_bars['close'].iloc[-1] + debug_print(f"VIX from data: {vix:.2f}") + return vix else: - # Estimate from S&P 500 volatility + debug_print("No VIX data, estimating from SPY volatility...") spy_bars = api.get_bars("SPY", "1Day", limit=20).df if len(spy_bars) >= 20: spy_returns = spy_bars['close'].pct_change() volatility = spy_returns.std() * np.sqrt(252) * 100 + debug_print(f"VIX estimated: {volatility:.2f}") return volatility + debug_print("Returning default VIX: 15") return 15 except Exception as e: logger.warning(f"âš ī¸ Could not get VIX level: {e}") + debug_print(f"VIX fetch failed: {e}, returning 15") return 15 def check_200_sma_filter(symbol): - # Check 200-day SMA for major trend direction + debug_print(f"Checking 200 SMA filter for {symbol}...") if not USE_200_SMA_FILTER: + debug_print("200 SMA filter disabled") return 'neutral' try: + debug_print("Fetching 210 days of daily bars...") daily_bars = api.get_bars(symbol, "1Day", limit=210).df if len(daily_bars) < 200: + debug_print(f"Insufficient data for 200 SMA: {len(daily_bars)} bars") return 'neutral' closes = daily_bars['close'] sma_200 = calculate_sma(closes, 200).iloc[-1] current_price = closes.iloc[-1] + debug_print(f"200 SMA: price={current_price:.2f}, SMA={sma_200:.2f}, ratio={current_price/sma_200:.4f}") + if current_price > sma_200 * 1.01: + debug_print("200 SMA: BULLISH") return 'bullish' elif current_price < sma_200 * 0.99: + debug_print("200 SMA: BEARISH") return 'bearish' else: + debug_print("200 SMA: NEUTRAL") return 'neutral' except Exception as e: logger.warning(f"âš ī¸ Could not check 200 SMA: {e}") + debug_print(f"200 SMA check failed: {e}") return 'neutral' def check_macd_confirmation(bars): - # Check MACD for trend confirmation + debug_print("Checking MACD confirmation...") if not REQUIRE_MACD_CONFIRMATION or len(bars) < 35: + debug_print("MACD confirmation disabled or insufficient data") return 'neutral' closes = bars['close'] @@ -418,55 +466,40 @@ def check_macd_confirmation(bars): prev_macd = macd_line.iloc[-2] prev_signal = signal_line.iloc[-2] - # Bullish: MACD crosses above signal + debug_print(f"MACD: current={current_macd:.4f}, signal={current_signal:.4f}, prev_macd={prev_macd:.4f}, prev_signal={prev_signal:.4f}") + if prev_macd <= prev_signal and current_macd > current_signal: + debug_print("MACD: BULLISH crossover") return 'bullish' - # Bearish: MACD crosses below signal elif prev_macd >= prev_signal and current_macd < current_signal: + debug_print("MACD: BEARISH crossover") return 'bearish' - # Continuation elif current_macd > current_signal: + debug_print("MACD: BULLISH continuation") return 'bullish' elif current_macd < current_signal: + debug_print("MACD: BEARISH continuation") return 'bearish' + debug_print("MACD: NEUTRAL") return 'neutral' def should_skip_trading_day(): - # Check if today should be skipped (Monday/Friday) + debug_print("Checking if should skip trading day...") if not SKIP_MONDAYS_FRIDAYS: + debug_print("Skip Monday/Friday disabled") return False today = datetime.now().weekday() - # 0 = Monday, 4 = Friday + day_name = datetime.now().strftime("%A") if today == 0 or today == 4: + debug_print(f"Skipping {day_name} (skip_mondays_fridays enabled)") return True + debug_print(f"Not skipping {day_name}") return False -# ----------------------------------------------------------------------------- -# Logging Configuration -# ----------------------------------------------------------------------------- - -LOG_PATH = SCRIPT_DIR / "daytrader.log" - -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler(LOG_PATH, mode='a'), - logging.StreamHandler(sys.stdout) - ] -) - -logger = logging.getLogger(__name__) - -# ----------------------------------------------------------------------------- -# Helper Functions -# ----------------------------------------------------------------------------- - def seconds_to_human_readable(seconds): - # Convert seconds to human-readable format if seconds < 0: return "0 seconds" @@ -485,12 +518,12 @@ def seconds_to_human_readable(seconds): return " ".join(time_parts) if time_parts else "0 seconds" def format_market_time(dt_obj): - # Format datetime object to readable string return dt_obj.strftime("%Y-%m-%d %I:%M:%S %p %Z") def apply_slippage(price, is_buy=True): - # Apply slippage and commission to price + debug_print(f"Applying slippage to price={price:.2f}, is_buy={is_buy}") if not ENABLE_SLIPPAGE: + debug_print("Slippage disabled") return price slippage_adjustment = price * SLIPPAGE_PCT @@ -501,27 +534,29 @@ def apply_slippage(price, is_buy=True): else: adjusted_price = price - slippage_adjustment - commission_adjustment + debug_print(f"Adjusted price: {adjusted_price:.2f}") return adjusted_price - -# ----------------------------------------------------------------------------- -# Advanced Trading Functions -# ----------------------------------------------------------------------------- - def advanced_backtest_strategy(): - # Comprehensive backtest with all advanced filters logger.info("📊 Running advanced backtest with all filters...") + debug_print("=== STARTING BACKTEST ===") try: end_date = datetime.now() start_date = end_date - timedelta(days=BACKTEST_DAYS) - # Format dates as YYYY-MM-DD for Alpaca API + debug_print(f"Backtest period: {start_date.date()} to {end_date.date()}") + debug_print(f"Fetching {BACKTEST_DAYS} days of 15min bars for {SYMBOL}...") + bars = api.get_bars(SYMBOL, "15Min", start=start_date.strftime('%Y-%m-%d'), end=end_date.strftime('%Y-%m-%d')).df + debug_print(f"Received {len(bars)} bars") + if len(bars) < 100: logger.warning("âš ī¸ Insufficient data for backtest") + debug_print("Insufficient data for backtest, aborting") return + debug_print("Calculating indicators for backtest...") closes = bars['close'] highs = bars['high'] lows = bars['low'] @@ -539,7 +574,8 @@ def advanced_backtest_strategy(): upper_bb, middle_bb, lower_bb = calculate_bollinger_bands(closes, BB_WINDOW, BB_STD) macd_line, signal_line, histogram = calculate_macd(closes) - # Track performance + debug_print("Indicators calculated, starting backtest simulation...") + initial_balance = 10000 balance = initial_balance position = 0 @@ -550,6 +586,8 @@ def advanced_backtest_strategy(): winning_trades = 0 daily_trades = {} + debug_print(f"Initial balance: ${initial_balance}") + for i in range(max(SHORT_WINDOW, LONG_WINDOW, BB_WINDOW, 35), len(bars)): current_price = closes.iloc[i] current_time = bars.index[i] @@ -558,18 +596,15 @@ def advanced_backtest_strategy(): current_rsi = rsi.iloc[i] current_atr = atr.iloc[i] - # Check daily trade limit if current_date not in daily_trades: daily_trades[current_date] = 0 regime = 'trend' if current_adx > ADX_THRESHOLD else 'range' macd_signal = 'bullish' if macd_line.iloc[i] > signal_line.iloc[i] else 'bearish' - # Check candle pattern recent_bars = bars.iloc[max(0, i-1):i+1] bullish_eng, bearish_eng = check_candle_pattern(recent_bars) - # Generate signals if regime == 'trend': ma_signal = 1 if short_ma.iloc[i] > long_ma.iloc[i] else -1 rsi_signal = 1 if current_rsi < 65 else (-1 if current_rsi > 35 else 0) @@ -582,7 +617,6 @@ def advanced_backtest_strategy(): else: combined_signal = 0 - # Enter position if position == 0 and abs(combined_signal) >= 1.2: if daily_trades[current_date] >= MAX_TRADES_PER_DAY: continue @@ -619,13 +653,11 @@ def advanced_backtest_strategy(): 'regime': regime }) - # Exit position elif position != 0: exit_triggered = False exit_price = None exit_reason = None - # Stop loss if position > 0 and current_price <= stop_loss: exit_triggered = True exit_price = apply_slippage(stop_loss, False) @@ -635,14 +667,12 @@ def advanced_backtest_strategy(): exit_price = apply_slippage(stop_loss, False) exit_reason = 'stop_loss' - # Time-based exit time_in_trade = (current_time - entry_time).total_seconds() if time_in_trade > MAX_HOLD_TIME: exit_triggered = True exit_price = apply_slippage(current_price, False) exit_reason = 'time_limit' - # Profit targets pnl_pct = (current_price - entry_price) / entry_price * position risk_amount = abs(entry_price - stop_loss) / entry_price @@ -651,7 +681,6 @@ def advanced_backtest_strategy(): exit_price = apply_slippage(current_price, False) exit_reason = 'target_1' - # Signal reversal exit_signal = -1 if position > 0 else 1 if (combined_signal * exit_signal) > 0.8: exit_triggered = True @@ -670,7 +699,8 @@ def advanced_backtest_strategy(): trades[-1]['pnl'] = pnl trades[-1]['exit_reason'] = exit_reason - # Calculate statistics + debug_print("Backtest simulation complete, calculating statistics...") + total_trades = len([t for t in trades if 'exit_price' in t]) win_rate = winning_trades / total_trades if total_trades > 0 else 0 total_return = (balance - initial_balance) / initial_balance @@ -691,6 +721,8 @@ def advanced_backtest_strategy(): logger.info(f" Avg loss: ${avg_loss:.2f}") logger.info(f" Final balance: ${balance:.2f}") + debug_print(f"Backtest results: trades={total_trades}, winrate={win_rate:.1%}, return={total_return:.1%}, PF={profit_factor:.2f}") + if total_trades < 5: logger.warning("âš ī¸ Very few trades - filters may be too strict") if win_rate < 0.45: @@ -702,22 +734,30 @@ def advanced_backtest_strategy(): error_msg = str(e).lower() if 'subscription' in error_msg or 'permit' in error_msg: logger.warning(f"âš ī¸ Backtest unavailable: Your subscription doesn't permit historical data access") + debug_print(f"Backtest failed: subscription issue - {e}") else: logger.warning(f"âš ī¸ Backtest failed: {e}") + debug_print(f"Backtest failed: {e}") def advanced_signal_generator(symbol): - # Advanced signal generation with ALL filters - # Returns: signal ('buy', 'sell', None), strength (0-1), stop_loss_price + debug_print(f"=== GENERATING SIGNAL FOR {symbol} ===") + + debug_print("Fetching recent bars...") bars = get_recent_bars(symbol, 100) if bars is None or len(bars) < 50: + debug_print("Insufficient bars for signal generation") return None, 0, 0 + debug_print(f"Received {len(bars)} bars") + closes = bars['close'] highs = bars['high'] lows = bars['low'] current_price = closes.iloc[-1] - # Calculate indicators + debug_print(f"Current price: ${current_price:.2f}") + + debug_print("Calculating indicators for signal...") if USE_EMA: short_ma = calculate_ema(closes, SHORT_WINDOW).iloc[-1] long_ma = calculate_ema(closes, LONG_WINDOW).iloc[-1] @@ -725,25 +765,37 @@ def advanced_signal_generator(symbol): short_ma = calculate_sma(closes, SHORT_WINDOW).iloc[-1] long_ma = calculate_sma(closes, LONG_WINDOW).iloc[-1] + debug_print(f"Moving averages: short={short_ma:.2f}, long={long_ma:.2f}") + rsi = calculate_rsi(closes, 14).iloc[-1] + debug_print(f"RSI: {rsi:.2f}") + adx, plus_di, minus_di = calculate_adx(highs, lows, closes, 14) current_adx = adx.iloc[-1] - atr = calculate_atr(highs, lows, closes, 14).iloc[-1] - upper_bb, middle_bb, lower_bb = calculate_bollinger_bands(closes, BB_WINDOW, BB_STD) + debug_print(f"ADX: {current_adx:.2f}") - # Filters + atr = calculate_atr(highs, lows, closes, 14).iloc[-1] + debug_print(f"ATR: {atr:.4f}") + + upper_bb, middle_bb, lower_bb = calculate_bollinger_bands(closes, BB_WINDOW, BB_STD) + debug_print(f"Bollinger Bands: upper={upper_bb.iloc[-1]:.2f}, middle={middle_bb.iloc[-1]:.2f}, lower={lower_bb.iloc[-1]:.2f}") + + debug_print("Applying filters...") vix_level = get_vix_level() if USE_VIX_FILTER and vix_level > VIX_THRESHOLD: logger.info(f"📉 VIX too high: {vix_level:.1f} > {VIX_THRESHOLD}") + debug_print(f"FILTER FAILED: VIX too high ({vix_level:.1f} > {VIX_THRESHOLD})") return None, 0, 0 sma_200_trend = check_200_sma_filter(symbol) if USE_200_SMA_FILTER and sma_200_trend == 'bearish': logger.info(f"📉 Below 200 SMA - avoiding longs") + debug_print("WARNING: Below 200 SMA - will avoid longs") volume_ok = check_volume_confirmation(bars) if not volume_ok: logger.info(f"📊 Insufficient volume") + debug_print("FILTER FAILED: Insufficient volume") return None, 0, 0 bullish_eng, bearish_eng = check_candle_pattern(bars) @@ -753,39 +805,50 @@ def advanced_signal_generator(symbol): fib_382, fib_500, fib_618, swing_high, swing_low = calculate_fibonacci_levels(bars, 20) regime = detect_market_regime(bars) + debug_print(f"Market regime: {regime}") if regime == 'low_vol': logger.info("📉 Low volatility regime") + debug_print("FILTER FAILED: Low volatility regime") return None, 0, 0 signal = None signal_strength = 0 stop_loss = 0 - # TREND REGIME + debug_print("Evaluating trading signals...") + if regime == 'trend': + debug_print("Processing TREND regime logic...") if current_adx > ADX_THRESHOLD: - # Bullish trend - wait for pullback if short_ma > long_ma: + debug_print(f"Bullish trend detected (short_ma > long_ma)") pullback_ok = False if USE_FIBONACCI and fib_382 is not None: if abs(current_price - fib_382) / current_price < 0.01: pullback_ok = True + debug_print(f"Pullback OK: near fib 38.2% ({fib_382:.2f})") elif current_price < short_ma * 1.005: pullback_ok = True + debug_print(f"Pullback OK: price near short MA") if pullback_ok and rsi < 55: + debug_print(f"Pullback and RSI conditions met (RSI={rsi:.2f})") if hourly_trend in ['bullish', 'neutral']: + debug_print(f"Hourly trend favorable: {hourly_trend}") if REQUIRE_CANDLE_PATTERN and not bullish_eng: logger.info("❌ No bullish engulfing") + debug_print("REJECTED: No bullish engulfing pattern") return None, 0, 0 if REQUIRE_MACD_CONFIRMATION and macd_signal != 'bullish': logger.info("❌ MACD not bullish") + debug_print(f"REJECTED: MACD not bullish ({macd_signal})") return None, 0, 0 if USE_200_SMA_FILTER and sma_200_trend == 'bearish': logger.info("❌ Below 200 SMA - no longs") + debug_print("REJECTED: Below 200 SMA") return None, 0, 0 if USE_PIVOT_POINTS and s1 is not None: @@ -793,28 +856,36 @@ def advanced_signal_generator(symbol): signal = 'buy' signal_strength = min(1.0, (current_adx / 40) * 0.8 + 0.2) stop_loss = current_price - (atr * ATR_STOP_MULTIPLIER) + debug_print(f"SIGNAL: BUY (trend with pivot, strength={signal_strength:.2f}, stop={stop_loss:.2f})") else: signal = 'buy' signal_strength = min(1.0, (current_adx / 40) * 0.7 + 0.3) stop_loss = current_price - (atr * ATR_STOP_MULTIPLIER) + debug_print(f"SIGNAL: BUY (trend, strength={signal_strength:.2f}, stop={stop_loss:.2f})") - # Bearish trend - wait for pullback elif short_ma < long_ma: + debug_print(f"Bearish trend detected (short_ma < long_ma)") pullback_ok = False if USE_FIBONACCI and fib_618 is not None: if abs(current_price - fib_618) / current_price < 0.01: pullback_ok = True + debug_print(f"Pullback OK: near fib 61.8% ({fib_618:.2f})") elif current_price > short_ma * 0.995: pullback_ok = True + debug_print(f"Pullback OK: price near short MA") if pullback_ok and rsi > 45: + debug_print(f"Pullback and RSI conditions met (RSI={rsi:.2f})") if hourly_trend in ['bearish', 'neutral']: + debug_print(f"Hourly trend favorable: {hourly_trend}") if REQUIRE_CANDLE_PATTERN and not bearish_eng: logger.info("❌ No bearish engulfing") + debug_print("REJECTED: No bearish engulfing pattern") return None, 0, 0 if REQUIRE_MACD_CONFIRMATION and macd_signal != 'bearish': logger.info("❌ MACD not bearish") + debug_print(f"REJECTED: MACD not bearish ({macd_signal})") return None, 0, 0 if USE_PIVOT_POINTS and r1 is not None: @@ -822,93 +893,122 @@ def advanced_signal_generator(symbol): signal = 'sell' signal_strength = min(1.0, (current_adx / 40) * 0.8 + 0.2) stop_loss = current_price + (atr * ATR_STOP_MULTIPLIER) + debug_print(f"SIGNAL: SELL (trend with pivot, strength={signal_strength:.2f}, stop={stop_loss:.2f})") else: signal = 'sell' signal_strength = min(1.0, (current_adx / 40) * 0.7 + 0.3) stop_loss = current_price + (atr * ATR_STOP_MULTIPLIER) + debug_print(f"SIGNAL: SELL (trend, strength={signal_strength:.2f}, stop={stop_loss:.2f})") - # RANGE REGIME elif regime == 'range': - # Oversold at lower band + debug_print("Processing RANGE regime logic...") if current_price <= lower_bb.iloc[-1] and rsi < 30: + debug_print(f"Oversold condition: price at/below lower BB and RSI < 30") if hourly_trend != 'bearish': + debug_print(f"Hourly trend not bearish: {hourly_trend}") if REQUIRE_CANDLE_PATTERN and not bullish_eng: logger.info("❌ No bullish engulfing in range") + debug_print("REJECTED: No bullish engulfing in range") return None, 0, 0 if USE_200_SMA_FILTER and sma_200_trend == 'bearish': logger.info("❌ Below 200 SMA - no mean reversion longs") + debug_print("REJECTED: Below 200 SMA for mean reversion") return None, 0, 0 signal = 'buy' signal_strength = 0.85 stop_loss = current_price - (atr * ATR_STOP_MULTIPLIER) + debug_print(f"SIGNAL: BUY (range oversold, strength={signal_strength:.2f}, stop={stop_loss:.2f})") - # Overbought at upper band elif current_price >= upper_bb.iloc[-1] and rsi > 70: + debug_print(f"Overbought condition: price at/above upper BB and RSI > 70") if hourly_trend != 'bullish': + debug_print(f"Hourly trend not bullish: {hourly_trend}") if REQUIRE_CANDLE_PATTERN and not bearish_eng: logger.info("❌ No bearish engulfing in range") + debug_print("REJECTED: No bearish engulfing in range") return None, 0, 0 signal = 'sell' signal_strength = 0.85 stop_loss = current_price + (atr * ATR_STOP_MULTIPLIER) + debug_print(f"SIGNAL: SELL (range overbought, strength={signal_strength:.2f}, stop={stop_loss:.2f})") - # HIGH VOL REGIME elif regime == 'high_vol': + debug_print("Processing HIGH_VOL regime logic...") if short_ma > long_ma and rsi < 35: + debug_print(f"High vol bullish setup: short_ma > long_ma and RSI < 35") if hourly_trend == 'bullish': + debug_print(f"Hourly trend bullish") if REQUIRE_CANDLE_PATTERN and not bullish_eng: + debug_print("REJECTED: No bullish engulfing in high vol") return None, 0, 0 if REQUIRE_MACD_CONFIRMATION and macd_signal != 'bullish': + debug_print(f"REJECTED: MACD not bullish in high vol ({macd_signal})") return None, 0, 0 signal = 'buy' signal_strength = 0.6 stop_loss = current_price - (atr * ATR_STOP_MULTIPLIER * 1.5) + debug_print(f"SIGNAL: BUY (high vol, strength={signal_strength:.2f}, stop={stop_loss:.2f})") elif short_ma < long_ma and rsi > 65: + debug_print(f"High vol bearish setup: short_ma < long_ma and RSI > 65") if hourly_trend == 'bearish': + debug_print(f"Hourly trend bearish") if REQUIRE_CANDLE_PATTERN and not bearish_eng: + debug_print("REJECTED: No bearish engulfing in high vol") return None, 0, 0 if REQUIRE_MACD_CONFIRMATION and macd_signal != 'bearish': + debug_print(f"REJECTED: MACD not bearish in high vol ({macd_signal})") return None, 0, 0 signal = 'sell' signal_strength = 0.6 stop_loss = current_price + (atr * ATR_STOP_MULTIPLIER * 1.5) + debug_print(f"SIGNAL: SELL (high vol, strength={signal_strength:.2f}, stop={stop_loss:.2f})") - # Check minimum signal strength if signal_strength < MIN_SIGNAL_STRENGTH: logger.info(f"❌ Signal strength {signal_strength:.2f} < {MIN_SIGNAL_STRENGTH:.2f}") + debug_print(f"REJECTED: Signal strength {signal_strength:.2f} < threshold {MIN_SIGNAL_STRENGTH:.2f}") return None, signal_strength, 0 - # Final risk/reward check if signal and stop_loss != 0: + debug_print("Performing risk/reward check...") potential_reward = abs(current_price - stop_loss) * MIN_RISK_REWARD if USE_PIVOT_POINTS: if signal == 'buy' and r1 is not None: actual_reward = r1 - current_price + debug_print(f"R:R check (buy): actual_reward={actual_reward:.2f}, potential_reward={potential_reward:.2f}") if actual_reward < potential_reward: logger.info(f"❌ R:R too low: {actual_reward:.2f} < {potential_reward:.2f}") + debug_print(f"REJECTED: R:R too low") return None, signal_strength, 0 elif signal == 'sell' and s1 is not None: actual_reward = current_price - s1 + debug_print(f"R:R check (sell): actual_reward={actual_reward:.2f}, potential_reward={potential_reward:.2f}") if actual_reward < potential_reward: logger.info(f"❌ R:R too low: {actual_reward:.2f} < {potential_reward:.2f}") + debug_print(f"REJECTED: R:R too low") return None, signal_strength, 0 + if signal: + debug_print(f"=== FINAL SIGNAL: {signal.upper()}, strength={signal_strength:.2f}, stop=${stop_loss:.2f} ===") + else: + debug_print("=== NO SIGNAL GENERATED ===") + return signal, signal_strength, stop_loss def wait_until_market_open(): - # Wait until the market opens + debug_print("Checking if market is open...") try: clock = api.get_clock() except Exception as e: logger.warning(f"âš ī¸ Failed to get clock: {e}") + debug_print(f"Failed to get clock: {e}") time.sleep(60) return @@ -917,6 +1017,7 @@ def wait_until_market_open(): if not clock.is_open: seconds_until_open = (next_open - now).total_seconds() + debug_print(f"Market closed, {seconds_until_open:.0f} seconds until open") if seconds_until_open > 0: readable_time = seconds_to_human_readable(seconds_until_open) logger.info(f"🕒 Market opens at {format_market_time(next_open)}") @@ -930,51 +1031,69 @@ def wait_until_market_open(): if sleep_time >= 60: remaining_readable = seconds_to_human_readable(seconds_until_open) logger.info(f"âąī¸ {remaining_readable} remaining...") + debug_print(f"Waiting... {remaining_readable} remaining") else: logger.info("✅ Market is open!") + debug_print("Market is open") else: logger.info("✅ Market is open!") + debug_print("Market is open") def fetch_equity(): - # Fetch the current account equity + debug_print("Fetching account equity...") try: account = api.get_account() - return float(account.equity) + equity = float(account.equity) + debug_print(f"Account equity: ${equity:.2f}") + return equity except Exception as e: logger.error(f"❌ Failed to fetch equity: {e}") + debug_print(f"Failed to fetch equity: {e}") return 0.0 def fetch_buying_power(): - # Fetch the current buying power + debug_print("Fetching buying power...") try: account = api.get_account() - return float(account.buying_power) + bp = float(account.buying_power) + debug_print(f"Buying power: ${bp:.2f}") + return bp except Exception as e: logger.error(f"❌ Failed to fetch buying power: {e}") + debug_print(f"Failed to fetch buying power: {e}") return 0.0 def get_day_trade_count(): - # Get the current day trade count + debug_print("Getting day trade count...") try: account = api.get_account() - return int(account.daytrade_count) + count = int(account.daytrade_count) + debug_print(f"Day trade count: {count}") + return count except Exception as e: logger.error(f"❌ Failed to fetch day trade count: {e}") + debug_print(f"Failed to fetch day trade count: {e}") return 0 def submit_limit_buy(symbol, notional, limit_price): - # Submit a limit buy order + debug_print(f"=== SUBMITTING LIMIT BUY ORDER ===") + debug_print(f"Symbol: {symbol}, Notional: ${notional:.2f}, Limit: ${limit_price:.2f}") + if notional < MIN_NOTIONAL: logger.warning(f"âš ī¸ Notional ${notional:.2f} < minimum ${MIN_NOTIONAL}") + debug_print(f"Order rejected: notional too small") return False try: shares = int(notional / limit_price) + debug_print(f"Calculated shares: {shares}") if shares == 0: logger.warning(f"âš ī¸ Cannot buy fractional shares with ${notional:.2f}") + debug_print(f"Order rejected: shares = 0") return False + debug_print(f"Submitting limit buy order to API...") order = api.submit_order( symbol=symbol, qty=shares, @@ -984,43 +1103,54 @@ def submit_limit_buy(symbol, notional, limit_price): time_in_force="gtc" ) + debug_print(f"Order submitted, ID: {order.id}") logger.info(f"đŸŸĸ LIMIT BUY: {shares} shares @ ${limit_price:.2f}") - # Wait for fill or timeout start_time = time.time() + debug_print(f"Waiting for fill (timeout: {LIMIT_ORDER_TIMEOUT}s)...") while (time.time() - start_time) < LIMIT_ORDER_TIMEOUT: order_status = api.get_order(order.id) + debug_print(f"Order status: {order_status.status}") if order_status.status == 'filled': filled_price = float(order_status.filled_avg_price) logger.info(f"✅ FILLED @ ${filled_price:.2f}") + debug_print(f"Order filled at ${filled_price:.2f}") return filled_price elif order_status.status in ['cancelled', 'expired', 'rejected']: logger.warning(f"âš ī¸ Limit order {order_status.status}") + debug_print(f"Order {order_status.status}") return False time.sleep(2) - # Timeout - cancel and use market order logger.warning("âąī¸ Timeout - switching to market") + debug_print("Timeout reached, canceling order and switching to market") api.cancel_order(order.id) return submit_market_buy(symbol, notional) except Exception as e: logger.error(f"❌ Failed limit buy: {e}") + debug_print(f"Limit buy failed: {e}") return False def submit_market_buy(symbol, notional): - # Submit a market buy order (fallback) + debug_print(f"=== SUBMITTING MARKET BUY ORDER ===") + debug_print(f"Symbol: {symbol}, Notional: ${notional:.2f}") + try: current_price = get_current_price(symbol) if current_price == 0: + debug_print("Market buy failed: could not get current price") return False execution_price = apply_slippage(current_price, True) shares = int(notional / execution_price) + debug_print(f"Shares: {shares}, Expected execution: ${execution_price:.2f}") if shares == 0: + debug_print("Market buy failed: shares = 0") return False + debug_print("Submitting market buy order to API...") api.submit_order( symbol=symbol, qty=shares, @@ -1029,14 +1159,19 @@ def submit_market_buy(symbol, notional): time_in_force="day" ) logger.info(f"đŸŸĸ MARKET BUY: {shares} shares @ ~${execution_price:.2f}") + debug_print(f"Market buy order submitted") return execution_price except Exception as e: logger.error(f"❌ Failed buy: {e}") + debug_print(f"Market buy failed: {e}") return False def submit_limit_sell(symbol, qty, limit_price): - # Submit a limit sell order + debug_print(f"=== SUBMITTING LIMIT SELL ORDER ===") + debug_print(f"Symbol: {symbol}, Qty: {qty}, Limit: ${limit_price:.2f}") + try: + debug_print("Submitting limit sell order to API...") order = api.submit_order( symbol=symbol, qty=qty, @@ -1046,39 +1181,49 @@ def submit_limit_sell(symbol, qty, limit_price): time_in_force="gtc" ) + debug_print(f"Order submitted, ID: {order.id}") logger.info(f"🔴 LIMIT SELL: {qty} shares @ ${limit_price:.2f}") - # Wait for fill or timeout start_time = time.time() + debug_print(f"Waiting for fill (timeout: {LIMIT_ORDER_TIMEOUT}s)...") while (time.time() - start_time) < LIMIT_ORDER_TIMEOUT: order_status = api.get_order(order.id) + debug_print(f"Order status: {order_status.status}") if order_status.status == 'filled': filled_price = float(order_status.filled_avg_price) logger.info(f"✅ FILLED @ ${filled_price:.2f}") + debug_print(f"Order filled at ${filled_price:.2f}") return filled_price elif order_status.status in ['cancelled', 'expired', 'rejected']: logger.warning(f"âš ī¸ Limit order {order_status.status}") + debug_print(f"Order {order_status.status}") return False time.sleep(2) - # Timeout - cancel and use market order logger.warning("âąī¸ Timeout - switching to market") + debug_print("Timeout reached, canceling order and switching to market") api.cancel_order(order.id) return submit_market_sell(symbol, qty) except Exception as e: logger.error(f"❌ Failed limit sell: {e}") + debug_print(f"Limit sell failed: {e}") return False def submit_market_sell(symbol, qty): - # Submit a market sell order (fallback) + debug_print(f"=== SUBMITTING MARKET SELL ORDER ===") + debug_print(f"Symbol: {symbol}, Qty: {qty}") + try: current_price = get_current_price(symbol) if current_price == 0: + debug_print("Market sell failed: could not get current price") return False execution_price = apply_slippage(current_price, False) + debug_print(f"Expected execution: ${execution_price:.2f}") + debug_print("Submitting market sell order to API...") api.submit_order( symbol=symbol, qty=qty, @@ -1087,71 +1232,91 @@ def submit_market_sell(symbol, qty): time_in_force="day" ) logger.info(f"🔴 MARKET SELL: {qty} shares @ ~${execution_price:.2f}") + debug_print(f"Market sell order submitted") return execution_price except Exception as e: logger.error(f"❌ Failed sell: {e}") + debug_print(f"Market sell failed: {e}") return False def close_all_positions(): - # Close all open positions + debug_print("Closing all positions...") try: positions = api.list_positions() if not positions: logger.info("✅ No open positions") + debug_print("No open positions to close") return + debug_print(f"Found {len(positions)} positions to close") logger.warning("âš ī¸ Closing all positions...") for pos in positions: + debug_print(f"Closing position: {pos.symbol}, qty={pos.qty}") submit_market_sell(pos.symbol, int(float(pos.qty))) logger.info("✅ All positions closed") + debug_print("All positions closed successfully") except Exception as e: logger.error(f"❌ Failed to close positions: {e}") + debug_print(f"Failed to close positions: {e}") def get_recent_bars(symbol, limit=100): - # Get recent bar data for a symbol + debug_print(f"Fetching {limit} recent bars for {symbol}...") try: timeframe = "15Min" bars = api.get_bars(symbol, timeframe, limit=limit).df + debug_print(f"Received {len(bars)} bars") return bars except Exception as e: logger.error(f"❌ Failed to fetch bars: {e}") + debug_print(f"Failed to fetch bars: {e}") return None def current_position_qty(symbol): - # Get the current position quantity + debug_print(f"Checking position quantity for {symbol}...") try: positions = api.list_positions() for pos in positions: if pos.symbol == symbol: - return int(float(pos.qty)) + qty = int(float(pos.qty)) + debug_print(f"Position qty: {qty}") + return qty + debug_print("No position found") return 0 except Exception as e: logger.error(f"❌ Failed to fetch positions: {e}") + debug_print(f"Failed to fetch positions: {e}") return 0 def pdt_allows_new_trade(): - # Check if PDT rules allow a new trade + debug_print("Checking PDT rules...") if not PDT_RULE: + debug_print("PDT rule disabled, allowing trade") return True equity = fetch_equity() day_trade_count = get_day_trade_count() + debug_print(f"PDT check: equity=${equity:.2f}, day_trades={day_trade_count}") + if equity < 25000: if day_trade_count >= 3: logger.error(f"🛑 PDT rule: {day_trade_count} trades in 5-day window") + debug_print(f"PDT violation: {day_trade_count} >= 3 with equity < $25k") return False + debug_print("PDT check passed") return True def get_market_status(): - # Get current market status + debug_print("Getting market status...") try: clock = api.get_clock() status = "open" if clock.is_open else "closed" next_event = clock.next_open if not clock.is_open else clock.next_close event_type = "open" if not clock.is_open else "close" + debug_print(f"Market status: {status}, next {event_type} at {next_event}") + return { "status": status, "next_event": next_event, @@ -1160,6 +1325,7 @@ def get_market_status(): } except Exception as e: logger.warning(f"âš ī¸ Failed to get market status: {e}") + debug_print(f"Failed to get market status: {e}") return { "status": "unknown", "next_event": None, @@ -1168,78 +1334,93 @@ def get_market_status(): } def calculate_position_size(equity, stop_loss, entry_price, regime='normal'): - # Calculate position size based on fixed risk per trade + debug_print(f"Calculating position size: equity=${equity:.2f}, entry=${entry_price:.2f}, stop=${stop_loss:.2f}, regime={regime}") + risk_amount = equity * RISK_PER_TRADE if regime == 'high_vol': risk_amount *= 0.5 logger.info(f"📊 High vol - reducing position 50%") + debug_print("High vol: reducing risk by 50%") stop_distance = abs(entry_price - stop_loss) if stop_distance == 0: + debug_print("Stop distance is 0, returning MIN_NOTIONAL") return MIN_NOTIONAL position_size = risk_amount / stop_distance * entry_price position_size = max(MIN_NOTIONAL, position_size) logger.info(f"💰 Position: Risk=${risk_amount:.2f}, Stop=${stop_distance:.2f}, Size=${position_size:.2f}") + debug_print(f"Position size: ${position_size:.2f}") return position_size def should_trade_based_on_market_hours(): - # Avoid trading during low-volume periods + debug_print("Checking if in tradeable market hours...") if not MARKET_HOURS_FILTER: + debug_print("Market hours filter disabled") return True now = datetime.now().time() - # Avoid first 30 min open_buffer_end = datetime.strptime("10:00", "%H:%M").time() - - # Avoid last 30 min close_buffer_start = datetime.strptime("15:30", "%H:%M").time() + debug_print(f"Current time: {now}") + if now < open_buffer_end: + debug_print("Before 10:00 AM, outside trading hours") return False if now >= close_buffer_start: + debug_print("After 3:30 PM, outside trading hours") return False + debug_print("Within trading hours") return True def atr_based_trailing_stop(symbol, entry_price, current_price, stop_loss, position_type='long'): - # ATR-based trailing stop loss + debug_print(f"Checking trailing stop: entry=${entry_price:.2f}, current=${current_price:.2f}, stop=${stop_loss:.2f}, type={position_type}") + if not USE_TRAILING_STOP: + debug_print("Trailing stop disabled, checking fixed stop") if position_type == 'long' and current_price <= stop_loss: + debug_print("Fixed stop hit (long)") return True elif position_type == 'short' and current_price >= stop_loss: + debug_print("Fixed stop hit (short)") return True return False position_qty = current_position_qty(symbol) if position_qty == 0: + debug_print("No position, skipping stop check") return False - # Get ATR for dynamic stop bars = get_recent_bars(symbol, 20) if bars is not None and len(bars) > 14: atr = calculate_atr(bars['high'], bars['low'], bars['close'], 14).iloc[-1] trail_distance = atr * ATR_STOP_MULTIPLIER + debug_print(f"ATR trail distance: {trail_distance:.4f}") else: trail_distance = abs(entry_price - stop_loss) + debug_print(f"Using fixed trail distance: {trail_distance:.4f}") - # Update trailing stop if not hasattr(atr_based_trailing_stop, 'trailing_stop'): atr_based_trailing_stop.trailing_stop = stop_loss + debug_print(f"Initialized trailing stop: ${stop_loss:.2f}") if position_type == 'long': new_stop = current_price - trail_distance if new_stop > atr_based_trailing_stop.trailing_stop: atr_based_trailing_stop.trailing_stop = new_stop logger.info(f"📈 Trailing stop → ${new_stop:.2f}") + debug_print(f"Trailing stop updated (long): ${new_stop:.2f}") if current_price <= atr_based_trailing_stop.trailing_stop: logger.info(f"🛑 Trailing stop hit @ ${current_price:.2f}") + debug_print(f"Trailing stop hit (long): price=${current_price:.2f} <= stop=${atr_based_trailing_stop.trailing_stop:.2f}") return True elif position_type == 'short': @@ -1247,17 +1428,22 @@ def atr_based_trailing_stop(symbol, entry_price, current_price, stop_loss, posit if new_stop < atr_based_trailing_stop.trailing_stop: atr_based_trailing_stop.trailing_stop = new_stop logger.info(f"📉 Trailing stop → ${new_stop:.2f}") + debug_print(f"Trailing stop updated (short): ${new_stop:.2f}") if current_price >= atr_based_trailing_stop.trailing_stop: logger.info(f"🛑 Trailing stop hit @ ${current_price:.2f}") + debug_print(f"Trailing stop hit (short): price=${current_price:.2f} >= stop=${atr_based_trailing_stop.trailing_stop:.2f}") return True + debug_print("Trailing stop not hit") return False def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, position_type='long'): - # Scale out at profit targets + debug_print(f"Checking profit targets: entry=${entry_price:.2f}, current=${current_price:.2f}, stop=${stop_loss:.2f}") + position_qty = current_position_qty(symbol) if position_qty == 0: + debug_print("No position, skipping profit targets") return False risk_distance = abs(entry_price - stop_loss) @@ -1269,12 +1455,15 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit profit_pct = (entry_price - current_price) / entry_price profit_in_r = (entry_price - current_price) / risk_distance if risk_distance > 0 else 0 - # First target: 1.5R + debug_print(f"Profit: {profit_pct:.2%}, {profit_in_r:.2f}R") + if profit_in_r >= PROFIT_TARGET_1: if not hasattr(scale_out_profit_taking, 'target_1_hit'): scale_out_profit_taking.target_1_hit = True partial_qty = position_qty // 2 + debug_print(f"Target 1 ({PROFIT_TARGET_1}R) hit, scaling out {partial_qty} shares") + if partial_qty > 0: if USE_LIMIT_ORDERS: limit_price = current_price @@ -1284,14 +1473,14 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit logger.info(f"đŸŽ¯ Target 1 ({PROFIT_TARGET_1}R) - 50% out @ ${current_price:.2f}") - # Move stop to breakeven atr_based_trailing_stop.trailing_stop = entry_price logger.info(f"🔒 Stop → breakeven: ${entry_price:.2f}") + debug_print(f"Stop moved to breakeven: ${entry_price:.2f}") return True - # Second target: 3R if profit_in_r >= PROFIT_TARGET_2: remaining_qty = current_position_qty(symbol) + debug_print(f"Target 2 ({PROFIT_TARGET_2}R) hit, exiting {remaining_qty} shares") if remaining_qty > 0: if USE_LIMIT_ORDERS: limit_price = current_price @@ -1302,40 +1491,47 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit logger.info(f"đŸŽ¯đŸŽ¯ Target 2 ({PROFIT_TARGET_2}R) - Full exit @ ${current_price:.2f}") return True + debug_print("No profit targets hit") return False def get_current_price(symbol): - # Get current price for a symbol + debug_print(f"Getting current price for {symbol}...") try: bars = api.get_bars(symbol, "1Min", limit=5).df if len(bars) > 0: - return bars['close'].iloc[-1] + price = bars['close'].iloc[-1] + debug_print(f"Current price: ${price:.2f}") + return price else: + debug_print("No bars returned") return 0 except Exception as e: logger.error(f"❌ Failed to get price: {e}") + debug_print(f"Failed to get price: {e}") return 0 def get_bid_ask(symbol): - # Get current bid/ask prices + debug_print(f"Getting bid/ask for {symbol}...") try: quote = api.get_latest_quote(symbol) - return float(quote.bid_price), float(quote.ask_price) + bid = float(quote.bid_price) + ask = float(quote.ask_price) + debug_print(f"Bid: ${bid:.2f}, Ask: ${ask:.2f}") + return bid, ask except Exception as e: logger.warning(f"âš ī¸ Could not get bid/ask: {e}") + debug_print(f"Failed to get bid/ask: {e}, using current price") current_price = get_current_price(symbol) return current_price, current_price - -# ----------------------------------------------------------------------------- -# Main Trading Loop - Continuous Operation -# ----------------------------------------------------------------------------- - def main(): - # Main trading function - runs continuously 24/7 logger.info("🚀 Starting daytrader.py - continuous operation") + if DEBUG_MODE: + print("\n" + "="*70) + print("DEBUG MODE ENABLED - Verbose output active") + print("="*70 + "\n") - # Validate API connectivity and credentials logger.info("🔍 Validating API connectivity...") + debug_print("Starting API validation...") try: account = api.get_account() logger.info(f"✅ API connected successfully") @@ -1345,96 +1541,103 @@ def main(): logger.info(f"✅ Day Trade Count: {int(account.daytrade_count)}") logger.info(f"✅ Pattern Day Trader: {account.pattern_day_trader}") - # Test market data access + debug_print(f"API validation successful") + + debug_print(f"Testing market data access for {SYMBOL}...") test_bars = api.get_bars(SYMBOL, "1Day", limit=1).df if len(test_bars) > 0: logger.info(f"✅ Market data access verified for {SYMBOL}") + debug_print("Market data access verified") else: logger.warning(f"âš ī¸ No market data returned for {SYMBOL}") + debug_print("WARNING: No market data returned") - # Test clock access + debug_print("Testing clock access...") clock = api.get_clock() logger.info(f"✅ Clock access verified - Market is {'OPEN' if clock.is_open else 'CLOSED'}") + debug_print(f"Clock access verified, market is {'OPEN' if clock.is_open else 'CLOSED'}") except Exception as e: error_msg = str(e).lower() logger.error(f"❌ API validation failed") + debug_print(f"API validation failed: {e}") if 'unauthorized' in error_msg or 'forbidden' in error_msg: logger.error(f"🔑 Invalid API credentials detected") logger.error(f"Please update your .env file with valid API keys") + debug_print("Invalid API credentials detected") else: logger.error(f"Error: {e}") logger.error(f"Please check your .env file and network connection") return - # Run backtest once at startup (optional - continues if it fails) try: advanced_backtest_strategy() except Exception as e: logger.warning(f"âš ī¸ Backtest skipped: {e}") logger.info(f"â„šī¸ Continuing without backtest - this is optional") + debug_print(f"Backtest skipped: {e}") - # Track daily state last_reset_date = None trades_today = 0 try: - while True: # Infinite loop for continuous operation + while True: + debug_print("=== NEW MAIN LOOP ITERATION ===") try: - # Check if we need to reset daily counters current_date = datetime.now().date() if last_reset_date != current_date: trades_today = 0 last_reset_date = current_date logger.info(f"📅 New day: {current_date}") + debug_print(f"New day: {current_date}, resetting counters") - # Reset function attributes if hasattr(scale_out_profit_taking, 'target_1_hit'): delattr(scale_out_profit_taking, 'target_1_hit') + debug_print("Reset target_1_hit attribute") if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(atr_based_trailing_stop, 'trailing_stop') + debug_print("Reset trailing_stop attribute") - # Check if should skip today if should_skip_trading_day(): day_name = datetime.now().strftime("%A") logger.info(f"📅 Skipping {day_name} - monitoring mode") - time.sleep(3600) # Sleep 1 hour + debug_print(f"Skipping trading today ({day_name})") + time.sleep(3600) continue - # Display market status market_info = get_market_status() if market_info['status'] == 'closed': logger.info(f"đŸ›ī¸ Market closed") logger.info(f"📅 Next open: {format_market_time(market_info['next_event'])}") + debug_print("Market closed, waiting for open...") wait_until_market_open() continue - # Market is open logger.info(f"đŸ›ī¸ Market OPEN - starting session") + debug_print("=== MARKET OPEN - STARTING SESSION ===") - # Record opening equity opening_equity = fetch_equity() if opening_equity == 0: logger.error("đŸ’Ĩ No equity. Waiting 5 min...") + debug_print("No equity detected, waiting 5 minutes...") time.sleep(300) continue logger.info(f"💰 Opening equity: ${opening_equity:.2f}") + debug_print(f"Opening equity: ${opening_equity:.2f}") - # Get VIX and 200 SMA vix_level = get_vix_level() logger.info(f"📊 VIX: {vix_level:.1f}") sma_200_trend = check_200_sma_filter(SYMBOL) logger.info(f"📈 200 SMA: {sma_200_trend.upper()}") - # Display config logger.info(f"âš™ī¸ Config: {SYMBOL}, Risk={RISK_PER_TRADE:.2%}, Trades={trades_today}/{MAX_TRADES_PER_DAY}") + debug_print(f"Config: SYMBOL={SYMBOL}, RISK={RISK_PER_TRADE:.2%}, TRADES={trades_today}/{MAX_TRADES_PER_DAY}") - # Session variables trade_count = 0 entry_price = 0 entry_time = None @@ -1443,56 +1646,61 @@ def main(): position_type = None total_pnl = 0 - # Trading session loop while True: - # Check market still open + debug_print("--- Session loop iteration ---") + try: clock = api.get_clock() if not clock.is_open: logger.info("❌ Market closed") + debug_print("Market closed, exiting session loop") break except Exception as e: logger.warning(f"âš ī¸ Clock check failed: {e}") + debug_print(f"Clock check failed: {e}, waiting 1 minute") time.sleep(60) continue - # Check day changed if datetime.now().date() != current_date: logger.info("📅 Day changed - resetting") + debug_print("Day changed, exiting session loop") break - # Check drawdown current_equity = fetch_equity() drawdown = (opening_equity - current_equity) / opening_equity + debug_print(f"Drawdown check: opening=${opening_equity:.2f}, current=${current_equity:.2f}, drawdown={drawdown:.2%}") if drawdown > MAX_DRAWDOWN: logger.error(f"💸 Max drawdown: {drawdown:.2%}") + debug_print(f"Max drawdown exceeded: {drawdown:.2%} > {MAX_DRAWDOWN:.2%}") break - # Market hours filter if not should_trade_based_on_market_hours(): + debug_print("Outside trading hours, sleeping 5 minutes") time.sleep(300) continue - # PDT check if not pdt_allows_new_trade(): logger.error("🛑 PDT violation") + debug_print("PDT violation detected, breaking") break - # Get current price current_price = get_current_price(SYMBOL) if current_price == 0: logger.warning("âš ī¸ No price, retrying...") + debug_print("No price data, waiting 1 minute") time.sleep(60) continue - # Manage existing position if position_active: - # Time-based exit + debug_print(f"Managing active position: type={position_type}, entry=${entry_price:.2f}") + if entry_time: time_in_trade = (datetime.now() - entry_time).total_seconds() + debug_print(f"Time in trade: {time_in_trade:.0f}s (max: {MAX_HOLD_TIME}s)") if time_in_trade > MAX_HOLD_TIME: logger.info(f"⏰ Max hold time ({MAX_HOLD_TIME//60} min)") + debug_print(f"Max hold time exceeded, closing position") qty = current_position_qty(SYMBOL) if qty > 0: submit_market_sell(SYMBOL, qty) @@ -1502,10 +1710,10 @@ def main(): delattr(scale_out_profit_taking, 'target_1_hit') if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(atr_based_trailing_stop, 'trailing_stop') + debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") time.sleep(POLL_INTERVAL) continue - # Profit targets if scale_out_profit_taking(SYMBOL, entry_price, current_price, stop_loss, position_type): remaining_qty = current_position_qty(SYMBOL) if remaining_qty == 0: @@ -1513,14 +1721,15 @@ def main(): trade_pnl = (current_price - entry_price) * 100 total_pnl += trade_pnl logger.info(f"✅ Position closed (PnL: ${trade_pnl:.2f})") + debug_print(f"Position fully closed, PnL: ${trade_pnl:.2f}") if hasattr(scale_out_profit_taking, 'target_1_hit'): delattr(scale_out_profit_taking, 'target_1_hit') if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(atr_based_trailing_stop, 'trailing_stop') + debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") time.sleep(POLL_INTERVAL) continue - # Trailing stop if atr_based_trailing_stop(SYMBOL, entry_price, current_price, stop_loss, position_type): qty = current_position_qty(SYMBOL) if qty > 0: @@ -1528,37 +1737,36 @@ def main(): position_active = False trade_count += 1 logger.info(f"🛑 Stop hit") + debug_print("Stop hit, position closed") if hasattr(scale_out_profit_taking, 'target_1_hit'): delattr(scale_out_profit_taking, 'target_1_hit') if hasattr(atr_based_trailing_stop, 'trailing_stop'): delattr(atr_based_trailing_stop, 'trailing_stop') + debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") time.sleep(POLL_INTERVAL) continue - # Daily trade limit if trades_today >= MAX_TRADES_PER_DAY: logger.info(f"📊 Daily limit ({MAX_TRADES_PER_DAY}) - monitoring only") + debug_print(f"Daily trade limit reached ({trades_today}/{MAX_TRADES_PER_DAY})") time.sleep(POLL_INTERVAL) continue - # Generate signal signal, strength, signal_stop_loss = advanced_signal_generator(SYMBOL) - # Get regime bars = get_recent_bars(SYMBOL, 50) if bars is not None: regime = detect_market_regime(bars) else: regime = 'unknown' - # Execute trades if signal in ['buy', 'sell'] and not position_active: + debug_print(f"Signal detected: {signal}, executing trade...") buying_power = fetch_buying_power() position_size = calculate_position_size(current_equity, signal_stop_loss, current_price, regime) if buying_power >= position_size: - # Use limit orders if USE_LIMIT_ORDERS and signal == 'buy': bid, ask = get_bid_ask(SYMBOL) limit_price = bid @@ -1580,12 +1788,14 @@ def main(): logger.info(f" Entry=${entry_price:.2f}, Stop=${stop_loss:.2f}, Risk={risk_amount:.2%}") logger.info(f" Regime={regime}, Strength={strength:.2f}, Trade #{trade_count} ({trades_today}/{MAX_TRADES_PER_DAY})") - # Initialize trailing stop + debug_print(f"Trade executed: entry=${entry_price:.2f}, stop=${stop_loss:.2f}, regime={regime}") + atr_based_trailing_stop.trailing_stop = stop_loss + debug_print(f"Trailing stop initialized: ${stop_loss:.2f}") else: logger.warning(f"âš ī¸ Insufficient buying power: ${buying_power:.2f} < ${position_size:.2f}") + debug_print(f"Insufficient buying power: ${buying_power:.2f} < ${position_size:.2f}") - # Status display position_status = f"{position_type.upper()}" if position_active else "FLAT" try: current_time = clock.timestamp.strftime("%I:%M:%S %p") @@ -1601,10 +1811,11 @@ def main(): status_msg += f" | H:{hourly_trend} | VIX:{vix_level:.1f} | {trades_today}/{MAX_TRADES_PER_DAY}" logger.info(status_msg) + debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)}...") time.sleep(POLL_INTERVAL) - # End of trading day logger.info("🔚 Session ending...") + debug_print("Session ending, closing all positions...") close_all_positions() final_equity = fetch_equity() session_pnl = final_equity - opening_equity @@ -1612,12 +1823,13 @@ def main(): logger.info(f"📊 Summary: {trade_count} trades") logger.info(f"💰 Final: ${final_equity:.2f} (PNL: ${session_pnl:+.2f}, {session_pnl_pct:+.2f}%)") logger.info("✅ Day complete. Waiting for next session...") + debug_print(f"Day complete. Trades: {trade_count}, PnL: ${session_pnl:+.2f}") - # Sleep before checking again time.sleep(3600) except Exception as e: logger.error(f"đŸ’Ĩ Session error: {e}") + debug_print(f"Session error: {e}") import traceback logger.error(traceback.format_exc()) logger.info("âŗ Waiting 5 min before retry...") @@ -1625,13 +1837,16 @@ def main(): except KeyboardInterrupt: logger.info("🛑 User interrupt") + debug_print("User interrupt detected") close_all_positions() except Exception as e: logger.error(f"đŸ’Ĩ Fatal error: {e}") + debug_print(f"Fatal error: {e}") import traceback logger.error(traceback.format_exc()) finally: logger.info("🔚 Shutdown") + debug_print("Script shutdown") if __name__ == "__main__": main()