Overall Statistics |
Total Orders
138
Average Win
2.44%
Average Loss
-1.26%
Compounding Annual Return
1.385%
Drawdown
30.400%
Expectancy
0.515
Start Equity
100000
End Equity
140917.17
Net Profit
40.917%
Sharpe Ratio
-0.211
Sortino Ratio
-0.234
Probabilistic Sharpe Ratio
0.000%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.94
Alpha
-0.013
Beta
0.045
Annual Standard Deviation
0.053
Annual Variance
0.003
Information Ratio
-0.344
Tracking Error
0.16
Treynor Ratio
-0.252
Total Fees
$53.51
Estimated Strategy Capacity
$0
Lowest Capacity Asset
CME_AD1.QuantpediaFutures 2S
Portfolio Turnover
0.09%
|
#region imports from AlgorithmImports import * from dateutil.relativedelta import relativedelta #endregion # 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): _last_update_date:Dict[str, datetime.date] = {} @staticmethod def get_last_update_date() -> Dict[str, datetime.date]: return QuantpediaFutures._last_update_date 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]) # store last update date if config.Symbol.Value not in QuantpediaFutures._last_update_date: QuantpediaFutures._last_update_date[config.Symbol.Value] = datetime(1,1,1).date() if data.Time.date() > QuantpediaFutures._last_update_date[config.Symbol.Value]: QuantpediaFutures._last_update_date[config.Symbol.Value] = data.Time.date() return data # source: https://data.oecd.org/conversion/purchasing-power-parities-ppp.htm class PPPData(PythonData): _last_update_date:Dict[str, datetime.date] = {} @staticmethod def get_last_update_date() -> Dict[str, datetime.date]: return PPPData._last_update_date def GetSource(self, config, date, isLiveMode): return SubscriptionDataSource(f'data.quantpedia.com/backtesting_data/economic/ppp/{config.Symbol.Value}.csv', SubscriptionTransportMedium.RemoteFile, FileFormat.Csv) def Reader(self, config, line, date, isLiveMode): data = PPPData() data.Symbol = config.Symbol if not line[0].isdigit(): return None split = line.split(';') # Parse the CSV file's columns into the custom data class data.Time = datetime.strptime(split[0], "%Y-%m-%d") + relativedelta(months=2) data.Value = float(split[1]) # store last update date if config.Symbol.Value not in PPPData._last_update_date: PPPData._last_update_date[config.Symbol.Value] = datetime(1,1,1).date() if data.Time.date() > PPPData._last_update_date[config.Symbol.Value]: PPPData._last_update_date[config.Symbol.Value] = data.Time.date() 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 from AlgorithmImports import * from typing import Dict, List class CurrencyValueFactorPPPStrategy(QCAlgorithm): def Initialize(self): self.SetStartDate(2000, 1, 1) self.SetCash(100000) self.leverage:int = 3 self.traded_count:int = 3 self.ppp_data:Dict[str, float] = {} # currency future symbol and PPP yearly symbol self.symbols:Dict[str, str] = { "CME_AD1" : "AUS_PPP", # Australian Dollar Futures, Continuous Contract #1 "CME_BP1" : "GBR_PPP", # British Pound Futures, Continuous Contract #1 "CME_CD1" : "CAD_PPP", # Canadian Dollar Futures, Continuous Contract #1 "CME_EC1" : "DEU_PPP", # Euro FX Futures, Continuous Contract #1 "CME_JY1" : "JPN_PPP", # Japanese Yen Futures, Continuous Contract #1 "CME_NE1" : "NZL_PPP", # New Zealand Dollar Futures, Continuous Contract #1 "CME_SF1" : "CHE_PPP" # Swiss Franc Futures, Continuous Contract #1 } for symbol, ppp_symbol in self.symbols.items(): data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily) data.SetFeeModel(data_tools.CustomFeeModel()) data.SetLeverage(self.leverage) # PPP quantpedia data self.AddData(data_tools.PPPData, ppp_symbol, Resolution.Daily) self.Settings.MinimumOrderMarginPortfolioPercentage = 0. self.recent_month:int = -1 def OnData(self, data: Slice) -> None: futures_last_update_date:Dict[str, datetime.date] = data_tools.QuantpediaFutures.get_last_update_date() ppp_last_update_date:Dict[str, datetime.date] = data_tools.PPPData.get_last_update_date() for symbol, ppp_symbol in self.symbols.items(): if ppp_symbol in data and data[ppp_symbol] and self.Time.date() < ppp_last_update_date[ppp_symbol]: self.ppp_data[symbol] = data[ppp_symbol].Value if self.recent_month == self.Time.month: return self.recent_month = self.Time.month # January rebalance if self.recent_month == 1: ppp:Dict[str, float] = {} for symbol, ppp_symbol in self.symbols.items(): if self.Securities[symbol].GetLastData() and self.Time.date() > futures_last_update_date[symbol]: self.Liquidate() return long:List[str] = [] short:List[str] = [] if len(self.ppp_data) >= self.traded_count*2: # ppp sorting sorted_by_ppp:List[str] = sorted(self.ppp_data.items(), key = lambda x: x[1], reverse = True) long = [x[0] for x in sorted_by_ppp[-self.traded_count:]] short = [x[0] for x in sorted_by_ppp[:self.traded_count]] self.ppp_data.clear() # trade execution invested:List[str] = [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 i, portfolio in enumerate([long, short]): for symbol in portfolio: if symbol in data and data[symbol]: self.SetHoldings(symbol, ((-1) ** i) / len(portfolio))