| Overall Statistics |
|
Total Orders 7404 Average Win 0.26% Average Loss -0.24% Compounding Annual Return 5.936% Drawdown 41.900% Expectancy 0.043 Start Equity 1000000 End Equity 1334607.84 Net Profit 33.461% Sharpe Ratio 0.225 Sortino Ratio 0.227 Probabilistic Sharpe Ratio 2.968% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.11 Alpha -0.014 Beta 0.625 Annual Standard Deviation 0.213 Annual Variance 0.045 Information Ratio -0.258 Tracking Error 0.198 Treynor Ratio 0.076 Total Fees $40758.13 Estimated Strategy Capacity $9700000.00 Lowest Capacity Asset FHC R735QTJ8XC9X Portfolio Turnover 17.24% |
#region imports
from AlgorithmImports import *
#endregion
class ShortTimeReversal(QCAlgorithm):
def initialize(self):
self.set_start_date(2016, 1, 1)
self.set_end_date(2021, 1, 1)
self.set_cash(1000000)
self.universe_settings.resolution = Resolution.DAILY
self.add_universe(self._select_coarse)
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 _select_coarse(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.end_time, cf.adjusted_price)
# 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
sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.dollar_volume, reverse=True)
selected = {cf.symbol: cf for cf in sorted_by_dollar_volume[: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].is_ready]
if symbols:
history = self.history(symbols, self._lookback+1, 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:
symbol_id = symbol.id.to_string()
if symbol_id not in history:
continue
# Create and warm-up the RateOfChange indicator
roc = RateOfChange(self._lookback)
for time, price in history[symbol_id].dropna().items():
roc.update(time, price)
if roc.is_ready:
self._roc_by_symbol[symbol] = roc
# Sort the symbols by their ROC values
selected_rate_of_change = {}
for symbol in selected.keys():
if symbol in self._roc_by_symbol:
selected_rate_of_change[symbol] = self._roc_by_symbol[symbol]
sorted_by_rate_of_change = sorted(selected_rate_of_change.items(), key=lambda kv: kv[1], reverse=True)
# Define the top and the bottom to buy and sell
self._roc_top = [x[0] for x in sorted_by_rate_of_change[:self._roc_selection_size]]
self._roc_bottom = [x[0] for x in sorted_by_rate_of_change[-self._roc_selection_size:]]
return self._roc_top + self._roc_bottom
def on_data(self, data):
# Rebalance
for symbol in self._roc_top:
self.set_holdings(symbol, -0.5/len(self._roc_top))
for symbol in self._roc_bottom:
self.set_holdings(symbol, 0.5/len(self._roc_bottom))
# Clear the list of securities we have placed orders for
# to avoid new trades before the next universe selection
self._roc_top.clear()
self._roc_bottom.clear()
def on_securities_changed(self, changes):
for security in changes.removed_securities:
self.liquidate(security.symbol, 'Removed from Universe')