К основному содержанию
скилл CX Codex

portfolio-analytics — скилл для Claude Code: метрики портфеля и отчёты

Считает доходность, риск и коэффициенты портфеля по equity curve и логу сделок. Пригодится после бэктеста, когда нужно сравнить стратегии или собрать отчёт.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add agiprolabs/claude-trading-skills --skill portfolio-analytics -a claude-code
Открыть на GitHub

Что делает portfolio-analytics

Скилл работает с equity curve — временным рядом стоимости портфеля. Из него считаются total return, CAGR, дневная доходность, волатильность, VaR и CVaR, максимальная просадка и время под водой. Дальше идут коэффициенты: Sharpe, Sortino, Calmar, Omega и Information Ratio. Для каждого есть готовая функция на pandas и numpy, так что писать формулы с нуля не придётся.

Есть скользящие окна — rolling Sharpe и rolling max drawdown, а также статистика по отдельным сделкам: win rate, средний выигрыш и проигрыш. Отдельно описано сравнение с бенчмарком и сборка HTML-отчёта через quantstats. Внутри — код на Python, а не набор промптов.

Перед первым запуском поставьте зависимости: uv pip install pandas numpy quantstats. Без quantstats HTML-отчёт не соберётся, а остальные метрики работают на pandas и numpy. Скилл рассчитан на данные бэктеста, поэтому пустая или короткая equity curve даст бессмысленные коэффициенты — проверьте, что ряд длиннее выбранного окна.

Какие задачи решает

  • посчитать доходность и риск портфеля по equity curve
  • сравнить несколько стратегий по коэффициентам Sharpe и Sortino
  • оценить максимальную просадку и время под водой
  • собрать HTML-отчёт о результатах через quantstats
  • посчитать win rate и средний результат по логу сделок

Примеры запросов агенту

  • Посчитай метрики портфеля по этой equity curve: total return, CAGR, волатильность и максимальную просадку
  • Сравни две стратегии по Sharpe, Sortino и Calmar и покажи, какая ровнее
  • Собери HTML-отчёт по бэктесту через quantstats

Как установить portfolio-analytics

1Через Skills CLI (Vercel)

npx skills add agiprolabs/claude-trading-skills --skill portfolio-analytics -a claude-code

2Вручную: положить файлы

~/.claude/skills/portfolio-analytics/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/portfolio-analytics/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о portfolio-analytics

Как установить portfolio-analytics в Claude?

Выполните npx skills add agiprolabs/claude-trading-skills --skill portfolio-analytics -a claude-code. Скилл появится в ~/.claude/skills/portfolio-analytics/SKILL.md.

Что делает portfolio-analytics?

Считает метрики портфеля по equity curve и логу сделок: доходность, волатильность, VaR, просадки, коэффициенты Sharpe, Sortino, Calmar, Omega и Information Ratio. Плюс скользящий анализ и HTML-отчёт через quantstats.

Что нужно поставить перед работой?

Зависимости: uv pip install pandas numpy quantstats. Без quantstats не получится собрать HTML-отчёт, остальные метрики считаются на pandas и numpy.

Исходный текст

SKILL.md · MIT
---
name: portfolio-analytics
description: Portfolio-level performance measurement including return metrics, risk metrics, risk-adjusted ratios, rolling analysis, and HTML reports
---

# Portfolio Analytics

Compute portfolio-level performance metrics from equity curves and trade logs. Covers return metrics, risk metrics, risk-adjusted ratios, drawdown analysis, rolling windows, benchmark comparison, trade-level statistics, and automated HTML report generation via quantstats.

## When to Use This Skill

- After backtesting a strategy (e.g., from `vectorbt` or `strategy-framework`)
- Comparing multiple strategies or parameter sets side-by-side
- Generating investor-ready performance reports
- Evaluating live trading performance against benchmarks
- Assessing risk-adjusted returns for portfolio allocation decisions

## Prerequisites

```bash
uv pip install pandas numpy quantstats
```

## Input Format

All analytics start from an **equity curve** — a time-indexed Series of portfolio values:

```python
import pandas as pd
import numpy as np

# From a backtest
equity = pd.Series(
    [10000, 10150, 10080, 10320, 10510, 10440, 10680],
    index=pd.date_range("2025-01-01", periods=7, freq="D"),
    name="strategy_equity"
)

# Convert to returns
returns = equity.pct_change().dropna()
```

## Return Metrics

### Total Return

```python
total_return = (equity.iloc[-1] / equity.iloc[0]) - 1
```

### CAGR (Compound Annual Growth Rate)

```python
days = (equity.index[-1] - equity.index[0]).days
cagr = (equity.iloc[-1] / equity.iloc[0]) ** (365.25 / days) - 1
```

### Daily Mean Return

```python
daily_mean = returns.mean()
annualized_mean = daily_mean * 252  # trading days
```

### Cumulative Returns

```python
cumulative = (1 + returns).cumprod() - 1
```

## Risk Metrics

### Annualized Volatility

```python
daily_vol = returns.std()
annual_vol = daily_vol * np.sqrt(252)
```

### Value at Risk (VaR)

Historical VaR at a given confidence level:

```python
def historical_var(returns: pd.Series, confidence: float = 0.95) -> float:
    """Compute historical VaR.

    Args:
        returns: Daily return series.
        confidence: Confidence level (e.g., 0.95 for 95%).

    Returns:
        VaR as a positive number representing potential loss.
    """
    return -np.percentile(returns, (1 - confidence) * 100)
```

### Conditional VaR (CVaR / Expected Shortfall)

```python
def historical_cvar(returns: pd.Series, confidence: float = 0.95) -> float:
    """Mean of returns below the VaR threshold."""
    var = historical_var(returns, confidence)
    return -returns[returns <= -var].mean()
```

### Maximum Drawdown

```python
def max_drawdown(equity: pd.Series) -> float:
    """Maximum peak-to-trough decline."""
    peak = equity.cummax()
    drawdown = (equity - peak) / peak
    return drawdown.min()  # negative number

def drawdown_series(equity: pd.Series) -> pd.Series:
    """Full drawdown time series."""
    peak = equity.cummax()
    return (equity - peak) / peak
```

### Time Underwater

```python
def time_underwater(equity: pd.Series) -> int:
    """Longest consecutive period below previous peak (in days)."""
    dd = drawdown_series(equity)
    is_underwater = dd < 0
    groups = (~is_underwater).cumsum()
    underwater_periods = is_underwater.groupby(groups).sum()
    return int(underwater_periods.max()) if len(underwater_periods) > 0 else 0
```

## Risk-Adjusted Ratios

### Sharpe Ratio

```python
def sharpe_ratio(
    returns: pd.Series,
    rf: float = 0.0,
    periods_per_year: int = 252
) -> float:
    """Annualized Sharpe ratio.

    Args:
        returns: Period returns.
        rf: Risk-free rate per period.
        periods_per_year: Annualization factor.

    Returns:
        Annualized Sharpe ratio.
    """
    excess = returns - rf
    if excess.std() == 0:
        return 0.0
    return (excess.mean() / excess.std()) * np.sqrt(periods_per_year)
```

### Sortino Ratio

```python
def sortino_ratio(
    returns: pd.Series,
    rf: float = 0.0,
    periods_per_year: int = 252
) -> float:
    """Annualized Sortino ratio (penalizes only downside vol)."""
    excess = returns - rf
    downside = excess[excess < 0]
    if len(downside) == 0 or downside.std() == 0:
        return float("inf") if excess.mean() > 0 else 0.0
    return (excess.mean() / downside.std()) * np.sqrt(periods_per_year)
```

### Calmar Ratio

```python
def calmar_ratio(equity: pd.Series, periods_per_year: int = 252) -> float:
    """CAGR divided by max drawdown (absolute value)."""
    returns = equity.pct_change().dropna()
    days = (equity.index[-1] - equity.index[0]).days
    cagr = (equity.iloc[-1] / equity.iloc[0]) ** (365.25 / days) - 1
    mdd = abs(max_drawdown(equity))
    if mdd == 0:
        return float("inf") if cagr > 0 else 0.0
    return cagr / mdd
```

### Omega Ratio

```python
def omega_ratio(
    returns: pd.Series,
    threshold: float = 0.0
) -> float:
    """Ratio of probability-weighted gains to losses."""
    excess = returns - threshold
    gains = excess[excess > 0].sum()
    losses = abs(excess[excess <= 0].sum())
    if losses == 0:
        return float("inf") if gains > 0 else 1.0
    return gains / losses
```

### Information Ratio

```python
def information_ratio(
    returns: pd.Series,
    benchmark_returns: pd.Series,
    periods_per_year: int = 252
) -> float:
    """Excess return per unit of tracking error."""
    active = returns - benchmark_returns
    if active.std() == 0:
        return 0.0
    return (active.mean() / active.std()) * np.sqrt(periods_per_year)
```

## Rolling Analysis

### Rolling Sharpe

```python
def rolling_sharpe(
    returns: pd.Series,
    window: int = 63,
    rf: float = 0.0,
    periods_per_year: int = 252
) -> pd.Series:
    """Rolling annualized Sharpe ratio."""
    excess = returns - rf
    roll_mean = excess.rolling(window).mean()
    roll_std = excess.rolling(window).std()
    return (roll_mean / roll_std) * np.sqrt(periods_per_year)
```

### Rolling Max Drawdown

```python
def rolling_max_drawdown(equity: pd.Series, window: int = 252) -> pd.Series:
    """Rolling max drawdown over a fixed window."""
    result = pd.Series(index=equity.index, dtype=float)
    for i in range(window, len(equity)):
        window_eq = equity.iloc[i - window:i + 1]
        peak = window_eq.cummax()
        dd = (window_eq - peak) / peak
        result.iloc[i] = dd.min()
    return result
```

## Trade-Level Analysis

When you have individual trade records:

```python
def trade_statistics(pnl: pd.Series) -> dict:
    """Compute trade-level statistics from a series of trade PnL values.

    Args:
        pnl: Series where each value is the PnL of one trade.

    Returns:
        Dictionary of trade statistics.
    """
    wins = pnl[pnl > 0]
    losses = pnl[pnl < 0]
    total = len(pnl)

    win_rate = len(wins) / total if total > 0 else 0.0
    avg_win = wins.mean() if len(wins) > 0 else 0.0
    avg_loss = losses.mean() if len(losses) > 0 else 0.0
    largest_win = wins.max() if len(wins) > 0 else 0.0
    largest_loss = losses.min() if len(losses) > 0 else 0.0

    gross_profit = wins.sum() if len(wins) > 0 else 0.0
    gross_loss = abs(losses.sum()) if len(losses) > 0 else 0.0
    profit_factor = gross_profit / gross_loss if gross_loss > 0 else float("inf")

    expectancy = pnl.mean() if total > 0 else 0.0

    return {
        "total_trades": total,
        "win_rate": win_rate,
        "avg_win": avg_win,
        "avg_loss": avg_loss,
        "largest_win": largest_win,
        "largest_loss": largest_loss,
        "profit_factor": profit_factor,
        "expectancy": expectancy,
        "gross_profit": gross_profit,
        "gross_loss": gross_loss,
    }
```

## Monthly / Yearly Return Tables

```python
def monthly_returns_table(returns: pd.Series) -> pd.DataFrame:
    """Pivot returns into a month-by-year table.

    Returns:
        DataFrame with years as rows, months (1-12) as columns,
        and an Annual column.
    """
    monthly = returns.resample("ME").apply(lambda x: (1 + x).prod() - 1)
    table = monthly.groupby([monthly.index.year, monthly.index.month]).first()
    table = table.unstack(level=1)
    table.columns = [
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    ]
    # Annual column
    annual = returns.resample("YE").apply(lambda x: (1 + x).prod() - 1)
    table["Annual"] = annual.values[:len(table)]
    return table
```

## Benchmark Comparison

```python
def benchmark_comparison(
    strategy_returns: pd.Series,
    benchmark_returns: pd.Series,
    rf: float = 0.0
) -> dict:
    """Compare strategy to benchmark across key metrics."""
    strat_eq = (1 + strategy_returns).cumprod()
    bench_eq = (1 + benchmark_returns).cumprod()

    return {
        "strategy_total_return": strat_eq.iloc[-1] - 1,
        "benchmark_total_return": bench_eq.iloc[-1] - 1,
        "strategy_sharpe": sharpe_ratio(strategy_returns, rf),
        "benchmark_sharpe": sharpe_ratio(benchmark_returns, rf),
        "strategy_max_dd": max_drawdown(strat_eq),
        "benchmark_max_dd": max_drawdown(bench_eq),
        "information_ratio": information_ratio(strategy_returns, benchmark_returns),
        "correlation": strategy_returns.corr(benchmark_returns),
        "beta": (
            strategy_returns.cov(benchmark_returns)
            / benchmark_returns.var()
        ),
        "alpha": (
            strategy_returns.mean()
            - (strategy_returns.cov(benchmark_returns) / benchmark_returns.var())
            * benchmark_returns.mean()
        ) * 252,
    }
```

## Quantstats HTML Reports

Generate investor-ready HTML reports with one function call:

```python
import quantstats as qs

# From returns Series
qs.reports.html(
    returns,
    benchmark=benchmark_returns,  # optional
    output="report.html",
    title="My Strategy",
    rf=0.0,
    periods_per_year=252
)

# Individual metrics
print(f"Sharpe: {qs.stats.sharpe(returns):.2f}")
print(f"Sortino: {qs.stats.sortino(returns):.2f}")
print(f"Max DD: {qs.stats.max_drawdown(returns):.2%}")
print(f"Calmar: {qs.stats.calmar(returns):.2f}")

# Console tearsheet
qs.reports.full(returns)
```

See `references/quantstats_guide.md` for full API reference and customization.

## Integration with Vectorbt

```python
import vectorbt as vbt

# After running a vectorbt backtest
portfolio = vbt.Portfolio.from_signals(close, entries, exits, init_cash=10000)

# Extract equity curve
equity = portfolio.value()
returns = portfolio.returns()

# Use quantstats
qs.reports.html(returns, output="backtest_report.html")
```

## Files

| File | Description |
|------|-------------|
| `references/metrics_guide.md` | Formulas, derivations, annualization factors, interpretation benchmarks |
| `references/quantstats_guide.md` | Quantstats library API, customization, integration patterns |
| `scripts/analyze_portfolio.py` | Single portfolio analysis with all metrics, rolling stats, monthly table |
| `scripts/compare_strategies.py` | Multi-strategy comparison with ranking by risk-adjusted metrics |

## Related Skills

- `vectorbt` — Backtesting engine that produces equity curves for analysis
- `risk-management` — Portfolio-level risk guardrails and allocation
- `position-sizing` — Optimal position sizing using portfolio metrics
- `kelly-criterion` — Optimal growth rate sizing from win rate and payoff
- `trading-visualization` — Chart generation for equity curves and drawdowns

© agiprolabs · MIT · agiprolabs/claude-trading-skills

Похожие скиллы

Всё по теме «Финансы и бизнес» →
analytics-data-analysis Mindrally Задаёт агенту правила работы с pandas, matplotlib и seaborn: как чистить датасет, строить графики и оформлять Jupyter-ноутбук. CX Codex +1 1 100установок financial-analysis-agent qodex-ai Помогает создавать ИИ-агентов для фундаментальной оценки акций и расчёта рыночных рисков. Пригодится при разработке робо-эдвайзеров и систем трекинга инвестиций. CX Codex +1 1 300установок gmgn-portfolio GMGNAI Скилл анализирует крипто-кошельки по адресу через API GMGN: показывает холдинги, реализованный и нереализованный P&L, win rate, историю сделок и токены, которые создал разработчик. Работает на Solana, BSC, Base и Ethereum. CX Codex +1 17 600установок fixed-income-portfolio официальный anthropics Скилл для анализа портфелей облигаций: цены, дюрация, DV01, сценарии ставок и кэшфлоу-прогнозы. Агрегирует данные из MCP-инструментов в отчёт с метриками риска. CX Codex +1 1 800установок trader-portfolio-cg ruvnet Решает задачу mean-variance через сопряжённые градиенты вместо ряда Неймана. Нужен, когда пересчитываете веса портфеля часто и упираетесь в задержку. CX Codex +1 513установок

Из того же набора52

ta-lib Скилл помогает рассчитывать более 150 технических индикаторов и распознавать 61 свечной паттерн через C-библиотеку TA-Lib. Пригодится для быстрых расчётов на больших массивах биржевых данных. CX Codex +1 493установок vectorbt Запускает векторизованный бэктестинг торговых стратегий через NumPy и pandas. Помогает тестировать сетки параметров без медленных циклов по свечам. CX Codex +1 492установок whale-tracking Скилл отслеживает поведение крупных кошельков в токенах Solana: накопление, распределение и сигналы от смарт-мани до того, как это отразится на цене. CX Codex +1 493установок mean-reversion Набор инструментов для проверки гипотезы возврата к среднему во временных рядах. Помогает рассчитывать z-score, период полураспада и параметры процесса Орнштейна — Уленбека. CX Codex +1 490установок sentiment-analysis Скилл собирает и оценивает настроение рынка по соцсетям, новостям и on-chain данным. Пригодится, когда нужно понять, что делает толпа, до входа в позицию. CX Codex +1 476установок market-microstructure-traditional Скилл обучает агента моделям микроструктуры рынка: расчёту спредов, оценке импакта заявок, дисбалансу книги ордеров и процессам поступления сделок. CX Codex +1 474установок slippage-modeling Считает стоимость исполнения сделки на AMM: проскальзывание, комиссии DEX, приоритетные сборы и риск сэндвича. Пригодится, когда нужно понять, сколько съест своп и где порог безубытка. CX Codex +1 474установок ohlcv-processing Скилл для подготовки рыночных данных: проверяет свечи, заполняет пропуски, помечает аномалии и приводит колонки к единому виду. CX Codex +1 472установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой