Back to Community
Is it possible to calculate the ratio of two securities ?

Hi ,

I'm trying to build a system using the ratio between SPY and TLT. I'm using (sid(8554) / sid(23921)), but i get the following error :

Runtime exception: TypeError: unsupported operand type(s) for /: 'Security' and 'Security'

Can the platform accept such calculations ?

Lionel

10 responses

Hi Lionel,

The issue here is that sid(x) returns a security object. If you want to calculate the ratio of the security prices you'd want to do something like:

data[sid(x)].price / data[sid(y)].price  

This will give you the latest price for each security. If you want historical prices you'll need to use history(), which is documented on our help page. Let me know if you have more questions.

Hope this helps,
Delaney

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Hi Delaney ,

Thank you for the quick response. I will try this .

Cheers

Lionel

Hi Delaney ,

One last question. To pull the moving average of that ratio , what should I write ? i wrote the following but getting a **KeyError:1.0"

ratio = data[sid(x)].price / data[sid(y)].price

ratioMA = data[ratio].mavg(20)

thanks

Sure thing. Our mavg functions work for things already in data (securities), but in this case I think you'll need to do it a little differently. The easiest way seems to be using history like this

prices = history(bar_count=20, frequency='1d', field='price')  
ratioMA = (prices[sid(x)] / prices[sid(y)]).mean()  

Calling history like this will give you a data structure containing the last 20 bars of price data for each security at a daily frequency. You can then index into prices like you did into data to get the 20 bars for that security. Dividing the two and taking the mean will give you the average for the last 20 days. This will be a moving average in your algorithm as the last 20 bars will be updated each time handle_data is called. Is this what you wanted? Another common use case would be computing the moving average for each of the last n days, which would be slightly different.

Hi Delaney,

Thank you for your help. I implemented your code the also, but the result are wrong. Here's the logic of the strategy :

  • RISK ON : SPY , RISK : OFF : TLT
  • calculate the SPY/TLT ratio : Ratio
  • calculate the 20 MA of SPY/TLT ratio : ratio MA
  • if Ratio > ratio MA, RISK ON, buy SPY
  • elif Ratio < ratio MA , RISK OFF , buy TLT

Simple enough , but the results are a bit out of whack .

Clone Algorithm
2
Loading...
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 talib
import math


def initialize(context):

# Securities 

    context.stocks = [
                          sid(8554) , # Risk ON asset SPY - SP 500
                          sid(23921) , # Risk OFF asset TLT - 20 yrs TSRY
                      ]                   

  
def handle_data(context, data):
           
    for stock in context.stocks:
        
        prices = history(bar_count=100, frequency='1d', field='price')
        ratio =  data[sid(8554)].price / data[sid(23921)].price 
          
         
        ratioMA= (prices[sid(8554)] / prices[sid(23921)]).mean() 
        
    
             
#RISK ON
        
    if ratio > ratioMA:
        order(sid(8554),100)
            
#RISK OFF
        
    elif ratio < ratioMA: 
        order(sid(23921),100)
            
            
            
            
            
                 
There was a runtime error.

In this case the algorithm is using a tremendous amount of leverage that would be unrealistic in the real world. It keeps ordering more and more securities, and never sells any. As a result the initial cash amount is exhausted and the algorithm goes into negative cash (borrows money) to make the following orders. I've recorded the cash in this backtest and it goes very negative almost immediately. Being able to borrow infinite amounts of money with no interest will certainly let you get the returns you see in your backtest.

In short your algorithm needs to keep track of the current leverage and not let it go above a certain amount. In the Quantopian Open contest our maximum is 3, but for personal trading you might want to restrict it to be lower or higher depending on your agreement with your broker. This can be accomplished by checking context.account.leverage before placing an order, and liquidating positions at some point in the algorithm.

Clone Algorithm
Loading...
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 talib
import math


def initialize(context):

# Securities 

    context.stocks = [
                          sid(8554) , # Risk ON asset SPY - SP 500
                          sid(23921) , # Risk OFF asset TLT - 20 yrs TSRY
                      ]                   

  
def handle_data(context, data):
           
    record('cash', context.portfolio.cash)
    
    for stock in context.stocks:
        
        prices = history(bar_count=100, frequency='1d', field='price')
        ratio =  data[sid(8554)].price / data[sid(23921)].price 
          
         
        ratioMA= (prices[sid(8554)] / prices[sid(23921)]).mean() 
        
    
             
#RISK ON
        
    if ratio > ratioMA:
        order(sid(8554),100)
            
#RISK OFF
        
    elif ratio < ratioMA: 
        order(sid(23921),100)
            
            
            
            
            
                 
There was a runtime error.

Another approach is to use order_target_percent(). This will adjust your portfolio to contain a certain percentage of each security. I'm attaching a backtest of the same algorithm modified to use this approach. As you can see it maintains a low leverage and does not borrow much cash.

Clone Algorithm
Loading...
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 talib
import math


def initialize(context):

# Securities 

    context.stocks = [
                          sid(8554) , # Risk ON asset SPY - SP 500
                          sid(23921) , # Risk OFF asset TLT - 20 yrs TSRY
                      ]
    
    set_commission(commission.PerTrade(cost=0))

  
def handle_data(context, data):
           
    record('cash', context.portfolio.cash, 'leverage', context.account.leverage,
          'SPY', data[sid(8554)].price, 'TLT', data[sid(23921)].price)
        
    prices = history(bar_count=100, frequency='1d', field='price')
    ratio =  data[sid(8554)].price / data[sid(23921)].price 


    ratioMA= (prices[sid(8554)] / prices[sid(23921)]).mean() 
        
    
             
    #RISK ON
    if ratio > ratioMA:
        order_target_percent(sid(8554), 1) 
        order_target_percent(sid(23921), 0)
    #RISK OFF    
    elif ratio < ratioMA: 
        order_target_percent(sid(8554), 0)         
        order_target_percent(sid(23921), 1)
            
            
            
            
            
                 
There was a runtime error.

I had to wonder about the two obvious paradigms portrayed in the above P&L curve. What could be the cause?
It appears that there were two distinctive phases between treasuries and the S&P, one less negatively correlated, and one more. What's the next phase going to bring?

In the chart the lower green line is the correlation (weekly bars, 52 periods). The red line is the correlation of the period returns (close() - close(-5)).

SPY vs TLT correlation chart

Delaney ,

Thank you so much for your help and latest code . I'm going to review it , and try to make it work. Will make sure to post the results here.

Market Tech , you raised a very good point. Based on Delany code, it seems that the strategy became more profitable when it when the inverse correlation was higher. Perhaps adding a rule such as * if correlation(SPY/TLT) < - X %* , would prevent the system to invest during less negatively correlated periods.

cheers

Lionel

Happy to help, although I just realized I accidentally left the following line in initialize(). I was just using it to test the ordering methods and you'll probably want to remove it so that your algorithm's performance reflects the cost of each trade.

set_commission(commission.PerTrade(cost=0)))