There are a couple of things wrong with your leveraging, but first want to clarify what you are trying to accomplish. Typically, one tries to create a portfolio which has a net beta of zero. However, you could strive for a net beta of any number. If the market is going up maybe try for a positive beta. If the market is going down then maybe a negative one. The Quantopian contest is looking for betas less than .3. Maybe that would be a good number?
So, starting with some basic math, how to calculate the net beta?
Consider two securities, ABC and XYZ, with betas of .5 and 1.5 respectively. One holds $1000 of each so equal dollar values of each. The net beta, beta_net, equals (.5 + 1.5) / 2 = 1.
In general, for an arbitrary number of securities, with d1, d2 ... d_n representing the amount (in dollars) held, and beta1, beta2 ... beta_n the betas, then beta_net can be calculated as below. It's really just a weighted average of betas. The weights (the d's) are the dollar amounts and not the quantity of shares.
beta_net = ((d1 * beta1) + (d2 * beta2) ... + (d_n * beta_n) ) / (d1 + d2 ... + d_n)
# in Pythonic pseudo-code this looks like
beta_net = d * beta / d.sum
# a convenient Numpy method is "average" which has a "weights" parameter to give a weighted average
beta_net = np.average(beta, weights=d)
Now, IF there are equal dollar amounts of each security then d1 = d2 = d_n , so beta_n can be simplified to
beta_n = ((d * beta1) + (d * beta2) ... + (d * beta_n)) / (d*n)
beta_n = ( beta1 + beta2 ... + beta_n) / n # which is just the mean or average beta
The formulas that both you and Luca used incorporate the "mean" method. That is fine as long as one later goes on to order equal amounts of each security. However, that is not the way your order logic is written. Your order logic seems to weight each security by it's rank. Therefore you also need to weight each beta by this same amount.
See if that fixes your logic.
Also, I'm not really familiar with the term "deleverage". Ultimately, you don't want to calculate the net beta (as above) but rather calculate how much to weight the long basket of stocks and how much to weight the short basket. This gets back to the question of what you want for the net beta. So, let's assume one has a dollar amount of long stocks and call that "dollars_long". What one wants to know is how much of the short stocks to purchase so the net beta of the portfolio is some value (call it beta_net). It's really just the equation above but instead of solving for beta_net one solves for the dollars_short.
# starting with the equation for beta_net and two positions (long and short)
beta_net = ((dollars_long * beta_long) + (dollars_short * beta_short)) / (dollars_long + dollars_short)
(dollars_long + dollars_short) * beta_net = (dollars_long * beta_long) + (dollars_short * beta_short)
dollars_short * beta_net = (dollars_long * beta_long) + (dollars_short * beta_short) - (dollars_long * beta_net)
dollars_short * beta_net = ((dollars_long * (beta_long - beta_net)) + (dollars_short * beta_short)
(dollars_short * beta_net) - (dollars_short * beta_short) = dollars_long * (beta_long - beta_net)
((dollars_short * (beta_net - beta_short)) = dollars_long * (beta_long - beta_net)
dollars_short = (dollars_long * (beta_long - beta_net)) / (beta_net - beta_short)
# so finally.... move the negative sign out front and we solve the amount of dollars short
# notice the negative sign implies "sell" or "short" this amount
dollars_short = -(dollars_long * (beta_net - beta_long)) / (beta_net - beta_short)
# we can check this with $1000 of longs with a beta of .5 and shorts with a beta of 1.5
# How much of the shorts to purchase to net a zero beta?
dollars_short = -1000 * (0 - .5)/(0 - 1.5) = -(1000*-.5)/ (-1.5) = -333.3
# Put this value into the original equation for beta_net to double check. We do indeed get 0 beta if we short $333.3 of the shorts.
beta_net = ((1000 * .5) + (-333.3 * 1.5)) / (1000 -333.3)
beta_net = (500 - 500) / -666.6 = 0
I won't go into the math here, but if you are trying to get a zero beta, a more useful number may be the "hedge_ratio" or the percentage of the portfolio which should be allocated to the "hedge". In this case the "shorts" are our hedge. This only works when you are trying for a zero beta. A non-zero beta has a little different equation.
hedge_ratio = beta_long / (beta_short + beta_long)
short_percentage = -hedge_ratio # negative because we want to go short these securities
long_percentage = 1-hedge_ratio # will be positive and therefore long if both betas are positive
Make sure you define what net beta you are trying to achieve and then calculate the correct values for the amounts of longs and shorts. Also, as I mentioned in a previous post, maybe ensure that both the long and short betas are positive.
Maybe something like this
# output is a pandas dataframe returned from the pipeline. It has columns named "beta", "longs", "shorts", and "rank"
# I assume the "longs" and "shorts" columns are boolean.
# There are many ways to index and slice a dataframe, but one convenient one is the query method.
# Both of the below statements are equivalent since "longs" is bolean.
long = output.query('longs')
long = output.query('longs == True')
# long is a dataframe with the same columns as output but only the rows where the column "longs" is True
# The betas can be accessed as follows. The result is a Pandas series type.
# Pandas series can be multiplied as long as the lengths are the same and then returns another Pandas series.
# Pandas series also have a convenient "sum" method.
# Putting this together we can get the average beta weighted by rank
beta_long = (long['beta'] * long['rank']).sum() / long['rank'].sum
# Another (perhaps faster) method is to use the numpy "average" method (remember to import numpy)
beta_long = numpy.average(long['beta'], weights = long['rank'])
I'd finally make a separate pandas series which has all the weights. Assuming one wants to weight based upon rank.
long_weights = output.query('longs')['rank'] / output.query('longs')['rank'].sum()
The order logic could then be something like this
short_percentage = beta_long / (beta_short + beta_long) # This should be positive. Remember to order a negative amount.
long_percentage = 1 - short_percentage
for stock in long_weights:
order_target_percent(stock, context.leverage * long_percentage * long_weight[stock])
for stock in short_weights:
order_target_percent(stock, context.leverage * short_percentage * -short_weight[stock])
Hope this helps? I didn't actually run any of the code or double check my math, so please excuse any mistakes and let me know.