Overall Statistics
Total Trades
952
Average Win
0.12%
Average Loss
-0.41%
Compounding Annual Return
-99.764%
Drawdown
99.900%
Expectancy
-0.995
Net Profit
-99.859%
Sharpe Ratio
-0.765
Probabilistic Sharpe Ratio
0.000%
Loss Rate
100%
Win Rate
0%
Profit-Loss Ratio
0.29
Alpha
-0.931
Beta
-0.142
Annual Standard Deviation
1.202
Annual Variance
1.445
Information Ratio
-0.688
Tracking Error
1.22
Treynor Ratio
6.454
Total Fees
$952.00
Estimated Strategy Capacity
$9600000.00
Lowest Capacity Asset
GHSI X3CY8FI8QOV9
import pandas as pd
import numpy as np
from AlgorithmImports import *
# endregion
from QuantConnect.Indicators import ExponentialMovingAverage

class QEMAAlphaModel(AlphaModel):

    openingBar = None
    currentBar = None
    """
    This class emits insights to hold a long (short) position after the chikou line of a
    security's Ichimoku Cloud crosses over (under) the top (bottom) of the cloud.
    """
    def __init__(self, vfastPeriod = 5, fastPeriod = 9, midPeriod = 21, slowPeriod = 55, resolution = Resolution.Minute, portfolioBias = PortfolioBias.Long):

        '''Initializes a new instance of the EmaCrossAlphaModel class
        Args:
            fastPeriod: The fast EMA period
            slowPeriod: The slow EMA period'''
        self.vfastPeriod = vfastPeriod
        self.fastPeriod = fastPeriod
        self.midPeriod = midPeriod
        self.slowPeriod = slowPeriod
        self.resolution = resolution
    
        #consFiveMin = TradeBarConsolidator(5)
        #consFiveMin.DataConsolidated += self.OnDataConsolidated
        #self.SubscriptionManager.AddConsolidator(symbol, consFiveMin)
        
        self.predictionInterval = Time.Multiply(Extensions.ToTimeSpan(resolution), slowPeriod)
        self.symbolDataBySymbol = {}

        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        self.Name = '{}({},{},{})'.format(self.__class__.__name__, vfastPeriod, fastPeriod, midPeriod, slowPeriod, resolutionString)


    def Update(self, algorithm, data):
        '''Updates this alpha model with the latest data from the algorithm.
        This is called each time the algorithm receives data for subscribed securities
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []
        for symbol, symbolData in self.symbolDataBySymbol.items():
            if symbolData.vFast.IsReady and symbolData.Slow.IsReady:

                if symbolData.vFast == symbolData.Fast:
                    insights.append(Insight.Price(symbolData.Symbol, self.predictionInterval, InsightDirection.Down))

                if (symbolData.Fast > symbolData.Mid and symbolData.Mid > symbolData.Slow):
                    insights.append(Insight.Price(symbolData.Symbol, self.predictionInterval, InsightDirection.Up))


        return insights

    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'''
        for added in changes.AddedSecurities:
            symbolData = self.symbolDataBySymbol.get(added.Symbol)
            if symbolData is None:
                symbolData = SymbolData(added, self.vfastPeriod, self.fastPeriod, self.midPeriod, self.slowPeriod, algorithm, self.resolution)
                self.symbolDataBySymbol[added.Symbol] = symbolData
            else:
                # a security that was already initialized was re-added, reset the indicators
                symbolData.vFast.Reset()
                symbolData.Fast.Reset()
                symbolData.Mid.Reset()
                symbolData.Slow.Reset()

        for removed in changes.RemovedSecurities:
            data = self.symbolDataBySymbol.pop(removed.Symbol, None)
            if data is not None:
                # clean up our consolidators
                data.RemoveConsolidators()

    def OnDataConsolidated(self, bar):
        self.currentBar = bar


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, security, vfastPeriod, fastPeriod, midPeriod, slowPeriod, algorithm, resolution):
        self.Security = security
        self.Symbol = security.Symbol
        self.algorithm = algorithm
        self.Resolution = resolution
        resolution = timedelta(minutes=5)

        self.vFastConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
        self.FastConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
        self.MidConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)
        self.SlowConsolidator = algorithm.ResolveConsolidator(security.Symbol, resolution)

        algorithm.SubscriptionManager.AddConsolidator(security.Symbol, self.vFastConsolidator)
        algorithm.SubscriptionManager.AddConsolidator(security.Symbol, self.FastConsolidator)
        algorithm.SubscriptionManager.AddConsolidator(security.Symbol, self.MidConsolidator)
        algorithm.SubscriptionManager.AddConsolidator(security.Symbol, self.SlowConsolidator)

        # create fast/slow EMAs
        self.vFast = ExponentialMovingAverage(security.Symbol, vfastPeriod, ExponentialMovingAverage.SmoothingFactorDefault(vfastPeriod))
        self.Fast = ExponentialMovingAverage(security.Symbol, fastPeriod, ExponentialMovingAverage.SmoothingFactorDefault(fastPeriod))
        self.Mid = ExponentialMovingAverage(security.Symbol, midPeriod, ExponentialMovingAverage.SmoothingFactorDefault(midPeriod))
        self.Slow = ExponentialMovingAverage(security.Symbol, slowPeriod, ExponentialMovingAverage.SmoothingFactorDefault(slowPeriod))

        algorithm.RegisterIndicator(security.Symbol, self.vFast, self.vFastConsolidator);
        algorithm.RegisterIndicator(security.Symbol, self.Fast, self.FastConsolidator);
        algorithm.RegisterIndicator(security.Symbol, self.Mid, self.MidConsolidator);
        algorithm.RegisterIndicator(security.Symbol, self.Slow, self.SlowConsolidator);

        algorithm.WarmUpIndicator(security.Symbol, self.vFast, resolution);
        algorithm.WarmUpIndicator(security.Symbol, self.Fast, resolution);
        algorithm.WarmUpIndicator(security.Symbol, self.Mid, resolution);
        algorithm.WarmUpIndicator(security.Symbol, self.Slow, resolution);

        

        # True if the fast is above the slow, otherwise false.
        # This is used to prevent emitting the same signal repeatedly
        self.FastIsOverSlow = True
        
    def RemoveConsolidators(self):
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.Security.Symbol, self.vFastConsolidator)
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.Security.Symbol, self.FastConsolidator)
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.Security.Symbol, self.MidConsolidator)
        self.algorithm.SubscriptionManager.RemoveConsolidator(self.Security.Symbol, self.SlowConsolidator)

    @property
    def SlowIsOverFast(self):
        return not self.FastIsOverSlow
from AlgorithmImports import *

from clr import AddReference

AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Algorithm import *

from alphamodel import QEMAAlphaModel
from risk import MaxDrawdownPercentModel

class QEMA_AlgorithmUniverse(QCAlgorithm):

    def Initialize(self):
        self.SetTimeZone(TimeZones.NewYork)
        self.SetStartDate(2021, 9, 20)
        self.SetEndDate(2022, 10, 20)
        self.SetCash(1000)
        
        self.AddUniverse(self.SelectCoarse)

        self.SetAlpha(QEMAAlphaModel(portfolioBias = PortfolioBias.Long))
        self.SetWarmup(55)
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(portfolioBias = PortfolioBias.Long))
        self.SetRiskManagement(MaxDrawdownPercentModel())
        self.SetExecution(ImmediateExecutionModel())
        
        self.averages = {}

        self.SetWarmup(200)


        
    def SelectCoarse(self, universe):
        """
        Coarse universe selection is called each day at midnight.
        Returns the symbols that have fundamental data.
        """

        selected = []
        universe = sorted(universe, key=lambda c: c.Volume, reverse=True)
        universe = [c for c in universe if c.Price < 50][:100]

        # Create a loop to use all the coarse data:
        for coarse in universe:
            symbol = coarse.Symbol

            if symbol not in self.averages:
                history = self.History(symbol, 200, Resolution.Daily)
                self.averages[symbol] = SelectionData(history)
                
            self.averages[symbol].update(self.Time, coarse.AdjustedPrice)

            # Check if EMA50 > EMA200 and if so, append the symbol to the selected list
            if self.averages[symbol].unifast > self.averages[symbol].unislow:
                if self.averages[symbol].is_ready():
                    selected.append(symbol)

        return selected[:20]


class SelectionData():

    def __init__(self, history):
        self.unislow = SimpleMovingAverage(200)
        self.unifast = SimpleMovingAverage(50)
        

        for bar in history.itertuples():
            self.unifast.Update(bar.Index[1], bar.close)
            self.unislow.Update(bar.Index[1], bar.close)

    def is_ready(self):
        return self.unislow.IsReady

    def update(self, time, price):
        self.unifast.Update(time, price)
        self.unislow.Update(time, price)



    
#region imports
from AlgorithmImports import *

# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

class MaxDrawdownPercentModel(RiskManagementModel):
    '''Provides an implementation of IRiskManagementModel that limits the drawdown of the portfolio to the specified percentage.'''

    def __init__(self, maximumDrawdownPercent = 0.025, isTrailing = False):
        '''Initializes a new instance of the MaximumDrawdownPercentPortfolio class
        Args:
            maximumDrawdownPercent: The maximum percentage drawdown allowed for algorithm portfolio compared with starting value, defaults to 5% drawdown</param>
            isTrailing: If "false", the drawdown will be relative to the starting value of the portfolio.
                        If "true", the drawdown will be relative the last maximum portfolio value'''
        self.maximumDrawdownPercent = -abs(maximumDrawdownPercent)
        self.isTrailing = isTrailing
        self.initialised = False
        self.portfolioHigh = 0

    def ManageRisk(self, algorithm, targets):
        '''Manages the algorithm's risk at each time step
        Args:
            algorithm: The algorithm instance
            targets: The current portfolio targets to be assessed for risk'''
        currentValue = algorithm.Portfolio.TotalPortfolioValue

        if not self.initialised:
            self.portfolioHigh = currentValue   # Set initial portfolio value
            self.initialised = True

        # Update trailing high value if in trailing mode
        if self.isTrailing and self.portfolioHigh < currentValue:
            self.portfolioHigh = currentValue
            return []   # return if new high reached

        pnl = self.GetTotalDrawdownPercent(currentValue)
        if pnl < self.maximumDrawdownPercent and len(targets) != 0:
            self.initialised = False # reset the trailing high value for restart investing on next rebalcing period
            return [ PortfolioTarget(target.Symbol, 0) for target in targets ]

        return []

    def GetTotalDrawdownPercent(self, currentValue):
        return (float(currentValue) / float(self.portfolioHigh)) - 1.0