Overall Statistics
Total Trades
1212
Average Win
0.15%
Average Loss
-0.10%
Compounding Annual Return
3.175%
Drawdown
4.200%
Expectancy
0.237
Net Profit
13.327%
Sharpe Ratio
0.686
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.53
Alpha
0.015
Beta
0.727
Annual Standard Deviation
0.039
Annual Variance
0.001
Information Ratio
0.259
Tracking Error
0.039
Treynor Ratio
0.036
Total Fees
$1691.24
# Your New Python File# 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.

from clr import AddReference
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")

from QuantConnect import *
from QuantConnect.Indicators import *
from QuantConnect.Algorithm.Framework.Alphas import *
from datetime import timedelta
from collections import deque
from dateutil.relativedelta import relativedelta

class LevermannFactorsAlphaModel(AlphaModel):
    '''Uses Historical returns to create insights.'''

    def __init__(self, time):
        '''Initializes a new default instance of the HistoricalReturnsAlphaModel class.
        Args:
            lookback(int): Historical return lookback period
            resolution: The resolution of historical data'''
        self.symbolDataBySymbol = {}
        self.lastRebalancing = time - relativedelta(month=1)
        self.portfolioSize = 10

    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 = []
        symbolsWithMinimalScore = []
        symbolsWithHighestScore = []
        if self.lastRebalancing.month != algorithm.Time.month:
            for symbolData in self.symbolDataBySymbol.values():
                symbolData.Update(algorithm.Securities[symbolData.symbol.Symbol].Close)
                if symbolData.IsReady:
                    if symbolData.Score()>4: 
                        symbolsWithMinimalScore.append(symbolData)
            symbolsWithHighestScore = sorted(symbolsWithMinimalScore, key = lambda x: x.Score(), reverse=True)[:self.portfolioSize]
            for symbolData in symbolsWithHighestScore:
                insights.append(Insight.Price(symbolData.symbol.Symbol, timedelta(days = 1), InsightDirection.Up))
            self.lastRebalancing = algorithm.Time
        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'''

        # clean up data for removed securities
        #algorithm.Log(changes.AddedSecurities[0].Fundamentals.ValuationRatios.TotalYield)
        for removed in changes.RemovedSecurities:
            symbolData = self.symbolDataBySymbol.pop(removed.Symbol, None)
    
        # initialize data for added securities
        symbols = [ x.Symbol for x in changes.AddedSecurities ]
        
        for added in changes.AddedSecurities:
            if added.Symbol not in self.symbolDataBySymbol:
                self.symbolDataBySymbol[added.Symbol]= SymbolData(added, algorithm)
                

class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, symbol, qcContext):
        self.symbol = symbol
        self.sixMonthsInDays = 100
        self.twelveMonthsInDays = 200
        self.queue = deque(maxlen=self.twelveMonthsInDays)
        self.qcContext = qcContext
        self.WarmUp()
        self.score=0
        
    def Return(self, period):
        return 2#self.queue[0]/self.queue[period-1]-1
        
    def Update(self, value):
        self.queue.appendleft(value)
        count = len(self.queue)
        self.IsReady = count == self.queue.maxlen
    
    def WarmUp(self):
        history = self.qcContext.History(self.symbol.Symbol,  self.twelveMonthsInDays, Resolution.Daily)
        if str(self.symbol) in history.index:             
            for tuple in history.itertuples(): self.Update(tuple.close)
        
    #Scoring according to https://letyourmoneygrow.com/2018/03/09/susan-levermann-approach-an-investment-strategy-that-works/
    
    def Score(self):
        score = 0
        #1 One year RoE >20%: +1 ; <10%: -1
        if self.symbol.Fundamentals.OperationRatios.ROE.OneYear > 0.2:
            score = score + 1
        elif self.symbol.Fundamentals.OperationRatios.ROE.OneYear < 0.1:
            score = score - 1 
        #2 EBIT One Year >12%: +1 ; <6%: -1
        if self.symbol.Fundamentals.OperationRatios.EBITMargin.OneYear > 0.12:
            score = score + 1
        elif self.symbol.Fundamentals.OperationRatios.EBITMargin.OneYear < 0.06:
             score = score - 1 
        #3 Equity Ratio one year >25%: +1 ; <15%: -1
        leverage = self.symbol.Fundamentals.OperationRatios.FinancialLeverage.OneYear
        if leverage != 0:
            if 1/leverage>0.25:
                score = score + 1
            elif 1/leverage<0.15:
                score = score - 1 
        #4 P/E one Year <12: +1 ; >16: -1
        if self.symbol.Fundamentals.ValuationRatios.PERatio <12:
            score = score +1 
        elif self.symbol.Fundamentals.ValuationRatios.PERatio>16:
            score = score - 1 
        #5 P/E five years <13: +1 ; >17: -1
        #if self.symbol.Fundamentals.ValuationRatios.PERatio.FiveYear<13:
        #    score = score +1 
        #elif self.symbol.Fundamentals.ValuationRatios.PERatio.FiveYear>17:
        #    score = score - 1 
        #6 Analyst Opinions
        #7 Real price reaction in % on quarterly EPS report >1%: +1 ; <-1%: -1
        #8 Current FQ Est EPS% change >5%: +1 ; <-5%: -1
        if self.symbol.Fundamentals.ValuationRatios.ForwardEarningYield > 0.05:
            score = score+1
        if self.symbol.Fundamentals.ValuationRatios.ForwardEarningYield < -0.05:
            score = score-1
        #9 6 months price change >5%: +1 ; <-5%: -1
        if self.Return(self.sixMonthsInDays) > 0.05: 
            score = score+1 
        elif self.Return(self.sixMonthsInDays)<-0.05:
            score = score-1
        #10 12 months price change >5%: +1 ; <-5%: -1
        if self.Return(self.twelveMonthsInDays) > 0.05: 
            score = score+1 
        elif self.Return(self.twelveMonthsInDays)<-0.05: 
            score = score-1
        #11 EPS growth: Change of current to next FY Est EPS >5%: +1; <-5%: -1 
        if self.symbol.Fundamentals.ValuationRatios.SecondYearEstimatedEPSGrowth > 0.05: 
            score = score+1 
        elif self.symbol.Fundamentals.ValuationRatios.SecondYearEstimatedEPSGrowth < -0.05: 
            score = score-1
        #12 Momentum: if 6 months price change > 5% and 12 month price change < -5%: 1
        #             if 6 months price change < -5% and 12 month price change > 5%: -1
        if self.Return(self.sixMonthsInDays) > 0.05 and self.Return(self.twelveMonthsInDays)<-0.05: 
            score = score+1 
        elif self.Return(self.sixMonthsInDays) <0.05 and self.Return(self.twelveMonthsInDays)>0.05: 
            score = score+1
        #13 Reversal: if better than benachmark: 1 ; if worse than benchmark -1
       
        return score
    def __str__(self, **kwargs):
        return 'Symbol'
from Selection.QC500UniverseSelectionModel import QC500UniverseSelectionModel

from Alphas.HistoricalReturnsAlphaModel import HistoricalReturnsAlphaModel

from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

from Execution.ImmediateExecutionModel import ImmediateExecutionModel

from LevermannFactorsAlphaModel import LevermannFactorsAlphaModel

class BasicTemplateFrameworkAlgorithm(QCAlgorithmFramework):

    def Initialize(self):

        # Set requested data resolution
        self.UniverseSettings.Resolution = Resolution.Minute

        self.SetStartDate(2015, 1, 1)   #Set Start Date
        self.SetEndDate(2019, 1, 1)    #Set End Date
        self.SetCash(100000)           #Set Strategy Cash

        self.SetUniverseSelection(QC500UniverseSelectionModel())

        self.SetAlpha(LevermannFactorsAlphaModel(self.Time))

        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())

        self.SetExecution(ImmediateExecutionModel())

        self.SetRiskManagement(NullRiskManagementModel())
        

    

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            # self.Debug("Purchased Stock: {0}".format(orderEvent.Symbol))
            pass