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

added beta & alpha #1221

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open

added beta & alpha #1221

wants to merge 11 commits into from

Conversation

jensnesten
Copy link

@jensnesten jensnesten commented Feb 16, 2025

Add Alpha and Beta Metrics to Performance Analysis

This PR introduces two key risk-adjusted performance metrics:

  • Alpha: Measures strategy's excess return relative to buy & hold benchmark
  • Beta: Measures strategy's systematic risk (correlation with market returns)

@kernc kernc force-pushed the master branch 3 times, most recently from 70abc06 to c79ffb0 Compare February 17, 2025 01:32
@kernc kernc changed the title added beta & alpha / resolved merge conflict added beta & alpha Feb 18, 2025
Copy link
Owner

@kernc kernc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great!

You'll need to amend a few tests to get the lights green ...

jensnesten and others added 2 commits February 26, 2025 13:53
Co-authored-by: kernc <kerncece@gmail.com>
Co-authored-by: kernc <kerncece@gmail.com>
@jensnesten
Copy link
Author

jensnesten commented Feb 27, 2025

When looking at the expected stats results, mine are quite off. Running the smacross strat as defined in _test.py i get the following output:

Start                     2004-08-19 00:00:00
End                       2013-03-01 00:00:00
Duration                   3116 days 00:00:00
Exposure Time [%]                    93.99441
Equity Final [$]                  39770.03122
Equity Peak [$]                     61303.036
Commissions [$]                    8584.81878
Return [%]                          297.70031
Buy & Hold Return [%]               522.06019
Return (Ann.) [%]                    17.58149
Volatility (Ann.) [%]                35.35528
CAGR [%]                             11.81185
Sharpe Ratio                          0.49728
Sortino Ratio                         0.89695
Calmar Ratio                          0.34926
Alpha [%]                           277.50897
Beta                                  0.03868
Max. Drawdown [%]                   -50.33958
Avg. Drawdown [%]                    -6.25468
Max. Drawdown Duration      963 days 00:00:00
Avg. Drawdown Duration       47 days 00:00:00
# Trades                                   65
Win Rate [%]                         46.15385
Best Trade [%]                       53.59595
Worst Trade [%]                     -18.39887
Avg. Trade [%]                        2.35371
Max. Trade Duration         183 days 00:00:00
Avg. Trade Duration          46 days 00:00:00
Profit Factor                         2.08802
Expectancy [%]                        3.09763
SQN                                   1.01383
Kelly Criterion                       0.14365

These do not line up with the expected output from the dataframe defined in test_compute_stats(). Seems like the last trade isn't closed properly, it should be 66 trades in total. Ran a simple strat that buys on first close and sells at the last, and it failed to close the trade as well (which works fine on my older version of this repo). Also the test results i get from this newer version differ from my older version when running the same strat, can't seem to tell why though - just thought I'd mention.

@kernc
Copy link
Owner

kernc commented Feb 27, 2025

These do not line up with the expected output from the dataframe defined in test_compute_stats().

Your stats contain 'Commissions [$]' but no test_compute_stats specifies them ... 💡

def test_compute_stats(self):
stats = Backtest(GOOG, SmaCross, finalize_trades=True).run()

def test_compute_stats(self):
stats = Backtest(GOOG, SmaCross).run()

There were a few changes recently that might change results obtained with prior versions, notably:

ae3d69f*
d4ec0ba
8fbb902
8b88e81
58de404

The current tests are considered accurate as long as they are passing on CI.

@jensnesten
Copy link
Author

Ahhh I see now, thank you! Have added some expected values of alpha and beta to the dataframe. Is that all that is needed?

@jensnesten jensnesten requested a review from kernc March 3, 2025 09:22
Comment on lines +70 to +71
Alpha [%] 450.62
Beta 0.02
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For given values of s.loc['Return [%]'] and s.loc['Buy & Hold Return [%]'], the previous computation of

s.loc['Alpha [%]'] = s.loc['Return [%]'] - s.loc['Buy & Hold Return [%]']  # == -144

seemed much more reasonable! Can you explain the discrepancy?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLM suggests, among the less applicable options, that the values are not computed on the same time scale—beta is computed on daily returns and alpha on the whole duration ... 🤔

Copy link
Author

@jensnesten jensnesten Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the 'simple' alpha we first had is absolute alpha, where we simply calculate the absolute difference of our strategy relative to the underlying benchmark. The new calc is risk-adjusted alpha, which is the official definition of alpha, but here we might end up in situations where alpha is positive, despite underperforming the benchmark in terms of return. Especially in beta neutral strategies where beta approaches 0, you'd have situations where the right-most term will go to 0 - essentially signaling very little risk:

$\alpha=(R-r) - \beta(R_{b}-r)$
when $\beta \rightarrow 0$:
$\alpha=(R-r)$

I personally like the simple alpha calc in this context, because its simple and intuitive. But the alpha we have now is the correct alpha definition.

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

Successfully merging this pull request may close these issues.

2 participants