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)