Overall Statistics
Total Orders
409
Average Win
5.14%
Average Loss
-2.60%
Compounding Annual Return
30.517%
Drawdown
42.000%
Expectancy
0.581
Start Equity
1000000
End Equity
17163691.33
Net Profit
1616.369%
Sharpe Ratio
0.802
Sortino Ratio
1.151
Probabilistic Sharpe Ratio
13.707%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
1.98
Alpha
0.248
Beta
-0.013
Annual Standard Deviation
0.308
Annual Variance
0.095
Information Ratio
0.525
Tracking Error
0.349
Treynor Ratio
-18.914
Total Fees
$64740.20
Estimated Strategy Capacity
$16000.00
Lowest Capacity Asset
EPIX W21WGWK0DLYD
Portfolio Turnover
0.53%
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/162


class MomentumInSmallPortfolio(QCAlgorithm):

    _blacklisted_assets = ['TOPT T0KDYN9C3IHX']  # /datasets/issue/15180

    def initialize(self):
        self.set_start_date(2008, 1, 1)   
        self.set_end_date(2018, 9, 1)         
        self.set_cash(1000000)            
        
        self.universe_settings.resolution = Resolution.DAILY
        self.add_universe(self._coarse_selection_function, self._fine_selection_function)
        self.add_equity("SPY", Resolution.DAILY)
        
        self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.after_market_open("SPY"), self._rebalance)
        
        # Count the number of months that have passed since the algorithm starts
        self._months = -1
        self._yearly_rebalance = True
        self._long = None
        self._short = None

    def _coarse_selection_function(self, coarse):
        if self._yearly_rebalance:
            # drop stocks which have no fundamental data or have low price
            self._filtered_coarse = [x.symbol for x in coarse if (x.has_fundamental_data) and str(x.symbol.id) not in self._blacklisted_assets]
            return self._filtered_coarse
        else: 
            return Universe.UNCHANGED

    def _fine_selection_function(self, fine):
        if self._yearly_rebalance:
            # Calculate the yearly return and market cap 
            top_market_cap = sorted(fine, key = lambda x: x.market_cap, reverse=True)[:int(len(self._filtered_coarse)*0.75)]
            
            has_return = []
            for i in top_market_cap:
                history = self.history([i.symbol], timedelta(days=365), Resolution.DAILY)
                if not history.empty:
                    close = history.loc[str(i.symbol)]['close']
                    i.returns = (close[0]-close[-1])/close[-1]
                    has_return.append(i)
            
            sorted_by_return = sorted(has_return, key = lambda x: x.returns)
            self._long = [i.symbol for i in sorted_by_return[-10:]]
            self._short = [i.symbol for i in sorted_by_return[:10]]

            return self._long+self._short
        else:
            return Universe.UNCHANGED

    def _rebalance(self):
        # yearly rebalance
        self._months += 1
        if self._months%12 == 0:
            self._yearly_rebalance = True

    def on_data(self, data):
        if not self._yearly_rebalance: 
            return 

        if self._long and self._short:
            stocks_invested = [x.key for x in self.portfolio if x.value.invested]
            # liquidate stocks not in the trading list 
            for i in stocks_invested:
                if i not in self._long+self._short:
                    self.liquidate(i)
            for i in self._short:
                self.set_holdings(i, -0.5/len(self._short))
            for i in self._long:
                self.set_holdings(i, 0.5/len(self._long))
            
            self._long = None
            self._short = None
            self._yearly_rebalance = False