fincl
Table of Contents
- 1 Getting Started
- 2 Options
- 3 Pricing engines
- 4 Stochastic processes
- 5 Extending fincl
[in package FINCL/DOCGEN]
fincl is a Common Lisp library for quantitative finance.
This documentation and the library is a work in progress. API is subject to change without notice.
1 Getting Started
Acquire the dependencies for fincl and the library itself and load it.
Assuming you have registered the package locally to quicklisp, you can load
the library
(ql:quickload :fincl)
(defpackage #:fun-in-fincl (:use #:cl #:fincl))
(in-package #:fun-in-fincl)
;;; Literals for dates and tenors
(named-readtables:in-readtable fincl:syntax)
The closest thing to a "Hello, World!" of a quantitative finance library is pricing a European option with the Black-Scholes formula.
(defconstant +today+ #D"2026-10-01")
(defparameter *market*
(make-bs-market :valuation-date +today+
:spot 100 :rate 0.04d0
:dividend 0.01d0
:vol 0.2d0))
(defparameter *option*
(make-option (call 110) (expires #T"1Y" :from +today+)))
(price *option* (make-engine :black-scholes) *market*)
;; ==> 5.240727867335534d0
;; NIL
;; #<ENGINE (make-engine :black-scholes)>
2 Options
An option is contract giving the holder the right, but not the obligation, to exercise a purchase or sale of an underlying asset at an agreed-upon price. Options have finite lifetime, and carry various exercise rights. e.g. European options can be exercised on the expiration date, while American options can be exercised at any point before the expiration.
Besides exercise rights, the primary difference of option types is how their payoff is assessed. The major supported option payoff schemes are presented in this section. The user may write their own option payoff schemes (see TODO).
Throughout, $K$ denotes the strike price of the option. $t\in [0, T]$ denotes a time between the inception of the option $0$ and the expiration date $T$ (measured in years), and $S_t$ denotes the price of the underlying asset at time $t$. $\phi$ will $\pm 1$ for calls/puts, respectively.
2.1 Vanilla Options
$$\max(\phi (S_{T} - K) , 0).$$
Exercise is European unless the option says otherwise (see make-option).
[class] FINCL:VANILLA-PAYOFF FINCL:STRIKE-PAYOFF
max(phi (S - K), 0). Engines with closed forms specialize on this class.
(make-instance 'vanilla-payoff :kind :put :strike 40d0)
2.2 Asian Options
With $A$ the average of $S_t$ over the observation dates $t_1,\dots,t_n$ (arithmetic $\frac{1}{n}\sum_i S _{t_i}$ or geometric $(\prod_i S _{t_i})^{1/n}$):
fixed strike: $\max(\phi (A - K), 0)$
floating strike: $\max(\phi (S_T - A), 0)$
European exercise only.
[class] FINCL:ASIAN FINCL:OBSERVED-PATH
The payoff is applied to the average of the observable on DATES (arithmetic or geometric, equal weights). A
:FIXEDstrike pays the payoff of the average; a:FLOATINGstrike pays max(phi (S_T - A), 0).
2.3 Barrier Options
The vanilla payoff, conditional on the path touching a level $H$ before $T$, monitored continuously or on a list of dates. With rebate $R$:
knock-out: the payoff if $H$ is never touched; $R$, paid at the touch, otherwise
knock-in: the payoff if $H$ is touched; $R$, paid at $T$, otherwise
The type is one of :down-and-out, :down-and-in, :up-and-out,
:up-and-in. European exercise only.
[class] FINCL:BARRIER FINCL:OBSERVED-PATH
A single barrier at LEVEL. A knock-out pays nothing once the observable has touched it and pays REBATE when it does; a knock-in pays only if it has, and REBATE at expiry otherwise. Monitored continuously, or on DATES.
2.4 Lookback Options
With $m = \min_t S_t$ and $M = \max_t S_t$ over the observation dates (every simulation step by default):
floating strike: call $S_T - m$, put $M - S_T$
fixed strike: call $\max(M - K, 0)$, put $\max(K - m, 0)$
European exercise only.
[class] FINCL:LOOKBACK FINCL:OBSERVED-PATH
The extremum of the observable: a
:FLOATINGstrike pays phi (S_T - extremum), the minimum for a call and the maximum for a put; a:FIXEDstrike pays the payoff of the maximum (call) or minimum (put).
2.5 make-option
(make-option clause...) builds an option from clauses, which may come in
any order. Arguments of a clause are evaluated.
payoff:
(call K)or(put K); one is requiredexercise:
(european)(default),(american), or(bermudan :dates dates)/(bermudan :every tenor :from date)(expires date), or(expires tenor :from date); required, except thatbermudan :datestakes the last datepath feature, optional:
(average :arithmetic :dates dates)(or:geometric,:every tenor :from date,:strike-type :floating),(barrier :down-and-out level :rebate R :monitoring dates)(continuous by default),(lookback :floating)(or:fixed)(underlying name)and(currency name), when the market has several
(make-option (put 95) (american) (expires #T"1Y" :from +today+))
(make-option (call 100) (barrier :up-and-out 130 :rebate 2) (expires #D"2027-06-15"))
(make-option (call 100) (average :arithmetic :every #T"1M" :from +today+)
(expires #T"1Y" :from +today+))
make-option-from-clauses takes the same clauses as a list, for building
options from data.
[macro] FINCL:MAKE-OPTION &REST CLAUSES
Build an option from clauses: a kind (call/put) and strike or a payoff scheme, an expiry, and optionally exercise rights. With no payoff clause the payoff is vanilla; with no exercise clause the option is European.
A vanilla European call K=110 with 1Y expiry (make-option (call 110) (expiry #
T"1Y" :from +today+)) Clause arguments are evaluated.
[function] FINCL:MAKE-OPTION-FROM-CLAUSES CLAUSES
Build an option from clauses: a kind (call/put) and strike or a payoff scheme, an expiry, and optionally exercise rights. With no payoff clause the payoff is vanilla; with no exercise clause the option is European.
A vanilla European call K=110 with 1Y expiry (make-option (call 110) (expiry #
T"1Y" :from +today+)) CLAUSES is a list of (head . arguments), as data.
3 Pricing engines
(price instrument engine market) returns (values npv standard-error engine).
An instrument is an instance of an instrument: an option, bond, a swap.
An engine is an instance, a keyword designator, a list (:cos :n-terms 512),
a process (priced by the first engine that can), or :auto, which tries the
engines below in order of speed and accuracy and falls back to Monte Carlo with
a warning. Build one with make-engine, which evaluates its arguments.
(make-engine :cos :process (make-heston) :n-terms 512)
(price option :auto market)
Designators:
:black-scholes: closed forms; European, barriers, geometric Asians:baw: American:merton-series: European, Merton jump diffusion:cev: European, CEV with $\beta\le 1$:binomial: American, Bermudan, GBM:cos: European, Bermudan, American; any process with a characteristic function:fd: European, Bermudan, American, barriers; GBM, CEV, local vol, Heston:mc: any exercise and process that can be simulated
[function] FINCL:MAKE-ENGINE KEY &REST SETTINGS
The engine designated by KEY (:black-scholes, :baw, :binomial, :cos, :mc, :merton-series, :cev, or any class's (:designator ...)) with SETTINGS, its initargs, evaluated.
:PROCESSgoes to engines that take one; an engine whose model is fixed (Black-Scholes, BAW) accepts a process only of its model, with no parameters set (it prices with the market's data).(make-engine :binomial :n-steps 4000) (make-engine :cos :process (make-heston :xi 0.5d0) :n-terms 512)
3.1 Black-Scholes
Closed forms in the forward, the discount factor and the total variance to expiry, so they hold under term structures of rates, dividends and vol. European vanillas; continuous barriers (Reiner-Rubinstein) and discrete monitoring through the continuity correction of Broadie, Glasserman and Kou; discrete geometric Asians.
See F. Black and M. Scholes, The pricing of options and corporate liabilities, Journal of Political Economy 81 (1973); E. Reiner and M. Rubinstein, Breaking down the barriers, Risk 4 (1991); M. Broadie, P. Glasserman and S. Kou, A continuity correction for discrete barrier options, Mathematical Finance 7 (1997); A. Kemna and A. Vorst, A pricing method for options based on average asset values, Journal of Banking and Finance 14 (1990).
[class] FINCL:BLACK-SCHOLES-ENGINE FINCL:ANALYTIC-ENGINE
Black's formula for European vanillas.
3.2 Barone-Adesi-Whaley
Quadratic approximation of the American premium over the European price. Returns the European price where early exercise is never optimal.
See G. Barone-Adesi and R. E. Whaley, Efficient analytic approximation of American option values, Journal of Finance 42 (1987).
[class] FINCL:BARONE-ADESI-WHALEY-ENGINE FINCL:ANALYTIC-ENGINE
Quadratic approximation for American vanillas.
3.3 Merton series
European vanillas under Merton jump diffusion as a Poisson-weighted sum of Black-Scholes prices conditional on the number of jumps.
See R. C. Merton, Option pricing when underlying stock returns are discontinuous, Journal of Financial Economics 3 (1976).
[class] FINCL:MERTON-SERIES-ENGINE FINCL:ANALYTIC-ENGINE
Merton's (1976) series for European vanillas under a jump diffusion: Black prices conditional on the number of jumps, weighted by their Poisson probabilities.
(make-instance 'merton-series-engine :process (make-instance 'merton-jump :intensity 1))
3.4 CEV formula
European vanillas under CEV with $\beta\le 1$ and absorption at zero, in terms of the noncentral $\chi^2$ distribution.
See R. Schroder, Computing the constant elasticity of variance option pricing formula, Journal of Finance 44 (1989).
[class] FINCL:CEV-ENGINE FINCL:ANALYTIC-ENGINE
Schroder's (1989) noncentral chi-square formula for European vanillas under CEV with absorption at zero, beta <= 1.
(make-instance 'cev-engine :process (make-instance 'cev :beta 0.5d0))
3.5 Binomial tree
Cox-Ross-Rubinstein tree for GBM. By default the last step is valued with the Black-Scholes formula to remove the odd-even oscillation (BBS), and the price is $2v(N)-v(N/2)$ (BBSR). Bermudan dates snap to the nearest step.
n-steps: steps in the treesmooth: Black-Scholes on the last stepextrapolate: Richardson extrapolation
See J. C. Cox, S. A. Ross and M. Rubinstein, Option pricing: a simplified approach, Journal of Financial Economics 7 (1979); M. Broadie and J. Detemple, American option valuation: new bounds, approximations, and a comparison of existing methods, Review of Financial Studies 9 (1996).
[class] FINCL:BINOMIAL-ENGINE FINCL:ENGINE
Cox-Ross-Rubinstein binomial tree.
(make-instance 'binomial-engine :n-steps 5000)
3.6 COS
Fourier-cosine expansion of the density of the log-return, with coefficients from the characteristic function. Europeans need only the characteristic function; Bermudans and Americans use the backward recursion, which needs state-independent log-increments, with the American price extrapolated from Bermudans of $M, 2M, 4M, 8M$ dates.
n-terms: terms in the expansiontruncation: half-width $L$ of the truncation range, in cumulant scalerichardson-dates: $M$
See F. Fang and C. W. Oosterlee, A novel pricing method for European options based on Fourier-cosine series expansions, SIAM Journal on Scientific Computing 31 (2008), and Pricing early-exercise and discrete barrier options by Fourier-cosine series expansions, Numerische Mathematik 114 (2009).
[class] FINCL:COS-ENGINE FINCL:ENGINE
Fang and Oosterlee's Fourier-cosine (
COS) method. Europeans need only a characteristic function; Bermudans and Americans use the backward recursion of their 2009 paper, which needs a process whose log-increments are independent of the state (the INDEPENDENT-INCREMENTS mixin; GBM).(make-instance 'cos-engine :process (make-instance 'heston) :n-terms 160)
3.7 Finite differences
Crank-Nicolson on a log-spot grid concentrated at the strike, with Rannacher start-up after each payoff discontinuity. Americans and Bermudans by Ikonen-Toivanen; barriers by truncating the grid or by events on the monitoring dates. Heston by Hundsdorfer-Verwer ADI on a (log-spot, variance) grid. Jump processes are refused.
n-space,n-variance,n-time: grid sizestheta: time-stepping weight, $\frac{1}{2}$ is Crank-Nicolsonrannacher-steps: implicit substeps after a discontinuityconcentration,width: grid concentration and half-width in standard deviations
See R. Rannacher, Finite element solution of diffusion problems with irregular data, Numerische Mathematik 43 (1984); S. Ikonen and J. Toivanen, Operator splitting methods for American option pricing, Applied Mathematics Letters 17 (2004); K. J. in 't Hout and S. Foulon, ADI finite difference schemes for option pricing in the Heston model with correlation, International Journal of Numerical Analysis and Modeling 7 (2010).
[class] FINCL:FD-ENGINE FINCL:ENGINE
Finite differences in log-spot: a theta-scheme with Rannacher start-up on a sinh-concentrated grid, Ikonen-Toivanen splitting for American exercise, continuous barriers as boundary conditions; Heston by Hundsdorfer-Verwer ADI.
(make-engine :fd :process (make-local-vol) :n-space 400 :n-time 400)
3.8 Monte Carlo
Simulates the paths of any process that defines a stepper. Terminal-only payoffs sample the terminal value exactly where the process allows; the rest step through a time grid containing every observation date. Americans and Bermudans by least-squares regression on the continuation value. Barriers monitored continuously are corrected with the Brownian bridge. Prices do not depend on the number of threads or chunks.
process,n-paths,n-steps,seedrng::counter(counter-based Philox),:sobol(randomized quasi-Monte Carlo) or arandom-stategeneratorantithetic: antithetic pairscontrol-variates::european,:geometric-asian
See P. Glasserman, Monte Carlo Methods in Financial Engineering (2004); F. A. Longstaff and E. S. Schwartz, Valuing American options by simulation: a simple least-squares approach, Review of Financial Studies 14 (2001); J. K. Salmon, M. A. Moraes, R. O. Dror and D. E. Shaw, Parallel random numbers: as easy as 1, 2, 3, Proceedings of SC11 (2011); S. Joe and F. Y. Kuo, Constructing Sobol sequences with better two-dimensional projections, SIAM Journal on Scientific Computing 30 (2008).
- [class] FINCL:MONTE-CARLO-ENGINE FINCL:ENGINE
4 Stochastic processes
A process is a stochastic differential equation. For the
purposes of financial modeling, these are typically drift-diffusion processes or Lévy processes.
However, fincl allows you to define your own (see TODO).
A general drift-diffusion process has the form $$dS_t = \mu(X_t, t) + \sigma(X_t,t)dW_t,$$ where $\mu(X_t,t)$ is called the drift, and $\sigma(X_t,t)$ is the diffusion. $W_t$ is a standard Brownian motion.
The Lévy processes add a jump term to drift-diffusion models dictated by a Poisson process. All processes forms are given under the risk-netural measure, where $r$ is the risk-free rate, $q$ is an equity dividend.
4.1 Drift-diffusion processes
4.1.1 Geometric Brownian motion
The classical drift-diffusion model of constant volatility. $$dS_t = (r - q) S_t \,dt + \sigma S_t\,dW_t.$$
[class] FINCL:GBM FINCL:STOCHASTIC-PROCESS FINCL:INDEPENDENT-INCREMENTS
Black-Scholes dynamics, with the market's rate, dividend and (by default) vol term structures.
[function] FINCL:MAKE-GBM &REST INITARGS &KEY VOL
A new gbm process: (make-instance 'gbm initarg...).
4.1.2 Constant elasticity of variance
$$dS_t = (r-q)\,S_t\,dt + \sigma S_t^{\beta}\,dW_t$$
$\beta$: elasticity of the diffusion in $S$
$\sigma$: scale of the diffusion
See J. C. Cox, The constant elasticity of variance option pricing model (1975).
[class] FINCL:CEV FINCL:STOCHASTIC-PROCESS
Constant-elasticity-of-variance dynamics.
[function] FINCL:MAKE-CEV &REST INITARGS &KEY BETA SIGMA SCHEME
A new cev process: (make-instance 'cev initarg...).
4.1.3 Dupire local volatility
$$\frac{dS_t}{S_t} = (r-q)\,dt + \sigma_{\mathrm{loc}}(S_t,t)\,dW_t$$
where $\sigma_{\mathrm{loc}}$ is the local volatility of an implied total variance surface $w(k,T)$, $k =\ln (K / F (T) )$, in Gatheral's form of Dupire's formula,
$$\sigma_{\mathrm{loc}}^2 =\frac{\partial _T w}{1 - \frac{k}{w}\partial _k w + \frac{1}{4}\left(-\frac{1}{4} - \frac{1}{w} + \frac{k^2}{w^2}\right)(\partial _k w)^2+ \frac{1}{2}\partial _{kk} w}.$$
The surface is the market's unless one is given.
[class] FINCL:LOCAL-VOL FINCL:STOCHASTIC-PROCESS
Dupire local volatility consistent with an implied-vol surface: Monte Carlo prices of Europeans reproduce the surface.
[function] FINCL:MAKE-LOCAL-VOL &REST INITARGS &KEY SURFACE SUBSTEPS
A new local-vol process: (make-instance 'local-vol initarg...).
4.1.4 Heston
[class] FINCL:HESTON FINCL:STOCHASTIC-PROCESS
Heston stochastic volatility model.
[function] FINCL:MAKE-HESTON &REST INITARGS &KEY V0 KAPPA THETA XI RHO SCHEME
A new heston process: (make-instance 'heston initarg...).
[generic-function] FINCL:MAKE-STEPPER PROCESS MARKET TIMES
Dispatch once on (process, market) and return a closure
(lambda (state normals j) ...)that advances STATE in place from
TIMEs[j] toTIMEs[j+1], consuming PROCESS-FACTORS normals.TIMEs is a (SIMPLE-ARRAYDOUBLE-FLOAT(*)) of grid times in market time, starting at 0 and possibly unevenly spaced. The closure is called once per path per step, so everything that can be hoisted belongs in the method body: per-interval drifts, variances and step lengths are computed there into typed vectors indexed by J. Rates enter through LOG-GROWTH over each interval, so the discounted observable is a martingale under any deterministic term structure. A process that is not resolved (see RESOLVE-PROCESS) is resolved at the last grid time.
4.1.5 NGARCH
$$\ln S_t = \ln S _{t-1} + (r-q)\Delta - \frac{1}{2} h _t + \sqrt{h _t}\,z _t, \qquad h _{t+1} = \omega + \beta h _t + \alpha\, h _t\,(z _t-\gamma )^2$$
with $z_t$ standard normal and period $\Delta$.
$\omega$: intercept of the variance
$\beta$: persistence of the variance
$\alpha$: sensitivity of the variance to shocks
$\gamma$: leverage, raising the next variance more after negative shocks
See J.-C. Duan, The GARCH option pricing model, Mathematical Finance 5 (1995).
[class] FINCL:GARCH FINCL:STOCHASTIC-PROCESS
Duan's NGARCH(1,1).
[function] FINCL:MAKE-GARCH &REST INITARGS &KEY PERIOD ALPHA BETA GAMMA OMEGA H0
A new garch process: (make-instance 'garch initarg...).
4.1.6 Hull-White
$$dr_t = \left(\theta(t) - a\,r_t\right)dt + \sigma\,dW_t$$
$a$: speed of mean reversion
$\sigma$: volatility of the short rate
$\theta(t)$: fitted to the initial yield curve
See J. Hull and A. White, Pricing interest-rate derivative securities, Review of Financial Studies 3 (1990).
[class] FINCL:HULL-WHITE FINCL:STOCHASTIC-PROCESS
Hull-White one-factor short-rate model, fitted to a yield curve. Priced through rates engines (
:JAMSHIDIAN,:HULL-WHITE-TREE); it does not drive an equity spot.
[function] FINCL:MAKE-HULL-WHITE &REST INITARGS &KEY A SIGMA
A new hull-white process: (make-instance 'hull-white initarg...).
4.2 Lévy processes
Lévy processes add jump terms to drift-diffusion models, accounting for random jumps seen in the price movement of various instruments. Throughout, $dN_t$ will denote a Poisson process with rate $\lambda$.
4.2.1 Merton Jump Diffusion
$$\frac{dS_t}{S_t} = (r-q-\lambda k)\,dt + \sigma\,dW_t + (e^{Y}-1)\,dN_t, \qquad Y \sim \mathcal{N}(\mu_J, \sigma_J^2),$$
with $k = e^{\mu_J + \sigma_J^2/2} - 1$.
$\sigma$: volatility of the diffusion
$\lambda$: jump intensity
$\mu_J$, $\sigma_J$: mean and standard deviation of the log jump $Y$
See R. C. Merton, Option pricing when underlying stock returns are discontinuous, Journal of Financial Economics 3 (1976).
[class] FINCL:MERTON-JUMP FINCL:JUMP-DIFFUSION
Merton's jump diffusion: GBM plus normally distributed log jumps at Poisson times.
[function] FINCL:MAKE-MERTON-JUMP &REST INITARGS &KEY VOL INTENSITY JUMP-MEAN JUMP-VOL
A new merton-jump process: (make-instance 'merton-jump initarg...).
4.2.2 Kou Jump Diffusion
Kou's jump-diffusion process dictated by $$\frac{dS_t}{S_t} = (r-q-\lambda)\, dt + \sigma \,dW_t + (e^{Y_t} - 1)\,dN_t.$$ See S.G. Kou, "A jump-diffusion model for option pricing"
[class] FINCL:KOU-JUMP FINCL:JUMP-DIFFUSION
Kou's jump diffusion: GBM plus double-exponential log jumps at Poisson times.
4.2.3 Variance Gamma
$$S_t = S_0\exp\left((r-q+\omega)\,t + \theta \, G_t + \sigma \, W_{G_t}\right), \qquad \omega = \frac1\nu\ln\left(1 - \theta\nu - \tfrac12\sigma^2\nu\right),$$
where $G$ is a gamma process with $\mathbb{E}[G_t] = t$ and $\operatorname{Var}[G_t] = \nu t$.
$\theta$: drift of the Brownian motion, setting the skewness
$\sigma$: volatility of the Brownian motion
$\nu$: variance rate of the gamma clock, setting the kurtosis
See D. B. Madan, P. P. Carr and E. C. Chang, The variance gamma process and option pricing, European Finance Review 2 (1998).
[class] FINCL:VARIANCE-GAMMA FINCL:EXPONENTIAL-LEVY FINCL:STOCHASTIC-PROCESS
The variance gamma process of Madan, Carr and Chang.
[function] FINCL:MAKE-VARIANCE-GAMMA &REST INITARGS &KEY SIGMA NU THETA
A new variance-gamma process: (make-instance 'variance-gamma initarg...).
4.2.4 Normal Inverse Gaussian
$$S_t = S_0\exp\left((r-q+\omega)\,t + \beta V_t + W_{V_t}\right), \qquad \omega = \delta\left(\sqrt{\alpha^2-(\beta+1)^2} - \gamma\right),$$
where $V$ is an inverse Gaussian process with $\mathbb{E}[V_t] = \delta t/\gamma$ and $\gamma = \sqrt{\alpha^2-\beta^2}$.
$\alpha$: heaviness of the tails, lighter as it grows
$\beta$: asymmetry, with $|\beta| < \alpha$
$\delta$: scale
See O. E. Barndorff-Nielsen, Processes of normal inverse Gaussian type, Finance and Stochastics 2 (1998).
[class] FINCL:NIG FINCL:EXPONENTIAL-LEVY FINCL:STOCHASTIC-PROCESS
Barndorff-Nielsen's normal inverse Gaussian process.
[function] FINCL:MAKE-NIG &REST INITARGS &KEY ALPHA BETA DELTA
A new nig process: (make-instance 'nig initarg...).
4.2.5 CGMY
$$S_t = S_0\exp\left((r-q+\omega)\,t + L_t\right), \qquad \omega = -C\,\Gamma(-Y)\left((M-1)^Y - M^Y + (G+1)^Y - G^Y\right),$$
where $L$ is a pure-jump process with Lévy density
$$\nu(x) = C\,\frac{e^{-G|x|}}{|x|^{1+Y}}\ (x<0), \qquad \nu(x) = C\,\frac{e^{-Mx}}{x^{1+Y}}\ (x>0).$$
$C$: overall activity
$G$, $M$: exponential decay of the left and right tails, with $M > 1$
$Y < 2$: fine structure of the jumps
See P. Carr, H. Geman, D. B. Madan and M. Yor, The fine structure of asset returns, Journal of Business 75 (2002).
[class] FINCL:CGMY FINCL:EXPONENTIAL-LEVY FINCL:STOCHASTIC-PROCESS
The CGMY tempered stable process.
[function] FINCL:MAKE-CGMY &REST INITARGS &KEY C G M Y
A new cgmy process: (make-instance 'cgmy initarg...).
4.2.6 Bates
$$\frac{dS_t}{S_t} = (r-q-\lambda k)\,dt + \sqrt{v_t}\,dW^1_t + (e^{Y}-1)\,dN_t, \qquad dv_t = \kappa(\theta - v_t)\,dt + \xi\sqrt{v_t}\,dW^2_t,$$
with $\mathrm{corr}(dW^1_t, dW^2_t) = \rho$, $Y \sim \mathcal{N}(\mu_J, \sigma_J^2)$ and $k = e^{\mu_J + \sigma_J^2/2} - 1$.
$v_0$, $\theta$: initial and long-run variance
$\kappa$: speed of mean reversion of the variance
$\xi$: volatility of the variance
$\rho$: correlation of the two Brownian motions
$\lambda$, $\mu_J$, $\sigma_J$: jump intensity, and mean and standard deviation of the log jump $Y$
See D. S. Bates, Jumps and stochastic volatility: exchange rate processes implicit in Deutsche mark options, Review of Financial Studies 9 (1996).
[class] FINCL:BATES FINCL:HESTON
Bates's model: Heston stochastic volatility plus Merton jumps.
[function] FINCL:MAKE-BATES &REST INITARGS &KEY V0 KAPPA THETA XI RHO SCHEME INTENSITY JUMP-MEAN JUMP-VOL
A new bates process: (make-instance 'bates initarg...).
5 Extending fincl
New currencies, calendars, processes and payoffs are defined with define-
macros, which write classes, constructors and the methods the engines call.
The dice game below uses all of them; each subsection defines one piece
and the last assembles them into a toy option contract pricing.
A fair die is rolled once a week. Players are paid in casino chips, and two contracts are written on it:
Jackpot: pays the face of the last roll, plus a bonus if it shows the top face. With a bonus of 10 on a six-sided die the expected payoff is $3.5 + 10/6$.
Bust game: after three weekly rolls it pays the running total, unless a 1 was rolled along the way, in which case the game ends and pays nothing. The expected payoff is $(5/6)^3 \cdot 12 \approx 6.944$, where 12 is the expected total of three rolls that are all at least 2.
Start with
(ql:quickload :fincl)
(defpackage #:dice (:use #:cl #:fincl))
(in-package #:dice)
(named-readtables:in-readtable fincl:syntax)
5.1 define-currency
Register a currency keyword with its name and the number of decimal places amounts are shown with. Options and markets refer to it by keyword.
(define-currency :chip :name "Casino chip" :fraction-digits 0)
[macro] FINCL:DEFINE-CURRENCY CODE &KEY NAME (FRACTION-DIGITS 2)
Register the currency CODE, a keyword, with its NAME and the number of FRACTION-DIGITS amounts are shown with.
(define-currency :usd :name "US Dollar" :fraction-digits 2)
5.2 define-calendar
Define a holiday calendar from rules: (:fixed month day),
(:nth-weekday n weekday month), (:easter offset), (:dates "YYYY-MM-DD"...)
and (:when predicate), each with :observe, :from, :to and :except.
The macro makes the class and the singleton +name+, which any function
taking a calendar accepts, e.g. (expires #T"2M" :from d :calendar +casino+).
define-observance adds a rule for holidays that fall on a weekend.
Our casino will be closed on weekends as well as Christmas and Dec 26, observing this holiday on the previous weekday if it happens to fall on a weekend.
(define-observance :previous-weekday (date)
(case (day-of-week date)
(6 (date+ date -1))
(7 (date+ date -2))
(t date)))
(define-calendar casino (weekends-only)
"Closed on Christmas Day, and on the 26th, observed on the previous weekday."
(:fixed 12 25)
(:fixed 12 26 :observe :previous-weekday))
[macro] FINCL:DEFINE-CALENDAR NAME (SUPERCLASS) &BODY BODY
Define the holiday calendar NAME: a class on SUPERCLASS (WEEKENDS-ONLY, or another holiday calendar, whose rules NAME inherits), the singleton +NAME+, and the holiday rules in BODY (after an optional docstring). Each rule is one of
(:fixed month day) a date every year (:nth-weekday n weekday month) e.g. 3 :monday 1; negative N counts from the month's end (-1 :monday 5: last Monday of May) (:easter offset) days from Easter Sunday (-2 Good Friday, 1 Easter Monday) (:dates "YYYY-MM-DD" ...) one-off closures (:when predicate) PREDICATE, evaluated, of (year month day)
and
:FIXED,:NTH-WEEKDAY,:EASTERand:WHENtake the options:OBSERVEkeyword (:none, :next-monday, :nearest-weekday, :sunday-to-monday, or one from DEFINE-OBSERVANCE),:FROMyear,:TOyear and:EXCEPT(years...). The weekend comes from SUPERCLASS.(define-calendar my-exchange (weekends-only) "An exchange closed on New Year's Day and Good Friday." (:fixed 1 1 :observe :sunday-to-monday) (:easter -2))
[macro] FINCL:DEFINE-OBSERVANCE NAME (DATE) &BODY BODY
Define the observance rule NAME: BODY maps the holiday's DATE to the date it is observed on, or
NILif it is not observed that year.(define-observance :sunday-to-monday (date) (if (= (day-of-week date) 7) (date+ date 1) date))
5.3 define-process
Define a custom process, its constructor make-name and its parameters,
which are validated, bounded and enumerable for calibration. The
dynamics are ordinary methods:
initial-state: the state vector at time zero. Element 0 is the observable that payoffs see; the others are latent.make-stepper: returns(lambda (state normals j)), which advances thestatevector in place fromtimes[j]totimes[j+1]using standard normals, and returnsNIL. Do expensive work outside the lambda.process-state-names: names for the state elements, which path payoffs refer to.
The constructor takes the parameters as keywords. process-parameter reads one.
Our process will model a fair die with faces faces. The state of the process
will consist of 2 values: the value of the most recent roll, and a running
total of all previous rolls.
(define-process fair-die (stochastic-process) (:state-size 2)
((faces 6d0 :bounds (2 nil) :doc "Number of faces."))
(:documentation "A die rolled once per step. Element 0 is the latest roll,
element 1 the running total."))
(defmethod initial-state ((p fair-die) market)
(declare (ignore market))
(make-array 2 :element-type 'double-float :initial-element 0d0))
(defmethod process-state-names ((p fair-die)) '(:spot :total))
(defmethod make-stepper ((p fair-die) market times)
(declare (ignore market times))
(let ((faces (process-parameter p :faces)))
(declare (type double-float faces))
(lambda (state normals j)
(declare (type (simple-array double-float (*)) state normals) (ignore j))
;; convert standard normal to discrete uniform U(1,FACES)
(let ((roll (+ 1d0 (min (- faces 1d0)
(ffloor (* faces (norm-cdf (aref normals 0))))))))
(setf (aref state 0) roll
(aref state 1) (+ (aref state 1) roll)))
nil)))
A parameter given as (name nil :market-default fn) is taken from the market
when left unset; (name default :parameter nil) is a setting, not a model
parameter.
[macro] FINCL:DEFINE-PROCESS NAME SUPERCLASSES (&KEY STATE-SIZE FACTORS) SLOTS &REST CLASS-OPTIONS
Define the process class NAME and its constructor MAKE-NAME. SUPERCLASSES defaults to (STOCHASTIC-PROCESS). STATE-SIZE and FACTORS, when given, define PROCESS-STATE-SIZE and PROCESS-FACTORS (both default to 1). Each of SLOTS is
(slot-name default &key bounds market-default (parameter t) doc reader type)
DEFAULT is the initform, evaluated per instance.
NIL, with a MARKET-DEFAULT, means "take it from the market".BOUNDS is (lo hi) in interval-designator form: a real is inclusive, (x) exclusive,
NILunbounded.MARKET-DEFAULT names a function (process market horizon) -> double-float.
PARAMETER
NILdeclares a setting: validated, but not a model parameter (not in PROCESS-PARAMETERS or PARAMETER-VECTOR).READERdefaults to SLOT-NAME, and the initarg is the keyword SLOT-NAME.TYPEdefaults toDOUBLE-FLOAT, or (OR(01)NULL(01)DOUBLE-FLOAT) when DEFAULT isNIL.
MAKE-NAME takes the initargs of NAME and its DEFINE-PROCESS superclasses as keywords, passing only those given, so omitted ones keep their defaults. The class option (:constructor nil) leaves it out (abstract classes); it signals CONSTRUCTOR-COLLISION at load time if MAKE-NAME is already a function it did not define for NAME. Steppers, characteristic functions and samplers stay ordinary
DEFMETHODs.(define-process heston (stochastic-process) (:state-size 2 :factors 2) ((v0 nil :market-default heston-default-v0 :bounds (0 nil) :doc "Initial variance.") (kappa 2d0 :bounds ((0) nil) :doc "Mean-reversion speed.")) (:documentation "Heston stochastic volatility."))
(make-heston :kappa 1.5d0)
5.4 define-payoff
Define a payoff class from a formula of the observable at expiry. Slots are
(name type &key initform doc fact); one without an initform is a required
initarg. The class becomes a clause of make-option taking its slots as
keywords. Calls and puts with a strike inherit from strike-payoff,
which supplies phi and strike to the formula.
(define-payoff jackpot (payoff)
((top double-float :initform 6d0 :doc "The face that wins the bonus.")
(bonus double-float :fact :bonus :doc "Paid on top of the face."))
(s)
(+ s (if (>= s top) bonus 0d0)))
Monte Carlo prices any such payoff. Closed-form engines only know the built-in
ones, so :auto falls back to simulation for new payoffs.
[macro] FINCL:DEFINE-PAYOFF NAME SUPERCLASSES SLOTS (OBSERVABLE) &BODY BODY
Define the payoff class NAME and derive PAYOFF-VALUE and PAYOFF-KERNEL from one formula. Each of SLOTS is (slot-name type &key initform reader doc fact); without INITFORM the initarg is required, and FACT names the construction fact that fills the slot (see MAKE-FROM-FACTS). BODY, optionally preceded by a docstring, computes the payoff from OBSERVABLE (a double-float) and may use every slot of NAME, and the variables its superclasses declare, as typed local variables. PAYOFF-KERNEL reads them once and closes over them, so the closure makes no generic call per evaluation.
Inherit from data classes such as CALL-PUT-PAYOFF or STRIKE-PAYOFF, not from a payoff with its own formula: engines with a closed form for the parent would price the subclass with it. Doing so signals FORMULA-PAYOFF-SUPERCLASS at macroexpansion.
(define-payoff digital (call-put-payoff) ((level double-float :doc "Pays 1 beyond this level.")) (s) (if (plusp (* phi (- s level))) 1d0 0d0))
5.5 define-path-payoff
Define a payoff that depends on the path, from clauses run on each path in
the order written. In them s is the observable at the current grid point.
(:accumulate (var init)...): variables kept along a path(:state var name):varis the process state element calledname(:start form...): at the valuation date(:on slot (i) form...): on each date in the date-list slot(:every form...): at every grid point(:final form): the amount paid at expiry
(pay amount) inside :on pays on that date, and (stop) ends the path,
which then pays nothing at expiry. The observation dates become points of the
simulation grid. Path payoffs are European and priced by Monte Carlo.
The bust-game will refer to the fair-die state :total, which
keeps the running total of die rolls. When the die is rolled on a date
in rolls, check if the die rolls 1, the contract goes bust.
(define-path-payoff bust-game (payoff) ((rolls list))
(s)
(:state total :total)
(:on rolls (i)
(when (= s 1d0) (stop)))
(:final total))
[macro] FINCL:DEFINE-PATH-PAYOFF NAME SUPERCLASSES SLOTS (OBSERVABLE) &BODY CLAUSES
Define the payoff class NAME (a PATH-PAYOFF) and its PATH-KERNEL from CLAUSES. SLOTS are as in DEFINE-PAYOFF; a slot holding dates has type
LIST. Every clause may use the slots and the variables SUPERCLASSES declare (PHI, STRIKE) as typed local variables, OBSERVABLE (the observable at the current grid point, a double-float), and the latent state variables of:STATE.(:accumulate (var init) ...) per-path double-float variables, set to INIT at the start of each path (:state var name) VAR is the state component NAME (STATE-INDEX), e.g. (:state v :variance) (:start form ...) at grid index 0 (the valuation date) (:on date-slot (index) form ...) at each date of DATE-SLOT; INDEX is the date's position in the slot (:every form ...) at every grid index (:final form) the amount paid at expiry
In
:ONclauses, (pay amount) pays AMOUNT on the current date and (pay amount i) on date I of the clause's slot. Clauses run in the order written; (stop) in any of them ends the path, which then pays nothing at expiry.(define-path-payoff autocallable (payoff) ((notional double-float) (initial double-float) (barrier double-float) (coupon double-float) (observations list)) (s) (:on observations (i) (when (>= s (* barrier initial)) (pay (+ notional coupon)) (stop))) (:final (min notional (* notional (/ s initial)))))
5.6 define-fact and define-clause
make-option is built from clauses that set named facts. define-fact declares
a fact; define-clause adds a clause setting it with publish, and a payoff
slot with :fact reads it:
(define-fact :bonus double-float "Paid on top of the top face."
:coerce #'real-to-double)
(define-clause option bonus (amount)
(:publishes :bonus)
"Pay AMOUNT on a roll of the top face."
(publish :bonus amount))
so (make-option (jackpot) (bonus 10) ...) fills the bonus slot of jackpot.
A clause lists the facts it may set in (:publishes ...).
[macro] FINCL:DEFINE-FACT NAME TYPE DOCUMENTATION &KEY COERCE
Register the fact NAME (a keyword) with values of
TYPE.COERCE, a function designator, normalizes a value before the type check. A name means the same thing in every language, so define a fact once and share it.(define-fact :strike double-float "The option's strike." :coerce (lambda (x) (float x 1d0)))
[macro] FINCL:DEFINE-CLAUSE LANGUAGE NAME LAMBDA-LIST &BODY BODY
Define the clause NAME of LANGUAGE. LAMBDA-LIST receives the clause's arguments, already evaluated; if it ends in &CLAUSES VAR (after required parameters only), the remaining forms are sub-clauses, bound to VAR as data, for the body to run with APPLY-CLAUSES, usually inside WITH-SCOPE. BODY may start with a docstring and a (
:PUBLISHESfact...) form declaring every fact it may set.(define-clause option expires (when &key from calendar) (:publishes :expiry) "The expiry: a date, or a tenor resolved from FROM." (publish :expiry (resolve-expiry when :from from :calendar calendar)))
5.7 A dice game
Putting the pieces together. Let us simulate our games played at our casino and price the contracts we'll write on them.
(define-currency :chip :name "Casino chip" :fraction-digits 0)
(define-observance :previous-weekday (date)
(case (day-of-week date)
(6 (date+ date -1))
(7 (date+ date -2))
(t date)))
(define-calendar casino (weekends-only)
"Closed on Christmas Day, and on the 26th, observed on the previous weekday."
(:fixed 12 25)
(:fixed 12 26 :observe :previous-weekday))
(define-process fair-die (stochastic-process) (:state-size 2)
((faces 6d0 :bounds (2 nil) :doc "Number of faces."))
(:documentation "A die rolled once per step. Element 0 is the latest roll,
element 1 the running total."))
(defmethod initial-state ((p fair-die) market)
(declare (ignore market))
(make-array 2 :element-type 'double-float :initial-element 0d0))
(defmethod process-state-names ((p fair-die)) '(:spot :total))
(defmethod make-stepper ((p fair-die) market times)
(declare (ignore market times))
(let ((faces (process-parameter p :faces)))
(declare (type double-float faces))
(lambda (state normals j)
(declare (type (simple-array double-float (*)) state normals) (ignore j))
;; Phi(Z) is uniform on (0, 1), so floor(faces U) + 1 is uniform on 1..faces.
(let ((roll (+ 1d0 (min (- faces 1d0)
(ffloor (* faces (norm-cdf (aref normals 0))))))))
(setf (aref state 0) roll
(aref state 1) (+ (aref state 1) roll)))
nil)))
(define-fact :bonus double-float "Paid on top of the top face."
:coerce #'real-to-double)
(define-clause option bonus (amount)
(:publishes :bonus)
"Pay AMOUNT on a roll of the top face."
(publish :bonus amount))
(define-payoff jackpot (payoff)
((top double-float :initform 6d0 :doc "The face that wins the bonus.")
(bonus double-float :fact :bonus :doc "Paid on top of the face."))
(s)
(+ s (if (>= s top) bonus 0d0)))
(define-path-payoff bust-game (payoff) ((rolls list))
(s)
(:state total :total)
(:on rolls (i)
(when (= s 1d0) (stop)))
(:final total))
Now, pricing two contracts on the die:
(defparameter *today* #D"2026-10-26")
(defparameter *market*
(make-market :valuation-date *today*
:underlyings '((:die :spot 1 :currency :chip :vol 0.2d0))
:curves '((:chip-curve :rate 0d0))
:discounting '((:chip . :chip-curve))))
(defparameter *engine*
(make-engine :mc :process (make-fair-die) :n-paths 200000 :n-steps 3))
;; Pays the last roll, plus 10 chips on a 6: expected 3.5 + 10/6.
(price (make-option (jackpot) (bonus 10) (currency :chip)
(expires #T"2M" :from *today* :calendar +casino+))
*engine* *market*)
;; ==> 5.17...
;; Weekly rolls; pays the total after three rolls unless a 1 shows up.
(price (make-option (bust-game :rolls (list #D"2026-11-02" #D"2026-11-09" #D"2026-11-16"))
(underlying :die) (expires #D"2026-11-16"))
*engine* *market*)
;; ==> 6.96... (exactly (5/6)^3 * 12 = 6.944)