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 None
from 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