| Overall Statistics |
|
Total Trades 8205 Average Win 0.25% Average Loss -0.21% Compounding Annual Return 3.342% Drawdown 44.100% Expectancy 0.028 Net Profit 17.885% Sharpe Ratio 0.24 Probabilistic Sharpe Ratio 2.989% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 1.18 Alpha 0.082 Beta -0.175 Annual Standard Deviation 0.242 Annual Variance 0.058 Information Ratio -0.256 Tracking Error 0.313 Treynor Ratio -0.331 Total Fees $38084.87 |
class ShortTimeReversal(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2016, 1, 1)
self.SetEndDate(2021, 1, 1)
self.SetCash(1000000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.SelectCoarse)
self.dollar_volume_selection_size = 100
self.roc_selection_size = int(0.1 * self.dollar_volume_selection_size)
self.lookback = 22
self.roc_by_symbol = {}
self.week = 0
def SelectCoarse(self, coarse):
# We should keep a dictionary for all securities that have been selected
for cf in coarse:
symbol = cf.Symbol
if symbol in self.roc_by_symbol:
self.roc_by_symbol[symbol].Update(cf.EndTime, cf.AdjustedPrice)
# Refresh universe each week
week_number = self.Time.date().isocalendar()[1]
if week_number == self.week:
return Universe.Unchanged
self.week = week_number
# sort and select by dollar volume
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
selected = {cf.Symbol: cf for cf in sortedByDollarVolume[:self.dollar_volume_selection_size]}
# New selections need a history request to warm up the indicator
symbols = [k for k in selected.keys()
if k not in self.roc_by_symbol or not self.roc_by_symbol[k].IsReady]
if symbols:
history = self.History(symbols, self.lookback, Resolution.Daily)
if history.empty:
self.Log(f'No history for {", ".join([x.Value for x in symbols])}')
history = history.close.unstack(0)
for symbol in symbols:
if symbol not in history:
continue
# Create and warm-up the RateOfChange indicator
roc = RateOfChange(self.lookback)
for time, price in history[symbol].dropna().iteritems():
roc.Update(time, price)
if roc.IsReady:
self.roc_by_symbol[symbol] = roc
# Sort the symbols by their ROC values
selectedRateOfChange = {}
for symbol in selected.keys():
if symbol in self.roc_by_symbol:
selectedRateOfChange[symbol] = self.roc_by_symbol[symbol]
sortedByRateOfChange = sorted(selectedRateOfChange.items(), key=lambda kv: kv[1], reverse=True)
# Define the top and the bottom to buy and sell
self.rocTop = [x[0] for x in sortedByRateOfChange[:self.roc_selection_size]]
self.rocBottom = [x[0] for x in sortedByRateOfChange[-self.roc_selection_size:]]
return self.rocTop + self.rocBottom
def OnData(self, data):
# Rebalance
for symbol in self.rocTop:
self.SetHoldings(symbol, -0.5/len(self.rocTop))
for symbol in self.rocBottom:
self.SetHoldings(symbol, 0.5/len(self.rocBottom))
# Clear the list of securities we have placed orders for
# to avoid new trades before the next universe selection
self.rocTop.clear()
self.rocBottom.clear()
def OnSecuritiesChanged(self, changes):
for security in changes.RemovedSecurities:
self.Liquidate(security.Symbol, 'Removed from Universe')