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)