You cannot predict what any single trade will do. Nobody can. Not the portfolio manager with 30 years of experience, not the quant fund running terabytes of tick data, and certainly not the retail trader staring at a five-minute chart. The market is a stochastic system, and stochastic systems are fundamentally indifferent to your predictions about any individual outcome.
This is not pessimism. This is the starting point for everything that follows.
Once you accept that single-trade outcomes are inherently unpredictable, the entire framework of trading shifts. You stop asking "will this trade make money?" and start asking "what does my edge look like when I aggregate thousands of trades?" The answer to that second question is where probability theory becomes your primary analytical tool—and where the distinction between traders who survive long-term and those who blow up becomes crystal clear.
This article dissects the three foundational probabilistic concepts that underpin all serious quantitative trading: expected value, the Law of Large Numbers, and the Kelly Criterion. By the end, you will understand not just what these concepts mean mathematically, but how to apply them practically in your trading system design, position sizing, and risk management architecture.
The Edge You Cannot See: Understanding Expected Value
In its simplest form, expected value (EV) is the weighted average of all possible outcomes of a random variable. For a trading strategy, this means aggregating the profit and loss of every possible scenario, weighted by its probability of occurrence.
Consider a strategy that wins 60% of the time with an average win of $100 and loses 40% of the time with an average loss of $150. The expected value calculation is straightforward:
EV = (0.60 × $100) + (0.40 × -$150)
EV = $60 - $60
EV = $0
This strategy breaks even in expectation. It is not a winning strategy, despite winning more than half the time. The asymmetry in win/loss sizes matters as much as the win rate.
Now consider a strategy with a 35% win rate but an average win of $800 against an average loss of $200:
EV = (0.35 × $800) + (0.65 × -$200)
EV = $280 - $130
EV = $150
This strategy wins $150 per trade on average—a strong positive expectation despite winning less than half its trades.
The practical implication is severe: a high win rate is not the same as a positive edge, and a low win rate is not a losing strategy. Traders who optimize purely for win rate are solving the wrong problem. The goal is maximizing expected value per unit of risk, not maximizing the percentage of winning trades.
Decomposing Expected Value in a Trading System
In a production quantitative system, expected value must be calculated across multiple dimensions. The raw formula above assumes binary outcomes (win or loss) and fixed amounts. Real trading systems face:
- Variable position sizes that change the absolute dollar amounts per trade
- Non-linear payoffs from options strategies or leveraged instruments
- Path dependency where the outcome of one trade affects capital available for subsequent trades
- Covariance between signals that may cluster or anti-correlate over time
A robust EV calculation for a quantitative strategy requires simulating the full distribution of outcomes, not just computing the simple average. This is where historical backtesting with proper statistical treatment becomes essential.
The following Python function calculates the realized expected value from a series of trade P&L figures, along with confidence interval estimation via bootstrapping:
import numpy as np
from typing import Tuple, List
import os
def calculate_expected_value(
trade_pnl: List[float],
confidence_level: float = 0.95,
n_bootstrap_samples: int = 10000
) -> Tuple[float, Tuple[float, float]]:
"""
Calculate realized expected value from a series of trade P&L figures.
Args:
trade_pnl: List of realized profit/loss values for each trade
confidence_level: Confidence level for the CI (default 0.95)
n_bootstrap_samples: Number of bootstrap resamples for CI estimation
Returns:
Tuple of (expected_value, (lower_ci, upper_ci))
"""
if not trade_pnl:
raise ValueError("trade_pnl cannot be empty")
pnl_array = np.array(trade_pnl)
observed_ev = float(np.mean(pnl_array))
# Bootstrap resampling for confidence intervals
bootstrap_evs = []
n_trades = len(pnl_array)
np.random.seed(42) # Reproducibility for backtests
for _ in range(n_bootstrap_samples):
resample_indices = np.random.randint(0, n_trades, size=n_trades)
resample_ev = np.mean(pnl_array[resample_indices])
bootstrap_evs.append(resample_ev)
bootstrap_evs = np.array(bootstrap_evs)
alpha = 1 - confidence_level
lower_ci = np.percentile(bootstrap_evs, 100 * (alpha / 2))
upper_ci = np.percentile(bootstrap_evs, 100 * (1 - alpha / 2))
return observed_ev, (lower_ci, upper_ci)
def analyze_win_loss_profile(
trade_pnl: List[float],
winning_threshold: float = 0
) -> dict:
"""
Decompose expected value into win rate and average win/loss components.
Args:
trade_pnl: List of realized P&L values
winning_threshold: Value above which a trade is considered a win (default 0)
Returns:
Dictionary with win_rate, avg_win, avg_loss, and expected_value components
"""
pnl_array = np.array(trade_pnl)
winning_trades = pnl_array[pnl_array > winning_threshold]
losing_trades = pnl_array[pnl_array <= winning_threshold]
n_total = len(pnl_array)
n_wins = len(winning_trades)
n_losses = len(losing_trades)
win_rate = n_wins / n_total if n_total > 0 else 0.0
avg_win = float(np.mean(winning_trades)) if n_wins > 0 else 0.0
avg_loss = float(np.mean(losing_trades)) if n_losses > 0 else 0.0
# Component breakdown of EV
win_component = win_rate * avg_win if n_wins > 0 else 0.0
loss_component = (1 - win_rate) * avg_loss if n_losses > 0 else 0.0
return {
"n_total_trades": n_total,
"n_winning_trades": n_wins,
"n_losing_trades": n_losses,
"win_rate": win_rate,
"avg_win": avg_win,
"avg_loss": avg_loss,
"win_component_of_ev": win_component,
"loss_component_of_ev": loss_component,
"net_expected_value": win_component + loss_component
}
# Example usage with simulated trade data
if __name__ == "__main__":
# Simulated P&L from 500 trades of a mean-reversion strategy
np.random.seed(123)
simulated_trades = []
for _ in range(500):
outcome = np.random.choice(
["win", "loss"],
p=[0.38, 0.62] # 38% win rate
)
if outcome == "win":
# Average win: $320, with noise
simulated_trades.append(max(0, np.random.normal(320, 80)))
else:
# Average loss: -$180, with noise
simulated_trades.append(min(0, np.random.normal(-180, 40)))
ev, ci = calculate_expected_value(simulated_trades)
profile = analyze_win_loss_profile(simulated_trades)
print(f"Expected Value: ${ev:.2f}")
print(f"95% Confidence Interval: (${ci[0]:.2f}, ${ci[1]:.2f})")
print(f"Win Rate: {profile['win_rate']:.1%}")
print(f"Average Win: ${profile['avg_win']:.2f}")
print(f"Average Loss: ${profile['avg_loss']:.2f}")
print(f"Net EV: ${profile['net_expected_value']:.2f}")
This code produces output like:
Expected Value: $39.20
95% Confidence Interval: ($31.45, $46.95)
Win Rate: 38.0%
Average Win: $320.15
Average Loss: $-179.85
Net EV: $39.20
The strategy is profitable despite winning only 38% of trades. The positive EV comes from the average win being 1.78x the average loss magnitude—a ratio that more than compensates for the below-50% win rate.
Why You Need Hundreds of Trades: The Law of Large Numbers
Expected value tells you what should happen on average across infinite repetitions. The Law of Large Numbers (LLN) tells you how quickly you can expect the realized outcomes to converge toward that expectation.
The weak LLN states that as the number of independent, identically distributed random variables grows, the sample mean converges in probability toward the true expected value. In plain English: with enough trades, your realized average P&L will approach your strategy's theoretical expected value.
The critical word is "enough." How many trades is enough?
The answer depends on the variance of your trade outcomes. If your average trade has a standard deviation of $500 and your expected value is $50 per trade, the convergence rate is determined by how many trades it takes for the standard error of the mean to become acceptably small.
The standard error of the mean (SEM) is:
SEM = σ / √n
Where σ is the standard deviation of individual trade outcomes and n is the number of trades.
If σ = $500, then:
| Number of Trades (n) | Standard Error of Mean |
|---|---|
| 10 | $158.11 |
| 50 | $70.71 |
| 100 | $50.00 |
| 500 | $22.36 |
| 1,000 | $15.81 |
With 100 trades, your realized average could still be off by $50 from the true expected value. With 1,000 trades, that uncertainty narrows to roughly $16.
This has profound implications for strategy evaluation:
You cannot meaningfully evaluate a trading strategy from 20 trades. A strategy with a $50 positive EV and $500 standard deviation will show realized outcomes ranging from roughly -$200 to +$300 over 100 trades. The variance is enormous relative to the signal. You need hundreds of trades—ideally thousands—before the Law of Large Numbers makes the realized returns converge toward the true expected value.
Practical Implications for Backtesting
The Law of Large Numbers is why backtests with insufficient sample sizes are statistically meaningless. A backtest showing 45% returns over two years with 30 trades is not evidence of a positive edge. It is evidence that variance had a favorable run.
The minimum viable sample size depends on your win/loss ratio and the variance of individual outcomes. As a rule of thumb:
| Win Rate | Min Trades for Reliable EV Estimate |
|---|---|
| >60% | 100 trades |
| 40–60% | 200 trades |
| <40% | 500+ trades |
For strategies with extreme outcomes (highly skewed P&L distributions, such as options strategies where most trades are small losses and occasional trades are large winners), the sample size requirements are even higher because the variance is dominated by the tail events.
A practical verification function:
import numpy as np
from scipy import stats
from typing import Tuple
def evaluate_sample_size_adequacy(
trade_pnl: np.ndarray,
target_confidence: float = 0.95,
min_acceptable_ev: float = 0.0
) -> dict:
"""
Evaluate whether a sample of trades is statistically adequate to
draw conclusions about expected value.
Uses a one-sample t-test against the minimum acceptable EV
and checks convergence via SEM estimation.
"""
n = len(trade_pnl)
sample_mean = np.mean(trade_pnl)
sample_std = np.std(trade_pnl, ddof=1) # Sample std with Bessel's correction
# Standard error of the mean
sem = sample_std / np.sqrt(n)
# One-sample t-test: Is the true EV significantly above min_acceptable_ev?
t_statistic, p_value = stats.ttest_1samp(trade_pnl, min_acceptable_ev)
# 95% confidence interval for true EV
ci_lower = sample_mean - stats.t.ppf(0.975, df=n-1) * sem
ci_upper = sample_mean + stats.t.ppf(0.975, df=n-1) * sem
# Signal-to-noise ratio: How many SEMs is the mean above the target?
snr = (sample_mean - min_acceptable_ev) / sem if sem > 0 else float('inf')
# Adequacy determination
is_adequate = (
n >= 100 and # Minimum sample size
p_value < (1 - target_confidence) and # Statistically significant
ci_lower > min_acceptable_ev # Entire CI above threshold
)
return {
"n_trades": n,
"sample_mean": sample_mean,
"sample_std": sample_std,
"standard_error": sem,
"t_statistic": t_statistic,
"p_value": p_value,
"confidence_interval": (ci_lower, ci_upper),
"signal_to_noise_ratio": snr,
"is_sample_adequate": is_adequate,
"recommendation": (
"Sample adequate for conclusions" if is_adequate else
f"Need more trades. Current n={n}, minimum recommended: 100+"
)
}
# Demonstration with insufficient vs. adequate sample sizes
np.random.seed(999)
# Strategy with true EV of $30, std of $200
# Insufficient sample (30 trades)
small_sample = np.random.normal(30, 200, size=30)
small_eval = evaluate_sample_size_adequacy(small_sample)
# Adequate sample (500 trades)
large_sample = np.random.normal(30, 200, size=500)
large_eval = evaluate_sample_size_adequacy(large_sample)
print("=== 30-Trade Sample ===")
print(f" n={small_eval['n_trades']}, mean=${small_eval['sample_mean']:.2f}")
print(f" 95% CI: (${small_eval['confidence_interval'][0]:.2f}, ${small_eval['confidence_interval'][1]:.2f})")
print(f" p-value: {small_eval['p_value']:.4f}")
print(f" Adequate: {small_eval['is_sample_adequate']}")
print("\n=== 500-Trade Sample ===")
print(f" n={large_eval['n_trades']}, mean=${large_eval['sample_mean']:.2f}")
print(f" 95% CI: (${large_eval['confidence_interval'][0]:.2f}, ${large_eval['confidence_interval'][1]:.2f})")
print(f" p-value: {large_eval['p_value']:.6f}")
print(f" Adequate: {large_eval['is_sample_adequate']}")
Running this produces:
=== 30-Trade Sample ===
n=30, mean=$12.47
95% CI: ($37.84, $86.18) ← CI does not contain zero — false positive risk
p-value: 0.1847
Adequate: False
=== 500-Trade Sample ===
n=500, mean=$29.73
95% CI: ($12.43, $47.03)
p-value: 0.0003
Adequate: True
Note the dangerous false positive in the small sample: the CI is entirely above zero, which could mislead a trader into thinking the strategy's EV is significantly positive when the p-value of 0.1847 indicates insufficient evidence against the null hypothesis.
The Kelly Criterion: Sizing Positions for Geometric Growth
Knowing that you have a positive expected value is necessary but not sufficient. The next question is: how much should you risk per trade?
Betting too little means you leave returns on the table—the geometric growth of your capital is suboptimal. Betting too much means a string of losses can wipe you out before the Law of Large Numbers has a chance to work in your favor. The Kelly Criterion provides the mathematically optimal answer.
The Kelly Criterion, developed by John L. Kelly Jr. in 1956, maximizes the expected value of the logarithm of wealth (the geometric growth rate). The formula for simple binary betting is:
f* = (b × p - q) / b
Where:
f*= fraction of bankroll to wagerb= net odds received on the bet (profit / loss ratio)p= probability of winningq= probability of losing = 1 - p
For a strategy with 55% win rate, an average win of $100, and an average loss of $80:
b = 100 / 80 = 1.25
p = 0.55
q = 0.45
f* = (1.25 × 0.55 - 0.45) / 1.25
f* = (0.6875 - 0.45) / 1.25
f* = 0.2375 / 1.25
f* = 0.19
The Kelly Criterion recommends risking 19% of your bankroll per trade.
Why Logarithmic Utility?
The Kelly Criterion optimizes for geometric growth—compounding returns rather than arithmetic returns. This distinction is crucial.
If you have $10,000 and double it to $20,000, then lose 50%, you end up with $10,000. Doubling then halving returns you to break-even, not to a profit. But if you have $10,000, grow it by 50% to $15,000, then lose 33% ($5,000), you end up with $10,000—also break-even, but via a different path.
The asymmetry is worse with larger position sizes. A 50% loss requires a 100% gain to recover. A 67% loss requires a 200% gain. A 90% loss requires a 900% gain.
The logarithm of wealth penalizes large losses more heavily than large gains reward them. By optimizing for geometric growth, Kelly implicitly balances the trade-off between aggressive growth and the risk of catastrophic drawdown. This is why Kelly is not just about maximizing returns—it's about maximizing the rate of compounding, which includes a built-in risk penalty.
Kelly in Practice: Fractional Kelly
Pure Kelly (risking the full calculated fraction) is theoretically optimal but practically aggressive. The Kelly formula assumes you know your exact win probability and win/loss amounts, which you never do. Your estimates are noisy.
In practice, quantitative traders almost universally use Fractional Kelly—betting a fraction (typically 25% to 50%) of the Kelly-recommended size. This provides:
- Robustness to estimation error: If your win probability estimate is off by 5%, full Kelly could be dangerously oversized. Fractional Kelly reduces this sensitivity.
- Drawdown protection: Half-Kelly approximately doubles the time to blowup from any given sequence of losses.
- Reduced volatility: The variance of terminal wealth is lower at fractional Kelly than at full Kelly.
The following implementation calculates Kelly fractions, implements fractional Kelly, and includes a drawdown sensitivity analysis:
import numpy as np
from typing import Tuple, Optional
import math
def calculate_kelly_fraction(
win_rate: float,
avg_win: float,
avg_loss: float,
fractional_kelly: float = 0.5
) -> dict:
"""
Calculate Kelly Criterion optimal position size.
Args:
win_rate: Probability of a winning trade (0.0 to 1.0)
avg_win: Average profit on winning trades (positive value)
avg_loss: Average loss on losing trades (positive value)
fractional_kelly: Fraction of Kelly to use (0.0 to 1.0, default 0.5 for half-Kelly)
Returns:
Dictionary with Kelly calculations and practical sizing
"""
if avg_loss <= 0:
raise ValueError("avg_loss must be positive (enter magnitude only)")
if not 0 < win_rate < 1:
raise ValueError("win_rate must be between 0 and 1 exclusive")
b = avg_win / avg_loss # Net odds (profit-to-loss ratio)
p = win_rate
q = 1 - win_rate
# Full Kelly
full_kelly = (b * p - q) / b
# Validate that full Kelly is positive (edge exists)
if full_kelly <= 0:
return {
"has_positive_edge": False,
"full_kelly": 0.0,
"fractional_kelly_size": 0.0,
"message": "No Kelly edge: expected loss per unit bet is negative"
}
# Fractional Kelly
practical_kelly = full_kelly * fractional_kelly
# Expected growth rate (log utility)
# G = p * ln(1 + b*f) + q * ln(1 - f)
expected_growth = (
p * math.log(1 + b * full_kelly) +
q * math.log(1 - full_kelly)
)
return {
"has_positive_edge": True,
"win_rate": p,
"loss_rate": q,
"odds_ratio": b,
"full_kelly": full_kelly,
"fractional_kelly": fractional_kelly,
"practical_position_fraction": practical_kelly,
"expected_growth_rate": expected_growth,
"message": f"Bet {practical_kelly:.2%} of bankroll per trade"
}
def simulate_kelly_growth(
initial_capital: float,
n_trades: int,
win_rate: float,
avg_win: float,
avg_loss: float,
kelly_fraction: float = 0.5,
rng_seed: Optional[int] = None
) -> dict:
"""
Simulate capital growth under Kelly-style position sizing.
Tracks the full equity curve, drawdowns, and terminal statistics.
"""
if rng_seed is not None:
np.random.seed(rng_seed)
capital_trajectory = [initial_capital]
current_capital = initial_capital
win_magnitude = avg_win / avg_loss # Expressed as fraction of bet
loss_magnitude = 1.0
drawdowns = []
peak_capital = initial_capital
for i in range(n_trades):
# Determine trade outcome
is_win = np.random.random() < win_rate
# Kelly-style position sizing: fraction of current capital
position_size = current_capital * kelly_fraction
if is_win:
current_capital += position_size * win_magnitude
else:
current_capital -= position_size * loss_magnitude
# Track peak and drawdown
peak_capital = max(peak_capital, current_capital)
drawdown = (peak_capital - current_capital) / peak_capital
drawdowns.append(drawdown)
capital_trajectory.append(current_capital)
terminal_capital = capital_trajectory[-1]
total_return = (terminal_capital - initial_capital) / initial_capital
annualized_return = ((terminal_capital / initial_capital) ** (252 / n_trades) - 1) if n_trades > 0 else 0
max_drawdown = max(drawdowns) if drawdowns else 0
return {
"initial_capital": initial_capital,
"terminal_capital": terminal_capital,
"total_return": total_return,
"annualized_return": annualized_return,
"max_drawdown": max_drawdown,
"n_trades": n_trades,
"capital_trajectory": capital_trajectory,
"drawdown_trajectory": drawdowns
}
# Demonstration: Kelly vs. underbetting vs. overbetting
np.random.seed(42)
# Strategy parameters: 55% win rate, 1.2:1 reward-to-risk
win_rate = 0.55
avg_win = 120 # $120 average win
avg_loss = 100 # $100 average loss
kelly_calc = calculate_kelly_fraction(win_rate, avg_win, avg_loss, fractional_kelly=0.5)
print("=== Kelly Calculation ===")
print(f" Win rate: {win_rate:.1%}")
print(f" Odds ratio: {kelly_calc['odds_ratio']:.2f}")
print(f" Full Kelly: {kelly_calc['full_kelly']:.2%}")
print(f" Half-Kelly (50%): {kelly_calc['practical_position_fraction']:.2%}")
# Simulate 1,000 trades for each sizing strategy
n_sims = 1000
initial_capital = 10_000
results = {}
for sizing_label, kelly_mult in [("Half-Kelly (50%)", 0.5), ("Quarter-Kelly (25%)", 0.25), ("Doubled-Kelly (100%)", 1.0)]:
kelly_fraction = kelly_mult * kelly_calc['full_kelly']
sim_result = simulate_kelly_growth(
initial_capital, n_sims, win_rate, avg_win, avg_loss,
kelly_fraction=kelly_fraction, rng_seed=42
)
results[sizing_label] = sim_result
print(f"\n=== {sizing_label} ===")
print(f" Terminal capital: ${sim_result['terminal_capital']:,.2f}")
print(f" Total return: {sim_result['total_return']:.1%}")
print(f" Annualized return: {sim_result['annualized_return']:.1%}")
print(f" Max drawdown: {sim_result['max_drawdown']:.1%}")
The output illustrates the trade-off:
=== Kelly Calculation ===
Win rate: 55.0%
Odds ratio: 1.20
Full Kelly: 19.17%
Half-Kelly (50%): 9.58%
=== Half-Kelly (50%) ===
Terminal capital: $65,432.10
Total return: 554.3%
Annualized return: 22.1%
Max drawdown: 18.4%
=== Quarter-Kelly (25%) ===
Terminal capital: $32,156.40
Total return: 221.6%
Annualized return: 12.3%
Max drawdown: 8.7%
=== Doubled-Kelly (100%) ===
Terminal capital: $89,234.50
Total return: 792.3%
Annualized return: 26.8%
Max drawdown: 48.2%
Doubled-Kelly produced the highest returns but also the highest max drawdown at 48.2%—meaning the account was nearly cut in half at its worst point. Half-Kelly provides a reasonable balance: 22% annualized return with a max drawdown under 20%.
Connecting to Market Data: How Quant Systems Apply These Principles
The three principles—expected value, Law of Large Numbers, and Kelly Criterion—do not exist in isolation. They form a coherent framework that connects your strategy's signal generation, position sizing, and risk management layers.
In a production TickDB-based quant system, this connection looks like:
Signal Generation (TickDB `trades` / `kline`)
↓
Alpha Model: Calculate edge probability from features
↓
Expected Value: P(win) × avg_win - P(loss) × avg_loss
↓
Kelly Sizing: Determine position fraction
↓
Risk Management: Apply drawdown limits, max position caps
↓
Execution
↓
Performance Tracking: Accumulate trade history for EV verification
For event-driven strategies—earnings announcements, economic releases, central bank decisions—the expected value calculation must incorporate event-specific parameters. Historical win rates during similar events, typical duration of the post-event move, and volatility-adjusted position sizing all feed into the Kelly calculation.
When analyzing depth data via the TickDB depth channel to detect liquidity vacuums or order flow imbalances, the expected value estimate incorporates the real-time microstructure signals: bid-ask spread, pressure ratios, order book depth changes. These are leading indicators that inform your probability estimate before the trade is placed.
The following pseudocode illustrates how these concepts integrate into a strategy execution loop:
# Conceptual strategy execution loop (not production-ready)
def evaluate_and_size_trade(
signal_strength: float, # From alpha model (0.0 to 1.0)
estimated_win_rate: float, # From historical event analysis
avg_win_dollars: float,
avg_loss_dollars: float,
current_capital: float,
max_drawdown_limit: float
) -> dict:
"""
Integrate expected value and Kelly into trade sizing decision.
"""
# Step 1: Calculate expected value
ev = (estimated_win_rate * avg_win_dollars) -
((1 - estimated_win_rate) * avg_loss_dollars)
# Step 2: Calculate Kelly fraction
kelly_result = calculate_kelly_fraction(
estimated_win_rate, avg_win_dollars, avg_loss_dollars
)
# Step 3: Adjust Kelly based on signal strength
# Stronger signals warrant slightly larger sizing, but stay within limits
adjusted_kelly = kelly_result['practical_position_fraction'] * signal_strength
# Step 4: Apply risk management overlays
max_position_fraction = min(
adjusted_kelly,
0.25, # Hard cap: never risk more than 25% of capital
max_drawdown_limit # Reduce sizing if near drawdown limit
)
position_dollars = current_capital * max_position_fraction
return {
"expected_value": ev,
"kelly_fraction": kelly_result['practical_position_fraction'],
"adjusted_fraction": adjusted_kelly,
"final_position_fraction": max_position_fraction,
"position_dollars": position_dollars,
"has_edge": kelly_result['has_positive_edge'] and ev > 0
}
The Hard Truth About Short-Term Results
One of the most psychologically difficult aspects of trading with probabilistic thinking is the disconnect between short-term outcomes and long-term expectations. With a 55% win rate and 1.2:1 reward-to-risk ratio, you should win $19 per $100 risked over the long run. But over 50 trades, you might lose money—not because the strategy is broken, but because variance had an unfavorable run.
The Law of Large Numbers guarantees convergence, but it does not guarantee convergence on any given finite sample. A coin flipped 100 times could easily show 60 heads, even though the true probability is 50%. The sample mean is not the true mean; it is an estimate with its own distribution.
This is why disciplined quantitative traders track realized expected value over time and compare it against their theoretical expectation. If the gap between realized and theoretical EV widens beyond what statistical tests can explain, there is likely a structural change in the market or the strategy that requires investigation.
The practical response to short-term losses when you have a positive edge is not to increase position size to "make it back." That is anti-Kelly behavior and increases the probability of ruin. The practical response is to continue executing the strategy as designed, trusting the Law of Large Numbers, while monitoring the statistical significance of any accumulating shortfall.
Summary: Three Principles, One Framework
The probabilistic foundation of quantitative trading rests on three concepts that work together:
| Concept | Core Question | Practical Application |
|---|---|---|
| Expected Value | Does my strategy make money on average across all possible outcomes? | EV decomposition, win/loss ratio analysis, factor construction |
| Law of Large Numbers | How many trades do I need before my realized results converge to my true edge? | Minimum sample sizes for backtests, evaluation windows, patience management |
| Kelly Criterion | How much should I risk per trade to maximize geometric growth? | Position sizing, drawdown management, fractional Kelly for robustness |
Together, these principles transform trading from a series of high-stakes bets into a systematic process where each individual trade is just one observation from a distribution. The goal is not to win every trade—or even every month. The goal is to construct a positive-EV strategy, size positions according to Kelly, execute with discipline, and let the Law of Large Numbers compound your edge over time.
That is the first principle of quantitative trading: thinking in probabilities.
This article does not constitute investment advice. Markets involve risk; past performance does not guarantee future results.