use serde::Deserialize;
use serde_json::Value;
use tracing::debug;
use crate::client::KuCoinClient;
use crate::error::Result;
use crate::types::Candle;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Ticker {
pub symbol: String,
pub best_bid_price: Option<f64>,
pub best_bid_size: Option<f64>,
pub best_ask_price: Option<f64>,
pub best_ask_size: Option<f64>,
pub ts: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct OrderBookSnapshot {
pub sequence: u64,
pub asks: Vec<[f64; 2]>,
pub bids: Vec<[f64; 2]>,
pub ts: Option<i64>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FundingRate {
pub symbol: String,
pub granularity: Option<i64>,
pub time_point: Option<i64>,
pub value: f64,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarkPrice {
pub symbol: String,
pub granularity: Option<i64>,
pub time_point: Option<i64>,
pub value: f64,
pub index_price: Option<f64>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractInfo {
pub symbol: String,
pub root_symbol: Option<String>,
pub contract_type: Option<String>,
pub first_open_date: Option<i64>,
pub expire_date: Option<i64>,
pub settle_date: Option<i64>,
pub base_currency: Option<String>,
pub quote_currency: Option<String>,
pub settle_currency: Option<String>,
pub max_order_qty: Option<u64>,
pub lot_size: Option<u64>,
pub tick_size: Option<f64>,
pub multiplier: Option<f64>,
pub initial_margin: Option<f64>,
pub maint_margin_rate: Option<f64>,
pub status: Option<String>,
pub funding_fee_rate: Option<f64>,
pub predicted_funding_fee_rate: Option<f64>,
pub open_interest: Option<String>,
pub turnover_of24h: Option<f64>,
pub volume_of24h: Option<f64>,
pub mark_price: Option<f64>,
pub index_price_value: Option<f64>,
}
impl KuCoinClient {
pub async fn fetch_klines(
&self,
symbol: &str,
limit: usize,
granularity: &str,
) -> Result<Vec<Candle>> {
let gran_i = granularity.parse::<i64>().unwrap_or(1);
let now_ms = chrono::Utc::now().timestamp_millis();
let limit_i64 = i64::try_from(limit).unwrap_or(i64::MAX);
let from_ms = now_ms - gran_i * 60_000 * limit_i64;
let from = from_ms.to_string();
let to = now_ms.to_string();
let raw: Vec<Vec<Value>> = self
.get(
"/api/v1/kline/query",
&[
("symbol", symbol),
("granularity", granularity),
("from", &from),
("to", &to),
],
)
.await?;
let candles = raw
.iter()
.filter_map(|r| match Candle::from_raw(r) {
Ok(c) => Some(c),
Err(e) => {
tracing::warn!(error = %e, "skipping malformed candle");
None
}
})
.collect();
debug!(symbol, granularity, count = raw.len(), "fetched klines");
Ok(candles)
}
pub async fn fetch_klines_extended(
&self,
symbol: &str,
total: usize,
granularity: &str,
page_size: usize,
) -> Result<Vec<Candle>> {
let gran_i = granularity.parse::<i64>().unwrap_or(1);
let now_ms = chrono::Utc::now().timestamp_millis();
let mut all: Vec<Candle> = Vec::with_capacity(total);
let mut window_end_ms = now_ms;
while all.len() < total {
let remaining = total - all.len();
let batch = remaining.min(page_size);
let batch_i64 = i64::try_from(batch).unwrap_or(i64::MAX);
let window_ms = gran_i * 60_000 * batch_i64;
let window_start_ms = window_end_ms - window_ms;
let from = window_start_ms.to_string();
let to = window_end_ms.to_string();
let raw: Vec<Vec<Value>> = self
.get(
"/api/v1/kline/query",
&[
("symbol", symbol),
("granularity", granularity),
("from", &from),
("to", &to),
],
)
.await?;
let n = raw.len();
let mut page: Vec<Candle> = raw
.iter()
.filter_map(|r| match Candle::from_raw(r) {
Ok(c) => Some(c),
Err(e) => {
tracing::warn!(error = %e, "skipping malformed candle in page");
None
}
})
.collect();
page.sort_by_key(|c| c.time);
all.extend(page);
if n == 0 {
break;
}
window_end_ms = window_start_ms - gran_i * 60_000;
}
all.sort_by_key(|c| c.time);
all.truncate(total);
Ok(all)
}
pub async fn get_orderbook_snapshot(&self, symbol: &str) -> Result<OrderBookSnapshot> {
self.get("/api/v1/level2/snapshot", &[("symbol", symbol)])
.await
}
pub async fn get_funding_rate(&self, symbol: &str) -> Result<FundingRate> {
self.get(&format!("/api/v1/funding-rate/{symbol}/current"), &[])
.await
}
pub async fn get_mark_price(&self, symbol: &str) -> Result<MarkPrice> {
self.get(&format!("/api/v1/mark-price/{symbol}/current"), &[])
.await
}
pub async fn get_active_contracts(&self) -> Result<Vec<ContractInfo>> {
self.get("/api/v1/contracts/active", &[]).await
}
pub async fn get_contract(&self, symbol: &str) -> Result<ContractInfo> {
self.get(&format!("/api/v1/contracts/{symbol}"), &[]).await
}
pub async fn get_ticker(&self, symbol: &str) -> Result<Ticker> {
self.get("/api/v1/ticker", &[("symbol", symbol)]).await
}
}