RainbowStats · Finance
From a Bike Ride to an Options Replay
Price the contracts. Solve for the strike. Follow the value and the hedge through history.
On my daily bike ride, I do code in my mind. On a recent ride, I found myself thinking: How can I backtest or validate option strategies using historical data?
During last night’s Brewers game, I wrote code to do exactly that: reconstruct theoretical contract values and hedge exposures along a historical market path.
I wanted to keep a contract’s strike and expiration fixed, feed it historical spot prices, and watch its value and delta change as expiration approached. Then I added another question: Instead of choosing every strike by hand, could the pricing model choose the short strike needed to fund a long option?
The workflow now brings together five RainbowStats tools:
| Command | Job |
|---|---|
FORWARD_SERIES | Reconstruct a fixed-maturity outright forward. |
EUROPEAN_FFT | Build an option-price grid from a characteristic function. |
SOLVE_STRIKE | Select a short strike for a target net premium. |
OPTION_SERIES | Replay fixed-strike, fixed-expiry European option values. |
OPTION_DELTA_SERIES | Replay the corresponding spot deltas. |
Open the complete interactive example and 14-slide presentation →
One market path. One fixed expiration.
The example uses daily USD/JPY observations from January 2 through July 31, 2024. Every contract matures on July 31. DEXJPUS is quoted in Japanese yen per U.S. dollar, so these are calls and puts on the dollar against yen.
I assume 10% annual volatility, a 0.5% JPY rate, and a 5% USD rate. These constants make the mechanics easy to inspect. They are illustrative inputs, not historical option quotes. The series tools can also accept dated inputs for the relevant volatility, rates, and yield.
First, watch the forward meet spot
S=SET_NAME(SERIES_BETWEEN(DEXJPUS,20240102,20240731),"USD/JPY spot")
F=SET_NAME(FORWARD_SERIES(S,20240102,20240731,0.005,0.05),"July 31 forward")
LINE_CHART(S,F)The JPY rate is the financing input; the USD rate is the yield input. With the assumed USD rate above the JPY rate, the dollar forward is below spot. As delivery approaches, the carry period disappears.
FORWARD_SERIES uses simple net carry over the remaining calendar days, divided by 365.25. It constructs an outright forward-price series; it does not forecast future spot.
Let the FFT model select the short strike
For a 1-by-2 put ratio, I buy one 150 put and sell two lower-strike puts. This time, I ask the model to find the short strike that makes the initial net premium zero.
First, I define a Black-Scholes log-return characteristic function and pass it to EUROPEAN_FFT. The initial spot is 141.89, and January 2 to July 31 is 211 calendar days:
CF=SYM(EXP(i*U*(R-Q-0.5*SIG^2)*T-0.5*SIG^2*U^2*T))
MODEL=EUROPEAN_FFT(CF,S0,141.89,R,0.005,Q,0.05,T,211/365.25,ALPHA,1.5,N,16384,ETA,0.25,SIG,0.10,CF_RETURN,1)
TRADE=SOLVE_STRIKE(MODEL,150,1,2,0,"PUT")
KS=EXTRACT(TRADE,"strike")The solver’s inputs say: use this priced FFT model, buy one put at 150, sell two puts at an unknown strike, and target zero net premium. It searches the existing grid and interpolates the required strike.
| Leg | Quantity | Strike | Unit premium, ¥/$ |
|---|---|---|---|
| Long put | +1 | 150.000000 | 12.506115 |
| Short puts | −2 | 141.825970 | 6.253057 |
One long premium is funded by two short premiums. Using the unrounded grid values, the net-premium residual is approximately 8.9 × 10−15 yen per dollar. That verifies the solver’s grid equation; it does not establish that the grid is economically exact.
The trade result also supplies an expiry payoff chart and a premium-check panel:
EXTRACT(TRADE,"trade")
EXTRACT(TRADE,"payoff")
EXTRACT(TRADE,"premium_check")Zero initial premium leaves substantial exposure. Below the selected short strike, the position behaves like an additional short put plus a fixed amount. A zero-premium trade still carries market risk and may require margin.
Pass the solved strike into the historical replay
EXTRACT(TRADE,"strike") returns a number that goes directly into the series commands. The strike is solved once at inception and held fixed for the whole replay.
A=OPTION_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"PUT")
B=OPTION_SERIES(S,20240102,20240731,KS,0.10,0.005,0.05,"PUT")
RA=SET_NAME(A-2*B,"FFT-selected put ratio value")
LINE_CHART(RA)The comparison matters: the FFT and the historical series use matching Black-Scholes assumptions, but different numerical pricing paths. If I later use a different characteristic function in the FFT, passing its selected strike will not transfer that distribution into OPTION_SERIES.
For delta, use the same quantities—one long, two short:
DA=OPTION_DELTA_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"PUT")
DB=OPTION_DELTA_SERIES(S,20240102,20240731,KS,0.10,0.005,0.05,"PUT")
RD=SET_NAME(DA-2*DB,"FFT-selected put ratio delta")
LINE_CHART(RD)Then build a call butterfly
The butterfly buys the 150 and 160 calls and sells two 155 calls, all expiring July 31. The quantities are one, minus two, one:
C=OPTION_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"CALL")
D=OPTION_SERIES(S,20240102,20240731,155,0.10,0.005,0.05,"CALL")
E=OPTION_SERIES(S,20240102,20240731,160,0.10,0.005,0.05,"CALL")
BF=SET_NAME(C-2*D+E,"150/155/160 butterfly value")
LINE_CHART(BF)Its maximum expiry payoff is ¥5 per dollar at 155, with zero payoff at or outside the wings. The delta combines in exactly the same way:
DC=OPTION_DELTA_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"CALL")
DD=OPTION_DELTA_SERIES(S,20240102,20240731,155,0.10,0.005,0.05,"CALL")
DE=OPTION_DELTA_SERIES(S,20240102,20240731,160,0.10,0.005,0.05,"CALL")
BD=SET_NAME(DC-2*DD+DE,"Butterfly delta")
LINE_CHART(BD)Make the analysis tell its own story
TEXT_SLIDE, LINE_CHART, and SLIDESHOW combine the explanations and results. The shared presentation includes forward convergence, the FFT-selected trade, terminal payoff, premium verification, and separate historical value and delta charts for both strategies.
SLIDESHOW(TEXT_SLIDE("USD/JPY: solve and replay","Fixed expiry July 31, 2024; illustrative constant inputs."),LINE_CHART(S,F),EXTRACT(TRADE,"trade"),EXTRACT(TRADE,"payoff"),EXTRACT(TRADE,"premium_check"),LINE_CHART(RA),LINE_CHART(RD),LINE_CHART(BF),LINE_CHART(BD))What the replay validates
I can inspect convergence, verify the premium budget, check the strategy arithmetic, and follow the changing directional exposure. For a dollar notional, an offsetting spot hedge holds minus the combined delta times that notional.
These charts reconstruct theoretical position values and exposures. A self-financing replication P/L also needs the initial premium, hedge trades, cash balances, financing, and transaction costs. Historical spot with assumed volatility does not reproduce historical option quotes.
That is a useful foundation for backtesting: choose the pricing assumptions, construct the trade, verify its initial economics, and follow the same contracts through time. The next step is to add date-appropriate market inputs and the full trading ledger.
An idea from a bike ride—and some coding during a Brewers game—now connects strike selection, historical valuation, and hedge exposure in one reproducible analysis.
Run the complete RainbowStats showcase →
Appendix: the FFT model and Peter Carr’s contribution
Peter Carr and Dilip B. Madan’s 1999 paper, Option valuation using the fast Fourier transform, develops efficient European option pricing from a known risk-neutral characteristic function. Their approach damps the call-price function in log strike, expresses its Fourier transform analytically, and uses the FFT for numerical inversion. The paper also illustrates the method with the Variance Gamma model. [1]
FFT is a numerical method, not a choice of stochastic model. Here, the supplied characteristic function describes Black-Scholes log returns. With X equal to log(ST/S0), the model is:
X = (r − q − σ²/2)T + σ√T · Z, Z ~ N(0,1)
φX(u) = Eᴽ[exp(iuX)]
= exp[iu(r − q − σ²/2)T − σ²u²T/2]
In the command, R is the continuous JPY discount rate, Q is the continuous USD yield, SIG is annual volatility, and T is remaining years. CF_RETURN,1 tells the engine that this is a log-return characteristic function, rather than one for the log terminal price. Initial spot supplies the price scale.
In the Carr–Madan construction, let k be log strike and Φ the characteristic function of log terminal price. For damping α > 0:
cα(k) = exp(αk) · C(k)
ψα(v) = exp(−rT) · Φ(v − i(α + 1))
/ [α² + α − v² + i(2α + 1)v]
C(k) = exp(−αk)/π · ∫₀∞ Re[exp(−ivk)ψα(v)] dv
The FFT evaluates a discretized inversion across a grid of log strikes. Damping must respect the model’s finite-moment conditions. [1, §§3–4]
| Example setting | Role in this RainbowStats run |
|---|---|
ALPHA,1.5 | Positive damping parameter. |
N,16384 | Power-of-two FFT grid size. |
ETA,0.25 | Fourier-frequency spacing. |
CF_RETURN,1 | Interpret the supplied function as a log-return CF. |
SOLVE_STRIKE then works on the priced grid. For this trade it solves P(150) − 2P(KS) = 0 by local linear interpolation. It does not estimate the return distribution or rebuild the FFT. The numerical residual checks this interpolation equation, so I also compare the first historical valuation and would reprice after rounding to an available strike.
The characteristic function is risk-neutral: it is used for valuation, not as an estimated probability forecast from the historical spot sample. A richer FFT model does not automatically carry into the Black-Scholes historical series. For consistent validation, both stages must use matching assumptions or be repriced with a common model.
Finally, the separate forward command uses simple net carry, whereas the option model uses continuous carry and discounting. The convergence chart demonstrates the forward command’s convention; it should not be treated as an exact put–call parity check against the option model.
Reference [1]. Carr, Peter, and Dilip B. Madan (1999). “Option valuation using the fast Fourier transform.” Journal of Computational Finance, 2(4), 61–73. DOI: 10.21314/JCF.1999.043. Read the paper.
Complete tested 14-slide showcase script
S=SET_NAME(SERIES_BETWEEN(DEXJPUS,20240102,20240731),"USD/JPY spot")
F=SET_NAME(FORWARD_SERIES(S,20240102,20240731,0.005,0.05),"July 31 forward")
CF=SYM(EXP(i*U*(R-Q-0.5*SIG^2)*T-0.5*SIG^2*U^2*T))
MODEL=EUROPEAN_FFT(CF,S0,141.89,R,0.005,Q,0.05,T,211/365.25,ALPHA,1.5,N,16384,ETA,0.25,SIG,0.10,CF_RETURN,1)
TRADE=SOLVE_STRIKE(MODEL,150,1,2,0,"PUT")
KS=EXTRACT(TRADE,"strike")
A=OPTION_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"PUT")
B=OPTION_SERIES(S,20240102,20240731,KS,0.10,0.005,0.05,"PUT")
RA=SET_NAME(A-2*B,"FFT-selected put ratio value")
RD=SET_NAME(OPTION_DELTA_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"PUT")-2*OPTION_DELTA_SERIES(S,20240102,20240731,KS,0.10,0.005,0.05,"PUT"),"FFT-selected put ratio delta")
C=OPTION_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"CALL")
D=OPTION_SERIES(S,20240102,20240731,155,0.10,0.005,0.05,"CALL")
E=OPTION_SERIES(S,20240102,20240731,160,0.10,0.005,0.05,"CALL")
BF=SET_NAME(C-2*D+E,"150/155/160 butterfly value")
BD=SET_NAME(OPTION_DELTA_SERIES(S,20240102,20240731,150,0.10,0.005,0.05,"CALL")-2*OPTION_DELTA_SERIES(S,20240102,20240731,155,0.10,0.005,0.05,"CALL")+OPTION_DELTA_SERIES(S,20240102,20240731,160,0.10,0.005,0.05,"CALL"),"150/155/160 butterfly delta")
G=SET_TITLE(LINE_CHART(S,F),"Spot and July 31 forward converge at maturity")
H=SET_TITLE(LINE_CHART(RA),"1-by-2 put ratio: theoretical position value")
J=SET_TITLE(LINE_CHART(RD),"1-by-2 put ratio: spot delta")
K=SET_TITLE(LINE_CHART(BF),"Call butterfly: theoretical position value")
L=SET_TITLE(LINE_CHART(BD),"Call butterfly: spot delta")
FFTTEXT=TEXT_SLIDE("Let the model choose the short strike","EUROPEAN_FFT prices options from a symbolic characteristic function. Here the model is Black-Scholes, matching the historical option-series assumptions.","Initial spot is 141.89 yen per dollar on January 2, 2024. The 211 calendar days to July 31 are converted to years using 365.25.","SOLVE_STRIKE takes a known 150 long put, quantities one and two, and a target net premium of zero. It finds the short put strike whose premium funds the long put.","The following panels show the selected trade, terminal payoff and premium verification. The payoff chart is across possible expiry spot prices, not a historical time series.")
INTRO=TEXT_SLIDE("Replay a contract through history","RainbowStats forwards, FFT strike selection and option series: USD/JPY from January 2 through July 31, 2024.","One fixed maturity: July 31, 2024. Historical spot from DEXJPUS, quoted in yen per U.S. dollar.","Illustrative constant inputs: 10% annual volatility, 0.5% JPY rate and 5% USD rate. Options are European calls and puts on USD against JPY.")
FTEXT=TEXT_SLIDE("Forward and spot: watch the carry disappear","FORWARD_SERIES applies the JPY financing rate minus the USD yield over the remaining time to delivery.","With these assumptions the dollar forward trades below spot. The gap closes as maturity approaches; forward and spot coincide at delivery.","This is a historical series of theoretical outright forward prices. Simple net carry is used; the series is not a forecast of future spot.")
RTEXT=TEXT_SLIDE("Replay the FFT-selected 1-by-2 spread","Buy one USD put at 150 and sell two USD puts at the strike returned by SOLVE_STRIKE. All options expire July 31.","The selected short strike is passed directly into OPTION_SERIES and OPTION_DELTA_SERIES using EXTRACT. Both price and delta use quantities one and minus two.","The FFT uses a Black-Scholes log-return characteristic function with the same volatility, rates, spot and remaining time as the initial historical valuation.","Check the first historical position value against the zero-premium FFT target. Small differences can arise because the FFT solver interpolates its strike grid while OPTION_SERIES uses Black-Scholes directly.","Zero initial premium does not eliminate risk. The extra short put retains downside exposure below the selected short strike.")
BTEXT=TEXT_SLIDE("Build a 150/155/160 call butterfly","Buy one USD call at 150, sell two USD calls at 155, and buy one USD call at 160, all expiring July 31.","Combine both prices and deltas with quantities one, minus two, one.","Expiry payoff peaks at 5 yen per dollar when spot is 155 and is zero at or outside the 150 to 160 wings.","The delta chart shows how the direction and size of the underlying exposure change as spot moves and time runs down.")
ENDTEXT=TEXT_SLIDE("From contract values to hedge exposure","The commands return ordinary time series: combine legs, chart values, and track delta using the same strategy arithmetic.","Values are in yen per dollar of underlying. Multiply by the USD notional of one strategy unit for yen position value.","An offsetting spot hedge holds minus the combined strategy delta in dollars per dollar of strategy notional.","These charts reconstruct theoretical values and exposures. A self-financing replication P/L also needs the initial premium, hedge trades, cash balances, financing and transaction costs.","Forward pricing uses simple net carry; option pricing uses Black-Scholes with continuous rates and yield. Volatility and rates are assumed constants for this illustration.")
SLIDESHOW(INTRO,FTEXT,G,FFTTEXT,EXTRACT(TRADE,"trade"),EXTRACT(TRADE,"payoff"),EXTRACT(TRADE,"premium_check"),RTEXT,H,J,BTEXT,K,L,ENDTEXT)