---
title: "Vine Copulas"
description: "High-dimensional dependence modeling with C-vine and D-vine copulas for multi-market tail risk analysis."
icon: "diagram-nested"
---

<Warning>
  **Ultra Feature.** Requires an Ultra subscription. [Get started at api.mathematicalcompany.com](https://api.mathematicalcompany.com)
</Warning>

<Tip>
  **What is this?** When you need to model dependence across three or more markets simultaneously, vine copulas decompose the problem into a tree of pairwise copulas. This lets you capture complex multi-market dependencies that simple correlation matrices miss. Use it for portfolio-level tail risk analysis and multi-market stress testing.
</Tip>

# Vine Copulas

Vine copulas extend bivariate copulas to model dependence among three or more markets. Where a single bivariate copula captures the relationship between two markets, a vine copula decomposes a high-dimensional dependence structure into a cascade of bivariate copulas arranged in a tree structure. Horizon implements both C-vine (canonical) and D-vine (drawable) constructions. All computation runs in Rust via PyO3.

<CardGroup cols={2}>
  <Card title="C-Vine" icon="star">
    Star-shaped tree with a central node. Best when one market dominates the dependence structure.
  </Card>
  <Card title="D-Vine" icon="timeline">
    Path-shaped tree. Best when markets form a sequential dependence chain.
  </Card>
  <Card title="Tail Risk" icon="triangle-exclamation">
    Compute joint tail probabilities across many markets simultaneously.
  </Card>
  <Card title="Pipeline Integration" icon="diagram-project">
    `hz.vine_risk_monitor()` tracks multi-market tail risk in real time within `hz.run()`.
  </Card>
</CardGroup>

---

## Why Vine Copulas?

Bivariate copulas work well for pairs of markets, but portfolio risk depends on the joint behavior of many markets simultaneously. The challenge is that most multivariate copula families (e.g., multivariate Gaussian, multivariate t) impose a single dependence structure across all pairs. Vine copulas solve this by:

1. **Pair-by-pair flexibility**: each pair of markets can have its own copula family (Clayton for one pair, Gumbel for another)
2. **Tail dependence heterogeneity**: some pairs can exhibit lower-tail dependence while others exhibit upper-tail dependence
3. **Scalability**: the vine structure decomposes an n-dimensional problem into n*(n-1)/2 bivariate problems

<Note>
  Vine copulas build on the bivariate copula module. See the [Copula Dependence](/docs/copula-dependence) page for details on the underlying copula families (Gaussian, Clayton, Gumbel, Frank).
</Note>

---

## API

### VineType Enum

```python
import horizon as hz

hz.VineType.CVine   # Canonical vine (star-shaped trees)
hz.VineType.DVine   # Drawable vine (path-shaped trees)
```

| Variant | Tree Structure | Best For |
|---------|---------------|----------|
| `CVine` | Star-shaped: one central node per tree level | Markets dominated by a single driver (e.g., BTC drives alts) |
| `DVine` | Path-shaped: sequential chain per tree level | Markets with sequential dependence (e.g., election timeline) |

### hz.fit_vine

Fit a vine copula to multivariate uniform data. The function selects the best bivariate copula family for each pair from Gaussian, Clayton, Gumbel, and Frank.

```python
import horizon as hz

# Prepare uniform marginals for 4 markets
# Each inner list is one market's pseudo-uniform observations
data = [
    [0.1, 0.3, 0.5, 0.7, 0.9, 0.2, 0.4, 0.6, 0.8, 0.15],  # market A
    [0.2, 0.4, 0.6, 0.5, 0.8, 0.3, 0.5, 0.7, 0.9, 0.25],  # market B
    [0.9, 0.7, 0.5, 0.3, 0.1, 0.8, 0.6, 0.4, 0.2, 0.85],  # market C
    [0.15, 0.35, 0.55, 0.65, 0.85, 0.25, 0.45, 0.6, 0.75, 0.2],  # market D
]

result = hz.fit_vine(data, vine_type=hz.VineType.CVine)
print(f"Vine type: {result.vine_type}")
print(f"Dimensions: {result.n_dimensions}")
print(f"Log-likelihood: {result.log_likelihood:.4f}")
print(f"AIC: {result.aic:.4f}")

# Inspect pair copulas
for pc in result.pair_copulas:
    print(f"  Tree {pc.tree}, edge ({pc.var_a}, {pc.var_b}): "
          f"{pc.family} theta={pc.parameter:.3f} tau={pc.kendall_tau:.3f}")
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `list[list[float]]` | N lists of uniform marginals, each of length T. All values in (0, 1). |
| `vine_type` | `VineType` | `CVine` or `DVine` |

Returns a `VineCopulaResult`.

### VineCopulaResult Type

| Field | Type | Description |
|-------|------|-------------|
| `vine_type` | `VineType` | The vine structure used |
| `n_dimensions` | `int` | Number of variables (markets) |
| `pair_copulas` | `list[PairCopula]` | All fitted pair copulas in the vine |
| `log_likelihood` | `float` | Total log-likelihood of the vine |
| `aic` | `float` | AIC of the full vine model |
| `tree_order` | `list[int]` | Variable ordering used in the vine |

### PairCopula Type

Each pair copula in the vine decomposition.

| Field | Type | Description |
|-------|------|-------------|
| `tree` | `int` | Tree level (0-indexed; tree 0 has unconditional pairs) |
| `var_a` | `int` | First variable index |
| `var_b` | `int` | Second variable index |
| `conditioned_on` | `list[int]` | Variables conditioned on (empty for tree 0) |
| `family` | `str` | Best-fit copula family name |
| `parameter` | `float` | Fitted copula parameter |
| `kendall_tau` | `float` | Implied Kendall's tau |
| `log_likelihood` | `float` | Log-likelihood for this pair |

---

## Sampling

### hz.vine_sample

Generate multivariate correlated samples from a fitted vine copula.

```python
import horizon as hz

result = hz.fit_vine(data, vine_type=hz.VineType.CVine)

samples = hz.vine_sample(
    vine=result,
    n=10000,
    seed=42,
)
# samples is a list of N-dimensional tuples
print(f"Generated {len(samples)} samples of dimension {len(samples[0])}")

# Verify marginals are approximately uniform
import statistics
for dim in range(result.n_dimensions):
    vals = [s[dim] for s in samples]
    print(f"  Dim {dim}: mean={statistics.mean(vals):.3f} std={statistics.stdev(vals):.3f}")
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `vine` | `VineCopulaResult` | A fitted vine copula from `fit_vine()` |
| `n` | `int` | Number of samples to generate |
| `seed` | `int` | Random seed for reproducibility (optional) |

Returns `list[tuple[float, ...]]`: n samples, each of dimension `vine.n_dimensions`.

---

## Tail Risk

### hz.vine_tail_risk

Compute joint tail probabilities from a fitted vine copula. Estimates the probability that all (or a subset of) markets simultaneously fall below (or exceed) specified quantiles.

```python
import horizon as hz

result = hz.fit_vine(data, vine_type=hz.VineType.CVine)

# Probability all 4 markets are below their 10th percentile simultaneously
tail_prob = hz.vine_tail_risk(
    vine=result,
    quantile=0.10,
    direction="lower",
    n_simulations=100000,
    seed=42,
)
print(f"Joint lower tail probability (10%): {tail_prob:.6f}")
# Under independence this would be 0.10^4 = 0.0001
# With positive dependence it will be higher

# Upper tail: probability all markets exceed their 90th percentile
upper_tail = hz.vine_tail_risk(
    vine=result,
    quantile=0.90,
    direction="upper",
    n_simulations=100000,
    seed=42,
)
print(f"Joint upper tail probability (90%): {upper_tail:.6f}")
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `vine` | `VineCopulaResult` | A fitted vine copula |
| `quantile` | `float` | Quantile threshold in (0, 1) |
| `direction` | `str` | `"lower"` (all below quantile) or `"upper"` (all above quantile) |
| `n_simulations` | `int` | Monte Carlo sample count (default: 100000) |
| `seed` | `int` | Random seed (optional) |

Returns `float`: estimated joint tail probability.

<Note>
  Joint tail probabilities are estimated via Monte Carlo simulation from the fitted vine. Increase `n_simulations` for more precise estimates of rare tail events. For a 4-market joint 5% tail event, 100,000 simulations typically provide 2 significant digits of precision.
</Note>

---

## Pipeline Integration

### hz.vine_risk_monitor

Pipeline function that fits a vine copula across all monitored markets and injects tail risk metrics into `ctx.params["vine_risk"]`.

```python
import horizon as hz

def vine_risk_strategy(ctx):
    vine = ctx.params.get("vine_risk")
    if vine is None:
        return []

    mid = ctx.feed.price

    # Reduce position size when joint tail risk is elevated
    joint_tail = vine.get("joint_lower_tail_10pct", 0.0)
    independence_baseline = 0.10 ** vine.get("n_markets", 1)

    # Tail dependence ratio: how much worse than independence
    if independence_baseline > 0:
        tail_ratio = joint_tail / independence_baseline
    else:
        tail_ratio = 1.0

    # Scale down size when tail dependence is high
    base_size = 20
    if tail_ratio > 5.0:
        size = max(2, int(base_size / tail_ratio))
        spread = 0.06  # widen for crash risk
    else:
        size = base_size
        spread = 0.03

    return [
        hz.quote(ctx, hz.Side.Yes, hz.OrderSide.Buy, mid - spread, size),
        hz.quote(ctx, hz.Side.Yes, hz.OrderSide.Sell, mid + spread, size),
    ]

hz.run(
    name="vine-risk-mm",
    markets=["0xmarket_a...", "0xmarket_b...", "0xmarket_c..."],
    pipeline=[
        hz.vine_risk_monitor(refit_interval=300),  # refit every 5 minutes
        vine_risk_strategy,
    ],
    interval=1.0,
)
```

The `ctx.params["vine_risk"]` dict contains:

| Key | Type | Description |
|-----|------|-------------|
| `vine_type` | `str` | Vine structure used (`"CVine"` or `"DVine"`) |
| `n_markets` | `int` | Number of markets in the vine |
| `joint_lower_tail_10pct` | `float` | Joint probability all markets below 10th percentile |
| `joint_upper_tail_90pct` | `float` | Joint probability all markets above 90th percentile |
| `max_pairwise_tau` | `float` | Strongest pairwise Kendall's tau in the vine |
| `aic` | `float` | AIC of the fitted vine model |
| `last_refit` | `float` | Timestamp of the last vine refit |

---

## C-Vine vs D-Vine

### When to Use C-Vine

C-vine (canonical vine) uses a star-shaped tree where one central variable is connected to all others at each level. Use C-vine when:

- One market dominates the dependence structure (e.g., BTC in crypto prediction markets)
- You want to condition all other pairwise relationships on the most connected market
- The most correlated variable can be identified a priori

```python
# C-vine: BTC is the central node, all others conditioned on BTC
result = hz.fit_vine(data, vine_type=hz.VineType.CVine)
# Tree 0: (BTC, ETH), (BTC, SOL), (BTC, AVAX)
# Tree 1: (ETH, SOL | BTC), (ETH, AVAX | BTC)
# Tree 2: (SOL, AVAX | BTC, ETH)
```

### When to Use D-Vine

D-vine (drawable vine) uses a path-shaped tree where variables form a sequential chain. Use D-vine when:

- Markets have a natural ordering (e.g., sequential election primaries)
- Dependence is strongest between "neighboring" markets
- No single variable dominates the dependence structure

```python
# D-vine: sequential chain
result = hz.fit_vine(data, vine_type=hz.VineType.DVine)
# Tree 0: (A, B), (B, C), (C, D)
# Tree 1: (A, C | B), (B, D | C)
# Tree 2: (A, D | B, C)
```

---

## Mathematical Background

<AccordionGroup>
  <Accordion title="Vine Decomposition">
    Bedford and Cooke (2001) showed that any n-dimensional copula density can be decomposed into a product of n*(n-1)/2 bivariate copula densities arranged in a nested tree structure called a regular vine (R-vine).

    For n=4, the decomposition produces 6 pair copulas across 3 tree levels:
   - Tree 1: 3 unconditional pairs
   - Tree 2: 2 conditional pairs (conditioned on 1 variable)
   - Tree 3: 1 conditional pair (conditioned on 2 variables)

    C-vine and D-vine are special cases of R-vine with specific tree topologies.
  </Accordion>
  <Accordion title="Conditional Copulas">
    Beyond tree 0, pair copulas are fit to conditional distributions. The conditional CDF F(x|z) is computed using the h-function:

    `h(u|v; theta) = dC(u,v; theta) / dv`

    Each bivariate copula family has a closed-form h-function. The h-function transforms observations to conditional uniform marginals, which are then used to fit the next tree level.
  </Accordion>
  <Accordion title="Model Selection">
    At each edge of the vine, `fit_vine` evaluates all four copula families (Gaussian, Clayton, Gumbel, Frank) and selects the best by AIC. This means different edges can use different families, providing maximum flexibility. The total vine AIC is the sum of all pair copula AICs.
  </Accordion>
  <Accordion title="Tail Risk Computation">
    Joint tail probabilities are computed via Monte Carlo simulation from the fitted vine. The vine sampling algorithm (Aas et al., 2009) proceeds sequentially through the tree levels:

    1. Sample the first variable uniformly
    2. For each subsequent variable, sample conditionally using the inverse h-function
    3. Count the fraction of samples where all variables fall below (or above) the specified quantile

    This captures the full heterogeneous tail dependence structure of the vine, unlike methods that assume a single copula family for all pairs.
  </Accordion>
</AccordionGroup>

<Warning>
  Vine copula fitting requires at least `n*(n-1)/2` parameters for n markets. With many markets (n > 10), the number of pair copulas grows quadratically. Ensure you have sufficient observations (at least 30 per pair copula) for reliable estimation.
</Warning>
