→ ↺

fincl

Table of Contents

[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).

← ↑ → ↺

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}$):

European exercise only.

← ↑ → ↺

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$:

The type is one of :down-and-out, :down-and-in, :up-and-out, :up-and-in. European exercise only.

← ↑ → ↺

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):

European exercise only.

← ↑ → ↺

2.5 make-option

(make-option clause...) builds an option from clauses, which may come in any order. Arguments of a clause are evaluated.

(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.

← ↑ → ↺

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:

← ↑ → ↺

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).

← ↑ → ↺

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).

← ↑ → ↺

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).

← ↑ → ↺

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).

← ↑ → ↺

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.

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).

← ↑ → ↺

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.

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).

← ↑ → ↺

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.

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).

← ↑ → ↺

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.

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).

← ↑ → ↺

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.$$

← ↑ → ↺

4.1.2 Constant elasticity of variance

$$dS_t = (r-q)\,S_t\,dt + \sigma S_t^{\beta}\,dW_t$$

See J. C. Cox, The constant elasticity of variance option pricing model (1975).

← ↑ → ↺

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.

← ↑ → ↺

4.1.4 Heston

← ↑ → ↺

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$.

See J.-C. Duan, The GARCH option pricing model, Mathematical Finance 5 (1995).

← ↑ → ↺

4.1.6 Hull-White

$$dr_t = \left(\theta(t) - a\,r_t\right)dt + \sigma\,dW_t$$

See J. Hull and A. White, Pricing interest-rate derivative securities, Review of Financial Studies 3 (1990).

← ↑ → ↺

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$.

See R. C. Merton, Option pricing when underlying stock returns are discontinuous, Journal of Financial Economics 3 (1976).

← ↑ → ↺

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"

← ↑ → ↺

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$.

See D. B. Madan, P. P. Carr and E. C. Chang, The variance gamma process and option pricing, European Finance Review 2 (1998).

← ↑ → ↺

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}$.

See O. E. Barndorff-Nielsen, Processes of normal inverse Gaussian type, Finance and Stochastics 2 (1998).

← ↑ → ↺

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).$$

See P. Carr, H. Geman, D. B. Madan and M. Yor, The fine structure of asset returns, Journal of Business 75 (2002).

← ↑ → ↺

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$.

See D. S. Bates, Jumps and stochastic volatility: exchange rate processes implicit in Deutsche mark options, Review of Financial Studies 9 (1996).

← ↑ → ↺

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:

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)

← ↑ → ↺

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))

← ↑ → ↺

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:

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.

← ↑ → ↺

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.

← ↑ → ↺

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.

(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))

← ↑ → ↺

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 ...).

← ↑ ↺

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)