Overall Statistics
Total Trades
304
Average Win
0.32%
Average Loss
-0.29%
Compounding Annual Return
20.474%
Drawdown
6.600%
Expectancy
0.327
Net Profit
20.412%
Sharpe Ratio
1.289
Probabilistic Sharpe Ratio
58.127%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
1.08
Alpha
0.18
Beta
-0.064
Annual Standard Deviation
0.137
Annual Variance
0.019
Information Ratio
0.788
Tracking Error
0.169
Treynor Ratio
-2.746
Total Fees
$338.86
Estimated Strategy Capacity
$1100000.00
# V1
# Find a source with historical OEF(SP100) holdings
# Check SPY holdings monthly only invest in top X OEF holdings
# Notice do not repeat GOOGL & GOOG
# Rebalance every month to maintian equal weight
# V2
# Calculate MOMP of last x days for each OEF holdings 
# Only invest in top numOfHoldings with greatest momp 
# Test this on QQQ as well

import numpy as np
import pandas as pd
from io import StringIO
from datetime import timedelta

class OptimizedBuyAndHold(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2005, 1, 1)  # Set Start Date
        self.SetEndDate(2006, 1, 1)    # Set Start Date       
        self.SetCash(100000)           # Set Strategy Cash
        self.SetWarmUp(timedelta(60)) # Warm up technical indicators

        self.UniverseSettings.Resolution = Resolution.Daily

        self.MOMPlist = {}      # Dict of Momentum indicator keyed by Symbol
        self.lookback = 10      # Momentum indicator lookback period
        self.numOfCoarse = 200   # Number of symbols selected at Coarse Selection
        self.numOfLong = 15       # Number of symbols with open positions

        self.month = -1
        self.file = self.Download("https://www.dropbox.com/s/mjqxvu73vyxvbtc/SP500HistoricalHolding_Modified.csv?dl=1")
        self.rebalance = False

        self.AddUniverse(self.CoarseSelectionFunction)


    def CoarseSelectionFunction(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
        
        df = pd.read_csv(StringIO(self.file))
        month = format(self.month, '02d')
        time = str(self.Time.year) + month
        candidates = pd.DataFrame({'Ticker': df['Ticker'], time: df[time]}).dropna()
        candidates = candidates.sort_values(by=time, ascending=False)[:int(self.numOfCoarse)]
        candidates = candidates['Ticker']
        
        selected = []
        for candidate in candidates:
            for x in coarse:
                if x.Symbol.Value == candidate:
                    selected.append(x)

        return [x.Symbol for x in selected]



    def OnData(self, data):

        # Update the indicator
        for symbol, mom in self.MOMPlist.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.MOMPlist.items() if v.IsReady],
            key=lambda x: self.MOMPlist[x].Current.Value, reverse=True)
        selected = sorted_mom[:self.numOfLong]

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

        # Buy selected securities
        for symbol in selected:
            self.SetHoldings(symbol, 1/self.numOfLong)

        self.rebalance = False


    def OnSecuritiesChanged(self, changes):

        # Clean up data for removed securities and Liquidate
        for security in changes.RemovedSecurities:
            symbol = security.Symbol
            if self.MOMPlist.pop(symbol, None) is not None:
                self.Liquidate(symbol, 'Removed from universe')

        for security in changes.AddedSecurities:
            if security.Symbol not in self.MOMPlist:
                self.MOMPlist[security.Symbol] = Momentum(self.lookback)

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

        history = self.History(addedSymbols, 1 + self.lookback, Resolution.Daily)
        history = history.close.unstack(level=0)

        for symbol in addedSymbols:
            ticker = str(symbol)
            if ticker in history:
                for time, value in history[ticker].items():
                    item = IndicatorDataPoint(symbol, time, value)
                    self.MOMPlist[symbol].Update(item)