use std::cell::RefCell;
use crate::math::integrals::Integrator;
use crate::math::integrals::simpson::SimpsonIntegral;
use crate::methods::finitedifferences::meshers::FdmMesher;
use crate::methods::finitedifferences::operators::FdmLinearOpIterator;
use crate::payoff::Payoff;
use crate::shared::Shared;
use crate::types::{Real, Size, Time};
pub trait FdmInnerValueCalculator {
fn inner_value(&self, iter: &FdmLinearOpIterator, t: Time) -> Real;
fn avg_inner_value(&self, iter: &FdmLinearOpIterator, t: Time) -> Real;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GridMapping {
Identity,
Exp,
}
impl GridMapping {
fn apply(self, x: Real) -> Real {
match self {
GridMapping::Identity => x,
GridMapping::Exp => x.exp(),
}
}
}
pub struct FdmCellAveragingInnerValue {
payoff: Shared<dyn Payoff>,
mesher: Shared<dyn FdmMesher>,
direction: Size,
grid_mapping: GridMapping,
avg_inner_values: RefCell<Vec<Real>>,
}
impl FdmCellAveragingInnerValue {
pub fn new(payoff: Shared<dyn Payoff>, mesher: Shared<dyn FdmMesher>, direction: Size) -> Self {
Self::with_grid_mapping(payoff, mesher, direction, GridMapping::Identity)
}
pub fn with_grid_mapping(
payoff: Shared<dyn Payoff>,
mesher: Shared<dyn FdmMesher>,
direction: Size,
grid_mapping: GridMapping,
) -> Self {
FdmCellAveragingInnerValue {
payoff,
mesher,
direction,
grid_mapping,
avg_inner_values: RefCell::new(Vec::new()),
}
}
#[allow(clippy::float_cmp)]
fn avg_inner_value_calc(&self, iter: &FdmLinearOpIterator, t: Time) -> Real {
let dim = self.mesher.layout().dim()[self.direction];
let coord = iter.coordinates()[self.direction];
if coord == 0 || coord == dim - 1 {
return self.inner_value(iter, t);
}
let loc = self.mesher.location(iter, self.direction);
let a = loc - self.mesher.dminus(iter, self.direction) / 2.0;
let b = loc + self.mesher.dplus(iter, self.direction) / 2.0;
let f = |x: Real| self.payoff.value(self.grid_mapping.apply(x));
let accuracy = if f(a) != 0.0 || f(b) != 0.0 {
(f(a) + f(b)) * 5e-5
} else {
1e-4
};
SimpsonIntegral::new(accuracy, 8)
.and_then(|integral| integral.integrate(f, a, b))
.map(|integral| integral / (b - a))
.unwrap_or_else(|_| self.inner_value(iter, t))
}
}
impl FdmInnerValueCalculator for FdmCellAveragingInnerValue {
fn inner_value(&self, iter: &FdmLinearOpIterator, _t: Time) -> Real {
let loc = self.mesher.location(iter, self.direction);
self.payoff.value(self.grid_mapping.apply(loc))
}
fn avg_inner_value(&self, iter: &FdmLinearOpIterator, t: Time) -> Real {
let uninitialized = self.avg_inner_values.borrow().is_empty();
if uninitialized {
let dim = self.mesher.layout().dim()[self.direction];
let mut values = vec![0.0; dim];
let mut initialized = vec![false; dim];
for position in self.mesher.layout().iter() {
let coord = position.coordinates()[self.direction];
if !initialized[coord] {
initialized[coord] = true;
values[coord] = self.avg_inner_value_calc(&position, t);
}
}
*self.avg_inner_values.borrow_mut() = values;
}
self.avg_inner_values.borrow()[iter.coordinates()[self.direction]]
}
}
pub fn fdm_log_inner_value(
payoff: Shared<dyn Payoff>,
mesher: Shared<dyn FdmMesher>,
direction: Size,
) -> FdmCellAveragingInnerValue {
FdmCellAveragingInnerValue::with_grid_mapping(payoff, mesher, direction, GridMapping::Exp)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use crate::instruments::PlainVanillaPayoff;
use crate::methods::finitedifferences::meshers::UniformGridMesher;
use crate::methods::finitedifferences::operators::FdmLinearOpLayout;
use crate::option::OptionType;
use crate::shared::shared;
const DIM: [Size; 2] = [5, 3];
const BOUNDARIES: [(Real, Real); 2] = [(80.0, 120.0), (0.0, 1.0)];
fn mesher() -> Shared<dyn FdmMesher> {
let layout = shared(FdmLinearOpLayout::new(DIM.to_vec()));
shared(UniformGridMesher::new(layout, &BOUNDARIES).unwrap())
}
fn call(strike: Real) -> Shared<dyn Payoff> {
shared(PlainVanillaPayoff::new(OptionType::Call, strike))
}
fn calculator(strike: Real) -> FdmCellAveragingInnerValue {
FdmCellAveragingInnerValue::new(call(strike), mesher(), 0)
}
struct SpikePayoff {
location: Real,
}
impl Payoff for SpikePayoff {
fn name(&self) -> String {
"Spike".to_string()
}
fn description(&self) -> String {
"Spike".to_string()
}
fn value(&self, price: Real) -> Real {
if (price - self.location).abs() < 1e-9 {
42.0
} else {
1e-13
}
}
}
struct CountingPayoff {
inner: PlainVanillaPayoff,
evaluations: Cell<usize>,
}
impl Payoff for CountingPayoff {
fn name(&self) -> String {
self.inner.name()
}
fn description(&self) -> String {
self.inner.description()
}
fn value(&self, price: Real) -> Real {
self.evaluations.set(self.evaluations.get() + 1);
self.inner.value(price)
}
}
#[test]
fn the_outermost_cells_take_the_grid_point_value() {
let calculator = calculator(80.0);
let mesher = mesher();
let mut boundary_points = 0;
for position in mesher.layout().iter() {
let coord = position.coordinates()[0];
if coord == 0 || coord == DIM[0] - 1 {
boundary_points += 1;
assert_eq!(
calculator.avg_inner_value(&position, 0.0),
calculator.inner_value(&position, 0.0)
);
}
}
assert_eq!(boundary_points, 2 * DIM[1]);
}
#[test]
fn the_average_depends_only_on_the_averaging_coordinate() {
let calculator = calculator(100.0);
let mesher = mesher();
let mut by_coordinate = vec![None; DIM[0]];
for position in mesher.layout().iter() {
let value = calculator.avg_inner_value(&position, 0.0);
match by_coordinate[position.coordinates()[0]] {
None => by_coordinate[position.coordinates()[0]] = Some(value),
Some(first) => assert_eq!(value, first),
}
}
assert!(by_coordinate.iter().all(Option::is_some));
}
#[test]
fn a_cell_over_a_linear_payoff_averages_to_its_grid_point() {
let calculator = calculator(84.0);
let mesher = mesher();
for position in mesher.layout().iter() {
let coord = position.coordinates()[0];
if coord == 0 || coord == DIM[0] - 1 {
continue;
}
let expected = calculator.inner_value(&position, 0.0);
let average = calculator.avg_inner_value(&position, 0.0);
assert!(
(average - expected).abs() <= 1e-13 * expected,
"coordinate {coord}: {average} vs {expected}"
);
}
}
#[test]
fn a_cell_straddling_the_strike_averages_above_its_grid_point() {
let calculator = calculator(100.0);
let mesher = mesher();
let at_the_money = mesher.layout().iter().find(|p| p.coordinates()[0] == 2);
let at_the_money = at_the_money.unwrap();
assert_eq!(calculator.inner_value(&at_the_money, 0.0), 0.0);
assert!(calculator.avg_inner_value(&at_the_money, 0.0) > 0.0);
}
#[test]
fn the_log_calculator_evaluates_the_payoff_at_the_exponential() {
let payoff = PlainVanillaPayoff::new(OptionType::Call, 100.0);
let layout = shared(FdmLinearOpLayout::new(vec![7]));
let mesher: Shared<dyn FdmMesher> =
shared(UniformGridMesher::new(layout, &[(4.0, 5.0)]).unwrap());
let calculator = fdm_log_inner_value(shared(payoff), Shared::clone(&mesher), 0);
for position in mesher.layout().iter() {
let x = mesher.location(&position, 0);
assert_eq!(
calculator.inner_value(&position, 0.0),
payoff.value(x.exp())
);
}
}
#[test]
fn an_unusable_accuracy_falls_back_to_the_grid_point_value() {
let mesher = mesher();
let interior = mesher.layout().iter().find(|p| p.coordinates()[0] == 1);
let interior = interior.unwrap();
let location = mesher.location(&interior, 0);
let calculator =
FdmCellAveragingInnerValue::new(shared(SpikePayoff { location }), mesher, 0);
assert_eq!(calculator.inner_value(&interior, 0.0), 42.0);
assert_eq!(calculator.avg_inner_value(&interior, 0.0), 42.0);
}
#[test]
fn the_average_is_computed_once_per_coordinate() {
let payoff = shared(CountingPayoff {
inner: PlainVanillaPayoff::new(OptionType::Call, 100.0),
evaluations: Cell::new(0),
});
let mesher = mesher();
let calculator = FdmCellAveragingInnerValue::new(
Shared::clone(&payoff) as Shared<dyn Payoff>,
Shared::clone(&mesher),
0,
);
let first: Vec<Real> = mesher
.layout()
.iter()
.map(|position| calculator.avg_inner_value(&position, 0.0))
.collect();
let evaluations = payoff.evaluations.get();
assert!(evaluations > 0);
let second: Vec<Real> = mesher
.layout()
.iter()
.map(|position| calculator.avg_inner_value(&position, 0.0))
.collect();
assert_eq!(second, first);
assert_eq!(payoff.evaluations.get(), evaluations);
}
}