Back to Community
ML Learned Price History using Del Prado triple barrier criteria

HI All,

This is my algo that implement an ML classifier that uses prices history to train a classifier. The training classes are obtained from applying a triple barrier on the price movement n days later. i.e. +1 if price crosses the top barrier, -1 if the price crosses the lower barrier and 0 if the price crosses the backwall.

It trains on price histories of the QTradeable universe. The time series are normalized and bunched together.

I am posting it now because there is a lot of improvement that can be done to it and maybe the B&B (best and brightest) can help.

Enjoy,

Luc

Clone Algorithm
7
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 quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q3000US
import numpy as np
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
# from sklearn.neural_network import MLPClassifier
from sklearn.neural_network import BernoulliRBM
from sklearn.ensemble import GradientBoostingClassifier

FEATURE_LENGTH = 50
TRIPLE_BARRIER_LENGTH = 5
SAMPLE_NUMBER = 50
JUMP_IN_SAMPLING = 5
BARRIER_TOP_BOTTOM = 0.03 # 2% of gain/loss to trigger profit/loss taking.

def initialize(context):
    algo.schedule_function(
        rebalance,
        algo.date_rules.week_start(),
        algo.time_rules.market_open(minutes=15),
    )

    algo.schedule_function(
        daily_braketing_setup,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(minutes=20),
    )        
    
    algo.schedule_function(
        record_vars,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(),
    )

    algo.attach_pipeline(make_pipeline(), 'pipeline')
    
    context.warmup_counter = 0
    context.warmup_done = False
    
    context.longs = []
    context.shorts = []

    set_commission(commission.PerShare(cost=0, min_trade_cost=0))
    set_slippage(slippage.FixedSlippage(spread=0))


def make_pipeline():
    base_universe = QTradableStocksUS()

    pipe = Pipeline(
        screen=base_universe
    )
    return pipe


def before_trading_start(context, data):
    context.output = algo.pipeline_output('pipeline')
    context.security_list = context.output.index    
    
    # we fit the classifier model over n=15 warmup cycles otherwise Q times out.  X, y are nuilt on the first cycle.  Then
    # batches of 5000 samples are used to train the classifier.
    # y is claculated using Lopez del  Prado triple barrier approach.
    
    if context.warmup_counter > 15:
        context.warmup_done = True
        return

    context.warmup_counter += 1
    
    print "warmup counter:", context.warmup_counter
    
    if context.warmup_counter == 1:   
        context.model = GradientBoostingClassifier(warm_start=False)

        us_equity_pricing = dict() # this is not optimal or Q-centric, but done so I can reuse my local python code
        
        for t in context.security_list:
            us_equity_pricing[t] = data.history(t, 'close', 500, '1d')
      
        X = []
        y = []

        for t in us_equity_pricing:    
            close = np.array(us_equity_pricing[t].dropna())
    
            if close.shape[0] < FEATURE_LENGTH + TRIPLE_BARRIER_LENGTH:
                continue
       
            for offset in range(0, SAMPLE_NUMBER*JUMP_IN_SAMPLING, JUMP_IN_SAMPLING):     
                try:
                    x = close[offset: offset+FEATURE_LENGTH]
                    x = (x/x[0])[1:] # normalize and scrap first columns of data as it is always 1.0.
                    tb = close[offset+FEATURE_LENGTH: offset+FEATURE_LENGTH+TRIPLE_BARRIER_LENGTH]
                    tb = (tb/tb[0])[1:]

                    res = 0
                    for one_tb in tb:
                        if one_tb >= 1.0 + BARRIER_TOP_BOTTOM:
                            res = 1
                            break
                        elif one_tb <= 1.0 - BARRIER_TOP_BOTTOM:
                            res = -1
                            break

                    X.append(list(x))
                    y.append(res)
                except:
                    pass
        
        context.X = np.array(X)
        context.y = np.array(y)
        
    l = context.warmup_counter*5000
    
    X = context.X[l:l+5000]
    y = context.y[l:l+5000]
        
    print X.shape, y.shape
    print (y == 0).mean()
    
    context.model.fit(X, y)
    print context.model.score(context.X, context.y)

    
def rebalance(context, data):
    if not context.warmup_done:
        return
    
    prices = data.history(context.security_list, 'close', 50, '1d')

    # close all previous weeks stocks.
    for t in context.longs + context.shorts:
        order_target_percent(t, 0)
    
    context.longs = []
    context.shorts = []
    
    context.purchase_prices = dict()
    
    for t in context.security_list:
        x = np.array(prices[t])
        x = (x/x[0])[1:]
        
        try:
            prediction = context.model.predict(x)[0]
        except:
            prediction = 0
            
        if prediction == 1:
            context.longs.append(t)
        if prediction == -1:
            context.shorts.append(t)
    
    
    # there are a number of stocks within the universe that the model predict to go up, down or stay within the 3% range.
    # we select the first 100 of up and down class.  This is not very sofisticated.  To be improved.
    context.longs = context.longs[:100]
    context.shorts = context.shorts[:100]
            
    record(nb_l=len(context.longs), nb_s=len(context.shorts))
    
    for t in context.longs:
        context.purchase_prices[t] = data.current(t, 'price')
        order_target_percent(t, 1.0/len(context.longs))
    
    for t in context.shorts:
        context.purchase_prices[t] = data.current(t, 'price')                             
        order_target_percent(t, -1.0/len(context.longs))

    
def daily_braketing_setup(context, data):
    # This code is to create am order bracket around each positions.  For long positions, this would mean sell when it reaches
    # +3% of purchase price and stop loss at -3%.  This could be improved as well.  
    
    for t in context.portfolio.positions:
        if context.portfolio.positions[t].amount > 0:
            try:
                price = context.purchase_prices[t]
                order_target_percent(t, 0,  style=StopOrder(price * (1 - BARRIER_TOP_BOTTOM)))
                order_target_percent(t, 0,  style=LimitOrder(price * (1 + BARRIER_TOP_BOTTOM)))
            except:
                pass
            
        if context.portfolio.positions[t].amount < 0:
            try:
                price = context.purchase_prices[t]
                order_target_percent(t, 0,  style=StopOrder(price * (1 + BARRIER_TOP_BOTTOM)))
                order_target_percent(t, 0,  style=LimitOrder(price * (1 - BARRIER_TOP_BOTTOM)))
            except:
                pass
            
            
def record_vars(context, data):
    record(nb_l=len(context.longs), nb_s=len(context.shorts))
    record(lev=context.account.leverage*100)
There was a runtime error.