Overall Statistics
Total Trades
4746
Average Win
0.51%
Average Loss
-0.54%
Compounding Annual Return
3.932%
Drawdown
37.800%
Expectancy
0.042
Net Profit
58.559%
Sharpe Ratio
0.276
Probabilistic Sharpe Ratio
0.128%
Loss Rate
46%
Win Rate
54%
Profit-Loss Ratio
0.93
Alpha
0.002
Beta
0.358
Annual Standard Deviation
0.129
Annual Variance
0.017
Information Ratio
-0.384
Tracking Error
0.151
Treynor Ratio
0.1
Total Fees
$54404.70
Estimated Strategy Capacity
$45000000.00
Lowest Capacity Asset
ZO Y6RTF5MGZVOL
# region imports
from AlgorithmImports import *
from datetime import datetime
import math
# endregionfs

class InverseVolatility(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2011, 1, 1) # Jens 
        #self.SetEndDate(2021, 12, 31)
        self.SetCash(1000000)  # Set Strategy Cash

        self.SetSecurityInitializer(BrokerageModelSecurityInitializer(self.BrokerageModel, FuncSecuritySeeder(self.GetLastKnownPrices)))

        self.volatility = {}
        tickers = [
            Futures.Indices.VIX,
            Futures.Indices.SP500EMini,
            Futures.Indices.NASDAQ100EMini,
            Futures.Indices.Dow30EMini,
            Futures.Energies.BrentCrude,
            Futures.Energies.Gasoline,
            Futures.Energies.HeatingOil,
            Futures.Energies.NaturalGas,
            Futures.Grains.Corn,
            Futures.Grains.Oats,
            Futures.Grains.Soybeans,
            Futures.Grains.Wheat
            ]
        for ticker in tickers:
            future = self.AddFuture(ticker, extendedMarketHours=True)

            # 30-day standard deviation of 1-day returns
            roc = self.ROC(future.Symbol, 1, Resolution.Daily)
            self.volatility[future] = IndicatorExtensions.Of(StandardDeviation(30), roc)

        self.SetWarmup(31, Resolution.Daily)

        self.Schedule.On(
            self.DateRules.WeekStart(),
            self.TimeRules.At(10,0,0),
            self.Rebalance)

    def Rebalance(self):
        if self.IsWarmingUp:
            return

        sorted_by_value = sorted([kvp for kvp in self.volatility.items() if kvp[1].IsReady], 
            key=lambda item: item[1].Current.Value)
        top5 = {k: v for k, v in sorted_by_value[:5]}

        # Sum the inverse STD of ROC
        # Jens: Added exception here as well. Not sure if that still works as it should. I ignore values that have no std yet 
        inverse_sum = sum([1/std.Current.Value if std.Current.Value else 0 for std in top5.values()]) 

        if inverse_sum == 0:
            return 
        
        # Create Portfoliotarget for the inverse weighted STD
        targets = [PortfolioTarget(future.Mapped, 0.1/std.Current.Value/inverse_sum)
        # Jens: added exception for std being zero then no target will be given. This accounts for the fact that these 
        # contracts are also not included in the inverse_sum 
            for future,std in top5.items() if future.Mapped and std.Current.Value] 

        # Liquidate if invested and no new negative news
        for symbol, holdings in self.Portfolio.items():
            if holdings.Invested and symbol not in top5:
                targets.append(PortfolioTarget(symbol, 0))

        #for target in targets:
        #    self.Plot("Targets", target.Symbol.Value, target.Quantity)

        self.SetHoldings(targets)