---
title: "Portfolio Management"
description: "Portfolio construction, optimization (Kelly, risk parity, min variance), rebalancing, and Monte Carlo risk metrics."
icon: "briefcase"
---

# Portfolio Management

The `Portfolio` class provides position tracking, weight optimization, risk analytics, and rebalancing for prediction market portfolios. All heavy computation (Monte Carlo, Greeks, Ledoit-Wolf) runs in Rust.

<CardGroup cols={2}>
  <Card title="4 Optimizers" icon="scale-balanced">
    Kelly, equal-weight, risk parity, and minimum variance allocation.
  </Card>
  <Card title="Monte Carlo Risk" icon="dice">
    VaR, CVaR, win probability, and Greeks via Rust simulation engine.
  </Card>
  <Card title="Auto-Rebalance" icon="arrows-rotate">
    Generate rebalancing orders to move from current to target weights.
  </Card>
  <Card title="Live Engine Sync" icon="bolt">
    Build a Portfolio from a running Engine with `Portfolio.from_engine()`.
  </Card>
</CardGroup>

---

## Quick Start

```python
import horizon as hz

# Create portfolio
portfolio = hz.Portfolio(name="my_portfolio", capital=10000.0)

# Add positions
portfolio.add_position("trump-win", "yes", 200.0, 0.45, 0.55)
portfolio.add_position("fed-cut", "yes", 100.0, 0.60, 0.65)
portfolio.add_position("btc-100k", "no", 150.0, 0.30, 0.25)

# Check current state
print(portfolio.weights())       # {"trump-win": 0.63, "fed-cut": 0.22, "btc-100k": 0.14}
print(portfolio.pnl())           # Total unrealised PnL
print(portfolio.pnl_pct())       # PnL as percentage of cost basis
print(portfolio.concentration()) # HHI concentration index
print(portfolio.summary())       # Human-readable summary
```

---

## Constructor

```python
portfolio = hz.Portfolio(
    name="my_portfolio",   # Portfolio name (default: "portfolio")
    capital=10000.0,       # Total capital budget (default: 10000.0)
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | `"portfolio"` | Portfolio identifier |
| `capital` | `float` | `10000.0` | Total capital for weight calculations |

---

## Managing Positions

### add_position

Add or replace a position in the portfolio.

```python
portfolio.add_position(
    market_id="trump-win",   # Market identifier
    side="yes",              # "yes" or "no"
    size=200.0,              # Position size in contracts
    entry_price=0.45,        # Average entry price
    current_price=0.55,      # Current market probability
)
```

### update_price

Update the current market price for an existing position.

```python
portfolio.update_price("trump-win", 0.58)
```

### remove_position

Remove a position from the portfolio.

```python
portfolio.remove_position("trump-win")
```

### from_engine

Build a Portfolio from a running Engine's live positions and feed data.

```python
engine = hz.Engine(risk_config=hz.RiskConfig(max_position_per_market=500))
# ... trade for a while ...

portfolio = hz.Portfolio.from_engine(engine, capital=10000.0)
print(portfolio.summary())
```

---

## Portfolio Analytics

### weights

Current weight per market (position value / total portfolio value).

```python
w = portfolio.weights()
# {"trump-win": 0.63, "fed-cut": 0.22, "btc-100k": 0.14}
```

### pnl / pnl_pct

```python
print(portfolio.pnl())      # Total unrealised PnL in dollars
print(portfolio.pnl_pct())  # PnL as percentage of cost basis
```

### concentration

Herfindahl-Hirschman Index (HHI): sum of squared weights. Lower is more diversified. `1/N` = perfectly equal.

```python
hhi = portfolio.concentration()
# 0.33 for 3 equal positions, 1.0 for single position
```

### summary

Human-readable portfolio overview.

```python
print(portfolio.summary())
# Portfolio 'my_portfolio' (3 positions)
# Capital: $10,000.00 | PnL: $25.00 (16.7%)
# ...
```

---

## Weight Optimization

All optimizers return target weights as `dict[str, float]` mapping market IDs to target allocations.

### Kelly Criterion

Compute Kelly-optimal weights given your probability estimates.

```python
kelly_weights = portfolio.optimize_kelly(
    probs={
        "trump-win": 0.60,   # Your estimated probability
        "fed-cut": 0.70,
        "btc-100k": 0.20,   # Probability of YES (you hold NO)
    },
    max_total=1.0,  # Cap total Kelly allocation (default: 1.0)
)
# {"trump-win": 0.37, "fed-cut": 0.28, "btc-100k": 0.13}
```

### Equal Weight

Simple 1/N allocation across all positions.

```python
equal_weights = portfolio.optimize_equal_weight()
# {"trump-win": 0.33, "fed-cut": 0.33, "btc-100k": 0.33}
```

### Risk Parity

Inverse-volatility weighting. Uses Ledoit-Wolf shrinkage covariance if return history is provided.

```python
# Without history (falls back to equal weight)
rp_weights = portfolio.optimize_risk_parity()

# With return history (T x N matrix)
returns_history = [
    [0.01, -0.02, 0.03],   # time step 1: 3 assets
    [0.02, -0.01, 0.04],   # time step 2
    [-0.01, 0.03, -0.02],  # time step 3
    # ... more observations
]
rp_weights = portfolio.optimize_risk_parity(returns_history)
```

### Minimum Variance

Minimum variance allocation using Ledoit-Wolf covariance estimation.

```python
returns_history = [
    [0.01, -0.02, 0.03],
    [0.02, -0.01, 0.04],
    [-0.01, 0.03, -0.02],
]
mv_weights = portfolio.optimize_min_variance(returns_history)
```

---

## Rebalancing

### needs_rebalance

Check whether any position deviates from target weights by more than a threshold.

```python
target = {"trump-win": 0.33, "fed-cut": 0.33, "btc-100k": 0.33}
if portfolio.needs_rebalance(target, threshold=0.05):
    print("Portfolio drift exceeds 5%")
```

### rebalance_orders

Generate the orders needed to move from current to target weights.

```python
target = portfolio.optimize_equal_weight()
orders = portfolio.rebalance_orders(target)

for order in orders:
    print(f"{order['action']} {order['size_delta']:.1f} of {order['market_id']}")
    print(f"  Current weight: {order['current_weight']:.2%}")
    print(f"  Target weight:  {order['target_weight']:.2%}")
```

Each order dict contains:

| Key | Type | Description |
|-----|------|-------------|
| `market_id` | `str` | Market identifier |
| `side` | `str` | `"yes"` or `"no"` |
| `action` | `str` | `"buy"` or `"sell"` |
| `size_delta` | `float` | Size to trade |
| `current_weight` | `float` | Current portfolio weight |
| `target_weight` | `float` | Target portfolio weight |

---

## Risk Metrics

### metrics

Compute portfolio risk metrics including Monte Carlo simulation.

```python
m = portfolio.metrics(
    n_simulations=10000,  # Monte Carlo scenarios
    seed=42,              # Reproducible results
    t_hours=24.0,         # Hours to expiry for Greeks
    vol=0.2,              # Implied vol for Greeks
)
```

Returns a `PortfolioMetrics` object:

| Field | Type | Description |
|-------|------|-------------|
| `num_positions` | `int` | Number of positions |
| `total_value` | `float` | Total portfolio value |
| `total_pnl` | `float` | Total unrealised PnL |
| `total_pnl_pct` | `float` | PnL as percentage |
| `concentration` | `float` | HHI index |
| `var_95` | `float` | 95% Value at Risk (Monte Carlo) |
| `cvar_95` | `float` | 95% Conditional VaR |
| `win_probability` | `float` | Probability of positive PnL |
| `greeks` | `dict[str, PredictionGreeks]` | Per-position Greeks (delta, gamma, theta, vega) |
| `correlation_matrix` | `list[list[float]] | None` | Ledoit-Wolf covariance if available |
| `shrinkage_intensity` | `float` | Shrinkage parameter |

```python
m = portfolio.metrics(seed=42)
print(f"VaR 95:   ${m.var_95:.2f}")
print(f"CVaR 95:  ${m.cvar_95:.2f}")
print(f"Win Prob: {m.win_probability:.1%}")
print(f"HHI:      {m.concentration:.3f}")

# Per-position Greeks
for market_id, g in m.greeks.items():
    print(f"{market_id}: delta={g.delta:.2f}, gamma={g.gamma:.2f}")
```

---

## Full Workflow Example

```python
import horizon as hz

# 1. Build portfolio from live engine
engine = hz.Engine(
    exchange=hz.Paper(),
    risk_config=hz.RiskConfig(max_position_per_market=500),
)
# ... run strategy, accumulate positions ...

portfolio = hz.Portfolio.from_engine(engine, capital=10000.0)

# 2. Analyze risk
metrics = portfolio.metrics(n_simulations=50000, seed=42)
print(f"Portfolio VaR 95: ${metrics.var_95:.2f}")
print(f"Win probability:  {metrics.win_probability:.1%}")

# 3. Optimize weights
kelly_targets = portfolio.optimize_kelly(
    probs={"trump-win": 0.60, "fed-cut": 0.70},
)

# 4. Check if rebalancing is needed
if portfolio.needs_rebalance(kelly_targets, threshold=0.05):
    orders = portfolio.rebalance_orders(kelly_targets)
    for o in orders:
        print(f"{o['action']} {o['size_delta']:.1f} {o['market_id']}")
```
