Parlays, same-game combos, multi-leg prediction market — anywhere you can combine two event contracts into one, there's a pricing problem lurking underneath: how do you price the combo when the two legs aren't independent?
Most naive approaches just multiply the individual probabilities together. It's simple, it's fast, and it's often badly wrong. I wanted to find out exactly how wrong, and whether a slightly more sophisticated model could fix it — so I built a small project around it using BTC and ETH price data as a stand-in for two correlated event legs.
Here's what I found.
The setup
The combo I priced: "BTC moves more than 1% in an hour AND ETH moves more than 1% in the same hour."
I pulled hourly BTC/USDT and ETH/USDT price data from Binance's public market data API (no auth needed — data-api.binance.vision), computed hourly returns, and compared two approaches to pricing this combo:
-
Naive independence — multiply
P(BTC > 1%)andP(ETH > 1%) - Copula-based — explicitly model the joint distribution of BTC and ETH returns, accounting for how correlated they actually are
p_btc = (df["btc_return"] > 0.01).mean()
p_eth = (df["eth_return"] > 0.01).mean()
naive_prob = p_btc * p_eth
Simple enough. But BTC and ETH aren't independent — they're famously correlated. My data showed a hourly return correlation of 0.84. A scatter plot makes it obvious:
(tight diagonal cloud, not a random blob — when BTC moves, ETH tends to move with it)
So multiplying independent probabilities was never going to be right. The question was: by how much?
Finding #1: naive pricing fails, and fails worse in the tails
| Threshold | Naive independence | Actual (empirical) | Error |
|---|---|---|---|
| Any positive move | 25.5% | 41.9% | ~1.6x underestimate |
| >1% move (tail) | 0.03% | 0.90% | ~32x underestimate |
That's not a rounding error. At the tail threshold — the kind of move that would actually make an interesting, tradeable combo — naive pricing was off by a factor of 32. This is a textbook case of tail dependence: correlated assets don't just move together on average, they move together especially during extreme moves.
Fixing it with copulas
I fit two copula models to correct this:
-
Gaussian copula, using the
copulasPython package - t-copula (fit via rank transform + a multivariate t-distribution), which explicitly models tail dependence and should theoretically do better here
from copulas.multivariate import GaussianMultivariate
gc = GaussianMultivariate()
gc.fit(df[["btc_return", "eth_return"]])
samples = gc.sample(10000)
gaussian_prob = ((samples["btc_return"] > 0.01) & (samples["eth_return"] > 0.01)).mean()
Results at the 1% tail threshold:
| Model | Estimate | Error vs. actual (0.90%) |
|---|---|---|
| Naive independence | 0.03% | -97% |
| Gaussian copula | 1.03% | +14% |
| t-copula | 0.96% | +6.5% |
The t-copula won, exactly as the theory predicts — tail dependence modeling matters most exactly where naive assumptions break down hardest.
Making sure it's not overfit
Fitting a model and testing it on the same data is a classic way to fool yourself. So I split the data 70/30, fit both copulas only on the training set, and checked them against the untouched test set:
| Model | Train-fit estimate | Test-set ground truth |
|---|---|---|
| Naive independence | 0.031% | — |
| Gaussian copula | 0.890% | — |
| t-copula | 0.920% | — |
| Empirical (unseen test data) | — | 1.000% |
Both copula models, trained without ever seeing the test period, landed close to what actually happened. This isn't a curve-fitting artifact — it's a real, generalizable relationship between how BTC and ETH move.
Turning probabilities into dollars
Probabilities are nice, but "how much money does this actually cost you" is nicer. Converting to a binary-contract price (a $1-payout contract priced in cents, equal to its probability):
A market maker selling 1,000 contracts of this combo at the naive price (0.03¢) collects $0.31. The expected payout, using the true probability, is $10.00. Expected P&L: -$9.69 — roughly 97% of collected premium, lost, in expectation.
That's the sentence that made this project click for me. This isn't an academic modeling nuance — ignoring correlation here is a business-ending pricing mistake at any real scale.
Building a quoting engine
The last piece: does correcting the pricing actually translate into better trading outcomes? I built a small quoting engine that:
- Computes a blended fair value from the Gaussian + t-copula estimates, refit on a rolling window
- Sets a bid/ask spread that widens with model disagreement (a cheap uncertainty proxy — if Gaussian and t-copula disagree a lot, be more cautious) and with inventory imbalance (classic adverse-selection protection — don't keep selling into a position you're already overexposed on)
- Runs against simulated order flow and tracks P&L
def get_quote(self, pricing_result):
fair_value = pricing_result["fair_value_cents"]
disagreement = pricing_result["disagreement_cents"]
extra_spread_inv, skew = self.inventory_skew_cents()
total_spread = min(MAX_SPREAD, BASE_SPREAD + disagreement + extra_spread_inv)
mid = fair_value + skew
return mid - total_spread / 2, mid + total_spread / 2
I ran naive-priced quoting and copula-priced quoting side by side across 40 independent historical windows, using identical simulated order flow in both cases so the only variable was the pricing logic itself.
First pass at the stats looked underwhelming — the two P&L distributions weren't meaningfully different by a standard unpaired comparison. But that was the wrong test. Trial-to-trial P&L swings hugely depending on whether the rare tail event fired in that window — a factor that affects both strategies equally and isn't the thing I was actually trying to measure.
Switching to a paired comparison (copula P&L minus naive P&L, per matched trial) told a completely different story:
| Metric | Result |
|---|---|
| Copula won | 40 / 40 trials |
| Mean improvement | +$0.696 per trial |
| Signal/noise ratio | 9.19x |
| Paired t-test p-value | 2.6 × 10⁻¹¹ |
| Sign test p-value | 9.1 × 10⁻¹³ |
Copula-based quoting beat naive quoting in every single trial. Small edge, but perfectly consistent — which, honestly, is what real market-making edges tend to look like. Nobody's doubling their money on this; it's a thin, reliable advantage that compounds over volume.
Takeaway
If you're pricing anything with correlated legs — sports parlays, prediction market combos, multi-asset derivatives — the independence assumption isn't just imprecise, it can be catastrophically wrong exactly where it matters most: the tails. Copula models are a well-established, relatively simple fix, and the improvement is measurable both in raw pricing accuracy and in downstream trading P&L.
Full code is on GitHub: https://github.com/keerat55/combo-pricing-engine/tree/main
Happy to go deeper on any part of this — the copula fitting, the paired statistical testing, or the quoting logic — just ask in the comments.
Top comments (1)
The 32x gap between naive marginal multiplication and empirical joint outcomes in the tail is the exact reason desk risk models blow up on combo contracts. Multiplying independent probabilities implicitly assumes zero joint tail dependence, which never holds in crypto or credit.
Two structural nuances usually show up once you move this into a production quoting engine.
The first is tail dependence structure. A Gaussian copula has zero asymptotic tail dependence in both directions, which means as you push the threshold further into multiple standard deviations, it will begin to decay back toward the naive independent outcome. The Student-t copula handles that better because of positive tail dependence, but standard elliptical copulas still impose symmetric joint probabilities on both tails. In crypto markets, joint downside moves tend to show tighter clustering than joint upside rallies. Running an asymmetric Archimedean copula, or simply fitting separate degrees of freedom across regimes, catches that skew before an adverse shock hits the inventory.
The second is correlation non-stationarity. The realized correlation of 0.84 is an average across regimes. During liquidity squeezes, cross-asset correlation spikes toward 1.0 just as volatility expands. When market makers get hit on multi-leg tails, it almost always coincides with that correlation breakdown, meaning static copula parameters understate the conditional payout exactly when the contract trades.