Overall Statistics
Total Trades
2849
Average Win
1.12%
Average Loss
-0.99%
Compounding Annual Return
22.140%
Drawdown
31.100%
Expectancy
0.361
Net Profit
8391.701%
Sharpe Ratio
0.742
Probabilistic Sharpe Ratio
1.585%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
1.14
Alpha
0.165
Beta
0.226
Annual Standard Deviation
0.241
Annual Variance
0.058
Information Ratio
0.441
Tracking Error
0.269
Treynor Ratio
0.791
Total Fees
$81570.19
Estimated Strategy Capacity
$740000.00
Lowest Capacity Asset
TLT SGNKIKYGE9NP
# O'Shaugnessey VC2 + Momentum
# PBRatio (Price-To-Book)
# PERatio (Price-To-Earnings)
# PSRatio (Price-To-Sales)
# EVToEBITDA
# PCFRatio (Price-To-Cash Flow)
# Absicherung nach unten durch SPY-SMA200-Filter
# Rebalancing Monthly

from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp
# import statsmodels.api as sm

class FundamentalFactorAlgorithm(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)          
  
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.spy = self.AddEquity("SPY", Resolution.Minute).Symbol 
        self.holding_months = 1
        self.num_screener = 100
        self.num_stocks = 9
        self.formation_days = 200
        self.lowmom = False
        self.month_count = self.holding_months
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.At(0, 0), Action(self.monthly_rebalance))
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.At(10, 0), Action(self.rebalance))
        # rebalance the universe selection once a month
        self.rebalence_flag = 0
        # make sure to run the universe selection at the start of the algorithm even it's not the manth start
        self.first_month_trade_flag = 1
        self.trade_flag = 0 
        self.symbols = None
 
    def CoarseSelectionFunction(self, coarse):
        if self.rebalence_flag or self.first_month_trade_flag:
            selected = [x for x in coarse if (x.HasFundamentalData) and (x.Price > 5)]
            # rank the stocks by dollar volume 
            filtered = sorted(selected, key=lambda x: x.DollarVolume, reverse=True) 
    
            return [ x.Symbol for x in filtered[:800]] # 200
        else:
            return self.symbols

    def FineSelectionFunction(self, fine):
        if self.rebalence_flag or self.first_month_trade_flag:
            hist = self.History([i.Symbol for i in fine], 1, Resolution.Daily)

            try:
                filtered_fine = [x for x in fine if (x.ValuationRatios.EVToEBITDA > 0) 
                                                    and (x.ValuationRatios.PBRatio > 0)
                                                    and (x.ValuationRatios.PERatio > 0)
                                                    and (x.ValuationRatios.PCFRatio > 0)
                                                    and (x.ValuationRatios.TotalYield > 0)
                                                    and (x.EarningReports.BasicAverageShares.ThreeMonths > 0) 
                                                    and (x.ValuationRatios.PSRatio > 0)
                                                    and float(x.EarningReports.BasicAverageShares.ThreeMonths) * hist.loc[str(x.Symbol)]['close'][0] > 2e9]
            except:
                filtered_fine = [x for x in fine if (x.ValuationRatios.EVToEBITDA > 0) 
                                                and (x.ValuationRatios.PBRatio > 0)
                                                and (x.ValuationRatios.PERatio > 0)
                                                and (x.ValuationRatios.PCFRatio > 0)
                                                and (x.ValuationRatios.TotalYield > 0)
                                                and (x.EarningReports.BasicAverageShares.ThreeMonths > 0)
                                                and (x.ValuationRatios.PSRatio > 0)] 

            sortedByEVToEBITDA = sorted(filtered_fine, key = lambda x: x.ValuationRatios.EVToEBITDA, reverse=True)
            sortedByPSRatio = sorted(filtered_fine, key = lambda x: x.ValuationRatios.PSRatio, reverse=True)
            sortedByPBRatio = sorted(filtered_fine, key = lambda x: x.ValuationRatios.PBRatio, reverse=True)
            sortedByPERatio = sorted(filtered_fine, key = lambda x: x.ValuationRatios.PERatio, reverse=True)
            sortedByPCFRatio = sorted(filtered_fine, key = lambda x: x.ValuationRatios.PCFRatio, reverse=True)
            sortedByTotalYield = sorted(filtered_fine, key = lambda x: x.ValuationRatios.TotalYield, reverse=False)
            
            stock_dict = {}
            
            for rank1, ele in enumerate(sortedByEVToEBITDA):
                rank2 = sortedByPSRatio.index(ele)
                rank3 = sortedByPBRatio.index(ele)
                rank4 = sortedByPERatio.index(ele)
                rank5 = sortedByPCFRatio.index(ele)
                rank6 = sortedByTotalYield.index(ele)
                stock_dict[ele] = rank1 + rank2 + rank3 + rank4 + rank5 + rank6
            
            top = sorted(stock_dict.items(), key=lambda d:d[1], reverse=True)[:self.num_screener]
            
    #        top = sorted(filtered_fine, key = lambda x: x.ValuationRatios.EVToEBITDA, reverse=True)[:self.num_screener]
            self.symbols = [x[0].Symbol for x in top]
            
            self.rebalence_flag = 0
            self.first_month_trade_flag = 0
            self.trade_flag = 1
            return self.symbols
        else:
            return self.symbols

    def OnData(self, data):
        pass
    
    def monthly_rebalance(self):
        self.rebalence_flag = 1

    def rebalance(self):
        spy_hist = self.History([self.spy], 120, Resolution.Daily).loc[str(self.spy)]['close'] #120
        if self.Securities[self.spy].Price < spy_hist.mean():
            for symbol in self.Portfolio.Keys:
                if symbol.Value != "TLT":
                    self.Liquidate()
            self.AddEquity("TLT")
            self.SetHoldings("TLT", 1)
            return

        if self.symbols is None: return
        chosen_df = self.calc_return(self.symbols)
        chosen_df = chosen_df.iloc[:self.num_stocks]
        
        self.existing_pos = 0
        add_symbols = []
        for symbol in self.Portfolio.Keys:
            if symbol.Value == 'SPY': continue
            if (symbol.Value not in chosen_df.index):  
                self.SetHoldings(symbol, 0)
            elif (symbol.Value in chosen_df.index): 
                self.existing_pos += 1
            
        weight = 0.99/len(chosen_df)
        for symbol in chosen_df.index:
            self.AddEquity(symbol)
            self.SetHoldings(symbol, weight)    
                
    def calc_return(self, stocks):
        hist = self.History(stocks, self.formation_days, Resolution.Daily)
        current = self.History(stocks, 1, Resolution.Minute)
        
        self.price = {}
        ret = {}
     
        for symbol in stocks:
            if str(symbol) in hist.index.levels[0] and str(symbol) in current.index.levels[0]:
                self.price[symbol.Value] = list(hist.loc[str(symbol)]['close'])
                self.price[symbol.Value].append(current.loc[str(symbol)]['close'][0])
        
        for symbol in self.price.keys():
            ret[symbol] = (self.price[symbol][-1] - self.price[symbol][0]) / self.price[symbol][0]
        df_ret = pd.DataFrame.from_dict(ret, orient='index')
        df_ret.columns = ['return']
        sort_return = df_ret.sort_values(by = ['return'], ascending = self.lowmom)
        
        return sort_return

# Coarse 100 / SPY 120 mean       
# self.num_stocks = 1 100k -> 3,6 Mio., CAR 17,55%, max DD 69,4%, Sharpe 0,53, 427 Trades, 20220306 +
# self.num_stocks = 2 100k -> 5,2 Mio., CAR 19,44%, max DD 59,1%, Sharpe 0,6, 732 Trades, 20220308 +
# self.num_stocks = 3 100k -> 3,6 Mio., CAR 17,47%, max DD 56,3%, Sharpe 0,59, 1.027 Trades, 20220308 +
# 4 erl.
# 5 erl.

# Coarse 200 / SPY 120 mean       
# self.num_stocks = 1 100k -> 286k, CAR 4,85%, max DD 79,7%, Sharpe 0,28, 436 Trades, 20220306 +
# 2 erl.
# 3 erl.
# 4 erl.
# 5 erl.
# 6 erl.
# 7 erl.

# Coarse 300 / SPY 120 mean       
# self.num_stocks = 1 100k -> 4,3 Mio., CAR 18,449%, max DD 68,3%, Sharpe 0,58, 438 Trades, 20220302 +
# self.num_stocks = 2 100k -> 437k, CAR 6,885%, max DD 65,8%, Sharpe 0,34, 489 Trades, 20220302 +
# self.num_stocks = 3 100k -> 1,3 Mio, CAR 12,262%, max DD 45,6%, Sharpe 0,53, 705 Trades, 20220302 +
# self.num_stocks = 4 100k -> 786k, CAR 9,748%, max DD 51,2%, Sharpe 0,47, 924 Trades, 20220302 +
# self.num_stocks = 5 100k -> 3,7 Mio., CAR 17,681%, max DD 31,4%, Sharpe 0,71, 1.627 Trades, 20220304  +
# self.num_stocks = 6 100k -> 362k, CAR 5,969%, max DD 43,0%, Sharpe 0,34, 1.424 Trades, 20220305 ??? +
# self.num_stocks = 7 100k -> 3,1 Mio., CAR 16,817%, max DD 27,6%, Sharpe 0,72, 2.234 Trades, 20220305 +
# self.num_stocks = 8 100k -> 3,2 Mio., CAR 16,859%, max DD 27,8%, Sharpe 0,74, 2.534 Trades, 20220305 +
# self.num_stocks = 9 100k -> 3,4 Mio., CAR 17,296%, max DD 27,0%, Sharpe 0,77, 2.806 Trades, 20220305 +
# self.num_stocks = 10 100k -> 3,1 Mio., CAR 16,797%, max DD 25,5%, Sharpe 0,77, 3.109 Trades, 20220305 +
# self.num_stocks = 11 100k -> 3,0 Mio., CAR 16,621%, max DD 26,4%, Sharpe 0,77, 3.396 Trades, 20220305 +
# self.num_stocks = 12 100k -> 2,8 Mio., CAR 16,116%, max DD 25,1%, Sharpe 0,76, 3.684 Trades, 20220306 +

# Coarse 400 / SPY 120 mean       
# self.num_stocks = 1 100k -> 1,1 Mio., CAR 11,265%, max DD 52,5%, Sharpe 0,41, 438 Trades, 20220223 +
# self.num_stocks = 2 100k -> 4,4 Mio., CAR 18,646%, max DD 37,4%, Sharpe 0,65, 738 Trades, 20220224 +
# self.num_stocks = 3 100k -> 2,2 Mio., CAR 15,032%, max DD 36,3%, Sharpe 0,59, 1.046 Trades, 20220225 +
# self.num_stocks = 4 100k -> 3,2 Mio., CAR 16,819%, max DD 32,7%, Sharpe 0,67, 1.348 Trades, 20220225 +
# self.num_stocks = 5 100k -> 2,5 Mio., CAR 15,724%, max DD 33,7%, Sharpe 0,66, 1.652 Trades, 20220226 +
# self.num_stocks = 6 100k -> 3,3 Mio., CAR 17,061%, max DD 28,6%, Sharpe 0,73, 1.935 Trades, 20220226 +
# self.num_stocks = 7 100k -> 3,1 Mio., CAR 16,706%, max DD 31,4%, Sharpe 0,73, 2.228 Trades, 20220226 +
# self.num_stocks = 8 100k -> 2,9 Mio., CAR 16,392%, max DD 31,2%, Sharpe 0,73, 2.525 Trades, 20220227 + 
# self.num_stocks = 9 100k -> 2,7 Mio., CAR 16,017%, max DD 28,5%, Sharpe 0,73, 2.817 Trades, 20220227 +
# self.num_stocks = 10 100k -> 2,8 Mio., CAR 16,282%, max DD 28,1%, Sharpe 0,75, 3.095 Trades, 20220227 +
# self.num_stocks = 11 100k -> 2,7 Mio., CAR 16,012%, max DD 27,0%, Sharpe 0,75, 3.376 Trades, 20220227 +
# self.num_stocks = 12 100k -> 3,0 Mio., CAR 16,498%, max DD 26,0%, Sharpe 0,78, 3.643 Trades, 20220227 +
# self.num_stocks = 13 100k -> 2,7 Mio., CAR 16,104%, max DD 25,7%, Sharpe 0,78, 3.933 Trades, 20220228 +
# self.num_stocks = 14 100k -> 2,5 Mio., CAR 15,597%, max DD 25,2%, Sharpe 0,76, 4.210 Trades, 20220228 +
# self.num_stocks = 15 100k -> 3,0 Mio., CAR 16,629%, max DD 24,4%, Sharpe 0,81, 4.486 Trades, 20220228 +

# Coarse 500 / SPY 120 mean       

# self.num_stocks = 1 100k -> 3,0 Mio., CAR 16,567%, max DD 58,4%, Sharpe 0,54, 426 Trades +
# self.num_stocks = 2 100k -> 5,3 Mio., CAR 19,65%%, max DD 47,9%, Sharpe 0,67, 745 Trades +
# self.num_stocks = 3 100k -> 7,4 Mio., CAR 21,534%, max DD 43,9%, Sharpe 0,78, 1.046 Trades +
# self.num_stocks = 4 100k -> 8,7 Mio., CAR 22,435%, max DD 33,0%, Sharpe 0,84, 1.349 Trades +
# self.num_stocks = 5 100k -> 8,6 Mio., CAR 22,345%, max DD 27,1%, Sharpe 0,88, 1.635 Trades+
# self.num_stocks = 6 100k -> 7,3 Mio., CAR 21,466%, max DD 24,6%, Sharpe 0,87, 1.925 Trades +
# self.num_stocks = 7 100k -> 6,3 Mio., CAR 20,654%, max DD 24,5%, Sharpe 0,86, 2.218 Trades +
# self.num_stocks = 8 100k -> 4,8 Mio., CAR 19,168%, max DD 24,7%, Sharpe 0,83, 2.507 Trades +
# self.num_stocks = 9 100k -> 4,3 Mio., CAR 18,505%, max DD 27,8%, Sharpe 0,81, 2.764 Trades+
# self.num_stocks = 10 100k -> 4,0 Mio., CAR 18,143%, max DD 23,2% %, Sharpe 0,82, 3.039 Trades +
# self.num_stocks = 11 100k -> 4,1 Mio., CAR 18,273%, max DD 26,3% %, Sharpe 0,84, 3.343 Trades +
# self.num_stocks = 12 100k -> 3,9 Mio., CAR 17,958%, max DD 27,7% %, Sharpe 0,84, 3.626 Trades +
# self.num_stocks = 13 100k -> 3,6 Mio., CAR 17,586%, max DD 27,3% %, Sharpe 0,83, 3.918 Trades +

# self.num_stocks = 15 100k -> 3,7 Mio., CAR 17,772%, max DD 27,1% %, Sharpe 0,69, 3.334 Trades +

# Coarse 600 / SPY 120 mean       
# self.num_stocks = 1 100k --> 1,0 Mio., CAR 10,983%, max DD 65,6%, Sharpe 0,41, 420 Trades, 20220206 +
# self.num_stocks = 2 100k --> 1,6 Mio., CAR 10,983%, max DD 65,6%, Sharpe 0,41, 420 Trades, 20220206 +
# self.num_stocks = 3 100k --> 2,0 Mio., CAR 13,218%, max DD 57,6%, Sharpe 0,5, 750 Trades, 20220207 +
# self.num_stocks = 4 100k --> 3,5 Mio., CAR 17,519%, max DD 34,9%, Sharpe 0,69, 1.359 Trades, 20220206 +
# self.num_stocks = 5 100k --> 5,0 Mio., CAR 19,308%, max DD 28,4%, Sharpe 0,77, 1.646 Trades, 20220209 +
# self.num_stocks = 6 100k --> 4,8 Mio., CAR 19,118%, max DD 29,5%, Sharpe 0,79, 1.946 Trades, 20220209
# self.num_stocks = 7 100k --> 4,6 Mio., CAR 18,904%, max DD 26,8%, Sharpe 0,8, 2.220 Trades, 20220209
# self.num_stocks = 8 100k --> 4,0 Mio., CAR 18,103%, max DD 24,5%, Sharpe 0,79, 2.514 Trades, 20220211
# self.num_stocks = 9 100k --> 3,4 Mio., CAR 17,285%, max DD 24,6%, Sharpe 0,77, 2.798 Trades, 20220211
# self.num_stocks = 10 100k --> 3,1 Mio., CAR 16,858%, max DD 24,6%, Sharpe 0,76, 3.075 Trades, 20220217

# self.num_stocks = 15 100k --> 2,4 Mio., CAR 15,377%, max DD 28,6%, Sharpe 0,75, 4.484 Trades, 20220211


# Coarse 700 / SPY 120 mean       
# self.num_stocks = 1 100k --> 1,0 Mio., CAR 11,139%, max DD 61,1%, Sharpe 0,41, 437 Trades, 20220306 +
# self.num_stocks = 5 100k --> 3,1 Mio., CAR 16,829%, max DD 28,6%, Sharpe 0,69, 1.658 Trades, 20220206 +

# Coarse 800 / SPY 120 mean       
# self.num_stocks = 1 100k --> 1,4 Mio., CAR 12,5%%, max DD 57,1%%, Sharpe 0,45, 444 Trades, 20220306 +
# self.num_stocks = 2 100k --> 2,1 Mio., CAR 14,6%, max DD 51,1%%, Sharpe 0,53, 763 Trades, 20220306 +
# self.num_stocks = 3 100k --> 1,7 Mio., CAR 13,75%, max DD 35,4%, Sharpe 0,54, 1.082 Trades, 20220306 +

# self.num_stocks = 5 100k --> 3,2 Mio., CAR 16,906%, max DD 30,0%, Sharpe 0,69, 1.654 Trades, 20220206 +
# self.num_stocks = 6 100k --> 3,8 Mio., CAR 17,896%, max DD 26,4%, Sharpe 0,75, 1.933 Trades, 20220207 +
# self.num_stocks = 7 100k --> 3,8 Mio., CAR 17,938%, max DD 25,6%, Sharpe 0,77, 2.213 Trades, 20220207 +
# self.num_stocks = 8 100k --> 3,3 Mio., CAR 17,109%, max DD 28,4%, Sharpe 0,75, 2.510 Trades, 20220207 +

# Coarse 900 / SPY 120 mean       
# self.num_stocks = 1 100k --> 2,2 Mio., CAR 14,958%, max DD 57,1%, Sharpe 0,5, 437 Trades, 20220306 +
# self.num_stocks = 8 100k --> 3,2 Mio., CAR 17,041%, max DD 31,1%, Sharpe 0,75, 2.500Trades, 20220207 +
# 10 erl.
# 11 erl.
# 12 erl.
# 13 erl.
# 14 erl.
# 15 erl.

# Coarse 1000 / SPY 120 mean       
# self.num_stocks = 1 100k --> 3,2 Mio., CAR 18,87%, max DD 57,1%, Sharpe 0,54, 439 Trades, 20220306 +
# self.num_stocks = 13 100k --> 2,5 Mio., CAR 15,52%, max DD 29,5%, Sharpe 0,74, 3.942 Trades, 20220307 +
# self.num_stocks = 14 100k --> 2,2 Mio., CAR 15,04%, max DD 28,6%, Sharpe 0,73, 4.239 Trades, 20220307 +
# self.num_stocks = 15 100k --> 2,1 Mio., CAR 14,69%, max DD 30,0%, Sharpe 0,72, 4.521 Trades, 20220307 +

# Coarse 500 / SPY 200 mean       
# self.num_stocks = 1 100k -> 6,1 Mio., CAR 20,418%, max DD 53,2% %, Sharpe 0,62, 416 Trades +
# self.num_stocks = 2 100k -> 6,8 Mio., CAR 20,996%, max DD 45,4% %, Sharpe 0,7, 737 Trade+s
# self.num_stocks = 3 100k -> 8,7 Mio., CAR 22,406%, max DD 45,6% %, Sharpe 0,79, 1.044 Tr+ades
# self.num_stocks = 4 100k -> 6,4 Mio., CAR 20,680%, max DD 40,9% %, Sharpe 0,78, 1.357 Trades +
# self.num_stocks = 5 100k -> 7,1 Mio., CAR 21,251%, max DD 36,8% %, Sharpe 0,82, 1.660 Trades +
# self.num_stocks = 6 100k -> 5,9 Mio., CAR 20,263%, max DD 32,9% %, Sharpe 0,80, 1.951 Trades+
# self.num_stocks = 7 100k -> 5,4 Mio., CAR 19,760%, max DD 33,1% %, Sharpe 0,81, 2.250 Trades+
# self.num_stocks = 8 100k -> 4,2 Mio., CAR 18,426%, max DD 34,7% %, Sharpe 0,77, 2.542 Trades, 20220205+


# ohne ADR / SPY 120 mean
# self.num_stocks = 10 100k -> 2,6 Mio., CAR 15,871%, max DD 27,3% %, Sharpe 0,7, 3.024 Trades