Overall Statistics
Total Trades
4809
Average Win
0.12%
Average Loss
-0.13%
Compounding Annual Return
12.074%
Drawdown
34.000%
Expectancy
0.419
Net Profit
282.763%
Sharpe Ratio
0.697
Probabilistic Sharpe Ratio
9.103%
Loss Rate
27%
Win Rate
73%
Profit-Loss Ratio
0.93
Alpha
0.022
Beta
0.77
Annual Standard Deviation
0.131
Annual Variance
0.017
Information Ratio
0.021
Tracking Error
0.076
Treynor Ratio
0.119
Total Fees
$295.13
Estimated Strategy Capacity
$9300000.00
Lowest Capacity Asset
WCBO R735QTJ8XC9X
# 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:
#   - 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):
        self.SetStartDate(2011, 1, 1)
        self.SetCash(100000)
        
        self.weight = {}
        self.investors_preferences = {}
        
        self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.months_lag = 2 # Lag for getting investors preferences report
            
        csv_string_file = self.Download('data.quantpedia.com/backtesting_data/economic/investors_preferences.csv')
        lines = csv_string_file.split('\r\n')
        dates = []
        # Skip csv header in loop
        for line in lines[1:]:
            line_split = line.split(';')
            date = datetime.strptime(line_split[0], "%d.%m.%Y").date()
            
            self.investors_preferences[date] = {}
            
            for ticker in line_split[1:]:
                if ticker not in self.investors_preferences[date]:
                    self.investors_preferences[date][ticker] = 0
                    
                self.investors_preferences[date][ticker] += 1
            
        self.month_counter = 0
        self.selection_flag = False
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction)
        self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
        
    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            security.SetFeeModel(CustomFeeModel())
            security.SetLeverage(5)
            
    def CoarseSelectionFunction(self, coarse):
        if not self.selection_flag:
            return Universe.Unchanged
        
        selected_report = None
        min_date = self.Time.date() - relativedelta(months=self.months_lag+1)   # quarterly data
        max_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 Universe.Unchanged
        
        # Select universe based on report
        selected = [x.Symbol for x in coarse if x.Symbol.Value in selected_report]
        
        # Calculate total preferences votes for selected report
        total_preferences_votes = 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.weight[symbol] = selected_report[symbol.Value] / total_preferences_votes

        return selected

    def OnData(self, data):
        if not self.selection_flag:
            return
        self.selection_flag = False
        
        # Trade Execution
        stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        for symbol in stocks_invested:
            if symbol not in self.weight:
                self.Liquidate(symbol)
        
        for symbol, w in self.weight.items():
            if symbol in data and data[symbol]:
                self.SetHoldings(symbol, w)
        
        self.weight.clear()
        
    def Selection(self):
        if self.Time.month % 3 == 2:
            self.selection_flag = True
        
# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))