Overall Statistics
Total Trades
120
Average Win
0.47%
Average Loss
-0.40%
Compounding Annual Return
10.778%
Drawdown
10.400%
Expectancy
0.672
Net Profit
15.600%
Sharpe Ratio
0.897
Probabilistic Sharpe Ratio
42.709%
Loss Rate
23%
Win Rate
77%
Profit-Loss Ratio
1.17
Alpha
-0.011
Beta
0.732
Annual Standard Deviation
0.086
Annual Variance
0.007
Information Ratio
-0.741
Tracking Error
0.059
Treynor Ratio
0.106
Total Fees
$167.19
Estimated Strategy Capacity
$50000000.00
Lowest Capacity Asset
PG R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/7
# The investment universe consists of global large cap stocks (or US large cap stocks). 
# At the end of the each month, the investor constructs equally weighted decile portfolios 
# by ranking the stocks on the past one year volatility of daily price. The investor 
# goes long stocks with the lowest volatility.
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp

class ShortTermReversalAlgorithm(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2017, 1, 1)  # Set Start Date
        self.SetEndDate(2018, 6, 1)    # Set Start Date       
        self.SetCash(100000)           # Set Strategy Cash
        self.lookback = 252
    
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.symbolDataDict = {}
        self.AddEquity("SPY", Resolution.Daily)
        self.Schedule.On(self.DateRules.MonthStart("SPY"),self.TimeRules.AfterMarketOpen("SPY"), self.rebalance)

    def CoarseSelectionFunction(self, coarse):
        # drop stocks which have no fundamental data or have too low prices
        selected = [x for x in coarse if (x.HasFundamentalData) and (float(x.Price) > 5)]
        # rank the stocks by dollar volume 
        filtered = sorted(selected, key=lambda x: x.DollarVolume, reverse=True) 
        return [ x.Symbol for x in filtered[:100]]

    def FineSelectionFunction(self, fine):
        # filter stocks with the top market cap
        top = sorted(fine, key = lambda x: x.EarningReports.BasicAverageShares.ThreeMonths * (x.EarningReports.BasicEPS.TwelveMonths*x.ValuationRatios.PERatio), reverse=True)
        return [x.Symbol for x in top[:50]]
        
    def rebalance(self):
        sorted_symbolData = sorted(self.symbolDataDict, key=lambda x: self.symbolDataDict[x].Volatility())
        # pick 5 stocks with the lowest volatility
        long_stocks = sorted_symbolData[:5]
        stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        # liquidate stocks not in the list 
        for i in stocks_invested:
            if i not in long_stocks:
                self.Liquidate(i)
        # long stocks with the lowest volatility by equal weighting
        for i in long_stocks:
            self.SetHoldings(i, 1/5)
            

    def OnData(self, data):
        for symbol, symbolData in self.symbolDataDict.items():
            if data.Bars.ContainsKey(symbol):
                symbolData.roc.Update(self.Time, self.Securities[symbol].Close)
        
    def OnSecuritiesChanged(self, changes):
        # clean up data for removed securities
        for removed in changes.RemovedSecurities:
            symbolData = self.symbolDataDict.pop(removed.Symbol, None)
            if symbolData:
                symbolData.Dispose()

        # warm up the indicator with history price for newly added securities
        addedSymbols = [ x.Symbol for x in changes.AddedSecurities if x.Symbol.Value != "SPY"]
        history = self.History(addedSymbols, self.lookback+1, Resolution.Daily)

        for symbol in addedSymbols:
            if symbol not in self.symbolDataDict.keys():
                symbolData = SymbolData(symbol, self.lookback)
                self.symbolDataDict[symbol] = symbolData
                if str(symbol) in history.index:             
                    symbolData.WarmUpIndicator(history.loc[str(symbol)])


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    
    def __init__(self, symbol, lookback):
        self.symbol = symbol
        self.roc = RateOfChange(1)
        self.roc.Updated += self.OnUpdate
        self.roc_window = RollingWindow[IndicatorDataPoint](lookback)

    def OnUpdate(self, sender, updated):
        self.roc_window.Add(updated)

    def WarmUpIndicator(self, history):
        # warm up the RateOfChange indicator with the history request
        for tuple in history.itertuples():
            self.roc.Update(tuple.Index, float(tuple.close))
            
    def Volatility(self):
        data = [float(x.Value) for x in self.roc_window]
        return np.std(data)

    def Dispose(self):
        self.roc.Updated -= self.OnUpdate
        self.roc_window.Reset()