Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions quantstats/report.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ <h3>Key Performance Metrics</h3>
<h3>Worst 10 Drawdowns</h3>
{{dd_info}}
</div>

<div id="param">
<h3>Parameters Used</h3>
{{parameters_info}}
</div>
</div>

</div>
Expand Down
29 changes: 17 additions & 12 deletions quantstats/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def html(
figfmt="svg",
template_path=None,
match_dates=True,
parameters: dict = None,
**kwargs,
):
"""
Expand Down Expand Up @@ -158,6 +159,8 @@ def html(
Path to custom HTML template file. Uses default if None
match_dates : bool, default True
Whether to align returns and benchmark start dates
parameters : dict, optional
Strategy parameters
**kwargs
Additional keyword arguments for customization:
- strategy_title: Custom name for the strategy
Expand Down Expand Up @@ -376,6 +379,10 @@ def html(
)
tpl = tpl.replace("{{dd_info}}", dd_html_table)

if parameters:
parameters_info = _pd.DataFrame(list(parameters.items()), columns=['Parameter', 'Value'])
tpl = tpl.replace("{{parameters_info}}", _html_table(parameters_info, False))

# Get active returns setting for plots
active = kwargs.get("active_returns", False)

Expand Down Expand Up @@ -1147,6 +1154,7 @@ def metrics(
blank = [""] * len(returns.columns) + [""]
for i, strategy_col in enumerate(returns.columns):
df["returns_" + str(i + 1)] = returns[strategy_col]
df.dropna(inplace=True)

# Calculate start and end dates for each series
if isinstance(returns, _pd.Series):
Expand Down Expand Up @@ -1232,15 +1240,15 @@ def metrics(

# Calculate Omega ratio (probability-weighted ratio)
if isinstance(returns, _pd.Series):
metrics["Omega"] = _stats.omega(df["returns"], rf, 0.0, win_year)
omega_values = [_stats.omega(df["returns"], rf, 0.0, win_year)]
elif isinstance(returns, _pd.DataFrame):
omega_values = [
_stats.omega(df[strategy_col], rf, 0.0, win_year)
for strategy_col in df_strategy_columns
]
if "benchmark" in df:
omega_values.append(_stats.omega(df["benchmark"], rf, 0.0, win_year))
metrics["Omega"] = omega_values
if "benchmark" in df:
omega_values.append(_stats.omega(df["benchmark"], rf, 0.0, win_year))
metrics["Omega"] = omega_values

# Add separator and prepare for drawdown metrics
metrics["~~~~~~~~"] = blank
Expand Down Expand Up @@ -1284,12 +1292,12 @@ def metrics(

# Calculate benchmark-relative metrics
if isinstance(returns, _pd.Series):
metrics["R^2"] = _stats.r_squared(
metrics["R^2"] = [_stats.r_squared(
df["returns"], df["benchmark"], prepare_returns=False
)
metrics["Information Ratio"] = _stats.information_ratio(
).round(2)] + ["-"]
metrics["Information Ratio"] = [_stats.information_ratio(
df["returns"], df["benchmark"], prepare_returns=False
)
).round(2)] + ["-"]
elif isinstance(returns, _pd.DataFrame):
metrics["R^2"] = (
[
Expand Down Expand Up @@ -1806,9 +1814,6 @@ def plots(

return

# Ensure returns is DataFrame for full mode processing
returns = _pd.DataFrame(returns)

# prepare timeseries
if benchmark is not None:
benchmark = _utils._prepare_benchmark(benchmark, returns.index)
Expand Down Expand Up @@ -1881,7 +1886,7 @@ def plots(

# Calculate figure size for smaller plots
small_fig_size = (figsize[0], figsize[0] * 0.35)
if len(returns.columns) > 1:
if isinstance(returns, _pd.DataFrame) and len(returns.columns) > 1:
small_fig_size = (
figsize[0],
figsize[0] * (0.33 * (len(returns.columns) * 0.66)),
Expand Down
25 changes: 13 additions & 12 deletions quantstats/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ def avg_win(returns, aggregate=None, compounded=True, prepare_returns=True):
returns = _utils.aggregate_returns(returns, aggregate, compounded)

# Calculate mean of positive returns only
return returns[returns > 0].dropna().mean()
return returns[returns > 0].mean()


def avg_loss(returns, aggregate=None, compounded=True, prepare_returns=True):
Expand Down Expand Up @@ -613,7 +613,7 @@ def avg_loss(returns, aggregate=None, compounded=True, prepare_returns=True):
returns = _utils.aggregate_returns(returns, aggregate, compounded)

# Calculate mean of negative returns only
return returns[returns < 0].dropna().mean()
return returns[returns < 0].mean()


def volatility(returns, periods=252, annualize=True, prepare_returns=True):
Expand Down Expand Up @@ -1798,17 +1798,18 @@ def conditional_value_at_risk(returns, sigma=1, confidence=0.95, prepare_returns
var = value_at_risk(returns, sigma, confidence)

# Calculate mean of returns below VaR threshold
below_var = returns[returns < var]
# Handle both Series and DataFrame inputs
if isinstance(returns, _pd.DataFrame):
# For DataFrame, use dropna() to remove NaN values after filtering
below_var = returns[returns < var].dropna()
c_var = below_var.values.mean() if len(below_var) > 0 else _np.nan
# For DataFrame
c_var = below_var.mean().values
# Return CVaR if valid, otherwise return VaR
return _np.where(_np.isnan(c_var), var, c_var)
else:
# For Series, the original approach works fine
c_var = returns[returns < var].values.mean()

# Return CVaR if valid, otherwise return VaR
return c_var if ~_np.isnan(c_var) else var
# For Series
c_var = below_var.values.mean()
# Return CVaR if valid, otherwise return VaR
return c_var if ~_np.isnan(c_var) else var


def cvar(returns, sigma=1, confidence=0.95, prepare_returns=True):
Expand Down Expand Up @@ -2117,7 +2118,7 @@ def outlier_win_ratio(returns, quantile=0.99, prepare_returns=True):

# Calculate ratio of high quantile to mean positive return
positive_mean = returns[returns >= 0].mean()
quantile_mean = returns.quantile(quantile).mean() if isinstance(returns, _pd.DataFrame) else returns.quantile(quantile)
quantile_mean = returns.quantile(quantile)

# Handle both Series (DataFrame input) and scalar (Series input) cases
if isinstance(positive_mean, _pd.Series):
Expand Down Expand Up @@ -2156,7 +2157,7 @@ def outlier_loss_ratio(returns, quantile=0.01, prepare_returns=True):

# Calculate ratio of low quantile to mean negative return
negative_mean = returns[returns < 0].mean()
quantile_mean = returns.quantile(quantile).mean() if isinstance(returns, _pd.DataFrame) else returns.quantile(quantile)
quantile_mean = returns.quantile(quantile)

# Handle both Series (DataFrame input) and scalar (Series input) cases
if isinstance(negative_mean, _pd.Series):
Expand Down