use crate::{
core::{
collateral::Discountable,
instrument::{AssetClass, Instrument},
trade::{Side, Trade},
},
currencies::currency::Currency,
indices::fxpair::FxPair,
time::{date::Date, daycounter::DayCounter},
utils::errors::Result,
xva::{
claimevaluationstrategy::ClaimEvaluationStrategy, contigentclaim::ContingentClaim,
makecontigentclaim::MakeContingentClaim,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FxForwardSettlement {
Deliverable,
NonDeliverable {
fixing_date: Date,
settlement_currency: Currency,
},
}
#[derive(Debug, Clone)]
pub struct FxForward {
identifier: String,
delivery_date: Date,
forward_price: Option<f64>,
forward_points: Option<f64>,
base_currency: Currency,
quote_currency: Currency,
day_counter: DayCounter,
settlement: FxForwardSettlement,
}
impl FxForward {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub const fn new(
identifier: String,
delivery_date: Date,
forward_price: Option<f64>,
forward_points: Option<f64>,
base_currency: Currency,
quote_currency: Currency,
day_counter: DayCounter,
settlement: FxForwardSettlement,
) -> Self {
Self {
identifier,
delivery_date,
forward_price,
forward_points,
base_currency,
quote_currency,
day_counter,
settlement,
}
}
#[must_use]
pub const fn delivery_date(&self) -> Date {
self.delivery_date
}
#[must_use]
pub const fn forward_price(&self) -> Option<f64> {
self.forward_price
}
#[must_use]
pub const fn forward_rate(&self) -> Option<f64> {
self.forward_price
}
#[must_use]
pub const fn forward_points(&self) -> Option<f64> {
self.forward_points
}
#[must_use]
pub const fn is_outright(&self) -> bool {
self.forward_price.is_some()
}
#[must_use]
pub const fn has_forward_points(&self) -> bool {
self.forward_points.is_some()
}
#[must_use]
pub const fn settlement(&self) -> FxForwardSettlement {
self.settlement
}
#[must_use]
pub const fn is_deliverable(&self) -> bool {
match self.settlement {
FxForwardSettlement::Deliverable => true,
FxForwardSettlement::NonDeliverable { .. } => false,
}
}
#[must_use]
pub const fn is_ndf(&self) -> bool {
!self.is_deliverable()
}
#[must_use]
pub const fn fixing_date(&self) -> Option<Date> {
match self.settlement {
FxForwardSettlement::Deliverable => None,
FxForwardSettlement::NonDeliverable { fixing_date, .. } => Some(fixing_date),
}
}
#[must_use]
pub const fn settlement_currency(&self) -> Option<Currency> {
match self.settlement {
FxForwardSettlement::Deliverable => None,
FxForwardSettlement::NonDeliverable {
settlement_currency,
..
} => Some(settlement_currency),
}
}
#[must_use]
pub const fn base_currency(&self) -> Currency {
self.base_currency
}
#[must_use]
pub const fn quote_currency(&self) -> Currency {
self.quote_currency
}
#[must_use]
pub const fn day_counter(&self) -> &DayCounter {
&self.day_counter
}
pub fn pair(&self) -> Result<FxPair> {
FxPair::new(self.base_currency, self.quote_currency)
}
}
impl Instrument for FxForward {
fn identifier(&self) -> String {
self.identifier.clone()
}
}
impl Discountable for FxForward {
fn currency(&self) -> Currency {
self.quote_currency
}
fn asset_class(&self) -> AssetClass {
AssetClass::Fx
}
}
pub struct FxForwardTrade {
instrument: FxForward,
trade_date: Date,
notional: f64,
side: Side,
}
impl FxForwardTrade {
#[must_use]
pub const fn new(instrument: FxForward, trade_date: Date, notional: f64, side: Side) -> Self {
Self {
instrument,
trade_date,
notional,
side,
}
}
#[must_use]
pub const fn notional(&self) -> f64 {
self.notional
}
}
impl Trade<FxForward> for FxForwardTrade {
fn instrument(&self) -> &FxForward {
&self.instrument
}
fn trade_date(&self) -> Date {
self.trade_date
}
fn side(&self) -> Side {
self.side
}
}
impl FxForwardTrade {
pub fn into_contingent_claims(&self) -> Result<Vec<ContingentClaim>> {
let fwd = self.instrument();
let trade_id = fwd.identifier();
let delivery = fwd.delivery_date();
let fwd_price = fwd
.forward_price()
.ok_or_else(|| crate::utils::errors::QSError::ValueNotSetErr("forward price".into()))?;
let opposite = match self.side {
Side::LongReceive => Side::PayShort,
Side::PayShort => Side::LongReceive,
};
let base_claim = MakeContingentClaim::default()
.with_trade_id(trade_id.clone())
.with_leg_id(0)
.with_payment_date(delivery)
.with_currency(fwd.base_currency())
.with_notional(self.notional)
.with_side(self.side)
.with_evaluation_strategy(ClaimEvaluationStrategy::Deterministic { amount: 1.0 })
.build()?;
let quote_claim = MakeContingentClaim::default()
.with_trade_id(trade_id)
.with_leg_id(1)
.with_payment_date(delivery)
.with_currency(fwd.quote_currency())
.with_notional(self.notional * fwd_price)
.with_side(opposite)
.with_evaluation_strategy(ClaimEvaluationStrategy::Deterministic { amount: 1.0 })
.build()?;
Ok(vec![base_claim, quote_claim])
}
}