Overall Statistics |
Total Orders 208 Average Win 0.15% Average Loss -0.21% Compounding Annual Return -1.116% Drawdown 4.700% Expectancy -0.161 Start Equity 1000000 End Equity 981424.92 Net Profit -1.858% Sharpe Ratio -1.395 Sortino Ratio -1.622 Probabilistic Sharpe Ratio 2.080% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 0.73 Alpha -0.033 Beta -0.028 Annual Standard Deviation 0.024 Annual Variance 0.001 Information Ratio -0.49 Tracking Error 0.137 Treynor Ratio 1.192 Total Fees $642.20 Estimated Strategy Capacity $150000000.00 Lowest Capacity Asset ZL X7TG3O4R7OKL Portfolio Turnover 1.13% |
#region imports from AlgorithmImports import * #endregion # https://quantpedia.com/Screener/Details/118 from QuantConnect.Python import PythonQuandl import numpy as np class TimeSeriesMomentumEffect(QCAlgorithm): def initialize(self): self.set_start_date(2018, 1, 1) self.set_end_date(2019, 9, 1) self.set_cash(1000000) symbols = [Futures.Meats.LIVE_CATTLE, Futures.Meats.LEAN_HOGS, Futures.Energies.BRENT_CRUDE, Futures.Energies.LOW_SULFUR_GASOIL, Futures.Softs.COTTON_2, Futures.Softs.COFFEE, Futures.Softs.COCOA, Futures.Softs.SUGAR_11, Futures.Grains.WHEAT, Futures.Grains.CORN, Futures.Grains.SOYBEANS, Futures.Grains.SOYBEAN_MEAL, Futures.Grains.SOYBEAN_OIL, ] # Last trading date tracker to achieve rebalancing the portfolio every month self.rebalancing_time = datetime.min self.period = 252 self.symbol_data = {} for symbol in symbols: future = self.add_future(symbol, resolution = Resolution.DAILY, extended_market_hours = True, data_normalization_mode = DataNormalizationMode.BACKWARDS_RATIO, data_mapping_mode = DataMappingMode.OPEN_INTEREST, contract_depth_offset = 0 ) future.set_leverage(1) self.symbol_data[future.symbol] = SymbolData(self, future, self.period) def on_data(self, data): ''' Monthly rebalance at the beginning of each month. ''' # Rollover for future contract mapping change for symbol, symbol_data in self.symbol_data.items(): if data.symbol_changed_events.contains_key(symbol): changed_event = data.symbol_changed_events[symbol] old_symbol = changed_event.old_symbol new_symbol = changed_event.new_symbol tag = f"Rollover - Symbol changed at {self.time}: {old_symbol} -> {new_symbol}" quantity = self.portfolio[old_symbol].quantity # Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract self.liquidate(old_symbol, tag = tag) self.market_order(new_symbol, quantity // self.securities[new_symbol].symbol_properties.contract_multiplier, tag = tag) # skip if less than 30 days passed since the last trading date if self.time < self.rebalancing_time: return # dataframe that contains the historical data for all securities history = self.history(list(self.symbol_data.keys()), self.period, Resolution.DAILY) history = history.droplevel([0]).unstack(level=0).close.pct_change().dropna() # Compute the inverse of the volatility # The weights are the normalized inverse of the volatility vol_inv = 1 / history.std(ddof=1) vol_sum = vol_inv.sum() weights = (vol_inv / vol_sum).fillna(0).to_dict() #np.sign(roc.current.value) tells us whether to long or short for symbol, symbol_data in self.symbol_data.items(): symbol_id = symbol.id.to_string() if symbol_id in weights: weight = np.sign(symbol_data.value) * weights[symbol_id] *.5 mapped = symbol_data.mapped qty = self.calculate_order_quantity(mapped, np.clip(weight, -1, 1)) multiplier = self.securities[mapped].symbol_properties.contract_multiplier order_qty = (qty - self.portfolio[mapped].quantity) // multiplier self.market_order(mapped, order_qty) # Set next rebalance time self.rebalancing_time = Expiry.end_of_month(self.time) class SymbolData: def __init__(self, algorithm, future, period): self._future = future self.symbol = future.symbol self.ROC = algorithm.ROC(future.symbol, period) algorithm.warm_up_indicator(future.symbol, self.ROC, Resolution.DAILY) @property def value(self): return self.ROC.current.value @property def mapped(self): return self._future.mapped @property def is_ready(self): return self.ROC.is_ready