| 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