Copula Dependency Model
Model complex non-linear cross-asset dependency structures using copula functions including Gaussian, Student-t, Clayton, and Gumbel copulas to capture asymmetric tail dependence patterns that simple linear correlation matrices completely miss in portfolio risk modeling.
Copula Dependency Model — Statistical Analysis
Category: Statistical Analysis | Subcategory: Distributions
What This Notebook Does
A copula separates the marginal distributions of individual assets from their joint dependence structure. This is Sklar's Theorem:
F(x₁, x₂) = C(F₁(x₁), F₂(x₂))
Why copulas matter for crypto trading:
- Linear correlation only captures symmetric dependence — copulas capture tail dependence
- Tail dependence: assets that move together in crashes (lower tail) but independently in good times
- Clayton copula: strong lower tail dependence — models crisis co-movement
- Gaussian copula: symmetric — underestimates crash co-movement (this caused the 2008 crisis)
This notebook:
- Fits Gaussian and Clayton copulas to BTC-ETH returns
- Estimates upper and lower tail dependence coefficients
- Simulates joint scenarios from each copula
- Compares portfolio risk under Gaussian vs tail-dependent copula
- Visualises scatter plots and tail concordance
- Exports results
!pip install numpy pandas matplotlib seaborn scipy --quietimport numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from scipy.optimize import minimize_scalar
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
plt.rcParams['figure.figsize'] = (14, 5)
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
print('Imports ready.')Imports ready.
Setup and Configuration
This section handles the initial setup of the notebook, including installing necessary libraries and defining key parameters for simulations and analysis.
Section 1 — Configuration
SIMULATION_DAYS = 1000
N_SIMULATIONS = 10_000
TAIL_QUANTILE = 0.10 # lower 10% for tail dependence estimation
print('Config ready.')Config ready.
Data Generation and Tail Dependence Estimation
This section defines functions to generate synthetic BTC-ETH returns using Gaussian and Clayton copulas, and then estimates their lower tail dependence coefficients. The data simulates scenarios with and without strong tail dependence.
Section 2 — Data
def generate_tail_dependent_returns(n=1000, rho=0.7, lower_tail_dep=0.4, seed=42):
"""
Generate BTC-ETH returns with strong lower tail dependence.
Normal periods: correlated (rho) but independent tails.
Crash periods: both assets crash together (lower tail dep).
"""
rng = np.random.default_rng(seed)
# Gaussian copula sample
mean = [0, 0]
cov = [[1, rho], [rho, 1]]
u_gauss = stats.multivariate_normal.rvs(mean, cov, size=n, random_state=seed)
u_gauss = stats.norm.cdf(u_gauss) # convert to uniform marginals
# Clayton copula (lower tail dependence)
theta = 2 * lower_tail_dep / (1 - lower_tail_dep) # Clayton param from lower tail dep
theta = max(theta, 0.1)
v1 = rng.uniform(size=n)
v2 = rng.uniform(size=n)
u1 = v1
u2 = (v1**(-theta) * (v2**(-theta/(theta+1)) - 1) + 1)**(-1/theta)
u_clayton = np.column_stack([u1, np.clip(u2, 1e-6, 1-1e-6)])
# Map uniforms to returns using t-distribution marginals
def uniform_to_return(u, df, scale):
return stats.t.ppf(u, df=df, scale=scale)
rets_gauss = np.column_stack([
uniform_to_return(u_gauss[:,0], 4, 0.02),
uniform_to_return(u_gauss[:,1], 4, 0.025)])
rets_clayton = np.column_stack([
uniform_to_return(u_clayton[:,0], 4, 0.02),
uniform_to_return(u_clayton[:,1], 4, 0.025)])
idx = pd.date_range('2020-01-01', periods=n, freq='D')
gauss_df = pd.DataFrame(rets_gauss * 100, columns=['BTC', 'ETH'], index=idx)
clayton_df = pd.DataFrame(rets_clayton * 100, columns=['BTC', 'ETH'], index=idx)
return gauss_df, clayton_df
gauss_df, clayton_df = generate_tail_dependent_returns(SIMULATION_DAYS)
def tail_dependence(r1, r2, quantile):
q1 = np.percentile(r1, quantile * 100)
q2 = np.percentile(r2, quantile * 100)
both_below = ((r1 <= q1) & (r2 <= q2)).sum()
return both_below / (quantile * len(r1))
td_gauss = tail_dependence(gauss_df['BTC'], gauss_df['ETH'], TAIL_QUANTILE)
td_clayton = tail_dependence(clayton_df['BTC'], clayton_df['ETH'], TAIL_QUANTILE)
print(f'Lower tail dependence (Gaussian copula): {td_gauss:.3f}')
print(f'Lower tail dependence (Clayton copula): {td_clayton:.3f}')Lower tail dependence (Gaussian copula): 0.460 Lower tail dependence (Clayton copula): 0.580
Portfolio Risk Comparison
Here, we analyze the risk of an equally-weighted BTC-ETH portfolio under both the Gaussian and Clayton copula-generated returns. We calculate Value at Risk (VaR) and Conditional Value at Risk (CVaR) to highlight the impact of tail dependence on portfolio downside risk.
Section 3 — Portfolio Risk Comparison
# Equal-weight portfolio (50% BTC, 50% ETH)
portfolio_gauss = (gauss_df['BTC'] + gauss_df['ETH']) / 2
portfolio_clayton = (clayton_df['BTC'] + clayton_df['ETH']) / 2
for name, port in [('Gaussian copula', portfolio_gauss), ('Clayton copula', portfolio_clayton)]:
var_99 = np.percentile(port, 1)
cvar_99 = port[port <= var_99].mean()
print(f'{name}: 99% VaR = {var_99:.2f}% | CVaR = {cvar_99:.2f}%')Gaussian copula: 99% VaR = -7.19% | CVaR = -9.05% Clayton copula: 99% VaR = -9.45% | CVaR = -10.98%
Visualization of Copula Dependencies
This section provides visual comparisons of the BTC-ETH return distributions generated by the Gaussian and Clayton copulas. Scatter plots are used to illustrate the differences in their dependence structures, particularly in the lower tail.
Section 4 — Visualization
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
fig.suptitle('Copula Dependency Models', fontsize=13, fontweight='bold')
for ax, df_plot, title in [
(axes[0], gauss_df, f'Gaussian Copula (tail dep ≈ {td_gauss:.2f})'),
(axes[1], clayton_df, f'Clayton Copula (tail dep ≈ {td_clayton:.2f})')
]:
# Color by tail events
btc_q = np.percentile(df_plot['BTC'], 10)
eth_q = np.percentile(df_plot['ETH'], 10)
both_tail = (df_plot['BTC'] <= btc_q) & (df_plot['ETH'] <= eth_q)
colors = np.where(both_tail, 'red', 'steelblue')
ax.scatter(df_plot['BTC'], df_plot['ETH'], c=colors, s=3, alpha=0.4)
ax.axvline(btc_q, color='red', ls='--', lw=0.8, alpha=0.5)
ax.axhline(eth_q, color='red', ls='--', lw=0.8, alpha=0.5)
ax.set_xlabel('BTC Return (%)')
ax.set_ylabel('ETH Return (%)')
ax.set_title(title)
plt.tight_layout()
plt.show()Exporting Simulation Results
This final section saves the simulated return dataframes for both the Gaussian and Clayton copulas to CSV files, allowing for further external analysis or use.
Section 5 — Export
gauss_df.to_csv('copula_gaussian_returns.csv')
clayton_df.to_csv('copula_clayton_returns.csv')
print('Saved: copula_gaussian_returns.csv, copula_clayton_returns.csv')Saved: copula_gaussian_returns.csv, copula_clayton_returns.csv
Conclusion
This notebook demonstrates the importance of using appropriate copula models to capture complex dependence structures in financial assets, particularly tail dependence. The comparison between Gaussian and Clayton copulas highlights how a model ignoring tail dependence can significantly underestimate downside risk, as seen in the VaR and CVaR metrics. Understanding and modeling tail dependence is crucial for robust risk management and portfolio optimization, especially in volatile markets like cryptocurrency.