Back to Community
Machine Learned Half Day SPY Strategy

I have built this strategy to try to take advantage of mid-day bounce back of the SPY. It is not based on any research paper or otherwise. I just felt that it seems that often, if the SPY opens higher then the previous day close, it will tend to dip a bit at around noon and bounce back up by the end of the day. The opposite seems true as well. This is not a very exciting algo, but it took me about 30 minutes to code and here it is.

The algo records the SPY price at previous close, and for the first 3 hours of the day. These form the features as input to the ML. The class is derived from the performance of the SPY from midday to close. The SPY is held (or shorted) from 12:30 to close each day. Positions are closed at day end. So this is a low leverage algo.

Maybe this could be enhanced with multiple uncorrelated ETF/stocks.

Enjoy and post your improvements if any.

/Luc

Clone Algorithm
34
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import quantopian.algorithm as algo
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
import numpy as np
import pandas as pd

def initialize(context):
    set_commission(commission.PerShare(cost=0, min_trade_cost=0))
    set_slippage(slippage.FixedSlippage(spread=0))

    
    # Rebalance every day, 1 hour after market open.
    algo.schedule_function(
        open_spy,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=3, minutes=1),
        half_days=False
    )

    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(),
        half_days=False
    )
    
    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=1),
        half_days=False
    )
    
    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=2),
        half_days=False
    )
    
    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=3),
        half_days=False
    )
    
    algo.schedule_function(
        close_spy,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(hours=1, minutes=5),
        half_days=False
    )
    
    context.spy = sid(8554)
    
    context.X = []
    context.y = []
    
    context.daily_data = []
    context.y_d = 0
    
    
def capture_price(context, data):
    context.daily_data.append(data.current(context.spy, 'price'))    
    
    
def open_spy(context, data):
    if len(context.daily_data) != 5:
        return
    
    X_d = (np.array(context.daily_data)/context.daily_data[0])[1:]
    context.X.append(X_d)
    context.y.append(context.y_d)
    
    y_train = np.array(context.y)[1:]
    X_train = np.array(context.X)[:-1]
    X_live = np.array(context.X)[-1:]
    
    if X_train.shape[0] > 50:
        model = GaussianNB()
        # model = LogisticRegression(C=5.0)
        # model = SVC(C=100, class_weight='auto')
        model.fit(X_train, y_train)
        if model.predict(X_live)[0] == 1:
            order_target_percent(context.spy, 1.0)
            record(pred=1.0)
        else:
            order_target_percent(context.spy, -1.0)
            record(pred=-1.0)
            
        record(mean=np.mean(context.y))
        
        record(leverage=context.account.leverage)
            
    
def close_spy(context, data):
    c_price = data.current(context.spy, 'price')
    context.y_d = 1 if c_price - context.daily_data[-1] > 0 else -1
    context.daily_data = [c_price]
    
    order_target_percent(context.spy, 0.0)
There was a runtime error.
3 responses

Hi Luc,

Thanks for sharing an awesome ML algorithm - it certainly has captured a unique insight into an intraday trading pattern - that 2003~2019 straight returns line is a testament of consistent herd behaviour!

I am happy to concur with your observation as I also recognise a similar pattern from my recent intraday studies on Uber and others - albeit from a non-ML multi-factor context - the exuberant spike in momentum in the early hours and the midday idle are quite unmistakable!

Karl

You are welcome Karl. I will work on it some more this coming week and modify it to run on multiple stocks/ETF and see if by simple diversification the performance can increase.

/Luc

Here is a version with multiple assets traded on half-day. Same as with the SPY, but with a number of assets. The assets I chose were the Dow Jones components circa 2012 so as not to introduce too much survivor ship bias. It is not spectacular. Post your improvements if any. Thanks.

I run a RFECV feature selection step as I believe not all features are very important. Other classifiers can be tried. The logistic regression is much like a zero hidden layer neural network. Enjoy

Clone Algorithm
11
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import quantopian.algorithm as algo
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
from sklearn.feature_selection import RFECV
import numpy as np
import pandas as pd

LEVERAGE=4.0  # this is not relaly a leverage factor.   Since we hold stocks only from noon to close, we may want
# to leverage a bit.

def initialize(context):
    set_commission(commission.PerShare(cost=0, min_trade_cost=0))
    set_slippage(slippage.FixedSlippage(spread=0))

    
    # Rebalance every day, 1 hour after market open.
    algo.schedule_function(
        open_spy,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=3, minutes=1),
        half_days=False
    )

    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(),
        half_days=False
    )
    
    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=1),
        half_days=False
    )
    
    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=2),
        half_days=False
    )
    
    algo.schedule_function(
        capture_price,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=3),
        half_days=False
    )
    
    algo.schedule_function(
        close_spy,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(minutes=5),
        half_days=False
    )
    
    context.spy = sid(8554)
    
    context.X = dict()
    context.y = dict()
    
    context.daily_data = dict()
    context.y_d = dict()
    
    context.assets = [sid(3149), sid(35920), sid(4922), sid(8347), sid(3766), sid(5061), sid(698), sid(1267), sid(23112), sid(679), sid(1900), sid(3951), sid(4151), sid(4707), sid(20088), sid(25006), sid(3496), sid(2190), sid(5029), sid(5923), sid(5938), sid(21839), sid(8229), sid(7792), sid(7883)]
    
    for a in context.assets:
        context.X[a] = []
        context.y[a] = []
    
        context.daily_data[a] = []
        context.y_d[a] = 0
        
    set_benchmark(context.spy)
    
    
def capture_price(context, data):
    for a in context.assets:
        context.daily_data[a].append(data.current(a, 'price'))    
    
    
def open_spy(context, data):
    for a in context.assets:   
        if len(context.daily_data[a]) != 5:
            continue
    
        X_d = (np.array(context.daily_data[a])/context.daily_data[a][0])[1:]
        context.X[a].append(X_d)
        context.y[a].append(context.y_d[a])
    
        y_train = np.array(context.y[a])[1:]
        X_train = np.array(context.X[a])[:-1]
        X_live = np.array(context.X[a])[-1:]
    
        if X_train.shape[0] > 50:
            # model = GaussianNB()
            model = LogisticRegression(C=5.0)
            ############# feature reduction##
            selector = RFECV(model, 1, 2)
            selector.fit(X_train, y_train)            
            if selector.score(X_train, y_train) < 0.50:
                continue
            model = selector
            #################################
                     
            # model = SVC(kernel='rbf', C=10, gamma='auto', class_weight='auto')
            # model.fit(X_train, y_train)      
            # if model.score(X_train, y_train) < 0.50:
            #     continue
            
            if model.predict(X_live)[0] == 1:
                order_target_percent(a, LEVERAGE/len(context.assets))
            else:
                order_target_percent(a, -LEVERAGE/len(context.assets))
        
            
    
def close_spy(context, data):
    # record(leverage=context.account.leverage)
    for a in context.assets:
        c_price = data.current(a, 'price')
        context.y_d[a] = 1 if c_price - context.daily_data[a][-1] > 0 else -1
        context.daily_data[a] = [c_price]
    
        order_target_percent(a, 0.0)
There was a runtime error.