use crate::Decimal;
use crate::types::decimal::{decimal_ln, decimal_powi};
use crate::types::error::{MMError, MMResult};
const SECONDS_PER_MILLISECOND: Decimal = Decimal::from_parts(1, 0, 0, false, 3); const SECONDS_PER_YEAR: Decimal = Decimal::from_parts(31_536_000, 0, 0, false, 0);
pub fn calculate_reservation_price(
mid_price: Decimal,
inventory: Decimal,
risk_aversion: Decimal,
volatility: Decimal,
time_to_terminal_ms: u64,
) -> MMResult<Decimal> {
if mid_price <= Decimal::ZERO {
return Err(MMError::InvalidMarketState(
"mid_price must be positive".to_string(),
));
}
if volatility <= Decimal::ZERO {
return Err(MMError::InvalidMarketState(
"volatility must be positive".to_string(),
));
}
if risk_aversion <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"risk_aversion must be positive".to_string(),
));
}
let time_to_terminal_ms_dec = Decimal::from(time_to_terminal_ms);
let time_to_terminal_years =
(time_to_terminal_ms_dec * SECONDS_PER_MILLISECOND) / SECONDS_PER_YEAR;
let volatility_squared = decimal_powi(volatility, 2)?;
let adjustment = inventory * risk_aversion * volatility_squared * time_to_terminal_years;
let reservation_price = mid_price - adjustment;
Ok(reservation_price)
}
pub fn calculate_optimal_spread(
risk_aversion: Decimal,
volatility: Decimal,
time_to_terminal_ms: u64,
order_intensity: Decimal,
) -> MMResult<Decimal> {
if risk_aversion <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"risk_aversion must be positive".to_string(),
));
}
if volatility <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"volatility must be positive".to_string(),
));
}
if order_intensity <= Decimal::ZERO {
return Err(MMError::InvalidConfiguration(
"order_intensity must be positive".to_string(),
));
}
let time_to_terminal_ms_dec = Decimal::from(time_to_terminal_ms);
let time_to_terminal_years =
(time_to_terminal_ms_dec * SECONDS_PER_MILLISECOND) / SECONDS_PER_YEAR;
let volatility_squared = decimal_powi(volatility, 2)?;
let inventory_risk_term = risk_aversion * volatility_squared * time_to_terminal_years;
let two = Decimal::from(2);
let one = Decimal::ONE;
let adverse_selection_inner = one + risk_aversion / order_intensity;
let adverse_selection_ln = decimal_ln(adverse_selection_inner)?;
let adverse_selection_term = (two / risk_aversion) * adverse_selection_ln;
let spread = inventory_risk_term + adverse_selection_term;
if spread < Decimal::ZERO {
return Err(MMError::NumericalError(
"spread calculation resulted in negative value".to_string(),
));
}
Ok(spread)
}
pub fn calculate_optimal_quotes(
mid_price: Decimal,
inventory: Decimal,
risk_aversion: Decimal,
volatility: Decimal,
time_to_terminal_ms: u64,
order_intensity: Decimal,
) -> MMResult<(Decimal, Decimal)> {
let reservation_price = calculate_reservation_price(
mid_price,
inventory,
risk_aversion,
volatility,
time_to_terminal_ms,
)?;
let spread = calculate_optimal_spread(
risk_aversion,
volatility,
time_to_terminal_ms,
order_intensity,
)?;
let two = Decimal::from(2);
let half_spread = spread / two;
let bid_price = reservation_price - half_spread;
let ask_price = reservation_price + half_spread;
if bid_price >= ask_price {
return Err(MMError::InvalidQuoteGeneration(
"bid price must be less than ask price".to_string(),
));
}
if bid_price <= Decimal::ZERO {
return Err(MMError::InvalidQuoteGeneration(
"bid price must be positive".to_string(),
));
}
Ok((bid_price, ask_price))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
#[test]
fn test_reservation_price_flat_inventory() {
let result =
calculate_reservation_price(dec!(100.0), Decimal::ZERO, dec!(0.1), dec!(0.2), 3600000);
assert!(result.is_ok());
let reservation = result.unwrap();
assert!((reservation - dec!(100.0)).abs() < dec!(0.0001));
}
#[test]
fn test_reservation_price_long_inventory() {
let result =
calculate_reservation_price(dec!(100.0), dec!(10.0), dec!(0.1), dec!(0.2), 3600000);
assert!(result.is_ok());
let reservation = result.unwrap();
assert!(reservation < dec!(100.0));
}
#[test]
fn test_reservation_price_short_inventory() {
let result =
calculate_reservation_price(dec!(100.0), dec!(-10.0), dec!(0.1), dec!(0.2), 3600000);
assert!(result.is_ok());
let reservation = result.unwrap();
assert!(reservation > dec!(100.0));
}
#[test]
fn test_reservation_price_invalid_mid_price() {
let result =
calculate_reservation_price(dec!(-100.0), Decimal::ZERO, dec!(0.1), dec!(0.2), 3600000);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidMarketState(_)
));
}
#[test]
fn test_reservation_price_invalid_volatility() {
let result =
calculate_reservation_price(dec!(100.0), Decimal::ZERO, dec!(0.1), dec!(-0.2), 3600000);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidMarketState(_)
));
}
#[test]
fn test_reservation_price_invalid_risk_aversion() {
let result =
calculate_reservation_price(dec!(100.0), Decimal::ZERO, dec!(-0.1), dec!(0.2), 3600000);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidConfiguration(_)
));
}
#[test]
fn test_reservation_price_invalid_inventory() {
let result =
calculate_reservation_price(dec!(100.0), Decimal::ZERO, dec!(0.1), dec!(0.2), 3600000);
assert!(result.is_ok());
}
#[test]
fn test_reservation_price_non_finite_result() {
let result = calculate_reservation_price(
dec!(100.0),
dec!(1000000),
dec!(1000),
dec!(1000),
u64::MAX,
);
let _ = result;
}
#[test]
fn test_optimal_spread_positive() {
let result = calculate_optimal_spread(dec!(0.1), dec!(0.2), 3600000, dec!(1.5));
assert!(result.is_ok());
let spread = result.unwrap();
assert!(spread > Decimal::ZERO);
}
#[test]
fn test_optimal_spread_increases_with_volatility() {
let spread1 = calculate_optimal_spread(dec!(0.1), dec!(0.1), 3600000, dec!(1.5)).unwrap();
let spread2 = calculate_optimal_spread(dec!(0.1), dec!(0.3), 3600000, dec!(1.5)).unwrap();
assert!(spread2 > spread1);
}
#[test]
fn test_optimal_spread_invalid_risk_aversion() {
let result = calculate_optimal_spread(dec!(-0.1), dec!(0.2), 3600000, dec!(1.5));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidConfiguration(_)
));
}
#[test]
fn test_optimal_spread_invalid_volatility() {
let result = calculate_optimal_spread(dec!(0.1), dec!(-0.2), 3600000, dec!(1.5));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidConfiguration(_)
));
}
#[test]
fn test_optimal_spread_invalid_order_intensity() {
let result = calculate_optimal_spread(dec!(0.1), dec!(0.2), 3600000, dec!(-1.5));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidConfiguration(_)
));
}
#[test]
fn test_optimal_spread_non_finite_result() {
let result = calculate_optimal_spread(
dec!(0.1),
Decimal::from_parts(u32::MAX, 0, 0, false, 10),
3600000,
dec!(1.5),
);
let _ = result;
}
#[test]
fn test_optimal_spread_negative_result() {
let result = calculate_optimal_spread(
dec!(0.0001),
dec!(0.0001),
1, dec!(1000000), );
if let Ok(spread) = result {
assert!(spread >= Decimal::ZERO);
}
}
#[test]
fn test_optimal_quotes_valid() {
let result = calculate_optimal_quotes(
dec!(100.0),
Decimal::ZERO,
dec!(0.1),
dec!(0.2),
3600000,
dec!(1.5),
);
assert!(result.is_ok());
let (bid, ask) = result.unwrap();
assert!(bid < ask);
assert!(bid < dec!(100.0));
assert!(ask > dec!(100.0));
assert!(bid > Decimal::ZERO);
}
#[test]
fn test_optimal_quotes_with_positive_inventory() {
let (bid_flat, ask_flat) = calculate_optimal_quotes(
dec!(100.0),
Decimal::ZERO,
dec!(0.1),
dec!(0.2),
3600000,
dec!(1.5),
)
.unwrap();
let (bid_long, ask_long) = calculate_optimal_quotes(
dec!(100.0),
dec!(10.0),
dec!(0.1),
dec!(0.2),
3600000,
dec!(1.5),
)
.unwrap();
assert!(bid_long < bid_flat);
assert!(ask_long < ask_flat);
}
#[test]
fn test_optimal_quotes_with_negative_inventory() {
let (bid_flat, ask_flat) = calculate_optimal_quotes(
dec!(100.0),
Decimal::ZERO,
dec!(0.1),
dec!(0.2),
3600000,
dec!(1.5),
)
.unwrap();
let (bid_short, ask_short) = calculate_optimal_quotes(
dec!(100.0),
dec!(-10.0),
dec!(0.1),
dec!(0.2),
3600000,
dec!(1.5),
)
.unwrap();
assert!(bid_short > bid_flat);
assert!(ask_short > ask_flat);
}
#[test]
fn test_optimal_quotes_spread_positive() {
let (bid, ask) = calculate_optimal_quotes(
dec!(100.0),
Decimal::ZERO,
dec!(0.1),
dec!(0.2),
3600000,
dec!(1.5),
)
.unwrap();
assert!(ask - bid > Decimal::ZERO);
}
#[test]
fn test_optimal_quotes_negative_bid_error() {
let result = calculate_optimal_quotes(
dec!(0.5),
dec!(-1000.0),
dec!(1.0),
dec!(1.0),
36000000,
dec!(0.1),
);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
MMError::InvalidQuoteGeneration(_)
));
}
#[test]
fn test_optimal_quotes_bid_exceeds_ask_error() {
let result = calculate_optimal_quotes(
dec!(100.0),
Decimal::from_parts(u32::MAX, u32::MAX, 0, false, 0),
dec!(0.0000000001),
dec!(0.001),
1,
Decimal::from_parts(u32::MAX, u32::MAX, 0, false, 0),
);
if let Err(err) = result {
assert!(matches!(
err,
MMError::InvalidQuoteGeneration(_)
| MMError::InvalidMarketState(_)
| MMError::InvalidConfiguration(_)
| MMError::NumericalError(_)
));
}
}
}