1use super::super::{Okx, parser};
4use ccxt_core::{
5 Error, ParseError, Result,
6 types::{Market, OHLCV, OhlcvRequest, OrderBook, Ticker, Trade},
7};
8use std::{collections::HashMap, sync::Arc};
9use tracing::{info, warn};
10
11impl Okx {
12 pub async fn fetch_markets(&self) -> Result<Arc<HashMap<String, Arc<Market>>>> {
34 let path = Self::build_api_path("/public/instruments");
35 let mut params = HashMap::new();
36 params.insert("instType".to_string(), self.get_inst_type().to_string());
37
38 let response = self.public_request("GET", &path, Some(¶ms)).await?;
39
40 let data = response
41 .get("data")
42 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
43
44 let instruments = data.as_array().ok_or_else(|| {
45 Error::from(ParseError::invalid_format(
46 "data",
47 "Expected array of instruments",
48 ))
49 })?;
50
51 let mut markets = Vec::new();
52 for instrument in instruments {
53 match parser::parse_market(instrument) {
54 Ok(market) => markets.push(market),
55 Err(e) => {
56 warn!(error = %e, "Failed to parse market");
57 }
58 }
59 }
60
61 let result = self.base().set_markets(markets, None).await?;
62
63 info!("Loaded {} markets for OKX", result.len());
64 Ok(result)
65 }
66
67 pub async fn load_markets(&self, reload: bool) -> Result<Arc<HashMap<String, Arc<Market>>>> {
79 let _loading_guard = self.base().market_loading_lock.lock().await;
80
81 {
82 let cache = self.base().market_cache.read().await;
83 if cache.is_loaded() && !reload {
84 return Ok(cache.markets());
85 }
86 }
87
88 info!("Loading markets for OKX (reload: {})", reload);
89 let _markets = self.fetch_markets().await?;
90
91 let cache = self.base().market_cache.read().await;
92 Ok(cache.markets())
93 }
94
95 pub async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
105 let market = self.base().market(symbol).await?;
106
107 let path = Self::build_api_path("/market/ticker");
108 let mut params = HashMap::new();
109 params.insert("instId".to_string(), market.id.clone());
110
111 let response = self.public_request("GET", &path, Some(¶ms)).await?;
112
113 let data = response
114 .get("data")
115 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
116
117 let tickers = data.as_array().ok_or_else(|| {
118 Error::from(ParseError::invalid_format(
119 "data",
120 "Expected array of tickers",
121 ))
122 })?;
123
124 if tickers.is_empty() {
125 return Err(Error::bad_symbol(format!("No ticker data for {}", symbol)));
126 }
127
128 parser::parse_ticker(&tickers[0], Some(&market))
129 }
130
131 pub async fn fetch_tickers(&self, symbols: Option<Vec<String>>) -> Result<Vec<Ticker>> {
141 let cache = self.base().market_cache.read().await;
142 if !cache.is_loaded() {
143 drop(cache);
144 return Err(Error::exchange(
145 "-1",
146 "Markets not loaded. Call load_markets() first.",
147 ));
148 }
149 let markets_snapshot: std::collections::HashMap<String, Arc<Market>> = cache
151 .iter_markets()
152 .map(|(_, m)| (m.id.clone(), m))
153 .collect();
154 drop(cache);
155
156 let path = Self::build_api_path("/market/tickers");
157 let mut params = HashMap::new();
158 params.insert("instType".to_string(), self.get_inst_type().to_string());
159
160 let response = self.public_request("GET", &path, Some(¶ms)).await?;
161
162 let data = response
163 .get("data")
164 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
165
166 let tickers_array = data.as_array().ok_or_else(|| {
167 Error::from(ParseError::invalid_format(
168 "data",
169 "Expected array of tickers",
170 ))
171 })?;
172
173 let mut tickers = Vec::new();
174 for ticker_data in tickers_array {
175 if let Some(inst_id) = ticker_data["instId"].as_str() {
176 if let Some(market) = markets_snapshot.get(inst_id) {
177 match parser::parse_ticker(ticker_data, Some(market)) {
178 Ok(ticker) => {
179 if let Some(ref syms) = symbols {
180 if syms.contains(&ticker.symbol) {
181 tickers.push(ticker);
182 }
183 } else {
184 tickers.push(ticker);
185 }
186 }
187 Err(e) => {
188 warn!(
189 error = %e,
190 symbol = %inst_id,
191 "Failed to parse ticker"
192 );
193 }
194 }
195 }
196 }
197 }
198
199 Ok(tickers)
200 }
201
202 pub async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
213 let market = self.base().market(symbol).await?;
214
215 let path = Self::build_api_path("/market/books");
216 let mut params = HashMap::new();
217 params.insert("instId".to_string(), market.id.clone());
218
219 let actual_limit = limit.map_or(100, |l| l.min(400));
220 params.insert("sz".to_string(), actual_limit.to_string());
221
222 let response = self.public_request("GET", &path, Some(¶ms)).await?;
223
224 let data = response
225 .get("data")
226 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
227
228 let books = data.as_array().ok_or_else(|| {
229 Error::from(ParseError::invalid_format(
230 "data",
231 "Expected array of orderbooks",
232 ))
233 })?;
234
235 if books.is_empty() {
236 return Err(Error::bad_symbol(format!(
237 "No orderbook data for {}",
238 symbol
239 )));
240 }
241
242 parser::parse_orderbook(&books[0], market.symbol.clone())
243 }
244
245 pub async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
256 let market = self.base().market(symbol).await?;
257
258 let path = Self::build_api_path("/market/trades");
259 let mut params = HashMap::new();
260 params.insert("instId".to_string(), market.id.clone());
261
262 let actual_limit = limit.map_or(100, |l| l.min(500));
263 params.insert("limit".to_string(), actual_limit.to_string());
264
265 let response = self.public_request("GET", &path, Some(¶ms)).await?;
266
267 let data = response
268 .get("data")
269 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
270
271 let trades_array = data.as_array().ok_or_else(|| {
272 Error::from(ParseError::invalid_format(
273 "data",
274 "Expected array of trades",
275 ))
276 })?;
277
278 let mut trades = Vec::new();
279 for trade_data in trades_array {
280 match parser::parse_trade(trade_data, Some(&market)) {
281 Ok(trade) => trades.push(trade),
282 Err(e) => {
283 warn!(error = %e, "Failed to parse trade");
284 }
285 }
286 }
287
288 trades.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
289
290 Ok(trades)
291 }
292
293 pub async fn fetch_ohlcv_v2(&self, request: OhlcvRequest) -> Result<Vec<OHLCV>> {
312 let market = self.base().market(&request.symbol).await?;
313
314 let timeframes = self.timeframes();
315 let okx_timeframe = timeframes.get(&request.timeframe).ok_or_else(|| {
316 Error::invalid_request(format!("Unsupported timeframe: {}", request.timeframe))
317 })?;
318
319 let path = Self::build_api_path("/market/candles");
320 let mut params = HashMap::new();
321 params.insert("instId".to_string(), market.id.clone());
322 params.insert("bar".to_string(), okx_timeframe.clone());
323
324 let actual_limit = request.limit.map_or(100, |l| l.min(300));
325 params.insert("limit".to_string(), actual_limit.to_string());
326
327 if let Some(start_time) = request.since {
328 params.insert("after".to_string(), start_time.to_string());
329 }
330
331 if let Some(end_time) = request.until {
332 params.insert("before".to_string(), end_time.to_string());
333 }
334
335 let response = self.public_request("GET", &path, Some(¶ms)).await?;
336
337 let data = response
338 .get("data")
339 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
340
341 let candles_array = data.as_array().ok_or_else(|| {
342 Error::from(ParseError::invalid_format(
343 "data",
344 "Expected array of candles",
345 ))
346 })?;
347
348 let mut ohlcv = Vec::new();
349 for candle_data in candles_array {
350 match parser::parse_ohlcv(candle_data) {
351 Ok(candle) => ohlcv.push(candle),
352 Err(e) => {
353 warn!(error = %e, "Failed to parse OHLCV");
354 }
355 }
356 }
357
358 ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
359
360 Ok(ohlcv)
361 }
362
363 #[deprecated(
381 since = "0.2.0",
382 note = "Use fetch_ohlcv_v2 with OhlcvRequest::builder() instead"
383 )]
384 pub async fn fetch_ohlcv(
385 &self,
386 symbol: &str,
387 timeframe: &str,
388 since: Option<i64>,
389 limit: Option<u32>,
390 ) -> Result<Vec<OHLCV>> {
391 let market = self.base().market(symbol).await?;
392
393 let timeframes = self.timeframes();
394 let okx_timeframe = timeframes.get(timeframe).ok_or_else(|| {
395 Error::invalid_request(format!("Unsupported timeframe: {}", timeframe))
396 })?;
397
398 let path = Self::build_api_path("/market/candles");
399 let mut params = HashMap::new();
400 params.insert("instId".to_string(), market.id.clone());
401 params.insert("bar".to_string(), okx_timeframe.clone());
402
403 let actual_limit = limit.map_or(100, |l| l.min(300));
404 params.insert("limit".to_string(), actual_limit.to_string());
405
406 if let Some(start_time) = since {
407 params.insert("after".to_string(), start_time.to_string());
408 }
409
410 let response = self.public_request("GET", &path, Some(¶ms)).await?;
411
412 let data = response
413 .get("data")
414 .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
415
416 let candles_array = data.as_array().ok_or_else(|| {
417 Error::from(ParseError::invalid_format(
418 "data",
419 "Expected array of candles",
420 ))
421 })?;
422
423 let mut ohlcv = Vec::new();
424 for candle_data in candles_array {
425 match parser::parse_ohlcv(candle_data) {
426 Ok(candle) => ohlcv.push(candle),
427 Err(e) => {
428 warn!(error = %e, "Failed to parse OHLCV");
429 }
430 }
431 }
432
433 ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
434
435 Ok(ohlcv)
436 }
437}