| Overall Statistics |
|
Total Orders 292 Average Win 1.12% Average Loss -0.28% Compounding Annual Return 1.465% Drawdown 16.300% Expectancy 0.927 Start Equity 100000.00 End Equity 122185.03 Net Profit 22.185% Sharpe Ratio -0.03 Sortino Ratio -0.017 Probabilistic Sharpe Ratio 0.027% Loss Rate 62% Win Rate 38% Profit-Loss Ratio 4.03 Alpha -0.001 Beta 0.019 Annual Standard Deviation 0.061 Annual Variance 0.004 Information Ratio 0.265 Tracking Error 0.093 Treynor Ratio -0.097 Total Fees $0.00 Estimated Strategy Capacity $1000.00 Lowest Capacity Asset CORNUSD 8I Portfolio Turnover 0.32% |
from AlgorithmImports import *
class DonchianCfdTradingAlgorithm(QCAlgorithm):
def Initialize(self):
"""Initialize the algorithm and set up necessary parameters and indicators."""
self.SetStartDate(2010, 1, 1)
#self.SetEndDate(2021,11,1)
self.SetCash(100000)
self.SetBrokerageModel(BrokerageName.OandaBrokerage, AccountType.Margin)
# Indicator config
self.donchian_period = int(self.GetParameter("donchian_period"))
self.ema_period = int(self.GetParameter("ema_period"))
self.atr_period = 14
self.risk_per_trade = 0.04 # 2% risk per trade
self.trade_resolution = Resolution.Daily
# CFD symbols to trade (full list of Oanda CFDs)
self.cfd_symbols = [
#"AU200AUD", "BCOUSD", "CH20CHF", "CORNUSD", "DE10YBEUR", "DE30EUR",
#"EU50EUR", "FR40EUR", "HK33HKD", "JP225USD", "NAS100USD", "NATGASUSD",
#"NL25EUR", "SG30SGD", "SOYBNUSD", "SPX500USD", "SUGARUSD", "UK100GBP",
#"UK10YBGBP", "US2000USD", "US30USD", "USB02YUSD", "USB05YUSD", "USB10YUSD",
#"USB30YUSD", "WHEATUSD", "WTICOUSD", "XAGUSD", "XAUUSD", "XCUUSD", "XPDUSD", "XPTUSD",
"AU200AUD", "BCOUSD", "CH20CHF", "CORNUSD", "DE10YBEUR", "DE30EUR",
"EU50EUR", "FR40EUR", "HK33HKD", "JP225USD", "NAS100USD", "NATGASUSD",
"NL25EUR", "SG30SGD", "SOYBNUSD", "SPX500USD", "SUGARUSD", "UK100GBP",
"UK10YBGBP", "US2000USD", "US30USD", "USB02YUSD", "USB05YUSD", "USB10YUSD",
"USB30YUSD", "WHEATUSD", "WTICOUSD", "XAGAUD", "XAGCAD", "XAGCHF", "XAGEUR",
"XAGGBP", "XAGHKD", "XAGJPY", "XAGNZD", "XAGSGD", "XAGUSD", "XAUAUD", "XAUCAD",
"XAUCHF", "XAUEUR", "XAUGBP", "XAUHKD", "XAUJPY", "XAUNZD", "XAUSGD", "XAUUSD",
"XAUXAG", "XCUUSD", "XPDUSD", "XPTUSD"
]
self.symbols = []
self.donchian_channels = {}
self.ema_indicators = {}
self.atr_indicators = {}
for symbol_str in self.cfd_symbols:
symbol = self.AddCfd(symbol_str, self.trade_resolution, Market.Oanda).Symbol
self.symbols.append(symbol)
self.donchian_channels[symbol] = self.dch(symbol, self.donchian_period, self.donchian_period)
self.ema_indicators[symbol] = self.SMA(symbol, self.ema_period)
self.atr_indicators[symbol] = self.ATR(symbol, self.atr_period)
self.SetWarmUp(max(self.donchian_period, self.ema_period, self.atr_period), self.trade_resolution)
def OnData(self, data):
"""Handle incoming data and execute trading logic."""
if self.IsWarmingUp:
return
for symbol in self.symbols:
if symbol not in data:
continue
if not self.AreIndicatorsReady(symbol):
continue
price = data[symbol].Close
donchian = self.donchian_channels[symbol].Current.Value
donchian_lower = self.donchian_channels[symbol].lower_band.Current.Value
donchian_upper = self.donchian_channels[symbol].upper_band.Current.Value
ema = self.ema_indicators[symbol].Current.Value
atr = self.atr_indicators[symbol].Current.Value
is_uptrend = price > ema
is_downtrend = price < ema
if self.Portfolio[symbol].Invested:
self.ManageExistingPosition(symbol, price, donchian_upper, donchian_lower, ema, atr)
self.CheckNewEntry(symbol, price, donchian_upper, donchian_lower, is_uptrend, is_downtrend, atr)
def AreIndicatorsReady(self, symbol):
"""Check if all indicators for a symbol are ready."""
return (self.donchian_channels[symbol].IsReady and
self.ema_indicators[symbol].IsReady and
self.atr_indicators[symbol].IsReady)
def ManageExistingPosition(self, symbol, price, donchian_upper, donchian_lower, ema, atr):
"""Manage existing positions, including trailing stops."""
if self.Portfolio[symbol].IsLong:
stop_price = donchian_lower + (2 * atr)
if price <=ema or price <= stop_price:
self.Liquidate(symbol)
self.Debug(f"Liquidated LONG position on {symbol.Value} at price {price}")
elif self.Portfolio[symbol].IsShort:
stop_price = donchian_upper - (2 * atr)
if price >= ema or price >= stop_price:
self.Liquidate(symbol)
self.Debug(f"Liquidated SHORT position on {symbol.Value} at price {price}")
def CheckNewEntry(self, symbol, price, donchian_upper, donchian_lower, is_uptrend, is_downtrend, atr):
"""Check for new entry conditions and enter positions if conditions are met."""
if is_uptrend and price >= donchian_upper:
self.EnterPosition(symbol, True, price, donchian_lower, atr)
elif is_downtrend and price <= donchian_lower:
self.EnterPosition(symbol, False, price, donchian_upper, atr)
def EnterPosition(self, symbol, is_long, price, band, atr):
"""Enter a new position with proper position sizing."""
stop_price = band + (2 * atr * (-1 if is_long else 1))
position_size = self.CalculatePositionSize(symbol, price, stop_price)
if position_size == 0:
return
if is_long:
self.SetHoldings(symbol, 0.1)
self.Debug(f"Entered LONG position on {symbol.Value} at price {price}, size: {position_size}")
else:
#self.SetHoldings(symbol, -0.3)
self.Debug(f"Entered SHORT position on {symbol.Value} at price {price}, size: {-position_size}")
def CalculatePositionSize(self, symbol, price, stop_price):
"""Calculate position size based on risk management rules."""
risk_amount = self.Portfolio.TotalPortfolioValue * self.risk_per_trade
price_range = abs(price - stop_price)
if price_range == 0:
self.Debug(f"Zero price range for {symbol.Value}. Skipping trade.")
return 0
# Calculate the position size as a fraction of the portfolio
position_size = risk_amount / price_range / price
# Ensure we're not risking more than 2% per trade
return min(position_size, 0.02)