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.