wickra_exchange_core/
options.rs1use serde::{Deserialize, Serialize};
10use std::time::Duration;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum MarketType {
15 Spot,
17 UsdMFutures,
19 CoinMFutures,
21 Margin,
23}
24
25impl MarketType {
26 #[must_use]
29 pub fn is_derivatives(self) -> bool {
30 matches!(self, MarketType::UsdMFutures | MarketType::CoinMFutures)
31 }
32
33 #[must_use]
35 pub fn describe(self) -> &'static str {
36 match self {
37 MarketType::Spot => "the spot market",
38 MarketType::UsdMFutures => "USD-margined (linear) futures",
39 MarketType::CoinMFutures => "coin-margined (inverse) futures",
40 MarketType::Margin => "the margin market",
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47pub enum PositionMode {
48 OneWay,
50 Hedge,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub enum MarginMode {
57 Cross,
59 Isolated,
61}
62
63#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum SelfTradePrevention {
67 #[default]
69 None,
70 ExpireMaker,
72 ExpireTaker,
74 ExpireBoth,
76}
77
78#[derive(Debug, Clone)]
80pub struct ExchangeOptions {
81 pub market_type: MarketType,
83 pub testnet: bool,
85 pub recv_window_ms: u64,
88 pub timeout: Duration,
90 pub user_agent: Option<String>,
92 pub proxy: Option<String>,
94 pub position_mode: PositionMode,
96 pub margin_mode: MarginMode,
98}
99
100impl ExchangeOptions {
101 #[must_use]
103 pub fn mainnet(market_type: MarketType) -> Self {
104 Self {
105 market_type,
106 testnet: false,
107 recv_window_ms: 5_000,
108 timeout: Duration::from_secs(10),
109 user_agent: None,
110 proxy: None,
111 position_mode: PositionMode::OneWay,
112 margin_mode: MarginMode::Cross,
113 }
114 }
115
116 #[must_use]
118 pub fn testnet(market_type: MarketType) -> Self {
119 Self {
120 testnet: true,
121 ..Self::mainnet(market_type)
122 }
123 }
124
125 #[must_use]
127 pub fn with_recv_window(mut self, ms: u64) -> Self {
128 self.recv_window_ms = ms;
129 self
130 }
131
132 #[must_use]
134 pub fn with_timeout(mut self, timeout: Duration) -> Self {
135 self.timeout = timeout;
136 self
137 }
138
139 #[must_use]
141 pub fn with_position_mode(mut self, mode: PositionMode) -> Self {
142 self.position_mode = mode;
143 self
144 }
145
146 #[must_use]
148 pub fn with_margin_mode(mut self, mode: MarginMode) -> Self {
149 self.margin_mode = mode;
150 self
151 }
152}
153
154impl Default for ExchangeOptions {
155 fn default() -> Self {
157 Self::mainnet(MarketType::Spot)
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn derivatives_classification() {
167 assert!(MarketType::UsdMFutures.is_derivatives());
168 assert!(MarketType::CoinMFutures.is_derivatives());
169 assert!(!MarketType::Spot.is_derivatives());
170 assert!(!MarketType::Margin.is_derivatives());
171 }
172
173 #[test]
174 fn testnet_flips_only_the_flag() {
175 let main = ExchangeOptions::mainnet(MarketType::Spot);
176 let test = ExchangeOptions::testnet(MarketType::Spot);
177 assert!(!main.testnet);
178 assert!(test.testnet);
179 assert_eq!(main.recv_window_ms, test.recv_window_ms);
180 assert_eq!(main.timeout, test.timeout);
181 }
182
183 #[test]
184 fn builders_override_fields() {
185 let opts = ExchangeOptions::mainnet(MarketType::UsdMFutures)
186 .with_recv_window(10_000)
187 .with_timeout(Duration::from_secs(30))
188 .with_position_mode(PositionMode::Hedge)
189 .with_margin_mode(MarginMode::Isolated);
190 assert_eq!(opts.recv_window_ms, 10_000);
191 assert_eq!(opts.timeout, Duration::from_secs(30));
192 assert_eq!(opts.position_mode, PositionMode::Hedge);
193 assert_eq!(opts.margin_mode, MarginMode::Isolated);
194 }
195
196 #[test]
197 fn default_is_mainnet_spot() {
198 let opts = ExchangeOptions::default();
199 assert_eq!(opts.market_type, MarketType::Spot);
200 assert!(!opts.testnet);
201 }
202}