use super::super::super::{Okx, parser};
use ccxt_core::{Error, ParseError, Result, types::Position};
use tracing::warn;
impl Okx {
pub(crate) fn inst_type_from_symbol(symbol: &str) -> &'static str {
if symbol.contains(':') {
if symbol.contains('-')
&& symbol
.rsplit('-')
.next()
.is_some_and(|s| s.len() == 6 && s.chars().all(|c| c.is_ascii_digit()))
{
"FUTURES"
} else {
"SWAP"
}
} else {
"SWAP"
}
}
pub(crate) fn symbol_to_inst_id(symbol: &str) -> String {
if let Some(pos) = symbol.find(':') {
let base_quote = &symbol[..pos];
let settle_part = &symbol[pos + 1..];
let base_quote_dash = base_quote.replace('/', "-");
if let Some(dash_pos) = settle_part.find('-') {
let expiry = &settle_part[dash_pos + 1..];
format!("{}-{}", base_quote_dash, expiry)
} else {
format!("{}-SWAP", base_quote_dash)
}
} else {
symbol.replace('/', "-")
}
}
pub async fn fetch_position_impl(&self, symbol: &str) -> Result<Position> {
let inst_id = Self::symbol_to_inst_id(symbol);
let inst_type = Self::inst_type_from_symbol(symbol);
let data = self
.signed_request("/api/v5/account/positions")
.param("instId", &inst_id)
.param("instType", inst_type)
.execute()
.await?;
let positions_array = data["data"].as_array().ok_or_else(|| {
Error::from(ParseError::invalid_format("data", "Expected data array"))
})?;
if positions_array.is_empty() {
return Err(Error::from(ParseError::missing_field_owned(format!(
"No position found for symbol: {}",
symbol
))));
}
parser::parse_position(&positions_array[0], symbol)
}
pub async fn fetch_positions_impl(&self, symbols: &[&str]) -> Result<Vec<Position>> {
let inst_type = if symbols.is_empty() {
self.get_inst_type()
} else {
Self::inst_type_from_symbol(symbols[0])
};
let mut builder = self
.signed_request("/api/v5/account/positions")
.param("instType", inst_type);
if !symbols.is_empty() && symbols.len() <= 10 {
let inst_ids: Vec<String> =
symbols.iter().map(|s| Self::symbol_to_inst_id(s)).collect();
builder = builder.param("instId", inst_ids.join(","));
}
let data = builder.execute().await?;
let positions_array = data["data"].as_array().ok_or_else(|| {
Error::from(ParseError::invalid_format("data", "Expected data array"))
})?;
let mut positions = Vec::new();
for position_data in positions_array {
let inst_id = position_data["instId"].as_str().unwrap_or_default();
let symbol_hint = Self::inst_id_to_symbol_hint(inst_id);
match parser::parse_position(position_data, &symbol_hint) {
Ok(position) => {
if symbols.is_empty() || symbols.contains(&position.symbol.as_str()) {
positions.push(position);
}
}
Err(e) => {
warn!(error = %e, inst_id = %inst_id, "Failed to parse OKX position");
}
}
}
Ok(positions)
}
fn inst_id_to_symbol_hint(inst_id: &str) -> String {
let parts: Vec<&str> = inst_id.split('-').collect();
match parts.len() {
2 => {
format!("{}/{}", parts[0], parts[1])
}
3 if parts[2] == "SWAP" => {
let settle = if parts[1] == "USD" {
parts[0]
} else {
parts[1]
};
format!("{}/{}:{}", parts[0], parts[1], settle)
}
3 => {
let settle = if parts[1] == "USD" {
parts[0]
} else {
parts[1]
};
format!("{}/{}:{}-{}", parts[0], parts[1], settle, parts[2])
}
_ => inst_id.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inst_type_from_symbol() {
assert_eq!(Okx::inst_type_from_symbol("BTC/USDT:USDT"), "SWAP");
assert_eq!(Okx::inst_type_from_symbol("BTC/USD:BTC"), "SWAP");
assert_eq!(
Okx::inst_type_from_symbol("BTC/USDT:USDT-241231"),
"FUTURES"
);
assert_eq!(Okx::inst_type_from_symbol("BTC/USDT"), "SWAP");
}
#[test]
fn test_symbol_to_inst_id() {
assert_eq!(Okx::symbol_to_inst_id("BTC/USDT:USDT"), "BTC-USDT-SWAP");
assert_eq!(Okx::symbol_to_inst_id("BTC/USD:BTC"), "BTC-USD-SWAP");
assert_eq!(
Okx::symbol_to_inst_id("BTC/USDT:USDT-241231"),
"BTC-USDT-241231"
);
assert_eq!(Okx::symbol_to_inst_id("BTC/USDT"), "BTC-USDT");
assert_eq!(Okx::symbol_to_inst_id("ETH/USD:ETH"), "ETH-USD-SWAP");
}
#[test]
fn test_inst_id_to_symbol_hint() {
assert_eq!(
Okx::inst_id_to_symbol_hint("BTC-USDT-SWAP"),
"BTC/USDT:USDT"
);
assert_eq!(Okx::inst_id_to_symbol_hint("BTC-USD-SWAP"), "BTC/USD:BTC");
assert_eq!(
Okx::inst_id_to_symbol_hint("BTC-USDT-241231"),
"BTC/USDT:USDT-241231"
);
assert_eq!(Okx::inst_id_to_symbol_hint("BTC-USDT"), "BTC/USDT");
}
}