Overall Statistics |
Total Trades
321
Average Win
0.67%
Average Loss
-0.71%
Compounding Annual Return
6.191%
Drawdown
30.800%
Expectancy
0.426
Net Profit
69.455%
Sharpe Ratio
0.395
Probabilistic Sharpe Ratio
1.548%
Loss Rate
26%
Win Rate
74%
Profit-Loss Ratio
0.94
Alpha
-0.005
Beta
0.699
Annual Standard Deviation
0.13
Annual Variance
0.017
Information Ratio
-0.317
Tracking Error
0.09
Treynor Ratio
0.073
Total Fees
$153.38
Estimated Strategy Capacity
$0
Lowest Capacity Asset
FSMAX.QuantpediaETF 2S
|
# 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. #region imports from AlgorithmImports import * #endregion 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.quantile = 10 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()) data.SetLeverage(5) self.data[symbol] = RollingWindow[float](self.period) self.recent_month = -1 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) if self.recent_month == self.Time.month: return self.recent_month = self.Time.month # Rebalance quarterly if self.recent_month % 3 != 0: return performance = {} for symbol in self.symbols: # If data for etf are ready calculate it's 6 month performance if self.data[symbol].IsReady: if self.Securities[symbol].GetLastData() and (self.Time.date() - self.Securities[symbol].GetLastData().Time.date()).days <= 3: prices = [x for x in self.data[symbol]] performance[symbol] = (prices[0] - prices[-1]) / prices[-1] if len(performance) < self.quantile: self.Liquidate() return decile = int(len(performance) / self.quantile) # 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) # 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"))