Overall Statistics |
Total Trades 4002 Average Win 0.53% Average Loss -0.39% Compounding Annual Return 20.715% Drawdown 40.300% Expectancy 0.232 Net Profit 323.842% Sharpe Ratio 0.785 Probabilistic Sharpe Ratio 19.130% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.37 Alpha 0.075 Beta 1.028 Annual Standard Deviation 0.268 Annual Variance 0.072 Information Ratio 0.367 Tracking Error 0.215 Treynor Ratio 0.204 Total Fees $4060.49 Estimated Strategy Capacity $6800000.00 Lowest Capacity Asset ENPH V5C9T324VA05 |
from datetime import timedelta, time from AlgorithmImports import * from alpha import FundamentalFactorAlphaModel import pandas as pd class TutorialFundamental(QCAlgorithm): def Initialize(self): self.SetStartDate(2014, 1, 1) # Set Start Date self.SetCash(15000) # Set Strategy Cash self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel()) #Dette er vores portfefølje model, idet det kan lade os styre hvor stor procentdel der skal allokeres til diverse aktier self.SetExecution(ImmediateExecutionModel()) #Dette er vores executionmodel, idet at alle andre modeller (altså færdiglavet) introducerer gearing. self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.07)) #Max drawdown, idet vi ikke vil have for meget drawdown på de enkelte aktier. Her kan 7% også skiftes ud med 10% eller andet. self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) #For at gøre det så realistisk som muligt. self.SetWarmUp(timedelta(365)) self.UniverseSettings.Resolution = Resolution.Hour#Kan også skiftes ud med Resolution.Daily self.AddUniverse(self.CoarseSelectionModel, self.FineSelectionModel) #Vores univers settings. self.SetBenchmark('SPY') self.num_coarse = 200 #antallet af aktier der kan komme igennem vores coarse filter self.final_stocks = 10 #antallet af aktier som vi køber self.lastMonth = -1 #Bruges til at sætte vores rebalance funktion. #Vægten af vores 3 parametre, nemlig kvalitet, størrelse, og værdiansættelse. Size burde måske skiftes ud med momentum? quality_weight = 2 value_weight= 2 #Her indsætter vi nogle parametre, idet vi har brug for dem i vores alphamodel self.AddAlpha(FundamentalFactorAlphaModel(self.final_stocks, quality_weight, value_weight)) #for at plotte nogle features, som eksempelvis om vi bruger gearing (margin) def OnEndOfDay(self): self.Plot("Positions", "Num", len([x.Symbol for x in self.Portfolio.Values if self.Portfolio[x.Symbol].Invested])) self.Plot(f"Margin", "Used", self.Portfolio.TotalMarginUsed) self.Plot(f"Margin", "Remaining", self.Portfolio.MarginRemaining) self.Plot(f"Cash", "Remaining", self.Portfolio.Cash) def CoarseSelectionModel(self, coarse): #Rebalanceringsfunktion if self.Time.month == self.lastMonth: return Universe.Unchanged self.lastMonth = self.Time.month #Sotering af aktier, her i forhold til om vores aktie har fundamental data, om prisen er over 5 dollars og sorterer derefter efter højst volumen selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5], key = lambda x: x.DollarVolume, reverse=True) #Retunerer 200 symboler return [x.Symbol for x in selected[:self.num_coarse]] def FineSelectionModel(self, fine): #Vores fine selection model, som filtrerer aktier ud, som har følgende nøgletal over nul, idet nogle aktier kan have nøgletal på nul, da de ikke findes. filtered_fine = [x.Symbol for x in fine if x.OperationRatios.GrossMargin.Value > 0 and x.OperationRatios.QuickRatio.Value > 0 and x.OperationRatios.DebttoAssets.Value > 0 and x.ValuationRatios.BookValuePerShare > 0 and x.ValuationRatios.CashReturn > 0 and x.ValuationRatios.EarningYield > 0] rankBy360 = self.Returns(filtered_fine, 360) sorteret_mom360 = self.Sortere(rankBy360) sorteret_mom360 = [x[0] for x in sorteret_mom360[:10]] #Returnerer listen, stadig med 200 symboler. return [x for x in filtered_fine if str(x) in sorteret_mom360] def Returns(self, historie, periode): historie = self.History(historie, periode, Resolution.Daily) historie = historie.drop_duplicates().close.unstack(level = 0) afkast = historie.iloc[0] / historie.iloc[-1] afkast = afkast.to_dict() rangeret = sorted(afkast, key=afkast.get, reverse = True) return {symbol: rank for rank, symbol in enumerate(rangeret, 1)} def Sortere(self, historie): historie = {key: historie.get(key, 0) for key in set(historie)} historie = sorted(historie.items(), key = lambda x: x[1], reverse=True) return historie
from AlgorithmImports import * from datetime import timedelta from System.Collections.ObjectModel import ReadOnlyDictionary import pandas as pd from System.Diagnostics import Debug class FundamentalFactorAlphaModel(AlphaModel): #Her indsætter vi 'AlphaModel' idet at det er en alpha model vi laver. Det er altså en subclass af alphamodel. def __init__(self, final_stocks, quality_weight, value_weight): #Vi starter med at implementerer en initialize funktion, dog bare init i stedet for initialize self.lastMonth = -1 #Hjælper med rebalancering self.longs = [] #En liste som skal indeholde de symboler vi gerne vil købe. self.final_stocks = final_stocks self.period = timedelta(31) #Vi holder hvert symbol i 31 dage #Tilføjer alle vores værdier til en liste, og herefter deler vi hvert element i listen med summen af listen. weights = [quality_weight, value_weight] weights = [float(i)/sum(weights) for i in weights] #Weights kommer til at hedde [0.4, 0.2, 0.4], hvilket giver 1, da vores Insights ikke må være mere end 1 #Vi tager altså det første element, og tildeler til self.qu..., osv. self.quality_weight = weights[0] self.value_weight = weights[1] def Update(self, algorithm, data): #Rebalanceringsfunktion if algorithm.Time.month == self.lastMonth: return [] self.lastMonth = algorithm.Time.month #Her er det vi opbevarer vores insights, hvor at vi til sidst retunerere vores insights insights = [] #Hvis vores symboler ikke er i self.long, så sender vi et flat signal, hvilket betyder at vi #skal likvidere vores portefølje for security in algorithm.Portfolio.Values: if security.Invested and security.Symbol not in self.longs: insights.append(Insight(security.Symbol, self.period, InsightType.Price, InsightDirection.Flat, None, None, None, None)) #vores symboler i self.longs skal vi sende et langt signal lenght = len(self.longs) for i in range(lenght): insights.append(Insight(self.longs[i], self.period, InsightType.Price, InsightDirection.Up, None, (lenght - i)**2, None, (lenght - i)**2)) #returnere insights return insights def OnSecuritiesChanged(self, algorithm, changes): #Tilføjer alle vores added securities til en listen, nemlig added added = [x for x in changes.AddedSecurities] #Her tager vi hvert element i vores fundamentale, og laver en lambda funktion over tallet. Her betyder "True", om det er godt at have et lavt, eller et højt tal. Hvis True #så er det godt at have et højt tal, og omvendt. Vores float, betyder hvilken vægt at det skal have. Det er normalized vægt, hvilket betyder at det er forholdet mellem tallene # der betyder noget, og ikke selve tallene. #Herefter så smider vi tallene ind i vores egen funktion, nemlig Scores. Til sidst bliver vores resultat fra funktionen gemt i vores forskellige variabler. quality_scores = self.Scores(added, [(lambda x: x.Fundamentals.OperationRatios.GrossMargin.Value, True, 2), (lambda x: x.Fundamentals.OperationRatios.QuickRatio.Value, True, 1), (lambda x: x.Fundamentals.OperationRatios.DebttoAssets.Value, False, 2)]) value_scores = self.Scores(added, [(lambda x: x.Fundamentals.ValuationRatios.BookValuePerShare, True, 0.5), (lambda x: x.Fundamentals.ValuationRatios.CashReturn, True, 0.25), (lambda x: x.Fundamentals.ValuationRatios.EarningYield, False, 0.25)]) #Vi laver et tomt dictionary scores = {} #Her laver vi et for loop over vores dictionary, hvor vi har 2 variabler. Nemlig symbol og value. for symbol, value in quality_scores.items(): #Her gemmer vi jsymbolerne i hver af vores liste, da det er blevet delt op og sorteret quality_rank = value value_rank = value_scores[symbol] #Til sidst så ganger vi vores vægt på, som vi fik fra vores alphamodel. Disse variabler er blevet gemt i vores main.py scores[symbol] = quality_rank*self.quality_weight + value_rank*self.value_weight #Vi skal altså sorterer vores symboler og vægt igen, da vi har ganget en vægt på. Her er den laveste vægt det bedste. Her sorterer vi et dictionary, derfor skal vi have fat i tup[1] sorted_stock = sorted(scores.items(), key = lambda tup: tup[1], reverse=False) #Nu skal vi have fat i de 10 første symboler, som vi gør med denne funktion. sorted_symbol = [tup[0] for tup in sorted_stock][:self.final_stocks] #Her skal vi bare have fat i vores symboler, og ikke værdien, så derfor skal vi lave dette for loop. self.longs = [security.Symbol for security in sorted_symbol] algorithm.Log(", ".join(str(x.Symbol.Value) + ": " + str(scores[x]) for x in sorted_symbol)) #Vores egen funktion for at udregne scoren def Scores(self, added, fundamentals): #3 korte linjer der tjekker om vi overhovedet har nogle fundamentale værdier at gå igennem. Hvis vi ikke har, så er det ligemeget. lenght = len(fundamentals) if lenght == 0: return {} #Vi indledder to variabler scores = {} sortedBy = [] #Her laver vi et for loop som looper igennem alle variabler i fundamentals, og sætter dem lig med 0 rank = [0 for _ in fundamentals] #fundamentals er en streng, hvor at vi lopper igennem de forskellige strenge, og vi udtager det 3 element #i strengen, idet at det er vores fundamentale værdi weights = [tup[2] for tup in fundamentals] #Herefter normaliserer vi vægten weights = [float(i)/sum(weights) for i in weights] #Her sorterer vi vores fundamentale værdier, hvor at de første værdier, er det bedste værdier for tup in fundamentals: #Her tilføjer vi værdierne til vores liste sortedBy.append(sorted(added, key=tup[0], reverse=tup[1])) #Her sætter vi et index for hver security, hvor at vi laver en enumeration over vores sortedBy for index, symbol in enumerate(sortedBy[0]): rank[0] = index for j in range(1, lenght): rank[j] = sortedBy[j].index(symbol) #Til sidst laver vi et loop, hvor at vi udregner vores score score = 0 for i in range(lenght): score += rank[i] * weights[i] scores[symbol] = score #Til sidst retunerer vi vores streng return scores