Overall Statistics
Total Trades
68
Average Win
0.22%
Average Loss
-0.49%
Compounding Annual Return
6.073%
Drawdown
1.300%
Expectancy
0.278
Net Profit
4.778%
Sharpe Ratio
1.652
Loss Rate
12%
Win Rate
88%
Profit-Loss Ratio
0.45
Alpha
0.051
Beta
-0.012
Annual Standard Deviation
0.029
Annual Variance
0.001
Information Ratio
-1.199
Tracking Error
0.128
Treynor Ratio
-3.945
Total Fees
$144.54
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from QuantConnect import Resolution, Extensions
from QuantConnect.Algorithm.Framework.Alphas import *
from itertools import groupby
from datetime import datetime, timedelta
from pytz import utc

UTCMIN = datetime.min.replace(tzinfo=utc)

class SimpleInsightPortfolioConstructionModel(PortfolioConstructionModel):
    '''Provides an implementation of IPortfolioConstructionModel that allocates percent of account
    to each insight, defaulting to 3%.
    For insights of direction InsightDirection.Up, long targets are returned and
    for insights of direction InsightDirection.Down, short targets are returned.
    No rebalancing shall be done, as a new insight or the age of the insight shall determine whether to exit
    the positions.
    Rules:
        1. On Up insight, increase position size by percent
        2. On Down insight, decrease position size by percent
        3. On Flat insight, move by percent towards 0
        4. On expired insight, perform a Flat insight'''

    def __init__(self, percent = 0.03):
        '''Initialize a new instance of SimpleInsightPortfolioConstructionModel
        Args:
            percent: percent of portfolio to allocate to each position'''
        self.insightCollection = InsightCollection()
        self.removedSymbols = []
        self.nextExpiryTime = UTCMIN
        self.percent = abs(percent)
        self.positionSizes = {}
        self.usedInsight = {}
        self.expiredList = {}

    def ShouldCreateTargetForInsight(self, insight):
        '''Method that will determine if the portfolio construction model should create a
        target for this insight
        Args:
            insight: The insight to create a target for'''
        # We can probably use this to do something smarter
        return True

    def DetermineTargetPercent(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        result = {}
        
        for insight in activeInsights:
            if insight in self.usedInsight:
                continue
            self.usedInsight[insight] = 1
            if insight.Symbol in self.positionSizes:
                self.positionSizes[insight.Symbol] += self.percent * insight.Direction
            else:
                self.positionSizes[insight.Symbol] = self.percent * insight.Direction
            
            if insight.Direction == 0:
                # We received a Flat
                
                # if adding or subtracting will push past 0, then make it 0
                if abs(self.positionSizes[insight.Symbol]) < self.percent:
                    self.positionSizes[insight.Symbol] = 0
                
                # otherwise, we flatten by percent
                if self.positionSizes[insight.Symbol] > 0:
                    self.positionSizes[insight.Symbol] -= self.percent
                if self.positionSizes[insight.Symbol] < 0:
                    self.positionSizes[insight.Symbol] += self.percent
                    
            result[insight] = self.positionSizes[insight.Symbol]
        return result

    def UpdateExpiredInsights(self, activeInsights):
        '''Will determine the target percent for each insight
        Args:
            activeInsights: The active insights to generate a target for'''
        result = {}
        
        for insight in activeInsights:
            if insight in self.expiredList:
                continue
            self.expiredList[insight] = 1
            
            # if an expiring insight pushes it past 0, then flatten to 0
            if abs(self.positionSizes[insight.Symbol]) < self.percent and insight.Direction != 0:
                self.positionSizes[insight.Symbol] = 0
            else:
                self.positionSizes[insight.Symbol] -= self.percent * insight.Direction
            result[insight] = self.positionSizes[insight.Symbol]
        return result

    def CreateTargets(self, algorithm, insights):
        '''Create portfolio targets from the specified insights
        Args:
            algorithm: The algorithm instance
            insights: The insights to create portfolio targets from
        Returns:
            An enumerable of portfolio targets to be sent to the execution model'''

        targets = []

        if (algorithm.UtcTime <= self.nextExpiryTime and len(insights) == 0 and self.removedSymbols is None):
            return targets

        for insight in insights:
            if self.ShouldCreateTargetForInsight(insight):
                self.insightCollection.Add(insight)

        # Create flatten target for each security that was removed from the universe
        if self.removedSymbols is not None:
            universeDeselectionTargets = [ PortfolioTarget(symbol, 0) for symbol in self.removedSymbols ]
            targets.extend(universeDeselectionTargets)
            self.removedSymbols = None

        # Get insight that haven't expired of each symbol that is still in the universe
        activeInsights = self.insightCollection.GetActiveInsights(algorithm.UtcTime)

        # Get the last generated active insight for each symbol
        lastActiveInsights = []
        for symbol, g in groupby(activeInsights, lambda x: x.Symbol):
            lastActiveInsights.append(sorted(g, key = lambda x: x.GeneratedTimeUtc)[-1])

        # Determine target percent for the given insights
        percents = self.DetermineTargetPercent(lastActiveInsights)

        errorSymbols = {}
        for insight in percents:
            target = PortfolioTarget.Percent(algorithm, insight.Symbol, percents[insight])
            if not target is None:
                targets.append(target)
            else:
                errorSymbols[insight.Symbol] = insight.Symbol

        # Get expired insights and create flatten targets for each symbol
        expiredInsights = self.insightCollection.RemoveExpiredInsights(algorithm.UtcTime)

        percents = self.UpdateExpiredInsights(expiredInsights)
        for insight in percents:
            target = PortfolioTarget.Percent(algorithm, insight.Symbol, percents[insight])
            if not target is None:
                targets.append(target)
            else:
                errorSymbols[insight.Symbol] = insight.Symbol
                
        self.nextExpiryTime = self.insightCollection.GetNextExpiryTime()
        if self.nextExpiryTime is None:
            self.nextExpiryTime = UTCMIN
        return targets

    def OnSecuritiesChanged(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''

        # Get removed symbol and invalidate them in the insight collection
        self.removedSymbols = [x.Symbol for x in changes.RemovedSecurities]
        self.insightCollection.Clear(self.removedSymbols)

class TransdimensionalResistanceAtmosphericScrubbers(QCAlgorithm):

    def Initialize(self):
        # Set Start Date so that backtest has 5+ years of data
        self.SetStartDate(2019, 1, 3)

        # No need to set End Date as the final submission will be tested
        # up until the review date

        # Set $1m Strategy Cash to trade significant AUM
        self.SetCash(1000000)

        # Add a relevant benchmark, with the default being SPY
        self.AddEquity('SPY', Resolution.Daily)
        self.SetBenchmark('SPY')

        # Use the Alpha Streams Brokerage Model, developed in conjunction with
        # funds to model their actual fees, costs, etc.
        # Please do not add any additional reality modelling, such as Slippage, Fees, Buying Power, etc.
        self.SetBrokerageModel(AlphaStreamsBrokerageModel())

        self.SetExecution(ImmediateExecutionModel())

        self.SetPortfolioConstruction(SimpleInsightPortfolioConstructionModel())

        self.SetUniverseSelection(TechnologyETFUniverse())

        self.universe = { }
        
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen('SPY', 30), self.ResetTrades)
        self.traded = 0

    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''
        pass
        
    
    def ResetTrades(self):
        if self.traded == 0:
            for symbol, assetData in self.universe.items():
                ins = Insight.Price(symbol, timedelta(10), InsightDirection.Up)
                self.EmitInsights(ins)
        if self.traded == 2:
            for symbol, assetData in self.universe.items():
                ins = Insight.Price(symbol, timedelta(10), InsightDirection.Up)
                self.EmitInsights(ins)
                
        self.traded = self.traded + 1

        
    # Initializing ETF Universe Securities
    def OnSecuritiesChanged(self, changes):
        for s in changes.AddedSecurities:
            if s.Symbol not in self.universe:
                self.universe[s.Symbol] = s.Symbol