1use super::{Okx, OkxAuth, error, parser};
6use ccxt_core::{
7 Error, ParseError, Result,
8 types::{Balance, Market, OHLCV, Order, OrderBook, OrderSide, OrderType, Ticker, Trade},
9};
10use reqwest::header::{HeaderMap, HeaderValue};
11use serde_json::Value;
12use std::{collections::HashMap, sync::Arc};
13use tracing::{debug, info, warn};
14
15impl Okx {
16 fn get_timestamp(&self) -> String {
22 chrono::Utc::now()
23 .format("%Y-%m-%dT%H:%M:%S%.3fZ")
24 .to_string()
25 }
26
27 fn get_auth(&self) -> Result<OkxAuth> {
29 let config = &self.base().config;
30
31 let api_key = config
32 .api_key
33 .as_ref()
34 .ok_or_else(|| Error::authentication("API key is required"))?;
35 let secret = config
36 .secret
37 .as_ref()
38 .ok_or_else(|| Error::authentication("API secret is required"))?;
39 let passphrase = config
40 .password
41 .as_ref()
42 .ok_or_else(|| Error::authentication("Passphrase is required"))?;
43
44 Ok(OkxAuth::new(
45 api_key.clone(),
46 secret.clone(),
47 passphrase.clone(),
48 ))
49 }
50
51 pub fn check_required_credentials(&self) -> Result<()> {
53 self.base().check_required_credentials()?;
54 if self.base().config.password.is_none() {
55 return Err(Error::authentication("Passphrase is required for OKX"));
56 }
57 Ok(())
58 }
59
60 fn build_api_path(&self, endpoint: &str) -> String {
62 format!("/api/v5{}", endpoint)
63 }
64
65 pub fn get_inst_type(&self) -> &str {
79 use ccxt_core::types::default_type::DefaultType;
80
81 match self.options().default_type {
82 DefaultType::Spot => "SPOT",
83 DefaultType::Margin => "MARGIN",
84 DefaultType::Swap => "SWAP",
85 DefaultType::Futures => "FUTURES",
86 DefaultType::Option => "OPTION",
87 }
88 }
89
90 async fn public_request(
92 &self,
93 method: &str,
94 path: &str,
95 params: Option<&HashMap<String, String>>,
96 ) -> Result<Value> {
97 let urls = self.urls();
98 let mut url = format!("{}{}", urls.rest, path);
99
100 if let Some(p) = params {
101 if !p.is_empty() {
102 let query: Vec<String> = p
103 .iter()
104 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
105 .collect();
106 url = format!("{}?{}", url, query.join("&"));
107 }
108 }
109
110 debug!("OKX public request: {} {}", method, url);
111
112 let mut headers = HeaderMap::new();
114 if self.is_testnet_trading() {
115 headers.insert("x-simulated-trading", HeaderValue::from_static("1"));
116 }
117
118 let response = match method.to_uppercase().as_str() {
119 "GET" => {
120 if headers.is_empty() {
121 self.base().http_client.get(&url, None).await?
122 } else {
123 self.base().http_client.get(&url, Some(headers)).await?
124 }
125 }
126 "POST" => {
127 if headers.is_empty() {
128 self.base().http_client.post(&url, None, None).await?
129 } else {
130 self.base()
131 .http_client
132 .post(&url, Some(headers), None)
133 .await?
134 }
135 }
136 _ => {
137 return Err(Error::invalid_request(format!(
138 "Unsupported HTTP method: {}",
139 method
140 )));
141 }
142 };
143
144 if error::is_error_response(&response) {
146 return Err(error::parse_error(&response));
147 }
148
149 Ok(response)
150 }
151
152 async fn private_request(
154 &self,
155 method: &str,
156 path: &str,
157 params: Option<&HashMap<String, String>>,
158 body: Option<&Value>,
159 ) -> Result<Value> {
160 self.check_required_credentials()?;
161
162 let auth = self.get_auth()?;
163 let urls = self.urls();
164 let timestamp = self.get_timestamp();
165
166 let query_string = if let Some(p) = params {
168 if !p.is_empty() {
169 let query: Vec<String> = p
170 .iter()
171 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
172 .collect();
173 format!("?{}", query.join("&"))
174 } else {
175 String::new()
176 }
177 } else {
178 String::new()
179 };
180
181 let body_string = body
183 .map(|b| serde_json::to_string(b).unwrap_or_default())
184 .unwrap_or_default();
185
186 let sign_path = format!("{}{}", path, query_string);
188 let signature = auth.sign(×tamp, method, &sign_path, &body_string);
189
190 let mut headers = HeaderMap::new();
192 auth.add_auth_headers(&mut headers, ×tamp, &signature);
193 headers.insert("Content-Type", HeaderValue::from_static("application/json"));
194
195 if self.is_testnet_trading() {
197 headers.insert("x-simulated-trading", HeaderValue::from_static("1"));
198 }
199
200 let url = format!("{}{}{}", urls.rest, path, query_string);
201 debug!("OKX private request: {} {}", method, url);
202
203 let response = match method.to_uppercase().as_str() {
204 "GET" => self.base().http_client.get(&url, Some(headers)).await?,
205 "POST" => {
206 let body_value = body.cloned();
207 self.base()
208 .http_client
209 .post(&url, Some(headers), body_value)
210 .await?
211 }
212 "DELETE" => {
213 self.base()
214 .http_client
215 .delete(&url, Some(headers), None)
216 .await?
217 }
218 _ => {
219 return Err(Error::invalid_request(format!(
220 "Unsupported HTTP method: {}",
221 method
222 )));
223 }
224 };
225
226 if error::is_error_response(&response) {
228 return Err(error::parse_error(&response));
229 }
230
231 Ok(response)
232 }
233
234 pub async fn fetch_markets(&self) -> Result<HashMap<String, Arc<Market>>> {
260 let path = self.build_api_path("/public/instruments");
261 let mut params = HashMap::new();
262 params.insert("instType".to_string(), self.get_inst_type().to_string());
263
264 let response = self.public_request("GET", &path, Some(¶ms)).await?;
265
266 let data = response
267 .get("data")
268 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
269
270 let instruments = data.as_array().ok_or_else(|| {
271 Error::from(ParseError::invalid_format(
272 "data",
273 "Expected array of instruments",
274 ))
275 })?;
276
277 let mut markets = Vec::new();
278 for instrument in instruments {
279 match parser::parse_market(instrument) {
280 Ok(market) => markets.push(market),
281 Err(e) => {
282 warn!(error = %e, "Failed to parse market");
283 }
284 }
285 }
286
287 let result = self.base().set_markets(markets, None).await?;
289
290 info!("Loaded {} markets for OKX", result.len());
291 Ok(result)
292 }
293
294 pub async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Arc<Market>>> {
306 let _loading_guard = self.base().market_loading_lock.lock().await;
309
310 {
312 let cache = self.base().market_cache.read().await;
313 if cache.loaded && !reload {
314 debug!(
315 "Returning cached markets for OKX ({} markets)",
316 cache.markets.len()
317 );
318 return Ok(cache
319 .markets
320 .iter()
321 .map(|(k, v)| (k.clone(), Arc::clone(v)))
322 .collect());
323 }
324 }
325
326 info!("Loading markets for OKX (reload: {})", reload);
327 let _markets = self.fetch_markets().await?;
328
329 let cache = self.base().market_cache.read().await;
330 Ok(cache
331 .markets
332 .iter()
333 .map(|(k, v)| (k.clone(), Arc::clone(v)))
334 .collect())
335 }
336
337 pub async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
347 let market = self.base().market(symbol).await?;
348
349 let path = self.build_api_path("/market/ticker");
350 let mut params = HashMap::new();
351 params.insert("instId".to_string(), market.id.clone());
352
353 let response = self.public_request("GET", &path, Some(¶ms)).await?;
354
355 let data = response
356 .get("data")
357 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
358
359 let tickers = data.as_array().ok_or_else(|| {
361 Error::from(ParseError::invalid_format(
362 "data",
363 "Expected array of tickers",
364 ))
365 })?;
366
367 if tickers.is_empty() {
368 return Err(Error::bad_symbol(format!("No ticker data for {}", symbol)));
369 }
370
371 parser::parse_ticker(&tickers[0], Some(&market))
372 }
373
374 pub async fn fetch_tickers(&self, symbols: Option<Vec<String>>) -> Result<Vec<Ticker>> {
384 let cache = self.base().market_cache.read().await;
385 if !cache.loaded {
386 drop(cache);
387 return Err(Error::exchange(
388 "-1",
389 "Markets not loaded. Call load_markets() first.",
390 ));
391 }
392 drop(cache);
393
394 let path = self.build_api_path("/market/tickers");
395 let mut params = HashMap::new();
396 params.insert("instType".to_string(), self.get_inst_type().to_string());
397
398 let response = self.public_request("GET", &path, Some(¶ms)).await?;
399
400 let data = response
401 .get("data")
402 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
403
404 let tickers_array = data.as_array().ok_or_else(|| {
405 Error::from(ParseError::invalid_format(
406 "data",
407 "Expected array of tickers",
408 ))
409 })?;
410
411 let mut tickers = Vec::new();
412 for ticker_data in tickers_array {
413 if let Some(inst_id) = ticker_data["instId"].as_str() {
414 let cache = self.base().market_cache.read().await;
415 if let Some(market) = cache.markets_by_id.get(inst_id) {
416 let market_clone = market.clone();
417 drop(cache);
418
419 match parser::parse_ticker(ticker_data, Some(&market_clone)) {
420 Ok(ticker) => {
421 if let Some(ref syms) = symbols {
422 if syms.contains(&ticker.symbol) {
423 tickers.push(ticker);
424 }
425 } else {
426 tickers.push(ticker);
427 }
428 }
429 Err(e) => {
430 warn!(
431 error = %e,
432 symbol = %inst_id,
433 "Failed to parse ticker"
434 );
435 }
436 }
437 } else {
438 drop(cache);
439 }
440 }
441 }
442
443 Ok(tickers)
444 }
445
446 pub async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
461 let market = self.base().market(symbol).await?;
462
463 let path = self.build_api_path("/market/books");
464 let mut params = HashMap::new();
465 params.insert("instId".to_string(), market.id.clone());
466
467 let actual_limit = limit.map(|l| l.min(400)).unwrap_or(100);
470 params.insert("sz".to_string(), actual_limit.to_string());
471
472 let response = self.public_request("GET", &path, Some(¶ms)).await?;
473
474 let data = response
475 .get("data")
476 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
477
478 let books = data.as_array().ok_or_else(|| {
480 Error::from(ParseError::invalid_format(
481 "data",
482 "Expected array of orderbooks",
483 ))
484 })?;
485
486 if books.is_empty() {
487 return Err(Error::bad_symbol(format!(
488 "No orderbook data for {}",
489 symbol
490 )));
491 }
492
493 parser::parse_orderbook(&books[0], market.symbol.clone())
494 }
495
496 pub async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
507 let market = self.base().market(symbol).await?;
508
509 let path = self.build_api_path("/market/trades");
510 let mut params = HashMap::new();
511 params.insert("instId".to_string(), market.id.clone());
512
513 let actual_limit = limit.map(|l| l.min(500)).unwrap_or(100);
515 params.insert("limit".to_string(), actual_limit.to_string());
516
517 let response = self.public_request("GET", &path, Some(¶ms)).await?;
518
519 let data = response
520 .get("data")
521 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
522
523 let trades_array = data.as_array().ok_or_else(|| {
524 Error::from(ParseError::invalid_format(
525 "data",
526 "Expected array of trades",
527 ))
528 })?;
529
530 let mut trades = Vec::new();
531 for trade_data in trades_array {
532 match parser::parse_trade(trade_data, Some(&market)) {
533 Ok(trade) => trades.push(trade),
534 Err(e) => {
535 warn!(error = %e, "Failed to parse trade");
536 }
537 }
538 }
539
540 trades.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
542
543 Ok(trades)
544 }
545
546 pub async fn fetch_ohlcv(
559 &self,
560 symbol: &str,
561 timeframe: &str,
562 since: Option<i64>,
563 limit: Option<u32>,
564 ) -> Result<Vec<OHLCV>> {
565 let market = self.base().market(symbol).await?;
566
567 let timeframes = self.timeframes();
569 let okx_timeframe = timeframes.get(timeframe).ok_or_else(|| {
570 Error::invalid_request(format!("Unsupported timeframe: {}", timeframe))
571 })?;
572
573 let path = self.build_api_path("/market/candles");
574 let mut params = HashMap::new();
575 params.insert("instId".to_string(), market.id.clone());
576 params.insert("bar".to_string(), okx_timeframe.clone());
577
578 let actual_limit = limit.map(|l| l.min(300)).unwrap_or(100);
580 params.insert("limit".to_string(), actual_limit.to_string());
581
582 if let Some(start_time) = since {
583 params.insert("after".to_string(), start_time.to_string());
584 }
585
586 let response = self.public_request("GET", &path, Some(¶ms)).await?;
587
588 let data = response
589 .get("data")
590 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
591
592 let candles_array = data.as_array().ok_or_else(|| {
593 Error::from(ParseError::invalid_format(
594 "data",
595 "Expected array of candles",
596 ))
597 })?;
598
599 let mut ohlcv = Vec::new();
600 for candle_data in candles_array {
601 match parser::parse_ohlcv(candle_data) {
602 Ok(candle) => ohlcv.push(candle),
603 Err(e) => {
604 warn!(error = %e, "Failed to parse OHLCV");
605 }
606 }
607 }
608
609 ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
611
612 Ok(ohlcv)
613 }
614
615 pub async fn fetch_balance(&self) -> Result<Balance> {
629 let path = self.build_api_path("/account/balance");
630 let response = self.private_request("GET", &path, None, None).await?;
631
632 let data = response
633 .get("data")
634 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
635
636 let balances = data.as_array().ok_or_else(|| {
638 Error::from(ParseError::invalid_format(
639 "data",
640 "Expected array of balances",
641 ))
642 })?;
643
644 if balances.is_empty() {
645 return Ok(Balance {
646 balances: HashMap::new(),
647 info: HashMap::new(),
648 });
649 }
650
651 parser::parse_balance(&balances[0])
652 }
653
654 pub async fn fetch_my_trades(
666 &self,
667 symbol: &str,
668 since: Option<i64>,
669 limit: Option<u32>,
670 ) -> Result<Vec<Trade>> {
671 let market = self.base().market(symbol).await?;
672
673 let path = self.build_api_path("/trade/fills");
674 let mut params = HashMap::new();
675 params.insert("instId".to_string(), market.id.clone());
676 params.insert("instType".to_string(), self.get_inst_type().to_string());
677
678 let actual_limit = limit.map(|l| l.min(100)).unwrap_or(100);
680 params.insert("limit".to_string(), actual_limit.to_string());
681
682 if let Some(start_time) = since {
683 params.insert("begin".to_string(), start_time.to_string());
684 }
685
686 let response = self
687 .private_request("GET", &path, Some(¶ms), None)
688 .await?;
689
690 let data = response
691 .get("data")
692 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
693
694 let trades_array = data.as_array().ok_or_else(|| {
695 Error::from(ParseError::invalid_format(
696 "data",
697 "Expected array of trades",
698 ))
699 })?;
700
701 let mut trades = Vec::new();
702 for trade_data in trades_array {
703 match parser::parse_trade(trade_data, Some(&market)) {
704 Ok(trade) => trades.push(trade),
705 Err(e) => {
706 warn!(error = %e, "Failed to parse my trade");
707 }
708 }
709 }
710
711 Ok(trades)
712 }
713
714 pub async fn create_order(
732 &self,
733 symbol: &str,
734 order_type: OrderType,
735 side: OrderSide,
736 amount: f64,
737 price: Option<f64>,
738 ) -> Result<Order> {
739 let market = self.base().market(symbol).await?;
740
741 let path = self.build_api_path("/trade/order");
742
743 let mut map = serde_json::Map::new();
745 map.insert(
746 "instId".to_string(),
747 serde_json::Value::String(market.id.clone()),
748 );
749 map.insert(
750 "tdMode".to_string(),
751 serde_json::Value::String(self.options().account_mode.clone()),
752 );
753 map.insert(
754 "side".to_string(),
755 serde_json::Value::String(match side {
756 OrderSide::Buy => "buy".to_string(),
757 OrderSide::Sell => "sell".to_string(),
758 }),
759 );
760 map.insert(
761 "ordType".to_string(),
762 serde_json::Value::String(match order_type {
763 OrderType::Market => "market".to_string(),
764 OrderType::Limit => "limit".to_string(),
765 OrderType::LimitMaker => "post_only".to_string(),
766 _ => "limit".to_string(),
767 }),
768 );
769 map.insert(
770 "sz".to_string(),
771 serde_json::Value::String(amount.to_string()),
772 );
773
774 if let Some(p) = price {
776 if order_type == OrderType::Limit || order_type == OrderType::LimitMaker {
777 map.insert("px".to_string(), serde_json::Value::String(p.to_string()));
778 }
779 }
780 let body = serde_json::Value::Object(map);
781
782 let response = self
783 .private_request("POST", &path, None, Some(&body))
784 .await?;
785
786 let data = response
787 .get("data")
788 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
789
790 let orders = data.as_array().ok_or_else(|| {
792 Error::from(ParseError::invalid_format(
793 "data",
794 "Expected array of orders",
795 ))
796 })?;
797
798 if orders.is_empty() {
799 return Err(Error::exchange("-1", "No order data returned"));
800 }
801
802 parser::parse_order(&orders[0], Some(&market))
803 }
804
805 pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
816 let market = self.base().market(symbol).await?;
817
818 let path = self.build_api_path("/trade/cancel-order");
819
820 let mut map = serde_json::Map::new();
821 map.insert(
822 "instId".to_string(),
823 serde_json::Value::String(market.id.clone()),
824 );
825 map.insert(
826 "ordId".to_string(),
827 serde_json::Value::String(id.to_string()),
828 );
829 let body = serde_json::Value::Object(map);
830
831 let response = self
832 .private_request("POST", &path, None, Some(&body))
833 .await?;
834
835 let data = response
836 .get("data")
837 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
838
839 let orders = data.as_array().ok_or_else(|| {
841 Error::from(ParseError::invalid_format(
842 "data",
843 "Expected array of orders",
844 ))
845 })?;
846
847 if orders.is_empty() {
848 return Err(Error::exchange("-1", "No order data returned"));
849 }
850
851 parser::parse_order(&orders[0], Some(&market))
852 }
853
854 pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
865 let market = self.base().market(symbol).await?;
866
867 let path = self.build_api_path("/trade/order");
868 let mut params = HashMap::new();
869 params.insert("instId".to_string(), market.id.clone());
870 params.insert("ordId".to_string(), id.to_string());
871
872 let response = self
873 .private_request("GET", &path, Some(¶ms), None)
874 .await?;
875
876 let data = response
877 .get("data")
878 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
879
880 let orders = data.as_array().ok_or_else(|| {
882 Error::from(ParseError::invalid_format(
883 "data",
884 "Expected array of orders",
885 ))
886 })?;
887
888 if orders.is_empty() {
889 return Err(Error::exchange("51400", "Order not found"));
890 }
891
892 parser::parse_order(&orders[0], Some(&market))
893 }
894
895 pub async fn fetch_open_orders(
916 &self,
917 symbol: Option<&str>,
918 since: Option<i64>,
919 limit: Option<u32>,
920 ) -> Result<Vec<Order>> {
921 let path = self.build_api_path("/trade/orders-pending");
922 let mut params = HashMap::new();
923 params.insert("instType".to_string(), self.get_inst_type().to_string());
924
925 let market = if let Some(sym) = symbol {
926 let m = self.base().market(sym).await?;
927 params.insert("instId".to_string(), m.id.clone());
928 Some(m)
929 } else {
930 None
931 };
932
933 let actual_limit = limit.map(|l| l.min(100)).unwrap_or(100);
935 params.insert("limit".to_string(), actual_limit.to_string());
936
937 if let Some(start_time) = since {
938 params.insert("begin".to_string(), start_time.to_string());
939 }
940
941 let response = self
942 .private_request("GET", &path, Some(¶ms), None)
943 .await?;
944
945 let data = response
946 .get("data")
947 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
948
949 let orders_array = data.as_array().ok_or_else(|| {
950 Error::from(ParseError::invalid_format(
951 "data",
952 "Expected array of orders",
953 ))
954 })?;
955
956 let mut orders = Vec::new();
957 for order_data in orders_array {
958 match parser::parse_order(order_data, market.as_ref().map(|v| &**v)) {
959 Ok(order) => orders.push(order),
960 Err(e) => {
961 warn!(error = %e, "Failed to parse open order");
962 }
963 }
964 }
965
966 Ok(orders)
967 }
968
969 pub async fn fetch_closed_orders(
981 &self,
982 symbol: Option<&str>,
983 since: Option<i64>,
984 limit: Option<u32>,
985 ) -> Result<Vec<Order>> {
986 let path = self.build_api_path("/trade/orders-history");
987 let mut params = HashMap::new();
988 params.insert("instType".to_string(), self.get_inst_type().to_string());
989
990 let market = if let Some(sym) = symbol {
991 let m = self.base().market(sym).await?;
992 params.insert("instId".to_string(), m.id.clone());
993 Some(m)
994 } else {
995 None
996 };
997
998 let actual_limit = limit.map(|l| l.min(100)).unwrap_or(100);
1000 params.insert("limit".to_string(), actual_limit.to_string());
1001
1002 if let Some(start_time) = since {
1003 params.insert("begin".to_string(), start_time.to_string());
1004 }
1005
1006 let response = self
1007 .private_request("GET", &path, Some(¶ms), None)
1008 .await?;
1009
1010 let data = response
1011 .get("data")
1012 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
1013
1014 let orders_array = data.as_array().ok_or_else(|| {
1015 Error::from(ParseError::invalid_format(
1016 "data",
1017 "Expected array of orders",
1018 ))
1019 })?;
1020
1021 let mut orders = Vec::new();
1022 for order_data in orders_array {
1023 match parser::parse_order(order_data, market.as_ref().map(|v| &**v)) {
1024 Ok(order) => orders.push(order),
1025 Err(e) => {
1026 warn!(error = %e, "Failed to parse closed order");
1027 }
1028 }
1029 }
1030
1031 Ok(orders)
1032 }
1033}
1034
1035#[cfg(test)]
1036mod tests {
1037 use super::*;
1038
1039 #[test]
1040 fn test_build_api_path() {
1041 let okx = Okx::builder().build().unwrap();
1042 let path = okx.build_api_path("/public/instruments");
1043 assert_eq!(path, "/api/v5/public/instruments");
1044 }
1045
1046 #[test]
1047 fn test_get_inst_type_spot() {
1048 let okx = Okx::builder().build().unwrap();
1049 let inst_type = okx.get_inst_type();
1050 assert_eq!(inst_type, "SPOT");
1051 }
1052
1053 #[test]
1054 fn test_get_inst_type_margin() {
1055 use ccxt_core::types::default_type::DefaultType;
1056 let okx = Okx::builder()
1057 .default_type(DefaultType::Margin)
1058 .build()
1059 .unwrap();
1060 let inst_type = okx.get_inst_type();
1061 assert_eq!(inst_type, "MARGIN");
1062 }
1063
1064 #[test]
1065 fn test_get_inst_type_swap() {
1066 use ccxt_core::types::default_type::DefaultType;
1067 let okx = Okx::builder()
1068 .default_type(DefaultType::Swap)
1069 .build()
1070 .unwrap();
1071 let inst_type = okx.get_inst_type();
1072 assert_eq!(inst_type, "SWAP");
1073 }
1074
1075 #[test]
1076 fn test_get_inst_type_futures() {
1077 use ccxt_core::types::default_type::DefaultType;
1078 let okx = Okx::builder()
1079 .default_type(DefaultType::Futures)
1080 .build()
1081 .unwrap();
1082 let inst_type = okx.get_inst_type();
1083 assert_eq!(inst_type, "FUTURES");
1084 }
1085
1086 #[test]
1087 fn test_get_inst_type_option() {
1088 use ccxt_core::types::default_type::DefaultType;
1089 let okx = Okx::builder()
1090 .default_type(DefaultType::Option)
1091 .build()
1092 .unwrap();
1093 let inst_type = okx.get_inst_type();
1094 assert_eq!(inst_type, "OPTION");
1095 }
1096
1097 #[test]
1098 fn test_get_timestamp() {
1099 let okx = Okx::builder().build().unwrap();
1100 let ts = okx.get_timestamp();
1101
1102 assert!(ts.contains("T"));
1104 assert!(ts.contains("Z"));
1105 assert!(ts.len() > 20);
1106 }
1107}