
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR Description.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General
# Public License along with this library; if not, write to the
# Free Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA  02111-1307  USA


################################################################################
# MOMENT MATCHING:                   DESCRIPTION:
#  MomentMatchedAsianOption           Valuate moment matched option prices
#  .LevyTurnbullWakemanAsianOption     Log-Normal Approximation
#  .MilevskyPosnerAsianOption          Reciprocal-Gamma Approximation
#  .PosnerMilevskyAsianOption          Johnson Type I Approximation
#  MomentMatchedAsianDensity          Valuate moment matched option densities
#  .LevyTurnbullWakemanAsianDensity    Log-Normal Approximation
#  .MilevskyPosnerAsianDensity         Reciprocal-Gamma Approximation
#  .PosnerMilevskyAsianDensity         Johnson Type I Approximation
# GRAM CHARLIER SERIES EXPANSION:    DESCRIPTION:
#  GramCharlierAsianOption            Calculate Gram-Charlier option prices
#  .GramCharlierAsianDensity          NA
# STATE SPACE MOMENTS:               DESCRIPTION:
#  AsianOptionMoments                 Methods to calculate Asian Moments
#  .DufresneAsianOptionMoments         Moments from Dufresne's Formula
#  .AbrahamsonAsianOptionMoments       Moments from Abrahamson's Formula
#  .TurnbullWakemanAsianOptionMoments  First 2 Moments from Turnbull-Wakeman
#  .TolmatzAsianOptionMoments          Asymptotic Behavior after Tolmatz
# STATE SPACE DENSITIES:              DESCRIPTION:
#  StateSpaceAsianDensity              NA
#  .Schroeder1AsianDensity             NA
#  .Schroeder2AsianDensity             NA
#  .Yor1AsianDensity                   NA
#  .Yor2AsianDensity                   NA
#  .TolmatzAsianDensity                NA
#  .TolmatzAsianProbability            NA
# PARTIAL DIFFERENTIAL EQUATIONS:     DESCRIPTION:
#  PDEAsianOption                      PDE Asian Option Pricing
#   .ZhangAsianOption                   Asian option price by Zhang's 1D PDE
#    ZhangApproximateAsianOption
#   .VecerAsianOption                   Asian option price by Vecer's 1D PDE
# LAPLACE INVERSION:                  DESCRIPTION:
#   GemanYorAsianOption                Asian option price by Laplace Inversion
#   gGemanYor                          Function to be Laplace inverted
# SPECTRAL EXPANSION:                 DESCRIPTION:
#   LinetzkyAsianOption                Asian option price by Spectral Expansion
#   gLinetzky                          Function to be integrated
# BOUNDS ON OPTION PRICES:            DESCRIPTION:
#   BoundsOnAsianOption                 Lower and upper bonds on Asian calls
#    CurranThompsonAsianOption          From Thompson's continuous limit
#    RogerShiThompsonAsianOption        From Thompson's single integral formula
#    ThompsonAsianOption                Thompson's upper bound
# SYMMETRY RELATIONS:                 DESCRIPTION:
#   CallPutParityAsianOption           Call-Put parity Relation
#   WithDividendsAsianOption           Adds dividends to Asian Option Formula
# TABULATED RESULTS:                  DESCRIPTION:
#   FuMadanWangTable                   Table from Fu, Madan and Wang's paper
#   FusaiTaglianiTable                 Table from Fusai und tagliani's paper
#   GemanTable                         Table from Geman's paper
#   LinetzkyTable                      Table from Linetzky's paper
#   ZhangTable                         Table from Zhang's paper
#   ZhangLongTable                     Long Table from Zhang's paper
#   ZhangShortTable                    Short Table from Zhang's paper
################################################################################


################################################################################
# MOMENT MATCHING:


MomentMatchedAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, table = NA, method = c("LN", "RG", "JI"))
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates price for Asian options based on moment matching
    #   LN:  Levy-Turnbull-Wakeman Log-Normal Approximation
    #   RG:  Milevsky-Posner Reciprocal-Gamma Approximation
    #   JI:  Posner-Milevski Johnson Type I Approximation

    # FUNCTION:

    # Set Default TypeFlag and Method, if no other is selected:
    TypeFlag = TypeFlag[1]
    method = method[1]

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[,1]
        X = table[,2]
        Time = table[,3]
        r = table[,4]
        sigma = table[,5]
    }
    call = rep(0, length=length(S))

    # Log-Normal Approximation:
    if (method == "LN") {
        for ( i in 1:length(S) ) {
            moments = masian(Time = Time[i], r = r[i],
                sigma = sigma[i])$rawMoments
            moments = moments / Time[i]^(1:4)
            meanlog = ( 2*log(moments[1]) - log(moments[2])/2 )
            sdlog = ( sqrt ( log(moments[2]) - 2*log(moments[1]) ) )
            d2 = ( -log(X[i]/S[i]) + meanlog*Time[i] ) / ( sdlog*sqrt(Time[i]) )
            d1 = d2 + sdlog*sqrt(Time[i])
            call[i] = moments[1]*pnorm(d1)-(X[i]/S[i])*pnorm(d2)
        }
     }

     # Reciprocal-Gamma Approximation:
     if (method == "RG") {
        for ( i in 1:length(S) ) {
            moments = masian(Time = Time[i], r = r[i],
                sigma = sigma[i])$rawMoments
            moments = moments / Time[i]^(1:4)
            alpha = (2*moments[2] - moments[1]^2) / (moments[2] - moments[1]^2)
            beta = (moments[2] - moments[1]^2) / (moments[1]*moments[2])
            call[i] = moments[1]*pgamma(S[i]/X[i], shape=alpha-1, scale=beta) -
                (X[i]/S[i])*pgamma(S[i]/X[i], shape=alpha, scale=beta)
        }
     }

     # Johnson-Type-I Approximation:
     if (method == "JI") {
        for ( i in 1:length(S) ) {
            cmoments = masian(Time = Time[i], r = r[i],
                sigma = sigma[i])$centralMoments
            cmoments = cmoments / Time[i]^(1:4)
            mu = cmoments[1]
            varsigma = sqrt(cmoments[2])
            eta = cmoments[3] / varsigma^3
            omega = 0.5 * ( 8 + 4*eta^2 + 4*sqrt(4*eta^2+eta^4) )^(1/3)
            omega = omega + 1/omega - 1
            b = 1 / sqrt(log(omega))
            a = 0.5 * b * log(omega*(omega-1)/varsigma^2)
            d = sign(eta)
            c = d*mu - exp(  (0.5/b-a)/b  )
            Q = a + b*log((X[i]/S[i]-c)/d)
            call[i] = mu - X[i]/S[i] + (X[i]/S[i] - c) * pnorm( Q ) -
                d * exp ( (1-2*a*b)/(2*b^2) ) * pnorm ( Q - 1/b )
        }
    }

    # Call Price:
    Price = S* exp(-r*Time) * call

    # Put Price:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        Price = Price - Parity
    }

    # Return Value:
    option = list(
        price = Price,
        call = match.call() )
    class(option) = "option"
    option
}


# ------------------------------------------------------------------------------


.LevyTurnbullWakemanAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Return Value:
    MomentMatchedAsianOption(TypeFlag[1], S, X, Time, r, sigma, method = "LN")
}


# ------------------------------------------------------------------------------


.MilevskyPosnerAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Return Value:
    MomentMatchedAsianOption(TypeFlag[1], S, X, Time, r, sigma, method = "RG")
}


# ------------------------------------------------------------------------------


.PosnerMilevskyAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Return Value:
    MomentMatchedAsianOption(TypeFlag[1], S, X, Time, r, sigma, method = "JI")
}


# ------------------------------------------------------------------------------


MomentMatchedAsianDensity =
function(x, Time = 1, r = 0.09, sigma = 0.30, method = c("LN", "RG", "JI"))
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates price for Asian options based on moment matching
    #   LN:  Levy-Turnbull-Wakeman Log-Normal Approximation
    #   RG:  Milevsky-Posner Reciprocal-Gamma Approximation
    #   JI:  Posner-Milevski Johnson Type I Approximation

    # FUNCTION:

    # Set Default Method, if no other is selected:
    method = method[1]

    # Log-Normal Approximation:
    if (method == "LN") {
        moments = masian(Time = Time, r = r, sigma = sigma)$rawMoments
        moments = moments / Time^(1:4)
        meanlog = 2*log(moments[1]) - log(moments[2])/2
        sdlog = sqrt ( log(moments[2]) - 2*log(moments[1]) )
        density = dlnorm(x = x, meanlog = meanlog, sdlog = sdlog)
     }

     # Reciprocal-Gamma Approximation:
     if (method == "RG") {
        moments = masian(Time = Time, r = r, sigma = sigma)$rawMoments
        moments = moments / Time^(1:4)
        alpha = (2*moments[2] - moments[1]^2) / (moments[2] - moments[1]^2)
        beta = (moments[2] - moments[1]^2) / (moments[1]*moments[2])
        density = drgam(x = x, alpha = alpha, beta = beta)
     }

     # Johnson Type I Approximation:
     if (method == "JI") {
        cmoments = masian(Time = Time, r = r, sigma = sigma)$centralMoments
        cmoments = cmoments / Time^(1:4)
        mu = cmoments[1]
        varsigma = sqrt(cmoments[2])
        eta = cmoments[3] / varsigma^3
        omega = 0.5 * ( 8 + 4*eta^2 + 4*sqrt(4*eta^2+eta^4) )^(1/3)
        omega = omega + 1/omega - 1
        b = 1 / sqrt(log(omega))
        a = 0.5 * b * log(omega*(omega-1)/varsigma^2)
        d = sign(eta)
        c = d*mu - exp(  (0.5/b-a)/b  )
        density = djohnson(x = x, a = a, b = b, c = c, d = d)
    }

    # Return Value:
    density
}


# ------------------------------------------------------------------------------


.LevyTurnbullWakemanAsianDensity =
function(x, Time = 1, r = 0.09, sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Return Value:
    MomentMatchedAsianDensity(x, Time, r, sigma, method = "LN")
}


# ------------------------------------------------------------------------------


.MilevskyPosnerAsianDensity =
function(x, Time = 1, r = 0.09, sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Return Value:
    MomentMatchedAsianDensity(x, Time, r, sigma, method = "RG")
}


# ------------------------------------------------------------------------------


.PosnerMilevskyAsianDensity =
function(x, Time = 1, r = 0.09, sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Return Value:
    MomentMatchedAsianDensity(x, Time, r, sigma, method = "JI")
}


################################################################################
# GRAM CHARLIER:


GramCharlierAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, table = NA, method = c("LN", "RG", "JI"))
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculate arithmetic Asian options price using
    #   Gram Charlier Statistical Series Expansion around
    #   LN:  Log-Normal Distribution
    #   RG:  Reciprocal-Gamma Distribution
    #   JI:  Johnson-Type-I Distribution

    # FUNCTION:

    # Select Method:
    TypeFlag = TypeFlag[1]
    method = method[1]

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[,1]
        X = table[,2]
        Time = table[,3]
        r = table[,4]
        sigma = table[,5]
    }

    # Calculate Price:
    Price = MomentMatchedAsianOption("c", S = S, X = X, Time = Time, r = r,
        sigma = sigma, method=method)$price
    gc3 = gc4 = rep(0, length(Price))

    # Log-Normal Approximation:
    if (method == "LN") {
        for ( i in 1:length(S) ) {
            moments = masian(Time[i], r[i], sigma[i])$rawMoments/Time[i]^(1:4)
            meanlog = 2*log(moments[1]) - log(moments[2])/2
            sdlog = sqrt ( log(moments[2]) - 2*log(moments[1]) )
            asian.cm = masian(Time[i], r[i],
                sigma[i])$centralMoments/Time[i]^(1:4)
            lnorm.cm = mlognorm(meanlog, sdlog)$centralMoments/Time[i]^(1:4)
            kappa = (asian.cm-lnorm.cm)
            gc3[i] = kappa[3]*dlognorm(X[i]/S[i], meanlog, sdlog, deriv=1)/6
            gc4[i] = kappa[4]*dlognorm(X[i]/S[i], meanlog, sdlog, deriv=2)/24
        }
    }

    # Reciprocal-Gamma Approximation:
    if (method == "RG" ) {
        for ( i in 1:length(S) ) {
            moments = masian(Time[i], r[i], sigma[i])$rawMoments/Time[i]^(1:4)
            alpha = (2*moments[2] - moments[1]^2) / (moments[2] - moments[1]^2)
            beta = (moments[2] - moments[1]^2) / (moments[1]*moments[2])
            asian.cm = masian(Time[i], r[i],
                sigma[i])$centralMoments/Time[i]^(1:4)
            rgam.cm = mrgam(alpha, beta)$centralMoments/Time[i]^(1:4)
            kappa = (asian.cm-rgam.cm)
            gc3[i] = kappa[3]*drgam(X[i]/S[i], alpha, beta, deriv = 1)/6
            gc4[i] = kappa[4]*drgam(X[i]/S[i], alpha, beta, deriv = 2)/24
        }
    }

    # Johnson-Type-I Approximation:
    if (method == "JI" ) {
        for ( i in 1:length(S) ) {
            cmoments = masian(Time = Time[i], r = r[i],
                sigma = sigma[i])$centralMoments
            cmoments = cmoments / Time[i]^(1:4)
            mu = cmoments[1]
            varsigma = sqrt(cmoments[2])
            eta = cmoments[3] / varsigma^3
            omega = 0.5 * ( 8 + 4*eta^2 + 4*sqrt(4*eta^2+eta^4) )^(1/3)
            omega = omega + 1/omega - 1
            b = 1 / sqrt(log(omega))
            a = 0.5 * b * log(omega*(omega-1)/varsigma^2)
            d = sign(eta)
            c = d*mu - exp(  (0.5/b-a)/b  )
            asian.cm = cmoments
            johnson.cm = cmoments
            skewness = sqrt((omega-1)*(omega+2)^2)
            kurtosis = omega^4 + 2*omega^3 + 3* omega^2 - 3
            johnson.cm[3] = skewness * varsigma^3
            johnson.cm[4] = kurtosis * varsigma^4
            kappa = (asian.cm-johnson.cm)
            gc3[i] = kappa[3]*djohnson(X[i]/S[i], a, b, c, d, deriv = 1)/6
            gc4[i] = kappa[4]*djohnson(X[i]/S[i], a, b, c, d, deriv = 2)/24
        }
    }

    # Gram-Charlier Approximated Call Price:
    Price = Price - S * exp(-r*Time) * (gc3-gc4)

    # Put Price:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        Price = Price - Parity
    }

    # Return Value:
    option = list(
        price = Price,
        call = match.call() )
    class(option) = "option"
    option
}


.GramCharlierAsianDensity =
function(Time = 1, r = 0.09, sigma = 0.30, method = c("LN", "RG", "JI"))
{   # A function ported by Diethelm Wuertz

    # Return Value:
    NA
}


################################################################################
# STATE SPACE MOMENTS:


AsianOptionMoments =
function(M = 4, Time = 1, r = 0.045, sigma = 0.30, log = FALSE,
method = c("A", "D", "TW", "T"))
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates Asian Moments using several approaches:
    #    A -  Moments from Abrahamson's Formula
    #    D -  Moments from Dufresne's Formula
    #    TW - First 2 Moments from Turnbull-Wakeman
    #    T - Asymptotic Behavior after Tolmatz

    # FUNCTION:

    # Settings:
    method = method[1]
    result = NA

    # Abrahamson Formula:
    if (method == "A") result =
        .AbrahamsonAsianOptionMoments(M = M, Time = Time, r = r,
            sigma = sigma)

    # Dufresne Formula:
    if (method == "D") result =
        .DufresneAsianOptionMoments(M = M, Time = Time, r = r,
            sigma = sigma)

    # Tolmatz Formula - Asymptotic Behavior:
    if (method == "T") result =
        .TolmatzAsianOptionMoments(M = M, Time = Time, r = r,
            sigma = sigma, log = log)

    # Turnbull Wakeman - Explicit 1st and Second Moment:
    if (method == "TW") result =
        .TurnbullWakemanAsianOptionMoments(M = M, Time = Time, r = r,
            sigma = sigma)

    # Return Value:
    result
}


# ------------------------------------------------------------------------------


.DufresneAsianOptionMoments =
function(M = 4, Time = 1, r = 0.045, sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates Moments of Asian Options Density
    #   according to the formula of Dufresne-GemanYor

    # FUNCTION:

    # Calculates: E[(A_\tau^{(\nu)})^n]
    moments = function (M, tau, nu) {
        d = function(j, n, beta) {
            d = 2^n
            for (i in 0:n) if (i != j) d = d  / ( (beta+j)^2 - (beta+i)^2 )
            d
        }
        moments = rep(0, length=M)
        for (n in 1:M) {
            moments[n] = 0
            for (j in 0:n) moments[n] = moments[n] +
                d(j, n, nu/2)*exp(2*(j^2+j*nu)*tau)
            moments[n] = prod(1:n) * moments[n] / (2^(2*n)) }
        moments
    }

    # Calculate for:
    tau = sigma^2*Time/4
    nu = 2*r/sigma^2-1

    # Return Value:
    (4/sigma^2)^(1:M) * moments(M, tau, nu)
}


# ------------------------------------------------------------------------------


.AbrahamsonAsianOptionMoments =
function (M = 4, Time = 1, r = 0.045, sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates Moments of Asian Options Density
    #   according to the formula of Abrahamson

    # FUNCTION:

    # Calculates: E[(A_\tau^{(\nu)})^n]
    moments = function (M, tau, nu) {
        moments = rep(0, times = M)
        for (N in 1:M) {
            d = c =  2 * ( (1:N)^2 + (1:N)*nu )
            for (m in 1:N) {
                for (j in 1:N) {
                    if (j!= m) d[m] = d[m]*(c[m]-c[j])
                }
                d[m] = exp(c[m]*tau) / d[m]
            }
            moments[N] = prod(1:N) * ( sum(d) + (-1)^N/prod(c) )
        }
        moments
    }

    # Calculate for:
    tau = sigma^2*Time/4
    nu = 2*r/sigma^2-1

    # Return Value:
    (4/sigma^2)^(1:M) * moments(M, tau, nu)
}


# ------------------------------------------------------------------------------


.TurnbullWakemanAsianOptionMoments =
function (M = 2, Time = 1, r = 0.045, sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates the first two moments as derived explicitly
    #   by Turnbull and Wakeman. It can serve as a test for
    #   other implementations.

    # Note:
    #   Maximum M is 2!

    # FUNCTION:

    # Moments:
    moments = rep(NA, times = M)
    if (M == 1 || M == 2)
        moments[1] = (exp(r*Time)-1)/(r*Time)
    if (M == 2)
        moments[2] =
            2*exp((2*r+sigma^2)*Time)/ ((r+sigma^2)*(2*r+sigma^2)*Time^2) +
            (2/(r*Time^2)) * ( 1/(2*r+sigma^2) - exp(r*Time)/(r+sigma^2) )

    # Return Value:
    moments
}


# ------------------------------------------------------------------------------


.TolmatzAsianOptionMoments =
function (M = 100, Time = 1, r = 0.045, sigma = 0.30, log = FALSE)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates Asymptotic Moments of Asian Options Density
    #   according to the formula of Tolmatz for nu=0 and Wuertz
    #   for nu different from zero - Log returns can be selected

    # FUNCTION:

    # Calculates: log { E[(A_\tau^{(\nu)})^n] }
    moments = function (M, tau, nu=0) {
        moments = rep(0, times=M)
        M = 1:M
        log.moments = -M*log(2) + lgamma(nu+M) - lgamma(nu+2*M) +
            2*(M^2+M*nu)*tau
        log.moments
    }

    # Calculate for:
    tau = sigma^2*Time/4
    nu = 2*r/sigma^2-1

    # Return Value:
    moments = (1:M)*log(4/sigma^2) + moments(M, tau, nu)

    # Return value:
    if (!log) moments = exp(moments)
    moments
}


################################################################################
# ASIAN DENSITY:


# STATE SPACE DENSITIES:              DESCRIPTION:
#  StateSpaceAsianDensity
#  .Schroeder1AsianDensity             S1
#  .Schroeder2AsianDensity             S2
#  .Yor1AsianDensity                   Y1
#  .Yor2AsianDensity                   Y2
#  .TolmatzAsianDensity                T
#  .TolmatzAsianProbability




################################################################################
# PDE SOLVER:


ZhangAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, table = NA, correction = TRUE, nint = 800, eps = 1.0e-8,
dt = 1.0e-10)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Valuates Asian options by Solving Zhang's one
    #   dimensional Partial Differential Equations

    # Source:
    #   For the Fortran Routine:
    #   TOMS ...

    # FUNCTION:

    # Settings:
    TypeFlag = TypeFlag[1]

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[, 1]
        X = table[, 2]
        Time = table[, 3]
        r = table[, 4]
        sigma = table[, 5]
    }
    Price = rep(0, times = length(S))

    # Set Model Identifier:
    modsel = 2

    # Option Parameters:
    if (TypeFlag == "c") z = +1
    if (TypeFlag == "p") z = -1

    # PDE Parameters - Do not change:
    T0 = 0; Tout = 1
    np = 0; Price.by.S = 0
    mf = 12
    npde = 1; kord = 4; ncc = 2; maxder = 5

    # Fill Working Arrays:
    xbkpt = rep(0, times = nint+1)
    length.work = kord+npde*(4+9*npde)+(kord+(nint-1)*(kord-ncc)) *
      (3*kord+2+npde*(3*(kord-1)*npde+maxder+4))
    work = rep(0, times = length.work)
    length.iwork = (npde+1)*(kord+(nint-1)*(kord-ncc))
    iwork = rep(0, times = length.iwork)

    # Compute Prices:
    for ( i in 1:length(S) ) {
        result = .Fortran("asianval",
            as.double(z),
            as.double(S[i]),
            as.double(X[i]),
            as.double(X[i]),
            as.double(X[i]),
            as.double(Time[i]),
            as.double(r[i]),
            as.double(sigma[i]),
            as.double(T0),
            as.double(Tout),
            as.double(eps),
            as.double(dt),
            as.double(Price.by.S),
            as.integer(np),
            as.integer(modsel),
            as.integer(mf),
            as.integer(npde),
            as.integer(kord),
            as.integer(nint),
            as.integer(ncc),
            as.integer(maxder),
            as.double(X[i]/S[i]),
            as.double(xbkpt),
            as.double(work),
            as.integer(iwork),
            PACKAGE = "fAsianOptions"
            )
        Price[i] = result[[13]]*S[i]
    }

    # ?
    Price = Price +
        ZhangApproximateAsianOption(TypeFlag, S, X, Time, r, sigma, table)

    # Return Value:
    Price
}


ZhangApproximateAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, table = NA)
{
    # Settings:
    TypeFlag = TypeFlag[1]

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[, 1]
        X = table[, 2]
        Time = table[, 3]
        r = table[, 4]
        sigma = table[, 5]
    }

    # Compute:
    I = 0
    xi = (Time*X-I)*exp(-r*Time)/S - (1-exp(-r*Time))/r
    eta = (sigma^2/(4*r^3)) * (-3 + 2*r*Time + 4*exp(-r*Time) - exp(-2*r*Time))

    # Call:
    price = (S/Time) *
        ( -xi * pnorm(-xi/sqrt(2*eta)) + sqrt(eta/pi)*exp(-xi^2/(4*eta)) )
    if (TypeFlag == "c") {
        ans = price
    } else {
        ans = CallPutParityAsianOption(TypeFlag = "c", price,
            S, X, Time, sigma, r, table = table)
    }

    # Return Value:
    ans
}


# ------------------------------------------------------------------------------


VecerAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, table = NA, nint = 800, eps = 1.0e-8, dt = 1.0e-10)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Valuates Asian options by Solving Vecer's one
    #   dimensional Partial Differential Equations

    # FUNCTION:

    # Vecer's PDE modeled by: modsel == 1
    # SUBROUTINE ASIANVAL(
    #   ZZ, SS, XS, XSMIN, XSMAX, TIME, RR, SIGMA,
    #   T0, TOUT, EPS, DT, PRICEBYS, NP, MODSEL,
    #   MF1, NPDE1, KORD1, MX1, NCC1, MAXDER1,
    #   XBYS, XBKPT, WORK, IWORK)

    # Settings:
    TypeFlag = TypeFlag[1]

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[, 1]
        X = table[, 2]
        Time = table[, 3]
        r = table[, 4]
        sigma = table[, 5]
    }
    Price = rep(0, times = length(S))

    # Set Model Identifier:
    modsel = 1

    # Option Parameters:
    if (TypeFlag == "c") z = +1
    if (TypeFlag == "p") z = -1

    # PDE Parameters - Do not change:
    T0 = 0
    Tout = 1
    np = 0
    Price.by.S = 0
    mf = 12
    npde = 1
    kord = 4
    ncc = 2
    maxder = 5

    # Fill Working Arrays:
    xbkpt = rep(0, times = nint+1)
    length.work = kord+npde*(4+9*npde)+(kord+(nint-1)*(kord-ncc)) *
      (3*kord+2+npde*(3*(kord-1)*npde+maxder+4))
    work = rep(0, times = length.work)
    length.iwork = (npde+1)*(kord+(nint-1)*(kord-ncc))
    iwork = rep(0, times = length.iwork)

    # Compute Prices:
    for ( i in 1:length(S) ) {
        result = .Fortran("asianval",
            as.double(z),
            as.double(S[i]),
            as.double(X[i]),
            as.double(X[i]),
            as.double(X[i]),
            as.double(Time[i]),
            as.double(r[i]),
            as.double(sigma[i]),
            as.double(T0),
            as.double(Tout),
            as.double(eps),
            as.double(dt),
            as.double(Price.by.S),
            as.integer(np),
            as.integer(modsel),
            as.integer(mf),
            as.integer(npde),
            as.integer(kord),
            as.integer(nint),
            as.integer(ncc),
            as.integer(maxder),
            as.double(X[i]/S[i]),
            as.double(xbkpt),
            as.double(work),
            as.integer(iwork),
            PACKAGE = "fAsianOptions"
            )
        Price[i] = result[[13]]*S[i]
    }

    # Return Value:
    Price
}


################################################################################
# LAPLACE INVERSION:


gGemanYor =
function(lambda, S = 100, X = 100, Time = 1, r = 0.05, sigma = 0.30,
log = FALSE, doplot = FALSE)
{   # A function written by Diethelm Wuertz

    # Description:
    #   Calculates function to be Laplace inverted

    # Arguments:
    #   lambda - complex vector

    # Notes:
    #   Equation 4.9 with notation as in
    #       Sudler G.F. [1999], "Asian Options: Inverse Laplace
    #       Transform and Martingale Methods Revisited".

    # FUNCTION:

    # Settings:
    x = lambda
    g = rep(complex(real = 0, imaginary = 0), length = length(x))

    # Calculate for each lambda value from Kummer Function:
    # Note Kummer function is not vectorized in Indexes !
    for ( i in 1:length(x) ) {
        # Settings:
        nu = 2*r/(sigma^2) - 1
        mu = sqrt(2*lambda[i] + nu^2)
        q = (sigma^2)*X*Time/(4*S)
        gamma1 = (mu-nu)/2
        gamma2 = (mu+nu)/2
        # Convergence Parameters:
        a = gamma1-2 + 1
        b = gamma2+1 + a + 1
        z = -1/(2*q)
        # From Kummer Function [one of ...]:
        # Use logarithmic Kummer and Gamma Functions to prevent
        # from numerical overflow!
        g[i] = kummerM(-z, b-a, b, lnchf = 1) +
            a*log(-z) + z + cgamma(b-a, log = TRUE) - cgamma(b, log = TRUE) -
            log (lambda[i]*(lambda[i] - 2 - 2 * nu))
        if (!log) g[i] = exp(g[i])
    }

    # Plot function if desired:
    if (doplot) {
        if (!is.complex(lambda)) {
            lam = lambda
            xlab = "lambda"
            ylab = "g"
        } else {
            lam = Im(lambda)
            xlab = "Im(lambda)"
            ylab = "Re(g)"
        }
        lambda.min = 4*r/sigma^2
        cat("\nmin lambda:", lambda.min, "\n")
        # Function to be Laplace inverted:
        print(cbind(lambda, g))
        plot(lam, Re(g), type = "l", main = "Laplace Inverse",
            xlab = xlab, ylab = ylab)
        lines(lam, 0*Re(g))
        # Convergence Indexes:
        mu = sqrt(2*lambda + nu^2)
        gamma1 = (mu-nu)/2; a = Re(gamma1-2 + 1)
        gamma2 = (mu+nu)/2; b = Re(gamma2+1 + a + 1)
        plot(lam, b, ylim = c(min(c(a,b)), max(c(a,b))), type = "n",
            xlab = xlab, ylab = "a  b", main = "Convergence Indexes")
        lines(lam, a, col = "red")
        lines(lam, b, col = "blue")
        lines(lam, 0*b, type = "l", col = "black")
        lines(x = lambda.min*c(1,1), y = c( min(c(a,b)), max(c(a,b)) ) )
    }

    # Return Value:
    g
}


# ------------------------------------------------------------------------------


GemanYorAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, doprint = FALSE)
{   # A function written by Diethelm Wuertz

    # Description:
    #   Valuate Asian options by Laplace inversion.

    # FUNCTION:

    # Parameters:
    TypeFlag = TypeFlag[1]
    nu = (2*r)/(sigma^2) - 1
    alpha = 1 / sigma^2 * (2*nu + 2)
    h = sigma^2*Time/4

    # Function to be inverted:
    fx = function(x, S, X, Time, roh, sigma) {
        nu = (2*roh)/(sigma^2) - 1
        alpha = 1 / sigma^2 * (2*nu + 2)
        h = sigma^2*Time/4
        zi = complex(real = 0, imaginary = x)
        zc = complex(real = alpha, imaginary = x)
        g2 = gGemanYor(lambda = zc, S = S, X = X,
            Time = Time, r = roh, sigma = sigma, log = TRUE)
        # Return Value:
        Re ( exp(zi*h + g2) / (2*pi) )
    }

    # Call:
    # Integrate stepwise until (hopefully) convergence is reached:
    q = (sigma^2)*X*Time/(4*S)
    delta = 10/(2*q)
    eps = 1.0e-20
    if (doprint) {
        cat("\nDelta:", delta)
        cat("\nS:", S, "X:", X)
        cat("\nTime:", Time, "r:", r, "sigma:", sigma, "\n")
    }
    i = 1
    I = integrate(fx, lower = 0, upper = delta,
        S = S, X = X, Time=Time, roh = r, sigma = sigma)
    Price = Increment = exp(-r*Time)*(S/h)*exp(alpha*h)*2*I$value
    while (abs(Increment)/abs(Price) > eps) {
        i = i+1
        I = integrate(fx, lower = (i-1)*delta, upper = i*delta,
            S = S, X = X, Time = Time, roh = r, sigma = sigma)
        Increment = exp(-r*Time)*(S/h)*exp(alpha*h)*2*I$value
        Price = Price + Increment
        if (doprint) print(c(i*delta, Price, Increment))
    }

    # Put:
    # Use Call-Put Parity:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        Price = Price - Parity
    }

    # Return Value:
    option = list(
        price = Price,
        call = match.call() )
    class(option) = "option"
    option
}


################################################################################
# SPECTRAL EXPANSION:


gLinetzky =
function(x, y, tau, nu, ip = 0)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates the function to be integrated for the Put Price
    #   in the expression for the spectral representation of
    #   $ P^{(\nu)} (k, \tau) $. Proposition 2 described bu eq. (16)

    # Note:
    #   Requires Confluent Hypergeometric Functions

    # Reference:
    #   [L] V. Linetzky, Spectral Expansions for Asian (Average Price)
    #   Options, Preprint, revised Version from October 2002

    # Function:
    result = V = rep(0, length = length(x))
    for (i in 1:length(x)) {
        p = x[i]
        if (p == 0) {
            result[i] = 0
        } else {
            z = 1/(2*y)
            kappa = -(nu+3)/2
            mu = complex(real = 0, imaginary = p/2)
            # V1 = exp( -(nu^2+p^2)*tau/2 ) * z^kappa * exp(-z/2)
              logV1 = -(nu^2+p^2)*tau/2 + kappa*log(z) - z/2
            # V2 = (abs(cgamma(nu/2+mu)))^2
            if (p < 100) {
                logV2 = log ( (abs(cgamma(nu/2+mu)))^2 )
            } else {
                # Shift by Pi - Take of the proper Phi
                g = cgamma(nu/2+mu, log = TRUE)
                r = abs(g)
                phi = atan(Im(g)/Re(g)) + pi
                logV2 = 2*r*cos(phi) }
            # V3 = sinh(pi*p) * p
            logV3 = pi*p + log(1/2 - exp(-2*pi*p)/2) + log(p)
            # Whittaker:
            if (p < 100) {
                V4 = Re ( whittakerW(z, kappa, mu, ip ) )
            } else {
                # Use: 2 * Re ( (cgamma(-2*mu)/cgamma(1/2-mu-kappa)) *
                # exp(-z/2) * z^(1/2+mu) * kummerM(z, 1/2+mu-kappa, 1+2*mu)
                g = log(2) + cgamma(-2*mu, log=TRUE) -
                    cgamma(1/2-mu-kappa, log=TRUE) - z/2 +(1/2+mu)*log(z) +
                    kummerM(z, 1/2+mu-kappa, 1+2*mu, lnchf=1, ip=ip)
                r = abs(g)
                # Shift by Pi - Take of the proper Phi
                phi = atan(Im(g)/Re(g)) + pi
                logV4 = r*cos(phi)
                argV4 = cos(r*sin(phi))
            }
            # Collect all terms:
            if ( p < 100) {
                result[i] = exp(logV1+logV2+logV3)*V4/(8*pi^2)
            } else {
                result[i] = exp(logV1+logV2+logV3+logV4)*argV4/(8*pi^2)
            }
            # print(c(p, logV5, logV5a, argV5, argV5a))
        }
    }

    # Return Value:
    result
}


# ------------------------------------------------------------------------------


LinetzkyAsianOption =
function(TypeFlag = c("c", "p"), S = 2, X = 2, Time = 1, r = 0.02,
sigma = 0.1, table = NA, lower = 0, upper = 100, method = "adaptive",
subdivisions = 100, ip = 0, doprint = TRUE, doplot = TRUE,...)
{   # A function implemented by Diethelm Wuertz

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[,1]
        X = table[,2]
        Time = table[,3]
        r = table[,4]
        sigma = table[,5] }
    if (doprint) print(cbind(S, X, Time, r, sigma))
    Price = rep(0, length=length(S))

    # Settings:
    tau = sigma^2*Time/4
    k = tau*X/S
    nu = 2*r/sigma^2 - 1
    if (doprint) print(cbind(k, tau, nu))

    # Parameters:
    z = 1 / (2*k)
    kappa = -(nu+3)/2
    mu.max = upper/2
    if (doprint) print(cbind(z, kappa, mu.max))

    # Calculate Spectral Measure:
    P = P1 = rep(0, times=length(S))
    for (i in 1:length(S)) {
        if (method == "adaptive") {
            P[i] = integrate(gLinetzky, lower = lower, upper = upper,
                y = k[i], tau = tau[i], nu = nu[i], ip = ip,
                subdivisions = subdivisions,
                rel.tol = .Machine$double.eps^0.25,
                abs.tol = .Machine$double.eps^0.25,
                stop.on.error = FALSE)$value
        }
        if (method == "trapez") {
            x = seq(lower, upper, length = subdivisions+1)
            delta = (upper-lower)/subdivisions
            F = gLinetzky(x = x, y = k[i], tau[i], nu[i])
            # print(c(F[1], F[length(F)], min(F), max(F)))
            P[i] = ( sum(F)-(F[1]+F[length(F)])/2 ) * delta
        }
        if (method == "simpson") {
            x = seq(lower, upper, length = subdivisions+1)
            delta = (upper-lower)/subdivisions
            F = gLinetzky(x = x, y = k[i], tau = tau[i], nu = nu[i], ip = ip)
            # print(c(F[1], F[length(F)], min(F), max(F)))
            FF = matrix(F[2:length(F)], byrow = TRUE, ncol = 2)
            P[i] = (F[1]+4*sum(FF[,1])+2*sum(FF[,2])-F[length(F)]) *
                delta[i]/3 }
        # For nu < 0 add:
        P1[i] = 0
        if (nu[i] < 0) {
            z = 1/(2*k[i])
            P1[i] = (2*k[i]*pgamma(abs(nu[i]), z) - pgamma(abs(nu[i])-1, z)) /
                ( 2 * gamma(abs(nu[i])) )
            P[i] = P[i] + P1[i]
        }
        # Plot:
        if (doplot) {
            x = seq(lower, upper, length = subdivisions)
            F = gLinetzky(x = x, y = k[i], tau = tau[i], nu = nu[i])
            plot(x, F, type = "l")
            lines(x, 0*x, col = "red")
            lines(x, F)
        }
    }
    if (doprint) print(cbind(P, P1))

    # Derive Call/Put Price:

    # Put Price:
    Linetzky = exp(-r*Time) * (S/tau) * P

    # Call Price:
    if (TypeFlag == "c") {
        Linetzky = Linetzky + S*(1-exp(-r*Time))/(r*Time) - X*exp(-r*Time) }

    # Return Value:
    option = list(
        price = Linetzky,
        call = match.call() )
    class(option) = "option"
    option
}



################################################################################
# ASIAN BOUNDS:

# Note:
#   We have not implemented the formula for the upper bound derived
#   by Rogers and Shi. Thompson's upper bound formula is much more
#   precise and therefore we have concentrated ourself on their
#   approach.


BoundsOnAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30, table = NA, method = c("CT", "RST", "T"))
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates Bounds on Asian Option Prices
    #   CT  - Curran-Thompson Lower Bound
    #   RST - Roger-Shi-Thompson Lower Bound
    #   T   - Thompson Upper Bound

    # FUNCTION:

    # Set Default Method, if no other is selected:
    TypeFlag = TypeFlag[1]
    if (length(method) == 3) method = "T"

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[,1]
        X = table[,2]
        Time = table[,3]
        r = table[,4]
        sigma = table[,5]
    }
    Price = rep(NA, length = length(S))

    # Curran-Thompson Lower Bound:
    if (method == "CT") {
        for ( i in 1:length(S) ) {
        Price[i] = CurranThompsonAsianOption(TypeFlag=TypeFlag,
            S=S[i], X=X[i], Time[i], r=r[i], sigma[i])$price
        }
    }

    # Roger-Shi-Thompson Lower Bound:
    if (method == "RST") {
        for ( i in 1:length(S) ) {
        Price[i] = RogerShiThompsonAsianOption(TypeFlag=TypeFlag,
            S = S[i], X = X[i], Time[i], r = r[i], sigma[i])$price
        }
    }

    # Thompson Upper Bound:
    if (method == "T") {
        for ( i in 1:length(S) ) {
        Price[i] = ThompsonAsianOption(TypeFlag = TypeFlag,
            S = S[i], X = X[i], Time[i], r = r[i], sigma[i])$price
        }
    }

    # Return Value:
    option = list(
        price = Price,
        call = match.call() )
    class(option) = "option"
    option
}


# ------------------------------------------------------------------------------


CurranThompsonAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates "lower bound" for Asian Call Option from
    #   Thompson's formula describing the continuous limit
    #   of Curran's approximation.

    # Note:
    #   Rescale sigma:
    #   Note the formula of Thompson work for Time=1 only!
    #   Thus the easiest way to cover times to maturity different
    #     from unity can be achieved by scale the volatility and
    #     interest rate! - Just do it

    # FUNCTION:

    # Settings:
    TypeFlag = TypeFlag[1]
    sigma = sigma*sqrt(Time)
    r = r*Time
    Time = 1

    # Settings:
    alpha = r - 0.5*sigma^2

    # Solve for gamma star:
    f1 =  function(x, S, X, alpha, sigma) {
        exp( 3 * ( log(X/S)-alpha/2 ) * x *(1-x/2) +
            alpha * x + 0.5 * sigma^2 * (x-3*x^2*(1-x/2)^2) )
    }

    # Integrate:
    gs = integrate(f1, lower = 0, upper = 1, S = S, X = X, alpha = alpha,
        sigma = sigma, subdivisions = 1000, rel.tol = .Machine$double.eps^0.5,
        abs.tol = .Machine$double.eps^0.5)$value

    # Final Calculate:
    g.star = ( log(2*X/S-gs) - alpha/2 ) / sigma

    # Solve for lower bound:
    f =  function(x, g.star, alpha, sigma) {
        time = x
        arg = (-g.star + sigma*time*(1-time/2))*sqrt(3)
        f = exp( (alpha+sigma^2/2)*time ) * pnorm(arg)
        f
    }

    # Integrate:
    value = integrate(f, lower=0, upper=1, g.star=g.star,
        alpha=alpha, sigma=sigma, subdivisions=1000,
        rel.tol=.Machine$double.eps^0.5,
        abs.tol=.Machine$double.eps^0.5)$value

    # Call Price:
    CurranThompson = exp(-r*Time) * ( S*value - X*pnorm(-g.star*sqrt(3)) )

    # Put Price:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        CurranThompson = CurranThompson - Parity
    }

    # Return Value:
    option = list(
        price = CurranThompson,
        call = match.call() )
    class(option) = "option"
    option
}


# ------------------------------------------------------------------------------


RogerShiThompsonAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates "lower bound" for Asian Call Option from
    #   Thompson's formula. Thompson's result is the same
    #   as can be obtained from Roger and Shi's formula.
    #   However, Thompson's formula is numerically more
    #   efficient since it requires a single integration
    #   only, whereas Roger and Shi's formula requires
    #   double integration.

    # Note:
    #   Rescale sigma:
    #   Note the formula of Thompson work for Time=1 only!
    #   Thus the easiest way to cover times to maturity different
    #     from unity can be achieved by scale the volatility and
    #     interest rate! - Just do it

    # FUNCTION:

    # Settings:
    TypeFlag = TypeFlag[1]
    sigma = sigma*sqrt(Time)
    r = r*Time
    Time = 1

    # Function from which to calculate gamma star:
    gamma.star =  function(S, X, r, sigma, lower = -99, upper = 99) {
        func =  function(gamma, S, X, r, sigma) {
            f =  function(x, gamma, roh, sigma) {
                time = x
                alpha = roh - sigma*sigma/2
                f = exp( 3 * gamma * sigma * time * (1-time/2) +
                    alpha * time +
                    sigma * sigma * (time-3*time^2*(1-time/2)^2)/2 )
                f
            }
            # Integrate:
            integrate(f, lower = 0, upper = 1, gamma = gamma, roh = r,
                sigma = sigma, subdivisions = 1000,
                rel.tol = .Machine$double.eps^0.5,
                abs.tol = .Machine$double.eps^0.5)$value - X/S
        }
        # Find Root Value:
        uniroot(func, lower = lower, upper = upper, S = S, X = X, r = r,
            sigma = sigma)$root
    }
    g.star = gamma.star(S, X, r, sigma)

    # Function to be integrated:
    f =  function(x, g.star, roh, sigma) {
        time = x
        alpha = roh - sigma*sigma/2
        arg = (-g.star + sigma*time*(1-time/2))*sqrt(3)
        f = exp( (alpha+sigma^2/2)*time ) * pnorm(arg)
        f
    }

    # Integrate:
    value = integrate(f, lower=0, upper=1, g.star=g.star, roh=r,
        sigma=sigma, subdivisions=1000, rel.tol=.Machine$double.eps^0.5,
        abs.tol=.Machine$double.eps^0.5)$value

    # Call Price:
    RogerShiThompson = exp(-r*Time) * ( S*value - X*pnorm(-g.star*sqrt(3)) )

    # Put Price:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        RogerShiThompson = RogerShiThompson - Parity }

    # Return Value:
    option = list(
        price = RogerShiThompson,
        call = match.call() )
    class(option) = "option"
    option
}


# ------------------------------------------------------------------------------


ThompsonAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates "upper bound" for Asian Call Option from
    #   Thompson's formula.

    # Note:
    #   Rescale sigma:
    #   Note the formula of Thompson work for Time=1 only!
    #   Thus the easiest way to cover times to maturity different
    #     from unity can be achieved by scale the volatility and
    #     interest rate! - Just do it

    # FUNCTION:

    # Settings:
    TypeFlag = TypeFlag[1]
    sigma = sigma*sqrt(Time)
    r = r*Time
    Time = 1

    # Internal Functions:
    sqrtvt =  function(x, S, X, alpha, sigma) {
        t = x
        ct = S*exp(alpha*t)*sigma - X*sigma
        vt = ct^2*t + 2*(X*sigma)*ct*t*(1-t/2) + (X*sigma)^2/3
        sqrt(vt) }

    fmu =  function(t, S, X, r, sigma) {
        alpha = r -sigma^2/2
        gint = integrate(sqrtvt, lower=0, upper=1, S=S, X=X,
            alpha=alpha, sigma=sigma, subdivisions=1000,
            rel.tol=.Machine$double.eps^0.5,
            abs.tol=.Machine$double.eps^0.5)$value
        gamma = (X - S * (exp(alpha)-1) / alpha ) / gint
        mu = (S*exp(alpha*t) +
            gamma*sqrtvt(x=t, S=S, X=X, alpha=alpha, sigma=sigma) ) / X
        mu }

    fatx =  function(t, x, S, X, r, sigma) {
        alpha = r - sigma^2/2
        mu = fmu(t=t, S=S, X=X, r=r, sigma=sigma)
        atx = S*exp(sigma*x+alpha*t) - X*(mu + sigma*x) + X*sigma*(1-t/2)*x
        atx }

    fbtx =  function(t, x, S, X, r, sigma) {
        alpha = r - sigma^2/2
        btx = X*sigma*sqrt(1/3 -t*(1-t/2)^2)
        btx }

    fw =  function(x, v, S2, X2, r2, sigma2) {
        w = x
        atx = fatx(t=v^2, x=w*v, S=S2, X=X2, r=r2, sigma=sigma2)
        btx = fbtx(t=v^2, x=w*v, S=S2, X=X2, r=r2, sigma=sigma2)
        2 * v * dnorm(w) * (atx*pnorm(atx/btx) + btx*dnorm(atx/btx)) }

    fv =  function(x, S1, X1, r1, sigma1) {
        fv = rep(0, length=length(x))
        for (i in 1:length(x))
            fv[i] = integrate(fw, lower=-20, upper=20,
                v=x[i], S2=S1, X2=X1, r2=r1, sigma2=sigma1,
                subdivisions=1000, rel.tol=.Machine$double.eps^0.5,
                abs.tol=.Machine$double.eps^0.5)$value
        exp(-r)*fv }

    # Integrate - Call Price:
    Thompson = integrate(fv, lower=0, upper=1,  S1=S, X1=X, r1=r,
        sigma1=sigma, subdivisions=1000,
        rel.tol=.Machine$double.eps^0.5,
        abs.tol=.Machine$double.eps^0.5)$value

    # Put Price:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        Thompson = Thompson - Parity }

    # Return Value:
    option = list(
        price = Thompson,
        call = match.call() )
    class(option) = "option"
    option
}


# ------------------------------------------------------------------------------


TolmatzAsianOption =
function(TypeFlag = c("c", "p"), S = 100, X = 100, Time = 1, r = 0.09,
sigma = 0.30)
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Calculates "lower bound" for Asian Call Option from
    #   the asymptotic behavior derived by Tolmatz

    # FUNCTION:
    TypeFlag = TypeFlag[1]
    Tolmatz = NA

    # Put Price:
    if (TypeFlag == "p") {
        Parity = (1/(r*Time))*(1-exp(-r*Time))*S - exp(-r*Time)*X
        Tolmatz = Tolmatz - Parity }

    # Return Value:
    option = list(
        price = Tolmatz,
        call = match.call() )
    class(option) = "option"
    option
}


################################################################################
# SYMMETRY AND EQUIVALENCE RELATIONS:


CallPutParityAsianOption =
function(TypeFlag = "p", Price = 8.828759, S = 100, X = 100, Time = 1,
r = 0.09, sigma = 0.3, table = NA)
{   # A function implemented by Diethelm Wuertz

    # Test for Table:
    if (is.data.frame(table)) {
        S = table[,1]
        X = table[,2]
        Time = table[,3]
        r = table[,4]
        sigma = table[,5] }

    # Call from Put:
    if (TypeFlag == "c") {
        # print(Price)
        Parity = S*(1-exp(-r*Time))/(r*Time) - X*exp(-r*Time)
        # print(Parity)
        result = Price + Parity }

    # Put from Call:
    if (TypeFlag == "p") {
        Parity = S*(1-exp(-r*Time))/(r*Time) - X*exp(-r*Time)
        result = Price - Parity }

    # Return Value:
    result
}


# ------------------------------------------------------------------------------


WithDividendsAsianOption =
function(TypeFlag = "c", Dividends = 0.45, S = 100, X = 100, Time = 1,
r = 0.09, sigma = 0.3, calculator = MomentMatchedAsianOption, method = "LN")
{   # A function implemented by Diethelm Wuertz

    # Add Dividends:
    q = Dividends = 0.05
    r.q = r - q
    X.q = X * exp(-q*Time)
    S.q<- S * exp(-q*Time)

    # Call Price:
    if (TypeFlag == "c")
        Price = calculator(TypeFlag = TypeFlag, S = S.q, X = X.q,
            Time = Time, r = r.q, sigma = sigma, method = method)$price
    # Put Price:
    if (TypeFlag == "p" )
        Price = NA

    # Return Value:
    option = list(
        price = Price,
        call = match.call() )
    class(option) = "option"
    option
}


################################################################################
# TABULATED RESULTS:


FuMadanWangTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Fu-Madan-Wang's results from Table 1

    # Source:

    # Settings:
    X = rep(c(90,95,100,105,110), times = 6)
    S = 100*rep(1, times = length(X))
    sigma = rep(0.20, length = length(X))
    r = rep(0.09, length = length(X))

    # Call Prices:
    CallEulerFMW = c(
         11.5293,  7.2131,  3.8087,  1.6465,  0.5761,
         11.9247,  7.7249,  4.3696,  2.1175,  0.8734,
         13.8372,  9.9998,  6.7801,  4.2982,  2.5473,
         17.1212, 13.6763, 10.6319,  8.0436,  5.9267,
         19.8398, 16.6740, 13.7974, 11.2447,  9.0316,
         24.0861, 21.3774, 18.8399, 16.4917, 14.3442)
     CPUEulerFMW = c(
        53,44,42,45,38, 34,33,33,32,32, 26,26,26,25,25,
        24,23,23,23,22, 21,21,21,20,20, 19,22,19,21,21)
     CallPostWidderFMW = c(
         11.5176,  7.1981,  3.8196,  1.6623,  0.5728,
         11.9241,  7.7185,  4.3759,  2.1290,  0.8753,
         13.8439, 10.0029,  6.7823,  4.3010,  2.5450,
         17.1297, 13.6830, 10.6370,  8.0474,  5.9295,
         19.8495, 16.6822, 13.8042, 11.2502,  9.0360,
         24.0861, 21.3774, 18.8399, 16.4917, 14.3442)
     CPUPostWidderFMW = c(
        640,631,628,623,625, 613,591,601,585,595, 518,513,514,503,503,
        475,473,473,469,474, 468,467,467,462,465, 453,442,449,441,453)
     CallZhang = c(
        NA,         NA,         NA,        NA,        NA,
        NA,         NA,         NA,        NA,        NA,
        13.8314996, 9.99566567, 6.7773481, 4.2964626, 2.5462209,
        NA,         NA,         NA,        NA,        NA,
        NA,         NA,         NA,        NA,        NA,
        NA,         NA,         NA,        NA,        NA)

     # Return Value:
     data.frame(cbind(
        S, X, r, sigma,
        CallEulerFMW, CPUEulerFMW, CallPostWidderFMW, CPUPostWidderFMW,
        CallZhang))
}


# ------------------------------------------------------------------------------


FusaiTaglianiTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Fusai and Tagliani's results from Table 6a - 6c

    # Source:
    #   G. Fusai and A. Tagliani [2002]
    #   An Accurate Valuation of Asian Options Using Moments

    # Settings:
    S = 100*rep(1, times = 45)
    X = rep(rep(c(90, 95, 100, 105, 110), times = 3), times = 3)
    Time = rep(1, times = 45)
    r = rep(sort(rep(c(0.05, 0.09, 0.15), times = 5)), times = 3)
    sigma = rep(0.10, times = 15); sigma = c(sigma, 3*sigma, 5*sigma)

    # Moment Matched Call Prices:
    CallLNa = c(
        11.95333,  7.41517,  3.64748,  1.30684,  0.32399,
        13.38629,  8.91721,  4.92310,  2.07045,  0.62338,
        15.39906, 11.12196,  7.03485,  3.61869,  1.41080)
    CallLNb = c(
        14.03812, 10.74879,  7.99251,  5.77417,  4.05724,
        15.06704, 11.73287,  8.88576,  6.54628,  4.69511,
        16.59082, 13.22661, 10.27814,  7.78408,  5.74800)
    CallLNc = c(
        17.67918, 14.91574, 12.48954, 10.38535,  8.58075,
        18.43698, 15.66486, 13.21198, 11.06751,  9.21323,
        19.55391, 16.78229, 14.30234, 12.10904, 10.18997)
    CallFTLN = c(CallLNa, CallLNb, CallLNc)

    # Gram-Charlier Call Prices:
    CallFTa = c(
        11.95127,  7.40754,  3.64091, 1.31097,  0.33156,
        13.38535,  8.91185,  4.91459, 2.07002,  0.63072,
        15.39885, 11.11966,  7.02746, 3.61216,  1.41384)
    CallFTb = c(
        13.89562, 10.63393,  7.92948, 5.76852,  4.09909,
        14.92543, 11.60605,  8.80190, 6.51749,  4.71737,
        16.45856, 13.09056, 10.16897, 7.72184,  5.73774)
    CallFTc = c(
        17.00382, 14.46257, 12.25646, 10.34604,  8.69620,
        17.69986, 15.13557, 12.89937, 10.95396,  9.26553,
        18.73925, 16.14761, 13.87252, 11.88030, 10.13926)
    CallFTLNGC = c(CallFTa, CallFTb, CallFTc)

    # Return Value:
    data.frame(S, X, Time, r, sigma, CallFTLN, CallFTLNGC)
}


# ------------------------------------------------------------------------------


GemanTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Geman's Table

    # Source:
    #   H. Geman [],
    #   Functionals of Brownian Motion in Path Dependent
    #   Option Valuation

    # Settings:
    S = c(1.9, 2.0, 2.1, 2.0, 2.0, 2.0)
    X = rep(2, times = 6)
    Time = c(1, 1, 1, 1, 2, 2)
    r = c(5, 5, 5, 2, 1.25, 5)/100
    sigma = c(5, 5, 5, 1, 2.5, 5)/10

    # Call Prices:
    CallGY = c(
        0.195, 0.248, 0.308, 0.058, 0.1772, 0.352)
    CallMC = c(
        0.191, 0.248, 0.306, 0.056, 0.1771, 0.347)

    # Return Value:
    data.frame(cbind(S, X, Time, r, sigma, CallGY, CallMC))
}


# ------------------------------------------------------------------------------


LinetzkyTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Linetzky's Table 3

    # Source:
    #   V. Linetzky [2002]
    #   Spectral Expansions for Asian (Average Price) Options

    # Settings:
    S     = c(2.00, 2.00, 2.00,   1.90, 2.00, 2.10, 2.00)
    X     = c(2.00, 2.00, 2.00,   2.00, 2.00, 2.00, 2.00)
    Time  = c(1.00, 1.00, 2.00,   1.00, 1.00, 1.00, 2.00)
    r     = c(0.02, 0.18, 0.0125, 0.05, 0.05, 0.05, 0.05)
    sigma = c(10.0, 30.0, 25.0,   50.0, 50.0, 50.0, 50.0) / 100

    # Call Prices:
    CallEE = c(
        0.0559860415,  0.2183875466,  0.1722687410,  0.1931737903,
        0.2464156905,  0.3062203648,  0.3500952190)
    CallSLT = c(
        0.055986,      0.218388,      0.172269,      0.193174,
        0.246416,      0.306220,      0.350095)
    CallMC = c(
        0.05602,       0.2185,        0.1725,        0.1933,
        0.2465,        0.3064,        0.3503)
    CallTLB = c(
        0.055985,      0.218366,      0.172226,      0.193060,
        0.246298,      0.306094,      0.349779)
    CallTUB = c(
        0.055989,      0.218473,      0.172451,      0.193799,
        0.247054,      0.306904,      0.352556)

    # Return Value:
    data.frame(S, X, Time, r, sigma, CallEE, CallSLT, CallMC, CallTLB, CallTUB)
}


# ------------------------------------------------------------------------------


ZhangTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Zhangs's results from Table 1

    # Source:
    #   J.E. Zhang [2002],
    #   A semi-analytical method for pricing and hedging
    #   continuously sampled arithmetic average rate options

    # Settings:
    S = 100*rep(1, times = 36)
    X = c(rep(c(95, 100, 105), times = 3), rep(c(90, 100, 110), times = 9))
    Time = rep(1, times = 36)
    r = rep(c(5, 5, 5, 9, 9, 9, 15, 15, 15)/100, times = 4)
    sigma = sort(rep(c(0.05, 0.10, 0.20, 0.30), times = 9))

    # Call Prices:
    CallZ = c(
         7.1777275, 2.7161745, 0.3372614,  8.8088302,  4.3082350, 0.9583841,
        11.0940944, 6.7943550, 2.7444531, 11.9510927,  3.6413864, 0.3312030,
        13.3851974, 4.9151167, 0.6302713, 15.3987687,  7.0277081, 1.4136149,
        12.5959916, 5.7630881, 1.9898945, 13.8314996,  6.7773481, 2.5462209,
        15.6417575, 8.4088330, 3.5556100, 13.9538233,  7.9456288, 4.0717942,
        14.9839595, 8.8287588, 4.6967089, 16.5129113, 10.2098305, 5.7301225)

    # Return Value:
    data.frame(cbind(S, X, Time, r, sigma, CallZ))
}


# ------------------------------------------------------------------------------


ZhangShortTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Zhangs's short-tenor (ST) results from Table 7

    # Source:
    #   J.E. Zhang [2002],
    #   A semi-analytical method for pricing and hedging
    #   continuously sampled arithmetic average rate options

    # Settings:
    S = 100*rep(1, times = 18)
    X = c(rep(c(95,100,105), times = 6))
    Time = rep(0.08, times = 18)
    r = rep(0.09, times = 18)
    sigma = sort(rep(c(0.05, 0.10, 0.20, 0.30, 0.40, 0.50), times = 3))

    # Call Prices:
    CallPM = c(
         5.3224059, 0.5343219, 0.0000000,  5.3225549, 0.8431357, 0.0014921,
         5.3816262, 1.4835707, 0.1292560,  5.6304554, 2.1291126, 0.4880727,
         6.0222502, 2.7758194, 0.9753549,  6.4947818, 3.4228569, 1.5274729)
    CallCT = c(
         5.3224059, 0.5343040, 0.0000000,  5.3225550, 0.8431302, 0.0014924,
         5.3816340, 1.4835272, 0.1292529,  5.6304480, 2.1289662, 0.4879999,
         6.0221516, 2.7754740, 0.9750983,  6.4944850, 3.4221863, 1.5268836)

    # Return Value:
    data.frame(cbind(S, X, Time, r, sigma, CallPM, CallCT))
}


# ------------------------------------------------------------------------------


ZhangLongTable =
function()
{   # A function implemented by Diethelm Wuertz

    # Description:
    #   Display Zhangs's long-tenor (LT) results from Table 7

    # Source:
    #   J.E. Zhang [2002],
    #   A semi-analytical method for pricing and hedging
    #   continuously sampled arithmetic average rate options

    # Settings:
    S = 100*rep(1, times = 18)
    X = c(rep(c(95,100,105), times = 6))
    Time = rep(3, times = 18)
    r = rep(0.09, times = 18)
    sigma = sort(rep(c(0.05, 0.10, 0.20, 0.30, 0.40, 0.50), times = 3))

    # Call Prices:
    CallPM = c(
         15.11626, 11.30359,  7.55327,   15.21331, 11.63716,  8.39113,
         16.63363, 13.76592, 11.22170,   19.01304, 16.58331, 14.39723,
         21.70848, 19.57038, 17.62156,   24.47434, 22.55837, 20.79507)
    CallCT = c(
         15.11626, 11.30361,  7.55328,   15.21376, 11.63752,  8.39084,
         16.63611, 13.76547, 11.21783,   19.01856, 16.58101, 14.38708,
         21.72991, 19.57593, 17.61228,   24.54788, 22.60645, 20.81811)

    # Return Value:
    data.frame(cbind(S, X, Time, r, sigma, CallPM, CallCT))
}


################################################################################

