1use async_trait::async_trait;
6use ccxt_core::{
7 Result,
8 exchange::{Exchange, ExchangeCapabilities},
9 types::{
10 Amount, Balance, Market, Ohlcv, Order, OrderBook, OrderSide, OrderType, Price, Ticker,
11 Timeframe, Trade,
12 },
13};
14use rust_decimal::Decimal;
15use std::collections::HashMap;
16
17use super::Okx;
18
19#[async_trait]
20impl Exchange for Okx {
21 fn id(&self) -> &str {
24 "okx"
25 }
26
27 fn name(&self) -> &str {
28 "OKX"
29 }
30
31 fn version(&self) -> &'static str {
32 "v5"
33 }
34
35 fn certified(&self) -> bool {
36 false
37 }
38
39 fn has_websocket(&self) -> bool {
40 true
41 }
42
43 fn capabilities(&self) -> ExchangeCapabilities {
44 ExchangeCapabilities {
45 fetch_markets: true,
47 fetch_currencies: false,
48 fetch_ticker: true,
49 fetch_tickers: true,
50 fetch_order_book: true,
51 fetch_trades: true,
52 fetch_ohlcv: true,
53 fetch_status: false,
54 fetch_time: false,
55
56 create_order: true,
58 create_market_order: true,
59 create_limit_order: true,
60 cancel_order: true,
61 cancel_all_orders: false, edit_order: false,
63 fetch_order: true,
64 fetch_orders: false,
65 fetch_open_orders: true,
66 fetch_closed_orders: true,
67 fetch_canceled_orders: false,
68
69 fetch_balance: true,
71 fetch_my_trades: true,
72 fetch_deposits: false,
73 fetch_withdrawals: false,
74 fetch_transactions: false,
75 fetch_ledger: false,
76
77 fetch_deposit_address: false,
79 create_deposit_address: false,
80 withdraw: false,
81 transfer: false,
82
83 fetch_borrow_rate: false,
85 fetch_borrow_rates: false,
86 fetch_funding_rate: false,
87 fetch_funding_rates: false,
88 fetch_positions: false,
89 set_leverage: false,
90 set_margin_mode: false,
91
92 websocket: true,
94 watch_ticker: true,
95 watch_tickers: false,
96 watch_order_book: true,
97 watch_trades: true,
98 watch_ohlcv: false,
99 watch_balance: false,
100 watch_orders: false,
101 watch_my_trades: false,
102 }
103 }
104
105 fn timeframes(&self) -> Vec<Timeframe> {
106 vec![
107 Timeframe::M1,
108 Timeframe::M3,
109 Timeframe::M5,
110 Timeframe::M15,
111 Timeframe::M30,
112 Timeframe::H1,
113 Timeframe::H2,
114 Timeframe::H4,
115 Timeframe::H6,
116 Timeframe::H12,
117 Timeframe::D1,
118 Timeframe::W1,
119 Timeframe::Mon1,
120 ]
121 }
122
123 fn rate_limit(&self) -> f64 {
124 20.0
125 }
126
127 async fn fetch_markets(&self) -> Result<Vec<Market>> {
130 Okx::fetch_markets(self).await
131 }
132
133 async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Market>> {
134 Okx::load_markets(self, reload).await
135 }
136
137 async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
138 Okx::fetch_ticker(self, symbol).await
139 }
140
141 async fn fetch_tickers(&self, symbols: Option<&[String]>) -> Result<Vec<Ticker>> {
142 let symbols_vec = symbols.map(|s| s.to_vec());
143 Okx::fetch_tickers(self, symbols_vec).await
144 }
145
146 async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
147 Okx::fetch_order_book(self, symbol, limit).await
148 }
149
150 async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
151 Okx::fetch_trades(self, symbol, limit).await
152 }
153
154 async fn fetch_ohlcv(
155 &self,
156 symbol: &str,
157 timeframe: Timeframe,
158 since: Option<i64>,
159 limit: Option<u32>,
160 ) -> Result<Vec<Ohlcv>> {
161 let timeframe_str = timeframe.to_string();
162 let ohlcv_data = Okx::fetch_ohlcv(self, symbol, &timeframe_str, since, limit).await?;
163
164 Ok(ohlcv_data
166 .into_iter()
167 .map(|o| Ohlcv {
168 timestamp: o.timestamp,
169 open: Price::from(Decimal::try_from(o.open).unwrap_or_default()),
170 high: Price::from(Decimal::try_from(o.high).unwrap_or_default()),
171 low: Price::from(Decimal::try_from(o.low).unwrap_or_default()),
172 close: Price::from(Decimal::try_from(o.close).unwrap_or_default()),
173 volume: Amount::from(Decimal::try_from(o.volume).unwrap_or_default()),
174 })
175 .collect())
176 }
177
178 async fn create_order(
181 &self,
182 symbol: &str,
183 order_type: OrderType,
184 side: OrderSide,
185 amount: Decimal,
186 price: Option<Decimal>,
187 ) -> Result<Order> {
188 let amount_f64 = amount.to_string().parse::<f64>().unwrap_or(0.0);
189 let price_f64 = price.map(|p| p.to_string().parse::<f64>().unwrap_or(0.0));
190
191 Okx::create_order(self, symbol, order_type, side, amount_f64, price_f64).await
192 }
193
194 async fn cancel_order(&self, id: &str, symbol: Option<&str>) -> Result<Order> {
195 let symbol_str = symbol.ok_or_else(|| {
196 ccxt_core::Error::invalid_request("Symbol is required for cancel_order on OKX")
197 })?;
198 Okx::cancel_order(self, id, symbol_str).await
199 }
200
201 async fn cancel_all_orders(&self, _symbol: Option<&str>) -> Result<Vec<Order>> {
202 Err(ccxt_core::Error::not_implemented("cancel_all_orders"))
203 }
204
205 async fn fetch_order(&self, id: &str, symbol: Option<&str>) -> Result<Order> {
206 let symbol_str = symbol.ok_or_else(|| {
207 ccxt_core::Error::invalid_request("Symbol is required for fetch_order on OKX")
208 })?;
209 Okx::fetch_order(self, id, symbol_str).await
210 }
211
212 async fn fetch_open_orders(
213 &self,
214 symbol: Option<&str>,
215 since: Option<i64>,
216 limit: Option<u32>,
217 ) -> Result<Vec<Order>> {
218 Okx::fetch_open_orders(self, symbol, since, limit).await
219 }
220
221 async fn fetch_closed_orders(
222 &self,
223 symbol: Option<&str>,
224 since: Option<i64>,
225 limit: Option<u32>,
226 ) -> Result<Vec<Order>> {
227 Okx::fetch_closed_orders(self, symbol, since, limit).await
228 }
229
230 async fn fetch_balance(&self) -> Result<Balance> {
233 Okx::fetch_balance(self).await
234 }
235
236 async fn fetch_my_trades(
237 &self,
238 symbol: Option<&str>,
239 since: Option<i64>,
240 limit: Option<u32>,
241 ) -> Result<Vec<Trade>> {
242 let symbol_str = symbol.ok_or_else(|| {
243 ccxt_core::Error::invalid_request("Symbol is required for fetch_my_trades on OKX")
244 })?;
245 Okx::fetch_my_trades(self, symbol_str, since, limit).await
246 }
247
248 async fn market(&self, symbol: &str) -> Result<Market> {
251 let cache = self.base().market_cache.read().await;
252
253 if !cache.loaded {
254 return Err(ccxt_core::Error::exchange(
255 "-1",
256 "Markets not loaded. Call load_markets() first.",
257 ));
258 }
259
260 cache
261 .markets
262 .get(symbol)
263 .cloned()
264 .ok_or_else(|| ccxt_core::Error::bad_symbol(format!("Market {} not found", symbol)))
265 }
266
267 async fn markets(&self) -> HashMap<String, Market> {
268 let cache = self.base().market_cache.read().await;
269 cache.markets.clone()
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use ccxt_core::ExchangeConfig;
277
278 #[test]
279 fn test_okx_exchange_trait_metadata() {
280 let config = ExchangeConfig::default();
281 let okx = Okx::new(config).unwrap();
282
283 let exchange: &dyn Exchange = &okx;
285
286 assert_eq!(exchange.id(), "okx");
287 assert_eq!(exchange.name(), "OKX");
288 assert_eq!(exchange.version(), "v5");
289 assert!(!exchange.certified());
290 assert!(exchange.has_websocket());
291 }
292
293 #[test]
294 fn test_okx_exchange_trait_capabilities() {
295 let config = ExchangeConfig::default();
296 let okx = Okx::new(config).unwrap();
297
298 let exchange: &dyn Exchange = &okx;
299 let caps = exchange.capabilities();
300
301 assert!(caps.fetch_markets);
303 assert!(caps.fetch_ticker);
304 assert!(caps.fetch_tickers);
305 assert!(caps.fetch_order_book);
306 assert!(caps.fetch_trades);
307 assert!(caps.fetch_ohlcv);
308
309 assert!(caps.create_order);
311 assert!(caps.cancel_order);
312 assert!(caps.fetch_order);
313 assert!(caps.fetch_open_orders);
314 assert!(caps.fetch_closed_orders);
315 assert!(caps.fetch_balance);
316 assert!(caps.fetch_my_trades);
317
318 assert!(caps.websocket);
320 assert!(caps.watch_ticker);
321 assert!(caps.watch_order_book);
322 assert!(caps.watch_trades);
323
324 assert!(!caps.edit_order);
326 assert!(!caps.cancel_all_orders);
327 assert!(!caps.fetch_currencies);
328 }
329
330 #[test]
331 fn test_okx_exchange_trait_timeframes() {
332 let config = ExchangeConfig::default();
333 let okx = Okx::new(config).unwrap();
334
335 let exchange: &dyn Exchange = &okx;
336 let timeframes = exchange.timeframes();
337
338 assert!(!timeframes.is_empty());
339 assert!(timeframes.contains(&Timeframe::M1));
340 assert!(timeframes.contains(&Timeframe::M3));
341 assert!(timeframes.contains(&Timeframe::M5));
342 assert!(timeframes.contains(&Timeframe::M15));
343 assert!(timeframes.contains(&Timeframe::H1));
344 assert!(timeframes.contains(&Timeframe::H4));
345 assert!(timeframes.contains(&Timeframe::D1));
346 assert!(timeframes.contains(&Timeframe::W1));
347 assert!(timeframes.contains(&Timeframe::Mon1));
348 }
349
350 #[test]
351 fn test_okx_exchange_trait_rate_limit() {
352 let config = ExchangeConfig::default();
353 let okx = Okx::new(config).unwrap();
354
355 let exchange: &dyn Exchange = &okx;
356 assert!((exchange.rate_limit() - 20.0).abs() < f64::EPSILON);
357 }
358
359 #[test]
360 fn test_okx_exchange_trait_object_safety() {
361 let config = ExchangeConfig::default();
362 let okx = Okx::new(config).unwrap();
363
364 let exchange: Box<dyn Exchange> = Box::new(okx);
366
367 assert_eq!(exchange.id(), "okx");
368 assert_eq!(exchange.name(), "OKX");
369 assert!((exchange.rate_limit() - 20.0).abs() < f64::EPSILON);
370 }
371
372 #[test]
373 fn test_okx_exchange_trait_polymorphic_usage() {
374 let config = ExchangeConfig::default();
375 let okx = Okx::new(config).unwrap();
376
377 fn check_exchange_metadata(exchange: &dyn Exchange) -> (&str, &str, bool) {
379 (exchange.id(), exchange.name(), exchange.has_websocket())
380 }
381
382 let (id, name, has_ws) = check_exchange_metadata(&okx);
383 assert_eq!(id, "okx");
384 assert_eq!(name, "OKX");
385 assert!(has_ws);
386 }
387
388 #[test]
389 fn test_okx_capabilities_has_method() {
390 let config = ExchangeConfig::default();
391 let okx = Okx::new(config).unwrap();
392
393 let exchange: &dyn Exchange = &okx;
394 let caps = exchange.capabilities();
395
396 assert!(caps.has("fetchMarkets"));
398 assert!(caps.has("fetchTicker"));
399 assert!(caps.has("fetchTickers"));
400 assert!(caps.has("fetchOrderBook"));
401 assert!(caps.has("fetchTrades"));
402 assert!(caps.has("fetchOHLCV"));
403 assert!(caps.has("createOrder"));
404 assert!(caps.has("cancelOrder"));
405 assert!(caps.has("fetchOrder"));
406 assert!(caps.has("fetchOpenOrders"));
407 assert!(caps.has("fetchClosedOrders"));
408 assert!(caps.has("fetchBalance"));
409 assert!(caps.has("fetchMyTrades"));
410 assert!(caps.has("websocket"));
411
412 assert!(!caps.has("editOrder"));
414 assert!(!caps.has("cancelAllOrders"));
415 assert!(!caps.has("fetchCurrencies"));
416 assert!(!caps.has("unknownCapability"));
417 }
418}