math-backend / utils /metrics.py
engineportf's picture
Upload folder using huggingface_hub
558db1e verified
Raw
History Blame Contribute Delete
2.35 kB
import pandas as pd
import numpy as np
def israelsen_sharpe(excess_return, vol):
"""
Modified Sharpe Ratio by Craig Israelsen.
Standard Sharpe penalizes higher volatility even when returns are negative.
This adjustment correctly rewards lower volatility when returns are negative.
"""
if excess_return == 0 or vol == 0:
return 0.0
exponent = excess_return / abs(excess_return)
return excess_return / (vol ** exponent)
def portfolio_gross_metrics(weights: pd.Series, current_yields: pd.Series = None, durations: pd.Series = None) -> dict:
"""
Calculates gross leverage, total long exposure, and total short exposure.
Optionally computes portfolio-level weighted yield and duration if data is provided.
Returns:
dict: Containing 'gross_lev', 'long_e', 'short_e', 'port_yield', 'port_duration'
"""
w = weights.drop(labels=['CASH'], errors='ignore')
long_e = w[w > 0].sum()
short_e = w[w < 0].sum()
gross_lev = abs(long_e) + abs(short_e)
port_yield = None
port_duration = None
if current_yields is not None:
port_yield = sum(w.get(t, 0) * current_yields.get(t, 0) for t in w.index)
if durations is not None:
port_duration = sum(w.get(t, 0) * durations.get(t, 0) for t in w.index)
return {
"gross_lev": float(gross_lev),
"long_e": float(long_e),
"short_e": float(short_e),
"port_yield": float(port_yield) if port_yield is not None else None,
"port_duration": float(port_duration) if port_duration is not None else None
}
def liquidity_score(weights, spread_map):
"""Calculates the weighted average bid-ask spread of the portfolio."""
w = weights.drop(labels=['CASH'], errors='ignore')
if w.abs().sum() == 0:
return 0.0
w_norm = w.abs() / w.abs().sum()
score = sum(w_norm.get(t, 0) * spread_map.get(t, 0.0008) for t in w.index)
return score
def annual_returns(daily_returns_series):
"""Aggregates daily returns into calendar year returns."""
if daily_returns_series.empty:
return {}
s = daily_returns_series.copy()
s.index = pd.to_datetime(s.index)
ann = s.groupby(s.index.year).apply(lambda x: (1 + x).prod() - 1)
return {int(year): float(v) for year, v in ann.items() if not pd.isna(v)}