Overall Statistics |
Total Trades
10243
Average Win
0.19%
Average Loss
-0.19%
Compounding Annual Return
-0.879%
Drawdown
47.800%
Expectancy
-0.015
Net Profit
-16.013%
Sharpe Ratio
-0.011
Probabilistic Sharpe Ratio
0.000%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.96
Alpha
0.002
Beta
-0.05
Annual Standard Deviation
0.115
Annual Variance
0.013
Information Ratio
-0.3
Tracking Error
0.219
Treynor Ratio
0.024
Total Fees
$352.40
|
import numpy as np def Return(values): return (values[-1] - values[0]) / values[0] def Volatility(values): values = np.array(values) returns = (values[1:]-values[:-1])/values[:-1] return np.std(returns) # Custom fee model class CustomFeeModel(FeeModel): def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD")) # Quantpedia data # NOTE: IMPORTANT: Data order must be ascending (datewise) class QuantpediaFutures(PythonData): def GetSource(self, config, date, isLiveMode): return SubscriptionDataSource("https://quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv) def Reader(self, config, line, date, isLiveMode): data = QuantpediaFutures() data.Symbol = config.Symbol try: 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]) except: return None return data
# https://quantpedia.com/strategies/consistent-momentum-strategy/ # # The investment universe consists of stocks listed at NYSE, AMEX, and NASDAQ, whose price data (at least for the past 7 months) are available # at the CRSP database. The investor creates a zero-investment portfolio at the end of the month t, longing stocks that are in the top decile # in terms of returns both in the period from t-7 to t-1 and from t-6 to t, while shorting stocks in the bottom decile in both periods (i.e. # longing consistent winners and shorting consistent losers). The stocks in the portfolio are weighted equally. The holding period is six months, # with no rebalancing during the period. There is a one-month skip between the formation and holding period. from collections import deque import numpy as np import fk_tools class Consistent_Momentum_Strategy(QCAlgorithm): def Initialize(self): self.SetStartDate(2000, 1, 1) self.SetEndDate(2019, 10, 1) self.SetCash(100000) self.course_count = 1000 self.long = [] self.short = [] self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.symbol = 'SPY' self.AddEquity(self.symbol, Resolution.Daily) self.period = 7*21 self.SetWarmUp(self.period) self.months = 0 self.selection_flag = False self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Rebalance) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: security.SetFeeModel(fk_tools.CustomFeeModel(self)) def CoarseSelectionFunction(self, coarse): if not self.selection_flag: return Universe.Unchanged selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5 and x.Market == 'usa'], key=lambda x: x.DollarVolume, reverse=True) return [x.Symbol for x in selected[:self.course_count]] def FineSelectionFunction(self, fine): momentum_t71 = {} momentum_t60 = {} for stock in fine: symbol = stock.Symbol hist = self.History([symbol], self.period, Resolution.Daily) if 'close' in hist.columns: closes = hist['close'] if len(closes) == self.period: # Return calc closes_t71 = closes[:self.period - 21] closes_t60 = closes[-self.period - 21:] momentum_t71[symbol] = fk_tools.Return(closes_t71) momentum_t60[symbol] = fk_tools.Return(closes_t60) if len(momentum_t71) == 0: return [] if len(momentum_t60) == 0: return [] # Momentum t-7 to t-1 sorting sorted_by_mom_t71 = sorted(momentum_t71.items(), key = lambda x: x[1], reverse = True) decile = int(len(sorted_by_mom_t71) / 10) high_by_mom_t71 = [x[0] for x in sorted_by_mom_t71[:decile]] low_by_mom_t71 = [x[0] for x in sorted_by_mom_t71[-decile:]] # Momentum t-6 to t sorting sorted_by_mom_t60 = sorted(momentum_t60.items(), key = lambda x: x[1], reverse = True) decile = int(len(sorted_by_mom_t60) / 10) high_by_mom_t60 = [x[0] for x in sorted_by_mom_t60[:decile]] low_by_mom_t60 = [x[0] for x in sorted_by_mom_t60[-decile:]] self.long = [x for x in high_by_mom_t71 if x in high_by_mom_t60] self.short = [x for x in low_by_mom_t71 if x in low_by_mom_t60] self.selection_flag = False return self.long + self.short def Rebalance(self): if self.months == 0: self.selection_flag = True self.months += 1 return if self.months == 1: # Trade execution and liquidation invested = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in invested: if symbol not in self.long + self.short: self.Liquidate(symbol) count = len(self.long + self.short) if count == 0: return for symbol in self.long: self.SetHoldings(symbol, 1/count) for symbol in self.short: self.SetHoldings(symbol, -1/count) self.months += 1 if self.months == 6: self.months = 0