Overall Statistics
Total Orders
79
Average Win
8.96%
Average Loss
-1.73%
Compounding Annual Return
17.960%
Drawdown
17.800%
Expectancy
0.335
Start Equity
2000.00
End Equity
2360.26
Net Profit
18.013%
Sharpe Ratio
0.624
Sortino Ratio
0.644
Probabilistic Sharpe Ratio
31.793%
Loss Rate
78%
Win Rate
22%
Profit-Loss Ratio
5.17
Alpha
0.145
Beta
0.052
Annual Standard Deviation
0.23
Annual Variance
0.053
Information Ratio
0.692
Tracking Error
0.246
Treynor Ratio
2.774
Total Fees
$0.00
Estimated Strategy Capacity
$14000000.00
Lowest Capacity Asset
GBPNZD 8G
Portfolio Turnover
151.43%
from AlgorithmImports import *

class ForexPairTradingAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2011, 1, 1)
        self.SetEndDate(2012, 1, 1)
        self.SetCash(2000)
        
        # Set Brokerage to OANDA
        self.SetBrokerageModel(BrokerageName.OandaBrokerage)
        
        # Add Forex pairs
        self.pairs = ["GBPNZD", "GBPAUD", "GBPUSD", "GBPCAD", "USDCAD"]
        self.symbols = [self.AddForex(pair, Resolution.Minute).Symbol for pair in self.pairs]

        # Configurable timeframes
        self.trend_timeframe = Resolution.Hour
        self.trend_period = 1
        
        self.entry_timeframe = Resolution.Minute
        self.entry_period = 5
        
        # Indicators
        self.ema20 = {symbol: self.EMA(symbol, 20, self.trend_timeframe) for symbol in self.symbols}
        self.ema50 = {symbol: self.EMA(symbol, 50, self.trend_timeframe) for symbol in self.symbols}
        self.ema200 = {symbol: self.EMA(symbol, 200, self.trend_timeframe) for symbol in self.symbols}
        self.rsi = {symbol: self.RSI(symbol, 14, MovingAverageType.Wilders, self.trend_timeframe) for symbol in self.symbols}
        self.atr = {symbol: self.ATR(symbol, 14, self.entry_timeframe) for symbol in self.symbols}
        
        # Variables to manage positions and cooldowns
        self.current_position = None
        self.last_exit_time = {symbol: datetime.min for symbol in self.symbols}
        self.cooldown_period = timedelta(hours=0)

        # Schedule function to screen pairs every hour
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(hours=1)), self.ScreenPairs)

    def ScreenPairs(self):
        if self.current_position:
            return  # Only one position at a time

        scores = {}
        for symbol in self.symbols:
            if self.ema200[symbol].IsReady and self.ema50[symbol].IsReady and self.ema20[symbol].IsReady and self.rsi[symbol].IsReady:
                if self.ema20[symbol].Current.Value > self.ema50[symbol].Current.Value > self.ema200[symbol].Current.Value and self.rsi[symbol].Current.Value > 50:
                    distance_20_50 = self.ema20[symbol].Current.Value - self.ema50[symbol].Current.Value
                    distance_20_200 = self.ema20[symbol].Current.Value - self.ema200[symbol].Current.Value
                    ema_score = distance_20_50 + distance_20_200
                    scores[symbol] = ema_score
        
        if scores:
            best_long = max(scores, key=scores.get)
            best_short = min(scores, key=scores.get)
            
            if scores[best_long] > 0:
                self.EnterPosition(best_long, "long", scores[best_long])
            elif scores[best_short] < 0:
                self.EnterPosition(best_short, "short", scores[best_short])

    def EnterPosition(self, symbol, direction, ema_score):
        history = self.History(symbol, 2, self.entry_timeframe)
        if len(history) < 2:
            return

        recent_data = history.iloc[-1]
        previous_data = history.iloc[-2]

        # Use standard lot size for OANDA (10,000 units)
        lot_size = 10000

        atr_value = self.atr[symbol].Current.Value
        stop_loss_pips = 1.5 * atr_value * 1000
        target_price_pips = 2 * stop_loss_pips

        if direction == "long":
            if self.MeetsLongConditions(symbol, recent_data, previous_data):
                self.MarketOrder(symbol, lot_size)
                self.current_position = symbol
                stop_price = recent_data.close - stop_loss_pips * 0.0001
                take_profit_price = recent_data.close + target_price_pips * 0.0001
                self.StopMarketOrder(symbol, -lot_size, stop_price)
                self.LimitOrder(symbol, -lot_size, take_profit_price)
                self.Debug(f"Entering long position on {symbol} at {self.Time} with EMA score: {ema_score}, filled price: {recent_data.close}, stop price: {stop_price}, take profit price: {take_profit_price}")
        elif direction == "short":
            if self.MeetsShortConditions(symbol, recent_data, previous_data):
                self.MarketOrder(symbol, -lot_size)
                self.current_position = symbol
                stop_price = recent_data.close + stop_loss_pips * 0.0001
                take_profit_price = recent_data.close - target_price_pips * 0.0001
                self.StopMarketOrder(symbol, lot_size, stop_price)
                self.LimitOrder(symbol, lot_size, take_profit_price)
                self.Debug(f"Entering short position on {symbol} at {self.Time} with EMA score: {ema_score}, filled price: {recent_data.close}, stop price: {stop_price}, take profit price: {take_profit_price}")

    def MeetsLongConditions(self, symbol, recent_data, previous_data):
        # Trend Confirmation
        uptrend = self.ema20[symbol].Current.Value > self.ema50[symbol].Current.Value > self.ema200[symbol].Current.Value and self.rsi[symbol].Current.Value > 50
        bullish_trend_bar = recent_data.close > recent_data.open and recent_data.close > previous_data.close

        # Pullbacks
        pullback = recent_data.low < previous_data.low

        # Breakouts
        history = self.History(symbol, 50, self.entry_timeframe)
        breakout = recent_data.close > max(history["high"])

        # Support Levels and Bullish Reversal Patterns
        near_support = recent_data.close > min(history["low"])
        bullish_reversal = self.IsBullishReversal(recent_data) or self.IsTwoBarBullish(previous_data, recent_data)

        return uptrend and (bullish_trend_bar or pullback or breakout or near_support or bullish_reversal)

    def MeetsShortConditions(self, symbol, recent_data, previous_data):
        # Trend Confirmation
        downtrend = self.ema20[symbol].Current.Value < self.ema50[symbol].Current.Value < self.ema200[symbol].Current.Value and self.rsi[symbol].Current.Value < 50
        bearish_trend_bar = recent_data.close < recent_data.open and recent_data.close < previous_data.close

        # Pullbacks
        pullback = recent_data.high > previous_data.high

        # Breakouts
        history = self.History(symbol, 50, self.entry_timeframe)
        breakout = recent_data.close < min(history["low"])

        # Resistance Levels and Bearish Reversal Patterns
        near_resistance = recent_data.close < max(history["high"])
        bearish_reversal = self.IsBearishReversal(recent_data) or self.IsTwoBarBearish(previous_data, recent_data)

        return downtrend and (bearish_trend_bar or pullback or breakout or near_resistance or bearish_reversal)

    def IsBullishReversal(self, recent_data):
        return (recent_data.close > recent_data.open) and ((recent_data.close - recent_data.open) / (recent_data.high - recent_data.low) > 0.5)

    def IsTwoBarBullish(self, previous_data, recent_data):
        return (previous_data.close < previous_data.open) and (recent_data.close > (previous_data.open + previous_data.close) / 2)

    def IsBearishReversal(self, recent_data):
        return (recent_data.close < recent_data.open) and ((recent_data.open - recent_data.close) / (recent_data.high - recent_data.low) > 0.5)

    def IsTwoBarBearish(self, previous_data, recent_data):
        return (previous_data.close > previous_data.open) and (recent_data.close < (previous_data.open + previous_data.close) / 2)

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            self.Debug(f"Order filled: {orderEvent.Symbol} {orderEvent.Direction} {orderEvent.FillQuantity} units at {orderEvent.FillPrice}")

    def OnData(self, data):
        if self.current_position and self.Portfolio[self.current_position].Invested:
            symbol = self.current_position
            holding = self.Portfolio[symbol]
            if holding.UnrealizedProfitPercent > 0.01 or holding.UnrealizedProfitPercent < -0.005:
                self.Liquidate(symbol)
                self.last_exit_time[symbol] = self.Time
                self.current_position = None
                self.Debug(f"Exiting position on {symbol} at {self.Time} due to {'profit target' if holding.UnrealizedProfitPercent > 0.01 else 'stop loss'}")

        for symbol in self.symbols:
            if not self.Portfolio[symbol].Invested and self.Time < self.last_exit_time[symbol] + self.cooldown_period:
                self.symbols.remove(symbol)