Overall Statistics
Total Orders
5652
Average Win
0.23%
Average Loss
-0.21%
Compounding Annual Return
2.923%
Drawdown
29.500%
Expectancy
0.049
Start Equity
100000
End Equity
127242.12
Net Profit
27.242%
Sharpe Ratio
0.144
Sortino Ratio
0.169
Probabilistic Sharpe Ratio
0.129%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
1.12
Alpha
0.017
Beta
0.085
Annual Standard Deviation
0.169
Annual Variance
0.029
Information Ratio
-0.324
Tracking Error
0.203
Treynor Ratio
0.289
Total Fees
$11378.92
Estimated Strategy Capacity
$4000.00
Lowest Capacity Asset
BHACW VYLU54GQQJQD
Portfolio Turnover
3.15%
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/51


class MomentumShortTermReversalAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2010, 1, 1)  
        self.set_end_date(2018, 5, 10)    
        self.set_cash(100000)         

        self.universe_settings.resolution = Resolution.DAILY
        self.set_security_initializer(BrokerageModelSecurityInitializer(
            self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))

        spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)
        date_rule = self.date_rules.month_start(spy)
        self.universe_settings.schedule.on(date_rule)
        self.add_universe(self._coarse_selection_function)
        self.schedule.on(date_rule, self.time_rules.midnight, self._rebalance)
        self._symbol_price = {}
        self._decrease_winner = None
        self._increase_loser = None
        self.set_warmup(timedelta(365))
        
    def _coarse_selection_function(self, coarse):
        for i in coarse:
            if i.symbol not in self._symbol_price:
                self._symbol_price[i.symbol] = SymbolData(self, i.symbol)
            self._symbol_price[i.symbol].window.add(float(i.adjusted_price))
            if self._symbol_price[i.symbol].window.is_ready:
                price = np.array([i for i in self._symbol_price[i.symbol].window])
                returns = (price[:-1]-price[1:])/price[1:]
                self._symbol_price[i.symbol].yearly_return = (price[0]-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
                self._symbol_price[i.symbol].garr_ratio = GARR_1 / GARR_12

        ready_symbol_price = {symbol: symbol_data for symbol, symbol_data in self._symbol_price.items() if symbol_data.window.is_ready}
        if ready_symbol_price and len(ready_symbol_price)>50:        
            sorted_by_return = sorted(ready_symbol_price, key=lambda x: ready_symbol_price[x].yearly_return, reverse=True)
            winner = sorted_by_return[:int(len(sorted_by_return)*0.3)]
            loser = sorted_by_return[-int(len(sorted_by_return)*0.3):]
            self._decrease_winner = sorted(winner, key=lambda x: ready_symbol_price[x].garr_ratio)[:50] 
            self._increase_loser = sorted(loser, key=lambda x: ready_symbol_price[x].garr_ratio, reverse=True)[:50]
            return self._decrease_winner+self._increase_loser
        else:
            return []

    def _get_tradable_assets(self, symbols):
        tradable_assets = []
        for symbol in symbols:
            security = self.securities[symbol]
            if security.price and security.is_tradable:
                tradable_assets.append(symbol)
        return tradable_assets[:15]

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

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

        stocks_invested = [x.key for x in self.portfolio]
        for i in stocks_invested:
            if i not in self._decrease_winner+self._increase_loser:
                self.liquidate(i) 
        
        short_weight = 0.25/len(self._increase_loser)
        for j in self._increase_loser:
            self.set_holdings(j, -short_weight)
            
        long_weight = 0.25/len(self._decrease_winner)
        for i in self._decrease_winner:
            self.set_holdings(i, long_weight)

 
class SymbolData:
    def __init__(self, algorithm, symbol):
        self.symbol = symbol
        self.window = RollingWindow[float](13)
        self.garr_ratio = None
        self.yearly_return = None