Metrics & Analytics¶
The metrics module computes 50+ financial and risk metrics from ISO trade data. The pipeline flows through a layered architecture: Schema adapts ISO-specific DataFrames, Series wraps daily time series, Calculator produces metric dataclasses, and Engine provides the public facade.
Quick Start¶
from progridpy.metrics import MetricsEngine
engine = MetricsEngine(iso_name="MISO", df_iso=trade_df)
result = engine.compute()
print(f"Sharpe Ratio: {engine.sharpe_overall:.2f}")
print(f"Max Drawdown: ${engine.max_drawdown:,.2f}")
print(f"Win Rate: {engine.win_rate_pct:.1f}%")
MetricsEngine¶
MetricsEngine is the primary entry point. It accepts an ISO name and a pandas DataFrame of trade data.
from progridpy.metrics import MetricsEngine
engine = MetricsEngine(iso_name="MISO", df_iso=trade_df)
Constructor Parameters¶
| Parameter | Type | Description |
|---|---|---|
iso_name |
str |
ISO identifier: "MISO", "SPP", or "ERCOT" |
df_iso |
pd.DataFrame |
Trade data with ISO-specific columns |
Computing Metrics¶
Call compute() to run the full calculation pipeline. This method is idempotent -- subsequent calls return the cached result.
Convenience Properties¶
After calling compute(), access key metrics directly:
engine.estimated_risk # float -- Estimated risk value
engine.sharpe_overall # float -- Overall Sharpe ratio
engine.sortino_overall # float -- Overall Sortino ratio
engine.win_rate_pct # float -- Win rate percentage
engine.max_drawdown # float -- Maximum drawdown
engine.calendar_returns # dict[int, float] -- Calendar returns by year
Call compute() first
Accessing any property before calling compute() raises RuntimeError.
MetricsResult¶
MetricsResult groups all metrics into five dataclass categories:
result = engine.compute()
result.risk # RiskMetrics
result.returns # ReturnMetrics
result.ratios # RatioMetrics
result.drawdowns # DrawdownMetrics
result.streaks # StreakMetrics
Exporting to DataFrame¶
Flatten all metrics into a two-column DataFrame:
metrics_df = engine.metrics_df # DataFrame with 'metric' and 'value' columns
metrics_df.to_csv("metrics_report.csv", index=False)
The to_frame() method on MetricsResult produces the same output:
Accessing Metric Groups¶
RiskMetrics¶
risk = result.risk
risk.estimated_risk # Estimated daily risk
risk.cvar_1 # CVaR at 1%
risk.cvar_5 # CVaR at 5%
risk.var_99 # VaR at 99%
risk.var_95 # VaR at 95%
risk.var_90 # VaR at 90%
risk.capital_required # ISO-specific capital requirement (or None)
ReturnMetrics¶
returns = result.returns
returns.mean_daily # Mean daily return
returns.median_daily # Median daily return
returns.mean_monthly_30d # Mean 30-day rolling return
returns.median_monthly_30d # Median 30-day rolling return
returns.mean_annual_365d # Mean 365-day rolling return
returns.median_annual_365d # Median 365-day rolling return
returns.calendar_returns # dict[int, float] -- total gains per year
returns.calendar_returns_risk_adjusted # dict[int, float] -- gains / estimated risk per year
RatioMetrics¶
ratios = result.ratios
ratios.mean_daily_over_estimated_risk # Mean daily / estimated risk
ratios.median_daily_over_estimated_risk # Median daily / estimated risk
ratios.mean_monthly_over_estimated_risk # Mean monthly / estimated risk
ratios.median_monthly_over_estimated_risk # Median monthly / estimated risk
ratios.mean_annual_over_estimated_risk # Mean annual / estimated risk
ratios.median_annual_over_estimated_risk # Median annual / estimated risk
ratios.sharpe_overall # Overall Sharpe ratio
ratios.sortino_overall # Overall Sortino ratio
ratios.sharpe_by_year # dict[int, float]
ratios.sortino_by_year # dict[int, float]
ratios.mean_annual_calendar_over_estimated_risk # Mean calendar annual / risk
ratios.median_annual_calendar_over_estimated_risk # Median calendar annual / risk
DrawdownMetrics¶
dd = result.drawdowns
dd.worst_daily_loss # Worst single-day loss
dd.worst_daily_loss_date # Date of worst daily loss
dd.worst_7d_loss # Worst 7-day rolling loss
dd.worst_7d_loss_start_date # Start date of worst 7-day period
dd.worst_7d_loss_end_date # End date of worst 7-day period
dd.worst_1m_30d_loss # Worst 30-day loss
dd.worst_3m_90d_loss # Worst 90-day loss
dd.worst_6m_180d_loss # Worst 180-day loss
dd.worst_12m_365d_loss # Worst 365-day loss
dd.max_drawdown # Worst any-period loss (peak-to-trough)
# Each period also has start_date, end_date, and /estimated_risk variants
StreakMetrics¶
streaks = result.streaks
streaks.worst_any_period_start_date # Start of worst any-period loss
streaks.worst_any_period_end_date # End of worst any-period loss
streaks.worst_any_period_length_days # Length in days
streaks.longest_loss_period_start_date # Start of longest consecutive loss
streaks.longest_loss_period_end_date # End of longest consecutive loss
streaks.longest_loss_period_length_days # Length in days
streaks.longest_period_loss # Total loss during longest loss period
streaks.win_rate_pct # Percentage of profitable days
streaks.pct_1m_periods_with_loss # % of 1-month periods with net loss
streaks.pct_3m_periods_with_loss # % of 3-month periods with net loss
streaks.pct_6m_periods_with_loss # % of 6-month periods with net loss
streaks.pct_12m_periods_with_loss # % of 12-month periods with net loss
Complete Metrics Reference¶
Risk Metrics (7)¶
| Metric | Field | Description |
|---|---|---|
| Estimated Risk | estimated_risk |
Estimated daily risk |
| CVaR 1% | cvar_1 |
Conditional Value at Risk at 1% |
| CVaR 5% | cvar_5 |
Conditional Value at Risk at 5% |
| VaR 99% | var_99 |
Value at Risk at 99th percentile |
| VaR 95% | var_95 |
Value at Risk at 95th percentile |
| VaR 90% | var_90 |
Value at Risk at 90th percentile |
| Capital Required | capital_required |
ISO-specific capital requirement |
Return Metrics (8+)¶
| Metric | Field | Description |
|---|---|---|
| Mean Daily Return | mean_daily |
Average daily gain |
| Median Daily Return | median_daily |
Median daily gain |
| Mean Monthly Return | mean_monthly_30d |
Average 30-day rolling sum |
| Median Monthly Return | median_monthly_30d |
Median 30-day rolling sum |
| Mean Annual Return | mean_annual_365d |
Average 365-day rolling sum |
| Median Annual Return | median_annual_365d |
Median 365-day rolling sum |
| Calendar Returns | calendar_returns |
Total gains per calendar year |
| Calendar Returns (Risk-Adj) | calendar_returns_risk_adjusted |
Gains / estimated risk per year |
Ratio Metrics (12+)¶
| Metric | Field | Description |
|---|---|---|
| Mean Daily / Risk | mean_daily_over_estimated_risk |
Daily return normalized by risk |
| Median Daily / Risk | median_daily_over_estimated_risk |
Median daily / risk |
| Mean Monthly / Risk | mean_monthly_over_estimated_risk |
Monthly return / risk |
| Median Monthly / Risk | median_monthly_over_estimated_risk |
Median monthly / risk |
| Mean Annual / Risk | mean_annual_over_estimated_risk |
Annual return / risk |
| Median Annual / Risk | median_annual_over_estimated_risk |
Median annual / risk |
| Sharpe Overall | sharpe_overall |
Overall Sharpe ratio |
| Sortino Overall | sortino_overall |
Overall Sortino ratio |
| Sharpe by Year | sharpe_by_year |
Sharpe ratio per calendar year |
| Sortino by Year | sortino_by_year |
Sortino ratio per calendar year |
| Mean Calendar Annual / Risk | mean_annual_calendar_over_estimated_risk |
Mean of calendar-year returns / risk |
| Median Calendar Annual / Risk | median_annual_calendar_over_estimated_risk |
Median of calendar-year returns / risk |
Drawdown Metrics (22)¶
| Metric | Field | Description |
|---|---|---|
| Worst Daily Loss | worst_daily_loss |
Single worst day |
| Worst Daily Loss Date | worst_daily_loss_date |
Date of worst day |
| Worst 7d Loss | worst_7d_loss |
Worst rolling 7-day sum |
| Worst 1m Loss (30d) | worst_1m_30d_loss |
Worst rolling 30-day sum |
| Worst 3m Loss (90d) | worst_3m_90d_loss |
Worst rolling 90-day sum |
| Worst 6m Loss (180d) | worst_6m_180d_loss |
Worst rolling 180-day sum |
| Worst 12m Loss (365d) | worst_12m_365d_loss |
Worst rolling 365-day sum |
| Max Drawdown | max_drawdown |
Worst peak-to-trough decline |
| Each period also includes | *_start_date, *_end_date |
Start and end dates |
| Risk-adjusted variants | *_over_estimated_risk |
Loss divided by estimated risk |
Streak Metrics (12)¶
| Metric | Field | Description |
|---|---|---|
| Worst Any Period Start | worst_any_period_start_date |
Start of worst drawdown period |
| Worst Any Period End | worst_any_period_end_date |
End of worst drawdown period |
| Worst Any Period Length | worst_any_period_length_days |
Duration in days |
| Longest Loss Period Start | longest_loss_period_start_date |
Start of longest consecutive loss |
| Longest Loss Period End | longest_loss_period_end_date |
End of longest consecutive loss |
| Longest Loss Period Length | longest_loss_period_length_days |
Duration in days |
| Longest Period Loss | longest_period_loss |
Total loss during longest loss period |
| Win Rate | win_rate_pct |
Percentage of profitable days |
| % 1m Periods with Loss | pct_1m_periods_with_loss |
Fraction of 30-day windows with net loss |
| % 3m Periods with Loss | pct_3m_periods_with_loss |
Fraction of 90-day windows with net loss |
| % 6m Periods with Loss | pct_6m_periods_with_loss |
Fraction of 180-day windows with net loss |
| % 12m Periods with Loss | pct_12m_periods_with_loss |
Fraction of 365-day windows with net loss |
Visualization Access¶
The engine provides a plots property that returns a MetricsPlots instance for individual Plotly charts:
plots = engine.plots
fig = plots.performance_chart() # 3-pane cumulative/drawdown/daily
fig = plots.gains_histogram("daily") # Distribution histogram
fig = plots.cumulative_gains_by_year() # Yearly overlay
fig = plots.daily_gains_bar_chart(30) # Last N days bar chart
fig = plots.daily_gains_heatmap(2026) # GitHub-style heatmap
fig = plots.quantile_box_plots() # Period distribution box plots
fig.show() # Opens in browser
See Dashboards for the full Streamlit dashboard and HTML export.