use std::cell::RefCell;
use crate::errors::QlResult;
use crate::methods::finitedifferences::operators::{FdmBlackScholesOp, FdmLinearOpComposite};
use crate::processes::GeneralizedBlackScholesProcess;
use crate::shared::{Shared, SharedMut, shared_mut};
use crate::types::{Real, Size};
use super::{Fdm1DimSolver, FdmSchemeDesc, FdmSolverDesc};
const DIRECTION: Size = 0;
pub struct FdmBlackScholesSolver {
process: Shared<GeneralizedBlackScholesProcess>,
strike: Real,
solver_desc: FdmSolverDesc,
scheme_desc: FdmSchemeDesc,
solver: RefCell<Option<Fdm1DimSolver>>,
}
impl FdmBlackScholesSolver {
pub fn new(
process: Shared<GeneralizedBlackScholesProcess>,
strike: Real,
solver_desc: FdmSolverDesc,
scheme_desc: FdmSchemeDesc,
) -> Self {
FdmBlackScholesSolver {
process,
strike,
solver_desc,
scheme_desc,
solver: RefCell::new(None),
}
}
pub fn value_at(&self, s: Real) -> QlResult<Real> {
self.with_solver(|solver| solver.interpolate_at(s.ln()))
}
pub fn delta_at(&self, s: Real) -> QlResult<Real> {
self.with_solver(|solver| Ok(solver.derivative_x(s.ln())? / s))
}
pub fn gamma_at(&self, s: Real) -> QlResult<Real> {
self.with_solver(|solver| {
let x = s.ln();
Ok((solver.derivative_xx(x)? - solver.derivative_x(x)?) / (s * s))
})
}
pub fn theta_at(&self, s: Real) -> QlResult<Option<Real>> {
self.with_solver(|solver| solver.theta_at(s.ln()))
}
fn calculate(&self) -> QlResult<()> {
if self.solver.borrow().is_some() {
return Ok(());
}
let op = shared_mut(FdmBlackScholesOp::new(
Shared::clone(&self.solver_desc.mesher),
&self.process,
self.strike,
DIRECTION,
)?) as SharedMut<dyn FdmLinearOpComposite>;
let solver = Fdm1DimSolver::new(self.solver_desc.clone(), self.scheme_desc, op);
*self.solver.borrow_mut() = Some(solver);
Ok(())
}
fn with_solver<T>(&self, read: impl FnOnce(&Fdm1DimSolver) -> QlResult<T>) -> QlResult<T> {
self.calculate()?;
let cached = self.solver.borrow();
let solver = cached.as_ref().expect("calculate leaves the solver built");
read(solver)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::handle::Handle;
use crate::instruments::PlainVanillaPayoff;
use crate::interestrate::Compounding;
use crate::methods::finitedifferences::meshers::FdmMesher;
use crate::methods::finitedifferences::schemes::testops;
use crate::methods::finitedifferences::stepconditions::FdmStepConditionComposite;
use crate::methods::finitedifferences::utilities::fdm_log_inner_value;
use crate::option::OptionType::Call;
use crate::payoff::Payoff;
use crate::quotes::make_quote_handle;
use crate::shared::shared;
use crate::termstructures::volatility::{BlackConstantVol, BlackVolTermStructure};
use crate::termstructures::yields::FlatForward;
use crate::termstructures::yieldtermstructure::YieldTermStructure;
use crate::time::date::{Date, Month};
use crate::time::daycounter::DayCounter;
use crate::time::daycounters::actual365fixed::Actual365Fixed;
use crate::time::frequency::Frequency;
use crate::types::{Rate, Time, Volatility};
const R: Rate = 0.05;
const Q: Rate = 0.02;
const VOL: Volatility = 0.2;
const STRIKE: Real = 100.0;
const MATURITY: Time = 0.75;
const STEPS: Size = 10;
const DAMPING_STEPS: Size = 2;
const SPOT: Real = 90.0;
fn process() -> Shared<GeneralizedBlackScholesProcess> {
let dc = Actual365Fixed::new();
let today = Date::new(11, Month::February, 2018);
shared(GeneralizedBlackScholesProcess::new(
make_quote_handle(100.0).handle(),
flat_rate(today, Q, dc.clone()),
flat_rate(today, R, dc.clone()),
flat_vol(today, VOL, dc),
))
}
fn flat_rate(reference: Date, rate: Rate, dc: DayCounter) -> Handle<dyn YieldTermStructure> {
Handle::new(shared(FlatForward::with_rate(
reference,
rate,
dc,
Compounding::Continuous,
Frequency::Annual,
)) as Shared<dyn YieldTermStructure>)
}
fn flat_vol(
reference: Date,
vol: Volatility,
dc: DayCounter,
) -> Handle<dyn BlackVolTermStructure> {
Handle::new(shared(BlackConstantVol::new(reference, None, vol, dc))
as Shared<dyn BlackVolTermStructure>)
}
fn desc(mesher: &Shared<dyn FdmMesher>) -> FdmSolverDesc {
let payoff = shared(PlainVanillaPayoff::new(Call, STRIKE)) as Shared<dyn Payoff>;
FdmSolverDesc {
mesher: Shared::clone(mesher),
bc_set: Vec::new(),
condition: shared(FdmStepConditionComposite::new(&[], Vec::new())),
calculator: shared(fdm_log_inner_value(payoff, Shared::clone(mesher), 0)),
maturity: MATURITY,
time_steps: STEPS,
damping_steps: DAMPING_STEPS,
}
}
fn solver(mesher: &Shared<dyn FdmMesher>) -> FdmBlackScholesSolver {
FdmBlackScholesSolver::new(process(), STRIKE, desc(mesher), FdmSchemeDesc::douglas())
}
fn driven_by_hand(mesher: &Shared<dyn FdmMesher>) -> Fdm1DimSolver {
let op = shared_mut(
FdmBlackScholesOp::new(Shared::clone(mesher), &process(), STRIKE, DIRECTION).unwrap(),
) as SharedMut<dyn FdmLinearOpComposite>;
Fdm1DimSolver::new(desc(mesher), FdmSchemeDesc::douglas(), op)
}
#[test]
fn the_value_is_the_read_off_at_the_log_of_the_spot() {
let mesher = testops::mesher();
assert_eq!(
solver(&mesher).value_at(SPOT).unwrap(),
driven_by_hand(&mesher).interpolate_at(SPOT.ln()).unwrap()
);
}
#[test]
fn the_probe_tells_the_two_derivatives_apart() {
let mesher = testops::mesher();
let hand = driven_by_hand(&mesher);
let dx = hand.derivative_x(SPOT.ln()).unwrap();
let dxx = hand.derivative_xx(SPOT.ln()).unwrap();
assert!(dx.abs() > 1e-3, "the first derivative is degenerate: {dx}");
assert!(
dxx.abs() > 1e-3,
"the second derivative is degenerate: {dxx}"
);
assert!(
(dxx - dx).abs() > 1e-3,
"the two derivatives do not discriminate: {dx} against {dxx}"
);
}
#[test]
fn the_delta_carries_the_chain_rule_for_the_log_grid() {
let mesher = testops::mesher();
let hand = driven_by_hand(&mesher);
assert_eq!(
solver(&mesher).delta_at(SPOT).unwrap(),
hand.derivative_x(SPOT.ln()).unwrap() / SPOT
);
}
#[test]
fn the_gamma_carries_the_second_chain_rule_term() {
let mesher = testops::mesher();
let hand = driven_by_hand(&mesher);
let expected = (hand.derivative_xx(SPOT.ln()).unwrap()
- hand.derivative_x(SPOT.ln()).unwrap())
/ (SPOT * SPOT);
assert_eq!(solver(&mesher).gamma_at(SPOT).unwrap(), expected);
}
#[test]
fn the_theta_is_the_read_off_at_the_log_of_the_spot() {
let mesher = testops::mesher();
let expected = driven_by_hand(&mesher)
.theta_at(SPOT.ln())
.unwrap()
.expect("a capture away from today has a theta");
assert_eq!(
solver(&mesher)
.theta_at(SPOT)
.unwrap()
.expect("a capture away from today has a theta"),
expected
);
}
#[test]
fn the_theta_asked_first_is_the_theta_asked_last() {
let mesher = testops::mesher();
let asked_first = solver(&mesher)
.theta_at(SPOT)
.unwrap()
.expect("a capture away from today has a theta");
let in_order = solver(&mesher);
in_order.value_at(SPOT).unwrap();
in_order.delta_at(SPOT).unwrap();
in_order.gamma_at(SPOT).unwrap();
assert_eq!(
in_order
.theta_at(SPOT)
.unwrap()
.expect("a capture away from today has a theta"),
asked_first
);
}
}