Overall Statistics
Total Trades
237
Average Win
0.44%
Average Loss
-0.04%
Compounding Annual Return
13.249%
Drawdown
16.400%
Expectancy
8.424
Net Profit
56.965%
Sharpe Ratio
1.291
Probabilistic Sharpe Ratio
65.611%
Loss Rate
23%
Win Rate
77%
Profit-Loss Ratio
11.19
Alpha
0.087
Beta
0.294
Annual Standard Deviation
0.108
Annual Variance
0.012
Information Ratio
-0.211
Tracking Error
0.175
Treynor Ratio
0.474
Total Fees
$291.83
Estimated Strategy Capacity
$6600000.00
Lowest Capacity Asset
IJH RV0PWMLXVHPH
import scipy
from pytz import timezone
import numpy as np
import scipy
from scipy import optimize
import pandas as pd

class AlertLightBrownAlligator(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 1, 27)
        self.SetCash(100000) 
        self.leverage = 1.0
        self.ret = []
        self.tickers = ["SPY", "TLT", "IJR", "IJH", "SVXY"]
        self.stocks = []
        for ticker in self.tickers:
            symbol = self.AddEquity(ticker, Resolution.Minute).Symbol
            self.stocks.append(symbol)
        self.bullish = self.AddEquity("QQQ", Resolution.Minute).Symbol
        self.bearish = self.AddEquity("IEF", Resolution.Minute).Symbol
        self.volatility = self.AddEquity("VXX", Resolution.Minute).Symbol
        self.stability = self.AddEquity("SVXY", Resolution.Minute).Symbol
        
        self.Schedule.On(self.DateRules.MonthEnd("QQQ"),
                 self.TimeRules.AfterMarketOpen(self.bullish, 15),     
                 self.allocVOL)
                 
        self.Schedule.On(self.DateRules.MonthEnd("QQQ"),
                 self.TimeRules.BeforeMarketClose(self.bullish, 15),     
                 self.allocSPY)
                 
        self.n = 0
        self.s = np.zeros_like(self.stocks) # Not needed
        self.x0 = np.zeros_like(self.stocks) # Not needed
        self.x1 = 1.0 * np.ones_like(self.stocks) / len(self.stocks)
        
        self.eps = 0.01
        self.tol = 1.06-6 # assume convergence is 10 time SLSQP ftol of 1e-6
        self.valid_constraint_count = 0
        self.opt_pass_count = 0
        self.run_count = 0
        self.eps_vals = []
        
        self.Schedule.On(self.DateRules.MonthEnd("QQQ"),
                 self.TimeRules.AfterMarketOpen(self.bullish, 60),     
                 self.allocate)
                 
        self.Schedule.On(self.DateRules.MonthEnd("QQQ", 1),
                 self.TimeRules.AfterMarketOpen(self.bullish, 60),     
                 self.trade)
                 
        #set_long_only()
        
        self.Schedule.On(self.DateRules.WeekStart("QQQ"),
                 self.TimeRules.BeforeMarketClose(self.bullish, 0),     
                 self.every_day_on_end)
                 
        self.Schedule.On(self.DateRules.WeekStart("QQQ"),
                 self.TimeRules.BeforeMarketClose(self.bullish, 0),     
                 self.dynamic_leverage)
                 
        self.data = None
        
        self.symbolDataBySymbol = {}
        
        self.symbolRsi = {}
        
        ''' This may cause some problems '''
        self.leverage = 2
        
        
            # rsi = self.RSI(symbol, 3, MovingAverageType.Simple, Resolution.Hour)
            # self.symbolRsi[symbol] = rsi
        
    def OnData(self, data):
        self.data = data


    ''' confused about this part '''
    def dynamic_leverage(self):
        pass
        # win = 3
        # rsi = 50 if len(self.ret) < win else talib.RSI(np.array(self.ret), timeperiod=win)[-1]          
        # rsi = 50 if rsi != rsi else rsi
        # self.leverage = max(0, min(50/rsi, 2.0))
        # record(rsi=rsi)
        # pass    

    def every_day_on_end(self):
        pass
        # context.ret.append(context.portfolio.returns)
        # record(l1=context.leverage*100, l2=context.account.leverage * 100)

    def allocate(self):
        self.run_count += 1
        tempPrices = self.History(self.stocks, 17*390, Resolution.Minute)
        # prices = data.history(context.stocks, 'price', 17 * 390, '1m')
        prices = {}
        for tuple in tempPrices.itertuples():
            if tuple.Index[0] not in prices:
                prices[tuple.Index[0]] = []
            prices[tuple.Index[0]].append(tuple.close)
        prices = pd.DataFrame.from_dict(prices)
        
        ret = prices.pct_change()[1:].as_matrix(self.stocks)
        ret_mean = prices.pct_change().mean()
        ret_std = prices.pct_change().std()
        ret_norm = ret_mean / ret_std
        ret_norm = ret_norm.as_matrix(self.stocks)
        ret_norm_max = np.max(ret_norm)
        eps_factor = 0.9 if ret_norm_max > 0 else 1.0
        self.eps = eps_factor * ret_norm_max
        bnds = []
        limits = (0, 1)
        for stock in self.stocks:
            bnds.append(limits)
        bnds = ((0, 1), (0, 1), (0, 1), (0, 1), (0, 1))
        cons = ({'type': 'eq', 'fun': lambda x:  np.sum(x) - 1.0},
                {'type': 'ineq', 'fun': lambda x:  np.dot(x, ret_norm) - self.eps})
                
        ''' Cannot fix the scipy optimize '''        
        # res = scipy.optimize.minimize(self.variance, self.x1, args=ret,
        #                               jac=self.jac_variance, method='SLSQP', constraints=cons, bounds=bnds)
    
        # allocation = np.copy(self.x0)
        # if res.success:    # if SLSQP declares success
        #     self.opt_pass_count += 1
        #     weighted_ret_norm = np.dot(res.x, ret_norm)
        #     w_ret_constraint = weighted_ret_norm - self.eps + self.tol
        #     if(w_ret_constraint > 0):  # and constraint is actually met
        #         self.valid_constraint_count += 1
        #         allocation = res.x
        #         allocation[allocation < 0] = 0
        #         denom = np.sum(allocation)
        #         if denom > 0:
        #             allocation = allocation / denom
        #         # msg = "{0} runs, {1} SLSQP passes, {2} constraints passed".format(
        #         #     context.run_count, context.opt_pass_count,
        #         #     context.valid_constraint_count)
        #         # if(self.run_count > 1000):
        #         #     log.info(msg)
        #     # else:
        #     #     log.info("constraint fail, SLSQP status = {0}".format(res.status))
    
        # # else:
        # #     log.info("SLSQP fail, SLSQP status = {0}".format(res.status))
        self.n += 1
        ''' Need to fix this '''
        self.s += 0.05
        # self.s += allocation

    def trade(self):
        if self.n > 0:
            allocation = self.s / self.n
        else:
            return
    
        self.n = 0
        self.s = np.zeros_like(self.stocks)
        self.x0 = allocation
    
        # if get_open_orders():
        #     return
    
    
    
        for i, stock in enumerate(self.stocks):
            p = allocation[i] * 0.6 * self.leverage
            if p < 0.05:
                self.Liquidate(stock)
            else:
                self.SetHoldings(stock, p)
    
    
    def allocVOL(self):
        vxx = self.volatility
        xiv = self.stability
        WFV_limit = 14  # (Kory used 14 but it becomes a bit too agressive)
        n = 28
        
        vxx_prices = self.History(vxx, n+2, Resolution.Daily)
        if not(len(vxx_prices) > 0):
            vxx_prices = self.History(vxx, n+2, Resolution.Daily)
        tempVxxPrice = {}
        tempVxxLow = {}
        for tuple in vxx_prices.itertuples():
            if tuple.Index[0] not in tempVxxPrice:
                tempVxxPrice[tuple.Index[0]] = []
                tempVxxLow[tuple.Index[0]] = []
            tempVxxPrice[tuple.Index[0]].append(tuple.close)
            tempVxxLow[tuple.Index[0]].append(tuple.low)
        vxx_prices = pd.DataFrame.from_dict(tempVxxPrice)
        vxx_lows = pd.DataFrame.from_dict(tempVxxLow)
        vxx_prices = vxx_prices[:-1]
        vxx_lows = vxx_lows[:-1]
        vxx_highest = vxx_prices.rolling(window=n, center=False).max()
    
        # William's VIX Fix indicator a.k.a. the Synthetic VIX
        WVF = ((vxx_highest - vxx_lows) / (vxx_highest)) * 100

        # Sell position when WVF crosses under 14
        if((float(WVF.iloc[-2]) > WFV_limit) and (float(WVF.iloc[-1]) <= WFV_limit)):
            self.Liquidate(xiv)
    
    def allocSPY(self):
    
        # Inputs Tab Criteria.
        _pd = 28  # "LookBack Period Standard Deviation High")
        bbl = 22  # "Bolinger Band Length")
        mult = 1.05  # "Bollinger Band Standard Devaition Up")
        lb = 22   # "Look Back Period Percentile High")
        ph = .90  # "Highest Percentile - 0.90=90%, 0.95=95%, 0.99=99%")
    
        # Criteria for Down Trend Definition for Filtered Pivots and Aggressive
        # Filtered Pivots
    
        ltLB = 40  # Long-Term Look Back Current Bar Has To Close Below This Value OR Medium Term--Default=40")
        mtLB = 14  # Medium-Term Look Back Current Bar Has To Close Below This Value OR Long Term--Default=14")
        Str = 3  # Entry Price Action Strength--Close > X Bars Back---Default=3")
        
        history = self.History(self.bullish, 2 * _pd + 2, Resolution.Daily)
        spy_close = {}
        spy_lows = {}
        spy_close[self.bullish] = []
        spy_lows[self.bullish] = []
        for tuple in history.loc[self.bullish].itertuples():
            spy_close[self.bullish].append(tuple.close)
            spy_lows[self.bullish].append(tuple.low)
        spy_close = pd.DataFrame.from_dict(spy_close)
        spy_lows = pd.DataFrame.from_dict(spy_lows)
        spy_highest = spy_close.rolling(window=_pd).max()
    
    
        # Williams Vix Fix Formula
        wvf = ((spy_highest - spy_lows) / (spy_highest)) * 100
        sDev = mult * np.std(wvf[-bbl:])
        midLine = np.mean(wvf[-bbl:])
    
        upperBand = midLine + sDev
        rangeHigh = (max(wvf.values[-lb:])) * ph
    
        spy_higher_then_Xdays_back = spy_close.values[-1] > spy_close.values[-Str]
        spy_lower_then_longterm = spy_close.values[-1] < spy_close.values[-ltLB]
        spy_lower_then_midterm = spy_close.values[-1] < spy_close.values[-mtLB]
    
        # Alerts Criteria
        alert = (wvf.values[-1][0] >= upperBand[0] and wvf.values[-1][0] >= rangeHigh[0]) and (wvf.values[-2][0] >= upperBand[0] and wvf.values[-2][0] >= rangeHigh[0])
    
        # spy_higher_then_Xdays_back
    
        if (alert or spy_higher_then_Xdays_back) and (spy_lower_then_longterm or spy_lower_then_midterm):
            self.Liquidate(self.bearish)
            self.SetHoldings(self.bullish, 0.3 * self.leverage)
        else:
            self.Liquidate(self.bullish)
            self.SetHoldings(self.bearish, 0.4 * self.leverage)
    
    
    
    def variance(x, *args):
        p = np.squeeze(np.asarray(args))
        Acov = np.cov(p.T)
        return np.dot(x, np.dot(Acov, x))
    
    def jac_variance(x, *args):
        p = np.squeeze(np.asarray(args))
        Acov = np.cov(p.T)
        return 2 * np.dot(Acov, x)