use crate::core::types::{AccountType, Symbol};
pub struct BitstampUrls;
impl BitstampUrls {
pub fn base_url() -> &'static str {
"https://www.bitstamp.net"
}
pub fn ws_url() -> &'static str {
"wss://ws.bitstamp.net"
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BitstampEndpoint {
Ticker, Orderbook, Transactions, Ohlc, Markets, Currencies,
Balance, AccountInfo, TradingFees, UserTransactions,
BuyLimit, SellLimit, BuyMarket, SellMarket, CancelOrder, CancelAllOrders, ReplaceOrder, OrderStatus, OpenOrders,
OpenPositions,
BuyStopLimit, SellStopLimit,
DepositAddress, Withdrawal, WithdrawalRequests,
OrderHistoryPair,
InstantBuy,
InstantSell,
SubAccountTransfer,
}
impl BitstampEndpoint {
pub fn path(&self) -> &'static str {
match self {
Self::Ticker => "/api/v2/ticker",
Self::Orderbook => "/api/v2/order_book",
Self::Transactions => "/api/v2/transactions",
Self::Ohlc => "/api/v2/ohlc",
Self::Markets => "/api/v2/markets/",
Self::Currencies => "/api/v2/currencies/",
Self::Balance => "/api/v2/account_balances/",
Self::AccountInfo => "/api/v2/balance/",
Self::TradingFees => "/api/v2/fees/trading/",
Self::UserTransactions => "/api/v2/user_transactions/",
Self::BuyLimit => "/api/v2/buy",
Self::SellLimit => "/api/v2/sell",
Self::BuyMarket => "/api/v2/buy/market",
Self::SellMarket => "/api/v2/sell/market",
Self::CancelOrder => "/api/v2/cancel_order/",
Self::CancelAllOrders => "/api/v2/cancel_all_orders/",
Self::ReplaceOrder => "/api/v2/replace_order/",
Self::OrderStatus => "/api/v2/order_status/",
Self::OpenOrders => "/api/v2/open_orders/all/",
Self::OpenPositions => "/api/v2/open_positions/",
Self::BuyStopLimit => "/api/v2/buy/stop_limit",
Self::SellStopLimit => "/api/v2/sell/stop_limit",
Self::DepositAddress => "/api/v2/deposit-address/",
Self::Withdrawal => "/api/v2/withdrawal/",
Self::WithdrawalRequests => "/api/v2/withdrawal-requests/",
Self::OrderHistoryPair => "/api/v2/order_history",
Self::InstantBuy => "/api/v2/buy/instant",
Self::InstantSell => "/api/v2/sell/instant",
Self::SubAccountTransfer => "/api/v2/sub-account/transfer/",
}
}
pub fn path_with_pair(&self, pair: &str) -> String {
match self {
Self::Ticker | Self::Orderbook | Self::Transactions | Self::Ohlc => {
format!("{}/{}/", self.path(), pair)
}
Self::BuyLimit | Self::SellLimit => {
format!("{}/{}/", self.path(), pair)
}
Self::BuyMarket | Self::SellMarket => {
format!("{}/{}/", self.path(), pair)
}
Self::BuyStopLimit | Self::SellStopLimit => {
format!("{}/{}/", self.path(), pair)
}
Self::DepositAddress => {
format!("/api/v2/{}_address/", pair)
}
Self::Withdrawal => {
format!("/api/v2/{}_withdrawal/", pair)
}
Self::OrderHistoryPair => {
format!("/api/v2/order_history/{}/", pair)
}
Self::InstantBuy => {
format!("/api/v2/buy/instant/{}/", pair)
}
Self::InstantSell => {
format!("/api/v2/sell/instant/{}/", pair)
}
_ => self.path().to_string(),
}
}
pub fn method(&self) -> &'static str {
match self {
Self::Ticker
| Self::Orderbook
| Self::Transactions
| Self::Ohlc
| Self::Markets
| Self::Currencies => "GET",
_ => "POST",
}
}
pub fn is_private(&self) -> bool {
match self {
Self::Ticker
| Self::Orderbook
| Self::Transactions
| Self::Ohlc
| Self::Markets
| Self::Currencies => false,
_ => true,
}
}
pub fn custodial_path(&self, currency: &str) -> String {
let currency_lower = currency.to_lowercase();
match self {
Self::DepositAddress => format!("/api/v2/{}_address/", currency_lower),
Self::Withdrawal => format!("/api/v2/{}_withdrawal/", currency_lower),
_ => self.path().to_string(),
}
}
}
pub fn format_symbol(symbol: &Symbol, account_type: AccountType) -> String {
let base_pair = format!("{}{}", symbol.base.to_lowercase(), symbol.quote.to_lowercase());
match account_type {
AccountType::FuturesCross | AccountType::FuturesIsolated => {
format!("{}-perp", base_pair)
}
_ => base_pair,
}
}
pub fn map_kline_interval(interval: &str) -> &'static str {
match interval {
"1m" => "60", "3m" => "180", "5m" => "300", "15m" => "900", "30m" => "1800", "1h" => "3600", "2h" => "7200", "4h" => "14400", "6h" => "21600", "12h" => "43200", "1d" => "86400", "3d" => "259200", _ => "3600", }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_symbol() {
let symbol = Symbol::new("BTC", "USD");
assert_eq!(format_symbol(&symbol, AccountType::Spot), "btcusd");
let symbol = Symbol::new("ETH", "EUR");
assert_eq!(format_symbol(&symbol, AccountType::Spot), "etheur");
let symbol = Symbol::new("xrp", "btc");
assert_eq!(format_symbol(&symbol, AccountType::Spot), "xrpbtc");
let symbol = Symbol::new("BTC", "USD");
assert_eq!(format_symbol(&symbol, AccountType::FuturesCross), "btcusd-perp");
let symbol = Symbol::new("ETH", "USD");
assert_eq!(format_symbol(&symbol, AccountType::FuturesIsolated), "ethusd-perp");
}
#[test]
fn test_map_kline_interval() {
assert_eq!(map_kline_interval("1m"), "60");
assert_eq!(map_kline_interval("1h"), "3600");
assert_eq!(map_kline_interval("1d"), "86400");
assert_eq!(map_kline_interval("invalid"), "3600"); }
#[test]
fn test_endpoint_paths() {
assert_eq!(BitstampEndpoint::Ticker.path(), "/api/v2/ticker");
assert_eq!(BitstampEndpoint::Balance.path(), "/api/v2/account_balances/");
assert_eq!(
BitstampEndpoint::Ticker.path_with_pair("btcusd"),
"/api/v2/ticker/btcusd/"
);
}
#[test]
fn test_endpoint_methods() {
assert_eq!(BitstampEndpoint::Ticker.method(), "GET");
assert_eq!(BitstampEndpoint::Balance.method(), "POST");
assert_eq!(BitstampEndpoint::BuyLimit.method(), "POST");
}
#[test]
fn test_endpoint_privacy() {
assert!(!BitstampEndpoint::Ticker.is_private());
assert!(BitstampEndpoint::Balance.is_private());
assert!(BitstampEndpoint::BuyLimit.is_private());
}
}