Skip to main content

acta_maker_sdk/types/
amounts.rs

1//! Human-unit views and contract amount math for 1e9-scaled values.
2
3use super::PRICE_SCALE;
4use super::ids::{Decimals, Price, Quantity, Strike};
5
6#[allow(clippy::cast_precision_loss)]
7fn scaled_to_f64(value: u64) -> f64 {
8    value as f64 / PRICE_SCALE as f64
9}
10
11impl Strike {
12    /// Quote tokens per one underlying token, for display.
13    #[must_use]
14    pub fn as_decimal(self) -> f64 {
15        scaled_to_f64(self.value())
16    }
17}
18
19impl Price {
20    /// Premium in quote tokens per one underlying token, for display.
21    #[must_use]
22    pub fn as_decimal(self) -> f64 {
23        scaled_to_f64(self.value())
24    }
25}
26
27impl Quantity {
28    /// Underlying atoms as whole tokens, for display.
29    #[must_use]
30    #[allow(clippy::cast_precision_loss)]
31    pub fn to_units(self, underlying_decimals: Decimals) -> f64 {
32        self.value() as f64 / 10f64.powi(i32::from(underlying_decimals.value()))
33    }
34}
35
36/// Gross premium of a fill before protocol fees, in quote-mint atoms, rounded
37/// down exactly as the contract computes it. `None` when decimals exceed 18 or the
38/// result does not fit `u64`.
39#[must_use]
40pub fn total_premium_quote_atoms(
41    price: Price,
42    quantity: Quantity,
43    underlying_decimals: Decimals,
44    quote_decimals: Decimals,
45) -> Option<u64> {
46    scaled_quote_amount(
47        price.value(),
48        quantity.value(),
49        quote_decimals.value(),
50        underlying_decimals.value(),
51    )
52}
53
54/// A covered-call or cash-secured-put fill's premium split, in quote atoms.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct PhysicalPremium {
57    pub gross_premium: u64,
58    pub protocol_fee: u64,
59    pub net_premium: u64,
60}
61
62/// The contract's fee: the smaller of the premium fee and the volume fee on the
63/// strike notional. `None` as for [`total_premium_quote_atoms`] or when a rate
64/// exceeds 10 000 bps. Rates come from `/api/v1/venue`.
65#[must_use]
66pub fn physical_premium(
67    price: Price,
68    strike: Strike,
69    quantity: Quantity,
70    underlying_decimals: Decimals,
71    quote_decimals: Decimals,
72    premium_bps: u16,
73    volume_bps: u16,
74) -> Option<PhysicalPremium> {
75    if premium_bps > 10_000 || volume_bps > 10_000 {
76        return None;
77    }
78    let quote = |per_unit: u64| {
79        scaled_quote_amount(
80            per_unit,
81            quantity.value(),
82            quote_decimals.value(),
83            underlying_decimals.value(),
84        )
85    };
86    let fee =
87        |amount: u64, bps: u16| u64::try_from(u128::from(amount) * u128::from(bps) / 10_000).ok();
88    let gross_premium = quote(price.value())?;
89    let premium_fee = gross_premium - quote(price.value() - fee(price.value(), premium_bps)?)?;
90    let protocol_fee = premium_fee.min(fee(quote(strike.value())?, volume_bps)?);
91    Some(PhysicalPremium {
92        gross_premium,
93        protocol_fee,
94        net_premium: gross_premium - protocol_fee,
95    })
96}
97
98/// The contract's `scaled_quote_amount`: `per_unit` (1e9-scaled quote per
99/// underlying token) times `quantity` (underlying atoms), in quote atoms.
100pub(crate) fn scaled_quote_amount(
101    per_unit: u64,
102    quantity: u64,
103    quote_decimals: u8,
104    underlying_decimals: u8,
105) -> Option<u64> {
106    if quote_decimals > 18 || underlying_decimals > 18 {
107        return None;
108    }
109    let quote_scale = 10u128.pow(u32::from(quote_decimals));
110    let denominator = u128::from(PRICE_SCALE) * 10u128.pow(u32::from(underlying_decimals));
111    let numerator = u128::from(per_unit) * u128::from(quantity);
112    let amount = if quote_scale <= denominator {
113        numerator / (denominator / quote_scale)
114    } else {
115        numerator.checked_mul(quote_scale / denominator)?
116    };
117    u64::try_from(amount).ok()
118}
119
120#[cfg(test)]
121#[path = "amounts_tests.rs"]
122mod tests;