Appendices Chapter A

Appendix A: Mathematical Reference

This appendix collects the mathematical derivations and formulas referenced throughout the book. Formulas are presented in a form usable for implementation; full proofs are referenced to primary sources.


A.1 Wilder's smoothing

Used in ATR, RSI, and ADX. Equivalent to an exponential moving average with α = 1/N.

Wilder_t = ((N − 1) × Wilder_(t−1) + value_t) / N

For N = 14:

Wilder_t = (13 × Wilder_(t−1) + value_t) / 14

Equivalent to EMA with α = 1/14 ≈ 0.0714. The standard EMA formula EMA = α × value + (1 − α) × EMA_prev produces the same series with α = 1/N.

Initialization

Wilder smoothing is typically initialized with a simple average of the first N values:

Wilder_N = (value_1 + value_2 + ... + value_N) / N

After bar N, recursive formula applies.

Reference

Wilder, J. Welles. New Concepts in Technical Trading Systems. Trend Research, 1978.


A.2 ATR (Average True Range)

True range per bar:

TR_t = max(high_t − low_t,
           |high_t − close_(t−1)|,
           |low_t − close_(t−1)|)

ATR is Wilder-smoothed TR:

ATR_t = ((N − 1) × ATR_(t−1) + TR_t) / N

For N = 14, the most common period.

Variants

  • Initial bar: ATR_1 = TR_1 (no smoothing yet).
  • First N bars: ATR_N = mean(TR_1, ..., TR_N).
  • After N bars: recursive formula above.

A.3 Bulk Volume Classification (BVC)

Easley, López de Prado, O'Hara (2012). Inferring trade direction from OHLCV bars when tick-side data is unavailable.

return_t = (close_t − close_(t−1)) / close_(t−1)   (or log return)
σ_t = rolling standard deviation of returns over recent W bars
z_t = return_t / σ_t

fraction_buy_t = Φ(z_t)        where Φ is standard normal CDF
fraction_sell_t = 1 − Φ(z_t) = Φ(−z_t)

buy_volume_t = total_volume_t × fraction_buy_t
sell_volume_t = total_volume_t × fraction_sell_t
delta_t = buy_volume_t − sell_volume_t

Notes

  • The window W for σ_t computation: typically 50 to 200 bars on intraday data.
  • Φ is implemented in standard libraries (scipy.stats.norm.cdf in Python; pnorm in R).
  • BVC's accuracy correlates 0.85 to 0.95 with true tick-rule delta on liquid bars.

Reference

Easley, D., López de Prado, M., O'Hara, M. "Flow Toxicity and Liquidity in a High-Frequency World." Review of Financial Studies 25 (5), 2012.


A.4 Volume Weighted Average Price (VWAP)

Cumulative across the session (or anchor):

VWAP_t = Σ (price_i × volume_i) / Σ volume_i      for i = anchor..t

Where price_i is the typical price (high_i + low_i + close_i) / 3, or alternatively the average traded price of bar i.

Volume-weighted standard deviation (for bands)

σ²_VWAP_t = Σ volume_i × (price_i − VWAP_t)² / Σ volume_i
σ_VWAP_t = sqrt(σ²_VWAP_t)

Bands at ±k × σ_VWAP for k = 1, 2, etc.

Anchored VWAP

Identical formula with anchor index changed:

AVWAP_t (anchor a) = Σ (price_i × volume_i) / Σ volume_i      for i = a..t

A.5 Bollinger Bands and BBW

Standard 20-period, 2σ:

middle_t = SMA(20, close_t)
σ_t = standard deviation of last 20 closes
upper_t = middle_t + 2σ_t
lower_t = middle_t − 2σ_t
BBW_t = (upper_t − lower_t) / middle_t

Percentile calculation

For BBW percentile over rolling window W:

BBW_percentile_t = rank of BBW_t among BBW values in [t−W, t] / W × 100

Typical W = 20 to 60 days for daily charts; 78 bars (≈ 1 RTH session) for intraday.


A.6 Yang-Zhang Volatility Estimator

Yang and Zhang (2000). Composite estimator that minimizes variance under zero-drift assumption.

σ²_overnight_t = (ln(open_t / close_(t−1)))²
σ²_open_to_close_t = (ln(close_t / open_t))²
σ²_RS_t = ln(high_t/close_t) × ln(high_t/open_t) + ln(low_t/close_t) × ln(low_t/open_t)

(Rogers-Satchell estimator, drift-independent.)

The composite:

σ²_YZ_t = σ²_overnight_t + k × σ²_open_to_close_t + (1 − k) × σ²_RS_t

Where:

k = 0.34 / (1.34 + (n + 1) / (n − 1))

n is the number of periods in the rolling window. For typical daily windows of 21 to 30 days, k is approximately 0.20 to 0.25.

The annualized YZ volatility:

σ_YZ_annualized = sqrt(252 × mean(σ²_YZ over window))

Reference

Yang, D., Zhang, Q. "Drift-Independent Volatility Estimation Based on High, Low, Open, and Close Prices." Journal of Business 73 (3), 2000.


A.7 Kaufman Efficiency Ratio (KER)

KER_t = |close_t − close_(t−N)| / Σ |close_i − close_(i−1)|     for i = t−N+1..t

Bounded between 0 (pure noise) and 1 (perfect linear trend). Standard N = 10 for intraday, 20 for daily.


A.8 ADX (Average Directional Index)

Wilder's construction.

Step 1: Directional movement

upMove_t = high_t − high_(t−1)
downMove_t = low_(t−1) − low_t

if upMove_t > downMove_t and upMove_t > 0:
    +DM_t = upMove_t
else:
    +DM_t = 0

if downMove_t > upMove_t and downMove_t > 0:
    −DM_t = downMove_t
else:
    −DM_t = 0

Step 2: Smoothed DM and TR (Wilder's smoothing, N = 14)

+DI_t = 100 × Wilder_smoothed(+DM, 14) / Wilder_smoothed(TR, 14)
−DI_t = 100 × Wilder_smoothed(−DM, 14) / Wilder_smoothed(TR, 14)

Step 3: Directional Index and ADX

DX_t = 100 × |+DI_t − −DI_t| / (+DI_t + −DI_t)
ADX_t = Wilder_smoothed(DX, 14)

Notes

ADX is direction-blind. To use directionally, pair with the sign of (+DI − −DI) or with price trend.

Reference

Wilder, J. Welles. New Concepts in Technical Trading Systems. Trend Research, 1978.


A.9 Kelly Criterion and Fractional Kelly

For a strategy with win probability p, loss probability q = 1 − p, and odds b (R-multiple of win to loss):

f* = (p × b − q) / b

f* is the optimal fraction of capital to bet per trade for long-run geometric growth.

Fractional Kelly

Common practice: - Half Kelly: f = 0.5 × f. - Quarter Kelly: f = 0.25 × f.

Reduces drawdown variance at the cost of long-run growth.

Worked example

For p = 0.55, b = 1.5:

f* = (0.55 × 1.5 − 0.45) / 1.5 = 0.825/1.5 − 0.45/1.5 = 0.55 − 0.30 = 0.25

Full Kelly = 25% of capital per trade. Half Kelly = 12.5%. Quarter Kelly = 6.25%.

Bayesian adjustment

For limited sample size, use the lower confidence bound on p rather than the point estimate. The 95% Wilson lower bound for p̂ from n trials:

p_lower = (p̂ + z²/(2n) − z × sqrt(p̂(1−p̂)/n + z²/(4n²))) / (1 + z²/n)

Where z = 1.96 for 95% confidence. For p̂ = 0.55, n = 100: p_lower ≈ 0.45. Resulting f* would be (0.45 × 1.5 − 0.55) / 1.5 = 0.075, much smaller.

Reference

Kelly, J. L. "A New Interpretation of Information Rate." Bell System Technical Journal 35, 1956.


A.10 Sharpe Ratio and Deflated Sharpe Ratio

Standard Sharpe

SR = (mean_return − risk_free_rate) / σ_return

Annualized:

SR_annualized = SR_per_period × sqrt(periods_per_year)

For daily returns: multiply by sqrt(252).

Deflated Sharpe Ratio (DSR)

Bailey, López de Prado (2014). Adjusts the observed Sharpe for the number of trials and non-normality.

SR_max_expected_under_null = sqrt(2 × ln(N)) × σ_SR_null

Where N is the number of strategies tested under null.

DSR = (SR_observed − SR_max_expected_under_null) / σ_SR_observed

The denominator σ_SR_observed accounts for skewness, kurtosis, and autocorrelation of the strategy's returns.

Implementation

The mlfinlab Python library implements DSR. The standard procedure:

  1. Compute observed Sharpe.
  2. Estimate the number of trials N (count parameter combinations and rule variants tested).
  3. Compute σ_SR_null based on null Sharpe distribution.
  4. Compute σ_SR_observed based on the strategy's own return characteristics.
  5. Apply the formula.

DSR > 0.95 indicates the observed Sharpe substantially exceeds chance given the trial count.

Reference

Bailey, D., López de Prado, M. "The Deflated Sharpe Ratio: Correcting for Selection Bias, Backtest Overfitting, and Non-Normality." Journal of Portfolio Management 40 (5), 2014.


A.11 Probability of Backtest Overfitting (PBO)

Bailey, Borwein, López de Prado, Zhu (2014, 2016). Computed via combinatorially symmetric cross-validation (CSCV).

Procedure

  1. Split the backtest period into S segments (typical S = 16).
  2. For each combinatorial split (S/2 in-sample segments, S/2 out-of-sample segments):
  3. Fit parameters on in-sample segments.
  4. Compute in-sample Sharpe ranking of strategy variants.
  5. Compute out-of-sample Sharpe ranking on the same variants.
  6. Record (in-sample rank, out-of-sample rank) pairs.
  7. Compute PBO = probability that in-sample rank-1 has out-of-sample rank below median.

Interpretation

  • PBO < 0.2: robust strategy.
  • PBO ~ 0.5: highly overfit (in-sample best is approximately uninformative for out-of-sample).
  • PBO > 0.5: anti-fit (in-sample best is below-median out-of-sample).

Reference

Bailey, D., Borwein, J., López de Prado, M., Zhu, Q. J. "The Probability of Backtest Overfitting." Journal of Computational Finance 20 (4), 2016.


A.12 Position Sizing Formula

contracts = floor(account × per_trade_risk_pct / (stop_distance_ticks × tick_value))

Worked through in Chapter 17 with examples for ES, NQ, GC, CL.

Multi-position aggregation

For correlated positions (correlation > 0.7):

combined_risk = single_position_risk × (1 + 0.5 × additional_positions × avg_correlation)

This is approximate; the rigorous version uses portfolio variance:

σ²_portfolio = Σ w_i² σ_i² + 2 Σ_{i<j} w_i w_j ρ_{ij} σ_i σ_j

For practical sizing, the approximate formula is sufficient.


A.13 Confidence Intervals on Win Rate

For an observed win rate p̂ from n trials, the 95% Wilson confidence interval:

center = (p̂ + z²/(2n)) / (1 + z²/n)
margin = z × sqrt(p̂(1−p̂)/n + z²/(4n²)) / (1 + z²/n)
CI_lower = center − margin
CI_upper = center + margin

Where z = 1.96 for 95% confidence.

For sizing decisions: use CI_lower as the win rate input, not p̂.

Sample size requirements

To estimate p with margin of error E at 95% confidence:

n ≥ z² × p̂(1−p̂) / E²

For p̂ = 0.55, E = 0.05: n ≥ 1.96² × 0.55 × 0.45 / 0.05² ≈ 380.

This is the basis for the institutional rule that 200 to 400+ trades are required for sizing decisions.


A.14 Stop Distance Optimization

For a structural setup, the optimal stop distance balances: - Tighter stops: lower per-trade risk, higher win rate-required to break even. - Wider stops: higher per-trade risk, lower win rate-required.

The Sharpe-maximizing stop distance is empirical (varies by setup), but a common heuristic:

optimal_stop_atr_multiple = some_function(setup_volatility, slippage_assumption)

For most book frameworks: stops in the range 1× to 1.5× ATR are typical optima.


A.15 Drawdown and Recovery Math

Recovery requirement

For a drawdown of d:

recovery_required = 1/(1 − d) − 1

Examples: - d = 0.10: recovery = 0.111 (11.1%). - d = 0.25: recovery = 0.333 (33.3%). - d = 0.50: recovery = 1.00 (100%). - d = 0.75: recovery = 3.00 (300%).

Geometric mean return constraint

For a sequence of returns r_1, r_2, ..., r_n, the geometric mean:

GM = ((1 + r_1) × (1 + r_2) × ... × (1 + r_n))^(1/n) − 1

A single large negative return reduces GM disproportionately. For a strategy with mean arithmetic return μ and variance σ²:

GM ≈ μ − σ²/2

This is why higher-variance strategies underperform their arithmetic mean. The variance drag is the operational cost of volatility.


A.16 Walk-Forward Sample Size Requirements

For meaningful out-of-sample testing:

  • W_test ≥ 30 trades minimum to have any statistical confidence.
  • W_test ≥ 100 trades for reasonable Sharpe estimation.
  • W_train ≥ 4 × W_test to reduce parameter fitting noise.

Typical institutional defaults: - W_train = 250 to 500 trades. - W_test = 50 to 100 trades. - Number of rolls = 4 to 10.


A.17 Multiple-Testing Correction

Bonferroni

For N tests at significance α:

corrected_α = α / N
corrected_p_value = min(observed_p × N, 1)

Conservative; may discard real edges.

Holm

Rank p-values: p_1 ≤ p_2 ≤ ... ≤ p_N.

For each i:

threshold_i = α / (N − i + 1)

Reject p_i if p_i ≤ threshold_i, sequentially. Stop at the first non-rejected.

Benjamini-Hochberg (FDR)

For N tests:

threshold_i = (i / N) × α

Reject p_i for the largest i such that p_i ≤ threshold_i. Controls expected false discovery rate.

For strategy validation, Bonferroni is the default (asymmetric cost of false positive). FDR is acceptable for exploratory research.


A.18 Implementation note on Pine Script v6

For Pine Script v6 implementations of these formulas:

  • Wilder smoothing: ta.rma(value, period) is the built-in equivalent.
  • ATR: ta.atr(period) returns Wilder-smoothed ATR.
  • RSI: ta.rsi(value, period) returns Wilder's RSI.
  • ADX: ta.dmi(period_len, smoothing_len) returns +DI, −DI, ADX.
  • Standard deviation: ta.stdev(value, period) for the Bollinger band σ.
  • VWAP: ta.vwap(value) is built-in but session-anchored only; for AVWAP, manual implementation required.

For Python implementations, pandas, numpy, and talib cover most of the above. For walk-forward and DSR, mlfinlab is the comprehensive reference.


This appendix is a quick reference. For full derivations, consult the primary sources cited.