use crate::core::types::AccountType;
#[derive(Debug, Clone)]
pub struct KrakenUrls {
pub spot_rest: &'static str,
pub futures_rest: &'static str,
pub spot_ws: &'static str,
pub futures_ws: &'static str,
}
impl KrakenUrls {
pub const MAINNET: Self = Self {
spot_rest: "https://api.kraken.com",
futures_rest: "https://futures.kraken.com",
spot_ws: "wss://ws.kraken.com/v2",
futures_ws: "wss://futures.kraken.com/ws/v1",
};
pub const TESTNET: Self = Self {
spot_rest: "https://api.kraken.com", futures_rest: "https://demo-futures.kraken.com",
spot_ws: "wss://ws.kraken.com/v2",
futures_ws: "wss://demo-futures.kraken.com/ws/v1",
};
pub fn rest_url(&self, account_type: AccountType) -> &str {
match account_type {
AccountType::Spot | AccountType::Margin => self.spot_rest,
AccountType::FuturesCross | AccountType::FuturesIsolated => self.futures_rest,
_ => self.spot_rest,
}
}
pub fn ws_url(&self, account_type: AccountType) -> &str {
match account_type {
AccountType::Spot | AccountType::Margin => self.spot_ws,
AccountType::FuturesCross | AccountType::FuturesIsolated => self.futures_ws,
_ => self.spot_ws,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KrakenEndpoint {
ServerTime,
SpotTicker,
SpotOrderbook,
SpotOHLC,
SpotAssetPairs,
SpotAddOrder,
SpotCancelOrder,
SpotCancelAll,
SpotEditOrder,
SpotGetOrder,
SpotOpenOrders,
SpotClosedOrders,
SpotBalance,
SpotTradeBalance,
SpotWebSocketToken,
TradesHistory,
FuturesTickers,
FuturesOrderbook,
FuturesInstruments,
FuturesHistory,
FuturesSendOrder,
FuturesCancelOrder,
FuturesBatchOrder,
FuturesEditOrder,
FuturesAccounts,
FuturesOpenPositions,
FuturesHistoricalFunding,
FuturesSetLeverage,
SpotDepositAddresses, SpotWithdraw, SpotDepositStatus, SpotWithdrawStatus,
SpotListSubaccounts, SpotTransferToSubaccount, SpotTransferFromSubaccount,
SpotLedgers,
}
impl KrakenEndpoint {
pub fn path(&self) -> &'static str {
match self {
Self::ServerTime => "/0/public/Time",
Self::SpotTicker => "/0/public/Ticker",
Self::SpotOrderbook => "/0/public/Depth",
Self::SpotOHLC => "/0/public/OHLC",
Self::SpotAssetPairs => "/0/public/AssetPairs",
Self::SpotAddOrder => "/0/private/AddOrder",
Self::SpotCancelOrder => "/0/private/CancelOrder",
Self::SpotCancelAll => "/0/private/CancelAll",
Self::SpotEditOrder => "/0/private/EditOrder",
Self::SpotGetOrder => "/0/private/QueryOrders",
Self::SpotOpenOrders => "/0/private/OpenOrders",
Self::SpotClosedOrders => "/0/private/ClosedOrders",
Self::SpotBalance => "/0/private/Balance",
Self::SpotTradeBalance => "/0/private/TradeBalance",
Self::SpotWebSocketToken => "/0/private/GetWebSocketsToken",
Self::TradesHistory => "/0/private/TradesHistory",
Self::FuturesTickers => "/derivatives/api/v3/tickers",
Self::FuturesOrderbook => "/derivatives/api/v3/orderbook",
Self::FuturesInstruments => "/derivatives/api/v3/instruments",
Self::FuturesHistory => "/derivatives/api/v3/history",
Self::FuturesSendOrder => "/derivatives/api/v3/sendorder",
Self::FuturesCancelOrder => "/derivatives/api/v3/cancelorder",
Self::FuturesBatchOrder => "/derivatives/api/v3/batchorder",
Self::FuturesEditOrder => "/derivatives/api/v3/editorder",
Self::FuturesAccounts => "/derivatives/api/v3/accounts",
Self::FuturesOpenPositions => "/derivatives/api/v3/openpositions",
Self::FuturesHistoricalFunding => "/derivatives/api/v4/historicalfundingrates",
Self::FuturesSetLeverage => "/derivatives/api/v3/leveragepreferences",
Self::SpotDepositAddresses => "/0/private/DepositAddresses",
Self::SpotWithdraw => "/0/private/Withdraw",
Self::SpotDepositStatus => "/0/private/DepositStatus",
Self::SpotWithdrawStatus => "/0/private/WithdrawStatus",
Self::SpotListSubaccounts => "/0/private/ListSubaccounts",
Self::SpotTransferToSubaccount => "/0/private/TransferToSubaccount",
Self::SpotTransferFromSubaccount => "/0/private/TransferFromSubaccount",
Self::SpotLedgers => "/0/private/Ledgers",
}
}
pub fn requires_auth(&self) -> bool {
match self {
Self::ServerTime
| Self::SpotTicker
| Self::SpotOrderbook
| Self::SpotOHLC
| Self::SpotAssetPairs
| Self::FuturesTickers
| Self::FuturesOrderbook
| Self::FuturesInstruments
| Self::FuturesHistory => false,
_ => true,
}
}
pub fn method(&self) -> &'static str {
match self {
Self::SpotAddOrder
| Self::SpotCancelOrder
| Self::SpotCancelAll
| Self::SpotEditOrder
| Self::SpotGetOrder
| Self::SpotOpenOrders
| Self::SpotClosedOrders
| Self::SpotBalance
| Self::SpotTradeBalance
| Self::SpotWebSocketToken
| Self::FuturesSendOrder
| Self::FuturesCancelOrder
| Self::FuturesBatchOrder
| Self::FuturesEditOrder
| Self::FuturesSetLeverage
| Self::SpotDepositAddresses
| Self::SpotWithdraw
| Self::SpotDepositStatus
| Self::SpotWithdrawStatus
| Self::SpotListSubaccounts
| Self::SpotTransferToSubaccount
| Self::SpotTransferFromSubaccount
| Self::TradesHistory
| Self::SpotLedgers => "POST",
_ => "GET",
}
}
}
pub fn format_symbol(base: &str, quote: &str, account_type: AccountType) -> String {
match account_type {
AccountType::Spot | AccountType::Margin => {
let base = if base.to_uppercase() == "BTC" { "XBT" } else { base };
format!("{}{}", base, quote)
}
AccountType::FuturesCross | AccountType::FuturesIsolated => {
let base = if base.to_uppercase() == "BTC" { "XBT" } else { base };
format!("PI_{}{}", base, quote)
}
_ => {
let base = if base.to_uppercase() == "BTC" { "XBT" } else { base };
format!("{}{}", base, quote)
}
}
}
#[allow(dead_code)]
pub fn parse_response_symbol(symbol: &str) -> Option<(String, String)> {
if symbol.starts_with("PI_") || symbol.starts_with("PF_") {
let parts = symbol.split('_').nth(1)?;
if parts.len() >= 6 {
let base = &parts[0..3];
let quote = &parts[3..];
return Some((base.to_string(), quote.to_string()));
}
}
let clean = symbol
.strip_prefix("XX")
.or_else(|| symbol.strip_prefix("X"))
.unwrap_or(symbol);
for fiat in &["ZUSD", "ZEUR", "ZGBP", "ZJPY", "ZCAD"] {
if let Some(base) = clean.strip_suffix(fiat) {
return Some((base.to_string(), fiat.strip_prefix("Z").expect("Fiat codes start with Z").to_string()));
}
}
if clean.len() >= 6 {
let base = &clean[0..3];
let quote = &clean[3..];
return Some((base.to_string(), quote.to_string()));
}
None
}
pub fn map_ohlc_interval(interval: &str) -> u32 {
match interval {
"1m" => 1,
"5m" => 5,
"15m" => 15,
"30m" => 30,
"1h" => 60,
"4h" => 240,
"1d" => 1440,
"1w" => 10080,
"15d" => 21600,
_ => 60, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_symbol_spot() {
assert_eq!(format_symbol("BTC", "USD", AccountType::Spot), "XBTUSD");
assert_eq!(format_symbol("ETH", "USD", AccountType::Spot), "ETHUSD");
assert_eq!(format_symbol("XBT", "EUR", AccountType::Spot), "XBTEUR");
}
#[test]
fn test_format_symbol_futures() {
assert_eq!(
format_symbol("BTC", "USD", AccountType::FuturesCross),
"PI_XBTUSD"
);
assert_eq!(
format_symbol("ETH", "USD", AccountType::FuturesCross),
"PI_ETHUSD"
);
}
#[test]
fn test_parse_response_symbol() {
assert_eq!(
parse_response_symbol("XXBTZUSD"),
Some(("XBT".to_string(), "USD".to_string()))
);
assert_eq!(
parse_response_symbol("XETHZUSD"),
Some(("ETH".to_string(), "USD".to_string()))
);
assert_eq!(
parse_response_symbol("PI_XBTUSD"),
Some(("XBT".to_string(), "USD".to_string()))
);
}
#[test]
fn test_map_ohlc_interval() {
assert_eq!(map_ohlc_interval("1m"), 1);
assert_eq!(map_ohlc_interval("1h"), 60);
assert_eq!(map_ohlc_interval("1d"), 1440);
assert_eq!(map_ohlc_interval("unknown"), 60);
}
}