use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MarketType {
Spot,
UsdMFutures,
CoinMFutures,
Margin,
}
impl MarketType {
#[must_use]
pub fn is_derivatives(self) -> bool {
matches!(self, MarketType::UsdMFutures | MarketType::CoinMFutures)
}
#[must_use]
pub fn describe(self) -> &'static str {
match self {
MarketType::Spot => "the spot market",
MarketType::UsdMFutures => "USD-margined (linear) futures",
MarketType::CoinMFutures => "coin-margined (inverse) futures",
MarketType::Margin => "the margin market",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PositionMode {
OneWay,
Hedge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MarginMode {
Cross,
Isolated,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SelfTradePrevention {
#[default]
None,
ExpireMaker,
ExpireTaker,
ExpireBoth,
}
#[derive(Debug, Clone)]
pub struct ExchangeOptions {
pub market_type: MarketType,
pub testnet: bool,
pub recv_window_ms: u64,
pub timeout: Duration,
pub user_agent: Option<String>,
pub proxy: Option<String>,
pub position_mode: PositionMode,
pub margin_mode: MarginMode,
}
impl ExchangeOptions {
#[must_use]
pub fn mainnet(market_type: MarketType) -> Self {
Self {
market_type,
testnet: false,
recv_window_ms: 5_000,
timeout: Duration::from_secs(10),
user_agent: None,
proxy: None,
position_mode: PositionMode::OneWay,
margin_mode: MarginMode::Cross,
}
}
#[must_use]
pub fn testnet(market_type: MarketType) -> Self {
Self {
testnet: true,
..Self::mainnet(market_type)
}
}
#[must_use]
pub fn with_recv_window(mut self, ms: u64) -> Self {
self.recv_window_ms = ms;
self
}
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[must_use]
pub fn with_position_mode(mut self, mode: PositionMode) -> Self {
self.position_mode = mode;
self
}
#[must_use]
pub fn with_margin_mode(mut self, mode: MarginMode) -> Self {
self.margin_mode = mode;
self
}
}
impl Default for ExchangeOptions {
fn default() -> Self {
Self::mainnet(MarketType::Spot)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derivatives_classification() {
assert!(MarketType::UsdMFutures.is_derivatives());
assert!(MarketType::CoinMFutures.is_derivatives());
assert!(!MarketType::Spot.is_derivatives());
assert!(!MarketType::Margin.is_derivatives());
}
#[test]
fn testnet_flips_only_the_flag() {
let main = ExchangeOptions::mainnet(MarketType::Spot);
let test = ExchangeOptions::testnet(MarketType::Spot);
assert!(!main.testnet);
assert!(test.testnet);
assert_eq!(main.recv_window_ms, test.recv_window_ms);
assert_eq!(main.timeout, test.timeout);
}
#[test]
fn builders_override_fields() {
let opts = ExchangeOptions::mainnet(MarketType::UsdMFutures)
.with_recv_window(10_000)
.with_timeout(Duration::from_secs(30))
.with_position_mode(PositionMode::Hedge)
.with_margin_mode(MarginMode::Isolated);
assert_eq!(opts.recv_window_ms, 10_000);
assert_eq!(opts.timeout, Duration::from_secs(30));
assert_eq!(opts.position_mode, PositionMode::Hedge);
assert_eq!(opts.margin_mode, MarginMode::Isolated);
}
#[test]
fn default_is_mainnet_spot() {
let opts = ExchangeOptions::default();
assert_eq!(opts.market_type, MarketType::Spot);
assert!(!opts.testnet);
}
}