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')