Back to Community
Stock-Bond Balance

The latest year Quantopian staff pushing crowd to create Long-Short portfolios to neutralize beta.
But there are many other ways to reach low beta. One of them Stock-Bond Balance.
I am happy that I am not the only in this community who prefer Balanced portfolios.
This algo was inspired by Grant Kiehne post.

Happy New Year to All!!!

Clone Algorithm
104
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: 5685dda01c65ae117b3bbd68
There was a runtime error.
20 responses

Awesome! I was literally just looking for this! Are there any other bonds (such as different maturity lengths) available?

Alexander
You may find them here.

Here's the algo run over the past year. It takes a pretty big hit right at the end, resulting in a max drawdown of 10% over the year.

By the way, my little doodle was inspired by Market Tech's post on https://www.quantopian.com/posts/slope-calculation, since we are crediting sources.

Clone Algorithm
1
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: 568678519abdaa117152162e
There was a runtime error.

@Alexander- Here are some bond ETFs.

  • iShares: AGG TLT TLH SHY SHV IEF IEI HYG LQD
  • SPDR: JNK
  • Vanguard: BND VGLT VGSH VCLT VCSH VMBS EDV

See also

@Andre

Awesome! This helps a lot.

Cheers!

@Alexander- Try this algorithm from half a year ago. Some ETFs produce runtime errors Quantopian doesnt care to explain - probably because they were created after the backtest start date.

Clone Algorithm
1
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: 559197e07c026d1248d37cb4
There was a runtime error.

Migrated to Q2 version out of sample results:
From 2015-12-30 to 2016-07-01

Total Returns
13.2%
Benchmark Returns
2.2%
Alpha
0.24
Beta
0.05
Sharpe
3.27
Sortino
4.94
Information Ratio
2.88
Volatility
0.07
Max Drawdown
3.1%

Clone Algorithm
104
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: 577aad4cd28c210f82817d1d
There was a runtime error.

Any Idea on how to apply this with results from pipeline? Cant figure out how to implement so far I have...

def before_trading_start(context, data):
context.data = pipeline_output('my_pipeline').dropna()

context.data_sort = context.data.sort(['eps_growth'], ascending=False).iloc[:200]  


context.final_longs_sort = context.data_sort.sort(['dollar_volume'], ascending=False).iloc[:100]  

context.long_final = context.final_longs_sort  

context.security_list = context.long_final.index  

context.hedge = symbol('TLT')  


log.info(context.long_final)  

def rebalance(context, data):

stock = context.security_list  
bond = context.hedge  
period = 70  
target_lev = 1.00  
pw = 0.5

price_hedge = data.history(context.hedge, 'price', period+1, '1d')[0:-1]  
ret_sum1 = price_hedge.pct_change().sum()  

prices = data.history(context.long_final.index, 'price', period+1, '1d')[0:-1]  
ret_sum2 = prices.pct_change().sum()  


r_diff = ret_sum2[stock] - ret_sum1[bond]  
#record(r_diff = r_diff)  
if r_diff>0.0:  
    adpt = (1+r_diff)**pw-1.0  
elif r_diff<0.0:  
    adpt =-((1+abs(r_diff))**pw-1.0)  
else:  
    adpt =0.0  

wt_stock = target_lev*(0.50+adpt)  
wt_bond = target_lev*(0.50-adpt)

order_target_percent(stock, wt_stock)  
order_target_percent(bond, wt_bond)  

record(wt_stock = wt_stock, wt_bond = wt_bond)

record(Leverage = context.account.leverage)  

This is the error I'm getting when I try to implement in my algo. Im new to quantopian and python so not sure what I'm doing wrong here. What should I do?

TypeError: 'float' object has no attribute '__getitem__'
... USER ALGORITHM:122, in rebalance
r_diff = ret_sum2[stock] - ret_sum1[bond]

I think your ret_sum1 is a scalar of type float, and so you can't index it with [bond]. Remove this part.

Also, you probably want the sum of all stock returns, which would be ret_sum2[stock].sum(). Does this work?

Add a line to print r_diff. It should be a float scalar, or you'll get errors in the if statement that follows.

Awesome Thanks André! I will give it a go . Should I remove the whole ret_sum1 or just the [bond] part?

I think that worked André. However, now I get UnsupportedOrderParameters: Passing non-Asset argument to 'order()' is not supported. Use 'sid()' or 'symbol()' methods to look up an Asset.
There was a runtime error on line 135.

This is how its looking now

stock = context.security_list
bond = context.hedge
period = 70
target_lev = 1.00
pw = 0.5

price_hedge = data.history(context.hedge, 'price', period+1, '1d')[0:-1]  
ret_sum1 = price_hedge.pct_change().sum()  

prices = data.history(context.long_final.index, 'price', period+1, '1d')[0:-1]  
ret_sum2 = prices.pct_change().sum()  


r_diff = ret_sum2[stock].sum() - ret_sum1  
if r_diff>0.0:  
    adpt = (1+r_diff)**pw-1.0  
elif r_diff<0.0:  
    adpt =-((1+abs(r_diff))**pw-1.0)  
else:  
    adpt =0.0  

wt_stock = target_lev*(0.50+adpt)  
wt_bond = target_lev*(0.50-adpt)

order_target_percent(stock, wt_stock)  
order_target_percent(bond, wt_bond)  

record(wt_stock = wt_stock, wt_bond = wt_bond)

record(Leverage = context.account.leverage)  

That's because your stock is not a single Asset like your bond, but a list. You have to write a loop over stock, which would order some portion of wt_stock, eg. wt_stock/len(stock), for each individual security.

Hi André check it out. Im posting the backtest with the suggested changes. Theres a couple of things Im having trouble with, for some reason the leverage is really high and then I get a KeyError: 'the label [2016-05-12 00:00:00+00:00] is not in the [index]'
There was a runtime error on line 83.
Before merging my algo with this one, I was not getting any leverage and had no problems. Im wondering if I'm doing something wrong in compute weights and the rebalance. Sorry for the messy code, I'm still trying to figure things out as a newb!

Clone Algorithm
1
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: 577d32e7bccc8710b3554129
There was a runtime error.

You should check your return sum computations. You want ret_sum2 in line 128 to contain a pandas.Series of sums of daily returns for each stock. But it's possible you're instead creating a Series of sums of returns across stocks for each day. Either way, it shouldn't matter, because you're only using the gross sum across stocks and days. Remove [stock] in line 131.

Also, understand clearly how much of each stock you want to order. If wt_stock is the total percentage of all the stocks in your target portfolio, you wouldn't want this much for each stock, would you? Unless you have only one stock, you'll exceed your target leverage. Reread my previous message.

ahhhh ok. Sorry I got crazy confused there for a sec. This makes sense. Let me give it a go. For the wt_stock issue, would something like this make sense? wt_stock = (target_lev*(0.50+adpt)) / (len(context.long_final))

Yes. Or call it wt_each_stock.

Or insert

    wt_each_stock = wt_stock / len(context.long_final)  

after line 141 and change wt_stock to wt_each_stock in line 148, but not line 156.

Hi André thanks for your help again! Things are working better but still not 100%. Im attaching the latest backtest I ran last night. I think the problem might be that when rebalancing, it is both selling and buying the hedge at the same time. There are times when the stock level goes to 1.14 and the bond level goes to -0.14. Check it out see what you think. Im sure that it is a minor tweak at this point. Again, thanks for all your help!

Clone Algorithm
17
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: 577f33d52435fb0f8a20b256
There was a runtime error.

HI André hope you're doing well. Do you think you can take a look at this to see what the issue could be? I know its something with either the ordering process or weighting but just can't figure it out. I have made several changes but still can't figure it out.