Anyone know how to build these in P123

See:

The hard part is dynamically changing the allocation weights (% risky and % risk-free) based on the FCount of the underlying risky assets above trend and Fcount of underlying bonds about trend.

Can kind of fake it with separate rules for each ETF – but wondering if anyone has more elegant solutions that get close to these core systems.

I’ve run into this several times in past, and sort of solved it on a case by case basis – but wondering if I’m missing some things in P123 (concepts, ways to use tools) – that may make building these easier / better?

Many of these tactical allocations systems have held up somewhat well since publication (like first one here – over past 4 years, still working fairly well).


1 Like

I am wary of anything Wouter Keller publishes.

Several years ago I tried to reproduce one of his papers (called FAA I believe). I got pretty close, but not quite identical, even though I had the exact same data. So I decided to contact him. He explained (in a face to face meeting) that there were a few “tricks” that he had not written about in the paper. No huge difference, but still…

That same asset allocation algorithm did quite poorly out of sample. Later papers were mostly variations on a similar theme. In my view, he’s just data mining several assets that he always uses.

I did not use P123 to reproduce this algorithm. I downloaded the data and wrote a Python program to do the backtesting.

You can test some of his strategies here: www.quantconnect.com

2 Likes

This isn’t a complete solution but gives you one idea how to make something similar. I don’t use IEF as the crash protection asset. Just go to cash. When you see zero holdings representing cash, just substitute IEF instead on your own. I made this simple screen public. You’ll likely want to build off it.

The idea I used was to create a universe of the 12 ETFs.
Then in the screener I used this rule: fcount(“close(0)>sma(200,0)-1”)>=7
If the number of ETFs passing this momentum filter are 7 or more, it can go long.
Then limit the number of ETFs in the screener to 6.
Use quick rank to add another momentum filter such as close(0)/sma(200,0) so that when it invests it will select the top 6 ETFs.

Again, this isn’t 100% what the paper did but I wanted to show one quick way you can use the logic in a screener to make your own of something similar.

https://www.portfolio123.com/app/screen/summary/280590?mt=9

1 Like

I am trying to figure out if it is actually possible to have Kelly’s entire strategy on a screen?

This has three major steps:

    1. Screen in the Canary universe with “(12 * (close(0) / close(21) - 1)) + (4 * (close(0) / close(63) - 1)) + (2 * (close(0) / close(214)- 1)) + (close(0)/ close(252)- 1)”
    1. In the offensive universe, pick the best ETF based on “(close(0) / close(252) )”
    1. or pick the top three from the defensive universe and repeat at the end of the month based on"

Here is a edited version of the strategy on the page,
https://allocatesmartly.com/bold-asset-allocation/:

  1. Start with the canary universe: Canary – Aggressive and Balanced: S&P 500 (represented by SPY), emerging market equities (EEM), developed Intl equities (EFA) and US aggregate bonds (AGG)
    Calculate the “13612W” momentum of each asset. This is a multi-timeframe measure of momentum, calculated as follows:
    (12 * (p0 / p1 – 1)) + (4 * (p0 / p3 – 1)) + (2 * (p0 / p6 – 1)) + (p0 / p12 – 1)

  2. (12 * (close(0) / close(21) - 1)) + (4 * (close(0) / close(63) - 1)) + (2 * (close(0) / close(214)- 1)) + (close(0)/ close(252)- 1)
    Where p0 = the price at today’s close, p1 = the price at the close of the previous month, etc.

  3. If all canary assets have positive momentum, select from the offensive universe, otherwise select from the defensive universe.

1. Offensive – Aggressive: Nasdaq 100 (QQQ), emerging market equities (EEM), developed intl equities (EFA) and US aggregate bonds (AGG)
** 2. Defensive – Aggressive and Balanced: TIPS (TIP), commodities (DBC), US Treasury bills (BIL), intermediate-term US Treasuries (IEF), long-term US Treasuries (TLT), US corporate bonds (LQD) and US aggregate bonds (AGG)**

  1. Select from within the appropriate universe (offensive or defensive) based on a slower relative momentum measurement. Calculate relative momentum as follows:
    p0 / average(p0…p12)

  2. (close(0) / close(252) - 1)
    I.e. today’s price divided by the average of the most recent 13 month-end prices.

  3. If selecting from the offensive universe, select 1 asset (aggressive version).

  4. If selecting from the defensive universe, select the 3 assets with the highest relative momentum. If the relative momentum of the asset is less than that of US T-Bills (represented by ETF: BIL), instead place that portion of the portfolio in cash.
    Here the strategy is using both relative and absolute momentum (aka “dual momentum”). More on this in a moment.

  5. Equally-weight to all assets selected at the close. Rebalance the portfolio even if there isn’t a change in signal. Hold all positions until the end of the following month.

Here are the paper: https://deliverypdf.ssrn.com/delivery.php?ID=406105097031087071029089086070100030017003038044067033122103101102092031124087022007007019055119006003021107114006093120108006030087011040086065117100117121092120107018022032074116120116024120068093125112068120001098089093001005098109068118108084083069&EXT=pdf&INDEX=TRUE

As I understand it, and as shown above, it is possible to use the Canary filter on the canary universe with "fcount(12 * (close(0) / close(21) - 1)) + (4 * (close(0) / close(63) - 1)) + (2 * (close(0) / close(214)- 1)) + (close(0)/ close(252)- 1) >=4 ", it is also possible to use the Quick Rank Formula “(close(0) / close(252))” to pick from the Offensive universe , but is it possible to have a third criterion for selecting 3 ETFs in the defensive universe?

On page 4, Kelly describes the selection of defensive ETFs as follows:

In addition, we will choose the Top3 defensive assets (instead of the Top1 for PAA) based on
the same relative momentum, SMA(12), as for the offensive universe in PAA above. In
addition, we will add absolute momentum here, such that defensive assets in the Top3 with
bad momentum (less than BIL) will be replaced by BIL. Notice that, like for PAA, we don’t use
absolute momentum for the Top6 selection of the Offensive universe, only relative
momentum.

1 Like

Does anyone know?

Is this too complicated to use in p123 because it uses three different weightings:

  1. one for the canary universe, 2. one for the ranking of the offensive universe, and 3. one for the weighting of the defensive universe (the defensive one also chooses - unlike the offensive one - several ETFs)?

I’m trying to implement this strategy too, and I’m getting hung up on the mix of asset weights for protective vs risky assets just as you are. The canary assets are not a problem as those aren’t held. The cleanest way to implement this it seems would be two books of ETF strategies, one strategy of protective assets of size 1, and one strategy of risky assets of size 6, and have the book weights vary at 0%, 50%, or 100% depending on the number of canary assets with negative momentum.

But book weights can only be static at the moment which is very limiting.

I’ll keep poking to see if there’s another way, but I’m currently at an impasse. It’s a shame, because this allocation logic would be easy to implement in just a few lines of python code.

Yes, it’s a bit frustrating. It would have been interesting to get as many good ETF systems as possible on p123.

As for Phyton, look at this: Discussion Forum - QuantConnect.com Is it correct and does it work as described here: Bold Asset Allocation - Allocate Smartly ?

I am trying to follow Wouter Keller’s Bold Asset Allocation – Aggressive version

I should mention that I’m specifically trying to implement the Defensive Asset Allocation strategy linked in the first post, though conceptually these are fairly similar.

I also tried a screen of screens approach (see below), which is elegant, but again I hit a roadblock when trying to match behavior when the number of bad canary assets equals 1. In that case, I should allocate 50% of the portfolio to the top 1 protective asset and 50% equally to the top 6 risky assets. But in the screen of screens, all 7 get equal weight, and I’m not aware of a way to control these weights in the screener.

In their current form, I think both books and screens lack the necessary functionality to support these particular asset allocation strategies, so I’ll have to keep trying with p123’s strategy functionality to see if I can make it work.

SetVar(@CanaryMomentum_1, 12*Close(0,GetSeries("EEM"))/Close(21,GetSeries("EEM")) + 4*Close(0,GetSeries("EEM"))/Close(63,GetSeries("EEM")) + 2*Close(0,GetSeries("EEM"))/Close(126,GetSeries("EEM")) + Close(0,GetSeries("EEM"))/Close(252,GetSeries("EEM")) - 19)
SetVar(@CanaryMomentum_2, 12*Close(0,GetSeries("AGG"))/Close(21,GetSeries("AGG")) + 4*Close(0,GetSeries("AGG"))/Close(63,GetSeries("AGG")) + 2*Close(0,GetSeries("AGG"))/Close(126,GetSeries("AGG")) + Close(0,GetSeries("AGG"))/Close(252,GetSeries("AGG")) - 19)
ShowVar(@BadCanaries, (@CanaryMomentum_1 < 0) + (@CanaryMomentum_2 < 0))
Eval(@BadCanaries = 2, Screen("DAA: Protective", 1), 1)
Eval(@BadCanaries = 1, Screen("DAA: Protective", 1) or Screen("DAA: Risky", 6), 1)
Eval(@BadCanaries = 0, Screen("DAA: Risky", 6), 1)

I’ve tried a bunch of these are really prefer the DAA-12. I think the problem you may run into is that the BND canary asset total return needs to include the coupon. Otherwise, its gonna show RISK OFF a lot and you’ll be in SHY/IEF way too often. I’m not aware of how to get the total return score of BND or AGG with Port123. So…I gave up and check their free strategy signals each month…

Thank you both for your feedback.

It looks like you both are good at reading Phyton, is the code below, the Bold Aggressive version or one of Kelly’s other systems? And how to change this so that it becomes the aggressive version?

From what I understand, the only difference to the aggressive version is what comes from point 4 here:

> 4. If selecting from the offensive universe, select the 6 assets (balanced version) or 1 asset (aggressive version) with the highest relative momentum.
>
> Warning: The aggressive version of the strategy is going all in on a single risk asset. That’s a dangerous approach. We suggest minimizing that risk by combining multiple unrelated strategies together in a combined portfolio (something our platform was built to tackle).

Otherwise, https://www.quantconnect.com/ actually gives you the opportunity to run the system for free, so it is the possibility to examine there what is the last position.

#region imports
from AlgorithmImports import *
#endregion

#See: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4166845
import pandas as pd
import numpy as np

class BoldAssetAllocation(QCAlgorithm):

def Initialize(self):
    self.SetStartDate(2015,1,1)  # Set Start Date
    self.start_cash = 100000
    self.SetCash(self.start_cash)  # Set Strategy Cash
    self.SetBenchmark('SPY')
    
    # Algo Parameters
    self.prds = [1,3,6,12]
    self.prdwts = np.array([12,6,2,1])
    self.LO, self.LD, self.LP, self.B, self.TO, self.TD = [12,12,0,1,1,3]
    self.hprd = max(self.prds+[self.LO,self.LD])*21+50
    
    # Assets
    self.canary = ['SPY','EFA','EEM','BND']
    self.offensive = ['QQQ','EFA','EEM','BND']
    self.defensive = ['BIL','BND','DBC','IEF','LQD','TIP','TLT']
    self.safe = 'BIL'
    # repeat safe asset so it can be selected multiple times
    self.alldefensive = self.defensive + [self.safe] * max(0,self.TD - sum([1*(e==self.safe) for e in self.defensive]))
    self.eqs = list(dict.fromkeys(self.canary+self.offensive+self.alldefensive))
    for eq in self.eqs:
        self.AddEquity(eq,Resolution.Minute)
    
    # monthly rebalance
    self.Schedule.On(self.DateRules.MonthStart(self.canary[0]),self.TimeRules.AfterMarketOpen(self.canary[0],30),self.rebal)
    self.Trade = True
    
def rebal(self):
    self.Trade = True

def OnData(self, data):           
    if self.Trade:
        # Get price data and trading weights
        h = self.History(self.eqs,self.hprd,Resolution.Daily)['close'].unstack(level=0)
        wts = self.trade_wts(h)

        # trade
        port_tgt = [PortfolioTarget(x,y) for x,y in zip(wts.index,wts.values)]
        self.SetHoldings(port_tgt)
        
        self.Trade = False

def trade_wts(self,hist):
    # initialize wts Series
    wts = pd.Series(0,index=hist.columns)
    # end of month values
    h_eom = (hist.loc[hist.groupby(hist.index.to_period('M')).apply(lambda x: x.index.max())]
            .iloc[:-1,:])

    # =====================================
    # check if canary universe is triggered
    # =====================================
    # build dataframe of momentum values
    mom = h_eom.iloc[-1,:].div(h_eom.iloc[[-p-1 for p in self.prds],:],axis=0)-1
    mom = mom.loc[:,self.canary].T
    # Determine number of canary securities with negative weighted momentum
    n_canary = np.sum(np.sum(mom.values*self.prdwts,axis=1)<0)
    # % equity offensive 
    pct_in = 1-min(1,n_canary/self.B)

    # =====================================
    # get weights for offensive and defensive universes
    # =====================================
    # determine weights of offensive universe
    if pct_in > 0:
        # price / SMA
        mom_in = h_eom.iloc[-1,:].div(h_eom.iloc[[-t for t in range(1,self.LO+1)]].mean(axis=0),axis=0)
        mom_in = mom_in.loc[self.offensive].sort_values(ascending=False)
        # equal weightings to top relative momentum securities
        in_wts = pd.Series(pct_in/self.TO,index=mom_in.index[:self.TO])
        wts = pd.concat([wts,in_wts])
    # determine weights of defensive universe
    if pct_in < 1:
        # price / SMA
        mom_out = h_eom.iloc[-1,:].div(h_eom.iloc[[-t for t in range(1,self.LD+1)]].mean(axis=0),axis=0)
        mom_out = mom_out.loc[self.alldefensive].sort_values(ascending=False)
        # equal weightings to top relative momentum securities
        out_wts = pd.Series((1-pct_in)/self.TD,index=mom_out.index[:self.TD])
        wts = pd.concat([wts,out_wts])     
    
    wts = wts.groupby(wts.index).sum()

    return wts