Back to Community
Setting Stop Loss in Pair Trading

Please help me in showing how to set the StopLoss Order if say the spread goes >2.56 stdev away from spread mean or <-2.56 in the following algorithm.

https://www.quantopian.com/algorithms/5669cd02221b3422940001b4

Thanks in advance.

15 responses

We can't see your algorithms at all you know. As for your question, it's basically impossible to place a stop-loss order on a custom spread, since there's an infinitely variety of ways that a spread can diverge to produce a z-score of a stop level. The best you can do is monitor the z-score every minute in handle_date and stop out by hand there. Another way that I am experimenting with is a transformation from z-score to desired position with humps around the entry and flat around the 0, and the stop levels...

Oh ,sorry in asking the question . My point is basically not z-score but if spread goes beyond some specific level in opposite , in that case , we can set up the stop loss ? may be the second point could be relevant to my query.

Well, the simplest is just to monitor the spread in handle_data and if it gets to your stop level, place orders to exit the positions?

Ok, I will try to do that and then get back to you accordingly.

I have tried to write this code but its not running,please see.

***def handle_data(context, data):
stop_level = spreads.mean() + 2.56*spreads.std()

try:  
 for stock_pairs() in data:  
if  new_spreads[i, :] > stop_level  
order(security, amount, style=StopOrder(stop_level))  

except Exception as e:  
    print(str(e))***  

I have no idea what this is doing, sorry. You'll need to debug it.

I have entered the position when the spread is at 1 stdev away from spread mean and want to set stop loss if the spread goes into the more than 2.56 stdev from the spread mean. I hope you got now.

When I full backtest , it says code has some problem.

Yeah, you just have to fix the problem, sorry. I know what you are trying to do, but I can't help you.

It would be much easier for us to help you if you put the relevant code into an algo and attach a backtest for us to clone. The code snippet you did give us will not run, so I'm pretty sure sure your problems are python related. Check out the debugger, that should help you figure out what's going on.

Hi David,
The entire code is here. Problem is when I do the backtest, it says
"Your algorithm couldn't be backtested because it has some code problems." I have just added few lines of codes i.e. def handle_data() part, in addition to the original one given in the Pair Trading Lecture on Quantopian. When I Debug at various Breakpoints in IDE, it still says the same code error.


import numpy as np  
import statsmodels.api as sm  
import pandas as pd  
from zipline.utils import tradingcalendar  
import pytz


def initialize(context):  
    # Quantopian backtester specific variables  
    set_slippage(slippage.FixedSlippage(spread=0))  
    set_commission(commission.PerTrade(cost=1))  
    set_symbol_lookup_date('2014-01-01')  
    context.stock_pairs = [(symbol('ABGB'), symbol('FSLR')),  
                           (symbol('CSUN'), symbol('ASTI'))]  
    # set_benchmark(context.y)  
    context.num_pairs = len(context.stock_pairs)  
    # strategy specific variables  
    context.lookback = 20 # used for regression  
    context.z_window = 20 # used for zscore calculation, must be <= lookback  
    context.spread = np.ndarray((context.num_pairs, 0))  
    # context.hedgeRatioTS = np.ndarray((context.num_pairs, 0))  
    context.inLong = [False] * context.num_pairs  
    context.inShort = [False] * context.num_pairs  
    # Only do work 30 minutes before close  
    schedule_function(func=check_pair_status, date_rule=date_rules.every_day(), time_rule=time_rules.market_close(minutes=30))  
# Will be called on every trade event for the securities you specify.  
    def handle_data(context, data):  
    short_cutoff_level = spreads.mean() + 2.56*spreads.std()  
    long_cutoff_level = spreads.mean() - 2.56*spreads.std()


if context.inShort[i] and zscore > short_cutoff_level:  
order(security, amount, style=StopOrder(short_cutoff_level))  

if context.inLong[i] and zscore < long_cutoff_level:  
order(security, amount, style=StopOrder(long_cutoff_level)) 


except Exception as e:  
    print(str(e))  


def check_pair_status(context, data):  
    if get_open_orders():  
        return  
    prices = history(35, '1d', 'price').iloc[-context.lookback::]  
    new_spreads = np.ndarray((context.num_pairs, 1))  
    for i in range(context.num_pairs):

        (stock_y, stock_x) = context.stock_pairs[i]

        Y = prices[stock_y]  
        X = prices[stock_x]

        try:  
            hedge = hedge_ratio(Y, X, add_const=True)  
        except ValueError as e:  
            log.debug(e)  
            return

        # context.hedgeRatioTS = np.append(context.hedgeRatioTS, hedge)  
        new_spreads[i, :] = Y[-1] - hedge * X[-1]

        if context.spread.shape[1] > context.z_window:  
            # Keep only the z-score lookback period  
            spreads = context.spread[i, -context.z_window:]

            zscore = (spreads[-1] - spreads.mean()) / spreads.std()

            if context.inShort[i] and zscore < 0.0:  
                order_target(stock_y, 0)  
                order_target(stock_x, 0)  
                context.inShort[i] = False  
                context.inLong[i] = False  
                record(X_pct=0, Y_pct=0)  
                return

            if context.inLong[i] and zscore > 0.0:  
                order_target(stock_y, 0)  
                order_target(stock_x, 0)  
                context.inShort[i] = False  
                context.inLong[i] = False  
                record(X_pct=0, Y_pct=0)  
                return

            if zscore < -1.0 and (not context.inLong[i]):  
                # Only trade if NOT already in a trade  
                y_target_shares = 1  
                X_target_shares = -hedge  
                context.inLong[i] = True  
                context.inShort[i] = False

                (y_target_pct, x_target_pct) = computeHoldingsPct( y_target_shares,X_target_shares, Y[-1], X[-1] )  
                order_target_percent( stock_y, y_target_pct * (1.0/context.num_pairs) / float(context.num_pairs) )  
                order_target_percent( stock_x, x_target_pct * (1.0/context.num_pairs) / float(context.num_pairs) )  
                record(Y_pct=y_target_pct, X_pct=x_target_pct)  
                return

            if zscore > 1.0 and (not context.inShort[i]):  
                # Only trade if NOT already in a trade  
                y_target_shares = -1  
                X_target_shares = hedge  
                context.inShort[i] = True  
                context.inLong[i] = False

                (y_target_pct, x_target_pct) = computeHoldingsPct( y_target_shares, X_target_shares, Y[-1], X[-1] )  
                order_target_percent( stock_y, y_target_pct * (1.0/context.num_pairs) / float(context.num_pairs) )  
                order_target_percent( stock_x, x_target_pct * (1.0/context.num_pairs) / float(context.num_pairs) )  
                record(Y_pct=y_target_pct, X_pct=x_target_pct)  
    context.spread = np.hstack([context.spread, new_spreads])

def hedge_ratio(Y, X, add_const=True):  
    if add_const:  
        X = sm.add_constant(X)  
        model = sm.OLS(Y, X).fit()  
        return model.params[1]  
    model = sm.OLS(Y, X).fit()  
    return model.params.values  
def computeHoldingsPct(yShares, xShares, yPrice, xPrice):  
    yDol = yShares * yPrice  
    xDol = xShares * xPrice  
    notionalDol =  abs(yDol) + abs(xDol)  
    y_target_pct = yDol / notionalDol  
    x_target_pct = xDol / notionalDol  
    return (y_target_pct, x_target_p  
```ct)

Hi quant 1. Looks like the code came from Delaney's 'E. Chan algorithm modified to trade multiple pairs' (nice choice) and you made some changes for the StopOrder experimentation. To help bring you up to speed here, in all programming, variables (like 'spreads' and 'zscore') have to be first initialized, inside every function (or "def") where they are used (unless initialized outside of those functions, globally). So when you copied some code from check_pair_status() to handle_data(), the initialization of those -- were also needed. Also the 'for' loop they were in, which initializes 'i'. And others.

Below are some additions that will at least make that run, otherwise I haven't put any attention into it (except for a couple of log line examples).

Just so you'll have something you can work with.

Edit: If that wasn't clear ...
MAINLY ALL THIS IS DOING IS FIXING ENOUGH ERRORS TO MAKE IT RUN.
Take it from there.

Meanwhile, since you are interested in StopOrder, look thru some of the Quantopian pages that mention it with a search like this:

"StopOrder" site:quantopian.com or https://www.google.com/search?q=%22StopOrder%22+site%3Aquantopian.com

... or if you'd like to try to make sure there is an attached backtest on each page that you can clone, add handle_data to that (for example), cutting the number of results by over 40% in this case:

"StopOrder" "handle_data" site:quantopian.com

Also on a google results page you can click the 'Search tools' button and then under 'Any time' can select 'Past year' for example, then can order by date with the most recent first. Or, 'Custom range' for specific dates -- works well.

Clone Algorithm
10
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
# Backtest ID: 566bd8195ef34a115a21bf41
There was a runtime error.

Ok garyha, let me follow that and see if I am able to do this time. Yes . when I "Build Algorithm". It also says similar things as Undefined entities....Trying to get it right. Thanks.

garyha ,Backtest taking into account the hedge ratio. have you noticed that with stop loss in place the performance has become poor ?

Clone Algorithm
2
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
# Backtest ID: 566c8356b23839116b7de727
There was a runtime error.

I took a look at this and it looks there are still some issues. One thing to remember (Simon pointed this out above) is that it is impossible to submit a stoploss for a pair trade. Since you are combining two stocks into a virtual security you need to base a stop on the spread, then submit market/limit orders to close each leg when the spread breaks your stop threshold.

I tweaked the algo a bit to submit market orders to close the pairs. I still think there are some issues with the ordering/stop logic though, it behaves odd sometimes, but I don't have to look into it.

I also have it trading a single pair, I would stick to one pair until you have that working the way you expect, then add more later.

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
# Backtest ID: 566c8e040b927c115e45b4e9
There was a runtime error.

Hi David,
Your backtest performance is better . My view from experience is that Stop Loss can be submitted for pair trade. Indeed, we put that on the spread and once it goes out of our level of tolerance , we need to cut both the stocks positions. As far as z-score is concerned, it is calculated from spread only. So,putting stop loss on z-score .
Or , what do you think,It could be that if P&L goes say for example <-$1000 for each trade, we need to exit from both the positions. Would you please try this and backtest.

Whats your view ?