hasquant
Safe HaskellNone
LanguageHaskell2010

QuantLib.Method

Description

Monte Carlo path generation (pathGenerator/sobolPathGenerator/next/asset) plus lsmRegress, a standalone Longstaff-Schwartz early-exercise regression primitive.

Custom early exercise with a Haskell payoff

lsmRegress lets a Haskell-defined payoff drive early exercise, something no bound pricing engine offers: every early-exercise engine in QuantLib.PricingEngine (e.g. mcAmericanEngine) computes its payoff entirely on the C++ side against a bound Payoff. lsmRegress is pure regression -- it never sees a payoff at all, so it works for any underlying-state-dependent early-exercise payoff, not just a vanilla put/call. The pattern, worked in full in test/example/QuantLib/Example/AmericanLSM.hs:

  1. Draw two path sets with pathGenerator: a calibration set used only to fit each date's regression, and a separate pricing set evaluated against the frozen fit. Splitting the sets avoids the in-sample bias a single-pass fit-and-price would have (the same reason mcAmericanEngine exposes its own nCalibrationSamples parameter). Use a fixed nonzero seed for each -- seed 0 means "seed from entropy" for PseudoRandom.
  2. Read out the state at every exercise date, across all paths, with asset (or assetAt), and transpose (transpose) into one state list per exercise date.
  3. Walk exercise dates *strictly backward*. At each date:

    • discount both the calibration and pricing cashflow vectors by the one-step discount factor (discount(t[i+1]) / discount(t[i]) from the underlying yield curve);
    • compute the Haskell payoff at this date for every path in both sets;
    • restrict the regression's fit inputs to *in-the-money calibration paths only* (fitStates/fitTargets below);
    • call lsmRegress twice against that one fit -- once evaluating at the calibration states (to keep the backward recursion's own targets self-consistent), once at the pricing states (the actual out-of-sample continuation-value estimate);
    • exercise wherever payoff > continuationValue (max(exercise, continuation)), on each path set independently.
  4. The pricing set's cashflows, discounted all the way back and averaged, are the estimated price. Never evaluate the fit on the calibration set's own state for the reported price -- that reintroduces the in-sample bias step 1 split the paths to avoid.

A rough sketch (see the full example for discounting, ITM filtering, and the backward recursion itself):

step df calibS priceS calibCF priceCF = do
  let calibTargets = map (* df) calibCF  -- discount to this date
      (fitStates, fitTargets) = -- ITM calibration paths only
        unzip $ filter (inTheMoney . fst) $ zip calibS calibTargets
  contCalib <- lsmRegress Monomial order fitStates fitTargets calibS
  contPrice <- lsmRegress Monomial order fitStates fitTargets priceS
  -- exercise wherever payoff > continuation, on each path set
  ...

Validated in the example against both mcAmericanEngine pricing the equivalent bound vanilla option (same process/grid/seed) and the published Longstaff-Schwartz (2001) reference value for the same benchmark fixture.

The same pattern extends to a Haskell-defined basket payoff (several correlated underlyings) via lsmRegressMulti / lsmBasisSize in place of lsmRegress: read each exercise date's state for every underlying (still with asset/assetAt, once per underlying) into a Matrix of one row per path, and guard the ITM-fit-size check with lsmBasisSize instead of the basis order -- the multi-asset basis has combinatorially many more terms than the scalar case.

test/example/QuantLib/Example/HaskellLSM.hs benchmarks lsmRegress against the same backward induction with the per-date regression reimplemented from scratch in plain Haskell (QuantLib used only for path generation) -- a worked illustration of why this module exposes the regression as a batched primitive instead of leaving callers to reinvent it.

Finite-difference PDE solving

This module also binds a full-grid finite-difference (FDM) driver, built up across two related issues (custom step-condition/operator hooks, then custom inner-value calculators) and spread across several functions with no single overview until now. Worked in full in test/example/QuantLib/Example/Fdm.hs -- every snippet below is a trimmed extract from that file; read it end to end for the full picture (discounting, fixtures, imports).

In one sentence, for anyone new to FDM pricing: instead of an integral (Monte Carlo) or a closed-form formula (an analytic*Engine), you discretize the underlying's state space (e.g. log-spot) into a grid of points, put the option's payoff on the grid at maturity, and step it backward to today, solving a small local linear system at each timestep. Reach for it when you need American/Bermudan-style early exercise (a plain Monte Carlo run can't do backward induction the way a grid can) or a process/payoff with no closed-form price. If a bound analytic*Engine or mc*Engine already covers your case (see QuantLib.PricingEngine), prefer that instead -- it's simpler, and this module's own examples validate their FDM results against exactly those engines.

Walkthrough: which function do I actually want?

Start here rather than at the reference list below -- picking the right entry point up front avoids reading five functions' haddock only to discover a sixth was the one you needed.

"I have a grid already, just roll it back"
fdmRollback. Supply the grid as a plain [Double] (one value per state, at maturity) plus three Haskell closures describing the PDE operator, and get the same grid rolled back to today -- no mesher, no FdmInnerValueCalculator, the simplest possible entry point:
let grid0 = map (\x -> max (exp x - strike) 0) xs   -- payoff at maturity, one value per grid point
fdmEuro <- fdmRollback 1 applyFn applyDirFn solveFn Nothing [] Douglas grid0 tMat 0 nSteps 0
"...and I need early exercise"
the same fdmRollback call, plus a step condition: a t -> [Double] -> [Double] closure called once per outer timestep with the whole current grid, returning it clamped to whatever the early-exercise rule requires:
let stepCond _t u = zipWith max u grid0   -- American: value can never fall below intrinsic
fdmAmerican <- fdmRollback 1 applyFn applyDirFn solveFn (Just stepCond) stepTimes Douglas grid0 tMat 0 nSteps 0
"I'd rather not hand-build the initial grid myself"
fdmSolve -- fdmRollback's sibling. Same operator/step-condition/scheme machinery, but the initial condition comes from an FdmMesher plus an FdmInnerValueCalculator evaluated at each node, instead of a grid you assembled by hand. Worth it once the mesher is doing real work (e.g. concentrating points near a strike or barrier) rather than just wrapping a list you already had:
mesh1d <- predefined1dMesher xs
mesher <- fdmMesherComposite [mesh1d]
let ivFn _t loc = case loc of [x] -> intrinsicAt x; _ -> error "expected a 1D location"
withCustomFdmInnerValueCalculator mesher ivFn ivFn $ \calc ->
  fdmSolve mesher calc 1 applyFn applyDirFn solveFn Nothing [] Douglas tMat 0 nSteps 0
"my payoff is a standard vanilla/log payoff, I don't want a per-node callback"
skip withCustomFdmInnerValueCalculator and reach for one of the native calculators instead -- QuantLib's own built-in FdmInnerValueCalculator subclasses, bound directly so pricing a plain payoff doesn't pay a Haskell round-trip per grid node:
logCalc <- fdmLogInnerValue payoff mesher 0        -- striked payoff on a log-spot grid
fdmLogEuro <- fdmSolve mesher logCalc 1 applyFn applyDirFn solveFn Nothing [] Douglas tMat 0 nSteps 0

fdmZeroInnerValue (always 0), fdmCellAveragingInnerValue/withCustomCellAveragingInnerValue (identity or custom gridMapping), and fdmLogInnerValue (gridMapping = exp, the common case on a log-spot grid) round out the set -- see the reference entries below for the exact cell-averaging-vs-point-evaluation contract each one has. Reach for withCustomFdmInnerValueCalculator only once none of these fit your payoff shape.

"my payoff depends on more than one underlying"
build one Fdm1dMesher per underlying and combine them with fdmMesherComposite; fdmLogBasketInnerValue takes a basket payoff (e.g. Max, see QuantLib.Instrument.Option) evaluated across all dimensions at once:
basketMesher <- fdmMesherComposite [mesh1d, mesh1d]   -- two correlated log-spot dimensions
basketCalc <- fdmLogBasketInnerValue (Max payoff) basketMesher
val <- fdmAvgInnerValue basketCalc basketMesher [i, j] tMat   -- inspect one node directly
"I want to price a swap/swaption under a calibrated short-rate model"
the most specialized entry points here: fdmAffineG2ModelSwapInnerValue/fdmAffineHullWhiteModelSwapInnerValue drive the same calculator fdG2SwaptionEngine/fdHullWhiteSwaptionEngine already use internally. Reach for these directly only when composing your own custom FDM pipeline around this calculator; if a plain Bermudan-swaption NPV is all you need, prefer those two already-bound black-box engines from QuantLib.PricingEngine instead.
"I just want one node's value, no PDE solve"
fdmInnerValue/fdmAvgInnerValue evaluate any bound calculator -- custom or native -- at a single mesher node directly, without assembling a whole fdmSolve. Handy as a sanity check while developing (as Fdm.hs's own tests do throughout), or whenever a single point's intrinsic value is all you actually need.

Technical reference

The terse version of the above, for a reader who already knows the vocabulary and wants the exact contract rather than the walkthrough's prose.

Rolling a grid back
fdmRollback takes a precomputed initial grid (a plain [Double]) and rolls it back through time via three Haskell-defined operator callbacks (withFdmApply et al.) plus an optional step condition (e.g. American/Bermudan early exercise). These callbacks cross the language boundary once per outer timestep, over the whole grid. fdmSolve is the sibling that instead derives its own initial grid from a mesher and an FdmInnerValueCalculator (below), reusing the same operator/step-condition machinery.
Building a grid
Fdm1dMeshers (predefined1dMesher, uniform1dMesher, concentrating1dMesher, fdmBlackScholesMesher, and the other process-specific meshers) each describe one PDE dimension; fdmMesherComposite combines one or more into the multi-dimensional FdmMesher fdmSolve and FdmInnerValueCalculator operate over. fdmMesherLocations reads a dimension's real-valued node locations back out, e.g. to map a flat result array back to coordinates. gluedMesher splices two Fdm1dMeshers end to end (e.g. a fine mesh near a barrier glued to a coarse one further out) -- their ranges must already be ordered and non-overlapping, and a shared boundary point is deduplicated automatically.
Custom inner values, fully general
withCustomFdmInnerValueCalculator wraps a Haskell t -> location -> value pair of functions as an FdmInnerValueCalculator. Unlike every callback above, this one crosses the language boundary once per grid node -- there is no batched shape for it anywhere in QuantLib or QuantLib-SWIG, so the real per-call cost is accepted, matching QuantLib-SWIG's own FdmInnerValueCalculatorDelegate precedent. Because the two callbacks are stored inside the returned calculator and invoked again on every later fdmSolve/fdmInnerValue call (not just during construction), the calculator is only valid inside this continuation -- it cannot be built with a plain IO FdmInnerValueCalculator smart constructor the way the native calculators below can.
Custom inner values, native
QuantLib's own concrete FdmInnerValueCalculator subclasses are bound directly, for the common cases that don't need a per-node Haskell callback at all: fdmZeroInnerValue (always 0), fdmCellAveragingInnerValue/fdmLogInnerValue (a payoff cell-averaged -- Simpson-integrated across each grid cell, not just evaluated at its center -- with an identity or exp value mapping respectively), and fdmLogBasketInnerValue (the multi-asset counterpart, one exp mapping per dimension). These hold no Haskell callback, so they're plain IO FdmInnerValueCalculator constructors -- except withCustomCellAveragingInnerValue, the one native constructor that does take an explicit gridMapping callback, which needs the same continuation treatment as the fully custom case above. fdmAffineG2ModelSwapInnerValue/fdmAffineHullWhiteModelSwapInnerValue price a swap under a calibrated G2/HullWhite model directly -- the same calculator fdG2SwaptionEngine/fdHullWhiteSwaptionEngine use internally.
Inspecting a calculator directly
fdmInnerValue/fdmAvgInnerValue evaluate any bound calculator (custom or native) at a single mesher node, without assembling a whole fdmSolve -- useful for a targeted self-consistency check, as Fdm.hs's own tests do throughout.

What's deliberately not bound: operators, schemes, boundary conditions

QuantLib-SWIG also exposes QuantLib's concrete FdmLinearOpComposite subclasses (FdmBlackScholesOp, FdmHestonOp, FdmG2Op, ...), its scheme objects (DouglasScheme, CraigSneydScheme, HundsdorferScheme, ...), and its FdmBoundaryCondition family as real C++ objects. hasquant does not mirror these, and won't by default -- it's a design boundary already crossed once, not a gap.

fdmRollback/fdmSolve take the operator, the implicit-solve step, and the scheme all as Haskell closures instead (applyFn/applyDirFn/solveFn above). That's the same "coarsen the language-boundary crossing" call already made for step conditions: bind the reusable numerical primitive (rollback through a fixed timestep, of an arbitrary tridiagonal/multi-dimensional operator) and let Haskell drive it, rather than bind every concrete operator/scheme QuantLib ships as its own object. test/example/QuantLib/Example/Fdm.hs's hand-rolled operatorBands/applyOp is the replacement for FdmBlackScholesOp + DouglasScheme, not a stand-in waiting for those to get bound -- pricing a new payoff/process combination here means writing its operator once in Haskell, not calling into fifteen QuantLib operator classes one by one.

Binding the operator/scheme family as objects would add a second, redundant way to drive the same fdmSolve/fdmRollback backbone, without extending what's actually solvable -- anything a bound FdmXxxOp could do, a Haskell applyFn already can. Revisit only if a concrete need shows up that the callback shape genuinely can't express (none has, so far).

Synopsis

Types

Path and random sequences

Finite differences

Constructors

Path generation

pathGenerator Source #

Arguments

:: RngTrait 
-> GenStochasticProcess p 
-> TimeGrid 
-> Word

seed

-> Word

dimension

-> Bool

brownian bridge

-> IO PathGenerator 

build a multi-asset path generator driven by a pseudo-random number generator (Mersenne Twister, Poisson, or Ziggurat, chosen by the RNG trait) over the given process and time grid.

sobolPathGenerator Source #

Arguments

:: SobolDirectionIntegers 
-> GenStochasticProcess p 
-> TimeGrid 
-> Word

seed

-> Word

dimension

-> Bool

brownian bridge

-> IO PathGenerator 

build a multi-asset path generator driven by a low-discrepancy (Sobol) sequence, using the given direction integers, over the given process and time grid.

Random sequence generation

gaussianRsg Source #

Arguments

:: RngTrait 
-> Word

dimension

-> Word

seed

-> IO GaussianRsg 

The gaussian sequence generator a pathGenerator drives its evolution with, exposed on its own so a Haskell-defined SDE can be simulated with no FFI call in the inner loop -- the same decomposition lsmRegress applies to LongstaffSchwartzPathPricer, one level lower down.

QuantLib's StochasticProcess has no Haskell-subclassable hook here by design: MultiPathGenerator (which pathGenerator wraps) calls process->evolve once per timestep per path, so binding that virtual as a callback would put an FFI crossing in the hottest loop there is. Drawing the normals with nextSequence and writing evolve in Haskell instead costs one crossing per path, and the result composes with lsmRegress into a complete custom-SDE American Monte Carlo. The trade-off is that the result is a set of paths, not a StochasticProcess object, so it cannot be fed to fdmSimpleProcess1dMesher or to a pricing engine -- but no stock QuantLib engine would have accepted a custom process anyway: their constructors are typed on concrete process classes (GeneralizedBlackScholesProcess and friends), not on the abstract base.

dimension is the length of each drawn sequence -- for a path set, assets * timesteps, matching what pathGenerator is passed. The construction mirrors pathGenerator's exactly (same trait, same seed, same direction integers), so a Haskell-evolved path can be compared draw for draw against a pathGenerator one on a bound process.

sobolGaussianRsg Source #

Arguments

:: SobolDirectionIntegers 
-> Word

dimension

-> Word

seed

-> IO GaussianRsg 

gaussianRsg driven by a low-discrepancy (Sobol) sequence with the given direction integers -- the sobolPathGenerator counterpart.

Finite-difference meshers

predefined1dMesher Source #

Arguments

:: RealVector

points

-> IO Fdm1dMesher 

'Predefined1dMesher(points)' -- an Fdm1dMesher over an explicit, caller-supplied set of grid points.

uniform1dMesher Source #

Arguments

:: Double

start

-> Double

end

-> Word

size

-> IO Fdm1dMesher 

'Uniform1dMesher(start, end, size)' -- an evenly spaced Fdm1dMesher.

concentrating1dMesher Source #

Arguments

:: Double

start

-> Double

end

-> Word

size

-> Maybe Double

concentration point location

-> Maybe Double

concentration point density

-> Bool

requireCPoint: force the concentration point itself onto the grid

-> IO Fdm1dMesher 

'Concentrating1dMesher(start, end, size, cPoint, requireCPoint)' -- an Fdm1dMesher with grid points concentrated near cPoint (e.g. a strike or barrier), or plain uniform spacing when cPoint is Nothing for both coordinates.

concentrating1dMesherMulti Source #

Arguments

:: Double 
-> Double 
-> Word 
-> [(Double, Double, Bool)]

concentration points: (location, density, requireCPoint)

-> Double

tol

-> IO Fdm1dMesher 

Multi-concentration-point overload of concentrating1dMesher (Concentrating1dMesher(start, end, size, cPoints, tol)) -- a distinct upstream constructor, not a defaulted-arg variant of the single-point one.

gluedMesher Source #

Arguments

:: Fdm1dMesher

leftMesher

-> Fdm1dMesher

rightMesher

-> IO Fdm1dMesher 

'Glued1dMesher(leftMesher, rightMesher)' -- splices two Fdm1dMeshers into one, deduplicating their shared boundary point if leftMesher's rightmost location and rightMesher's leftmost location coincide (within QuantLib's usual close tolerance). Throws if leftMesher's rightmost point is strictly greater than rightMesher's leftmost point -- the two ranges may touch or be disjoint-but-ordered, never overlap or reverse.

fdmBlackScholesMesher Source #

Arguments

:: Word

size

-> GeneralizedBlackScholesProcess 
-> Double

maturity

-> Double

strike

-> Maybe Double

xMinConstraint

-> Maybe Double

xMaxConstraint

-> Double

eps

-> Double

scaleFactor

-> Maybe Double

concentration point location

-> Maybe Double

concentration point density

-> [Dividend] 
-> Maybe FdmQuantoHelper 
-> Double

spotAdjustment

-> IO Fdm1dMesher 

'FdmBlackScholesMesher(size, process, maturity, strike, ...)' -- the standard log-spot mesher for a Black-Scholes-family process, reusing the same GeneralizedBlackScholesProcess/ Dividend/FdmQuantoHelper plumbing QuantLib.PricingEngine's fd* engines already use.

fdmCev1dMesher Source #

Arguments

:: Word

size

-> Double

f0

-> Double

alpha

-> Double

beta

-> Double

maturity

-> Double

eps

-> Double

scaleFactor

-> Maybe Double

concentration point location

-> Maybe Double

concentration point density

-> IO Fdm1dMesher 

'FdmCEV1dMesher(size, f0, alpha, beta, maturity, eps, scaleFactor, cPoint)' -- the standard mesher for a CEV process.

exponentialJump1dMesher Source #

Arguments

:: Word

steps

-> Double

beta

-> Double

jumpIntensity

-> Double

eta

-> Double

eps

-> IO Fdm1dMesher 

'ExponentialJump1dMesher(steps, beta, jumpIntensity, eta, eps)' -- mesher for the jump-diffusion component of a jump-diffusion process.

fdmSimpleProcess1dMesher Source #

Arguments

:: Word

size

-> StochasticProcess1D 
-> Double

maturity

-> Word

tAvgSteps

-> Double

epsilon

-> Maybe Double

mandatoryPoint

-> IO Fdm1dMesher 

'FdmSimpleProcess1dMesher(size, process, maturity, tAvgSteps, epsilon, mandatoryPoint)' -- generic mesher for any bound one-dimensional StochasticProcess1D.

fdmHestonVarianceMesher Source #

Arguments

:: Word

size

-> GenHestonProcess hp 
-> Double

maturity

-> Word

tAvgSteps

-> Double

epsilon

-> Double

mixingFactor

-> IO Fdm1dMesher 

'FdmHestonVarianceMesher(size, process, maturity, tAvgSteps, epsilon, mixingFactor)' -- variance mesher for a Heston-family process.

fdmHestonLocalVolatilityVarianceMesher Source #

Arguments

:: Word

size

-> GenHestonProcess hp 
-> GenLocalVolTermStructure lv

leverageFct

-> Double

maturity

-> Word

tAvgSteps

-> Double

epsilon

-> Double

mixingFactor

-> IO Fdm1dMesher 

'FdmHestonLocalVolatilityVarianceMesher(size, process, leverageFct, maturity, tAvgSteps, epsilon, mixingFactor)' -- Heston variance mesher accounting for a local-volatility leverage function.

fdmMesherComposite :: [Fdm1dMesher] -> IO FdmMesher Source #

FdmMesherComposite -- combine one or more Fdm1dMeshers into the multi-dimensional FdmMesher the operator/step-condition callbacks and fdmSolve operate over; the sole concrete FdmMesher upstream.

Finite-difference inner-value calculators

withCustomFdmInnerValueCalculator Source #

Arguments

:: FdmMesher 
-> (Double -> [Double] -> Double)

innerValue(t, location)

-> (Double -> [Double] -> Double)

avgInnerValue(t, location)

-> (FdmInnerValueCalculator -> IO b) 
-> IO b 

Wraps a Haskell t -> location -> value pair of innerValue/avgInnerValue functions as a real FdmInnerValueCalculator object, valid only inside the continuation -- the fully custom counterpart to constructors built from QuantLib's own concrete subclasses (bound alongside this, which need no such bracket: they hold no Haskell callback). Unlike every callback fdmRollback takes, this crosses the language boundary once per grid node, not once per outer iteration over the whole grid -- there is no batched "whole-grid inner value" shape anywhere in QuantLib or QuantLib-SWIG. The per-call FFI cost across every node (and, if a step condition also calls the calculator, every node at every exercise date) is accepted -- matching QuantLib-SWIG's own accepted-cost precedent, FdmInnerValueCalculatorDelegate (SWIG/fdm.i).

fdmZeroInnerValue :: IO FdmInnerValueCalculator Source #

FdmZeroInnerValue -- an FdmInnerValueCalculator whose innerValue/avgInnerValue are always 0.

fdmCellAveragingInnerValue Source #

Arguments

:: Payoff 
-> FdmMesher 
-> Int

direction

-> IO FdmInnerValueCalculator 

'FdmCellAveragingInnerValue(payoff, mesher, direction)' -- cell-averages payoff over each grid cell along direction (Simpson-integrating across the cell straddling a kink, e.g. a strike, rather than just evaluating at the cell center), with the identity value mapping. See withCustomCellAveragingInnerValue for the gridMapping-taking overload (e.g. to reproduce fdmLogInnerValue by hand), and fdmLogInnerValue for the common log-mapped case QuantLib itself gives its own dedicated subclass.

withCustomCellAveragingInnerValue :: Payoff -> FdmMesher -> Int -> (Double -> Double) -> (FdmInnerValueCalculator -> IO b) -> IO b Source #

As fdmCellAveragingInnerValue, but with an explicit gridMapping :: Double -> Double applied to each node's location before the payoff sees it (e.g. exp on a log-spot grid, reproducing fdmLogInnerValue by hand) -- a genuine per-node Haskell callback; see withCustomFdmInnerValueCalculator. The resulting FdmInnerValueCalculator is only valid inside this continuation.

fdmLogInnerValue Source #

Arguments

:: Payoff 
-> FdmMesher 
-> Int

direction

-> IO FdmInnerValueCalculator 

'FdmLogInnerValue(payoff, mesher, direction)' -- fdmCellAveragingInnerValue with the gridMapping = exp QuantLib itself gives its own dedicated subclass (the standard shape for a log-spot grid, e.g. fdmBlackScholesMesher's own grid).

fdmLogBasketInnerValue :: BasketPayoff -> FdmMesher -> IO FdmInnerValueCalculator Source #

'FdmLogBasketInnerValue(payoff, mesher)' -- the multi-asset counterpart to fdmLogInnerValue: evaluates a BasketPayoff with each dimension's location exponentiated first (exp on every mesher direction, i.e. a log-spot grid per underlying), no cell averaging.

fdmAffineG2ModelSwapInnerValue :: G2 -> G2 -> GenFixedVsFloatingSwap f -> [(Double, Day)] -> FdmMesher -> Int -> IO FdmInnerValueCalculator Source #

'FdmAffineModelSwapInnerValue<G2>(disModel, fwdModel, swap, exerciseDates, mesher, direction)' -- the swap-NPV-under-the-model FdmInnerValueCalculator used internally by fdG2SwaptionEngine. exerciseDates pairs each exercise time (the same Time-as-Double year-fraction convention used throughout, not a dedicated type) with the Day it corresponds to (upstream's std::map<Time, Date>).

Inspectors

Path and random sequences

next :: PathGenerator -> IO SamplePath Source #

draw the next weighted sample path from the generator.

antithetic :: PathGenerator -> IO SamplePath Source #

draw the antithetic (sign-flipped) counterpart of the last drawn sample path.

nextSequence :: GaussianRsg -> IO (RealVector, Double) Source #

draw the next sequence of standard normal variates, with its sample weight (1 for every trait bound here, carried through for symmetry with weight).

Longstaff-Schwartz regression

lsmRegress Source #

Arguments

:: PolynomialType 
-> Word

basis order

-> RealVector

fit states (in-the-money paths only)

-> RealVector

fit targets (continuation value at these states)

-> RealVector

eval states (all paths' state at this date)

-> IO RealVector 

one step of Longstaff-Schwartz early-exercise regression: fit a polynomial basis of the given order/type against the (in-the-money) fit states and their continuation targets, then evaluate the fitted continuation value at each of the given eval states. This is the same per-exercise-date regression LongstaffSchwartzPathPricer performs internally against a bound Payoff, exposed so it can be driven from a Haskell-defined payoff instead: call it once per exercise date, walking dates strictly backward, batched across all paths rather than per path. See this module's header for the full backward-induction pattern.

lsmBasisSize :: Word -> Word -> Word Source #

number of basis terms lsmRegressMulti fits for a given number of underlyings and order -- C(dim+order, order), the binomial coefficient LsmBasisSystem::multiPathBasisSystem actually returns (not order+1, which only coincides at dim=1 -- lsmRegress uses that special case directly rather than calling this). Use it to size the "enough in-the-money calibration paths to fit" guard before calling lsmRegressMulti: the underlying least-squares solve requires at least this many fit rows, and undershooting it throws rather than returning a degenerate fit.

lsmRegressMulti Source #

Arguments

:: PolynomialType 
-> Word 
-> RealMatrix

fit states (in-the-money paths only)

-> RealVector

fit targets (continuation value at these states)

-> RealMatrix

eval states (all paths' state at this date)

-> IO RealVector

continuation value estimate per eval row

multi-asset counterpart of lsmRegress, for a Haskell-defined basket (several correlated underlyings) early-exercise payoff -- lsmRegress itself only regresses against one state variable. Fit/eval states are contiguous row-major RealMatrix values: one row per path, one column per underlying, and the two matrices' column counts must agree. Regresses against LsmBasisSystem::multiPathBasisSystem's combinatorial basis; see lsmBasisSize for its size and this module's header for the surrounding backward-induction pattern (identical to the scalar case, just with Matrix-shaped states).

Finite differences

fdmRollback Source #

Arguments

:: Int

number of PDE directions/dimensions the operator has (e.g. 1 for a 1D Black-Scholes-in-log-spot operator) -- not the grid array length, which is the length of every [Double] passed to/returned from the callbacks below

-> ((Double, Double) -> RealVector -> RealVector)

apply(r): whole-grid operator application at the current (t1,t2) time pair (no direction argument -- QuantLib's own FdmLinearOp base method)

-> (Int -> (Double, Double) -> RealVector -> RealVector)
apply_direction(direction, r)
-> (Int -> Double -> (Double, Double) -> RealVector -> RealVector)

solve_splitting(direction, r, s) -- the implicit per-direction solve (e.g. a tridiagonal/Thomas-algorithm solve for a 1D operator)

-> Maybe (Double -> RealVector -> RealVector)

optional step condition applyTo(a, t), e.g. American/Bermudan early exercise (max(a_i, intrinsic_i) at every step) or a barrier knockout

-> RealVector

stopping times at which the step condition above is applied (ignored if there is no step condition); pass every rollback step's time to apply it at every step

-> FdmScheme

the finite-difference scheme (see the haddock above for which schemes are actually safe to use here)

-> RealVector

initial grid values, at time 'from'

-> Double

from (start time of the rollback, e.g. option maturity)

-> Double

to (end time of the rollback, e.g. 0)

-> Int

steps

-> Int

dampingSteps

-> IO RealVector 

Drive FdmBackwardSolver::rollback with a Haskell-defined FdmLinearOpComposite (the apply/apply_direction/solve_splitting callbacks) and an optional Haskell-defined step condition (e.g. American/Bermudan early exercise, or a barrier), instead of a bound mesher + FdmInnerValueCalculator as every concrete FDM pricing engine in QuantLib.PricingEngine uses. This coarsened callback shape is modeled on QuantLib-SWIG's FdmLinearOpCompositeDelegate/ FdmStepConditionDelegate (SWIG/fdm.i): each callback crosses once per outer iteration over the whole grid array, not once per grid node. Internally each callback receives one native argument record, avoiding a wide mixed-argument callback ABI at the Haskell boundary; this does not affect the public callback types.

The grid is a plain [Double] in and out -- no mesher, no FdmInnerValueCalculator, no FdmSolverDesc is bound; callers manage their own grid geometry entirely in Haskell. Boundary conditions are always the empty FdmBoundaryConditionSet() (not bound).

Only DouglasScheme::step's three virtuals are implemented -- apply, apply_direction and solve_splitting; apply_mixed/preconditioner are unimplemented and QL_FAIL at the C++ level if called. This makes fdmRollback safe to drive with Douglas or CrankNicolson in one dimension (the two schemes DouglasScheme::step itself is used for) -- anything needing mixed derivatives across more than one PDE direction (Craig-Sneyd, Hundsdorfer, or any genuinely multi-dimensional operator) will throw partway through fdmRollback rather than silently mispricing.

fdmInnerValue Source #

Arguments

:: FdmInnerValueCalculator 
-> FdmMesher 
-> [Int]

node coordinates

-> Double

t

-> IO Double 

Evaluate an FdmInnerValueCalculator's innerValue at the mesher node given by its coordinates (one index per PDE dimension), at time t -- lets any bound calculator (native or built via withCustomFdmInnerValueCalculator) be inspected directly without assembling a whole fdmSolve.

fdmAvgInnerValue Source #

Arguments

:: FdmInnerValueCalculator 
-> FdmMesher 
-> [Int]

node coordinates

-> Double

t

-> IO Double 

As fdmInnerValue, but for avgInnerValue.

fdmSolve Source #

Arguments

:: FdmMesher 
-> FdmInnerValueCalculator 
-> Int

number of PDE directions/dimensions the operator has

-> ((Double, Double) -> RealVector -> RealVector)
apply(r)
-> (Int -> (Double, Double) -> RealVector -> RealVector)
apply_direction(direction, r)
-> (Int -> Double -> (Double, Double) -> RealVector -> RealVector)
solve_splitting(direction, r, s)
-> Maybe (Double -> RealVector -> RealVector)

optional step condition

-> RealVector

stopping times at which the step condition above is applied

-> FdmScheme

the finite-difference scheme

-> Double

maturity (start time of the rollback, and the time at which avgInnerValue builds the initial grid)

-> Double

to (end time of the rollback, e.g. 0)

-> Int

steps

-> Int

dampingSteps

-> IO RealVector 

Sibling of fdmRollback that derives its own initial grid from a mesher and an FdmInnerValueCalculator (avgInnerValue(t, location) per node, called once per mesher node at t = maturity -- mirroring Fdm1DimSolver/FdmNdimSolver's own constructor loop) instead of taking a precomputed grid array. Everything else (operator/step-condition/scheme/rollback) is identical to fdmRollback, reusing the same callback machinery. The calculator can be either fully custom (fdmInnerValueCalculator) or one of QuantLib's own native subclasses.

Fdm1DimSolver/FdmNdimSolver themselves (their own LazyObject caching and cubic-spline interpolation) are not bound; combine this function's result with fdmMesherLocations for interpolation.

Paths

weight :: SamplePath -> Double Source #

the weight associated with a sample path.

assetNumber :: SamplePath -> Word Source #

the number of correlated asset paths in a sample.

pathSize :: SamplePath -> Word Source #

the number of time steps in each asset path of a sample.

assetAt Source #

Arguments

:: SamplePath 
-> Word

asset

-> Word

point

-> IO Double 

the value of one asset's path at a given time step.

asset :: SamplePath -> Word -> IO RealVector Source #

The full simulated path (values at every time step) of a single asset.

Random sequences and meshers

rsgDimension :: GaussianRsg -> Word Source #

the length of each sequence the generator draws.

lastSequence :: GaussianRsg -> IO (RealVector, Double) Source #

re-read the sequence nextSequence last drew, without advancing the generator.

fdmMesherLocations Source #

Arguments

:: FdmMesher 
-> Int

direction

-> IO RealVector 

Real-valued node locations along one dimension of a mesher, e.g. to map fdmSolve's flat result array back to coordinates (mirrors how Fdm1DimSolver/FdmNdimSolver build their own x_ arrays from this same call upstream).