use serde::Deserialize;
use serde_json::json;
use tracing::{debug, info};
use crate::client::KuCoinClient;
use crate::error::Result;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountOverview {
pub available_balance: f64,
pub order_margin: Option<f64>,
pub position_margin: Option<f64>,
pub unrealised_pnl: Option<f64>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PositionInfo {
pub current_qty: i32,
pub symbol: String,
pub avg_entry_price: Option<f64>,
pub unrealised_pnl: Option<f64>,
pub realised_pnl: Option<f64>,
pub leverage: Option<f64>,
pub is_open: Option<bool>,
pub mark_price: Option<f64>,
pub mark_value: Option<f64>,
pub maintenance_margin: Option<f64>,
}
impl PositionInfo {
pub const fn is_flat(&self) -> bool {
self.current_qty == 0
}
pub const fn is_long(&self) -> bool {
self.current_qty > 0
}
pub const fn is_short(&self) -> bool {
self.current_qty < 0
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FundingRecord {
pub id: Option<String>,
pub symbol: String,
pub time_point: Option<i64>,
pub funding_rate: Option<f64>,
pub mark_price: Option<f64>,
pub position_qty: Option<i32>,
pub position_cost: Option<f64>,
pub funding: Option<f64>,
pub settlement: Option<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RiskLimitLevel {
pub symbol: String,
pub level: u32,
pub max_risk_limit: Option<f64>,
pub min_risk_limit: Option<f64>,
pub max_leverage: Option<u32>,
pub initial_margin: Option<f64>,
pub maint_margin_rate: Option<f64>,
}
impl KuCoinClient {
pub async fn get_balance(&self, currency: &str) -> Result<f64> {
let overview: AccountOverview = self
.get("/api/v1/account-overview", &[("currency", currency)])
.await?;
debug!(
balance = overview.available_balance,
currency, "got balance"
);
Ok(overview.available_balance)
}
pub async fn get_account_overview(&self, currency: &str) -> Result<AccountOverview> {
self.get("/api/v1/account-overview", &[("currency", currency)])
.await
}
pub async fn get_position(&self, symbol: &str) -> Result<PositionInfo> {
self.get("/api/v1/position", &[("symbol", symbol)]).await
}
pub async fn get_all_positions(&self) -> Result<Vec<PositionInfo>> {
self.get("/api/v1/positions", &[]).await
}
pub async fn set_auto_deposit(&self, symbol: &str, auto_deposit: bool) -> Result<()> {
self.post::<serde_json::Value>(
"/api/v1/position/changeAutoDeposit",
&json!({ "symbol": symbol, "autoDeposit": auto_deposit }),
)
.await?;
info!(symbol, auto_deposit, "auto-deposit updated");
Ok(())
}
pub async fn set_risk_limit_level(&self, symbol: &str, level: u32) -> Result<()> {
let resp: serde_json::Value = self
.post(
"/api/v1/position/risk-limit-level/change",
&json!({ "symbol": symbol, "level": level }),
)
.await?;
info!(symbol, level, resp = %resp, "risk limit level updated");
Ok(())
}
pub async fn get_risk_limit_levels(&self, symbol: &str) -> Result<Vec<RiskLimitLevel>> {
self.get(&format!("/api/v1/contracts/risk-limit/{symbol}"), &[])
.await
}
pub async fn get_funding_history(
&self,
symbol: &str,
max_count: u32,
) -> Result<Vec<FundingRecord>> {
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct Page {
data_list: Vec<FundingRecord>,
}
let limit = max_count.min(100).to_string();
let page: Page = self
.get(
"/api/v1/funding-history",
&[("symbol", symbol), ("maxCount", &limit)],
)
.await?;
Ok(page.data_list)
}
}