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)