Overall Statistics |
Total Trades
338
Average Win
0.59%
Average Loss
-0.97%
Compounding Annual Return
3.232%
Drawdown
38.000%
Expectancy
0.085
Net Profit
26.081%
Sharpe Ratio
0.257
Probabilistic Sharpe Ratio
1.392%
Loss Rate
33%
Win Rate
67%
Profit-Loss Ratio
0.61
Alpha
0.053
Beta
-0.117
Annual Standard Deviation
0.147
Annual Variance
0.022
Information Ratio
-0.385
Tracking Error
0.229
Treynor Ratio
-0.323
Total Fees
$164.59
Estimated Strategy Capacity
$0
|
# https://quantpedia.com/strategies/momentum-in-mutual-fund-returns/ # # The investment universe consists of equity funds from the CRSP Mutual Fund database. # This universe is then shrunk to no-load funds (to remove entrance fees). # Investors then sort mutual funds based on their past 6-month return and divide them into deciles. # The top decile of mutual funds is then picked into an investment portfolio (equally weighted), and funds are held for three months. # Other measures of momentum could also be used in sorting (fund’s closeness to 1 year high in NAV and momentum factor loading), # and it is highly probable that the combined predictor would have even better results than only the simple 6-month momentum. # # QC Implementation: # - Universe consist of approximately 850 mutual funds. class MomentuminMutualFundReturns(QCAlgorithm): def Initialize(self): # NOTE: most of the data start from 2014 and until 2015 there wasn't any trade self.SetStartDate(2014, 1, 1) self.SetCash(100000) self.data = {} self.symbols = [] self.period = 21 * 6 # Storing 6 months of daily prices self.symbol = self.AddEquity('SPY', Resolution.Daily).Symbol # Load csv file with etf symbols and split line with semi-colon etf_symbols_csv = self.Download("data.quantpedia.com/backtesting_data/equity/mutual_funds/symbols.csv") splitted_csv = etf_symbols_csv.split(';') for symbol in splitted_csv: self.symbols.append(symbol) # Subscribe for QuantpediaETF by etf symbol, then set fee model and leverage data = self.AddData(QuantpediaETF, symbol, Resolution.Daily) data.SetFeeModel(CustomFeeModel(self)) data.SetLeverage(5) self.data[symbol] = RollingWindow[float](self.period) self.month = 0 self.selection_flag = False self.Schedule.On(self.DateRules.MonthStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection) def OnData(self, data): # Update daily prices of etfs for symbol in self.symbols: if symbol in data and data[symbol]: price = data[symbol].Value self.data[symbol].Add(price) # Rebalance monthly if not self.selection_flag: return self.selection_flag = False performance = {} for symbol in self.symbols: # If data for etf are ready calculate it's 6 month performance if self.data[symbol].IsReady: prices = [x for x in self.data[symbol]] performance[symbol] = (prices[0] - prices[-1]) / prices[-1] if len(performance) == 0: self.Liquidate() return decile = int(len(performance) / 10) # sort dictionary by performance and based on it create sorted list sorted_by_perf = [x[0] for x in sorted(performance.items(), key=lambda item: item[1], reverse=True)] # select top decile etfs for investment based on performance long = sorted_by_perf[:decile] # Trade execution invested_etfs = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in invested_etfs: if symbol not in long: self.Liquidate(symbol) long_length = len(long) for symbol in long: self.SetHoldings(symbol, 1 / long_length) def Selection(self): # Rebalance quarterly if self.month % 3 == 0: self.selection_flag = True self.month += 1 # Quantpedia data # NOTE: IMPORTANT: Data order must be ascending (datewise) class QuantpediaETF(PythonData): def GetSource(self, config, date, isLiveMode): return SubscriptionDataSource("data.quantpedia.com/backtesting_data/equity/mutual_funds/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv) def Reader(self, config, line, date, isLiveMode): data = QuantpediaETF() data.Symbol = config.Symbol if not line[0].isdigit(): return None split = line.split(';') data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1) data['settle'] = float(split[1]) data.Value = float(split[1]) return data # Custom fee model class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD"))