Overall Statistics
Total Orders
556
Average Win
0.14%
Average Loss
-0.09%
Compounding Annual Return
7.245%
Drawdown
1.700%
Expectancy
-0.068
Start Equity
100000
End Equity
107238.29
Net Profit
7.238%
Sharpe Ratio
-0.117
Sortino Ratio
-0.171
Probabilistic Sharpe Ratio
61.863%
Loss Rate
62%
Win Rate
38%
Profit-Loss Ratio
1.47
Alpha
-0.01
Beta
0.077
Annual Standard Deviation
0.039
Annual Variance
0.002
Information Ratio
-0.465
Tracking Error
0.154
Treynor Ratio
-0.06
Total Fees
$596.45
Estimated Strategy Capacity
$2000.00
Lowest Capacity Asset
YGMZ XIU99CJS1PPH
Portfolio Turnover
0.64%
Drawdown Recovery
77
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/51

class MomentumShortTermReversalAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(datetime.now() - timedelta(365))   
        self.set_cash(100000)         

        self.universe_settings.resolution = Resolution.DAILY
        self.settings.seed_initial_prices = True
        
        self._prices_by_symbol = {}
        self._decrease_winner = []
        self._increase_loser = []
        
        date_rule = self.date_rules.month_start("SPY")
        self.universe_settings.schedule.on(date_rule)
        self.add_universe(self._select)
        
        self.schedule.on(date_rule, self.time_rules.midnight, self._rebalance)
        self.set_warmup(252, Resolution.DAILY)
        
    def _select(self, fundamental):
        for f in fundamental:
            symbol = f.symbol
            self._prices_by_symbol[symbol] = self._prices_by_symbol.get(symbol, RollingWindow(13))
            self._prices_by_symbol[symbol].add(f.adjusted_price)

        prices_by_symbol = {s: p for s, p in self._prices_by_symbol.items() if p.is_ready}
        if len(prices_by_symbol) < 50:
            return Universe.UNCHANGED

        count = math.floor(len(prices_by_symbol)/3)

        def calc_yearly_return(data):
            prices = data[1]
            return prices[0]/prices[-1] - 1
        sorted_by_return = sorted(prices_by_symbol.items(), key=calc_yearly_return, reverse=True)

        def calc_garr_ratio(data):
            price = np.array([i for i in data[1]])
            returns = (price[:-1]-price[1:])/price[1:]
            GARR_12 = np.prod([(1+i)**(1/12) for i in returns])-1
            GARR_1 = (1+returns[0])**(1/12)-1
            return data[0], GARR_1 / GARR_12

        winners = sorted(sorted_by_return[:count], key=calc_garr_ratio)[:50] 
        losers = sorted(sorted_by_return[-count:], key=calc_garr_ratio, reverse=True)[:50]

        self._decrease_winner = [symbol for symbol, x in  winners]
        self._increase_loser = [symbol for symbol, x in  losers]        
        return self._decrease_winner + self._increase_loser

    def _rebalance(self):
        if self.is_warming_up:
            return

        if not (self._decrease_winner and self._increase_loser):
            return

        weight = -.25/len(self._increase_loser)
        targets = [PortfolioTarget(s, weight) for s in self._increase_loser]
        weight = .25/len(self._decrease_winner)
        targets += [PortfolioTarget(s, weight) for s in self._decrease_winner]
        self.set_holdings(targets, True)