Skip to content

About

Delta-hedging backtester for basket call options (C# / .NET 10). Self-financing replication with periodic or threshold rebalancing, source-agnostic engine, 20 unit tests, output verified to 1e-14 against an independent re-simulation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Delta Hedging

Backtesting engine for the delta hedging of basket call options, in C# / .NET 10.

Sell a call on a weighted basket of shares, then replicate it day by day with a self-financing portfolio of the underlyings and cash. The engine replays a market path, maintains the hedge, and records how far the portfolio drifts from the option it is meant to replicate.

Test_1_1


What it does

  • Basket calls on 1 to n underlyings, payoff max(Σ wᵢ·Sᵢ − K, 0)
  • Two rebalancing rules — every N business days, or whenever an underlying has moved more than a threshold since the last rebalancing
  • Self-financing by construction — rebalancing only moves money between shares and cash; the portfolio value is unchanged at the instant of every update
  • Source-agnostic — the engine consumes a stream of daily price snapshots and does not know whether they came from a CSV or a simulator, so backtests and forward tests share every line of hedging logic
  • Verified — 20 unit tests, plus an independent re-simulation of the daily hedge that matches the engine's output to 7 × 10⁻¹⁴ over 124 days

The hedging model

The pricer takes today's date and spots and returns the option's theoretical price and its deltas — the sensitivity of the price to each underlying. The whole strategy is four rules:

V₀     = p₀                                   the initial capital is the option premium
qᵢ     = δᵢ                                   hold delta of each share
cash   = V − Σ qᵢ·Sᵢ                          whatever is not in shares is cash (usually borrowed)
V(t)   = Σ qᵢ·Sᵢ(t) + cash · e^(r·τ)          shares at today's prices, cash accrued since last rebalancing

On every rebalancing the value is measured before the composition changes, the quantities are replaced by the new deltas, and the cash absorbs the difference. The premium is used exactly once, on day one; after that the portfolio's value is its own, and the gap to the option price is the hedging error — measured, never corrected.

The pricer itself (Black-Scholes basket approximation, constant 2% risk-free rate, 252 business days per year, bank-holiday calendar) is a pre-built library shipped in lib/.

Architecture

src/CoreBacktester/
  Backtester.cs               the day-by-day loop
  Hedging/HedgeComposer        the only class that talks to the pricer
  PortfolioHandler/Portfolio   a cash amount and a composition — knows nothing about deltas
  Oracles/                     RegularOracle · SpotOracle · factory from the scenario file
  MarketData/                  CSV provider · simulated provider · shared date grouping
  Parameters/ · Output/        JSON in, JSON out
src/BacktestConsole/           three arguments, no logic
tests/CoreBacktester.Tests/    20 NUnit tests

The boundary that matters is between the mathematical model and the financial one. The pricer works in ordered arrays aligned on its own share ordering; the portfolio works in compositions keyed by share id. HedgeComposer is the single place where the two meet — it builds the pricer's spot array from a price snapshot, and turns the pricer's deltas back into a composition. Nothing else in the codebase sees an ordered array, so the ordering contract cannot be broken anywhere else.

Backtester depends only on interfaces: IMarketDataProvider, IRebalancingOracle, IPortfolio. Switching from a historical CSV to a simulated path, or from a periodic to a threshold oracle, is one line in Program.cs.

Results

Twelve scenarios in scenarios/: three single-underlying calls and three five-underlying baskets, each with both rebalancing rules on the same price path. Outputs are in results/, and the analysis notebook is in notebooks/.

scenario underlyings strike oracle days rebalancings V₀ final price final value tracking error
Test_1_1 1 10 daily 124 124 0.193 0.216 0.247 +16.0 %
Test_1_1_Spot 1 10 2 % move 124 19 0.193 0.216 0.239 +12.1 %
Test_1_2 1 9 daily 124 124 1.239 1.347 1.364 +1.4 %
Test_1_2_Spot 1 9 2 % move 124 20 1.239 1.347 1.363 +1.3 %
Test_1_3 1 12 daily 124 124 0.725 0.044 0.224 +24.9 %
Test_1_3_Spot 1 12 2 % move 124 26 0.725 0.044 0.205 +22.3 %
Test_5_1 5 12 every 3 days 199 67 1.487 0.000 0.074 +5.0 %
Test_5_1_Spot 5 12 2 % move 199 105 1.487 0.000 0.074 +5.0 %
Test_5_2 5 12 daily 244 244 1.200 0.000 0.037 +3.1 %
Test_5_2_Spot 5 12 2 % move 244 241 1.200 0.000 0.037 +3.1 %
Test_5_3 5 10 daily 244 244 0.936 2.776 2.882 +11.4 %
Test_5_3_Spot 5 10 2 % move 244 220 0.936 2.776 2.872 +10.3 %

Tracking error is the final value − price, relative to the initial option price. It reads large where that price is small: Test_1_1 starts out of the money at 0.19, so an absolute error of 0.03 is 16 %; the same option struck at 9 starts at 1.24 and shows 1.4 % for a comparable absolute error.

The deltas converge to the basket weights

Deep in the money the payoff is Σ wᵢ·Sᵢ − K, whose derivative with respect to each share is its weight. The hedge therefore ends up holding the basket itself — without that being coded anywhere.

deltas

Regular vs spot rebalancing

regular vs spot

On a single calm underlying the spot rule rebalances 19 times instead of 124 and lands slightly closer to the option. Discrete hedging error is path-dependent noise, so that is not a rule — but most daily rebalancings on a calm path are churn, and with transaction costs modelled the spot rule would win on both counts.

Verification

Check Result
Portfolio value at inception equals the option premium identical to all 17 digits
Rebalancing leaves the portfolio value unchanged within 10⁻¹⁰, over sequences of rebalancings
Cash accrual restarts from each rebalancing date unit-tested
Hedge is invariant to the insertion order of the price snapshot unit-tested, at portfolio and engine level
Final option price equals the contractual payoff max(basket − K, 0) exact, all 12 scenarios
Engine output vs an independent Python re-simulation of the daily hedge max gap 7.1 × 10⁻¹⁴ over 124 days
Final deltas of a deep-in-the-money basket vs its weights equal to 4 decimals

The Python re-simulation also recovers the library's risk-free rate: fitting the accrual that reproduces the engine's value path gives 2.0000 %.

Running it

Requires the .NET 10 SDK.

dotnet build DeltaHedging.slnx -c Release
dotnet test  tests/CoreBacktester.Tests/CoreBacktester.Tests.csproj -c Release

dotnet src/BacktestConsole/bin/Release/net10.0/BacktestConsole.dll \
       scenarios/Test_1_1/params_1_1.json \
       scenarios/Test_1_1/data_1_1.csv \
       results/Test_1_1.json

All twelve scenarios:

for d in scenarios/*/; do n=$(basename "$d")
  dotnet src/BacktestConsole/bin/Release/net10.0/BacktestConsole.dll "$d"/*.json "$d"/*.csv "results/$n.json"
done

The notebook (pandas, matplotlib) reads results/ and regenerates every figure in docs/plots/.

Scenario files

params_*.json describes the option and the rebalancing rule:

{
  "basketOption":   { "strike": 10, "maturity": "2023-12-12T00:00:00",
                      "underlyingShareIds": ["share_1"], "weights": [1] },
  "pricingParams":  { "volatilities": [0.2], "correlations": [[1]] },
  "rebalancingOracleDescription": { "type": "Regular", "period": 1 }
}

data_*.csv is one row per share per business day (Id,DateOfPrice,Value). Weekends and bank holidays are absent from the grid, and the engine never generates dates of its own — it iterates what the file contains.

What this does not model

  • Transaction costs — the output carries a transactionCosts field, held at zero
  • Stochastic rates or volatility — both are constant in the pricer
  • Bid–ask — every fill is at the snapshot price

All twelve final gaps are positive. Over six independent paths that is suggestive, not conclusive, and consistent with simulated paths drifting above the risk-free rate — a delta-hedged short call is long the underlying and benefits from that drift.


Research and educational use. All figures are computed from the scenario files in this repository under the assumptions stated above.

About

Delta-hedging backtester for basket call options (C# / .NET 10). Self-financing replication with periodic or threshold rebalancing, source-agnostic engine, 20 unit tests, output verified to 1e-14 against an independent re-simulation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages