Overall Statistics
Total Trades
5719
Average Win
1.96%
Average Loss
-1.20%
Compounding Annual Return
41.511%
Drawdown
80.900%
Expectancy
0.165
Net Profit
498.902%
Sharpe Ratio
1.114
Probabilistic Sharpe Ratio
33.518%
Loss Rate
56%
Win Rate
44%
Profit-Loss Ratio
1.64
Alpha
0.837
Beta
-0.429
Annual Standard Deviation
0.696
Annual Variance
0.485
Information Ratio
0.848
Tracking Error
0.743
Treynor Ratio
-1.805
Total Fees
$298463.30
from Configure import OrderTolerance

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel



class WeightToShareConstruction(PortfolioConstructionModel):
    
    def CreateTargets(self, algorithm, insights):
            targets = []
            for insight in insights:
                quantity = algorithm.CalculateOrderQuantity(insight.Symbol, insight.Weight)
                targets.append(PortfolioTarget(insight.Symbol,quantity))
                
            return targets

class LimitOrderLowImpactExecution(ExecutionModel):
    
    # Fill the supplied portfolio targets efficiently
    def Execute(self, algorithm, targets):
        for target in targets:
            Ticker = target.Symbol.Value
          
            # Sell at bid
            if target.Quantity < 0:
                if abs(target.Quantity) > algorithm.Securities[Ticker].BidSize: algorithm.Debug("Lower Fixed Dollar")
                limitTicket = algorithm.LimitOrder(
                        Ticker, target.Quantity, 
                        algorithm.Securities[Ticker].BidPrice  - OrderTolerance
                        )
            #Buy at ask
            if target.Quantity > 0:
                if abs(target.Quantity) > algorithm.Securities[Ticker].AskSize: algorithm.Debug("Lower Fixed Dollar")
                limitTicket = algorithm.LimitOrder(
                        Ticker, target.Quantity, 
                        algorithm.Securities[Ticker].AskPrice  + OrderTolerance
                        )
            """
            # RiskManager Told Us To Get Out. Only time a target should come back 0.            
            elif target.Quantity == 0:
                HoldingQuantity = algorithm.Portfolio[Ticker].Quantity
                if not algorithm.Portfolio[Ticker].Invested : continue
                # Reverse with long positon at asking price plus whatever youre willing to pay over the price to get it.
                elif algorithm.Portfolio[Ticker].IsShort :
                    price = algorithm.Securities[Ticker].AskPrice + OrderTolerance
                    limitTicket = algorithm.LimitOrder(Ticker, abs(HoldingQuantity), price )
                    continue
                # Reverse with short positon at bidding price
                elif algorithm.Portfolio[Ticker].IsLong :
                    price =  algorithm.Securities[Ticker].BidPrice - OrderTolerance
                    limitTicket = algorithm.LimitOrder(Ticker, -1* abs(HoldingQuantity), price )
                    continue
            """
from Information import *


#### HERE IS WHERE THE MANUAL UNIVERSE SELECTION TICKES ARE DEFINED FOR ALL INTENTS AND PURPOSES #######################
# Append a defined dictionary to add to to the UniverseSelectionModel later on.

# LETFInformation is the NAME OF IMPORTED OBJECT that will be used directly in the algorithm
SubUniverses = [Miners]
LETFInformation, PairsList = LoadSymbolData(SubUniverses)

RollingWindowLength = int(6.5 * 60)
VolatilityLookback = (30 * RollingWindowLength)  # Days * Hours/1 Trading Day * Minutes/Hour
PremiumLevel= .4  # Standard Deviations
DiscountLevel= -.67 # Standard Deviations
OrderTolerance= .03# Cents from current bid/ask against your favor
TakeProfit= .05 # Percentage at which to start to liquidate regardless of Spread
BarSize = 2 #Minutes
#DollarToWeight() converts FixedDollarSize into its current portfolio weight.
FixedDollarSize = 5000
#NoiseFilter is the abs minumum value of the Spread we must overreach before we consider it an Insight.
NoiseFilter = .0001
POV = .99
from clr import AddReference
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Indicators import *
from QuantConnect import Market 


from RiskManager import TakeProfitsPerPair
from OrderAndExecution import WeightToShareConstruction, LimitOrderLowImpactExecution

# Our Universe Selection and Parameters are mutable in Configure
from Configure import LETFInformation, PairsList, PremiumLevel, DiscountLevel, VolatilityLookback, BarSize, FixedDollarSize, NoiseFilter
from collections import deque
import numpy as np


class LETFArb(QCAlgorithmFramework):
    
    def Initialize(self):
        
        self.SetStartDate(2015, 7, 1)  # Set Start Date
        self.SetEndDate(2020, 8, 24)
        self.SetBrokerageModel(BrokerageName.AlphaStreams)
        self.SetCash(round(2*len(PairsList)*1000000))
        
        #Holds any ticker specific data like quotebars
        
        
        self.LETFDataByTicker = {}
        self.PremiumLevel = PremiumLevel
        self.DiscountLevel= DiscountLevel
        
        # Holds pairs related information such as historical spreads
        
        self.AddEquity("SPY", Resolution.Minute)
        self.SetBenchmark("SPY")
        
         # write up our consolidator to update the indicator
        BarPeriod = TimeSpan.FromMinutes(BarSize)
        symbols = []
       
        
        for ticker in list(LETFInformation.keys()):
            
            equity = self.AddEquity(ticker, Resolution.Minute)
            self.LETFDataByTicker[ticker]=  SpreadIndicator(ticker, VolatilityLookback )
            bench = LETFInformation[ticker].TrackingBenchmark
            consolidator =  QuoteBarConsolidator(BarPeriod)
            consolidator.DataConsolidated += self.QuoteBarHandler
            self.SubscriptionManager.AddConsolidator(ticker, consolidator)
            if bench not in self.LETFDataByTicker:
                self.LETFDataByTicker[bench] =  SpreadIndicator(bench, VolatilityLookback)
                
                equity = self.AddEquity(ticker, Resolution.Minute)
                consolidator =  QuoteBarConsolidator(BarPeriod)
                consolidator.DataConsolidated += self.QuoteBarHandler
                self.SubscriptionManager.AddConsolidator(ticker, consolidator)
            
        self.SetExecution(LimitOrderLowImpactExecution())
        self.Settings.FreePortfolioValuePercentage = 0.025
       
        
        ##### Set Universe Selection Model #######    
        for symbol in list(LETFInformation.keys()):
            symbols.append(Symbol.Create(symbol, SecurityType.Equity, Market.USA))
            symbols.append(Symbol.Create(LETFInformation[symbol].TrackingBenchmark, SecurityType.Equity, Market.USA)) 
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
    
      
        ##### Set Portfolio Model #######  
        self.SetPortfolioConstruction( WeightToShareConstruction() )
        #### Set Risk Model #######  
        self.AddRiskManagement( TakeProfitsPerPair() )
         #### Set Execution Model ####### 
        self.SetExecution( LimitOrderLowImpactExecution() )
        
        self.Trade = False
        
    def QuoteBarHandler(self, sender, bar):
        
        ticker = bar.Symbol.Value
        self.Debug(type(bar))
        if bar.Time.hour == 9 and bar.Time.minute == 30:
            self.LETFDataByTicker[ticker].Open = bar
        else: 
            self.LETFDataByTicker[ticker].Current = bar
            #self.LETFDataByTicker[ticker].AskSize = bar.LastAskSize
            #self.LETFDataByTicker[ticker].BidSize = bar.LastBidSize
            self.Trade = True
            
        
        
    ### OnData is where Insights will be Emitted. Implementing an AlphaModel was annoying and not needed.
    def Ready(self):
        for ticker in self.LETFDataByTicker.keys():
            if  self.LETFDataByTicker[ticker].Current is None: return False 
            elif  self.LETFDataByTicker[ticker].Open is None: return False
            else:continue
        return True
        
    
    def OnData(self, data):
        if not self.Trade: return
        else : self.EmitSpreadSignalInsights()
  
    def EmitSpreadSignalInsights(self):
        insights = []
        #Pairs List holds list of tuples (BullTicker,BearTicker) that serve as self hedging pairs.
        for Pairs in PairsList:
        
            BullTicker = Pairs[0]
            BearTicker = Pairs[1]
            # We pass in all of the data so UpdateSpreads can also access the benchmarks quotebars
            self.LETFDataByTicker[BullTicker].UpdateSpreads(self.LETFDataByTicker)
            self.LETFDataByTicker[BearTicker].UpdateSpreads(self.LETFDataByTicker)
                  
            Discount = self.CheckForDiscount(self.LETFDataByTicker[BullTicker],self.LETFDataByTicker[BearTicker])
            Invested = self.Portfolio[BullTicker].Invested or self.Portfolio[BearTicker].Invested
            if Discount: 
                
                if (self.Portfolio[BullTicker].IsLong or self.Portfolio[BearTicker].IsLong) or not Invested: # Buy to Open
                    insights.append(Insight.Price(BullTicker, timedelta(minutes = BarSize), InsightDirection.Up, None, None, None, self.DollarToWeight()))
                    insights.append(Insight.Price(BearTicker, timedelta(minutes = BarSize),InsightDirection.Up, None, None, None, self.DollarToWeight()))
                
                
                if self.Portfolio[BullTicker].IsShort: # Look to close the short position 
                    insights.append(Insight.Price(BullTicker, timedelta(days = BarSize), InsightDirection.Flat, None, None, None, 0))
                
                if self.Portfolio[BearTicker].IsShort:  
                    insights.append(Insight.Price(BearTicker, timedelta(days = BarSize), InsightDirection.Flat, None, None, None, 0))
                    
                    
            else: 
                #Dont check for premium at all if we had a discount event. Just faster than checking for both.
                Premium = self.CheckForPremium(self.LETFDataByTicker[BullTicker],self.LETFDataByTicker[BearTicker])
                
                if Premium:
                    if (self.Portfolio[BullTicker].IsShort or self.Portfolio[BearTicker].IsShort) or not Invested:
                        insights.append(Insight.Price(BullTicker, timedelta(days = BarSize), InsightDirection.Down, None, None, None, -1* self.DollarToWeight()))
                        insights.append(Insight.Price(BearTicker, timedelta(days = BarSize),InsightDirection.Down, None, None, None, -1* self.DollarToWeight()))
                        
                    if self.Portfolio[BullTicker].IsLong: # Look to close the long position 
                        insights.append(Insight.Price(BullTicker, timedelta(days = BarSize), InsightDirection.Flat, None, None, None, 0))
                    
                    if self.Portfolio[BearTicker].IsLong:  
                        insights.append(Insight.Price(BearTicker, timedelta(days = BarSize), InsightDirection.Flat, None, None, None, 0)) 
                        
                else: continue
        self.EmitInsights(Insight.Group(insights)) 
    
       
    def CheckForDiscount(self,BullSpreads,BearSpreads):
        if len(BullSpreads.AskSpread) < 30 or len(BearSpreads.AskSpread) <30 : return False
        Together = BullSpreads.AddOtherLETFSpread(BearSpreads,'Ask')
        Vol =  np.std(Together)
        
        if Together[-1]/Vol < self.DiscountLevel: 
            self.Debug('Discount {}'.format(Together[-1]))
            return True
        else: return False
        
    def CheckForPremium(self,BullSpreads,BearSpreads):
        
        Together = BullSpreads.AddOtherLETFSpread(BearSpreads,'Bid')
        Vol =  np.std(Together)
        if len(BullSpreads.BidSpread) < 30 or len(BearSpreads.BidSpread) <30 : return False
        if Together[-1]/Vol > self.PremiumLevel:
            return True
        else: return False
        
                
    def DollarToWeight(self):
        
        return round(float(FixedDollarSize)/float(self.Portfolio.TotalPortfolioValue),5)
        
""" 
Class Declaration for Spread Indicator and Synopsis of the Thesis behind it
"""
class SpreadIndicator:
    
    def __init__(self, ticker, VolatilityLookback):
        
        self.Symbol = ticker
        
        self.Beta = LETFInformation[self.Symbol].Beta
        self.TrackingBenchmark = LETFInformation[self.Symbol].TrackingBenchmark
        
        self.Current = QuoteBar()
        self.Open =  QuoteBar()
    
        # Deques are faster to work with the RollingWindow
        self.AskSpread = deque(maxlen=VolatilityLookback)
        self.BidSpread = deque(maxlen =VolatilityLookback)
        
         # Spreads are ready?
        self.Ready = False
    ''' self holds QuoteBars for us, and is passed into  UpdateSpreads when it is exposed in LETFAlphaModel.Update() 
    We update spreads as the AlphaModel updates. Makes sense to me. '''
    
    def UpdateSpreads(self,SpreadDataByTicker):
        
        letf_ask_return = self.GetDailyReturn(self,"ask")
        letf_bid_return = self.GetDailyReturn(self,"bid")
        
        bench_ask_return = self.GetDailyReturn(SpreadDataByTicker[self.TrackingBenchmark],"ask")
        bench_bid_return = self.GetDailyReturn( SpreadDataByTicker[self.TrackingBenchmark],"bid")
        # Sample non-Noise only
        if  not abs(letf_bid_return -  self.Beta *  bench_bid_return) < NoiseFilter: self.BidSpread.append( letf_bid_return -  self.Beta *  bench_bid_return)
        if  not abs(letf_ask_return -  self.Beta *  bench_ask_return) < NoiseFilter:self.AskSpread.append( letf_ask_return -  self.Beta *  bench_ask_return)
        
      
    ''' Vectorized Addition of two deques via list comprehension. Returns a list, which is fine since were gonna call np.std() on it.'''
    def AddOtherLETFSpread(self, other_spread_data, which):
        if which =='Ask':
            result = []
            # Beecause we added a min threshold, the deques may not be equal length
            for x in range(len(self.AskSpread)):
               try:
                   result.append(self.AskSpread[x]+other_spread_data.AskSpread[x])
                   
               except IndexError:
                   return result
            return result
            
        elif which == "Bid":
            result = []
            for x in range(len(self.BidSpread)):
               try:
                   result.append(self.BidSpread[x]+other_spread_data.BidSpread[x])
                   
               except IndexError:
                   return result
            return result
    
    def GetDailyReturn(self,data,which):
        '''
        The thesis of this strategy is premised on:
        
        1) The Spread in Daily Cummulative Returns for a Pair of Equal Beta Leveraged ETFs
        is essentially realized Gamma + realized Volatility. Shown in Tim's textbook.
        
        2) Intraday trading is an interesting trading window for LETFs because LETFs * have mandates *:
            
            A) Active market makers ensure that LETFs meet their mandate of "Leveraged Single Day Return".
            (At least in periods of low volatility.) Spreads are managed on a Ticker-by-Ticker basis. 
            Sometimes, there is a significant Spread between an LETF and its mandated expected current daily return. 
            Usually its 0, because market makers do their jobs.
            
            In other words, the excess Gamma and Volatility is managed and mean reverting. But simply going long
            a single Ticker exposes you to Delta. So a Spread in a single LETF on its own is not interesting.
           
            Therefore, we move on to thinking about this Synthetic Asset:
                $D in LETF_TrackingBenchmarkSomeIndex with Beta = X
                $D in LETF_TrackingTheSameIndex with Beta = -X. 
            
            B)  We also know that the returns of LETFs tracking the same index come from the same
            source of randomness. LETFs tracking the same index, by design, are cointegrated. 
            
            In (A) we already mentioned that the Spread is mean-reverting for a given LETF. 
            
            We now have the following conjecture:
            
                If our two LETFs are cointegrated with opposite correlations, and their individual Spreads are mean reverting.
                The Spread of our synthetic asset should be mean reverting around 0.
            
            C) If we go long when the Spread of the Synthetic Asset is significantly letf-tailed. We can potentially scalp Gamma and Vega.
                This is analogous to buying a straddle when gamma or vega is mispriced.
                If we sell during a right-tailed Spread event, we achieve buying high and selling low over time, without
                buying Delta.
        '''
        
        #Use log returns because current daily returns are cummulative from the opening bar
        if which == "ask":
            if data.Open.Ask.Close != 0 :
                return np.log(data.Current.Ask.Close/data.Open.Ask.Close)
            else : return 0
        elif which =="bid":
            if data.Open.Bid.Close != 0 :
                return np.log(data.Current.Bid.Close/data.Open.Bid.Close) 
            else: return 0
from clr import AddReference
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Indicators import *

from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from collections import deque, UserDict
import numpy as np


ll = 0
ul = 3

class Universe(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass


        

EmergingMarkets = [
    ("EFO","EFU",-1,3,3), #Proshares MSCI EAFE
    ("UPV","EPV",-1,3,3), #Proshares MSCI Developed EU
    ("FXP","XPP",-1,3,3), #Proshares MSCI China
    ("EWV","EZJ",-1,3,3)] #Proshares MSCI Japan]
    

ProsharesSectorETF =  [  
    ("UYM","SMN",-1,3,3), #Proshares Dow Jones U.S. Basic Materials    
    ("UBIO","ZBIO",-1,3,3), #Proshares Nasdaq Biotech  3x
    ("BIB","BIS",-1,3,3), #Proshares Nasdaq Biotech  2x
    ("SCOM","UCOM",-1,3,3), #Proshares S&P Communication Services Select Sector  3x
    ("SKF","UYG",-1,3,3), #Proshares Dow Jones U.S. Financials
    ("FINU","FINZ",-1,3,3), #Proshares S&P Financial Select Sector
    ("RXD","RXL",-1,3,3), #Proshares Dow Jones U.S. Health Care
    ("UXI","SIJ",-1,3,3), #Proshares Dow Jones U.S. Industrials
    ("DIG","DUG",-1,3,3), #Proshares Dow Jones U.S. Oil & Gas
    ("SRS","URE",-1,3,3), #Proshares Dow Jones Real Estate
    ("USD","SSG",-1,3,3), #Proshares Dow Jones U.S. Semiconductors
    ("ROM","REW",-1,3,3), #Proshares Dow Jones U.S. Technology
    ("SDP","UPW",-1,3,3)]     

NotLiquid = [
    ("SAA", "SDD"),
    ("MZZ", "MVV", -1,3,3),
    ("UMDD", "SMDD", -1,3,3),
     ("GLL","UGL",-1,3,3),#Proshares Bloomberg Gold Subindex
      ("AGQ","ZSL",-1,3,3),#Proshares Bloomberg Silver Subindex 
         ("YCS","YCL",-1,3,3),
         ("DSLV","USLV",-1,3,3), 
    ("UGLD","DGLD",-1,3,3),
    ("GUSH","DRIP",-1,3,3), #Direxion Oils and Gas Exploration
    ("RUSL","RUSS",-1,3,3), #Direxion Russia
    ("GASL","GASX",-1,3,3), #Direxion Natural Gas
    ("FAZ","FAS",-1,3,3),#Direxion Financials
     ("ERY","ERX",-1,3,3), #Direxion Energy
      ("YINN","YANG",-1,3,3)
      
    ] + EmergingMarkets + ProsharesSectorETF

USTreasury = [
  ("TBT","UBT",-1,3,3), #Proshares ICE U.S. Treasury 20+ Year Bond
  ("PST","UST",-1,3,3), #Proshares ICE U.S. Treasury 7 Year Bond
  ("TMF","TMV",-1,3,3)]
  
LiquidETFCompetition = [
    ("UGAZ","DGAZ",-1,3,3),
("ERY","ERX",-1,3,3),  
("NUGT","DUST",-1,3,3),
("UCO","SCO",-1,3,3),
("NUGT","DUST",-1,3,3),
("TECS","TECL",-1,3,3),
("SOXS","SOXL",-1,3,3)]

SP500 = [  #Proshares SP Small Cap
     #Proshares SP Mid Cap 2x 
     #Proshares SP Mid Cap 3x
    ("SPY", "SH", -1, 3,3), #-1
    ("SDS","SSO",-1,3,3),#Proshares SP500 2x
    ("UPRO","SPXU",-1,3,3), #3x
    ("SPXL","SPXS",-1,3,3)]# 3x

NASDAQ = [ 
    ("TQQQ","SQQQ",-1,2,2), #Proshares Nasdaq 3x
    ("QQQ","PSQ",-1,2,2 ), #1x
    ("QLD","QID",-1,2,2)] #2x
    

Russell2000 = [
    ("SRTY","URTY",-1,ul,ll), #Proshares Russel 3x
    ("RWM","IWM",-1,ul,ll), #Proshares Russel 1x
    ("TWM","UWM",-1,ul,ll)]
    
DirexionETFs = [ 
  ("TECL","TECS",-1,ll,ul),#Direxion Tech 3x
  ("TNA","TZA",-1,ll,ul), #Direxion Small Cap 3x
  ("LABU","LABD",-1,ll,ul), #Direxion Biotech
  ("NUGT","DUST",-1,ll,ul), #Direxion Gold Miners
  ("JNUG","JDST",-1,ll,ul) #Direxion Junior Gold Miners
 ] 
  
Commoditities = [
     ("OILU","OILD",-1,ll,ul), #Proshares Bloomberg WTI Crude Oil Subindex 3x
    ("UCO","SCO",-1,ll,ul),#Proshares Bloomberg WTI Crude Oil Subindex 2x
    ("ERY","ERX",-1,ll,ul)]  
    

def fetch_symbols(Pairs):
    symbols = []
    for info in Pairs:
        symbols.append(info[0])
        symbols.append(info[1])
    return symbols




DJIA  =  Universe()
DJIA.Benchmark = "DIA"
DJIA.Pairs =  [("DIA", 'DOG', -1, ll,ul), #Proshares Dow 1x
    ("SDOW","UDOW",-1),#Proshares Dow 3x
    ("DDM","DXD",-1)
    ] 
    
    
Russel  =  Universe()
Russel.Benchmark = "IWM"
Russel.Pairs =  [
    #("SRTY","URTY",-1,ul,ll), #Proshares Russel 3x
    ("RWM","IWM",-1,ul,ll), #Proshares Russel 1
    #("TWM","UWM",-1,ul,ll)
    ]

     


TradedUniverse = Russel

Bars = 15
PosSize =5000


RiskCap= -.5
Profit = .0003
MinSpread = 0
Z = .68
SlowVol = 30 #Days
BarLookBack = SlowVol*(6.5)*(60)/Bars
PairLookBack =  5


 

class LETFArb(QCAlgorithmFramework):

    def Initialize(self):
        self.SetStartDate(2015, 4, 1)  # Set Start Date
        self.SetEndDate(2019, 3, 2)
        BarPeriod = TimeSpan.FromMinutes(Bars)
       
        
        self.SetBrokerageModel(BrokerageName.AlphaStreams)
        self.BettingSize =  float(1/len(fetch_symbols(TradedUniverse.Pairs)))
        self.Debug(str(self.BettingSize))
        self.SetCash(round(PosSize/self.BettingSize))
    
        
        self.PriceData = {}
        equity = self.AddEquity("VXX", Resolution.Daily)
        self.VIX = RateOfChangePercent("VXX",Resolution.Daily)
        symbol = TradedUniverse.Benchmark
        equity = self.AddEquity(symbol, Resolution.Daily)
      
        
        for symbol in fetch_symbols(TradedUniverse.Pairs):
            equity = self.AddEquity(symbol, Resolution.Minute)
            self.PriceData[symbol] = deque(maxlen=2)
            
           
            
        self.Data = {}
    
       
      
        self.LETFSymbols = []
        for PairsInfo in TradedUniverse.Pairs:
            IndexConsolidator = TradeBarConsolidator(BarPeriod)
            LETFConsolidator= TradeBarConsolidator(BarPeriod)
            self.LETFSymbols.append(PairsInfo[1])
           
            
            data = Universe()
            data.LETFTicker =  PairsInfo[1]
            data.IndexTicker = PairsInfo[0]
            data.Leverage = PairsInfo[2]
            data.Spreads= deque(maxlen= int(BarLookBack))
            data.Pair = deque([],maxlen=PairLookBack)
            
            self.Data[data.LETFTicker] =  data
            
            
           
            IndexConsolidator.DataConsolidated += self.IndexHandler
            LETFConsolidator.DataConsolidated += self.LETFHandler
            
            self.SubscriptionManager.AddConsolidator(self.Data[data.LETFTicker].LETFTicker,LETFConsolidator)
            self.SubscriptionManager.AddConsolidator(self.Data[data.LETFTicker].IndexTicker,IndexConsolidator)
           
           
            
        self.SetExecution(ImmediateExecutionModel())
        self.SetBenchmark("SPY")
        self.IndexUpdated = False
        self.LETFUpdated = False
        symbols = []
        
        for symbol in fetch_symbols(TradedUniverse.Pairs):
            symbols.append(Symbol.Create(symbol, SecurityType.Equity, Market.USA))
        symbols.append(Symbol.Create("TVIX", SecurityType.Equity, Market.USA))
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
        
    
    
    def IndexHandler(self,sender, bar):
        
       
        try:
           Prices =  self.PriceData[bar.Symbol.Value]
        
           Prices.append(bar.Close)
           self.PriceData[bar.Symbol.Value] =  Prices
           self.IndexUpdated = True
            
        except KeyError:
            pass
        
    def LETFHandler(self,sender, bar):
        
        try:
           Prices =  self.PriceData[bar.Symbol.Value]
        
           Prices.append(bar.Close)
           self.PriceData[bar.Symbol.Value] =  Prices
           self.LETFUpdated = True
        except KeyError:
            pass
        
    def NowStale(self):
        self.IndexUpdated = False
        self.LETFUpdated = False
        
        
    def RecordPair(self,Data):
        Pair = Data.Pair
        IndexMV = self.Portfolio[Data.IndexTicker].Quantity * self.Portfolio[Data.IndexTicker].Price
        LETFMV = self.Portfolio[Data.LETFTicker].Quantity * self.Portfolio[Data.LETFTicker].Price
        Pair.append(IndexMV +LETFMV)
        Data.Pair = Pair
        
    def OnData(self, data):
        
        Updated = self.IndexUpdated and self.LETFUpdated
        if Updated:
            
            for key in self.LETFSymbols:
               
               Data = self.Data[key]
               LETFTicker =  Data.LETFTicker
               IndexTicker = Data.IndexTicker
               LETFPrices = self.PriceData[LETFTicker]
               IndexPrices = self.PriceData[IndexTicker]
               
               if len(LETFPrices) != 2: continue
               if len(IndexPrices) != 2: continue
               
               if LETFPrices[-2] !=0 and IndexPrices[-2] !=0:
                   LETFReturn = (LETFPrices[-1] - LETFPrices[-2])/ LETFPrices[-2]
                   IndexReturn = (IndexPrices[-1] - IndexPrices[-2])/ IndexPrices[-2]    
               
                   Spread = np.log(1+LETFReturn) - np.log(1+ Data.Leverage* IndexReturn)
                   Spreads = Data.Spreads
                   
                   
                   Spreads.append(Spread)
                   Data.Spreads = Spreads 
                   
               else: continue  
               
              
               
               OpenPosition = (self.Securities[Data.LETFTicker].Invested) and (self.Securities[Data.IndexTicker].Invested)
               if OpenPosition: self.RecordPair(Data)
                   
               
               
               if len(Data.Spreads) >= BarLookBack:
                    Spread = Data.Spreads[-1]
                    SpreadStds =  np.std(Data.Spreads)
                    Lowerband = -1*Z * SpreadStds
                    Upperband = Z* SpreadStds
                    Discount = Spread <= MinSpread and Spread < Lowerband
                    Premium = Spread >= abs(MinSpread) and Spread > Upperband
                    
               
                    if (Discount and not OpenPosition):
                           
                            
                            LETFInsight =  Insight.Price(LETFTicker, timedelta(Bars), InsightDirection.Up)
                            LETFInsight.Weight =  self.BettingSize
                            
                            IndexInsight =  Insight.Price(IndexTicker, timedelta(Bars), InsightDirection.Down)
                            IndexInsight.Weight =  self.BettingSize
                            
                            insights = [LETFInsight, IndexInsight]
                            
                            self.EmitInsights(Insight.Group(insights))
                            
                            self.SetHoldings([PortfolioTarget(LETFTicker, self.BettingSize), PortfolioTarget(IndexTicker, self.BettingSize)])
                            
                    if (Premium and OpenPosition):
                        self.EmitInsights(Insight.Price(Data.LETFTicker, timedelta(10), InsightDirection.Flat))
                        self.EmitInsights(Insight.Price(Data.IndexTicker, timedelta(10), InsightDirection.Flat))
                        self.Liquidate(Data.LETFTicker)
                        self.Liquidate(Data.IndexTicker)
                        Data.Pair = deque([], maxlen=int(PairLookBack))
            self.NowStale()
        else:
            for key in self.LETFSymbols:
                Data = self.Data[key]
            
                OpenPosition = (self.Securities[Data.LETFTicker].Invested) and (self.Securities[Data.IndexTicker].Invested)
                if OpenPosition:
                    self.RecordPair(Data)
                    Pair = Data.Pair
                    if len(Pair) == 0: continue
                    TotalReturn = (Pair[-1] - Pair[0])/Pair[0]
                    UnrealizedProfit = (self.Portfolio[Data.LETFTicker].UnrealizedProfitPercent + self.Portfolio[Data.IndexTicker].UnrealizedProfitPercent)/100
                    
                    if (UnrealizedProfit > Profit) or UnrealizedProfit< -.02: 
                        self.Debug("Early Exit: {}".format(UnrealizedProfit))                            
                        self.EmitInsights(Insight.Price(Data.LETFTicker, timedelta(10), InsightDirection.Flat))
                        self.EmitInsights(Insight.Price(Data.IndexTicker, timedelta(10), InsightDirection.Flat))
                        self.Liquidate(Data.LETFTicker)
                        self.Liquidate(Data.IndexTicker)
                        Data.Pair = deque([], maxlen=int(PairLookBack))
                    else:continue
''' LETFData holds all relevant fundamentals needed to build its signal '''
class LETFData:
    
    def __init__(self,symbol,benchmark,beta, opposite):
        
        self.TrackingBenchmark = benchmark
        self.Beta = beta
        self.HedgingSymbol = opposite
      

''' LoadSymbolData() takes in list of information dictionaries that are manually defined. Each dictionary represents a SubUniverse of LETFs, ie LETFs which track a unique benchmark.


The function returns two objects:

1) dict SymbolData[LETFTicker:LETFData] -   maps an LETFTicker to its LETFData. This object will be globally exposed in Main.py and LETFAlphaModel in order to 
have to quick access to fundamentals information.

2) list PairsList[(BullETF_Beta1:BearETF_-Beta1)] - Lists of tuples holdings the tickers that would constitute a Pairs Trade 

These objects only have to be created once at runtime, and it simplifies the passing of information within the self.
'''


def LoadSymbolData(dict_list):

    SymbolData = {} #1
    PairsList = [] #2
    
    # iterate over each individual SubUniverse's informaton dictionary
    for info_dict in dict_list:    
        # Then there is more than One Pair and I manaully set the Pairs in a nested dictionary that is retreived with the key "Trade"
        if "Trade"  in info_dict.keys():
            #BullETF_Beta:BearETF_-Beta is the format of the what .items() returns
            for ticker1, ticker2 in info_dict["Trade"].items():
                # Append a tupple of the Pair tickers which we will need later in the AlphaModels.Update() method.
                PairsList.append((ticker1,ticker2))
                
                SymbolData[ticker1] = LETFData(
                    symbol = ticker1, 
                    benchmark = info_dict["Benchmark"], 
                    # The Beta of an LETF is found within the Bull/Bear ETF subdictionaries. "Trade" is conventional and manually written in Bull:Bear format. 
                    beta = info_dict["BullETFs"][ticker1], 
                    opposite = ticker2)
                    
                SymbolData[ticker2] = LETFData(
                    symbol = ticker2,
                    benchmark = info_dict["Benchmark"],
                    beta = info_dict["BearETFs"][ticker2],
                    opposite = ticker1
                    )
        else: #only 1 pair
            bear = list(info_dict["BearETFs"].keys())[0]
            bull = list(info_dict["BullETFs"].keys())[0]
            bench =  info_dict["Benchmark"]
            
            PairsList.append((bull,bear))
            
            SymbolData[bench]= LETFData(
                    symbol = bench,
                    benchmark = info_dict["Benchmark"],
                    beta = 1,
                    opposite = None
                    )
                    
            SymbolData[bull]= LETFData(
                    symbol = bull,
                    benchmark = info_dict["Benchmark"],
                    beta = info_dict["BullETFs"][bull],
                    opposite = bear
                    )
                    
            SymbolData[bear] = (LETFData(
                    symbol = bear,
                    benchmark = info_dict["Benchmark"],
                    beta = info_dict["BearETFs"][bear],
                    opposite = bull))
                    
    return SymbolData, PairsList
    
    

NASDAQ = {
    "Benchmark": "QQQ",
    "BullETFs": 
        {  "TQQQ":3,
            "QLD":2,
            "QQQ":1
        },
    "BearETFs": {
            "SQQQ":-3,
            "QID":-2 ,
            "PSQ":-1
            },
    "Trade":{
        "TQQQ":"SQQQ",
        "QLD":"QID",
        "QQQ":"PSQ"
    }
}

SP500 = {
    "Benchmark": "SPY",
    "BullETFs":  { 
            "UPRO":3,
            "SDS":2,
            "SPY":1
        
    },
    "BearETFs": 
            {
            "SPXY":-3,
            "SSO":-2 ,
            "SH":-1
            },
    "Trade":{
        "UPRO":"SPXU",
        "SDS":"SSO",
        "SPY":"SH"
    }
    
}
   
Russell = {
    "Benchmark": "IWM",
    "BullETFs": 
        {  "TNA":3,
            "URTY":3,
            "UWM":2,
            "IWM":1
        },
    "BearETFs": {
            "TZA":-3 ,
            "SRTY":-3,
            "TWM":-2 ,
            "RWM":-1
            },
    "Trade":{
        "TNA":"TZA",
        "URTY":"SRTY",
        "UWM":"TWM",
        "IWM":"RWM"
    }
}         
Russia= {
    "Benchmark": "RSX",
    "BullETFs":  
        { 
            "RUSL":3
        },
    "BearETFs": 
            {
            "RUSS":-3
            }
            }
            
DevelopedMSCI = {
    "Benchmark": "EFA",
    "BullETFs":  
        { 
            "EFO":2
        },
    "BearETFs": 
            {
            "EFU":-2
            }
            }   

China = {
    "Benchmark": "FXI",
    "BullETFs":  
        { 
            "YINN":3,
            "XXP":2
        },
    "BearETFs": 
            {
            "YANG":-3,
            "FXP":-2,
            "YXI":-1
            },
    "Trade":{
        "YINN":"YANG",
        "XXP":"FXP",
        "FXI":"YXI"
    }
            }

Japan= { 
    "Benchmark": "",
    "BullETFs":  
        { 
            "EZJ":2
        },
    "BearETFs": 
            {
            "EWV":-2
            }
            }

Miners = {
     "Benchmark": "GDX",
    "BullETFs":  
        { 
            "NUGT":2
        },
    "BearETFs": 
            {
            "DUST":-2
            }
            }

JuniorMiners = {
     "Benchmark": "GDXJ",
    "BullETFs":  
        { 
            "JNUG":2
        },
    "BearETFs": 
            {
            "JDST":-2
            }
            }

Gold = {
     "Benchmark": "GLD",
    "BullETFs":  
        { 
            "UGL":2
        },
    "BearETFs": 
            {
            "GLL":-2
            }
            }

DowMaterials = {
     "Benchmark": "IYM",
    "BullETFs":  
        { 
            "UYM":2
        },
    "BearETFs": 
            {
            "SBM":-2
            }
            }

Biotech = {
     "Benchmark": "IBB",
    "BullETFs":  
        { 
            "BIB":2
        },
    "BearETFs": 
            {
            "BIS":-2
            }
            }

DowFinancials = {
    #SEF is the -1x and Bull/Bear defined relative to benchmark
      "Benchmark": "SEF",
    "BullETFs":  
        { 
            "SKF":2 # actually -2x the index
        },
    "BearETFs": 
            {
            "UYG":-2   # actually 2x the index
            }
}

DowHealth = {
      "Benchmark": "IYH",
    "BullETFs":  
        { 
            "RXL":2
        },
    "BearETFs": 
            {
            "RXD":-2   
            }
}



DowIndustrials = {
      "Benchmark": "IYJ",
    "BullETFs":  
        { 
            "UXI":2 
        },
    "BearETFs": 
            {
            "SIJ":-2 
            }
}

DowOilGas = {
      "Benchmark": "IYE",
    "BullETFs":  
        { 
            "DIG":2,
            "IYE":1
        },
    "BearETFs": 
            {
            "DUG":-2,  
            "DDG":-1},
    "Trade":{
        "DIG":"DUG",
        "IYE":"DDG"
    }
}

DowRealEstate = {
      "Benchmark": "IYR",
    "BullETFs":  
        { 
            "URE":2,
            "IYR":1
        },
    "BearETFs": 
            {
            "SRS":-2,  
            "REK":-1},
    "Trade":{
        "URE":"SRS",
        "IYR":"REK"}
}

DowUtilities = {
      "Benchmark": "IDU",
    "BullETFs":  
        { 
            "UPW":2 
        },
    "BearETFs": 
            {
            "SDP":-2 
            }
}

SP500SmallCap = {
      "Benchmark": "IJR",
    "BullETFs":  
        { 
            "SAA":2,
            "IJR":1
        },
    "BearETFs": 
            {
            "SDD":-2, 
            "SBB":-1 
            },
    "Trade":
        {
            "SAA":"SDD",
            "IJR":"SBB"
        }
}

SP500MidCap = {
      "Benchmark": "IJH",
    "BullETFs":  
        { 
            "SMDD":3,
            "MVV":2,
            "IJH":1
        },
    "BearETFs": 
            {
            "SMDD":-3,
            "MZZ": -2,
            "SBB":-1, 
            },
    "Trade": {
        "UMDD":"SMDD",
        "MVV":"MZZ",
        "IJH":"SBB",
        
        
        
    }
}

BloombergSilverIndex =  {
      "Benchmark": "SLV",
    "BullETFs":  
        { 
            "AGQ":2 
        },
    "BearETFs": 
            {
            "ZSL":-2 
            }
}

YenUSD =  {
      "Benchmark": "FXY",
    "BullETFs":  
        { 
            "YCS":2 
        },
    "BearETFs": 
            {
            "YCL":-2 
            }
}

SP500OilGas =  {
      "Benchmark": "XOP",
    "BullETFs":  
        { 
            "GUSH":2 
        },
    "BearETFs": 
            {
            "DRIP":-2 
            }
}

SP500Energy =  {
      "Benchmark": "XLE",
    "BullETFs":  
        { 
            "ERX":2 
        },
    "BearETFs": 
            {
            "ERY":-2 
            }
}


SP500Tech =  {
      "Benchmark": "XLK",
    "BullETFs":  
        { 
            "TECL":2 
        },
    "BearETFs": 
            {
            "TECS":-2 
            }
}
USTreasury = {
      "Benchmark": "TLT",
    "BullETFs":  
        { 
            "TMF":3,  #Direxion
            "UBT":2 
        },
    "BearETFs": 
            {
            "TMV": -3 , #Direxion
            "TBT":-2,
            "TBF": -1
            },
    "Trade": {
            "TMF":"TMV",
            "UBT":"TBT",
            "TLT": "TBF"
    }
}
import Configure as config
from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel



class TakeProfitsPerPair(RiskManagementModel):
  
    def __init__(self, minimumReturnPercent = config.TakeProfit):
        self.TakeProfit = minimumReturnPercent 
        
    def ManageRisk(self, algorithm, targets):
        targets = []
        for target in targets:
            
            Pair = (target.Symbol , config.LETFInformation[target.Symbol].HedgingSymbol)
        
            pnl1 = algorthim.Securities[Pair[0]].Holdings.UnrealizedProfitPercent
            pnl2 = algorthim.Securities[Pair[1]].Holdings.UnrealizedProfitPercent
            if pnl1 + pnl2 > self.TakeProfit:
                # liquidate
                algorith.Debug("took profits")
                targets.append(PortfolioTarget(Pairs[0], 0))
                targets.append(PortfolioTarget(Pairs[1], 0))
            else:
                #keep old target
                targets.append(target)
                
        return targets
# Your New Python Filefrom clr import AddReference
from clr import AddReference
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Indicators import *

from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from collections import deque, UserDict
import numpy as np



class Universe:
    def __init__(self):
        self.Benchmark = ''
        self.BearETFs = dict()
        self.BullETFs = dict()
    
    def FindLeverage(self,ticker):
        if ticker in self.BearETFs: return self.BearETFs[ticker]
        elif ticker in self.BullETFs: return self.BullETFs[ticker]
    
    def ListETFs(self,kind):
        
            if kind == "Bear":
                return list(self.BearETFs.keys())
            elif kind == "Bull":
                return list(self.BullETFs.keys()) + [self.Benchmark] 
            elif kind == "LETF":
                 return list(self.BullETFs.keys()) + list(self.BearETFs.keys())
            elif kind == "All": 
                return list(self.BullETFs.keys()) + list(self.BearETFs.keys()) + [self.Benchmark] 
    def ListEqualBetaPairs(self):
        PairsList = []
        for BearETF in self.ListETFs(kind = "Bear"):
            
            Beta = abs(self.BearETFs[BearETF])
            BullETF  = list(self.BullETFs.keys()) [list(self.BullETFs.values()).index(Beta)]
            PairsList.append(
                {"Bull":BullETF,
                "Bear": BearETF
            }
            )
        return PairsList
Russel  =  Universe()
Russel.Benchmark = "GDX"
Russel.BearETFs = {"DUST":-2,
                    #"RWM":-1,
                   # "TWM":-2,
                    }
Russel.BullETFs = {"NUGT":2,
                    #"UWM":2,
                    #"IWM":1
                    }

       
TradedUniverse = Russel
# Length of container that will hold QuoteBars intraday. 6.5 hours in a Trading Day. 60 Minutes in an hour.
RollingWindowLength = (6.5)*(60)*30
VOL_LOOKBACK = RollingWindowLength   # Days
SIGMA_D = .67
SIGMA_P = .4
# Amount from the bid/ask price for limit order 
CLOSE_ORDER_PENNY_TOLERANCE = .02
OPEN_ORDER_PENNY_TOLERANCE = .02
# There is only X amount shares at the bid/ask. What percentage are you going to order. Anything more the 1 would likely have market impact.
PERCENTAGE_AVAILABLEVOLUME = .95

""" Human Defined Frequencies"""
# How often to look for trades.
TRADE_FREQUENCY = 2 #Minutes
# How often to look for extreme profits and losses, report Net Beta Exposure
TAKE_PROFIT = .3
# POE = Percentage of Equity that triggers a rebalance of Benchmark into MostBullETF
#  A fixed dollar amount per trade
DOLLAR_SIZE = 10000

class LETFArb(QCAlgorithmFramework):

    def Initialize(self):
        self.SetStartDate(2015, 7, 1)  # Set Start Date
        self.SetEndDate(2020, 8, 24)
        self.SetBrokerageModel(BrokerageName.AlphaStreams)
        self.SetCash(round(2*1000000))
        
        #Holds the raw data. Updated with  UpdateQuoteBars() nested withing OnData
        self.DailyQuoteBars = {}
        self.SymbolList = TradedUniverse.ListETFs("All")  #Comes in handy
        
        for symbol in self.SymbolList:
            equity = self.AddEquity(symbol, Resolution.Minute)
            self.DailyQuoteBars[symbol] = RollingWindow[QuoteBar](RollingWindowLength)
            
        self.SetExecution(ImmediateExecutionModel())
        self.Settings.FreePortfolioValuePercentage = 0.025
        self.SetBenchmark("SPY")
       
        symbols = []
        for symbol in self.SymbolList:
            symbols.append(Symbol.Create(symbol, SecurityType.Equity, Market.USA))
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
        
        self.TU = TradedUniverse
        self.BearMV = 0
        self.BullMV = 0
        self.AskSpreads = {}
        self.BidSpreads = {}
        for EqualLeveragePair in self.TU.ListEqualBetaPairs():
            self.AskSpreads[EqualLeveragePair["Bull"],EqualLeveragePair["Bear"]] = RollingWindow[float](VOL_LOOKBACK* RollingWindowLength)
            self.BidSpreads[EqualLeveragePair["Bull"],EqualLeveragePair["Bear"]]=  RollingWindow[float](VOL_LOOKBACK*RollingWindowLength)
        self.Schedule.On(
            self.DateRules.EveryDay(self.TU.Benchmark), 
            self.TimeRules.BeforeMarketClose(self.TU.Benchmark,2), self.ManageBeforeClose)
            
        self.Schedule.On(
            self.DateRules.EveryDay(self.TU.Benchmark), 
            self.TimeRules.AfterMarketOpen(self.TU.Benchmark,4), self.ManageAtMarketOpen)
        self.Schedule.On(
            self.DateRules.EveryDay(self.TU.Benchmark), 
            self.TimeRules.Every(timedelta(minutes=TRADE_FREQUENCY)),self.Trade
             )
    
    
    
    # I prefer OnData() to handle plumbing not logic. Logic is handled periodically with Scheduled Events.  
    
    def OnData(self, data):
        
        AllQuoteBars = self.UpdateQuoteBars(data)
        for EqualLeveragePair in self.TU.ListEqualBetaPairs():
            Bull_Ticker = EqualLeveragePair["Bull"]
            Bear_Ticker = EqualLeveragePair["Bear"]
            
            BullSpread_Ask = self.GetSpread(Bull_Ticker,"Ask") # Find GetSpread(ticker, which_price) .
            BearSpread_Ask = self.GetSpread(Bear_Ticker,"Ask")
            BullSpread_Bid = self.GetSpread(Bull_Ticker,"Bid")
            BearSpread_Bid = self.GetSpread(Bear_Ticker,"Bid")
            
            self.AskSpreads[Bull_Ticker,Bear_Ticker].Add(BullSpread_Ask + BearSpread_Ask)
            self.BidSpreads[Bull_Ticker,Bear_Ticker].Add(BullSpread_Bid + BearSpread_Bid)
        
    ######################### Scheduled Human-Agent Events ########################################
    
    def Trade(self):    
        BenchmarkQuoteBars =  self.DailyQuoteBars[self.TU.Benchmark]
        if BenchmarkQuoteBars.Count <= 5:return
       
        for EqualLeveragePair in self.TU.ListEqualBetaPairs():
                BullTicker = EqualLeveragePair["Bull"]
                BearTicker = EqualLeveragePair["Bear"]
                if self.AskSpreads[BullTicker,BearTicker].Count < RollingWindowLength: return
                self.Signal(BullTicker, BearTicker)
                
    
    def ManageAtMarketOpen(self):
        self.ManageProfits()
    
    def ManageBeforeClose(self):
        self.ManageProfits()
        self.ResetQuoteBars()
        
    def ResetQuoteBars(self):
         for symbol in self.SymbolList:
            self.DailyQuoteBars[symbol] = RollingWindow[QuoteBar](RollingWindowLength)
            
    def ManageProfits(self):
        for EqualLeveragePair in self.TU.ListEqualBetaPairs():
                BullTicker = EqualLeveragePair["Bull"]
                BearTicker = EqualLeveragePair["Bear"]
                # Take it and run
                if self.Portfolio[BullTicker].UnrealizedProfitPercent + self.Portfolio[BearTicker].UnrealizedProfitPercent> TAKE_PROFIT:
                    self.Debug("Managed Profits")
                    
                    
                    if self.Portfolio[BearTicker].IsLong or self.Portfolio[BullTicker].IsLong: self.Sell([BullTicker,BearTicker],"Close")
                    
                    elif self.Portfolio[BearTicker].IsShort or self.Portfolio[BullTicker].IsShort: self.Buy([BullTicker,BearTicker],"Close")
                    
            
    def ManageBeta(self):
        # The idea on not using    
        #elif self.Portfolio[self.TU.Benchmark].HoldingsValue >=  POE * self.Portfolio.TotalPortfolioValue:
        # Show on the console what my the Bearish and Bullish Exposure is. Should about even at all times. 
        #Manage the Beta "TRADE_MANAGEBETA_OFFSET" minutes after Trade()
        self.ReportExposures(Debug=True) 
        
    """
The sum of the cummulative returns for an LETF Pair drifits negative in proportion to realized volatility. 
The longer the time horizon, the larger the *volatility decay*. For t < 1 day, however, market makers have a job to do.
When the Spread in Daily Return drifts beyond what continious time models predict, we can expect market makers to manage the 
realized volatility on a intraday basis. So we can trade the pair as it was a straddle and we were scalping vega.
    """ 
    
    def GetBands(self,Bull_Ticker,Bear_Ticker, kind):
        vals = []
        if kind == 'Ask':
            for t in range(self.AskSpreads[Bull_Ticker,Bear_Ticker].Count):
                vals.append(self.AskSpreads[Bull_Ticker,Bear_Ticker][t])
            
        elif kind == "Bid":
            for t in range(self.BidSpreads[Bull_Ticker,Bear_Ticker].Count):
                vals.append(self.BidSpreads[Bull_Ticker,Bear_Ticker][t])
        return np.mean(vals), np.std(vals)
        
    def Signal(self, Bull_Ticker, Bear_Ticker):
        # Use Asking Prices  more relevant for BUYING. Vice versa for selling
        CurrentAskSpread =  self.AskSpreads[Bull_Ticker,Bear_Ticker][0]
        CurrentBidSpread = self.BidSpreads[Bull_Ticker,Bear_Ticker][0]
        
       
        VolAskSpread = np.std(list(self.AskSpreads[Bull_Ticker,Bear_Ticker]))
    
        VolBidSpread = np.std(list(self.BidSpreads[Bull_Ticker,Bear_Ticker]))
        
        if (CurrentAskSpread)/ VolAskSpread <=  -1* SIGMA_D :
            self.Debug("DISCOUNT {} {}".format(Bull_Ticker, Bear_Ticker))
            self.Debug(str(CurrentAskSpread))
            if not (self.Portfolio[Bull_Ticker].IsLong or self.Portfolio[Bull_Ticker].IsShort) :self.Buy([Bull_Ticker,Bear_Ticker],"Open") 
            if not (self.Portfolio[Bear_Ticker].IsLong or self.Portfolio[Bear_Ticker].IsShort) :self.Buy([Bear_Ticker,Bull_Ticker],"Open")     
             
           
            if self.Portfolio[Bull_Ticker].IsShort:  self.Buy([Bull_Ticker,Bear_Ticker],"Close")
            if self.Portfolio[Bear_Ticker].IsShort:  self.Buy([Bull_Ticker,Bear_Ticker],"Close") 
               
        elif (CurrentBidSpread)/VolBidSpread >  SIGMA_P :
            self.Debug("PREMIUM {} {}".format(Bull_Ticker, Bear_Ticker))
            self.Debug(str(CurrentBidSpread))
            if not (self.Portfolio[Bull_Ticker].IsLong or self.Portfolio[Bull_Ticker].IsShort) :self.Sell([Bull_Ticker,Bear_Ticker],"Open") 
            if not (self.Portfolio[Bear_Ticker].IsLong or self.Portfolio[Bear_Ticker].IsShort) :self.Sell([Bull_Ticker,Bear_Ticker],"Open")  
                
            if self.Portfolio[Bull_Ticker].IsLong:  self.Sell([Bull_Ticker,Bear_Ticker],"Close")
            if self.Portfolio[Bear_Ticker].IsLong:  self.Sell([Bull_Ticker,Bear_Ticker],"Close") 
           
        
######################################## HELPER METHODS #####################################################
    
    def GetSpread(self,Ticker,which_price):
        # GetCurrentDailyReturn() in Plumbing Section
        BenchReturn = self.GetCurrentDailyReturn(self.DailyQuoteBars[self.TU.Benchmark], which_price)
        LETFReturn = self.GetCurrentDailyReturn(self.DailyQuoteBars[Ticker], which_price)
        Beta = self.TU.FindLeverage(Ticker)
        return LETFReturn - Beta* BenchReturn
        
############################### METHODS THAT HANDLE PURCHASING #####################################
    def Buy(self,Tickers, OpenClose):
       
        if OpenClose == "Open":
            for Ticker in Tickers:
                
                quantity = self.CalculateOrderQuantity(Ticker, self.DollarsToWeight())
                
                MaxOrderSize =  self.DailyQuoteBars[Ticker][0].LastAskSize*PERCENTAGE_AVAILABLEVOLUME
                if quantity > MaxOrderSize: self.Debug("Increase PERCENTAGE_AVAILABLEVOLUME or Lower DOLLAR_SIZE")
                if quantity != 0:
                    limitTicket = self.LimitOrder(
                        Ticker, quantity, 
                        self.DailyQuoteBars[Ticker][0].Ask.Close  + OPEN_ORDER_PENNY_TOLERANCE
                        )
                    self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity,Ticker))
        elif OpenClose== "Close":
            for Ticker in Tickers: 
                quantity_owned =  self.Portfolio[Ticker].Quantity 
                if quantity_owned < 0:
                    limitTicket = self.LimitOrder(
                        Ticker, abs(quantity_owned), 
                        self.DailyQuoteBars[Ticker][0].Ask.Close  + CLOSE_ORDER_PENNY_TOLERANCE
                        )
                    self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity_owned,Ticker))
    def Sell(self,Tickers, OpenClose):
        
        if OpenClose == "Open":
            
            for Ticker in Tickers:
                quantity = self.CalculateOrderQuantity(Ticker, self.DollarsToWeight())
                MaxOrderSize =  self.DailyQuoteBars[Ticker][0].LastBidSize*PERCENTAGE_AVAILABLEVOLUME
                
                if quantity > MaxOrderSize: self.Debug("Increase PERCENTAGE_AVAILABLEVOLUME or Lower DOLLAR_SIZE")
                if quantity ==0:  self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity,Ticker))
                else:   
                    limitTicket = self.LimitOrder(
                        Ticker, -1* quantity, 
                        self.DailyQuoteBars[Ticker][0].Bid.Close  - OPEN_ORDER_PENNY_TOLERANCE
                        )
                self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity,Ticker))
        elif OpenClose== "Close":
            for Ticker in Tickers: 
                quantity_owned =  self.Portfolio[Ticker].Quantity 
                if quantity_owned > 0:
                    limitTicket = self.LimitOrder(
                        Ticker, -1*quantity_owned, 
                        self.DailyQuoteBars[Ticker][0].Bid.Close -  CLOSE_ORDER_PENNY_TOLERANCE )           
                    self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity_owned,Ticker))
######################################## Handles Plumbing ########################################################    
#### Update the QuoteData container###
    def UpdateQuoteBars(self,data):
        AllQuoteBars = data.QuoteBars
        for symbol in self.SymbolList:
            QuoteBars=self.DailyQuoteBars[symbol]
            if AllQuoteBars.ContainsKey(symbol): 
                QuoteBars.Add(AllQuoteBars[symbol])
                self.DailyQuoteBars[symbol] = QuoteBars
        return AllQuoteBars

## Get cummulative log return for two prices out of a RollingWIndow<quote>
    def GetCurrentDailyReturn(self, quoterollingwindow, kind):
        if kind == 'Ask':return np.log(quoterollingwindow[0].Ask.Close/quoterollingwindow[quoterollingwindow.Count-1].Ask.Close)
        elif kind == 'Bid':return np.log(quoterollingwindow[0].Bid.Close/quoterollingwindow[quoterollingwindow.Count-1].Bid.Close)
        else : return np.log(quoterollingwindow[0].Close/quoterollingwindow[quoterollingwindow.Count-1].Close)
    def ReportExposures(self, Debug):
        self.UpdateMarketValues()
        if Debug: self.Debug("Bullish Exposure {}, Bearish Exposure {}".format(self.BullMV, self.BearMV))
    
    def UpdateMarketValues(self):
        self.BearMV = self.GetMarketValue("Bear")
        self.BullMV =  self.GetMarketValue("Bull")
        
    def GetMarketValue(self, side):
                mv = 0
                for ticker in self.TU.ListETFs(side):
                    mv += self.Portfolio[ticker].HoldingsValue
                return mv   
                
    def DollarsToWeight(self):
        
        return round(float(DOLLAR_SIZE)/float(self.Portfolio.TotalPortfolioValue),5)
# Your New Python Filefrom clr import AddReference
from clr import AddReference
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.UniverseSelection import *
from QuantConnect.Indicators import *
from QuantConnect import Market 


from RiskManager import TakeProfitsPerPair
from OrderAndExecution import WeightToShareConstruction, LimitOrderLowImpactExecution

# Our Universe Selection and Parameters are mutable in Configure
from Configure import LETFInformation, PairsList, PremiumLevel, DiscountLevel, VolatilityLookback, BarSize, FixedDollarSize, TakeProfit, POV, RollingWindowLength, OrderTolerance
from collections import deque
import numpy as np

class LETFArb(QCAlgorithmFramework):

    def Initialize(self):
        self.SetStartDate(2015, 7, 1)  # Set Start Date
        self.SetEndDate(2020, 8, 24)
        self.SetBrokerageModel(BrokerageName.AlphaStreams)
        self.SetCash(round( 2* len(PairsList) *1000000))
        
        #Holds the raw data. Updated with  UpdateQuoteBars() nested withing OnData
        self.DailyQuoteBars = {}
        
        for symbol in LETFInformation.keys():
            equity = self.AddEquity(symbol, Resolution.Minute)
            self.DailyQuoteBars[symbol] = RollingWindow[QuoteBar](RollingWindowLength)
            
        self.SetExecution(ImmediateExecutionModel())
        self.Settings.FreePortfolioValuePercentage = 0.025
        
        equity = self.AddEquity("SPY", Resolution.Minute)
        self.SetBenchmark("SPY")
       
        symbols = []
        for symbol in LETFInformation.keys():
            symbols.append(Symbol.Create(symbol, SecurityType.Equity, Market.USA))
        self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
        
        # Holds pairwise Rolling Window of asking and bidding spreads data
        self.AskSpreads = {}
        self.BidSpreads = {}
        for EqualLeveragePair in PairsList:
            self.AskSpreads[EqualLeveragePair[0],EqualLeveragePair[1]] = RollingWindow[float](VolatilityLookback)
            self.BidSpreads[EqualLeveragePair[0],EqualLeveragePair[1]]=  RollingWindow[float](VolatilityLookback)
        
        
        ### Scheduled Events to handle logic and risk managment is intuitive to me ###
        
        # ManageBeforeClose() - What am I doing EOD?
        self.Schedule.On(
            self.DateRules.EveryDay("SPY"), 
            self.TimeRules.BeforeMarketClose("SPY",1), self.ManageBeforeClose)
        
        # ManageBeforeClose() - What am I doing BOD?    
        self.Schedule.On(
            self.DateRules.EveryDay("SPY"), 
            self.TimeRules.AfterMarketOpen("SPY",4), self.ManageAtMarketOpen)
            
        # Trade() implements my logic on a scheduled basis.    
        self.Schedule.On(
            self.DateRules.EveryDay("SPY"), 
            self.TimeRules.Every(timedelta(minutes=BarSize)),self.Trade
             )
    
    
    # I prefer OnData() to handle plumbing not logic. Logic is handled periodically with Scheduled Events.  See above ^^^ .
    
    #### 1) Update the DailyQuoteData container with data passed in OnData()###
    def UpdateQuoteBars(self,data):
        AllQuoteBars = data.QuoteBars
        
        for symbol in LETFInformation.keys():
            # Daily quote was have saved so far.
            QuoteBars=self.DailyQuoteBars[symbol]
            # did OnData give us new quotes in data?
            if AllQuoteBars.ContainsKey(symbol): 
                QuoteBars.Add(AllQuoteBars[symbol])
                self.DailyQuoteBars[symbol] = QuoteBars
        return AllQuoteBars
    
    
      #### 2) Get the Spread of the LETF with respect to its TrackingBenchmark and Expected Daily Return. We Look at Ask and Bid Seperately.  
    def GetSpread(self,Ticker,which_price):
        # GetCurrentDailyReturn() get todays current log return.
        BenchReturn = self.GetCurrentDailyReturn(self.DailyQuoteBars[LETFInformation[Ticker].TrackingBenchmark], which_price)
        LETFReturn = self.GetCurrentDailyReturn(self.DailyQuoteBars[Ticker], which_price)
        Beta = LETFInformation[Ticker].Beta
        # The fundamental relationship LETFs promise. Spreads occur from gamma , realized vol, market makers pulling out of their role in maintaining parity.
        return LETFReturn - Beta* BenchReturn
    
    def GetCurrentDailyReturn(self, quoterollingwindow, kind):
        if kind == 'Ask':return np.log(quoterollingwindow[0].Ask.Close/quoterollingwindow[quoterollingwindow.Count-1].Ask.Close)
        elif kind == 'Bid':return np.log(quoterollingwindow[0].Bid.Close/quoterollingwindow[quoterollingwindow.Count-1].Bid.Close)
        else : return np.log(quoterollingwindow[0].Close/quoterollingwindow[quoterollingwindow.Count-1].Close)
        
    def OnData(self, data):
        # 1)
        AllQuoteBars = self.UpdateQuoteBars(data)
        # Pairslist is defined in the Config file and comes from static fundamental data
        for EqualLeveragePair in PairsList:
            Bull_Ticker = EqualLeveragePair[0]
            Bear_Ticker = EqualLeveragePair[1]
            #2) 
            BullSpread_Ask = self.GetSpread(Bull_Ticker,"Ask") # Find GetSpread(ticker, which_price) .
            BearSpread_Ask = self.GetSpread(Bear_Ticker,"Ask")
            BullSpread_Bid = self.GetSpread(Bull_Ticker,"Bid")
            BearSpread_Bid = self.GetSpread(Bear_Ticker,"Bid")
            #Update Spread RollingWindows
            self.AskSpreads[Bull_Ticker,Bear_Ticker].Add(BullSpread_Ask + BearSpread_Ask)
            self.BidSpreads[Bull_Ticker,Bear_Ticker].Add(BullSpread_Bid + BearSpread_Bid)
        
    ######################### Scheduled Human-Agent Events ie, The "Logic" #######################################
    
    def Trade(self):    
        for EqualLeveragePair in PairsList:
                BullTicker = EqualLeveragePair[0]
                BearTicker = EqualLeveragePair[1]
                BenchmarkQuoteBars =  self.DailyQuoteBars[LETFInformation[BullTicker].TrackingBenchmark]
                # Are We Ready to Trade? 
                if BenchmarkQuoteBars.Count <= 5:return # We dont even have 5 minutes of daily data yet
                if self.AskSpreads[BullTicker,BearTicker].Count < RollingWindowLength: return # We dont even have a days worth of Spreads to estimate off of.
                # Look for Signals an
                self.SignalAndTradeExecution(BullTicker, BearTicker)
                
    def ManageAtMarketOpen(self):
        self.ManageProfits()
    
    def ManageBeforeClose(self):
        self.ManageProfits()
        self.ResetQuoteBars()
        
    # New day, new data. RollingWindowLength is set in Configure to be one trading day's worth of minute bars.   
    def ResetQuoteBars(self):
         for symbol in LETFInformation.keys():
            self.DailyQuoteBars[symbol] = RollingWindow[QuoteBar](RollingWindowLength)
    
            
    def ManageProfits(self):
        for EqualLeveragePair in PairsList:
                BullTicker = EqualLeveragePair[0]
                BearTicker = EqualLeveragePair[1]
                # Take it and run
                if self.Portfolio[BullTicker].UnrealizedProfitPercent + self.Portfolio[BearTicker].UnrealizedProfitPercent> TakeProfit:
                    self.Debug("Managed Profits")
                    
                    
                    if self.Portfolio[BearTicker].IsLong or self.Portfolio[BullTicker].IsLong: self.Sell([BullTicker,BearTicker],"Close")
                    
                    elif self.Portfolio[BearTicker].IsShort or self.Portfolio[BullTicker].IsShort: self.Buy([BullTicker,BearTicker],"Close")
                    
            
    def ManageBeta(self):
        # The idea on not using    
        #elif self.Portfolio[self.TU.Benchmark].HoldingsValue >=  POE * self.Portfolio.TotalPortfolioValue:
        # Show on the console what my the Bearish and Bullish Exposure is. Should about even at all times. 
        #Manage the Beta "TRADE_MANAGEBETA_OFFSET" minutes after Trade()
        self.ReportExposures(Debug=True) 
        
    """
The sum of the cummulative returns for an LETF Pair drifits negative in proportion to realized volatility. 
The longer the time horizon, the larger the *volatility decay*. For t < 1 day, however, market makers have a job to do.
When the Spread in Daily Return drifts beyond what continious time models predict, we can expect market makers to manage the 
realized volatility on a intraday basis. So we can trade the pair as it was a straddle and we were scalping vega.
    """ 
 
        
    def SignalAndTradeExecution(self, Bull_Ticker, Bear_Ticker):
        # Use Asking Prices  more relevant for BUYING. Vice versa for selling
        CurrentAskSpread =  self.AskSpreads[Bull_Ticker,Bear_Ticker][0]
        CurrentBidSpread = self.BidSpreads[Bull_Ticker,Bear_Ticker][0]
        
        VolAskSpread = np.std(list(self.AskSpreads[Bull_Ticker,Bear_Ticker]))
        VolBidSpread = np.std(list(self.BidSpreads[Bull_Ticker,Bear_Ticker]))
       
        if (CurrentAskSpread)/ VolAskSpread <=  DiscountLevel : # Defined in Configure.py
            self.Debug("DISCOUNT {} {}".format(Bull_Ticker, Bear_Ticker))
            self.Debug(str(CurrentAskSpread))
            
            if not (self.Portfolio[Bull_Ticker].IsLong or self.Portfolio[Bull_Ticker].IsShort) :self.Buy([Bull_Ticker,Bear_Ticker],"Open") 
            if not (self.Portfolio[Bear_Ticker].IsLong or self.Portfolio[Bear_Ticker].IsShort) :self.Buy([Bear_Ticker,Bull_Ticker],"Open")     
             
            # See Methods that handle Purchasing. 
            if self.Portfolio[Bull_Ticker].IsShort:  self.Buy([Bull_Ticker,Bear_Ticker],"Close")
            if self.Portfolio[Bear_Ticker].IsShort:  self.Buy([Bull_Ticker,Bear_Ticker],"Close") 
               
        elif (CurrentBidSpread)/VolBidSpread >  PremiumLevel :
            self.Debug("PREMIUM {} {}".format(Bull_Ticker, Bear_Ticker))
            self.Debug(str(CurrentBidSpread))
            if not (self.Portfolio[Bull_Ticker].IsLong or self.Portfolio[Bull_Ticker].IsShort) :self.Sell([Bull_Ticker,Bear_Ticker],"Open") 
            elif  (self.Portfolio[Bear_Ticker].IsLong or self.Portfolio[Bear_Ticker].IsShort) :self.Sell([Bull_Ticker,Bear_Ticker],"Open")  
                
            if self.Portfolio[Bull_Ticker].IsLong:  self.Sell([Bull_Ticker,Bear_Ticker],"Close")
            if self.Portfolio[Bear_Ticker].IsLong:  self.Sell([Bull_Ticker,Bear_Ticker],"Close") 
           
    """ 
    Methods that handle purchasing. 
    
    Two methods are:
    
    Buy() and Sell(). 
    
    Each method takes in :
        1 ) OpenClose - a tag called  that tells the algorithm whether were are buying/selling to open or to close.
        2)  Tickers - a List of Ticker to create limit orders for. Almost always will be a pair of ticker to ensure we are always placing dollar neutral orders. 
    
    """
    def Buy(self,Tickers, OpenClose):
       
        if OpenClose == "Open":
            for Ticker in Tickers:
                
                quantity = self.CalculateOrderQuantity(Ticker, self.DollarsToWeight())
                
                MaxOrderSize =  self.DailyQuoteBars[Ticker][0].LastAskSize*POV
                if quantity > MaxOrderSize: self.Debug("Increase POV or Lower DOLLAR_SIZE")
                if quantity != 0:
                    limitTicket = self.LimitOrder(
                        Ticker, quantity, 
                        self.DailyQuoteBars[Ticker][0].Ask.Close  + OrderTolerance
                        )
                    self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity,Ticker))
        elif OpenClose== "Close":
            for Ticker in Tickers: 
                quantity_owned =  self.Portfolio[Ticker].Quantity 
                if quantity_owned < 0:
                    limitTicket = self.LimitOrder(
                        Ticker, abs(quantity_owned), 
                        self.DailyQuoteBars[Ticker][0].Ask.Close  + OrderTolerance
                        )
                    self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity_owned,Ticker))
    def Sell(self,Tickers, OpenClose):
        
        if OpenClose == "Open":
            
            for Ticker in Tickers:
                quantity = self.CalculateOrderQuantity(Ticker, self.DollarsToWeight())
                MaxOrderSize =  self.DailyQuoteBars[Ticker][0].LastBidSize*POV
                
                if quantity > MaxOrderSize: self.Debug("Increase POV or Lower Fixed Dollar")
                if quantity ==0:  self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity,Ticker))
                else:   
                    limitTicket = self.LimitOrder(
                        Ticker, -1* quantity, 
                        self.DailyQuoteBars[Ticker][0].Bid.Close  - OrderTolerance
                        )
                self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity,Ticker))
        elif OpenClose== "Close":
            for Ticker in Tickers: 
                quantity_owned =  self.Portfolio[Ticker].Quantity 
                if quantity_owned > 0:
                    limitTicket = self.LimitOrder(
                        Ticker, -1*quantity_owned, 
                        self.DailyQuoteBars[Ticker][0].Bid.Close -  OrderTolerance )           
                    self.Debug("{} Shares {} Shares of {}".format(OpenClose, quantity_owned,Ticker))
######################################## Handles Plumbing ########################################################    


## Get cummulative log return for two prices out of a RollingWIndow<quote>
  
    def ReportExposures(self, Debug):
        self.UpdateMarketValues()
        if Debug: self.Debug("Bullish Exposure {}, Bearish Exposure {}".format(self.BullMV, self.BearMV))
    
    def UpdateMarketValues(self):
        self.BearMV = self.GetMarketValue("Bear")
        self.BullMV =  self.GetMarketValue("Bull")
        
    def GetMarketValue(self, side):
                mv = 0
                for ticker in self.TU.ListETFs(side):
                    mv += self.Portfolio[ticker].HoldingsValue
                return mv   
                
    def DollarsToWeight(self):
        
        return round(float(FixedDollarSize)/float(self.Portfolio.TotalPortfolioValue),5)