packages feed

hasquant-0.8.0.0: QuantLib/Internal/Common.chs

{-# LANGUAGE TemplateHaskell, StandaloneDeriving, ExistentialQuantification #-}
-- internal utilities to convert special enums: either complex ones or represented as QuantLib objects that I didn't want to expose so I represented them as ADTs
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
module QuantLib.Internal.Common
  (
    qlInterpolation
  , qlInterpolation'
  , Approximation(..)
  , Interpolation(..)
  , Interpolation2D(..)
  , IterativeBootstrapOpts(..)
  , defaultIterativeBootstrapOpts

  , ExerciseType(..)
  , Exercise(..)
  , QlExercise
  , EuropeanExercise(..)
  , BermudanExercise(..)
  , QlEuropeanExercise
  , QlBermudanExercise
  , SwingExercise(..)
  , QlSwingExercise

  , OptionType(..)
  , PositionType(..)
  , BondPriceType(..)

  , StrikedPayoff(..)
  , PlainVanillaPayoff(..)
  , PercentageStrikePayoff(..)
  , QlPlainVanillaPayoff
  , QlPercentageStrikePayoff
  , QlStrikedTypePayoff
  , Payoff(..)
  , QlPayoff
  , BasketPayoff(..)
  , QlBasketPayoff
  , TypePayoff(..)
  , QlTypePayoff

  , CallabilityType(..)
  , Callability(..)
  , QlCallability

  , Claim(..)
  , QlClaim
  , withClaim

  , FittingMethod(..)
  , QlFittedBondDiscountCurveFittingMethod
  , withFittedBondDiscountCurveFittingMethod

  , FdmSchemeType(..)
  , FdmScheme(..)
  , QlFdmSchemeDesc
  , withFdmSchemeDesc

  , CPIInterpolationType(..)

  , StubIndexSelection(..)
  , withStubIndexSelection
  , OvernightObservation(..)
  , defaultOvernightObservation

  , Constraint(..)
  , QlConstraint
  , withConstraint
  , withMaybeConstraint
  , OptimizationMethod(..)
  , QlOptimizationMethod
  , withOptimizationMethod
  , withMaybeOptimizationMethod
  , EndCriteria(..)
  , QlEndCriteria
  , withEndCriteria
  , withMaybeEndCriteria

  , QlRounding
  , RoundingType(..)
  , Rounding(..)
  , withRounding
  , withMaybeRounding

  , withCallability
  , withCallabilityArray

  , QlLmVolatilityModel
  , LmVolatilityModel(..)
  , QlLmCorrelationModel
  , LmCorrelationModel(..)
  , withLmCorrelationModel
  , withLmVolatilityModel

  , TimeUnit(..)
  , BusinessDayConvention(..)

  , withEuropeanExercise
  , withSwingExercise
  , withBermudanExercise
  , withExercise
  , withPercentageStrikePayoff
  , withPlainVanillaPayoff
  , withStrikedPayoff
  , withTypePayoff
  , withBasketPayoff
  , withPayoff
  , withCustomPayoff
  , withCustomStrikedPayoff
  , withCustomBasketPayoff

  , strikedPayoff
  , percentageStrikePayoff
  , plainVanillaPayoff
  , swingExercise

  , CalibrationBasketType(..)

  , UnitOfMeasureType(..)
  , PaymentTermEventType(..)
  , PricingErrorLevel(..)
  , peekPricingErrorLevelArray
  , DeliverySchedule(..)
  , QuantityPeriodicity(..)

  , AdditionalResultType(..)
  , AdditionalResultVal(..)
  , RawResultPtr
  , RawResult(..)
  , convertResult
  , preAdditionalResults
  , peekAdditionalResults
  ) where
import Foreign.Ptr(Ptr, nullPtr, castPtr)
import Foreign.C.Types(CUInt, CInt, CDouble)
import Foreign.C.String(CString, peekCString)
import Foreign.Storable(Storable(..))
import Foreign.Marshal.Utils(withMany)
import Foreign.Marshal.Array(withArray, peekArray)
import Data.List.NonEmpty(NonEmpty, toList)

import QuantLib.Internal
import QuantLib.Internal.Type hiding(ptr)
import QuantLib.Internal.Syntax

#include "qlTypesC2HS.h"
#include "ql.h"

#include "qlEnumC2HS.h"
#include "qlEnumObjects.h"

-- |Every constructor parameter of QuantLib's curve-generic @IterativeBootstrap@.
-- The first three fields are 'Maybe' because 'Nothing' preserves QuantLib's
-- @Null\<Real\>()@ sentinel, which selects a suitable value for each curve pillar.
$(deriveOptionsRecord "IterativeBootstrapOpts" []
  [ ("ibAccuracy", [t|Maybe Double|], [|Nothing|])
  , ("ibMinValue", [t|Maybe Double|], [|Nothing|])
  , ("ibMaxValue", [t|Maybe Double|], [|Nothing|])
  , ("ibMaxAttempts", [t|Word|], [|1|])
  , ("ibMaxFactor", [t|Double|], [|2.0|])
  , ("ibMinFactor", [t|Double|], [|2.0|])
  , ("ibDontThrow", [t|Bool|], [|False|])
  , ("ibDontThrowSteps", [t|Word|], [|10|])
  , ("ibMaxEvaluations", [t|Word|], [|100|])
  ])

-- this enum is not special, just used in many places and was put here to avoid cyclic dependencies
{#enum TimeUnit{} deriving(Show, Eq, Read, Bounded)#}
-- moved from QuantLib.Time.Calendar, its "natural" home, for the same reason as TimeUnit above --
-- needed directly here for RebatedExercise's rebatePaymentConvention param. Like TimeUnit, every
-- cross-module use marshals through a manual fromEnumC/toEnumC/fromMaybeEnum function rather than
-- a bare c2hs backtick spec: a bare `` `BusinessDayConvention' `` needs the type's own module's
-- .chi already built, which this other-modules-listed module isn't guaranteed to have by the time
-- an early exposed-modules file (e.g. QuantLib.Time.Calendar itself) is processed.
{#enum BusinessDayConvention{} deriving(Show, Eq, Read)#}
{#enum ApproximationType{} add prefix="Approximation__" deriving(Show, Eq, Read)#}
{#enum InterpolationType{} add prefix="Interpolation" deriving(Show, Eq, Read)#}
-- 2-D interpolators for a BlackVarianceSurface. Unlike InterpolationType/ApproximationType
-- above (merged into the public Interpolation ADT by deriveCrossEnum), this enum is itself the
-- public type: setInterpolation on a surface is a member template over a default-constructed
-- interpolator, so there is no approximator to pair it with. Declared here rather than in
-- QuantLib.TermStructure.Volatility for the usual cross-module {#import#} ordering reason.
{#enum Interpolation2D{} deriving (Show, Eq, Read, Bounded)#}
{#enum ExerciseType{} add prefix = "ExerciseType" deriving (Show, Eq, Read)#}
{#enum OptionType{} deriving (Show, Eq, Read)#}
{#enum PositionType{} deriving (Show, Eq, Read)#}
{#enum BondPriceType{} deriving (Show, Eq, Read)#}
{#enum CallabilityType{} add prefix="Callability" deriving(Show, Eq, Read)#}
{#enum FdmSchemeType{} deriving(Show, Eq, Read)#}
{#enum RoundingType{} deriving (Show, Eq, Read)#}
-- experimental/commodities: cross-cutting the same way TimeUnit is (UnitOfMeasureType is used by
-- both UnitOfMeasure itself and, in a later stage, CommodityPricingHelper/EnergyCommodity).
-- Quantity's C tag is renamed QuantityUnit in cbits/qlEnumC2HS.h to avoid colliding with the
-- Quantity class bound in QuantLib.Commodity.
{#enum UnitOfMeasureType{} deriving (Show, Eq, Read, Bounded)#}
{#enum PaymentTermEventType{} deriving (Show, Eq, Read, Bounded)#}
-- experimental/commodities/commodity.hpp (PricingError::Level) and
-- experimental/commodities/energycommodity.hpp (EnergyCommodity::DeliverySchedule,
-- EnergyCommodity::QuantityPeriodicity), homed here for the same cross-cutting reason as
-- UnitOfMeasureType above (used by both EnergyCommodity's leaf constructors and
-- CommodityPricingHelper::createPricingPeriods, both in QuantLib.Instrument.Energy -- a later
-- stage than this module).
{#enum PricingErrorLevel{} deriving (Show, Eq, Read, Bounded)#}
peekPricingErrorLevelArray :: Ptr CUInt -> Ptr (Ptr CInt) -> IO [PricingErrorLevel]
peekPricingErrorLevelArray = peekIntArray' toEnumC
-- Prefix these tags to avoid clashes with QuantLib.Time.Schedule's Frequency constructors.
{#enum DeliverySchedule{} add prefix = "Delivery" deriving (Show, Eq, Read, Bounded)#}
{#enum QuantityPeriodicity{} deriving (Show, Eq, Read, Bounded)#}
-- CPI interpolation skips upstream's deprecated AsIndex value, so the enum starts at 1.
-- Shared users import it from here to avoid a module cycle.
{#enum CPIInterpolationType{} deriving (Show, Eq, Read, Bounded)#}
{#enum CalibrationBasketType{} deriving (Show, Eq, Read, Bounded)#}

-- |How an overnight leg observes its index fixings. Shared by every overnight-leg producer:
-- the swap constructors, the OIS rate helpers, and the cross-currency swaps. 'lookbackDays'
-- is 'Nothing' for upstream's @Null@ default, meaning the index's own fixing days.
--
-- Declared here rather than in a topical module because its producers span
-- "QuantLib.Instrument.Swap" and "QuantLib.TermStructure.Yield", whose build order cannot
-- accommodate one importing the other.
data OvernightObservation = OvernightObservation
  { lookbackDays :: !(Maybe Word)
  , lockoutDays :: !Word
  , applyObservationShift :: !Bool
  } deriving (Eq, Show)

-- |Upstream's own defaults: index fixing days, no lockout, no observation shift.
defaultOvernightObservation :: OvernightObservation
defaultOvernightObservation = OvernightObservation Nothing 0 False

-- Payoff/Exercise pointer hierarchy: the Finalizable/Upcastable instances and raw phantom
-- tags (CPayoff' etc.) live in QuantLib.Internal.Type alongside every other class hierarchy;
-- these are just c2hs-local aliases so {#fun#} specs below can keep writing the bare `QlX'
-- names, resolving to a raw, unwrapped Ptr (no auto-generated foreign-pointer code) since
-- construction/upcasting is handled by hand in the with* functions further down.
type QlPayoff = Ptr CPayoff'
type QlBasketPayoff = Ptr CBasketPayoff'
type QlTypePayoff = Ptr CTypePayoff'
type QlStrikedTypePayoff = Ptr CStrikedTypePayoff'
type QlPercentageStrikePayoff = Ptr CPercentageStrikePayoff'
type QlPlainVanillaPayoff = Ptr CPlainVanillaPayoff'
type QlExercise = Ptr CExercise'
type QlEuropeanExercise = Ptr CEuropeanExercise'
type QlAmericanExercise = Ptr CAmericanExercise'
type QlSwingExercise = Ptr CSwingExercise'
type QlBermudanExercise = Ptr CBermudanExercise'
type QlRebatedExercise = Ptr CRebatedExercise'
-- identity peek function: c2hs {#fun#} return specs always need a named out-marshaller,
-- even when (as here) construction should just hand back the raw, un-wrapped pointer.
peekPtr :: Ptr a -> IO (Ptr a)
peekPtr = pure
{#pointer *QlPayoff nocode#}
{#pointer *QlBasketPayoff nocode#}
{#pointer *QlTypePayoff nocode#}
{#pointer *QlStrikedTypePayoff nocode#}
{#pointer *QlPercentageStrikePayoff nocode#}
{#pointer *QlPlainVanillaPayoff nocode#}
{#pointer *QlExercise nocode#}
{#pointer *QlEuropeanExercise nocode#}
{#pointer *QlAmericanExercise nocode#}
{#pointer *QlSwingExercise nocode#}
{#pointer *QlBermudanExercise nocode#}
{#pointer *QlRebatedExercise nocode#}
{#pointer *Calendar foreign -> CCalendar nocode#}
{#pointer *QlCallability foreign -> CQlCallability nocode#}
{#pointer *QlOptimizationMethod as QlOptimizationMethod foreign -> COptimizationMethod nocode#}
{#pointer *QlEndCriteria as QlEndCriteria foreign -> CEndCriteria nocode#}
{#pointer *Constraint as QlConstraint foreign -> CConstraint nocode#}
{#pointer *FdmSchemeDesc as QlFdmSchemeDesc foreign -> CFdmSchemeDesc nocode#}
{#pointer *FittedBondDiscountCurveFittingMethod as QlFittedBondDiscountCurveFittingMethod foreign -> CFittedBondDiscountCurveFittingMethod nocode#}
{#pointer *QlClaim as Claim foreign -> CQlClaim nocode#}
{#pointer *QlBond as Bond foreign -> CBond' nocode#}
{#pointer *QlLmCorrelationModel foreign -> CLmCorrelationModel nocode#}
{#pointer *QlLmVolatilityModel foreign -> CLmVolatilityModel nocode#}
{#pointer *Rounding as QlRounding foreign -> CRounding nocode#}
{#pointer *QlAdditionalResult as RawResultPtr nocode#}

-- |Discriminants for `QlAdditionalResult.type`, bound from `enum AdditionalResultType` in
-- `cbits/qlInstrument.h` (read from the header, not hardcoded).
{#enum AdditionalResultType {} deriving (Show, Eq, Read) #}

-- monotonic flag for CubicInterpolation::Spline/::Parabolic -- tells deriveCrossEnum to give
-- these two values a runtime Bool field instead of cross-producting named sub-values (same
-- pattern as Actual360Convention etc. in CalendarEnum.chs). Order matters here in a way it
-- doesn't for the ApproximationType enum itself: these two type synonyms (and ApproximationExtra
-- below) must be declared textually *above* the deriveCrossEnum splice, since a TH splice can
-- only see top-level declarations that already exist earlier in the same module -- classifySub's
-- lookupTypeName would silently miss them (falling back to NoSub, dropping the Bool field) if
-- they were moved below the splice.
type NaturalSplineMonotonic = Bool
type ParabolicMonotonic = Bool

-- every Approximation case is driven by ApproximationType itself, so unlike
-- CalendarExtra/DayCounterExtra/IborExtra there are no non-enum-driven cases to add here
data ApproximationExtra

$(deriveCrossEnum CrossEnumSpec
    { crossTypeName = "Approximation"
    , crossMapperFn = "qlApproximation"
    , crossMainEnum = ''ApproximationType
    , crossSubSuffix = "Monotonic"
    , crossExtraType = ''ApproximationExtra
    })

deriving instance Show Approximation
deriving instance Eq Approximation
deriving instance Read Approximation

-- Approximation and interpolation dispatch through `qlTermStructureAux.cpp` rather than a factory
-- table. New enum values need no Haskell change, but `dispatchInterpolation` (and `makeCubic` for
-- an approximation) still needs a matching C++ case; otherwise QuantLib fails at runtime.

qlInterpolation :: Interpolation -> (Int, (Int, Int))
qlInterpolation BackwardFlat = (fromEnum InterpolationBackwardFlat, (0, 0))
qlInterpolation ForwardFlat = (fromEnum InterpolationForwardFlat, (0, 0))
qlInterpolation Linear = (fromEnum InterpolationLinear, (0, 0))
qlInterpolation LogLinear = (fromEnum InterpolationLogLinear, (0, 0))
qlInterpolation (Cubic x) = (fromEnum InterpolationCubic, qlApproximation x)
qlInterpolation (LogCubic x) = (fromEnum InterpolationLogCubic, qlApproximation x)
qlInterpolation Abcd = (fromEnum InterpolationAbcd, (0, 0))

qlInterpolation' :: Maybe Interpolation -> (Int, (Int, Int))
qlInterpolation' Nothing = (fromIntegral qlNullInteger, (0, 0))
qlInterpolation' (Just i) = qlInterpolation i

data Interpolation =
  BackwardFlat
  | ForwardFlat
  | Linear
  | LogLinear
  | Cubic !Approximation
  | LogCubic !Approximation
  | Abcd
  deriving (Show, Eq)

data EuropeanExercise = EuropeanExercise Day

-- | Use 'swingExerice' to construct 'Exercise'
data SwingExercise =
    SwingListExercise !(NonEmpty (Day, Word)) -- ^(dates, seconds)
    | SwingIntervalExercise !Day !Day !Word -- ^stepSizeSecs
data BermudanExercise =
    BermudanExercise !(NonEmpty Day) !Bool
    | Swing SwingExercise

-- | > Exercise
-- >  American
-- >  Early
-- >  Vanilla
-- >  EuropeanExercise
-- >  BermudanExercise
-- >    SwingExercise
-- >  Rebated (wraps another Exercise)
data Exercise =
    American
      !(Maybe Day) -- ^earliestDate
      !Day -- ^latestDate
      !Bool -- ^paoffAtExpiry
    | Early !ExerciseType !Bool
    | Vanilla !ExerciseType
    | European !EuropeanExercise
    | Bermudan !BermudanExercise
    | Rebated
        !Exercise -- ^wrapped exercise
        !Double -- ^rebate
        !Word -- ^rebateSettlementDays
        !Calendar -- ^rebatePaymentCalendar
        !BusinessDayConvention -- ^rebatePaymentConvention

-- |Candidate indices for broken IBOR periods, selected by nearest maturity or interpolated
-- between bracketing maturities in calendar days. Candidates keep their own curves and histories.
-- Their currency, fixing days/calendar and day counter must match; enable indexed coupons
-- when supplying a selection. Requires QuantLib 1.44.
data StubIndexSelection ibor
  = ClosestStubIndex !(NonEmpty (GenIborIndex ibor))
  | InterpolatedStubIndexes !(NonEmpty (GenIborIndex ibor))

type StubSelectionPtr = Ptr CStubIndexSelection'
{#pointer *QlStubIndexSelection nocode#}
{#pointer *QlIborIndex as IborIndex foreign -> CIborIndex' nocode#}

withStubIndexSelection :: Maybe (StubIndexSelection ibor) -> (Ptr CStubIndexSelection' -> IO r) -> IO r
withStubIndexSelection Nothing f = f nullPtr
withStubIndexSelection (Just selection) f = case selection of
  ClosestStubIndex indices -> withConstructed (stubIndexSelectionRaw 0 (toList indices)) f
  InterpolatedStubIndexes indices -> withConstructed (stubIndexSelectionRaw 1 (toList indices)) f

{#fun qlExercise{`ExerciseType',preErrorCheck-`String'errorCheck*-}->`QlExercise'peekPtr*#}
{#fun qlAmericanExercise{withDay*`Day',withDay*`Day',`Bool',preErrorCheck-`String'errorCheck*-}->`QlAmericanExercise'peekPtr*#}
{#fun qlAmericanExercise1{withDay*`Day',`Bool',preErrorCheck-`String'errorCheck*-}->`QlAmericanExercise'peekPtr*#}
{#fun qlBermudanExercise{withDayArray*`[Day]'&,`Bool',preErrorCheck-`String'errorCheck*-}->`QlBermudanExercise'peekPtr*#}
{#fun qlEarlyExercise{`ExerciseType',`Bool',preErrorCheck-`String'errorCheck*-}->`QlExercise'peekPtr*#}
{#fun qlEuropeanExercise{withDay*`Day',preErrorCheck-`String'errorCheck*-}->`QlEuropeanExercise'peekPtr*#}
{#fun qlSwingExercise{withDayArray*`[Day]'&,withIntArray*`[Word]'&,preErrorCheck-`String'errorCheck*-}->`QlSwingExercise'peekPtr*#}
{#fun qlSwingExercise1{withDay*`Day',withDay*`Day',fromIntegral`Word',preErrorCheck-`String'errorCheck*-}->`QlSwingExercise'peekPtr*#}
{#fun qlRebatedExercise{`QlExercise',`Double',fromIntegral`Word',withCalendar*`Calendar',fromEnumC`BusinessDayConvention',preErrorCheck-`String'errorCheck*-}->`QlRebatedExercise'peekPtr*#}

withEuropeanExercise :: EuropeanExercise -> (QlEuropeanExercise -> IO a) -> IO a
withEuropeanExercise (EuropeanExercise d) f = withConstructed (qlEuropeanExercise d) f

withSwingExercise :: SwingExercise -> (QlSwingExercise -> IO a) -> IO a
withSwingExercise (SwingListExercise ds) f = withConstructed (uncurry qlSwingExercise (unzip (toList ds))) f
withSwingExercise (SwingIntervalExercise d1 d2 s) f = withConstructed (qlSwingExercise1 d1 d2 s) f

withBermudanExercise :: BermudanExercise -> (QlBermudanExercise -> IO a) -> IO a
withBermudanExercise (BermudanExercise d p) f = withConstructed (qlBermudanExercise (toList d) p) f
withBermudanExercise (Swing e) f = withSwingExercise e (\sp -> withUpcast sp f)

withExercise :: Exercise -> (QlExercise -> IO a) -> IO a
withExercise (American Nothing d p) f = withConstructedUpcast (qlAmericanExercise1 d p) f
withExercise (American (Just d0) d p) f = withConstructedUpcast (qlAmericanExercise d0 d p) f
withExercise (Early t p) f = withConstructed (qlEarlyExercise t p) f
withExercise (Vanilla t) f = withConstructed (qlExercise t) f
withExercise (European e) f = withEuropeanExercise e (\ep -> withUpcast ep f)
withExercise (Bermudan e) f = withBermudanExercise e (\bp -> withUpcast bp f)
withExercise (Rebated e rebate days cal bdc) f = withExercise e (\ep -> withConstructedUpcast (qlRebatedExercise ep rebate days cal bdc) f)

-- | use 'percentageStrikePayoff' to construct 'Payoff'
data PercentageStrikePayoff = PercentageStrikePayoff
      !OptionType -- ^type
      !Double -- ^moneyness

-- | use 'plainVanillaPayoff' to construct 'Payoff'
data PlainVanillaPayoff = PlainVanillaPayoff
      !OptionType -- ^type
      !Double -- ^strike

-- | use 'strikedPayoff' to construct 'Payoff'
data StrikedPayoff =
  AssetOrNothing
    !OptionType -- ^type
    !Double -- ^strike
  | CashOrNothing
      !OptionType -- ^type
      !Double -- ^strike
      !Double -- ^cashPayoff
  | Gap
      !OptionType -- ^type
      !Double -- ^strike
      !Double -- ^secondStrike
  | PercentageStrike !PercentageStrikePayoff
  | PlainVanilla !PlainVanillaPayoff
  | SuperFund
      !Double -- ^strike
      !Double -- ^secondStrike
  | SuperSharePayoff
      !Double -- ^strike
      !Double -- ^secondStrike
      !Double -- ^cashPayoff
  -- |A Haskell-defined payoff carrying an advisory @(type, strike)@ pair. Build it with
  -- 'withCustomStrikedPayoff'.
  | CustomStriked
      !OptionType -- ^type
      !Double -- ^strike
      !String -- ^name
      !Callback

withPercentageStrikePayoff :: PercentageStrikePayoff -> (QlPercentageStrikePayoff -> IO a) -> IO a
withPercentageStrikePayoff (PercentageStrikePayoff t m) f = withConstructed (qlPercentageStrikePayoff t m) f

withPlainVanillaPayoff :: PlainVanillaPayoff -> (QlPlainVanillaPayoff -> IO a) -> IO a
withPlainVanillaPayoff (PlainVanillaPayoff t s) f = withConstructed (qlPlainVanillaPayoff t s) f

withStrikedPayoff :: StrikedPayoff -> (QlStrikedTypePayoff -> IO a) -> IO a
withStrikedPayoff (AssetOrNothing t s) f = withConstructed (qlAssetOrNothingPayoff t s) f
withStrikedPayoff (CashOrNothing t s c) f = withConstructed (qlCashOrNothingPayoff t s c) f
withStrikedPayoff (Gap t s ss) f = withConstructed (qlGapPayoff t s ss) f
withStrikedPayoff (PercentageStrike p) f = withPercentageStrikePayoff p (\pp -> withUpcast pp f)
withStrikedPayoff (PlainVanilla p) f = withPlainVanillaPayoff p (\pp -> withUpcast pp f)
withStrikedPayoff (SuperFund s ss) f = withConstructed (qlSuperFundPayoff s ss) f
withStrikedPayoff (SuperSharePayoff s ss c) f = withConstructed (qlSuperSharePayoff s ss c) f
withStrikedPayoff (CustomStriked t k n fp) f = withConstructed (qlStrikedPayoffFromFunction t k n fp) f

data TypePayoff = Striked !StrikedPayoff
  | Floating !OptionType -- ^type
data BasketPayoff =
    Average
      !Payoff -- ^p
      !Word -- ^n
  | AverageMultiple
      !Payoff -- ^p
      ![Double] -- ^a
  | Max
      !Payoff -- ^p
  | Min
      !Payoff -- ^p
  | Spread
      !Payoff -- ^p
  -- |A Haskell-defined @accumulate@ over the underlying-state vector, wrapped around a base
  -- 'Payoff' exactly as 'Max'\/'Min'\/'Spread' are. Build it with 'withCustomBasketPayoff'.
  | CustomAccumulate
      !Payoff -- ^base payoff, applied to the accumulated value
      !Callback

withTypePayoff :: TypePayoff -> (QlTypePayoff -> IO a) -> IO a
withTypePayoff (Floating t) f = withConstructed (qlFloatingTypePayoff t) f
withTypePayoff (Striked s) f = withStrikedPayoff s (\sp -> withUpcast sp f)

withBasketPayoff :: BasketPayoff -> (QlBasketPayoff -> IO a) -> IO a
withBasketPayoff (Average p n) f = withPayoff p (\pp -> withConstructed (qlAverageBasketPayoff pp n) f)
withBasketPayoff (AverageMultiple p a) f = withPayoff p (\pp -> withConstructed (qlAverageBasketPayoff1 pp a) f)
withBasketPayoff (Max p) f = withPayoff p (\pp -> withConstructed (qlMaxBasketPayoff pp) f)
withBasketPayoff (Min p) f = withPayoff p (\pp -> withConstructed (qlMinBasketPayoff pp) f)
withBasketPayoff (Spread p) f = withPayoff p (\pp -> withConstructed (qlSpreadBasketPayoff pp) f)
withBasketPayoff (CustomAccumulate p fp) f = withPayoff p (\pp -> withConstructed (qlBasketPayoffFromFunction pp fp) f)

-- | > Payoff
-- >  DoubleStickyRatchet
-- >  ForwardType
-- >  RatchettMax
-- >  RatchetMin
-- >  StickyMax
-- >  StickyMin
-- >  Sticky
-- >  Custom
-- >  TypePayoff
-- >    Floating
-- >    Striked
-- >      AssetOrNothing
-- >      CashOrNothing
-- >      Gap
-- >      PercentageStrike
-- >      PlainVanilla
-- >      SuperFund
-- >      SuperSharePayoff
-- >      CustomStriked
-- >  BasketPayoff
-- >    Average
-- >    AverageMultiple
-- >    Max
-- >    Min
-- >    Spread
-- >    CustomAccumulate
data Payoff =
    DoubleStickyRatchet
      !Double -- ^type1
      !Double -- ^type2
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^gearing3
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^spread3
      !Double -- ^initialValue1
      !Double -- ^initialValue2
      !Double -- ^accrualFactor
  | ForwardType
      !PositionType -- ^type
      !Double -- ^strike
  | RatchetMax
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^gearing3
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^spread3
      !Double -- ^initialValue1
      !Double -- ^initialValue2
      !Double -- ^accrualFactor
  | RatchetMin
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^gearing3
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^spread3
      !Double -- ^initialValue1
      !Double -- ^initialValue2
      !Double -- ^accrualFactor
  | Ratchet
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^initialValue
      !Double -- ^accrualFactor
  | StickyMax
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^gearing3
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^spread3
      !Double -- ^initialValue1
      !Double -- ^initialValue2
      !Double -- ^accrualFactor
  | StickyMin
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^gearing3
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^spread3
      !Double -- ^initialValue1
      !Double -- ^initialValue2
      !Double -- ^accrualFactor
  | Sticky
      !Double -- ^gearing1
      !Double -- ^gearing2
      !Double -- ^spread1
      !Double -- ^spread2
      !Double -- ^initialValue
      !Double -- ^accrualFactor
  | Type !TypePayoff
  | Basket !BasketPayoff
  -- |A Haskell-defined payoff with a managed callback; build with 'withCustomPayoff'.
  | Custom
      !String -- ^name
      !String -- ^description
      !Callback


{#fun qlAssetOrNothingPayoff{`OptionType',`Double',preErrorCheck-`String'errorCheck*-}->`QlStrikedTypePayoff'peekPtr*#}
{#fun qlAverageBasketPayoff{`QlPayoff',fromIntegral`Word',preErrorCheck-`String'errorCheck*-}->`QlBasketPayoff'peekPtr*#}
{#fun qlCashOrNothingPayoff{`OptionType',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlStrikedTypePayoff'peekPtr*#}
{#fun qlDoubleStickyRatchetPayoff{`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlFloatingTypePayoff{`OptionType',preErrorCheck-`String'errorCheck*-}->`QlTypePayoff'peekPtr*#}
{#fun qlForwardTypePayoff{`PositionType',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlGapPayoff{`OptionType',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlStrikedTypePayoff'peekPtr*#}
{#fun qlMaxBasketPayoff{`QlPayoff',preErrorCheck-`String'errorCheck*-}->`QlBasketPayoff'peekPtr*#}
{#fun qlMinBasketPayoff{`QlPayoff',preErrorCheck-`String'errorCheck*-}->`QlBasketPayoff'peekPtr*#}
{#fun qlPercentageStrikePayoff{`OptionType',`Double',preErrorCheck-`String'errorCheck*-}->`QlPercentageStrikePayoff'peekPtr*#}
{#fun qlPlainVanillaPayoff{`OptionType',`Double',preErrorCheck-`String'errorCheck*-}->`QlPlainVanillaPayoff'peekPtr*#}
{#fun qlRatchetMaxPayoff{`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlRatchetMinPayoff{`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlRatchetPayoff{`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlSpreadBasketPayoff{`QlPayoff',preErrorCheck-`String'errorCheck*-}->`QlBasketPayoff'peekPtr*#}
{#fun qlStickyMaxPayoff{`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlStickyMinPayoff{`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlStickyPayoff{`Double',`Double',`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlSuperFundPayoff{`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlStrikedTypePayoff'peekPtr*#}
{#fun qlSuperSharePayoff{`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlStrikedTypePayoff'peekPtr*#}
{#fun qlAverageBasketPayoff1{`QlPayoff',withDoubleArray*`[Double]'&,preErrorCheck-`String'errorCheck*-}->`QlBasketPayoff'peekPtr*#}
{#fun qlPayoffFromFunction{`String',`String',withCallbackPtr*`Callback',preErrorCheck-`String'errorCheck*-}->`QlPayoff'peekPtr*#}
{#fun qlBasketPayoffFromFunction{`QlPayoff',withCallbackPtr*`Callback',preErrorCheck-`String'errorCheck*-}->`QlBasketPayoff'peekPtr*#}
{#fun qlStrikedPayoffFromFunction{`OptionType',`Double',`String',withCallbackPtr*`Callback',preErrorCheck-`String'errorCheck*-}->`QlStrikedTypePayoff'peekPtr*#}

withPayoff :: Payoff -> (QlPayoff -> IO a) -> IO a
withPayoff (DoubleStickyRatchet t1 t2 g1 g2 g3 s1 s2 s3 i1 i2 a) f = withConstructed (qlDoubleStickyRatchetPayoff t1 t2 g1 g2 g3 s1 s2 s3 i1 i2 a) f
withPayoff (ForwardType t s) f = withConstructed (qlForwardTypePayoff t s) f
withPayoff (RatchetMax g1 g2 g3 s1 s2 s3 i1 i2 a) f = withConstructed (qlRatchetMaxPayoff g1 g2 g3 s1 s2 s3 i1 i2 a) f
withPayoff (RatchetMin g1 g2 g3 s1 s2 s3 i1 i2 a) f = withConstructed (qlRatchetMinPayoff g1 g2 g3 s1 s2 s3 i1 i2 a) f
withPayoff (Ratchet g1 g2 s1 s2 i a) f = withConstructed (qlRatchetPayoff g1 g2 s1 s2 i a) f
withPayoff (StickyMax g1 g2 g3 s1 s2 s3 i1 i2 a) f = withConstructed (qlStickyMaxPayoff g1 g2 g3 s1 s2 s3 i1 i2 a) f
withPayoff (StickyMin g1 g2 g3 s1 s2 s3 i1 i2 a) f = withConstructed (qlStickyMinPayoff g1 g2 g3 s1 s2 s3 i1 i2 a) f
withPayoff (Sticky g1 g2 s1 s2 i a) f = withConstructed (qlStickyPayoff g1 g2 s1 s2 i a) f
withPayoff (Type t) f = withTypePayoff t (\tp -> withUpcast tp f)
withPayoff (Basket b) f = withBasketPayoff b (\bp -> withUpcast bp f)
withPayoff (Custom n d fp) f = withConstructed (qlPayoffFromFunction n d fp) f

-- |Wrap a Haskell @price -> value@ function as a real QuantLib @Payoff@, usable anywhere a
-- 'Payoff' is (@QuantLib.Instrument.Option.oneAssetOption@, @multiAssetOption@,
-- @QuantLib.Instrument.Swap.varianceOption@, @QuantLib.Method.fdmLogInnerValue@,
-- @fdmCellAveragingInnerValue@, ...) -- the fully custom counterpart to the concrete
-- pre-implemented payoffs listed by the 'Payoff' constructors above.
--
-- The payoff ADT and every native consumer retain the callback after the continuation returns.
-- Callback exceptions are rethrown by the enclosing Haskell call.
--
-- @name@ and @description@ are what QuantLib's own error messages and @Payoff::name@ report; they
-- are not interpreted.
--
-- __Not every engine accepts a non-standard payoff.__ QuantLib's analytic, binomial, finite-
-- difference and @MCEuropeanEngine@ families all recover the strike by downcasting to
-- @StrikedTypePayoff@\/@PlainVanillaPayoff@ first, and a further ~30 engines route through
-- @BlackCalculator@, whose @AcyclicVisitor@ knows only the four built-in striked payoffs. Most of
-- these fail with a clean QuantLib exception. The two bound finite-difference vanilla engines
-- validate the required striked payoff, including on QuantLib 1.43. Confirmed-generic consumers:
-- @QuantLib.Method.fdmLogInnerValue@\/@fdmCellAveragingInnerValue@ (and hence @fdmSolve@), and
-- @QuantLib.PricingEngine.mcAmericanEngine@ with @controlVariate = False@.
withCustomPayoff :: String -- ^name
  -> String -- ^description
  -> (Double -> Double) -- ^payoff(price)
  -> (Payoff -> IO b) -> IO b
withCustomPayoff n d f k = withPayoffFun f (k . Custom n d)

-- |As 'withCustomPayoff', but produces a real QuantLib @StrikedTypePayoff@ carrying an
-- @(optionType, strike)@ pair alongside the Haskell function.
--
-- __The pair is advisory: it does not define the payoff__ -- @payoff(price)@ alone does, exactly
-- as for 'withCustomPayoff'. It exists because QuantLib's finite-difference vanilla engines reach
-- past the @Payoff@ interface for a strike when sizing their grid:
-- @FdBlackScholesVanillaEngine@ @dynamic_pointer_cast@s to @StrikedTypePayoff@ and calls
-- @strike()@ twice -- once for the mesher's extent, once for its node-concentration point -- then
-- hands the payoff itself to @FdmLogInnerValue@, which takes a plain @Payoff@. So a payoff built
-- here prices correctly through 'QuantLib.PricingEngine.fdBlackScholesVanillaEngine' and
-- 'QuantLib.PricingEngine.fdHestonVanillaEngine', where one built by 'withCustomPayoff' is
-- rejected with an exception. Pass the strike you want the grid centred on.
--
-- The payoff and native consumers retain the callback after the continuation returns.
-- @description@ is derived from the type and strike by @StrikedTypePayoff@.
--
-- Engines routing through @BlackCalculator@ (the @analytic*@ family) still reject this, as they
-- must: its @AcyclicVisitor@ knows only the four built-in striked payoffs, and there is no
-- closed-form price for an arbitrary function. That rejection is a clean QuantLib exception.
withCustomStrikedPayoff :: OptionType -- ^advisory option type
  -> Double -- ^advisory strike (grid centring only)
  -> String -- ^name
  -> (Double -> Double) -- ^payoff(price)
  -> (StrikedPayoff -> IO b) -> IO b
withCustomStrikedPayoff t k n f g = withPayoffFun f (g . CustomStriked t k n)

-- |Wrap a Haskell @underlyings -> accumulated@ function as a real QuantLib @BasketPayoff@ around
-- @base@ (which is applied to the accumulated value, exactly as for 'Max'\/'Min'\/'Spread') --
-- usable with @QuantLib.Instrument.Option.basketOption@ and
-- @QuantLib.Method.fdmLogBasketInnerValue@. Same ownership rule as 'withCustomPayoff';
-- unlike it, this callback crosses once per evaluation with the whole underlying-state vector,
-- because that is the shape @BasketPayoff::accumulate@ already has upstream.
withCustomBasketPayoff :: Payoff -- ^base payoff
  -> ([Double] -> Double) -- ^accumulate(underlyings)
  -> (BasketPayoff -> IO b) -> IO b
withCustomBasketPayoff base f k = withBasketAccumulateFun f (k . CustomAccumulate base)

data Callability =
  Soft
    !(Double, BondPriceType)
    !Day
    !Double -- ^trigger
  | Callability
      !(Double, BondPriceType)
      !CallabilityType
      !Day

callability :: Callability -> IO (Standalone CQlCallability)
callability (Soft (p, t) d tg) = qlSoftCallability p t d tg
callability (Callability (p, t) ct d) = qlCallability p t ct d

newtype EnumMeta a b = EnumMeta (a -> IO (Standalone b))

withEnumType :: EnumMeta a b -> a -> (Ptr b -> IO c) -> IO c
withEnumType (EnumMeta t) x f = t x >>= (`withStandalone` f)

withMaybeEnumType :: EnumMeta a b -> Maybe a -> (Ptr b -> IO c) -> IO c
withMaybeEnumType (EnumMeta t) x f = maybe (f nullPtr) (\xx -> t xx >>= (`withStandalone` f)) x

withEnumTypeArray :: EnumMeta a b -> [a] -> ((CUInt, Ptr (Ptr b)) -> IO c) -> IO c
withEnumTypeArray m x f = withMany (withEnumType m) x (`withArray` (\px -> f (fromIntegral $ length x, px)))

callabilityMeta :: EnumMeta Callability CQlCallability
callabilityMeta = EnumMeta callability

withCallability :: Callability -> (Ptr CQlCallability -> IO a) -> IO a
withCallability = withEnumType callabilityMeta

withCallabilityArray :: [Callability] -> ((CUInt, Ptr (Ptr CQlCallability)) -> IO c) -> IO c
withCallabilityArray = withEnumTypeArray callabilityMeta

constraintMeta :: EnumMeta Constraint CConstraint
constraintMeta = EnumMeta constraint

roundingMeta :: EnumMeta Rounding CRounding
roundingMeta = EnumMeta rounding

withMaybeConstraint :: Maybe Constraint -> (Ptr CConstraint -> IO a) -> IO a
withMaybeConstraint = withMaybeEnumType constraintMeta

withMaybeRounding :: Maybe Rounding -> (Ptr CRounding -> IO a) -> IO a
withMaybeRounding = withMaybeEnumType roundingMeta

withConstraint :: Constraint -> (Ptr CConstraint -> IO a) -> IO a
withConstraint = withEnumType constraintMeta

withRounding :: Rounding -> (Ptr CRounding -> IO a) -> IO a
withRounding = withEnumType roundingMeta

fittedBondDiscountFittingMethodMeta :: EnumMeta FittingMethod CFittedBondDiscountCurveFittingMethod
fittedBondDiscountFittingMethodMeta = EnumMeta fittingMethod

withFittedBondDiscountCurveFittingMethod :: FittingMethod -> (Ptr CFittedBondDiscountCurveFittingMethod -> IO a) -> IO a
withFittedBondDiscountCurveFittingMethod = withEnumType fittedBondDiscountFittingMethodMeta

endCriteriaMeta :: EnumMeta EndCriteria CEndCriteria
endCriteriaMeta = EnumMeta endCriteria

withEndCriteria :: EndCriteria -> (Ptr CEndCriteria -> IO a) -> IO a
withEndCriteria = withEnumType endCriteriaMeta

withMaybeEndCriteria :: Maybe EndCriteria -> (Ptr CEndCriteria -> IO a) -> IO a
withMaybeEndCriteria = withMaybeEnumType endCriteriaMeta

fdmSchemeDescMeta :: EnumMeta FdmScheme CFdmSchemeDesc
fdmSchemeDescMeta = EnumMeta fdmScheme

withFdmSchemeDesc :: FdmScheme -> (Ptr CFdmSchemeDesc -> IO a) -> IO a
withFdmSchemeDesc = withEnumType fdmSchemeDescMeta

optimizationMethodMeta :: EnumMeta OptimizationMethod COptimizationMethod
optimizationMethodMeta = EnumMeta optimizationMethod

withOptimizationMethod :: OptimizationMethod -> (Ptr COptimizationMethod -> IO a) -> IO a
withOptimizationMethod = withEnumType optimizationMethodMeta

withMaybeOptimizationMethod :: Maybe OptimizationMethod -> (Ptr COptimizationMethod -> IO a) -> IO a
withMaybeOptimizationMethod = withMaybeEnumType optimizationMethodMeta

-- Payoff/Exercise with* functions are now defined directly, near their ADTs, using
-- Upcastable/GenForeignPtr (see QuantLib.Internal.Type) instead of EnumMeta'/IsQlPayoff/IsQlExercise.

-- |callability leaving to the holder the possibility to convert
{#fun qlSoftCallability{`Double',`BondPriceType',withDay*`Day',`Double',preErrorCheck-`String'errorCheck*-}->`QlCallability'peekCallability*#}
{#fun qlCallability{`Double',`BondPriceType',`CallabilityType',withDay*`Day',preErrorCheck-`String'errorCheck*-}->`QlCallability'peekCallability*#}

-- Every constructor below binds the QuantLib overload's leading optimizationMethod param via a
-- trailing Maybe OptimizationMethod field (Nothing -> upstream's own empty-shared_ptr default,
-- letting the fit fall back to LevenbergMarquardt). OptimizationMethod's hasquant-side handle
-- (QlOptimizationMethod) is a shared_ptr box, not a raw Haskell-finalized pointer -- see the
-- qlaux.h comment above the QlEndCriteria/QlOptimizationMethod typedefs -- so a caller-supplied
-- one can safely be copied into FittingMethod's own shared_ptr member (and survive
-- FittedBondDiscountCurve cloning the fitting method) regardless of when Haskell's own box is
-- collected.
data FittingMethod =
  CubicBSplines
    ![Double] -- ^knotVector (year fraction)
    !Bool -- ^constrainAtZero
    ![Double] -- ^weights
    ![Double] -- ^l2
    !Double -- ^minCutoffTime
    !Double -- ^maxCutoffTime
    !(Maybe Constraint)
    !(Maybe OptimizationMethod)
  | ExponentialSplines
    !Bool -- ^constrainAtZero
    ![Double] -- ^weights
    ![Double] -- ^l2
    !Double -- ^minCutoffTime
    !Double -- ^maxCutoffTime
    !Word -- ^numCoeffs
    !(Maybe Double) -- ^fixedKappa
    !(Maybe Constraint)
    !(Maybe OptimizationMethod)
  | NelsonSiegel
    ![Double] -- ^weights
    ![Double] -- ^l2
    !Double -- ^minCutoffTime
    !Double -- ^maxCutoffTime
    !(Maybe Constraint)
    !(Maybe OptimizationMethod)
  | SimplePolynomial
    !Word -- ^degree
    !Bool -- ^constrainAtZero
    ![Double] -- ^weights
    ![Double] -- ^l2
    !Double -- ^minCutoffTime
    !Double -- ^maxCutoffTime
    !(Maybe Constraint)
    !(Maybe OptimizationMethod)
  | Svensson
    ![Double] -- ^weights
    ![Double] -- ^l2
    !Double -- ^minCutoffTime
    !Double -- ^maxCutoffTime
    !(Maybe Constraint)
    !(Maybe OptimizationMethod)

fittingMethod :: FittingMethod -> IO QlFittedBondDiscountCurveFittingMethod
fittingMethod (CubicBSplines k c w l2 mn mx cn om) = qlCubicBSplinesFitting k c w l2 mn mx om cn
fittingMethod (ExponentialSplines c w l2 mn mx n fk cn om) = qlExponentialSplinesFitting c w l2 mn mx n fk om cn
fittingMethod (NelsonSiegel w l2 mn mx cn om) = qlNelsonSiegelFitting w l2 mn mx om cn
fittingMethod (SimplePolynomial d c w l2 mn mx cn om) = qlSimplePolynomialFitting d c w l2 mn mx om cn
fittingMethod (Svensson w l2 mn mx cn om) = qlSvenssonFitting w l2 mn mx om cn

{#fun qlCubicBSplinesFitting{withDoubleArray*`[Double]'&,`Bool'
  ,withDoubleArray*`[Double]'& -- ^weights
  ,withDoubleArray*`[Double]'& -- ^l2
  ,`Double' -- ^minCutoffTime
  ,`Double' -- ^maxCutoffTime
  ,withMaybeOptimizationMethod*`Maybe OptimizationMethod'
  ,withMaybeConstraint*`Maybe Constraint'
  ,preErrorCheck-`String'errorCheck*-}->`QlFittedBondDiscountCurveFittingMethod'peekFittedBondDiscountCurveFittingMethod*#}
{#fun qlExponentialSplinesFitting{`Bool'
  ,withDoubleArray*`[Double]'& -- ^weights
  ,withDoubleArray*`[Double]'& -- ^l2
  ,`Double' -- ^minCutoffTime
  ,`Double' -- ^maxCutoffTime
  ,fromIntegral`Word' -- ^numCoeffs
  ,fromMaybeDouble`Maybe Double' -- ^fixedKappa
  ,withMaybeOptimizationMethod*`Maybe OptimizationMethod'
  ,withMaybeConstraint*`Maybe Constraint'
  ,preErrorCheck-`String'errorCheck*-}->`QlFittedBondDiscountCurveFittingMethod'peekFittedBondDiscountCurveFittingMethod*#}
{#fun qlNelsonSiegelFitting{withDoubleArray*`[Double]'& -- ^weights
  ,withDoubleArray*`[Double]'& -- ^l2
  ,`Double' -- ^minCutoffTime
  ,`Double' -- ^maxCutoffTime
  ,withMaybeOptimizationMethod*`Maybe OptimizationMethod'
  ,withMaybeConstraint*`Maybe Constraint'
  ,preErrorCheck-`String'errorCheck*-}->`QlFittedBondDiscountCurveFittingMethod'peekFittedBondDiscountCurveFittingMethod*#}
{#fun qlSimplePolynomialFitting{fromIntegral`Word',`Bool'
  ,withDoubleArray*`[Double]'& -- ^weights
  ,withDoubleArray*`[Double]'& -- ^l2
  ,`Double' -- ^minCutoffTime
  ,`Double' -- ^maxCutoffTime
  ,withMaybeOptimizationMethod*`Maybe OptimizationMethod'
  ,withMaybeConstraint*`Maybe Constraint'
  ,preErrorCheck-`String'errorCheck*-}->`QlFittedBondDiscountCurveFittingMethod'peekFittedBondDiscountCurveFittingMethod*#}
{#fun qlSvenssonFitting{withDoubleArray*`[Double]'& -- ^weights
  ,withDoubleArray*`[Double]'& -- ^l2
  ,`Double' -- ^minCutoffTime
  ,`Double' -- ^maxCutoffTime
  ,withMaybeOptimizationMethod*`Maybe OptimizationMethod'
  ,withMaybeConstraint*`Maybe Constraint'
  ,preErrorCheck-`String'errorCheck*-}->`QlFittedBondDiscountCurveFittingMethod'peekFittedBondDiscountCurveFittingMethod*#}

data FdmScheme =
  FdmScheme
    !FdmSchemeType -- ^type
    !Double -- ^theta
    !Double -- ^mu
  | CraigSneyd
  | Douglas
  | ExplicitEuler
  | Hundsdorfer
  | ImplicitEuler
  | ModifiedCraigSneyd
  | ModifiedHundsdorfer
  | MethodOfLines
    !Double -- ^eps
    !Double -- ^relInitStepSize

{#fun qlFdmSchemeDesc{`FdmSchemeType',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescCraigSneyd{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescDouglas{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescExplicitEuler{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescHundsdorfer{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescImplicitEuler{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescModifiedCraigSneyd{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescModifiedHundsdorfer{preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}
{#fun qlFdmSchemeDescMethodOfLines{`Double' -- ^eps
  ,`Double' -- ^relInitStepSize
  ,preErrorCheck-`String'errorCheck*-}->`QlFdmSchemeDesc'peekFdmSchemeDesc*#}

fdmScheme :: FdmScheme -> IO QlFdmSchemeDesc
fdmScheme (FdmScheme t th mu) = qlFdmSchemeDesc t th mu
fdmScheme CraigSneyd = qlFdmSchemeDescCraigSneyd
fdmScheme Douglas = qlFdmSchemeDescDouglas
fdmScheme ExplicitEuler = qlFdmSchemeDescExplicitEuler
fdmScheme Hundsdorfer = qlFdmSchemeDescHundsdorfer
fdmScheme ImplicitEuler = qlFdmSchemeDescImplicitEuler
fdmScheme ModifiedCraigSneyd = qlFdmSchemeDescModifiedCraigSneyd
fdmScheme ModifiedHundsdorfer = qlFdmSchemeDescModifiedHundsdorfer
fdmScheme (MethodOfLines eps relInitStepSize) = qlFdmSchemeDescMethodOfLines eps relInitStepSize

data Constraint =
  Boundary
    !Double -- ^low
    !Double -- ^high
  | Composite
    !Constraint -- ^c1
    !Constraint -- ^c2
  | NoConstraint
  | PositiveConstraint

constraint :: Constraint -> IO QlConstraint
constraint (Boundary l h) = qlBoundaryConstraint l h
constraint (Composite c1 c2) = qlCompositeConstraint c1 c2
constraint NoConstraint = qlNoConstraint
constraint PositiveConstraint = qlPositiveConstraint

{#fun qlBoundaryConstraint{`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlConstraint'peekConstraint*#}
{#fun qlCompositeConstraint{withConstraint*`Constraint',withConstraint*`Constraint',preErrorCheck-`String'errorCheck*-}->`QlConstraint'peekConstraint*#}
{#fun qlNoConstraint{preErrorCheck-`String'errorCheck*-}->`QlConstraint'peekConstraint*#}
{#fun qlPositiveConstraint{preErrorCheck-`String'errorCheck*-}->`QlConstraint'peekConstraint*#}

data OptimizationMethod =
  LevenbergMarquardt
    !Double -- ^epsfcn
    !Double -- ^xtol
    !Double -- ^gtol
    !Bool -- ^useCostFunctionsJacobian
  | Simplex !Double -- ^lambda, characteristic length

optimizationMethod :: OptimizationMethod -> IO QlOptimizationMethod
optimizationMethod (LevenbergMarquardt e x g j) = qlLevenbergMarquardt e x g j
optimizationMethod (Simplex l) = qlSimplex l
{#fun qlLevenbergMarquardt{`Double',`Double',`Double',`Bool',preErrorCheck-`String'errorCheck*-}->`QlOptimizationMethod'peekOptimizationMethod*#}
{#fun qlSimplex{`Double',preErrorCheck-`String'errorCheck*-}->`QlOptimizationMethod'peekOptimizationMethod*#}

data EndCriteria =
  EndCriteria
    !Word -- ^maxIterations
    !Word -- ^maxStationaryStateIterations
    !Double -- ^rootEpsilon
    !Double -- ^functionEpsilon
    !Double -- ^gradientNormEpsilon

endCriteria :: EndCriteria -> IO QlEndCriteria
endCriteria (EndCriteria m1 m2 e f g) = qlEndCriteria m1 m2 e f g
{#fun qlEndCriteria{fromIntegral`Word',fromIntegral`Word',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlEndCriteria'peekEndCriteria*#}

data Rounding = NoRounding
  | Rounding
    !Int -- ^precision
    !RoundingType
    !Int -- ^digit
  deriving (Show, Eq)

rounding :: Rounding -> IO QlRounding
rounding NoRounding = qlRounding
rounding (Rounding p t d) = qlRounding1 p t d

{#fun qlRounding{preErrorCheck-`String'errorCheck*-}->`QlRounding'peekRounding*#}
{#fun qlRounding1{`Int',`RoundingType',`Int',preErrorCheck-`String'errorCheck*-}->`QlRounding'peekRounding*#}

data LmCorrelationModel = ConstWrapperCorrelation LmCorrelationModel
  | ExponentialCorrelation Word -- ^size
    !Double -- ^rho
  | LinearExponentialCorrelation Word -- ^size
    !Double -- ^rho
    !Double -- ^beta
    !Word -- ^factors
  deriving (Show, Eq)

{#fun qlLmConstWrapperCorrelationModel{withStandalone*`QlLmCorrelationModel',preErrorCheck-`String'errorCheck*-}->`QlLmCorrelationModel'peekLmCorrelationModel*#}
{#fun qlLmExponentialCorrelationModel{fromIntegral`Word',`Double',preErrorCheck-`String'errorCheck*-}->`QlLmCorrelationModel'peekLmCorrelationModel*#}
{#fun qlLmLinearExponentialCorrelationModel{fromIntegral`Word',`Double',`Double',fromIntegral`Word',preErrorCheck-`String'errorCheck*-}->`QlLmCorrelationModel'peekLmCorrelationModel*#}

correlationModel :: LmCorrelationModel -> IO QlLmCorrelationModel
correlationModel (ConstWrapperCorrelation m) = correlationModel m >>= qlLmConstWrapperCorrelationModel
correlationModel (ExponentialCorrelation s r) = qlLmExponentialCorrelationModel s r
correlationModel (LinearExponentialCorrelation s r b f) = qlLmLinearExponentialCorrelationModel s r b f

correlationModelMeta :: EnumMeta LmCorrelationModel CLmCorrelationModel
correlationModelMeta = EnumMeta correlationModel

withLmCorrelationModel :: LmCorrelationModel -> (Ptr CLmCorrelationModel -> IO a) -> IO a
withLmCorrelationModel = withEnumType correlationModelMeta

data LmVolatilityModel = ConstWrapperVolatility LmVolatilityModel
  | FixedVolatility !(NonEmpty (Double, Double)) -- ^(start time, volatility)
  | LinearExponentialVolatility ![Double] -- ^fixing times
    !Double -- ^a
    !Double -- ^b
    !Double -- ^c
    !Double -- ^d
  deriving (Show, Eq)

{#fun qlLmConstWrapperVolatilityModel{withStandalone*`QlLmVolatilityModel',preErrorCheck-`String'errorCheck*-}->`QlLmVolatilityModel'peekLmVolatilityModel*#}
{#fun qlLmFixedVolatilityModel{withDoubleArray*`[Double]'&,withDoubleArray*`[Double]'&,preErrorCheck-`String'errorCheck*-}->`QlLmVolatilityModel'peekLmVolatilityModel*#}
{#fun qlLmLinearExponentialVolatilityModel{withDoubleArray*`[Double]'&,`Double',`Double',`Double',`Double',preErrorCheck-`String'errorCheck*-}->`QlLmVolatilityModel'peekLmVolatilityModel*#}

volatilityModel :: LmVolatilityModel -> IO QlLmVolatilityModel
volatilityModel (ConstWrapperVolatility m) = volatilityModel m >>= qlLmConstWrapperVolatilityModel
volatilityModel (FixedVolatility tv) = qlLmFixedVolatilityModel vols times
  where (times, vols) = unzip (toList tv)
volatilityModel (LinearExponentialVolatility s a b c d) = qlLmLinearExponentialVolatilityModel s a b c d

volatilityModelMeta :: EnumMeta LmVolatilityModel CLmVolatilityModel
volatilityModelMeta = EnumMeta volatilityModel

withLmVolatilityModel :: LmVolatilityModel -> (Ptr CLmVolatilityModel -> IO a) -> IO a
withLmVolatilityModel = withEnumType volatilityModelMeta

data Claim = FaceValue | forall b. FaceValueAccrual (GenBond b)
claimMeta :: EnumMeta Claim CQlClaim
claimMeta = EnumMeta claim

withClaim :: Claim -> (Ptr CQlClaim -> IO a) -> IO a
withClaim = withEnumType claimMeta

claim :: Claim -> IO QlClaim
claim FaceValue = qlFaceValueClaim
claim (FaceValueAccrual b) = qlFaceValueAccrualClaim b

-- |Claim on a notional
{#fun qlFaceValueClaim{preErrorCheck-`String'errorCheck*-}->`QlClaim'peekClaim*#}

-- |Claim on the notional of a reference security, including accrual
{#fun qlFaceValueAccrualClaim{withBond*`GenBond b',preErrorCheck-`String'errorCheck*-}->`QlClaim'peekClaim*#}

strikedPayoff :: StrikedPayoff -> Payoff
strikedPayoff = Type . Striked

percentageStrikePayoff :: PercentageStrikePayoff -> Payoff
percentageStrikePayoff = Type . Striked . PercentageStrike

plainVanillaPayoff :: PlainVanillaPayoff -> Payoff
plainVanillaPayoff = Type . Striked . PlainVanilla

swingExercise :: SwingExercise -> Exercise
swingExercise = Bermudan . Swing

-- |One value from QuantLib's `Instrument::additionalResults()` map. QuantLib stores the map as
-- `ext::any`, so this Haskell view picks three concrete shapes -- `Real` (`Double`), `std::string`
-- (`String`), `std::vector<Real>` (`[Double]`) -- plus an `UnsupportedVal` fallback recording the
-- value's C++ RTTI type name, so no key is ever silently dropped or mislabelled.
data AdditionalResultVal = RealVal Double | StringVal String | RealVectorVal [Double] | UnsupportedVal String
  deriving (Show, Eq)

type RawResultPtr = Ptr RawResult

-- |One raw `QlAdditionalResult` entry, peeked field-by-field via c2hs `{#get#}` hooks. Its
-- `Storable` instance (`sizeOf`/`alignment` from `{#sizeof#}`/`{#alignof#}`, both read straight
-- from the C struct layout, not hand-computed) is what lets `peekStructArray`
-- (`QuantLib.Internal`) walk the C array via a plain `peekArray`, rather than hand-rolled pointer
-- arithmetic.
data RawResult = RawResult
  { rKey :: CString, rType :: CInt, rDval :: CDouble
  , rSval :: CString, rVarr :: Ptr CDouble, rVlen :: CUInt }

instance Storable RawResult where
  sizeOf _ = {#sizeof QlAdditionalResult #}
  alignment _ = {#alignof QlAdditionalResult #}
  peek p = RawResult <$> {#get QlAdditionalResult.key #} p
                      <*> {#get QlAdditionalResult.type #} p
                      <*> {#get QlAdditionalResult.dval #} p
                      <*> {#get QlAdditionalResult.sval #} p
                      <*> {#get QlAdditionalResult.varr #} p
                      <*> {#get QlAdditionalResult.vlen #} p
  poke = error "RawResult is peek-only (read from C, never constructed in Haskell)"

-- |Convert one raw entry into its keyed Haskell value. `sval`/`varr` are only read for the
-- discriminant that owns them; their buffers are released in bulk afterwards, by
-- `qlFreeAdditionalResults`, not per-field here.
convertResult :: RawResult -> IO (String, AdditionalResultVal)
convertResult r = do
  key <- peekCString (rKey r)
  val <- case toEnum (fromIntegral (rType r)) of
    AdditionalResultDouble -> return (RealVal (realToFrac (rDval r)))
    AdditionalResultString -> StringVal <$> peekCString (rSval r)
    AdditionalResultDoubleVector -> RealVectorVal . map realToFrac
                                       <$> peekArray (fromIntegral (rVlen r)) (rVarr r)
    AdditionalResultUnknown -> UnsupportedVal <$> peekCString (rSval r)
  return (key, val)

-- |Peek the C array of `QlAdditionalResult` into a keyed list, then release the whole array (keys,
-- `sval`/`varr` buffers, and the array itself) in one `qlFreeAdditionalResults` call.
peekAdditionalResults :: Ptr CUInt -> Ptr RawResultPtr -> IO [(String, AdditionalResultVal)]
peekAdditionalResults = peekStructArray convertResult (\l p -> qlFreeAdditionalResults l (castPtr p))

-- vim: set ff=unix ts=8 sts=2 sw=2 et:

preAdditionalResults :: ((Ptr CUInt, Ptr RawResultPtr) -> IO b) -> IO b
preAdditionalResults = preArrayWith (\l p -> qlFreeAdditionalResults l (castPtr p))

{#fun qlStubIndexSelection as stubIndexSelectionRaw{`Int'
  ,withIborIndexArray*`[GenIborIndex ibor]'&
  ,preErrorCheck-`String'errorCheck*-}->`StubSelectionPtr'peekPtr*#}