Overall Statistics
# Import necessary modules and classes from QuantConnect
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import QCAlgorithm

# Import datetime for time-related operations
import pytz

class FairValueGapStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)  # Set your backtest start date
        self.SetEndDate(2020, 12, 31)  # Set your backtest end date
        self.SetCash(100000)  # Set your starting capital
        
        # Define the symbol to trade (NASDAQ E-mini 100 futures)
        self.symbol = "NQ"  # Symbol for NASDAQ E-mini 100 futures on QuantConnect
        
        # Define strategy parameters
        self.tolerance = 0.02  # Tolerance for equal highs/lows detection
        self.risk_percentage = 0.01  # Risk 1% of total account per trade
        self.stop_loss_ratio = 0.5  # Stop loss distance as fraction of ATR
        self.take_profit_ratio = 1.0  # Take profit distance as multiple of ATR
        
        # Initialize indicators and trade parameters
        self.eqh = None
        self.eql = None
        self.entry_price = None
        self.stop_loss_price = None
        self.take_profit_price = None
        self.trade_opened = False
        
        # Schedule function to run every minute
        self.Schedule.On(self.DateRules.EveryDay(self.symbol), 
                         self.TimeRules.Every(TimeSpan.FromMinutes(1)), 
                         self.CheckTimeAndTrade)
        
        # Log initializations and strategy parameters
        self.Log(f"Starting with symbol: {self.symbol}")
        self.Log(f"Strategy parameters: tolerance={self.tolerance}, risk_percentage={self.risk_percentage}, stop_loss_ratio={self.stop_loss_ratio}, take_profit_ratio={self.take_profit_ratio}")

    def OnData(self, data):
        # Check for end of trading day at 3:31 PM EST
        if self.Time.hour == 15 and self.Time.minute == 31:
            self.Liquidate(self.symbol)
            self.trade_opened = False
            self.Log("End of trading day reached. Liquidating all positions.")
        
        # Implement your trading logic here if needed
        pass

    def CheckTimeAndTrade(self):
        # Check if current time is within trading hours (9:32 AM EST to 3:31 PM EST)
        if not self.IsWithinTradingHours():
            return
        
        # Execute trading logic if no trade is currently open
        if not self.trade_opened:
            self.CheckTrade()

    def IsWithinTradingHours(self):
        # Convert current time to New York (Eastern Time)
        new_york_tz = pytz.timezone('America/New_York')
        eastern_time = self.Time.astimezone(new_york_tz)
        
        # Extract hour and minute components
        hour = eastern_time.hour
        minute = eastern_time.minute
        
        # Check if current time is within specified trading hours (9:32 AM EST to 3:31 PM EST)
        if hour < 9 or (hour == 15 and minute > 31):
            return False
        elif hour == 9 and minute < 32:
            return False
        return True

    def CheckTrade(self):
        # Fetch historical data to analyze (15 minute timeframe)
        history_15m = self.History(self.symbol, TimeSpan.FromMinutes(15), Resolution.Minute)
        
        # Convert history_15m to a list to iterate over it
        history_15m_list = list(history_15m)
        
        # Check if history_15m_list is empty
        if len(history_15m_list) == 0:
            self.Log("No historical data available for 15 minute bars.")
            return
        
        # Detect equal highs (EQH) and equal lows (EQL) within the last 15 minutes
        self.eqh, self.eql = self.DetectEqualHighsLows(history_15m_list)

        if self.eqh is not None:
            # Bullish scenario: FVG between EQH and current low
            fvg_bullish = self.CalculateFairValueGap(self.eqh[1], history_15m_list[-1].Low)
            self.Log(f"Bullish FVG: {fvg_bullish}")
            if fvg_bullish > 0:
                # Enter bullish trade
                self.EnterTrade(self.eqh[1], history_15m_list[-1].Low, "bullish")
                return  # Exit function after entering trade

        if self.eql is not None:
            # Bearish scenario: FVG between EQL and current high
            fvg_bearish = self.CalculateFairValueGap(self.eql[1], history_15m_list[-1].High)
            self.Log(f"Bearish FVG: {fvg_bearish}")
            if fvg_bearish > 0:
                # Enter bearish trade
                self.EnterTrade(self.eql[1], history_15m_list[-1].High, "bearish")
                return  # Exit function after entering trade

    def DetectEqualHighsLows(self, history):
        eqh = None
        eql = None
        
        for i in range(1, len(history)):
            if abs(history[i].High - history[i - 1].High) < self.tolerance:
                eqh = history[i].Time, history[i].High
            if abs(history[i].Low - history[i - 1].Low) < self.tolerance:
                eql = history[i].Time, history[i].Low
        
        return eqh, eql

    def CalculateFairValueGap(self, reference_price, current_price):
        return current_price - reference_price

    def EnterTrade(self, entry_price, reference_price, direction):
        self.entry_price = entry_price
        
        stop_loss_distance = abs(entry_price - reference_price)
        take_profit_distance = abs(reference_price - entry_price)
        
        self.stop_loss_price = entry_price - stop_loss_distance * self.stop_loss_ratio
        self.take_profit_price = entry_price + take_profit_distance * self.take_profit_ratio
        
        risk_amount = self.Portfolio.TotalPortfolioValue * self.risk_percentage
        position_size = int(risk_amount / stop_loss_distance)
        
        if direction == "bullish":
            self.SetHoldings(self.symbol, position_size)
        elif direction == "bearish":
            self.SetHoldings(self.symbol, -position_size)
        
        self.trade_opened = True
        self.Log(f"Trade entered. Entry={self.entry_price}, StopLoss={self.stop_loss_price}, TakeProfit={self.take_profit_price}, Direction={direction}")

# End of FairValueGapStrategy class