Overall Statistics
Total Orders
184
Average Win
2.27%
Average Loss
-0.72%
Compounding Annual Return
10.835%
Drawdown
18.700%
Expectancy
0.424
Start Equity
100000
End Equity
181633.05
Net Profit
81.633%
Sharpe Ratio
0.53
Sortino Ratio
0.541
Probabilistic Sharpe Ratio
24.899%
Loss Rate
66%
Win Rate
34%
Profit-Loss Ratio
3.17
Alpha
0.021
Beta
0.293
Annual Standard Deviation
0.1
Annual Variance
0.01
Information Ratio
-0.377
Tracking Error
0.146
Treynor Ratio
0.18
Total Fees
$99.73
Estimated Strategy Capacity
$0
Lowest Capacity Asset
CME_EC1.QuantpediaFutures 2S
Portfolio Turnover
0.72%
# https://quantpedia.com/strategies/trendfollowing-in-futures-markets/
#
# The investment universe consists of 20 futures markets (4 currencies, 4 financials, 2 metals, 3 softs, 3 grains, 2 meats, and 2 energies). 
# The initial position size is based on the maximum amount of risk the investor is willing to take for each position. The system described 
# (and all simulation in the source paper) has an initial risk of 2%, i.e. if the market works against the investor, he will lose a maximum 
# of 2% of his equity in this position. The exposure of each position is additionally restricted to 10% of current equity.
#
# The selected strategy is a Donchian Channel Breakout 100 (chosen due to its lower probability of curve-fitting as it depends only on 3 parameters: 
# the number of days used to calculate the Donchian channel and 2 parameters in the ATR stop). The Donchian channel is formed by taking the highest
# high and the lowest low of the last n (100) periods.
#
# Rules:
# – The investor goes long/short at Stop as soon as the price crosses the upper/lower Donchian band of 100 days.
# – The investor exits and reverses position as soon as the opposite Donchian band is crossed.
# – He/she uses 4 times the ATR of 10 days as a parameter for the maximum risk to calculate the position size.
#
# Position sizing:
# 2% based on the initial stop loss (based on 4 times the 10-day-ATR).
#
# QC implementation:
#   - Daily close prices are traded only.

from math import ceil
from AlgorithmImports import *

class ReturnsSignalMomentum(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2019, 1, 1)
        self.SetCash(100000)

        self.symbols = [
                        "CME_AD1",      # Australian Dollar Futures, Continuous Contract #1
                        "CME_BP1",      # British Pound Futures, Continuous Contract #1
                        "CME_EC1",      # Euro FX Futures, Continuous Contract #1
                        "CME_JY1",      # Japanese Yen Futures, Continuous Contract #1

                        "ICE_DX1",      # US Dollar Index Futures, Continuous Contract #1
                        "CME_NQ1",      # E-mini NASDAQ 100 Futures, Continuous Contract #1
                        "EUREX_FDAX1",  # DAX Futures, Continuous Contract #1
                        "CME_ES1",      # E-mini S&P 500 Futures, Continuous Contract #1

                        "CME_GC1",      # Gold Futures, Continuous Contract
                        "CME_SI1",      # Silver Futures, Continuous Contract

                        "ICE_CC1",      # Cocoa Futures, Continuous Contract 
                        "ICE_KC1",      # Coffee C Futures, Continuous Contract
                        "ICE_SB1",      # Sugar No. 11 Futures, Continuous Contract
                        
                        "CME_S1",       # Soybean Futures, Continuous Contract
                        "CME_W1",       # Wheat Futures, Continuous Contract
                        "CME_C1",       # Corn Futures, Continuous Contract

                        "CME_LC1",      # Live Cattle Futures, Continuous Contract
                        "CME_FC1",      # Feeder Cattle Futures, Continuous Contract

                        "CME_NG1",      # Natural Gas (Henry Hub) Physical Futures, Continuous Contract
                        "ICE_O1"        # Heating Oil Futures, Continuous Contract
                        ]
        
        period = 100
        self.SetWarmUp(period)
        self.dch = {}
        self.atr = {}

        for symbol in self.symbols:
            data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(CustomFeeModel())
            data.SetLeverage(10)
            
            self.dch[symbol] = self.DCH(symbol, period, Resolution.Daily)
            self.atr[symbol] = self.ATR(symbol, 10, Resolution.Daily)
            
    def OnData(self, data):
        if self.IsWarmingUp: return
        
        dollar_pos_size = {}
        equity = self.Portfolio.TotalPortfolioValue
        
        long = []
        short = []
        for symbol in self.symbols:
            if self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days >= 5:
                continue

            if symbol not in data or not data[symbol]: continue
            if not self.dch[symbol].IsReady or not self.atr[symbol].IsReady: continue
        
            price = data[symbol].Price
            atr = self.atr[symbol].Current.Value

            upper_band = self.dch[symbol].UpperBand.Current.Value
            lower_band = self.dch[symbol].LowerBand.Current.Value
            
            # Close position.
            if self.Portfolio[symbol].IsLong:
                if price < lower_band:
                    self.Liquidate(symbol)
            if self.Portfolio[symbol].IsShort:
                if price > upper_band:
                    self.Liquidate(symbol)
            
            # Calculate positon size.
            unit_size = (equity * 0.1 * 0.02) / (atr*4)
            
            if price > upper_band:
                if not self.Portfolio[symbol].IsLong:
                    long.append(symbol)
                    dollar_pos_size[symbol] = unit_size * price
            elif price < lower_band:
                if not self.Portfolio[symbol].IsShort:
                    short.append(symbol)
                    dollar_pos_size[symbol] = unit_size * price
                
        # Rebalance opened positions and open new ones.
        for symbol in long + short:
            percentage = dollar_pos_size[symbol] / equity
            if symbol in long: self.SetHoldings(symbol, percentage)
            if symbol in short: self.SetHoldings(symbol, -percentage)

# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
        data['back_adjusted'] = float(split[1])
        data['spliced'] = float(split[2])
        data.Value = float(split[1])

        return data
        
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))