| Overall Statistics |
|
Total Trades 124 Average Win 0.61% Average Loss -0.62% Compounding Annual Return -5.385% Drawdown 19.700% Expectancy -0.291 Net Profit -8.697% Sharpe Ratio -0.572 Probabilistic Sharpe Ratio 1.706% Loss Rate 64% Win Rate 36% Profit-Loss Ratio 0.99 Alpha 0 Beta 0 Annual Standard Deviation 0.085 Annual Variance 0.007 Information Ratio -0.572 Tracking Error 0.085 Treynor Ratio 0 Total Fees $129.71 |
import numpy as np
import time
class FFFiveFactorAdapted(QCAlgorithm):
''' Stocks Selecting Strategy based on Fama French 5 Factors Model
Reference: https://tevgeniou.github.io/EquityRiskFactors/bibliography/FiveFactor.pdf
'''
def Initialize(self):
self.SetStartDate(2018, 1, 1) # Set Start Date
self.SetEndDate(2019, 8, 24) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.Log("here41")
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.Log("here51")
# self.num_coarse = 10000 # Number of symbols selected at Coarse Selection
self.fraction = 20 # Number of stocks to long
self.longSymbols = [] # Contains the stocks we'd like to long
self.nextLiquidate = self.Time # Initialize last trade time
self.rebalance_days = 180
# Set the weights of each factor
self.beta_m = 4 # 3 better than 2? 3 better. 4 better than 3? 4 better. 5>4? about even. 6>5? 5.
self.beta_s = 1
self.beta_h = 0 # putting to 0? 0 better than 1
self.beta_r = 1 # 2 better than 1.5? 1.5. 1>1.5? slightly
self.beta_g = 3 # putting to zero not good, from 1 to 2: 2 better, from 2 to 3? 3 better. 3vs 4? 3 better
self.beta_c = 2 # better at 2 than 3, 2 better than 1
def CoarseSelectionFunction(self, coarse):
'''Drop securities which have no fundamental data or have too low prices.
Select those with highest by dollar volume'''
if self.Time < self.nextLiquidate:
return Universe.Unchanged
self.Log("here31")
selected = sorted([x for x in coarse if x.HasFundamentalData], # and x.Price > 1
key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in selected]
def FineSelectionFunction(self, fine):
'''Select securities with highest score on Fama French 5 factors'''
# Select stocks with these 5 factors:
# MKT -- Book value per share: Value
# SMB -- TotalEquity: Size
# HML -- Operation profit margin: Quality
# RMW -- ROE: Profitability
# RMW2 -- gross margin
# CMA -- TotalAssetsGrowth: Investment Pattern
if self.Time >= self.nextLiquidate:
self.Log("here21")
filtered = [x for x in fine if x.ValuationRatios.PBRatio
and x.FinancialStatements.BalanceSheet.TotalEquity
and x.OperationRatios.GrossMargin
and x.ValuationRatios.PERatio > 0
and x.ValuationRatios.EVToEBITDA > 0
and x.CompanyReference.CountryId != "CHN"] # and x.CompanyReference.BusinessCountryID != "CHN"
self.Log("nr filtered: " + str(len(filtered)))
# for x in filtered:
# if x.ValuationRatios.PERatio < 4:
# self.Log(str(x.Symbol)[:4] + str(x.ValuationRatios.PERatio))
# self.Log(str(x.CompanyReference.BusinessCountryID))
# self.Log(str(x.CompanyReference.CountryId))
# Sort by factors
sortedByMkt1 = sorted(filtered, key=lambda x: x.ValuationRatios.PBRatio)
sortedByMkt2 = sorted(filtered, key=lambda x: x.ValuationRatios.PERatio)
sortedByMkt4 = sorted(filtered, key=lambda x: x.ValuationRatios.EVToEBITDA, reverse=False)
sortedByMkt3 = sorted(filtered, key=lambda x: x.ValuationRatios.FCFRatio)
sortedBySmb1 = sorted(filtered, key=lambda x: x.FinancialStatements.BalanceSheet.TotalEquity.Value)
sortedByHml1 = sorted(filtered, key=lambda x: x.ValuationRatios.PayoutRatio, reverse=True)
sortedByRmw1 = sorted(filtered, key=lambda x: x.OperationRatios.GrossMargin.Value, reverse=True)
sortedByRmw2 = sorted(filtered, key=lambda x: x.OperationRatios.ROE.Value, reverse=True)
sortedByRmw3 = sorted(filtered, key=lambda x: x.OperationRatios.ROA.Value, reverse=True)
sortedByCma1 = sorted(filtered, key=lambda x: abs(x.OperationRatios.TotalAssetsGrowth.Value), reverse=False)
sortedByCma2 = sorted(filtered, key=lambda x: x.OperationRatios.DebttoAssets.Value, reverse=False)
stockBySymbol = {}
# Get the rank based on 5 factors for every stock
for index, stock in enumerate(sortedBySmb1):
mktRank = self.beta_m * index
smbRank1 = self.beta_s * sortedByMkt1.index(stock)
smbRank2 = self.beta_s * sortedByMkt2.index(stock)
smbRank3 = self.beta_s * sortedByMkt3.index(stock)
smbRank4 = self.beta_s * sortedByMkt4.index(stock)
hmlRank = self.beta_h * sortedByHml1.index(stock)
rmwRank1 = self.beta_r * sortedByRmw1.index(stock)
rmwRank2 = self.beta_r * sortedByRmw2.index(stock)
rmwRank3 = self.beta_r * sortedByRmw3.index(stock)
cmaRank1 = self.beta_c * sortedByCma1.index(stock)
cmaRank2 = self.beta_g * sortedByCma2.index(stock)
avgRank = np.mean([mktRank, smbRank1, smbRank2, smbRank3, smbRank4, hmlRank, rmwRank1, rmwRank2, rmwRank3, cmaRank1, cmaRank2])
stockBySymbol[stock.Symbol] = avgRank
self.Log("here11")
sorted_dict = sorted(stockBySymbol.items(), key = lambda x: x[1], reverse = False)
sectors = [MorningstarSectorCode.BasicMaterials, MorningstarSectorCode.ConsumerCyclical, MorningstarSectorCode.FinancialServices, MorningstarSectorCode.RealEstate, MorningstarSectorCode.ConsumerDefensive, MorningstarSectorCode.Healthcare, MorningstarSectorCode.Utilities, MorningstarSectorCode.CommunicationServices, MorningstarSectorCode.Energy, MorningstarSectorCode.Industrials, MorningstarSectorCode.Technology]
self.longSymbols = []
self.Log("here12")
for i, sector in enumerate(sectors):
sector_symbols = []
# filtered or fine???
sector_stocks = [x.Symbol for x in fine if x.AssetClassification.MorningstarSectorCode == sector]
for x in sorted_dict:
if x[0] in sector_stocks:
sector_symbols.append(x[0])
self.longSymbols += sector_symbols[:int(len(sector_stocks)/self.fraction)]
self.Log("here13")
return self.longSymbols
def OnData(self, data):
'''Rebalance Every self.rebalance_days'''
# Liquidate stocks in the end of every month
self.Log("here1")
# if len(self.longSymbols)!= 0:
# for symbol in self.longSymbols:
# self.AddEquity(symbol)
if self.Time >= self.nextLiquidate:
for holding in self.Portfolio.Values:
# If the holding is in the long/short list for the next month, don't liquidate
if holding.Symbol in self.longSymbols:
continue
# If the holding is not in the list, liquidate
if holding.Invested:
self.Liquidate(holding.Symbol)
count = len(self.longSymbols)
self.Log("here2")
# It means the long & short lists for the month have been cleared
if count == 0:
return
self.Log("here3")
# Open long position at the start of every month
for symbol in self.longSymbols:
# stock_price = data[symbol].Price
self.MarketOrder(symbol, 100)
# self.SetHoldings(symbol, 1/count)
self.Log("here4")
# Set the Liquidate Date
self.nextLiquidate = self.Time + timedelta(self.rebalance_days)
self.Log("amount:" + str(len(self.longSymbols)))
self.Log("invested: " + str([symbol.Value for symbol in self.longSymbols]))
# After opening positions, clear the long & short symbol lists until next universe selection
self.longSymbols.clear()
# symbols = [x[0] for x in sorted_dict]
# quality_symbols = [x for x in symbols[:self.num_quality]]
# # Get the quarterly returns for each symbol
# history = self.History(quality_symbols, self.momentum_days, Resolution.Daily)
# history = history.close.unstack(level = 0) #.drop_duplicates()
# rankByQuarterReturn = self.GetQuarterlyReturn(history)
# for symbol in quality_symbols:
# if symbol not in rankByQuarterReturn.keys():
# rankByQuarterReturn[symbol] = self.num_coarse/2
# stockBySymbol = {}
# for index, stock in enumerate(quality_symbols):
# stockBySymbol[stock] = np.mean([rankByQuarterReturn[stock]])
# sorted_dict = sorted(stockBySymbol.items(), key = lambda x: x[1], reverse = False)
# def GetQuarterlyReturn(self, history):
# '''
# Get the rank of securities based on their quarterly return from historical close prices
# Return: dictionary
# '''
# # Get quarterly returns for all symbols
# # (The first row divided by the last row)
# returns = history.iloc[0] / history.iloc[-1]
# # Transform them to dictionary structure
# returns = returns.to_dict()
# # Get the rank of the returns (key: symbol; value: rank)
# # (The symbol with the 1st quarterly return ranks the 1st, etc.)
# ranked = sorted(returns, key = returns.get, reverse = True)
# return {symbol: rank for rank, symbol in enumerate(ranked, 1)}import numpy as np
import time
class FFFiveFactorAdapted(QCAlgorithm):
''' Stocks Selecting Strategy based on Fama French 5 Factors Model
Reference: https://tevgeniou.github.io/EquityRiskFactors/bibliography/FiveFactor.pdf
'''
def Initialize(self):
self.SetStartDate(2010, 1, 1) # Set Start Date
self.SetEndDate(2020, 8, 24) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
# self.num_coarse = 10000 # Number of symbols selected at Coarse Selection
self.fraction = 20 # Number of stocks to long
self.longSymbols = [] # Contains the stocks we'd like to long
self.nextLiquidate = self.Time # Initialize last trade time
self.rebalance_days = 340
# Set the weights of each factor
self.beta_m = 4
def CoarseSelectionFunction(self, coarse):
'''Drop securities which have no fundamental data or have too low prices.
Select those with highest by dollar volume'''
if self.Time < self.nextLiquidate:
return Universe.Unchanged
self.Log("here31")
selected = sorted([x for x in coarse if x.HasFundamentalData], # and x.Price > 1
key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in selected]
def FineSelectionFunction(self, fine):
'''Select securities with highest score on Fama French 5 factors'''
# Select stocks with these 5 factors:
# MKT -- Book value per share: Value
# SMB -- TotalEquity: Size
# HML -- Operation profit margin: Quality
# RMW -- ROE: Profitability
# RMW2 -- gross margin
# CMA -- TotalAssetsGrowth: Investment Pattern
if self.Time >= self.nextLiquidate:
filtered = [x for x in fine if x.ValuationRatios.PBRatio
and x.FinancialStatements.BalanceSheet.TotalEquity
and x.OperationRatios.GrossMargin
and x.ValuationRatios.PERatio
and x.ValuationRatios.EVToEBITDA] # and x.CompanyReference.BusinessCountryID != "CHN"
self.Log("nr filtered: " + str(len(filtered)))
# Sort by factors
sortedBySmb1 = sorted(filtered, key=lambda x: x.FinancialStatements.BalanceSheet.TotalEquity.Value)
stockBySymbol = {}
# Get the rank based on 5 factors for every stock
for index, stock in enumerate(sortedBySmb1):
mktRank = self.beta_m * index
avgRank = np.mean([mktRank])
stockBySymbol[stock.Symbol] = avgRank
sorted_dict = sorted(stockBySymbol.items(), key = lambda x: x[1], reverse = False)
sectors = [MorningstarSectorCode.BasicMaterials, MorningstarSectorCode.ConsumerCyclical, MorningstarSectorCode.FinancialServices, MorningstarSectorCode.RealEstate, MorningstarSectorCode.ConsumerDefensive, MorningstarSectorCode.Healthcare, MorningstarSectorCode.Utilities, MorningstarSectorCode.CommunicationServices, MorningstarSectorCode.Energy, MorningstarSectorCode.Industrials, MorningstarSectorCode.Technology]
self.longSymbols = []
for i, sector in enumerate(sectors):
sector_symbols = []
# filtered or fine???
sector_stocks = [x.Symbol for x in fine if x.AssetClassification.MorningstarSectorCode == sector]
for x in sorted_dict:
if x[0] in sector_stocks:
sector_symbols.append(x[0])
self.longSymbols += sector_symbols[:int(len(sector_stocks)/self.fraction)]
return self.longSymbols
def OnData(self, data):
'''Rebalance Every self.rebalance_days'''
# Liquidate stocks in the end of every month
if self.Time >= self.nextLiquidate:
for holding in self.Portfolio.Values:
# If the holding is in the long/short list for the next month, don't liquidate
if holding.Symbol in self.longSymbols:
continue
# If the holding is not in the list, liquidate
if holding.Invested:
self.Liquidate(holding.Symbol)
count = len(self.longSymbols)
# It means the long & short lists for the month have been cleared
if count == 0:
return
# Open long position at the start of every month
for symbol in self.longSymbols:
self.SetHoldings(symbol, 1/count)
# Set the Liquidate Date
self.nextLiquidate = self.Time + timedelta(self.rebalance_days)
# After opening positions, clear the long & short symbol lists until next universe selection
self.longSymbols.clear()import numpy as np
import time
class FFFiveFactorAdapted(QCAlgorithm):
''' Stocks Selecting Strategy based on Fama French 5 Factors Model
Reference: https://tevgeniou.github.io/EquityRiskFactors/bibliography/FiveFactor.pdf
'''
def Initialize(self):
self.SetStartDate(2000, 1, 1) # Set Start Date
self.SetEndDate(2020, 8, 21) # Set End Date
self.SetCash(100000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
# self.num_coarse = 10000 # Number of symbols selected at Coarse Selection
self.fraction = 20 # Number of stocks to long
self.longSymbols = [] # Contains the stocks we'd like to long
self.nextLiquidate = self.Time # Initialize last trade time
self.rebalance_days = 180
# Set the weights of each factor
self.beta_m = 2
self.beta_s = 1
self.beta_h = 0 # putting to 0? 0 better than 1
self.beta_r = 1.5
self.beta_g = 3 # putting to zero not good, from 1 to 2: 2 better, from 2 to 3? 3 better
self.beta_c = 2 # better at 2 than 3, 2 better than 1
def CoarseSelectionFunction(self, coarse):
'''Drop securities which have no fundamental data or have too low prices.
Select those with highest by dollar volume'''
if self.Time < self.nextLiquidate:
return Universe.Unchanged
selected = sorted([x for x in coarse if x.HasFundamentalData ], # and x.Price > 1
key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in selected]
def FineSelectionFunction(self, fine):
'''Select securities with highest score on Fama French 5 factors'''
# Select stocks with these 5 factors:
# MKT -- Book value per share: Value
# SMB -- TotalEquity: Size
# HML -- Operation profit margin: Quality
# RMW -- ROE: Profitability
# RMW2 -- gross margin
# CMA -- TotalAssetsGrowth: Investment Pattern
if self.Time >= self.nextLiquidate:
filtered = [x for x in fine if x.ValuationRatios.PBRatio
and x.FinancialStatements.BalanceSheet.TotalEquity
and x.OperationRatios.GrossMargin
and x.ValuationRatios.PERatio > 0
and x.ValuationRatios.EVToEBITDA > 0] # and x.OperationRatios.TotalAssetsGrowth.Value > 0
# assetgrowth higher than 0???
self.Log("nr filtered: " + str(len(filtered)))
# Sort by factors
sortedByMkt1 = sorted(filtered, key=lambda x: x.ValuationRatios.PBRatio)
sortedByMkt2 = sorted(filtered, key=lambda x: x.ValuationRatios.PERatio)
sortedByMkt4 = sorted(filtered, key=lambda x: x.ValuationRatios.EVToEBITDA, reverse=False)
sortedByMkt3 = sorted(filtered, key=lambda x: x.ValuationRatios.FCFRatio)
sortedBySmb1 = sorted(filtered, key=lambda x: x.FinancialStatements.BalanceSheet.TotalEquity.Value)
sortedByHml1 = sorted(filtered, key=lambda x: x.ValuationRatios.PayoutRatio, reverse=True)
sortedByRmw1 = sorted(filtered, key=lambda x: x.OperationRatios.GrossMargin.Value, reverse=True)
sortedByRmw2 = sorted(filtered, key=lambda x: x.OperationRatios.ROE.Value, reverse=True)
sortedByRmw3 = sorted(filtered, key=lambda x: x.OperationRatios.ROA.Value, reverse=True)
sortedByCma1 = sorted(filtered, key=lambda x: abs(x.OperationRatios.TotalAssetsGrowth.Value), reverse=False)
sortedByCma2 = sorted(filtered, key=lambda x: x.OperationRatios.DebttoAssets.Value, reverse=False)
stockBySymbol = {}
# Get the rank based on 5 factors for every stock
for index, stock in enumerate(sortedBySmb1):
mktRank = self.beta_m * index
smbRank1 = self.beta_s * sortedByMkt1.index(stock)
smbRank2 = self.beta_s * sortedByMkt2.index(stock)
smbRank3 = self.beta_s * sortedByMkt3.index(stock)
smbRank4 = self.beta_s * sortedByMkt4.index(stock)
hmlRank = self.beta_h * sortedByHml1.index(stock)
rmwRank1 = self.beta_r * sortedByRmw1.index(stock)
rmwRank2 = self.beta_r * sortedByRmw2.index(stock)
rmwRank3 = self.beta_r * sortedByRmw3.index(stock)
cmaRank1 = self.beta_c * sortedByCma1.index(stock)
cmaRank2 = self.beta_g * sortedByCma2.index(stock)
avgRank = np.mean([mktRank, smbRank1, smbRank2, smbRank3, smbRank4, hmlRank, rmwRank1, rmwRank2, rmwRank3, cmaRank1, cmaRank2])
stockBySymbol[stock.Symbol] = avgRank
sorted_dict = sorted(stockBySymbol.items(), key = lambda x: x[1], reverse = False)
sectors = [MorningstarSectorCode.BasicMaterials, MorningstarSectorCode.ConsumerCyclical, MorningstarSectorCode.FinancialServices, MorningstarSectorCode.RealEstate, MorningstarSectorCode.ConsumerDefensive, MorningstarSectorCode.Healthcare, MorningstarSectorCode.Utilities, MorningstarSectorCode.CommunicationServices, MorningstarSectorCode.Energy, MorningstarSectorCode.Industrials, MorningstarSectorCode.Technology]
self.longSymbols = []
for i, sector in enumerate(sectors):
sector_symbols = []
# filtered or fine???
sector_stocks = [x.Symbol for x in fine if x.AssetClassification.MorningstarSectorCode == sector]
for x in sorted_dict:
if x[0] in sector_stocks:
sector_symbols.append(x[0])
self.longSymbols += sector_symbols[:int(len(sector_stocks)/self.fraction)]
# Materials_symbols = []
# for x in sorted_dict:
# if x[0] in Materials:
# Materials_symbols.append(x[0])
# ConsumerCyclical_symbols = []
# for x in sorted_dict:
# if x[0] in ConsumerCyclical:
# ConsumerCyclical_symbols.append(x[0])
# Financial_symbols = []
# for x in sorted_dict:
# if x[0] in Financial:
# Financial_symbols.append(x[0])
# RealEstate_symbols = []
# for x in sorted_dict:
# if x[0] in RealEstate:
# RealEstate_symbols.append(x[0])
# ConsumerDefensive_symbols = []
# for x in sorted_dict:
# if x[0] in ConsumerDefensive:
# ConsumerDefensive_symbols.append(x[0])
# Healthcare_symbols = []
# for x in sorted_dict:
# if x[0] in Healthcare:
# Healthcare_symbols.append(x[0])
# Utilities_symbols = []
# for x in sorted_dict:
# if x[0] in Utilities:
# Utilities_symbols.append(x[0])
# Communication_symbols = []
# for x in sorted_dict:
# if x[0] in Communication:
# Communication_symbols.append(x[0])
# Energy_symbols = []
# for x in sorted_dict:
# if x[0] in Energy:
# Energy_symbols.append(x[0])
# Industrials_symbols = []
# for x in sorted_dict:
# if x[0] in Industrials:
# Industrials_symbols.append(x[0])
# Technology_symbols = []
# for x in sorted_dict:
# if x[0] in Technology:
# Technology_symbols.append(x[0])
# # Pick the stocks with the highest scores to long
# self.longSymbols= Materials_symbols[:int(len(Materials_symbols)/self.fraction)] + ConsumerCyclical_symbols[:int(len(ConsumerCyclical_symbols)/self.fraction)] + Financial_symbols[:int(len(Financial_symbols)/self.fraction)] + RealEstate_symbols[:int(len(RealEstate_symbols)/self.fraction)] + ConsumerDefensive_symbols[:int(len(ConsumerDefensive_symbols)/self.fraction)] + Healthcare_symbols[:int(len(Healthcare_symbols)/self.fraction)] + Utilities_symbols[:int(len(Utilities_symbols)/self.fraction)] + Communication_symbols[:int(len(Communication_symbols)/self.fraction)] + Energy_symbols[:int(len(Energy_symbols)/self.fraction)] + Industrials_symbols[:int(len(Industrials_symbols)/self.fraction)] + Technology_symbols[:int(len(Technology_symbols)/self.fraction)]
## one sector:
# one_sector = []
# for x in sorted_dict:
# if x[0] in Technology:
# one_sector.append(x[0])
# self.longSymbols= one_sector[:self.num_long]
return self.longSymbols
def OnData(self, data):
'''Rebalance Every self.rebalance_days'''
# Liquidate stocks in the end of every month
if self.Time >= self.nextLiquidate:
for holding in self.Portfolio.Values:
# If the holding is in the long/short list for the next month, don't liquidate
if holding.Symbol in self.longSymbols:
continue
# If the holding is not in the list, liquidate
if holding.Invested:
self.Liquidate(holding.Symbol)
count = len(self.longSymbols)
# It means the long & short lists for the month have been cleared
if count == 0:
return
# Open long position at the start of every month
for symbol in self.longSymbols:
self.SetHoldings(symbol, 1/count)
# Set the Liquidate Date
self.nextLiquidate = self.Time + timedelta(self.rebalance_days)
self.Log("amount:" + str(len(self.longSymbols)))
self.Log("invested: " + str([symbol.Value for symbol in self.longSymbols]))
# After opening positions, clear the long & short symbol lists until next universe selection
self.longSymbols.clear()
# symbols = [x[0] for x in sorted_dict]
# quality_symbols = [x for x in symbols[:self.num_quality]]
# # Get the quarterly returns for each symbol
# history = self.History(quality_symbols, self.momentum_days, Resolution.Daily)
# history = history.close.unstack(level = 0) #.drop_duplicates()
# rankByQuarterReturn = self.GetQuarterlyReturn(history)
# for symbol in quality_symbols:
# if symbol not in rankByQuarterReturn.keys():
# rankByQuarterReturn[symbol] = self.num_coarse/2
# stockBySymbol = {}
# for index, stock in enumerate(quality_symbols):
# stockBySymbol[stock] = np.mean([rankByQuarterReturn[stock]])
# sorted_dict = sorted(stockBySymbol.items(), key = lambda x: x[1], reverse = False)
# def GetQuarterlyReturn(self, history):
# '''
# Get the rank of securities based on their quarterly return from historical close prices
# Return: dictionary
# '''
# # Get quarterly returns for all symbols
# # (The first row divided by the last row)
# returns = history.iloc[0] / history.iloc[-1]
# # Transform them to dictionary structure
# returns = returns.to_dict()
# # Get the rank of the returns (key: symbol; value: rank)
# # (The symbol with the 1st quarterly return ranks the 1st, etc.)
# ranked = sorted(returns, key = returns.get, reverse = True)
# return {symbol: rank for rank, symbol in enumerate(ranked, 1)}