Overall Statistics
Total Orders
757
Average Win
0.86%
Average Loss
-1.08%
Compounding Annual Return
15.758%
Drawdown
27.600%
Expectancy
0.327
Start Equity
100000
End Equity
432546.45
Net Profit
332.546%
Sharpe Ratio
0.638
Sortino Ratio
0.7
Probabilistic Sharpe Ratio
10.097%
Loss Rate
26%
Win Rate
74%
Profit-Loss Ratio
0.79
Alpha
0.002
Beta
1.164
Annual Standard Deviation
0.177
Annual Variance
0.031
Information Ratio
0.165
Tracking Error
0.105
Treynor Ratio
0.097
Total Fees
$3051.03
Estimated Strategy Capacity
$86000000.00
Lowest Capacity Asset
AMT RBASL7V8PIZP
Portfolio Turnover
2.50%
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/14


class MomentumEffectAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2009, 7, 1)  # Set Start Date
        self.set_end_date(2019, 7, 1)    # Set Start Date       
        self.set_cash(100000)           # Set Strategy Cash

        self.universe_settings.resolution = Resolution.DAILY

        self._momp = {}          # Dict of Momentum indicator keyed by Symbol
        self._lookback = 252     # Momentum indicator lookback period
        self._num_coarse = 100   # Number of symbols selected at Coarse Selection
        self._num_fine = 50      # Number of symbols selected at Fine Selection
        self._num_long = 5       # Number of symbols with open positions

        self._month = -1
        self._rebalance = False

        self.add_universe(self._coarse_selection_function, self._fine_selection_function)

    def _coarse_selection_function(self, coarse):
        '''Drop securities which have no fundamental data or have too low prices.
        Select those with highest by dollar volume'''
        if self._month == self.time.month:
            return Universe.UNCHANGED

        self._rebalance = True
        self._month = self.time.month

        selected = sorted([x for x in coarse if x.has_fundamental_data and x.price > 5],
            key=lambda x: x.dollar_volume, reverse=True)

        return [x.symbol for x in selected[:self._num_coarse]]

    def _fine_selection_function(self, fine):
        '''Select security with highest market cap'''
        selected = sorted(fine, key=lambda f: f.market_cap, reverse=True)
        return [x.symbol for x in selected[:self._num_fine]]

    def on_data(self, data):
        # Update the indicator
        for symbol, mom in self._momp.items():
            mom.update(self.time, self.securities[symbol].close)

        if not self._rebalance:
            return

        # Selects the securities with highest momentum
        sorted_mom = sorted([k for k,v in self._momp.items() if v.is_ready],
            key=lambda x: self._momp[x].current.value, reverse=True)
        selected = sorted_mom[:self._num_long]

        # Liquidate securities that are not in the list
        for symbol, mom in self._momp.items():
            if symbol not in selected:
                self.liquidate(symbol, 'Not selected')

        # Buy selected securities
        for symbol in selected:
            self.set_holdings(symbol, 1/self._num_long)

        self._rebalance = False

    def on_securities_changed(self, changes):
        # Clean up data for removed securities and Liquidate
        for security in changes.removed_securities:
            symbol = security.symbol
            if self._momp.pop(symbol, None) is not None:
                self.liquidate(symbol, 'Removed from universe')

        for security in changes.added_securities:
            if security.symbol not in self._momp:
                self._momp[security.symbol] = MomentumPercent(self._lookback)

        # Warm up the indicator with history price if it is not ready
        added_symbols = [k for k,v in self._momp.items() if not v.is_ready]

        history = self.history(added_symbols, 1 + self._lookback, Resolution.DAILY)
        history = history.close.unstack(level=0)

        for symbol in added_symbols:
            ticker = symbol.id.to_string()
            if ticker in history:
                for time, value in history[ticker].dropna().items():
                    item = IndicatorDataPoint(symbol, time.date(), value)
                    self._momp[symbol].update(item)