Overall Statistics
Total Orders
1704
Average Win
1.11%
Average Loss
-1.03%
Compounding Annual Return
27.370%
Drawdown
33.900%
Expectancy
0.309
Start Equity
1000000
End Equity
14187747.05
Net Profit
1318.775%
Sharpe Ratio
1.058
Sortino Ratio
1.094
Probabilistic Sharpe Ratio
66.393%
Loss Rate
37%
Win Rate
63%
Profit-Loss Ratio
1.07
Alpha
0.109
Beta
0.807
Annual Standard Deviation
0.164
Annual Variance
0.027
Information Ratio
0.806
Tracking Error
0.116
Treynor Ratio
0.215
Total Fees
$68342.15
Estimated Strategy Capacity
$33000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
20.66%
Drawdown Recovery
751
from AlgorithmImports import *
from datetime import timedelta, time


class BlackSwan_SPY_Algorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 1, 12)
        self.SetCash(1_000_000)
        self.SetWarmup(timedelta(30))
        self.SetBenchmark("SPY")

        self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol
        self.uvxy = self.AddEquity("UVXY", Resolution.Minute).Symbol

        self.UniverseSettings.Resolution = Resolution.Hour

        self.base_weight = 0.995
        self.sniper_spy_weight = 0.045
        self.sniper_uvxy_weight = 0.95
        self.base_duration = timedelta(30)

        self.SetAlpha(
            BlackSwan_SPY(self.spy,self.uvxy,base_weight=self.base_weight,sniper_spy_weight=self.sniper_spy_weight,sniper_uvxy_weight=self.sniper_uvxy_weight,period_sma=2,resolution=Resolution.Hour))

        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
        self.SetExecution(ImmediateExecutionModel())

        self.Schedule.On(self.DateRules.EveryDay(self.spy), self.TimeRules.AfterMarketOpen(self.spy, 1),self.EmitBaseInsight)

    def EmitBaseInsight(self):
        self.EmitInsights(
            Insight.Price(self.spy,self.base_duration,InsightDirection.Up,None,None,"base",self.base_weight))

    def OnData(self, data):
        if self.IsWarmingUp:
            return
        if not self.Portfolio.Invested:
            self.EmitBaseInsight()


class BlackSwan_SPY(AlphaModel):

    def __init__(self,spy_symbol,uvxy_symbol,base_weight=0.995,sniper_spy_weight=0.045,sniper_uvxy_weight=0.95,period_sma=2,resolution=Resolution.Hour):
        self.spy = spy_symbol
        self.uvxy = uvxy_symbol
        self.base_weight = base_weight
        self.sniper_spy_weight = sniper_spy_weight
        self.sniper_uvxy_weight = sniper_uvxy_weight
        self.period_sma = period_sma
        self.resolution = resolution
        self.data = None
        self.Name = "BlackSwan_SPY"

    def Update(self, algorithm, data):
        remaining = self._remaining_until_eod(algorithm)
        if remaining <= timedelta(0):
            return []

        if self.data is None or not self.EmitNow(algorithm):
            return []

        price = algorithm.Securities[self.uvxy].Price
        direction = InsightDirection.Up if self.data.VOL_UP(price) else InsightDirection.Flat

        if direction == self.data.PreviousDirection:
            return []

        self.data.PreviousDirection = direction

        if direction == InsightDirection.Up:
            return [
                Insight.Price(self.spy, remaining, InsightDirection.Up, None, None, "sniper", self.sniper_spy_weight),
                Insight.Price(self.uvxy, remaining, InsightDirection.Up, None, None, "sniper", self.sniper_uvxy_weight),
            ]

        return [
            Insight.Price(self.uvxy, remaining, InsightDirection.Flat, None, None, "sniper", 0),
            Insight.Price(self.spy, remaining, InsightDirection.Up, None, None, "base_restore", self.base_weight),
        ]

    def OnSecuritiesChanged(self, algorithm, changes):
        for security in changes.AddedSecurities:
            if security.Symbol == self.uvxy:
                self.data = SymbolData(algorithm, self.uvxy, self.period_sma, self.resolution)

    def EmitNow(self, algorithm):
        t = algorithm.Time.time()
        return algorithm.Securities[self.uvxy].HasData and time(9, 56) <= t <= time(14, 4)

    def _remaining_until_eod(self, algorithm):
        close_dt = algorithm.Time.replace(hour=14, minute=15, second=0, microsecond=0)
        return close_dt - algorithm.Time


class SymbolData:

    def __init__(self, algorithm, symbol, period_sma, resolution):
        self.PreviousDirection = InsightDirection.Flat
        self.SMA = algorithm.SMA(symbol, period_sma, resolution)

    def VOL_UP(self, price):
        return self.SMA.IsReady and price < round(self.SMA.Current.Value * 1.09, 2)
OrderTypeKeys = [
    'Market', 'Limit', 'StopMarket', 'StopLimit', 'MarketOnOpen',
    'MarketOnClose', 'OptionExercise',
]

OrderTypeCodes = dict(zip(range(len(OrderTypeKeys)), OrderTypeKeys))