Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Need help with a Pairs trading algo - bad at coding!

I have a code from an old trading platform that I need to convert to quantopian logic. This pits VTI:SPY as a pair trading system:

Here is the old code, sorry I'm really bad at algorithmic code and need help. This is a steady wealth accumulator. I have added notes to describe what I am trying to accomplish.

Thanks for anyone that is willing to help!

SYMLIST(VTI,SPY)

/*CALCULATE THE PAIR RATIO*/

SET{LEFT, IND(VTI,CLOSE)}
SET{RIGHT, IND(SPY,CLOSE)}
SET{RATIO, LEFT / RIGHT}
SET{PAIRSPREAD, LEFT - RIGHT}

/*DETERMINE THE 14 DAY MOVING AVERAGE FOR THE RATIO*/

SET{RATIOMA14, CMA(RATIO,14)}
SET{SPREADMA14, CMA(PAIRSPREAD,14)}

/*DETERMINE THE PERCENT ABOVE/BELOW THE MA(14) FOR THE RATIO*/

SET{RATIOPCT1, RATIO / RATIOMA14}
SET{RATIOPCT2, RATIOPCT1 - 1}
SET{RATIOPCT, RATIOPCT2 * 100}

/*DETERMINE THE 14 DAY ZSCORE FOR THE RATIO*/

SET{RATIOSTD14, CSTDDEV(RATIO,14)}
SET{RATIO2STD, 2 * RATIOSTD14}
SET{RATIOUPPERBB, RATIOMA14 + RATIO2STD}
SET{RATIOLOWERBB, RATIOMA14 - RATIO2STD}
SET{RATIODIFF14, RATIO - RATIOMA14}
SET{RATIOZSCORE, RATIODIFF14 / RATIOSTD14}

SET{SPREADSTD14, CSTDDEV(PAIRSPREAD,14)}
SET{SPREADDIFF14, PAIRSPREAD - SPREADMA14}
SET{SPREADZSCORE, SPREADDIFF14 / SPREADSTD14}

/*DETERMINE DIRECTION OF PAIR TRADE AND THE NUMBER OF SHARES TO BE BOUGHT OR SOLD*/

SET{BELOWLBB, COUNT(RATIOZSCORE BELOW -2,1)}
SET{ABOVEUBB, COUNT(RATIOZSCORE ABOVE 2,1)}

SET{LEFTSHARES1, 10000 / LEFT}
SET{LEFTSHARES, ROUND(LEFTSHARES1, 0)}
SET{LEFTLONG, BELOWLBB * LEFTSHARES}
SET{LEFTSHORT, ABOVEUBB * LEFTSHARES}

SET{RIGHTSHARES1, 10000 / RIGHT}
SET{RIGHTSHARES, ROUND(RIGHTSHARES1, 0)}
SET{RIGHTLONG, ABOVEUBB * RIGHTSHARES}
SET{RIGHTSHORT, BELOWLBB * RIGHTSHARES}

Hopefully this can be coded as it has a 100% winning record from 2011!

17 responses

What does the cma() function do? Does it return an average, or a rolling average (i.e., just a number, or a vector (series of numbers))?

Same question for the cstddev()?

Could you describe the workings of the function count()?

Thanks in advance,

Tim

CMA is custom moving average - so yes, in this case it is the custom moving average of the 14 day ratio
CSTDDEV is Custom Standard Deviation, which is again based on the 14 day ratio
Function count just counts the number of occurrences, it will either be a 1 or a 0. It is just looking to see if the ratio falls above the Upper band or below the Lower band.

Hopefully this helps!

So BELOWBB is the number of times during the past 14 days in which RATIOZSCORE was lower than -2?

Kinda. BELOWBB is looking at the actual RATIOZSCORE that was already calculated using the 14 day ratio (CMA). The count is only looking at the current day denoted by the ,1 at the end. So basically the code is looking at the RATIOZSCORE that was calculated above and looking for either below -2 or above 2 or else it stays in cash.

Also, I noticed there is no exit, but the exit is when the ratio crossed the middle ratio14 - if that makes any sense.

I can't post pictures, but I have a picture where you can see where the ratio exceeded -2 and 2 triggering buys - then retreated to the ratio14 where once it crosses you exit.

Do you know how to post pictures?

Here's something I wrote based on my current understanding of your algorithm.

Unfortunately, it does not behave quite as expected, I suppose, but perhaps it can serve as a starting point for improvements.

Nice! One other thing. If the ratio is above 2 you short both Vti and SPY and you would go long Vti & SPY when the ratio is below -2

Maybe that will clear up some of the disconnect or was that already built in?

Sorry, long day at work. You would go long one and short the other. I think the original code setup which to go long and which to short

Tim,

I think you intended to use mean instead of median for the ratio's moving average. Also, this will only exit trades if the zscore = +/- 0.1 (which wouldn't capture when the zscore crosses zero but doesn't land in this range).

I made these updates but it didn't make the algo any better looking.

Thanks, Martin,

Great solution for the zero-crossing problem. The median on the other hand, was used on purpose, because it is a more robust estimator than the mean when only a small number of samples is available (14). This detail does not influence the results, though, as you have pointed out. Something is missing ....
Perhaps we should test for cointegration of SPY an VTI first ...

I have simplified the zero-crossing condition a bit and have re-inserted the median.

One of the issues was that the leverage and exposure (net leverage) where not the way they should be because lots of orders went unfilled. The algo was trading at market close with a capital of 1M$. Now it is trading in the morning with 10000$ and the leverage and exposure graph looks better, although it still leaves to be desired.

I also believe that I got the signs of the long and short positions wrong initially.

With all these changes the algo's performance is now almost entirely flat ...

I simplified the trading logic a bit and the results are virtually unchanged.

An expert in pairs trading should have a look at this ...

Tim, sorry I have been travelling for business. I just checked the backtest vs my records and the buy/sell dates are a little off. I think it should be at the open after the day of the cross (ratio above or below -2/2). Same for the open after the cross above/below 0 of the ratio.

Also, it was shorting VTI and going long SPY - when it should've been the other way around.

Again, I am terrible with Quant coding and appreciate all your help as well as anyone else that is willing to convert this to quant code.

Alright, trying to revive this.

Is there a way to have the code check based on minutes instead of "30 minutes after open"?

I have found that making the settings more restrictive (2.5 SD as the entry), The algorithm needs to track every 1 MIN bar and enters/exits using the DAILY thresholds of ma(14) of the pair, 2.5 SD away from mean for entry, cross back over/under mean for exit. The nice thing about using the 14 day MA is that the thresholds are set based on the prior days close, and do not move throughout the day.

So we need the 1 minute data on the ratio difference and marry it up with the MA(14) of the ratio based on the prior close.

I am going to try and talk through the code again:

  1. We need to figure out the ratio of VTI/SPY
    Ratio VTI divided by SPY

  2. We need to determine the 14 day moving average of the ratio (from above)
    Ratio - Need the history of VTI divided by SPY for last 14 days

  3. Now we need to create our 2.5% standard deviation (upper and lower bands)
    We need to take the 14 day moving average of the ratio (from above) and multiply by 2.5
    Then take from the calculation right above and add/subtract to our 14 day moving average of the ratio get our upper/lower bands

Now this calculation needs to happen based on the close of the day prior and not update!
So we now have our upper and lower band based on 2.5 standard deviation from the 14 day ratio moving average.

We now need to calculate the ratio - based on each minute - and when it goes above or below the 2.5 standard deviation of the 14 day ratio moving average we need to make our long/short position

IF the ratio goes above the upper standard deviation we need to short VTI and go long SPY
IF the ratio goes below the lower standard deviation we need to go long VTI and go short SPY

Also, shares based on the old code is split between each position (I.E. 20,000 would be 10,000 into each position - short and long) - we need to ensure the starting amount is divided evenly between the long/short position.

The signal to exit would be when the ratio retreats back below or above the 2.5 standard deviation of the 14 day ratio moving average.

Does this simplify it a little more?

Is this something that is even possible in Quantopian. To have a 14 day moving average of the ratio +/- 2.5 standard deviations from that 14 day moving average and take the current minute data and compare for buy/sell signals?

Obviously this could introduce multiple buy/sells in the same day.

Yes, that is totally possible.

Anybody got anything? To summarize we need to take the vti/spy ratio 14 day moving average - take that and multiply by 2.5 then add/subtract to get your upper/lower bands. This needs to be based on the close of the prior day. Then we need the ratio vti/spy in minute mode for the current day to find when it goes above or below this ratio band and place orders detailed above. Then the exit is a retreat back below or above the ratio band.

This is a constant winner from 2011 to current and based on my manual trades made 2k in June alone! Let's get this coded to run in robinhood!