Overall Statistics
Total Trades
1570
Average Win
0.17%
Average Loss
-0.21%
Compounding Annual Return
2.573%
Drawdown
29.500%
Expectancy
0.265
Net Profit
73.065%
Sharpe Ratio
0.286
Probabilistic Sharpe Ratio
0.013%
Loss Rate
30%
Win Rate
70%
Profit-Loss Ratio
0.80
Alpha
0.026
Beta
-0.016
Annual Standard Deviation
0.087
Annual Variance
0.008
Information Ratio
-0.25
Tracking Error
0.198
Treynor Ratio
-1.528
Total Fees
$179.02
Estimated Strategy Capacity
$0
Lowest Capacity Asset
CME_MP1.QuantpediaFutures 2S
# https://quantpedia.com/strategies/fx-carry-trade/
#
# Create an investment universe consisting of several currencies (10-20). Go long three currencies with the highest central bank prime rates and
# go short three currencies with the lowest central bank prime rates. The cash not used as the margin is invested in overnight rates. The strategy 
# is rebalanced monthly.

import data_tools

class ForexCarryTrade(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1) 
        self.SetCash(100000)
        
        # Source: https://www.quandl.com/data/OECD-Organisation-for-Economic-Co-operation-and-Development
        self.symbols = {
                        "CME_AD1" : "OECD/KEI_IR3TIB01_AUS_ST_M",   # Australian Dollar Futures, Continuous Contract #1
                        "CME_BP1" : "OECD/KEI_IR3TIB01_GBR_ST_M",   # British Pound Futures, Continuous Contract #1
                        "CME_CD1" : "OECD/KEI_IR3TIB01_CAN_ST_M",   # Canadian Dollar Futures, Continuous Contract #1
                        "CME_EC1" : "OECD/KEI_IR3TIB01_EA19_ST_M",  # Euro FX Futures, Continuous Contract #1
                        "CME_JY1" : "OECD/KEI_IR3TIB01_JPN_ST_M",   # Japanese Yen Futures, Continuous Contract #1
                        "CME_MP1" : "OECD/KEI_IR3TIB01_MEX_ST_M",   # Mexican Peso Futures, Continuous Contract #1
                        "CME_NE1" : "OECD/KEI_IR3TIB01_NZL_ST_M",   # New Zealand Dollar Futures, Continuous Contract #1
                        "CME_SF1" : "SNB/ZIMOMA"                    # Swiss Franc Futures, Continuous Contract #1
        }
        
        for symbol, rate_symbol in self.symbols.items():
            self.AddData(data_tools.QuandlValue, rate_symbol, Resolution.Daily)

            data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(data_tools.CustomFeeModel(self))
            data.SetLeverage(5)
            
        self.Schedule.On(self.DateRules.MonthStart("CME_AD1"), self.TimeRules.AfterMarketOpen("CME_AD1"), self.Rebalance)

    def Rebalance(self):
        # Interbank rate sorting.
        sorted_by_rate = sorted([y for y in self.symbols if self.Securities.ContainsKey(self.symbols[y]) and self.Securities[y].Price != 0], key = lambda x: self.Securities[self.symbols[x]].Price, reverse = True)
        traded_count = 3
        long = [x for x in sorted_by_rate[:traded_count]]
        short = [x for x in sorted_by_rate[-traded_count:]]
        
        # 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, 1 / len(long))
        for symbol in short:
            self.SetHoldings(symbol, -1 / len(short))
# Quandl "value" data
class QuandlValue(PythonQuandl):
    def __init__(self):
        self.ValueColumnName = 'Value'

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