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

Implement exponential mode for annualized_return calculation in risk_… #1433

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions qlib/contrib/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,14 @@
logger = get_module_logger("Evaluate")


def risk_analysis(r, N: int = None, freq: str = "day"):
def risk_analysis(r, N: int = None, freq: str = "day", accumulation_mode = "summation"):
Copy link
Collaborator

Choose a reason for hiding this comment

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

"""Risk Analysis
NOTE:
The calculation of annulaized return is different from the definition of annualized return.
It is implemented by design.
Qlib tries to cumulated returns by summation instead of production to avoid the cumulated curve being skewed exponentially.
All the calculation of annualized returns follows this principle in Qlib.

TODO: add a parameter to enable calculating metrics with production accumulation of return.

Parameters
----------
r : pandas.Series
Expand All @@ -42,6 +40,8 @@ def risk_analysis(r, N: int = None, freq: str = "day"):
scaler for annualizing information_ratio (day: 252, week: 50, month: 12), at least one of `N` and `freq` should exist
freq: str
analysis frequency used for calculating the scaler, at least one of `N` and `freq` should exist
accumulation_mode: str
the mode of calculating the cumulative return, options are 'exponential' and 'summation'
"""

def cal_risk_analysis_scaler(freq):
Expand All @@ -61,10 +61,16 @@ def cal_risk_analysis_scaler(freq):
warnings.warn("risk_analysis freq will be ignored")
if N is None:
N = cal_risk_analysis_scaler(freq)

if accumulation_mode not in ["summation", "exponential"]:
raise ValueError("Invalid value for `accumulation_mode`. Only 'summation' and 'exponential' are supported")

mean = r.mean()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be geometric mean if production is considered

std = r.std(ddof=1)
annualized_return = mean * N
if accumulation_mode == "summation":
annualized_return = mean * N
else:
annualized_return = (np.prod(1 + r) - 1)
information_ratio = mean / std * np.sqrt(N)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure if the annualized volatility should be chagned if production is considered.
But intuitively, the accumulated volatility and production vollatility is different.

max_drawdown = (r.cumsum() - r.cumsum().cummax()).min()
Copy link
Collaborator

Choose a reason for hiding this comment

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

The maxdrawdown should also be changed if production is considered

data = {
Expand Down