From fe28c82379c37b81e9cee97255533fd6e9978c4d Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Sun, 8 Feb 2026 13:23:21 -0700 Subject: [PATCH] feat(strategy): add Opening Range + Fair Value Gap (OR-FVG) execution logic --- .gitignore | 0 README.md | 336 +++++++++++++++++++++++++++------------- alpaca_trader/engine.py | 296 ++++++++++++++++++++++++++++++++++- run.py | 0 4 files changed, 519 insertions(+), 113 deletions(-) mode change 100644 => 100755 .gitignore mode change 100755 => 100644 run.py diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/README.md b/README.md index 7207ab4..b381341 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,265 @@ # Alpaca Trader -Automated day trading bot for Alpaca Markets using technical indicators and risk management. +Advanced algorithmic trading bot built for Alpaca Markets. -## Features +Designed for automated strategy execution, multi-layer technical analysis, and research-driven trading experimentation. -- Multiple technical indicators (SMA/EMA, RSI, ADX, ATR, MACD, Bollinger Bands) -- Advanced risk management with trailing stops and profit targets -- Market regime detection (trend/range/high vol/low vol) -- Multi-timeframe confluence analysis -- Position sizing based on account equity and risk per trade -- Support for both long and short positions -- Configurable via JSON config file -- Comprehensive debug logging +Supports configurable strategies, risk management automation, and detailed performance logging. -## Requirements +--- -- Python 3.8+ -- Alpaca Markets account (paper or live) +## 🚀 Quick Start -## Installation +Clone and install: ```bash +git clone https://github.com/YOUR_REPO/alpaca-trader.git +cd alpaca-trader pip install -r requirements.txt ``` -## Configuration - -1. Create `.env` file in `alpaca_trader/` directory: -``` -APCA_API_KEY_ID="your_api_key" -APCA_API_SECRET_KEY="your_secret_key" -APCA_API_BASE_URL="https://paper-api.alpaca.markets" -``` - -2. Modify `config.json` to adjust trading parameters: -- `SYMBOL`: Stock to trade (default: SPY) -- `RISK_PER_TRADE`: Risk per trade as % of equity (default: 0.01) -- `MAX_TRADES_PER_DAY`: Daily trade limit (default: 5) -- `ENABLE_SHORT_SELLING`: Enable/disable short positions (default: true) -- `MIN_SIGNAL_STRENGTH`: Minimum signal strength to enter trades (default: 0.3) - -## Usage +Run: ```bash python3 run.py ``` -Or: +On first launch, the bot will create: -```bash -python3 -m alpaca_trader +``` +alpaca_trader/.env ``` -## Key Parameters +Add your Alpaca API keys: -### General Settings -- **DEBUG_MODE**: Enable detailed debug logging (default: true) -- **BAR_TIMEFRAME**: Candlestick timeframe for analysis (default: "5Min") -- **POLL_INTERVAL**: Seconds between market checks (default: 60) -- **MIN_NOTIONAL**: Minimum position size in dollars (default: 1.0) -- **PDT_RULE**: Enforce pattern day trader rules (default: true) +``` +APCA_API_KEY_ID="your_key" +APCA_API_SECRET_KEY="your_secret" +APCA_API_BASE_URL="https://paper-api.alpaca.markets" +``` -### Entry/Exit Signals -- **SHORT_WINDOW**: Fast moving average period (default: 10) -- **LONG_WINDOW**: Slow moving average period (default: 30) -- **USE_EMA**: Use EMA instead of SMA (default: true) -- **REQUIRE_MA_CROSSOVER**: Require recent MA crossover for signals (default: true) -- **CROSSOVER_LOOKBACK**: Bars to look back for crossovers (default: 5) -- **RSI_BUY_MAX**: Maximum RSI for buy signals in trend (default: 55) -- **RSI_SELL_MIN**: Minimum RSI for sell signals in trend (default: 45) -- **RSI_SELL_MAX**: Maximum RSI for sell signals in trend (default: 70) -- **RSI_RANGE_OVERSOLD**: RSI threshold for range-bound buy (default: 30) -- **RSI_RANGE_OVERBOUGHT**: RSI threshold for range-bound sell (default: 70) -- **ADX_THRESHOLD**: Minimum ADX for trend detection (default: 25) -- **MIN_SIGNAL_STRENGTH**: Minimum signal strength threshold (default: 0.3) +--- -### Risk Management -- **ATR_STOP_MULTIPLIER**: Stop loss distance in ATR units (default: 2.0) -- **USE_TRAILING_STOP**: Enable trailing stop loss (default: true) -- **PROFIT_TARGET_1**: First profit target in R (default: 2.0) -- **PROFIT_TARGET_2**: Second profit target in R (default: 4.0) -- **MAX_DRAWDOWN**: Maximum account drawdown threshold (default: 0.08) -- **MAX_HOLD_TIME**: Maximum position hold time in seconds (default: 3600) -- **MIN_RISK_REWARD**: Minimum risk/reward ratio required (default: 2.0) -- **VOLATILITY_ADJUSTMENT**: Adjust position size based on volatility (default: true) +## 🎯 Features + +### Core Trading Engine + +- Automated signal evaluation loop +- Multi-strategy architecture +- Risk-aware position sizing +- Market regime detection +- Config-driven behavior (no code changes required) ### Technical Indicators -- **BB_WINDOW**: Bollinger Bands period (default: 20) -- **BB_STD**: Bollinger Bands standard deviation (default: 2.0) -- **REQUIRE_CANDLE_PATTERN**: Require bullish/bearish candle patterns (default: false) -- **REQUIRE_MACD_CONFIRMATION**: Require MACD crossover confirmation (default: false) + +- SMA / EMA +- RSI +- MACD +- ADX +- ATR +- Bollinger Bands +- Multi-timeframe signal confirmation + +### Strategy System + +Supports multiple strategy modes: + +- Moving Average crossover (default) +- Opening Range + Fair Value Gap (OR/FVG) +- Regime-filtered execution + +### Risk Management + +- ATR-based stop loss +- Trailing stop logic +- Multi-level take profits +- Risk-per-trade sizing +- Max drawdown protection +- Risk/reward validation +- Position hold-time limits + +### Execution Controls + +- Market or limit orders +- Slippage simulation +- Commission modeling +- Cash account compatibility +- T+1 settlement handling +- PDT rule awareness + +### Market Filters + +- Market regime classification +- Volume filters +- 200 SMA trend filter +- VIX volatility filter +- Candle confirmation +- MACD confirmation layer + +### Analytics & Logging + +Automatically generates: + +``` +logs/ +├── trading.log +├── debug.log + +data/ +├── trades.csv +├── signals.csv +├── performance.csv +├── indicators.csv +├── session.csv +``` + +--- + +## 🧠 Strategy Overview + +### Moving Average Strategy + +Primary signal generated when: + +- Short MA crosses long MA +- Trend filters confirm +- Risk/reward meets threshold +- Market regime supports trade + +Optional confirmation: + +- MACD alignment +- RSI thresholds +- Volume confirmation + +--- + +### Opening Range + Fair Value Gap Strategy + +Designed for intraday momentum: + +1. Detect opening range window. +2. Identify Fair Value Gap structures. +3. Validate volume and direction. +4. Execute with ATR-based risk controls. + +Configurable parameters: + +- Opening range duration +- Minimum gap size +- Entry timeframe +- Risk/reward target +- Maximum entry window + +--- + +## ⚙️ Configuration + +All trading behavior controlled via: + +``` +alpaca_trader/config.json +``` + +Key sections: + +### Strategy + +``` +STRATEGY_MODE +OR_FVG_ENABLED +OR_FVG_OPENING_RANGE_MINUTES +OR_FVG_MIN_GAP_SIZE +``` + +### Risk + +``` +RISK_PER_TRADE +ATR_STOP_MULTIPLIER +MAX_DRAWDOWN +MIN_RISK_REWARD +``` ### Filters -- **REGIME_DETECTION**: Enable market regime detection (default: true) -- **MULTIFRAME_FILTER**: Enable hourly timeframe confirmation (default: false) -- **USE_VIX_FILTER**: Filter trades based on VIX (default: false) -- **VIX_THRESHOLD**: Maximum VIX level to allow trades (default: 30) -- **USE_200_SMA_FILTER**: Filter based on 200-day SMA (default: false) -- **VOLUME_MULTIPLIER**: Minimum volume as multiple of average (default: 0.5) -- **MARKET_HOURS_FILTER**: Only trade during specific hours (default: false) -- **SKIP_MONDAYS_FRIDAYS**: Skip trading on Mondays and Fridays (default: false) -### Order Execution -- **USE_LIMIT_ORDERS**: Use limit orders instead of market orders (default: false) -- **LIMIT_ORDER_TIMEOUT**: Seconds to wait for limit order fill (default: 60) -- **ENABLE_SLIPPAGE**: Account for slippage in backtesting (default: true) -- **SLIPPAGE_PCT**: Estimated slippage percentage (default: 0.0005) -- **COMMISSION_PCT**: Commission percentage per trade (default: 0.0005) +``` +REGIME_DETECTION +USE_200_SMA_FILTER +USE_VIX_FILTER +MULTIFRAME_FILTER +``` -### Backtesting -- **BACKTEST_DAYS**: Days of historical data for backtesting (default: 90) +### Execution -### Advanced Features -- **USE_PIVOT_POINTS**: Use pivot point analysis (default: false) -- **USE_FIBONACCI**: Use Fibonacci retracement levels (default: false) -- **PULLBACK_PERCENTAGE**: Fibonacci pullback level (default: 0.382) +``` +USE_LIMIT_ORDERS +LIMIT_ORDER_TIMEOUT +SLIPPAGE_PCT +COMMISSION_PCT +``` -## Logging +--- -- `trading.log`: Main trading activity log -- `debug.log`: Detailed debug information (when DEBUG_MODE is enabled) - -## Architecture +## 🏗 Architecture ``` alpaca_trader/ -├── __init__.py # Package initialization -├── __main__.py # Module entry point -├── api.py # Alpaca API wrapper with retry logic -├── engine.py # Main trading engine -├── indicators.py # Technical indicator calculations -├── filters.py # Market filters and regime detection -├── risk.py # Risk management data structures -├── utils.py # Utility functions -├── config.json # Configuration parameters -└── .env # API credentials (create this) +├── api.py # Alpaca API interface +├── engine.py # Core trading loop +├── indicators.py # Technical analysis +├── filters.py # Market condition filters +├── risk.py # Risk & position sizing +├── utils.py # Helpers +├── cli.py # CLI interface +├── config.json # Main configuration ``` -## Warning +--- -**DISCLAIMER: This software is provided for educational and research purposes only. The author is not responsible for any financial losses, damages, or liabilities incurred from using this trading bot. Use at your own risk.** +## 🔄 How It Works -- Test thoroughly in paper trading before using real capital -- Past performance does not guarantee future results -- Trading involves substantial risk of loss -- Never trade with money you cannot afford to lose -- This is NOT financial advice -- Consult a licensed financial advisor before making investment decisions +1. Load configuration and API credentials +2. Fetch historical market data +3. Calculate indicators +4. Evaluate market regime +5. Generate trading signals +6. Validate risk constraints +7. Execute trades via Alpaca API +8. Log analytics data + +--- + +## 📊 Design Philosophy + +- Config-first architecture +- Strategy isolation +- Risk before execution +- Modular extensibility +- Research-friendly logging + +--- + +## ⚠️ Important Notes + +- Use paper trading first. +- Algorithmic trading involves financial risk. +- No strategy guarantees profit. + +--- + +## 🛠 Roadmap (Example) + +- Strategy plug-in system +- ML signal scoring +- Portfolio-level risk controls +- Multi-symbol scanning +- Performance dashboard + +--- + +## Disclaimer + +This software is provided for educational and research purposes only. + +Not financial advice. diff --git a/alpaca_trader/engine.py b/alpaca_trader/engine.py index 5ab2c59..848fde2 100644 --- a/alpaca_trader/engine.py +++ b/alpaca_trader/engine.py @@ -110,7 +110,15 @@ DEFAULT_CONFIG = { "CROSSOVER_LOOKBACK": 3, "REQUIRE_CASH_ACCOUNT": False, "T1_SETTLEMENT_ENABLED": False, - "CASH_RESERVE_PCT": 0.0 + "CASH_RESERVE_PCT": 0.0, + "STRATEGY_MODE": "ma_crossover", + "OR_FVG_ENABLED": False, + "OR_FVG_OPENING_RANGE_MINUTES": 15, + "OR_FVG_ENTRY_TIMEFRAME": "3Min", + "OR_FVG_MIN_GAP_SIZE": 0.05, + "OR_FVG_RISK_REWARD_RATIO": 2.0, + "OR_FVG_MAX_ENTRY_TIME": "10:30", + "OR_FVG_REQUIRE_VOLUME_CONFIRM": True } if not ENV_PATH.exists(): @@ -155,6 +163,15 @@ RISK_PER_TRADE = float(config["RISK_PER_TRADE"]) SHORT_WINDOW = int(config["SHORT_WINDOW"]) LONG_WINDOW = int(config["LONG_WINDOW"]) +STRATEGY_MODE = config.get("STRATEGY_MODE", "ma_crossover") +OR_FVG_ENABLED = bool(config.get("OR_FVG_ENABLED", False)) +OR_FVG_OPENING_RANGE_MINUTES = int(config.get("OR_FVG_OPENING_RANGE_MINUTES", 15)) +OR_FVG_ENTRY_TIMEFRAME = config.get("OR_FVG_ENTRY_TIMEFRAME", "3Min") +OR_FVG_MIN_GAP_SIZE = float(config.get("OR_FVG_MIN_GAP_SIZE", 0.05)) +OR_FVG_RISK_REWARD_RATIO = float(config.get("OR_FVG_RISK_REWARD_RATIO", 2.0)) +OR_FVG_MAX_ENTRY_TIME = config.get("OR_FVG_MAX_ENTRY_TIME", "10:30") +OR_FVG_REQUIRE_VOLUME_CONFIRM = bool(config.get("OR_FVG_REQUIRE_VOLUME_CONFIRM", True)) + if SHORT_WINDOW >= LONG_WINDOW: logger.error(f"⚠️ Configuration error: SHORT_WINDOW ({SHORT_WINDOW}) must be less than LONG_WINDOW ({LONG_WINDOW})") sys.exit(1) @@ -791,6 +808,189 @@ def calculate_position_size(equity, stop_loss, current_price): if position_value > max_position: position_value = max_position debug_print(f"Position capped at 25% equity: ${position_value:.2f}") + +class ORFVGState: + def __init__(self): + self.opening_range_high = None + self.opening_range_low = None + self.opening_range_set = False + self.fvg_detected = False + self.fvg_direction = None + self.fvg_candle_index = None + self.entry_triggered = False + + def reset(self): + self.opening_range_high = None + self.opening_range_low = None + self.opening_range_set = False + self.fvg_detected = False + self.fvg_direction = None + self.fvg_candle_index = None + self.entry_triggered = False + +or_fvg_state = ORFVGState() + +def detect_fair_value_gap(bars, min_gap_pct=0.05): + if bars is None or len(bars) < 3: + return None, None + + for i in range(len(bars) - 3, max(len(bars) - 10, 0) - 1, -1): + if i < 0 or i + 2 >= len(bars): + continue + + candle_1_high = bars['high'].iloc[i] + candle_1_low = bars['low'].iloc[i] + candle_2_high = bars['high'].iloc[i + 1] + candle_2_low = bars['low'].iloc[i + 1] + candle_3_high = bars['high'].iloc[i + 2] + candle_3_low = bars['low'].iloc[i + 2] + + bullish_gap = candle_3_low > candle_1_high + if bullish_gap: + gap_size = candle_3_low - candle_1_high + if candle_2_high > 0: + gap_pct = (gap_size / candle_2_high) * 100 + if gap_pct >= min_gap_pct: + debug_print(f"Bullish FVG detected: gap={gap_size:.2f} ({gap_pct:.2f}%)") + return "bullish", i + 2 + + bearish_gap = candle_3_high < candle_1_low + if bearish_gap: + gap_size = candle_1_low - candle_3_high + if candle_2_low > 0: + gap_pct = (gap_size / candle_2_low) * 100 + if gap_pct >= min_gap_pct: + debug_print(f"Bearish FVG detected: gap={gap_size:.2f} ({gap_pct:.2f}%)") + return "bearish", i + 2 + + return None, None + +def or_fvg_signal_generator(symbol): + debug_print("Checking OR-FVG strategy") + + now = datetime.now(EASTERN) + market_open = now.replace(hour=9, minute=30, second=0, microsecond=0) + opening_range_end = market_open + timedelta(minutes=OR_FVG_OPENING_RANGE_MINUTES) + + max_entry_time_parts = OR_FVG_MAX_ENTRY_TIME.split(":") + max_entry_time = now.replace( + hour=int(max_entry_time_parts[0]), + minute=int(max_entry_time_parts[1]), + second=0, + microsecond=0 + ) + + if now > max_entry_time: + debug_print(f"Past max entry time ({OR_FVG_MAX_ENTRY_TIME})") + return None, 0, 0, None + + if not or_fvg_state.opening_range_set and now >= opening_range_end: + start_time = market_open + end_time = opening_range_end + + bars_or = api.get_bars( + symbol, + "1Min", + start=start_time.isoformat(), + end=end_time.isoformat(), + limit=OR_FVG_OPENING_RANGE_MINUTES + ) + + if bars_or is not None and len(bars_or) > 0: + or_fvg_state.opening_range_high = bars_or['high'].max() + or_fvg_state.opening_range_low = bars_or['low'].min() + + if (pd.isna(or_fvg_state.opening_range_high) or + pd.isna(or_fvg_state.opening_range_low) or + or_fvg_state.opening_range_high <= 0 or + or_fvg_state.opening_range_low <= 0 or + or_fvg_state.opening_range_low >= or_fvg_state.opening_range_high): + logger.error(f"❌ Invalid opening range: High={or_fvg_state.opening_range_high}, Low={or_fvg_state.opening_range_low}") + debug_print("Invalid opening range values detected") + return None, 0, 0, None + + or_fvg_state.opening_range_set = True + logger.info(f"📊 Opening Range set: High=${or_fvg_state.opening_range_high:.2f}, Low=${or_fvg_state.opening_range_low:.2f}") + debug_print(f"OR set: H={or_fvg_state.opening_range_high:.2f}, L={or_fvg_state.opening_range_low:.2f}") + + if not or_fvg_state.opening_range_set: + debug_print("Opening range not yet set") + return None, 0, 0, None + + bars_1min = api.get_bars(symbol, OR_FVG_ENTRY_TIMEFRAME, limit=50) + if bars_1min is None or len(bars_1min) == 0: + debug_print("No 1-min bars available") + return None, 0, 0, None + + bars_df = bars_1min.reset_index() + + bars_after_or = bars_df[bars_df['timestamp'] >= opening_range_end] + if len(bars_after_or) < 3: + debug_print("Not enough bars after opening range") + return None, 0, 0, None + + current_price = bars_after_or['close'].iloc[-1] + + if not or_fvg_state.fvg_detected: + fvg_direction, fvg_index = detect_fair_value_gap(bars_after_or, OR_FVG_MIN_GAP_SIZE) + + if fvg_direction: + or_fvg_state.fvg_detected = True + or_fvg_state.fvg_direction = fvg_direction + or_fvg_state.fvg_candle_index = fvg_index + logger.info(f"🎯 FVG detected: {fvg_direction.upper()}") + debug_print(f"FVG set: direction={fvg_direction}") + + if not or_fvg_state.fvg_detected: + debug_print("No FVG detected yet") + return None, 0, 0, None + + if or_fvg_state.entry_triggered: + debug_print("Entry already triggered today") + return None, 0, 0, None + + breakout_detected = False + position_type = None + + if or_fvg_state.fvg_direction == "bullish": + if current_price > or_fvg_state.opening_range_high: + breakout_detected = True + position_type = "long" + debug_print(f"Bullish breakout: ${current_price:.2f} > ${or_fvg_state.opening_range_high:.2f}") + elif or_fvg_state.fvg_direction == "bearish": + if current_price < or_fvg_state.opening_range_low: + breakout_detected = True + position_type = "short" + debug_print(f"Bearish breakout: ${current_price:.2f} < ${or_fvg_state.opening_range_low:.2f}") + + if not breakout_detected: + debug_print("No breakout detected") + return None, 0, 0, None + + if OR_FVG_REQUIRE_VOLUME_CONFIRM: + if len(bars_after_or) >= 20: + avg_volume = bars_after_or['volume'].rolling(window=20).mean().iloc[-1] + current_volume = bars_after_or['volume'].iloc[-1] + if current_volume < avg_volume * 1.2: + debug_print(f"Volume confirmation failed: {current_volume:.0f} < {avg_volume*1.2:.0f}") + return None, 0, 0, None + else: + debug_print(f"Volume confirmation skipped: only {len(bars_after_or)} bars available (need 20)") + + if position_type == "long": + stop_loss = or_fvg_state.opening_range_low + signal = "buy" + else: + stop_loss = or_fvg_state.opening_range_high + signal = "sell" + + strength = 1.0 + + logger.info(f"✅ OR-FVG Entry: {signal.upper()} @ ${current_price:.2f}, Stop=${stop_loss:.2f}") + debug_print(f"OR-FVG signal generated: {signal}, stop={stop_loss:.2f}") + + return signal, strength, stop_loss, position_type + if position_value < MIN_NOTIONAL: position_value = MIN_NOTIONAL debug_print(f"Position set to minimum: ${position_value:.2f}") @@ -960,6 +1160,23 @@ def scale_out_profit_taking(symbol, entry_price, current_price, stop_loss, posit risk_pct = abs((entry_price - stop_loss) / entry_price) * 100 + if STRATEGY_MODE == "or_fvg" or OR_FVG_ENABLED: + target_pct = risk_pct * OR_FVG_RISK_REWARD_RATIO + + if profit_pct >= target_pct: + qty = current_position_qty(symbol) + if qty != 0: + debug_print(f"OR-FVG target hit ({target_pct:.2f}%), closing {qty} shares") + exit_price = None + if position_type == 'long': + exit_price = submit_market_sell(symbol, qty) + else: + exit_price = submit_buy_to_cover(symbol, qty) + logger.info(f"💰 OR-FVG Target @ {profit_pct:.2f}%") + debug_print(f"OR-FVG profit target hit: closed @ {profit_pct:.2f}%") + return True, exit_price if exit_price else current_price + return False, None + target_1_pct = risk_pct * PROFIT_TARGET_1 target_2_pct = risk_pct * PROFIT_TARGET_2 @@ -1079,6 +1296,7 @@ def main(): signal_state.reset() position_state.reset() + or_fvg_state.reset() restored_state = load_session_state() if restored_state: @@ -1289,7 +1507,72 @@ def main(): time.sleep(POLL_INTERVAL) continue - if atr_based_trailing_stop(SYMBOL, entry_price, current_price, stop_loss, position_type): + if STRATEGY_MODE == "or_fvg" or OR_FVG_ENABLED: + stop_hit = False + if position_type == 'long' and current_price <= stop_loss: + stop_hit = True + debug_print(f"OR-FVG long stop hit: ${current_price:.2f} <= ${stop_loss:.2f}") + elif position_type == 'short' and current_price >= stop_loss: + stop_hit = True + debug_print(f"OR-FVG short stop hit: ${current_price:.2f} >= ${stop_loss:.2f}") + + if stop_hit: + qty = current_position_qty(SYMBOL) + if qty != 0: + exit_time = datetime.now(EASTERN) + hold_minutes = (exit_time - entry_time).total_seconds() / 60 if entry_time else 0 + + if position_type == 'long': + exit_price = submit_market_sell(SYMBOL, qty) + pnl_dollars = (exit_price - entry_price) * qty if exit_price else 0 + else: + exit_price = submit_buy_to_cover(SYMBOL, abs(qty)) + pnl_dollars = (entry_price - exit_price) * abs(qty) if exit_price else 0 + + pnl_percent = (pnl_dollars / (entry_price * abs(qty)) * 100) if entry_price > 0 and qty != 0 else 0 + + if pnl_dollars > 0: + winners += 1 + elif pnl_dollars < 0: + losers += 1 + + risk_pct = abs((entry_price - stop_loss) / entry_price) if entry_price > 0 else 0 + target_1 = entry_price + (entry_price - stop_loss) * PROFIT_TARGET_1 if position_type == 'long' else entry_price - (stop_loss - entry_price) * PROFIT_TARGET_1 + target_2 = entry_price + (entry_price - stop_loss) * PROFIT_TARGET_2 if position_type == 'long' else entry_price - (stop_loss - entry_price) * PROFIT_TARGET_2 + + log_trade( + entry_time, + exit_time, + SYMBOL, + position_type, + entry_price, + exit_price if exit_price else current_price, + abs(qty), + entry_price * abs(qty), + stop_loss, + target_1, + target_2, + pnl_dollars, + pnl_percent, + hold_minutes, + 'stop_hit', + entry_regime, + entry_strength, + entry_rsi, + entry_adx, + entry_ma_spread, + 0 + ) + + position_active = False + trade_count += 1 + logger.info("🛑 Stop hit") + debug_print("Stop hit, position closed") + position_state.reset() + debug_print(f"Sleeping {seconds_to_human_readable(POLL_INTERVAL)} after exit") + time.sleep(POLL_INTERVAL) + continue + elif atr_based_trailing_stop(SYMBOL, entry_price, current_price, stop_loss, position_type): qty = current_position_qty(SYMBOL) if qty != 0: exit_time = datetime.now(EASTERN) @@ -1346,7 +1629,10 @@ def main(): time.sleep(POLL_INTERVAL) continue - signal, strength, signal_stop_loss, signal_position_type = advanced_signal_generator(SYMBOL) + if STRATEGY_MODE == "or_fvg" or OR_FVG_ENABLED: + signal, strength, signal_stop_loss, signal_position_type = or_fvg_signal_generator(SYMBOL) + else: + signal, strength, signal_stop_loss, signal_position_type = advanced_signal_generator(SYMBOL) bars_for_signal = get_recent_bars(SYMBOL, 50) signal_rsi = 0 @@ -1438,6 +1724,10 @@ def main(): logger.info(f" Regime={regime}, Strength={strength:.2f}, Trade {trade_count} ({trades_today}/{MAX_TRADES_PER_DAY})") debug_print(f"Trade executed: entry=${entry_price:.2f}, stop=${stop_loss:.2f}, regime={regime}") + if STRATEGY_MODE == "or_fvg" or OR_FVG_ENABLED: + or_fvg_state.entry_triggered = True + debug_print("OR-FVG entry_triggered flag set") + position_state.trailing_stop = stop_loss debug_print(f"Trailing stop initialized: ${stop_loss:.2f}") else: diff --git a/run.py b/run.py old mode 100755 new mode 100644