Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Protective Asset Allocation - asking for help again

Dear Quantopians,

I would like you all for a huge favor again. Building my first algorithm here and I've got a lot of coding-related questions.

Below is the link to the strategy that I was trying to code:
https://allocatesmartly.com/protective-asset-allocation/

I use this strategy in my paper trading and it is a simple and conservative strategy with a relatively smooth equity curve and low drawdowns. I tested it multiple times in Excel and saw tests by other people which were consistent with my tests.

In brief, the rules are the following:

  • On the last trading day of each month (t), calculate a momentum score
    (MOM) for each of 12 global asset classes, where MOMt = [(closet / SMA(t..t-12)) – 1]. Note that
    the SMA is calculated based on month-end values, so t..t-12
    represents the most recent 13 month-end values, including today.
  • To determine the % of the portfolio to allocate to the crash
    protection asset (CP) at the close, first calculate n where n equals
    the number of asset classes where MOM > 0. If n <= 6, then allocate
    the entire portfolio to the CP asset, otherwise CP % = [(12 – n) /
    6]. More on the CP asset in a moment.
  • If n >= 7 (or put another way, if CP % < 100%), select the 6 asset
    class with the highest MOM value, and allocate 1/6 of the remaining
    portfolio to each at the close.
  • Hold positions until the final trading day of the following month.
    Rebalance the entire portfolio monthly, regardless of whether there
    is a change in position.

The questions that I have are the following:

1) The results are way different from my previous tests - I was hoping to fix it through answering the questions below, but if you see other flaws, please let me know.

2) I am getting a lot of logs like "2016-04-13 16:00 WARN Your order for 1 shares of EWJ failed to fill by the end of day and was canceled." I am really confused why I am getting those, especially given that my handle_data function is scheduled for the beginning of the month only.

3) My leverage is well above 1, often closer to 2 - not sure why my positions get leveraged.

4) Should I be using the Pipeline API? Still trying to wrap my head around it and understand the while Pipeline concept. If you could point me in the right direction as to how the code should look like with the Pipeline, I would really appreciate.

5) I have introduced 3 functions (compute_weight_risk, and ranking) - all are doing similar things, I just need slightly different outputs. Just wondering if there is a way to optimize the code to do everything at once?

Many thanks in advance for all your help, very much appreciate it!
Alexey

21 responses

The function handle_data gets called every minute by the backtester, apparently scheduling it doesn't change that. Just rename it to anything else, like 'trade' or 'rebalance' and your perfomance will look a bit better already.
I'm looking into the rest of the code and will get back to you once I wrapped my head around it.

Tenor, this is so incredibly helpful! Thank very much for your help! Will give it a try first thing tomorrow morning.

I've coded the algorithm from scratch after the description you provided. As for your questions:

1) Does this result look more like your previous tests? If not, did you make some changes to the original rules?
2) That was the handle_data issue I previously mentioned.
3) Your version didn't close any open positions, so you just added weights to your existing positions.
4) Pipeline is mainly thought to speed up computations if you're trading a large univese, you don't really need it for this algo.
5) This version should do what the rules say in one function, but if you want to make some changes and can't figure out how to apply them - let me know.

@ Alexey

For such a strategy, it is better to do everything in one place.
I changed your momentum a bit and backtested on sectors ETF.
Is this what you are trying to achieve?

@Vladimir
Do you want to give the above rules a go? A longer backtest of my version did not really get the results shown on the website...
I would find it interesting if your version gets other results.
EDIT: Never mind, the website is a bit inconsistent about which assets they use and what backtests they show...

@Vladimir
Haha, I feel honored ;) Allthough I didn't come up with that, it's just how I interpreted this:

On the last trading day of each month (t), calculate a momentum score
(MOM) for each of 12 global asset classes, where MOMt = [(closet / SMA(t..t-12)) – 1]. Note that the SMA is calculated based on month-end values, so t..t-12
represents the most recent 13 month-end values, including today.

@ Tentor Testivis, @Vladimir - thank you so much both for your help! I could not be more grateful for your support!

The equity curves that you are showing are very similar to my Excel backtests. Let me digest the codes that you shared and I will let you know if I have any questions.

By the way - you may want to check out a slightly modified version of the same strategy, which uses leveraged ETFs:

https://indexswingtrader.blogspot.com/2018/12/exploring-smart-leverage-daa-on-steroids.html

Over a 19-year period the strategy demonstrated an average return of 13% with a Sharpe ratio of 1.5 and a max DD of 9% only - doesn't look too bad to me!

Many thanks again!
Alexey

PS: one of the possible reasons for the difference with the web-site is that they use a full return for momentum, i.e. including dividends. I would not figure out how to include dividends in the calculation.

As a matter, of update - I've tried the updated version of the algorithm using your suggestions.

The shape of the equity curve was very close to what I was expecting to be, but the returns % and the drawdowns were off.

I was expecting to see returns above 60% over a 10-year period (backtest - 40%).

The expected MAX drawdowns were ~4%, while the backtest showed 8% - that was even after I fixed the error ("rank 6").

For the drawdown I suspect the reason is that my Excel test only took into account the month-end points, ignoring the intra-month fluctuations. Still not 100% about the returns.

I tried zero commission / slippage, doesn't make a lot of difference.

In my excel test I took dividends into account, not sure if the backtest accounts for that.

@ Tentor Testivis, also, to you point - I realized I was indeed running a slightly modified version of the strategy. It was previously available on the web-site as well and uses a different version of momentum.

As for the list of securities, it does not have a major impact - I've modified the list slightly to include lower cost ETFs, but my Excel tests are almost dollar for dollar consistent with AllocateSmartly and TrendExplorer websites, so I assume I must be doing something slightly wrong here, which drives my profits down.

Sorry, another update, I played a little bit more with the commissions, and apparently they were driving the difference. After I eliminated the commissions completely in both tests, the results got very close. Thank you very much to both of you here - your help made a whole world of difference for me!

@Vladimir - thank you!
The strategy was not doing awesome in the last few years compared to the index, but on a risk-adjusted basis still looks promising.

Did you have a chance to see the TrendX post where they test the leveraged version of the strategy on a longer horizon?
Leveraged strategy

@ Alexey,

After reading the original paper, I came to this slightly adjusted version of Protective Asset Allocation (PAA)
Do the results match your spreadsheet.

Hi Vladimir,

My results are lower - from Jan-12 my test generated 79% without commissions. Although, I realized that I was testing slightly different strategies.

This is the first one:

https://indexswingtrader.blogspot.com/2018/07/announcing-defensive-asset-allocation.html
also described in this paper:
https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3212862
This one generated 79%

The second one was a bit of a hybrid. The rules were consistent with what I laid out at the very beginning of the thread, just the momentum calculation was different - I used the (m1*12 + m3*4 + m6*2 + m12)/4 formula.
This one generated 62% - my Excel results are consistent with the backtest attached below.

I will spend some more time with your code to see where the differences come from. If you'd like, I can also share my spreadsheet - just send me a message to adremov84[at]gmail.com.

You results look very reassuring - the equity curve looks solid to me.

On a separate note - this line attracted my attention:
"prices = data.history(Vigilant_Assets, 'close', M*12 + 1, '1d')" - is it a way to use monthly data without resampling? Also, as for the strategy itself, I don't quite understand why they use "a" in this formula "(N - n)/(N - a*N/4)" - please let me know if you can follow.

Thank you!

@ Alexey,

On a separate note - this line attracted my attention: "prices =
data.history(Vigilant_Assets, 'close', M*12 1, '1d')"
*

To define momentum you used code:

risk = data.history(context.risk_universe, 'close', 260, '1d')  
m1 = risk.pct_change(20)  
m3 = risk.pct_change(60)  
m6 = risk.pct_change(120)  
m12 = risk.pct_change(240)  
momentum_13612 = (m1*12   m3*4   m6*2   m12)  
momentum = momentum_13612.iloc[-1]

Usually on average
1 month = 21 trading days,
3 month = 63 trading days,
6 month = 126 trading days,
12 month = 252 trading days,

So to test momentum with length different than 20 trading days I created global variable M = 20 and defined momentum as

    prices = data.history(Vigilant_Assets, 'close', M*12 + 1, '1d')  
    m1 = prices.pct_change(M).iloc[-1]  
    m3 = prices.pct_change(M*3).iloc[-1]  
    m6 = prices.pct_change(M*6).iloc[-1]  
    m12 = prices.pct_change(M*12).iloc[-1]  
    momentum = (m1*12 + m3*4 + m6*2 + m12)/4

M*12 + 1 = 241 if M = 20
M*12 + 1 = 253 if M = 21
etc

BTW M = 20 gives the best results.

I don't quite understand why they use "a" in this formula "(N - n)/(N - a*N/4)

I recommend to read original Protective Asset Allocation (PAA) by Wouter J. Keller and Jan Willem Keuning

1. Compute the bond fraction BF depending on the number of good assets n: BF = (N-n)/(N-n1), with n1 = a*N/4, (2)

@ Alexey,

Here is my modification of your latest version. without "a".

@Vladimir - thank you very much!

"BTW M = 20 gives the best results."

Thanks for explaining M - it was well after midnight my time when I was reading your code, I did not notice a global variable - now everything makes perfect sense. Also, after a more thorough read of the SSRN paper factor "a" makes sense as well.

And yes, I noticed in my test as well that 20 gives a noticeably better result.

As for this line "weight_protect = min(1.0,(max(0, (N - n)/(N - N/4))))" - do we really need "Max(0"? I understand that n is always less than N, therefore (N - n)/(N - N/4) can never become negative?

UPD:
@Vladimir,
I've also aligned my Excels with your backtests - the results are almost identical. Your version is probably 2-3% more profitable over a 8-year period, but I consider it to be a rounding error. The difference can be explained by the fact that (1) I am using the actual month-ends to calculate the momentum, and (2) I use full returns to calculate the momentum (i. e. including dividends).

Interesting enough, as I tried to include some leveraged ETFs in the portfolio, the results didn't change too much. Perhaps that's because some of these ETFs did not exist at the beginning of the test.

Aleksei,

If you are looking for other non-correlated assets to put in your model, this website has a very nice calculator. I plugged in the ETFs from your algo just to see the relationships.

https://www.portfoliovisualizer.com/asset-correlations

@frank
Or numpy.corrcoef
or scipy.stats.pearsonr
or pandas.df.corr()
...

@ Alexey,

As for this line "weight_protect = min(1.0,(max(0, (N - n)/(N - N/4))))" - do we really need "Max(0"?
I understand that n is always less than N,
therefore (N - n)/(N - N/4) can never become negative?

n can be equal to N "n <= N"

If you will try

weight_protect = (N - n)/(N - N/4)  

you will see not frequent spikes in leverage.

I haven't found the reason yet.

weight_protect = min(1.0,(max(0, (N - n)/(N - N/4))))  

is a simple protection from overleveraging and be only long.

@ Vladimir - I see - "min(1.0..." makes perfect sense. Especially when n=0, you will get (12-0)/(12-12/4)=12/9, which is a leveraged position.
My question really related to "max(0,...", but I see where you are coming from - thank you for your response!