| Overall Statistics |
|
Total Trades 1133 Average Win 1.28% Average Loss -0.46% Compounding Annual Return 77.302% Drawdown 19.000% Expectancy 1.158 Net Profit 1800.905% Sharpe Ratio 2.288 Probabilistic Sharpe Ratio 99.431% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 2.78 Alpha 0.505 Beta 0.135 Annual Standard Deviation 0.229 Annual Variance 0.052 Information Ratio 1.454 Tracking Error 0.264 Treynor Ratio 3.888 Total Fees $1600645.67 Estimated Strategy Capacity $660000.00 Lowest Capacity Asset DOTUSD E3 |
from AlgorithmImports import *
class NewImmediateExecutionModel(ExecutionModel):
'''Provides an implementation of IExecutionModel that immediately submits market orders to achieve the desired portfolio targets'''
def __init__(self):
'''Initializes a new instance of the ImmediateExecutionModel class'''
self.targetsCollection = PortfolioTargetCollection()
self.one = 1
self.zero = self.one - self.one
def Execute(self, algorithm, targets):
'''Immediately submits orders for the specified portfolio targets.
Args:
algorithm: The algorithm instance
targets: The portfolio targets to be ordered'''
self.targetsCollection.AddRange(targets)
if self.targetsCollection.Count > self.zero:
for target in self.targetsCollection.OrderByMarginImpact(algorithm):
security = algorithm.Securities[target.Symbol]
# calculate remaining quantity to be ordered
quantity = OrderSizing.GetUnorderedQuantity(algorithm, target, security)
if quantity != self.zero:
aboveMinimumPortfolio = BuyingPowerModelExtensions.AboveMinimumOrderMarginPortfolioPercentage(security.BuyingPowerModel, security, quantity, algorithm.Portfolio, algorithm.Settings.MinimumOrderMarginPortfolioPercentage)
if aboveMinimumPortfolio:
if quantity < self.zero and quantity < -algorithm.Portfolio[target.Symbol].Quantity:
continue
if abs(quantity) < 0.0000001:
continue
algorithm.MarketOrder(security, quantity)
self.targetsCollection.ClearFulfilled(algorithm)from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
import statistics
class LongOnlyAlphaModel(AlphaModel):
def __init__(self, period, recalculationDayNumber, refusedSymbols, maxExposure):
# 0 / 1 CANCEL INCORRECT PARAMETERS CALCULATIONS
self.one = 1
self.zero = self.one - self.one
self.two = self.one + self.one
# Initializations
self.securities = []
self.period = period
self.recalculationDayNumber = recalculationDayNumber
self.refusedSymbols = refusedSymbols
self.maxExposure = maxExposure
self.isNewDay = False
self.lastDay = self.zero
self.counterDay = self.two * self.two
def Update(self, algorithm, data):
# New Day analysis
if self.lastDay == algorithm.Time.day:
self.isNewDay = False
else:
if algorithm.Time.hour == self.zero and algorithm.Time.minute == self.zero:
self.isNewDay = True
self.lastDay = algorithm.Time.day
self.counterDay += self.one
insights = [] # List to store the new insights
symbol_data = {}
# Append insights at (re)launch or between Monday and Tuesday at midnight
if (algorithm.Time.weekday() == self.recalculationDayNumber or self.counterDay > 6) and self.isNewDay == True:
sum_inverse_stdev = self.zero
unavailableSymbols = []
longSymbols = []
allSymbols = [x.Symbol for x in self.securities if x.Symbol not in self.refusedSymbols]
weight = self.zero
magnitude = self.one
confidence = self.one
for symbol in allSymbols:
symbol_history = algorithm.History(symbol, self.period, Resolution.Daily)
if len(symbol_history) < self.two: # Check for usable dataframe
unavailableSymbols.append(symbol)
continue
else:
symbol_historical_values = symbol_history["close"][-self.period:]
symbol_roc = symbol_historical_values[-self.one] / symbol_historical_values[self.zero]
symbol_daily_returns = symbol_historical_values.pct_change()[symbol_historical_values.shift(self.one).notnull()].dropna()
if symbol not in unavailableSymbols and not symbol_daily_returns.empty:
symbol_stdev = statistics.pstdev(symbol_daily_returns)
symbol_data[symbol] = (symbol_roc, symbol_stdev)
if symbol_stdev != self.zero and symbol_roc >= self.one:
sum_inverse_stdev = sum_inverse_stdev + self.one / symbol_stdev
for symbol in allSymbols:
if symbol not in unavailableSymbols:
longSymbols.append(symbol)
if (algorithm.Time.month % self.two) == self.zero and algorithm.Time.month < 6: # "Sell in May and go away" effect
seasonweight = self.one
else:
seasonweight = self.one / 4
sumweight = self.zero
if len(longSymbols) != self.zero:
for symbol in longSymbols:
if symbol_data[symbol][self.zero] >= self.one and symbol_data[symbol][self.one] > self.zero and sum_inverse_stdev > self.zero:
weight = ((seasonweight * self.maxExposure) / symbol_data[symbol][self.one]) / (sum_inverse_stdev)
sumweight += weight
insights.append(Insight.Price(str(symbol), timedelta(days = self.period, minutes = -self.one), InsightDirection.Up, magnitude, confidence, "Long", weight))
if sumweight < self.maxExposure * 0.8:
allRefusedSymbols = [x.Symbol for x in self.securities if x.Symbol in self.refusedSymbols]
for symbol in allRefusedSymbols:
if symbol == 'SPY':
continue
else:
symbol_hist = algorithm.History(symbol, self.period, Resolution.Daily)
if len(symbol_hist) > self.two: # Check for usable dataframe
symbol_hist_values = symbol_hist["close"][-self.period:]
symbol_roc = symbol_hist_values[-self.one] / symbol_hist_values[self.zero]
if symbol_roc < self.one:
insights.append(Insight.Price(symbol, timedelta(days = self.period, minutes = -self.one), InsightDirection.Up, magnitude, confidence, None, self.maxExposure * 0.8 - sumweight))
self.counterDay = algorithm.Time.weekday() - self.recalculationDayNumber
return insights
def OnSecuritiesChanged(self, algorithm, changes):
# Add new securities
for added in changes.AddedSecurities:
self.securities.append(added)
# Remove old securities
for removed in changes.RemovedSecurities:
if removed in self.securities:
self.securities.remove(removed)
import datetime
from LongOnlyAlphaModel import LongOnlyAlphaModel
from NewImmediateExecutionModel import NewImmediateExecutionModel
from FixedInsightWeightingPortfolioConstructionModel import FixedInsightWeightingPortfolioConstructionModel
from QuantConnect.Data.UniverseSelection import *
class GEAlgo_Crypto_Momentum(QCAlgorithm):
def Initialize(self):
# Automatic rebalancing parameters
self.Settings.RebalancePortfolioOnInsightChanges = True
self.Settings.RebalancePortfolioOnSecurityChanges = False
###############################################################################################################################
self.SetStartDate(2016, 11, 13) # 5 years up to the submission date
self.SetCash(1000000) # Set $1m Strategy Cash to trade significant AUM
self.reference = "SPY" # SPY Benchmark
self.AddEquity(self.reference, Resolution.Minute)
self.SetBenchmark(self.reference)
self.SetBrokerageModel(AlphaStreamsBrokerageModel())
self.SetExecution(NewImmediateExecutionModel())
self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel(self.RebalanceFunction))
###############################################################################################################################
# Universe
symbols = ['BTCUSD', 'ETHUSD', 'XRPUSD', 'ADAUSD', 'SOLUSD', 'DOTUSD', 'LTCUSD', 'LUNAUSD', 'USDTUSD']
for symbol in symbols:
self.AddCrypto(symbol, Resolution.Minute, Market.Bitfinex)
self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
# INPUTS
# Parameters
maxExposure = 0.7 # max exposure before discrepancies
recalculationDayNumber = 1 # rebalancing between monday and tuesday (more liquidity than during the week-end)
period = 7 # rebalancing each week
# Symbol "manually" refused: the benchmark and USDTUSD
refusedSymbols = [self.reference, 'USDTUSD']
# Alpha model
self.AddAlpha(LongOnlyAlphaModel(period = period, recalculationDayNumber = recalculationDayNumber,
refusedSymbols = refusedSymbols, maxExposure = maxExposure))
# Risk Management
self.AddRiskManagement(NullRiskManagementModel())
def RebalanceFunction(self, time): # Using the manual rebalancing to avoid any rebalancing not needed: the weightings are managed in the Alpha Model
return Nonefrom clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
from System import *
from QuantConnect import *
from itertools import groupby
###############################################################################################################################
# PORTFOLIO CONSTRUCTION MODEL CLASS
class FixedInsightWeightingPortfolioConstructionModel(PortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that gives weighting to all selected securities that are given by the algorithm, without any rebalancing or orders adjustments.
Hence the orders sent will not be different from the insights generated, as there will not be any rebalancing for open insights (and open positions)
Therefore useless trading costs will be avoided, and the statistics of orders (average win/loss...) will be correct and usable.'''
def __init__(self): #, rebalancingParam = Resolution.Daily
self.insightCollection = InsightCollection()
self.removedSymbols = []
self.one = 1
self.zero = self.one - self.one
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'''
# Ignore insights that don't have Weight value
return insight.Weight is not None
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:
result[insight] = insight.Direction * self.GetValue(insight)
return result
def GetValue(self, insight):
'''Method that will determine which member will be used to compute the weights and gets its value
Args:
insight: The insight to create a target for
Returns:
The value of the selected insight member'''
return insight.Weight
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 (len(insights) == self.zero 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, self.zero) 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)[-self.one])
# Determine target percent for the given insights
percents = self.DetermineTargetPercent(lastActiveInsights)
# Send ONLY NEW insight(s) as orders, NOT all last active insights sent again (= no periodic rebalancing)
errorSymbols = {}
for insight in insights:
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)
expiredTargets = []
for symbol, f in groupby(expiredInsights, lambda x: x.Symbol):
if not self.insightCollection.HasActiveInsights(symbol, algorithm.UtcTime) and not symbol in errorSymbols:
expiredTargets.append(PortfolioTarget(symbol, self.zero))
continue
targets.extend(expiredTargets)
return targets