use crate::{
ad::scalar::Scalar,
core::{
collateral::Discountable,
instrument::AssetClass,
marketdatahandling::{
forwardraterequest::ForwardRateRequest, fxrequest::FxRequest,
pathdependentrequest::PathDependentRequest, spotrequest::SpotRequest,
},
trade::Side,
},
currencies::currency::Currency,
indices::marketindex::MarketIndex,
instruments::cashflows::payoffops::PayoffOps,
time::{
date::Date,
daycounter::DayCounter,
},
utils::errors::Result,
xva::{
claimevaluationstrategy::ClaimEvaluationStrategy,
visitors::{preprocessorexecutor::SimulationRequest, marketmodel::SimulationResponse},
},
};
pub struct ContingentClaim {
trade_id: String,
leg_id: usize,
idx: Option<usize>,
payment_date: Date,
fixing_date: Option<Date>,
accrual_start: Option<Date>,
accrual_end: Option<Date>,
currency: Currency,
foreign_currency: Option<Currency>,
notional: f64,
side: Side,
evaluation_strategy: ClaimEvaluationStrategy,
index: Option<MarketIndex>,
realized_fixing: Option<f64>,
partial_fixing: Option<PartialFixing>,
}
pub struct PartialFixing {
pub realized_accrual_factor: f64,
pub original_accrual_start: Date,
}
impl ContingentClaim {
#[allow(clippy::too_many_arguments)]
#[must_use]
pub const fn new(
trade_id: String,
leg_id: usize,
payment_date: Date,
fixing_date: Option<Date>,
accrual_start: Option<Date>,
accrual_end: Option<Date>,
currency: Currency,
foreign_currency: Option<Currency>,
notional: f64,
side: Side,
evaluation_strategy: ClaimEvaluationStrategy,
index: Option<MarketIndex>,
) -> Self {
Self {
trade_id,
leg_id,
idx: None,
payment_date,
fixing_date,
accrual_start,
accrual_end,
currency,
foreign_currency,
notional,
side,
evaluation_strategy,
index,
realized_fixing: None,
partial_fixing: None,
}
}
#[must_use]
pub fn trade_id(&self) -> &str {
&self.trade_id
}
#[must_use]
pub const fn leg_id(&self) -> usize {
self.leg_id
}
#[must_use]
pub const fn payment_date(&self) -> Date {
self.payment_date
}
#[must_use]
pub const fn fixing_date(&self) -> Option<Date> {
self.fixing_date
}
#[must_use]
pub const fn accrual_start(&self) -> Option<Date> {
self.accrual_start
}
#[must_use]
pub const fn accrual_end(&self) -> Option<Date> {
self.accrual_end
}
#[must_use]
pub const fn currency(&self) -> Currency {
self.currency
}
#[must_use]
pub const fn foreign_currency(&self) -> Option<Currency> {
self.foreign_currency
}
#[must_use]
pub const fn notional(&self) -> f64 {
self.notional
}
#[must_use]
pub const fn side(&self) -> Side {
self.side
}
#[must_use]
pub const fn evaluation_strategy(&self) -> &ClaimEvaluationStrategy {
&self.evaluation_strategy
}
#[must_use]
pub const fn index(&self) -> Option<&MarketIndex> {
self.index.as_ref()
}
#[must_use]
pub const fn idx(&self) -> Option<usize> {
self.idx
}
pub const fn set_idx(&mut self, idx: usize) {
self.idx = Some(idx);
}
pub const fn set_realized_fixing(&mut self, rate: f64) {
self.realized_fixing = Some(rate);
}
#[must_use]
pub const fn realized_fixing(&self) -> Option<f64> {
self.realized_fixing
}
pub const fn set_partial_fixing(
&mut self,
realized_accrual_factor: f64,
original_accrual_start: Date,
new_start: Date,
) {
self.partial_fixing = Some(PartialFixing {
realized_accrual_factor,
original_accrual_start,
});
self.accrual_start = Some(new_start);
}
#[must_use]
pub const fn partial_fixing(&self) -> Option<&PartialFixing> {
self.partial_fixing.as_ref()
}
#[must_use]
pub fn simulation_request(&self) -> SimulationRequest {
let fx_request = Some(self.foreign_currency.map_or_else(
|| FxRequest::single(self.currency),
|quote| FxRequest::pair(self.currency, quote),
));
match &self.evaluation_strategy {
ClaimEvaluationStrategy::Deterministic { .. } => SimulationRequest {
discount_request: None,
forward_rate_request: None,
fx_request,
spot_request: None,
path_dependent_request: None,
},
ClaimEvaluationStrategy::LinearRate { .. }
| ClaimEvaluationStrategy::NonLinearRate { .. } => {
let forward_request = if self.realized_fixing.is_some() {
None
} else {
let forward_index = self.index.clone();
let fixing_date = self.fixing_date.unwrap_or(self.payment_date);
forward_index.map(|idx| {
ForwardRateRequest::new(idx, fixing_date)
.with_start_date(self.accrual_start.unwrap_or(fixing_date))
.with_end_date(self.accrual_end.unwrap_or(self.payment_date))
})
};
SimulationRequest {
discount_request: None,
forward_rate_request: forward_request,
fx_request,
spot_request: None,
path_dependent_request: None,
}
}
ClaimEvaluationStrategy::SpotPayoff {
observation_date, ..
} => {
let spot_request = self
.index
.clone()
.map(|idx| SpotRequest::new(idx, *observation_date));
SimulationRequest {
discount_request: None,
forward_rate_request: None,
fx_request,
spot_request,
path_dependent_request: None,
}
}
ClaimEvaluationStrategy::PathDependent {
observation_dates, ..
} => {
let path_request = self
.index
.clone()
.map(|idx| PathDependentRequest::new(observation_dates.clone(), idx));
SimulationRequest {
discount_request: None,
forward_rate_request: None,
fx_request,
spot_request: None,
path_dependent_request: path_request,
}
}
ClaimEvaluationStrategy::ExerciseContingent { inner, .. } => {
let inner_request = inner.simulation_request();
SimulationRequest {
discount_request: None,
forward_rate_request: inner_request.forward_rate_request,
fx_request,
spot_request: inner_request.spot_request,
path_dependent_request: inner_request.path_dependent_request,
}
}
}
}
fn eval_linear_rate<T: Scalar + 'static>(
&self,
response: &SimulationResponse<T>,
spread: f64,
day_counter: DayCounter,
) -> T {
let (start, end) = (
self.accrual_start().unwrap_or_else(|| self.payment_date()),
self.accrual_end().unwrap_or_else(|| self.payment_date()),
);
match (self.realized_fixing, &self.partial_fixing) {
(Some(rf), _) => {
let tau = day_counter.year_fraction(start, end);
T::scalar((rf + spread) * tau)
}
(None, Some(pf)) => {
let tau_full = day_counter.year_fraction(pf.original_accrual_start, end);
let tau_rem = day_counter.year_fraction(start, end);
let fwd = response.forward_rates.unwrap_or_else(T::zero);
let compound = T::scalar(pf.realized_accrual_factor)
.mul_val(T::one().add_val(fwd.mul_val(T::scalar(tau_rem))));
compound
.sub_val(T::one())
.div_val(T::scalar(tau_full))
.add_val(T::scalar(spread))
.mul_val(T::scalar(tau_full))
}
(None, None) => {
let rate = response.forward_rates.unwrap_or_else(T::zero);
let tau = day_counter.year_fraction(start, end);
rate.add_val(T::scalar(spread)).mul_val(T::scalar(tau))
}
}
}
fn eval_nonlinear_rate<T: Scalar + 'static>(
&self,
response: &SimulationResponse<T>,
payoff_ops: &PayoffOps,
spread: f64,
day_counter: DayCounter,
) -> T {
let (start, end) = (
self.accrual_start().unwrap_or_else(|| self.payment_date()),
self.accrual_end().unwrap_or_else(|| self.payment_date()),
);
match (self.realized_fixing, &self.partial_fixing) {
(Some(rf), _) => {
let tau = day_counter.year_fraction(start, end);
let fixing = T::scalar(rf + spread);
let payoff = payoff_ops.eval(fixing).unwrap_or_else(|_| T::zero());
payoff.mul_val(T::scalar(tau))
}
(None, Some(pf)) => {
let tau_full = day_counter.year_fraction(pf.original_accrual_start, end);
let tau_rem = day_counter.year_fraction(start, end);
let fwd = response.forward_rates.unwrap_or_else(T::zero);
let compound = T::scalar(pf.realized_accrual_factor)
.mul_val(T::one().add_val(fwd.mul_val(T::scalar(tau_rem))));
let rate = compound.sub_val(T::one()).div_val(T::scalar(tau_full));
let fixing = rate.add_val(T::scalar(spread));
let payoff = payoff_ops.eval(fixing).unwrap_or_else(|_| T::zero());
payoff.mul_val(T::scalar(tau_full))
}
(None, None) => {
let rate = response.forward_rates.unwrap_or_else(T::zero);
let tau = day_counter.year_fraction(start, end);
let fixing = rate.add_val(T::scalar(spread));
let payoff = payoff_ops.eval(fixing).unwrap_or_else(|_| T::zero());
payoff.mul_val(T::scalar(tau))
}
}
}
pub fn evaluate<T: Scalar + 'static>(&self, response: &SimulationResponse<T>) -> Result<T> {
let sign = self.side().sign();
let notional = self.notional();
let fx = response.fx_rates.unwrap_or_else(T::one);
let discount = response.discounts.unwrap_or_else(T::one);
let raw: T = match self.evaluation_strategy() {
ClaimEvaluationStrategy::Deterministic { amount } => T::scalar(*amount),
ClaimEvaluationStrategy::LinearRate {
spread,
day_counter,
} => self.eval_linear_rate(response, *spread, *day_counter),
ClaimEvaluationStrategy::NonLinearRate {
payoff_ops,
spread,
strike: _,
day_counter,
} => self.eval_nonlinear_rate(response, payoff_ops, *spread, *day_counter),
ClaimEvaluationStrategy::SpotPayoff {
payoff_ops,
..
} => {
let spot = response.spots.unwrap_or_else(T::zero);
payoff_ops.eval(spot).unwrap_or_else(|_| T::zero())
}
ClaimEvaluationStrategy::PathDependent {
payoff_ops,
..
} => {
let obs = response.path_dependent_observations.unwrap_or_else(T::zero);
payoff_ops.eval(obs).unwrap_or_else(|_| T::zero())
}
ClaimEvaluationStrategy::ExerciseContingent { inner, .. } => {
inner.evaluate(response)?
}
};
Ok(raw
.mul_val(discount)
.mul_val(fx)
.mul_val(T::scalar(sign * notional)))
}
}
impl Discountable for ContingentClaim {
fn asset_class(&self) -> AssetClass {
AssetClass::InterestRate
}
fn currency(&self) -> Currency {
self.currency
}
fn discount_index(&self) -> Option<MarketIndex> {
self.index.clone()
}
}