Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mean Period Wise Spread (bps) leverage is 2 in Returns Analysis table #21

Open
Peropero0 opened this issue Dec 22, 2023 · 1 comment
Open

Comments

@Peropero0
Copy link

Problem Description

In Return Analysis section, the mean period wise return by factor quantile is computed as the period wise return of a portfolio made with assets belonging to the quantile. This information is useful to understand what forward returns one could expect when going long 100% a portfolio made of instruments belonging to a specific quantile.

The mean period wise spread though, is computed as the difference of mean period wise returns of specific quantiles, i.e. return of quantile 2 minus return of quantile 1. This should be useful to understand what forward returns to expect when building a long short portfolio.

Alphalens computes the spread as the difference of the two quantiles portfolios returns. In our example, this is like going 100% long the quantile 2 portfolio and 100% short the quantile 1 portfolio. In this way, the resulting portfolio is dollar neutral, but its leverage is 200%.

To account for a 100% leverage, Alphalens should rescale long portfolios to a total weight of 50% and short portfolios to -50%.

Please provide a minimal, self-contained, and reproducible example:

# imports
import pandas as pd
import alphalens

# build a dataframe with some signals for assets a and b
dates = pd.date_range('2020-01-02', '2020-01-07')
assets = ['asset_a', 'asset_b']

signals = pd.DataFrame({'signal': [1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 1, 2]}, index = pd.MultiIndex.from_product([dates, assets]))
signals.index = signals.index.rename(['date', 'asset'])

# build a dataframe with some prices for the instruments
prices = pd.DataFrame(
    {
    'asset_a': [10, 11, 10, 12, 10, 11, 13],
    'asset_b': [10, 9, 10, 11, 12, 11, 12],
    'date': pd.date_range('2020-01-02', '2020-01-08')
    })

prices = prices.set_index('date')

# get clean factor and forward returns, we want to go long the asset with signal = 2 and short the asset with signal = 1
quantiles = alphalens.utils.get_clean_factor_and_forward_returns(
        signals['signal'],
        prices,
        periods=(1,),
        quantiles=2
    )
image
# alphalens mean return spread is -253 bps
alphalens.tears.create_summary_tear_sheet(quantiles, long_short=True)
image
# now I compute the mean return spread without alphalens.
# I compute the mean daily return by quantile as the average of daily return by quantile (this is trivial since we have just one asset per quantile)
spread_df = quantiles.groupby(['date', 'factor_quantile'])['1D'].mean().unstack()

# then I compute the daily mean return spread using the following formula:
# daily mean return spread = daily mean fwd return quantile 2 - daily mean fwd return quantile 1
long_weight = 1
short_weight = -1
spread_df['mean_return_spread'] = long_weight * spread_df[2] + short_weight * spread_df[1]

# this results in -253 bps as computed using alphalens
# notice that we are dollar neutral but with a leverage of 2
print(f"mean return spread = {spread_df['mean_return_spread'].mean() * 10000} bps")
print("")
print(f"Long weight = {long_weight},  Short weight = {short_weight}")
print(f"Long leg + short leg weights = {long_weight + short_weight}")
print(f"total absolute weights (leverage) = {abs(long_weight) + abs(short_weight)}")
image
# now do the same, but set long weights and short weights to +0.5 and -0.5 respectively
# notice that this results in a halved mean return spread because we are now dollar neutral and with leverage 1

long_weight = 0.5
short_weight = -0.5

spread_df = quantiles.groupby(['date', 'factor_quantile'])['1D'].mean().unstack()
spread_df['mean_return_spread'] = long_weight * spread_df[2] + short_weight * spread_df[1]

print(f"mean return spread = {spread_df['mean_return_spread'].mean() * 10000} bps")
print("")
print(f"Long weight = {long_weight},  Short weight = {short_weight}")
print(f"Long leg + short leg weights = {long_weight + short_weight}")
print(f"total absolute weights (leverage) = {abs(long_weight) + abs(short_weight)}")
image

Please provide any additional information below:
We would like to do a pull request to implement a solution.

Versions

  • Alphalens version: 0.4.2
  • Python version: 3.9.9
  • Pandas version: 1.5.3
@stefan-jansen
Copy link
Owner

Fair point, a PR would be welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants