Overall Statistics |
Total Orders 9386 Average Win 0.13% Average Loss -0.09% Compounding Annual Return 24.252% Drawdown 64.500% Expectancy 0.401 Start Equity 1000000 End Equity 4272380.09 Net Profit 327.238% Sharpe Ratio 0.592 Sortino Ratio 0.715 Probabilistic Sharpe Ratio 10.176% Loss Rate 42% Win Rate 58% Profit-Loss Ratio 1.41 Alpha 0.122 Beta 1.23 Annual Standard Deviation 0.366 Annual Variance 0.134 Information Ratio 0.454 Tracking Error 0.308 Treynor Ratio 0.176 Total Fees $137045.54 Estimated Strategy Capacity $62000.00 Lowest Capacity Asset AE R735QTJ8XC9X Portfolio Turnover 3.04% |
# region imports from AlgorithmImports import * # endregion class CrackSpreadAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2018, 1, 1) self.set_cash(1_000_000) self._universe = self.add_universe(lambda fundamental: [f.symbol for f in fundamental if f.AssetClassification.MorningstarIndustryCode == MorningstarIndustryCode.OIL_AND_GAS_REFINING_AND_MARKETING]) self._crude, self._gasoline, self._heating_oil = [self.add_future(ticker) for ticker in [Futures.Energies.CRUDE_OIL_WTI, Futures.Energies.GASOLINE, Futures.Energies.HEATING_OIL]] for future in [self._crude, self._gasoline, self._heating_oil]: future.set_filter(lambda future_filter_universe: future_filter_universe.front_month()) self._spread_sma = SimpleMovingAverage(int(7.5*60*5*4.33*12)) # Yearly SMA self.set_warm_up(timedelta(400)) spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA) self.schedule.on(self.date_rules.every_day(spy), self.time_rules.after_market_open(spy, 1), self._rebalance) def on_data(self, data): future_chains = [data.futures_chains.get(future.symbol) for future in [self._crude, self._gasoline, self._heating_oil]] if not all(future_chains): return # Not all data is available crude_price, gasoline_price, heating_oil_price = [[contract for contract in chain][0].last_price for chain in future_chains] self._spread = (2*gasoline_price*42 + heating_oil_price*42) - 3*crude_price # margin = outputs - inputs self._spread_sma.update(self.time, self._spread) def _rebalance(self): if self.is_warming_up or not self._spread_sma.is_ready: return exposure = 1.5 if self._spread > self._spread_sma.current.value else 1 self.set_holdings([PortfolioTarget(symbol, exposure/len(self._universe.selected)) for symbol in self._universe.selected], True)