Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
"Translating" Alphalens results into an algorithm?

After finding a factor that looks promising in Alphalens, I'm having trouble getting similarly promising returns when placing the factor in an algorithm. (Not expecting the algorithm to exactly replicate Alphalens results, but hoping to get results with at least the same positive/negative sign as the numbers reported in Alphalens!)

Based on this Alphalens issue, it seems like Alphalens does the following to compute returns:

...the cumulative returns computed from N periods returns are calculated assuming an algorithm that divides the initial capital by N and trade each 1/N sub-capital at subsequent periods and each sub-capital is then rebalanced every N periods. This is similar of having N algorithms each rebalancing every N periods and started at subsequent periods, not at the same time.

And looking through the code, this does seem to be exactly what is happening.

Given an attached Pipeline with my factor values and initialized (empty Series) context.port_B and context.port_C, I tried using this rebalance function:

def rebalance(context, data):

    context.output = algo.pipeline_output('pipeline')

    # Get top- and bottom-quintile securities  
    q4 = context.output[context.output['q_rank'] == 4]['factor']  
    q0 = context.output[context.output['q_rank'] == 0]['factor']

    context.port_A = q4.append(q0)

    # Combine portfolio A weights with portfolio B, portfolio C  
    all_factor_values = context.port_A.append([context.port_B, context.port_C]).groupby(level=0).sum()

    # Compute weights and place orders  
    target_weights = opt.MaximizeAlpha(all_factor_values)  
    constraints = [opt.MaxGrossExposure(1.0)]  
    order_optimal_portfolio(objective = target_weights, constraints = constraints)

    # Reset portfolios for next iteration  
    context.port_C = context.port_B  
    context.port_B = context.port_A  

But this has actually yielded negative alpha while Alphalens indicated significant positive alpha.

Does this make sense, or am I doing something incorrectly here? Or is there a better way to get a factor from Alphalens into an algorithm? Any help is much appreciated!

12 responses

Here is a more detailed note regarding the cumulative return computation in Alphalens:

Since we are computing the factor daily, via Pipeline, if period is
greater than 1 day the returns happening on one day overlap the
returns happening the following days, this is because new daily factor
values are computed before the previous returns are realized. if we
have new factor values but we haven’t exited the previous positions
yet, how can we trade the new factor values?

Let’s consider for example a 5 days period. A first solution could be
to trade the factor every 5 days instead of daily. Unfortunately this
approach has several drawbacks. One is that depending on the starting
day of the algorithm we get different results, but the most important
issue comes from The Fundamental Law of Asset Management
which states:

IR = IC √BR

The more independent bets we make the higher IR we get. In a year we
have 252 trading days, if we rebalance every 5 days we have 252/5= ~50
bets instead of 252. We don't really want to decrease our performance
decreasing the bets, so here is the solution:

Given a period N, the cumulative returns are computed building and
averaging N interleaved sub portfolios (each started at subsequent
periods 1,2,..,N) each one rebalancing every N periods and each
trading 1/Nth of the initial capital. This corresponds to an algorithm
which split its initial capital in N sub capitals and trades each sub
capital independently, each one rebalancing every N days. Since the
sub portfolios are started in subsequent days the algorithm trades
every single day.

Compared to an algorithm that trades the factor every N day, this
method increases the IR , has lower volatility, the returns are
independent from the starting day and, very important, the algorithm
capacity is increased by N times since the capital is split between N
subportfolios and the slippage impact is decreased by N.

For more details see help(al.performance.cumulative_returns)

Now, let's get to your point: how can we reproduce Alphalens results into an algorithm?

First, please make sure you set the following in your algorithm:

# not commission applied to the orders  
set_commission(commission.PerShare(cost=0., min_trade_cost=0))  
# no slippage and no share volume limit applied to the orders  
set_slippage(slippage.FixedSlippage(spread=0.00))  

Regarding your implementation, If I understood it correctly, you are trying to implement a rebalance period of 3 days averaging the last 3 days factor values and using this average as the daily weights. Is this correct? A more convenient way of achieving this is to use the builtin SimpleMovingAverage factor and compute the weights from there, e.g.

avgFactor = SimpleMovingAverage(inputs=[ myFactor ],   window_length=3)  
pipe.add(avgFactor, "myFactor")  

Having said that, averaging the last N days factor values is not the right way to replicate Alphalens behavior. There are two reasons for this:

1 - After the stock weights are computed from the factor values they are subject to change due to stock prices movements: i.e. the weights are valid only at the moment you enter the orders, after that point the weights keep changing accordingly with stock prices. If we average the last N days factor values, we force each stock to maintain the same weight it had when the positions were entered. More precisely, the algorithm would keep buying more shares of stocks that decrease in price and would sell shares of stocks that increase in price (here I am referring to the long positions, invert the meaning for the short leg). Is this something good? I haven't thought about it but it's not the Alphalens behavior.

2 - This is not very important when testing, but I want to state it for the sake of completeness: we have to take into consideration stocks that is not possible to buy or sell (not traded on a specific day or not enough volume) and correct stock weights accordingly with the actual shares bought or sold.

I post here my template algorithm that I use to properly reproduce Alphalens behavior. You'll see that it is complex and I encourage you to not look at the full code right away; first test it and see if you are able to get the same Alphalens results. To use my algorithm template you need to do the following:

1 - Define at the beginning of the algo the factors you need to use
2 - Inside make_pipeline function add all the factors you want to the pipeline
3- Inside initialize function define the settings of your algorithm, especially the factors you want to use and how you want to use them (see example below)

The template algorithm supports as many strategies as you like. A strategy is an independent portfolio. For example let's see how the example code defines a strategy:

    # I believe the settings are rather self explanatory  
    s1 = Strategy(rebalance_days=3, max_long_sec=300, max_short_sec=300,  
                  group_neutral=context.group_neutral,  
                  factors=["factor1"])  # this factor was added inside make_pipeline function

    # this field is mandatory and must contains all the strategies you want to trade  
    context.strategies = [s1]  

If you want to have multiple strategies you can do something like this (the total capital will be evenly split among strategies s1,s2, that is 50%/50%):

    s1 = Strategy(rebalance_days=4, max_long_sec=200, max_short_sec=200,  
                  group_neutral=context.group_neutral,  
                  factors=["factor1"])  # this factor was added inside make_pipeline fcuntion  
    s2 = Strategy(rebalance_days=2, max_long_sec=400, max_short_sec=400,  
                  group_neutral=context.group_neutral,  
                  factors=["factor2"])  # this factor was added inside make_pipeline fcuntion    

    # this field is mandatory and must contains all the strategies you want to trade  
    context.strategies = [s1, s2]  

A strategy can also use multiple factors and inside a strategy each factor will weight the same (but the total capital will be evenly split among strategies s1,s2, s3, that is 33.3%/33.3%/33.3%):

    s1 = Strategy(rebalance_days=4, max_long_sec=200, max_short_sec=200,  
                  group_neutral=context.group_neutral,  
                  factors=["factor1", "factor2", "factor3"])  # factors were added inside make_pipeline fcuntion  
    s2 = Strategy(rebalance_days=2, max_long_sec=600, max_short_sec=400,  
                  group_neutral=context.group_neutral,  
                  factors=["factor4"])  # this factor was added inside make_pipeline fcuntion  
    s3 = Strategy(rebalance_days=5, max_long_sec=100, max_short_sec=350,  
                  group_neutral=context.group_neutral,  
                  factors=["factor5", "factor6", "factor7"])  # factors were added inside make_pipeline fcuntion

    # this field is mandatory and must contains all the strategies you want to trade  
    context.strategies = [s1, s2,s3]  

The big issue of this code is that it currently doesn't use the optimize API, so you cannot submit the algorithm to the contest.

Wow, what an awesome post! Thanks Luca.

Kudos Luca! This will save us all godzillion hours of backtesting to work the Alphalens factors!

Thanks heaps!

@Luca,
This is great...thanks!
alan

The commission being set to zero for daily rebalancing strategy looks bit ominous to me.

Thanks so much for the detailed response, Luca!

One quick question -- is the periods parameter from the Alphalens tearsheet function incorporated via the rebalance_days parameter of the Strategy object? For example, say I wanted to recreate an Alphalens study with a 60D period for a given factor. Would I simply place the factor in a Pipeline and put it in a Strategy object with rebalance_days=60?

is the periods parameter from the Alphalens tearsheet function
incorporated via the rebalance_days parameter of the Strategy object?
For example, say I wanted to recreate an Alphalens study with a 60D
period for a given factor. Would I simply place the factor in a
Pipeline and put it in a Strategy object with rebalance_days=60?

Yes, exactly.

Awesome analysis and template Luca. Very well written.

This corresponds to an algorithm
which split its initial capital in N sub capitals and trades each sub
capital independently, each one rebalancing every N days.

How can we do this with the template? If I set 30 sub strategies with rebalance_days=30 (for example), how to ensure that each strategy is rebalanced on a different day?

@Satya Kris. If you set a single strategy with rebalance_days=30 then the code will actually create the 30 sub strategies rebalanced on different days.

Awesome thanks Luca.

Luca, great work and elegant piece of ensemble coding! This code routine is probably somewhat similar to the Q fund portfolio allocation process. Have you tried incorporating the Optimized API with constraints? Or is this not possible or difficult because of different rebalance frequencies of the sub strategies?