| Overall Statistics |
|
Total Orders
4883
Average Win
0.12%
Average Loss
-0.12%
Compounding Annual Return
10.281%
Drawdown
34.200%
Expectancy
0.436
Start Equity
100000
End Equity
382931.20
Net Profit
282.931%
Sharpe Ratio
0.501
Sortino Ratio
0.433
Probabilistic Sharpe Ratio
4.903%
Loss Rate
27%
Win Rate
73%
Profit-Loss Ratio
0.96
Alpha
0.001
Beta
0.69
Annual Standard Deviation
0.121
Annual Variance
0.015
Information Ratio
-0.3
Tracking Error
0.084
Treynor Ratio
0.088
Total Fees
$294.78
Estimated Strategy Capacity
$390000000.00
Lowest Capacity Asset
LINTA TIIB7Z82AFS5
Portfolio Turnover
0.65%
|
# https://quantpedia.com/strategies/alpha-cloning-following-13f-fillings/
#
# Create a universe of active mutual fund managers.
# Use 13F filings to identify the “best idea” stocks for each manager.
# Invest in the stocks, which are the “best ideas” for most of the managers.
#
# QC Implementation changes:
# - Investor preferences was downloaded from https://www.insidermonkey.com/hedge-fund/browse/A/
# - Investors list consists of first 10 investors in each browse letter and from lists in basic and premium cards on https://www.gurufocus.com/guru/list
# - Investor preferences are modeled to be known 2 months after announcement.
#region imports
from AlgorithmImports import *
import numpy as np
from dateutil.relativedelta import relativedelta
#endregion
class AlphaCloningFollowing13FFillings(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2011, 1, 1)
self.SetCash(100_000)
self.UniverseSettings.Leverage = 5
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.settings.daily_precise_end_time = False
self.months_lag: int = 2 # Lag for getting investors preferences report
self.last_update_date: datetime.date
self.weights: dict[Symbol, float] = {}
self.investors_preferences: dict[datetime, dict[str, int]] = self.GetPreferences()
self.selection_flag: bool = False
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthStart(market), self.TimeRules.AfterMarketOpen(market), self.Selection)
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
return Universe.Unchanged
selected_report: dict[str, int] = None
min_date: datetime.date = self.Time.date() - relativedelta(months=self.months_lag+1) # quarterly data
max_date: datetime.date = self.Time.date()
for date in self.investors_preferences:
# Get latest report
if date >= min_date and date <= max_date:
selected_report = self.investors_preferences[date]
# Report might not be selected, because there are no data for that date
if selected_report is None:
return []
# Select universe based on report
selected: list[Symbol] = [x.Symbol for x in fundamental if x.Symbol.Value in selected_report]
# Calculate total preferences votes for selected report
total_preferences_votes: int = sum([x[1] for x in selected_report.items()])
# Calculate weight for each stock in selected universe
for symbol in selected:
# weight = total stock preferences votes / total investor votes in selected report
self.weights[symbol] = selected_report[symbol.Value] / total_preferences_votes
return selected
def OnData(self, slice: Slice) -> None:
if not self.selection_flag:
return
self.selection_flag = False
# Trade Execution
portfolio: List[PortfolioTarget] = [PortfolioTarget(symbol, w)
for symbol, w in self.weights.items()
if symbol in slice and slice[symbol]]
self.SetHoldings(portfolio, True)
self.weights.clear()
def Selection(self) -> None:
if self.Time.month % 3 == 2:
self.selection_flag = True
def GetPreferences(self) -> Dict[datetime, Dict[str, int]]:
preferences: dict[datetime, dict[str, int]] = {}
csv_string_file = self.Download('data.quantpedia.com/backtesting_data/economic/investors_preferences.csv')
lines: list[str] = csv_string_file.split('\r\n')
# Skip csv header in loop
for line in lines[1:]:
line_split: list[str] = line.split(';')
date: datetime.date = datetime.strptime(line_split[0], "%d.%m.%Y").date()
preferences[date]: dict[str, int] = {}
for ticker in line_split[1:]:
if ticker not in preferences[date]:
preferences[date][ticker] = 0
preferences[date][ticker] += 1
# Set last update date
if line == lines[-1]:
self.last_update_date = date
return preferences
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))