Files
2026-06-17 17:12:19 -04:00

61 lines
2.2 KiB
Python

"""Simple strategy performance metrics.
Scores a ``signal`` column against forward returns: total return, annualized
Sharpe, win rate and max drawdown.
"""
import numpy as np
def compute_performance_metrics(data, signal_col="signal", forecast_periods=12,
annualization=24 * 365, verbose=True):
"""Compute strategy performance from a signal column and forward returns.
Parameters
----------
data : pd.DataFrame
Must contain a ``close`` column and ``signal_col`` (values in {-1, 0, 1}).
forecast_periods : int
Holding horizon (in bars) used to compute the forward return.
annualization : float
Factor applied under the square root when annualizing the Sharpe ratio.
Returns
-------
dict
total_return, sharpe_ratio, win_rate, max_drawdown and the augmented frame.
"""
df = data.copy()
# Forward return over the forecast horizon.
df["future_return"] = df["close"].pct_change(periods=forecast_periods).shift(-forecast_periods)
# Strategy return: signal * future return (long: +return, short: -return).
df["strategy_return"] = df[signal_col] * df["future_return"]
df = df.dropna(subset=["strategy_return"])
# Cumulative return.
df["cumulative_return"] = (1 + df["strategy_return"]).cumprod() - 1
total_return = df["cumulative_return"].iloc[-1] if len(df) else float("nan")
sharpe_ratio = (
df["strategy_return"].mean() / df["strategy_return"].std() * np.sqrt(annualization)
if df["strategy_return"].std() else float("nan")
)
active = df[df["strategy_return"] != 0]
win_rate = len(df[df["strategy_return"] > 0]) / len(active) if len(active) else float("nan")
max_drawdown = (df["cumulative_return"].cummax() - df["cumulative_return"]).max()
if verbose:
print(f"Total Return: {total_return:.2%}")
print(f"Sharpe Ratio: {sharpe_ratio:.2f}")
print(f"Win Rate: {win_rate:.2%}")
print(f"Max Drawdown: {max_drawdown:.2%}")
return {
"total_return": total_return,
"sharpe_ratio": sharpe_ratio,
"win_rate": win_rate,
"max_drawdown": max_drawdown,
"data": df,
}