| 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)