Overall Statistics
Total Trades
1593
Average Win
0.59%
Average Loss
-0.77%
Compounding Annual Return
-0.161%
Drawdown
39.600%
Expectancy
0.007
Net Profit
-3.248%
Sharpe Ratio
0.021
Probabilistic Sharpe Ratio
0.000%
Loss Rate
43%
Win Rate
57%
Profit-Loss Ratio
0.77
Alpha
0.002
Beta
-0.013
Annual Standard Deviation
0.077
Annual Variance
0.006
Information Ratio
-0.31
Tracking Error
0.197
Treynor Ratio
-0.129
Total Fees
$1746.39
# https://quantpedia.com/strategies/currency-momentum-factor/
#
# Create an investment universe consisting of several currencies (10-20). Go long three currencies with the highest 12-month momentum against USD
# and go short three currencies with the lowest 12-month momentum against USD. Cash not used as margin invest on overnight rates. Rebalance monthly.

from collections import deque
import fk_tools
from math import sqrt

class Currency_Momentum_Factor(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        # Daily close prices.
        self.data = {}

        self.period = 12 * 21
        
        self.targeted_volatility = 0.10

        self.symbols = [
                        "CME_AD1", # Australian Dollar Futures, Continuous Contract #1
                        "CME_BP1", # British Pound Futures, Continuous Contract #1
                        "CME_CD1", # Canadian Dollar Futures, Continuous Contract #1
                        "CME_EC1", # Euro FX Futures, Continuous Contract #1
                        "CME_JY1", # Japanese Yen Futures, Continuous Contract #1
                        "CME_MP1", # Mexican Peso Futures, Continuous Contract #1
                        "CME_NE1", # New Zealand Dollar Futures, Continuous Contract #1
                        "CME_SF1" # Swiss Franc Futures, Continuous Contract #1
                        ]
                        
        for symbol in self.symbols:
            data = self.AddData(fk_tools.QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(fk_tools.CustomFeeModel(self))
            data.SetLeverage(20)
            
            self.data[symbol] = deque(maxlen = self.period)
            
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0]), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance)

    def OnData(self, data):
        for symbol in self.symbols:
            if self.Securities.ContainsKey(symbol):
                price = self.Securities[symbol].Price
                if price != 0:
                    self.data[symbol].append(price)
                else:
                    # Append latest price as a next one in case there's 0 as price.
                    if len(self.data[symbol]) > 0:
                        last_price = self.data[symbol][-1]
                        self.data[symbol].append(last_price)
    
    def Rebalance(self):
        # Store monthly closes and calculate momentum.
        momentum = {}
        volatility = {}

        for symbol in self.symbols:
            if len(self.data[symbol]) == self.data[symbol].maxlen:
                prices = [x for x in [x for x in self.data[symbol]]]
                momentum[symbol] = fk_tools.Return(prices)
                volatility[symbol] = fk_tools.Volatility(prices[-21:]) * sqrt(252)
        
        if len(momentum) == 0 or len(volatility) == 0: 
            self.Liquidate()
            return
    
        sorted_by_momentum = sorted(momentum.items(), key = lambda x: x[1], reverse = True)
        long = [x[0] for x in sorted_by_momentum[:3]]
        short = [x[0] for x in sorted_by_momentum[-3:]]

        # Volatility targeting.
        count = len(long + short)
        portfolio_volatility = sum([((volatility[x]) / count) for x in long + short])
        volatility_target_leverage = self.targeted_volatility / portfolio_volatility

        # Trade execution.
        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in long + short:
                self.Liquidate(symbol)
                
        for symbol in long:
            self.SetHoldings(symbol, volatility_target_leverage / len(long))
        for symbol in short:
            self.SetHoldings(symbol, -volatility_target_leverage / len(short))
import numpy as np

def Return(values):
    return (values[-1] - values[0]) / values[0]
    
def Volatility(values):
    values = np.array(values)
    returns = (values[1:] - values[:-1]) / values[:-1]
    return np.std(returns)  

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

# Quandl free data
class QuandlFutures(PythonQuandl):
    def __init__(self):
        self.ValueColumnName = "settle"
        
# 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['settle'] = float(split[1])
        data.Value = float(split[1])

        return data