Overall Statistics |
Total Orders 259 Average Win 1.03% Average Loss -0.27% Compounding Annual Return 5.169% Drawdown 18.400% Expectancy 2.321 Start Equity 75000 End Equity 207990.46 Net Profit 177.321% Sharpe Ratio 0.222 Sortino Ratio 0.208 Probabilistic Sharpe Ratio 0.706% Loss Rate 31% Win Rate 69% Profit-Loss Ratio 3.82 Alpha 0.015 Beta -0 Annual Standard Deviation 0.068 Annual Variance 0.005 Information Ratio -0.252 Tracking Error 0.17 Treynor Ratio -32.101 Total Fees $531.53 Estimated Strategy Capacity $4900000.00 Lowest Capacity Asset GLD T3SKPOF94JFP Portfolio Turnover 0.60% |
# region imports from AlgorithmImports import * # endregion class SymbolData(): def __init__(self, period: int, mom_period: int) -> None: self._prices: RollingWindow = RollingWindow[float](mom_period) self._cpi_mom_change: RollingWindow = RollingWindow[float](period) self._last_inflation_change: int = 0 def price_is_ready(self) -> bool: return self._prices.is_ready def is_ready(self) -> bool: return self._cpi_mom_change.is_ready def update_price(self, price: float) -> None: self._prices.add(price) def update(self, cpi_value: float) -> None: self._cpi_mom_change.add(cpi_value) def inflation_increase(self) -> bool: inflation_change: int = ( 1 if all(x > 0 for x in list(self._cpi_mom_change)) else ( -1 if all(x < 0 for x in list(self._cpi_mom_change)) else self._last_inflation_change ) ) self._last_inflation_change = inflation_change return True if inflation_change == 1 else False def positive_momentum(self) -> bool: momentum: float = self._prices[0] / self._prices[self._prices.count - 1] - 1 return True if momentum > 0 else False class BLS_CPI(PythonData): _last_update_date: datetime.date = datetime(1,1,1).date() @staticmethod def get_last_update_date() -> datetime.date: return BLS_CPI._last_update_date def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource: return SubscriptionDataSource(f'data.quantpedia.com/backtesting_data/economic/BLS_INFLATION_AS_REPORTED.csv', SubscriptionTransportMedium.RemoteFile, FileFormat.Csv) def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData: data = BLS_CPI() 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[1], "%Y-%m-%d") #+ timedelta(days=1) if split[-1] != '': data.Value = float(split[-1]) if data.Time.date() > BLS_CPI._last_update_date: BLS_CPI._last_update_date = data.Time.date() return data
# region imports from AlgorithmImports import * import data_tools # endregion class InflationforGoldandTreasuries(QCAlgorithm): _notional_value: int = 75_000 _trade_exec_minute_offset: int = 15 _period: int = 2 _mom_period: int = 12 def initialize(self) -> None: self.set_start_date(2005, 1, 1) self.set_cash(self._notional_value) leverage: int = 1 tickers: List[str] = ['GLD', 'IEF', 'SHY'] self._gld, self._ief, self._shy = [ self.add_equity(ticker, Resolution.MINUTE, leverage=leverage).symbol for ticker in tickers ] self._CPI: Symbol = self.add_data(data_tools.BLS_CPI, 'BLSCPI', Resolution.DAILY).symbol self._data: Dict[Symbol, data_tools.SymbolData] = {} self._price_symbols: List[Symbol] = [self._gld, self._ief] for symbol in self._price_symbols: self._data[symbol] = data_tools.SymbolData(self._period, self._mom_period) self._data[self._CPI] = data_tools.SymbolData(self._period, self._mom_period) self.settings.daily_precise_end_time = False self.settings.minimum_order_margin_portfolio_percentage = 0. market: Symbol = self.add_equity('SPY', Resolution.MINUTE).symbol self.set_warm_up(self._mom_period*30, Resolution.DAILY) self._rebalance_flag: bool = False self.schedule.on( self.date_rules.month_start(market), self.time_rules.before_market_close(market, self._trade_exec_minute_offset), self._rebalance ) def on_data(self, slice: Slice) -> None: # Update data when new CPI data arrives. if slice.contains_key(self._CPI) and slice[self._CPI]: self.log(f'new CPI data arrived - CPI: {slice[self._CPI].value}') # store CPI self._data[self._CPI].update(slice[self._CPI].value) # store asset prices for s in self._price_symbols: if self.securities.contains_key(s) and self.securities[s].get_last_data(): self.log(f'{s} asset price data updated') self._data[s].update_price(self.securities[s].get_last_data().close) if self.is_warming_up: return # Monthly rebalance. if not self._rebalance_flag: return self._rebalance_flag = False self.log('new monthly rebalance') price_ready: Dict[str, bool] = { s.value : self._data[s].price_is_ready() for s in self._price_symbols } if not all(price_ready.values()): self.log(f'price data not ready yet - {price_ready}') return if not self._data[self._CPI].is_ready(): self.log(f'CPI data not ready yet') return # Check if data is still coming. CPI_last_update_date: datetime.date = data_tools.BLS_CPI.get_last_update_date() if self.securities[self._CPI].get_last_data() and self.time.date() > CPI_last_update_date: self.log(f'data for CPI stopped coming in; last update date: {CPI_last_update_date}') self.liquidate() return # Trade asset decision. traded_asset: Symbol = ( self._gld if self._data[self._CPI].inflation_increase() and self._data[self._gld].positive_momentum() else ( self._ief if not self._data[self._CPI].inflation_increase() and self._data[self._ief].positive_momentum() else self._shy ) ) # Trade execution. if not self.portfolio[traded_asset].invested: self.log(f'liquidating existing holdings') self.liquidate() # Calculate additional quantity. quantity: int = self._notional_value // self.securities[traded_asset].price additional_q: float = quantity - self.portfolio[traded_asset].quantity self.log(f'additional quantity for {traded_asset.value}: {additional_q}; existing quantity: {self.portfolio[traded_asset].quantity}') self.market_order(traded_asset, additional_q) def _rebalance(self) -> None: self._rebalance_flag = True