Overall Statistics
Total Trades
120
Average Win
0.49%
Average Loss
-1.15%
Compounding Annual Return
-0.151%
Drawdown
40.700%
Expectancy
-0.075
Net Profit
-3.117%
Sharpe Ratio
0.004
Probabilistic Sharpe Ratio
0.000%
Loss Rate
35%
Win Rate
65%
Profit-Loss Ratio
0.43
Alpha
0.001
Beta
-0.008
Annual Standard Deviation
0.054
Annual Variance
0.003
Information Ratio
-0.373
Tracking Error
0.187
Treynor Ratio
-0.028
Total Fees
$31.36
# 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 "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
# https://quantpedia.com/strategies/currency-value-factor-ppp-strategy/
#
# Create an investment universe consisting of several currencies (10-20). Use the latest OECD Purchasing Power Parity figure to assess 
# the fair value of each currency versus USD in the month of publishing and then use monthly CPI changes and exchange rate changes to 
# create fair PPP value for the month prior to the current month. Go long three currencies that are the most undervalued (lowest PPP 
# fair value figure) and go short three currencies that are the most overvalued (highest PPP fair value figure). Invest cash not used
# as margin on overnight rates. Rebalance quarterly or monthly.
#
# QC implementation changes:
#   - Yearly rebalance instead of quarterly is performed.

import data_tools

class CurrencyValueFactorPPPStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        # Currency future symbol and PPP yearly quandl symbol.
        # PPP source: https://www.quandl.com/data/ODA-IMF-Cross-Country-Macroeconomic-Statistics?keyword=%20United%20States%20Implied%20PPP%20Conversion%20Rate
        self.symbols = {"CME_AD1" : "ODA/AUS_PPPEX", # Australian Dollar Futures, Continuous Contract #1
                        "CME_BP1" : "ODA/GBR_PPPEX", # British Pound Futures, Continuous Contract #1
                        "CME_CD1" : "ODA/CAD_PPPEX", # Canadian Dollar Futures, Continuous Contract #1
                        "CME_EC1" : "ODA/DEU_PPPEX", # Euro FX Futures, Continuous Contract #1
                        "CME_JY1" : "ODA/JPN_PPPEX", # Japanese Yen Futures, Continuous Contract #1
                        "CME_NE1" : "ODA/NZL_PPPEX", # New Zealand Dollar Futures, Continuous Contract #1
                        "CME_SF1" : "ODA/CHE_PPPEX"  # Swiss Franc Futures, Continuous Contract #1
                        }

        for symbol in self.symbols:
            data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
            data.SetFeeModel(data_tools.CustomFeeModel(self))
            data.SetLeverage(5)
            
            # PPP quandl data.
            ppp_symbol = self.symbols[symbol]
            self.AddData(data_tools.QuandlValue, ppp_symbol, Resolution.Daily)

        self.month = 12
        self.Schedule.On(self.DateRules.MonthStart('CME_AD1'), self.TimeRules.AfterMarketOpen('CME_AD1'), self.Rebalance)

    def Rebalance(self):
        # Yearly sorting.
        if self.month < 12:
            self.month += 1
            return
        else:
            self.month = 1
        
        # PPP sorting.
        sorted_by_ppp = sorted([x for x in self.symbols.items() if self.Securities.ContainsKey(x[1])], key = lambda x: self.Securities[x[1]].Price, reverse = True)
        count = 3
        long = [x[0] for x in sorted_by_ppp[-count:]]
        short = [x[0] for x in sorted_by_ppp[: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:
            if self.Securities[symbol].Price != 0:
                self.SetHoldings(symbol, 1 / len(long))
        for symbol in short:
            if self.Securities[symbol].Price != 0:
                self.SetHoldings(symbol, -1 / len(short))