Skip to main content

kraken_api_client/types/
common.rs

1//! Common domain types for Kraken API.
2
3use serde::{Deserialize, Serialize};
4
5/// Buy or sell side of an order.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum BuySell {
9    /// Buy order
10    Buy,
11    /// Sell order
12    Sell,
13}
14
15impl std::fmt::Display for BuySell {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            BuySell::Buy => write!(f, "buy"),
19            BuySell::Sell => write!(f, "sell"),
20        }
21    }
22}
23
24/// Order type for trading.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26#[serde(rename_all = "kebab-case")]
27pub enum OrderType {
28    /// Market order - execute immediately at best available price
29    Market,
30    /// Limit order - execute at specified price or better
31    Limit,
32    /// Stop-loss order - trigger market order when price reaches stop price
33    StopLoss,
34    /// Take-profit order - trigger market order when price reaches profit target
35    TakeProfit,
36    /// Stop-loss limit - trigger limit order when price reaches stop price
37    StopLossLimit,
38    /// Take-profit limit - trigger limit order when price reaches profit target
39    TakeProfitLimit,
40    /// Trailing stop order
41    TrailingStop,
42    /// Trailing stop limit order
43    TrailingStopLimit,
44    /// Settle position order
45    SettlePosition,
46}
47
48impl std::fmt::Display for OrderType {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        let s = match self {
51            OrderType::Market => "market",
52            OrderType::Limit => "limit",
53            OrderType::StopLoss => "stop-loss",
54            OrderType::TakeProfit => "take-profit",
55            OrderType::StopLossLimit => "stop-loss-limit",
56            OrderType::TakeProfitLimit => "take-profit-limit",
57            OrderType::TrailingStop => "trailing-stop",
58            OrderType::TrailingStopLimit => "trailing-stop-limit",
59            OrderType::SettlePosition => "settle-position",
60        };
61        write!(f, "{}", s)
62    }
63}
64
65/// Status of an order.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67#[serde(rename_all = "lowercase")]
68pub enum OrderStatus {
69    /// Order is pending (not yet submitted)
70    Pending,
71    /// Order is open and active
72    Open,
73    /// Order has been partially filled
74    #[serde(alias = "partial")]
75    PartiallyFilled,
76    /// Order has been completely filled
77    #[serde(alias = "filled")]
78    Closed,
79    /// Order has been canceled
80    Canceled,
81    /// Order has expired
82    Expired,
83}
84
85impl std::fmt::Display for OrderStatus {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            OrderStatus::Pending => write!(f, "pending"),
89            OrderStatus::Open => write!(f, "open"),
90            OrderStatus::PartiallyFilled => write!(f, "partially_filled"),
91            OrderStatus::Closed => write!(f, "closed"),
92            OrderStatus::Canceled => write!(f, "canceled"),
93            OrderStatus::Expired => write!(f, "expired"),
94        }
95    }
96}
97
98/// Time in force for orders.
99#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
100#[serde(rename_all = "UPPERCASE")]
101pub enum TimeInForce {
102    /// Good till canceled (default)
103    #[default]
104    GTC,
105    /// Immediate or cancel - fill what's possible immediately, cancel rest
106    IOC,
107    /// Good till date - order expires at specified time
108    GTD,
109}
110
111/// Order flags for special order behavior.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
113#[serde(rename_all = "lowercase")]
114pub enum OrderFlag {
115    /// Post-only order - will only make liquidity, not take it
116    Post,
117    /// Fee in base currency
118    #[serde(rename = "fcib")]
119    FeeInBase,
120    /// Fee in quote currency
121    #[serde(rename = "fciq")]
122    FeeInQuote,
123    /// No market price protection
124    #[serde(rename = "nompp")]
125    NoMarketPriceProtection,
126    /// Order volume in quote currency
127    #[serde(rename = "viqc")]
128    VolumeInQuote,
129}
130
131/// Trigger type for conditional orders.
132#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(rename_all = "lowercase")]
134pub enum TriggerType {
135    /// Trigger on last trade price
136    #[default]
137    Last,
138    /// Trigger on index price
139    Index,
140}
141
142/// Self-trade prevention mode.
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
144#[serde(rename_all = "kebab-case")]
145pub enum SelfTradePrevent {
146    /// Cancel newest order
147    CancelNewest,
148    /// Cancel oldest order
149    CancelOldest,
150    /// Cancel both orders
151    CancelBoth,
152}
153
154/// Asset class for trading pairs.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
156#[serde(rename_all = "lowercase")]
157pub enum AssetClass {
158    /// Currency/forex
159    Currency,
160    /// Cryptocurrency
161    #[serde(alias = "crypto")]
162    Cryptocurrency,
163}
164
165/// Ledger entry type.
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
167#[serde(rename_all = "lowercase")]
168pub enum LedgerType {
169    /// Trade execution
170    Trade,
171    /// Deposit
172    Deposit,
173    /// Withdrawal
174    Withdrawal,
175    /// Transfer between accounts
176    Transfer,
177    /// Margin trade
178    Margin,
179    /// Adjustment
180    Adjustment,
181    /// Rollover
182    Rollover,
183    /// Credit
184    Credit,
185    /// Settled
186    Settled,
187    /// Staking
188    Staking,
189    /// Dividend
190    Dividend,
191    /// Sale
192    Sale,
193    /// NFT-related
194    #[serde(rename = "nft")]
195    Nft,
196}
197
198/// Verification tier for rate limiting purposes.
199#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
200pub enum VerificationTier {
201    /// Starter tier (lowest limits)
202    #[default]
203    Starter,
204    /// Intermediate tier
205    Intermediate,
206    /// Pro tier (highest limits)
207    Pro,
208}
209
210impl VerificationTier {
211    /// Get the rate limit parameters for this verification tier.
212    ///
213    /// Returns a tuple of (max_counter, decay_rate_per_sec).
214    pub fn rate_limit_params(&self) -> (u32, f64) {
215        match self {
216            VerificationTier::Starter => (15, 0.33),
217            VerificationTier::Intermediate => (20, 0.5),
218            VerificationTier::Pro => (20, 1.0),
219        }
220    }
221}
222
223/// OHLC interval in minutes.
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
225#[serde(into = "u32", try_from = "u32")]
226pub enum OhlcInterval {
227    /// 1 minute
228    Min1,
229    /// 5 minutes
230    Min5,
231    /// 15 minutes
232    Min15,
233    /// 30 minutes
234    Min30,
235    /// 1 hour
236    Hour1,
237    /// 4 hours
238    Hour4,
239    /// 1 day
240    Day1,
241    /// 1 week
242    Week1,
243    /// 15 days
244    Day15,
245}
246
247impl From<OhlcInterval> for u32 {
248    fn from(interval: OhlcInterval) -> u32 {
249        match interval {
250            OhlcInterval::Min1 => 1,
251            OhlcInterval::Min5 => 5,
252            OhlcInterval::Min15 => 15,
253            OhlcInterval::Min30 => 30,
254            OhlcInterval::Hour1 => 60,
255            OhlcInterval::Hour4 => 240,
256            OhlcInterval::Day1 => 1440,
257            OhlcInterval::Week1 => 10080,
258            OhlcInterval::Day15 => 21600,
259        }
260    }
261}
262
263impl TryFrom<u32> for OhlcInterval {
264    type Error = String;
265
266    fn try_from(value: u32) -> Result<Self, Self::Error> {
267        match value {
268            1 => Ok(OhlcInterval::Min1),
269            5 => Ok(OhlcInterval::Min5),
270            15 => Ok(OhlcInterval::Min15),
271            30 => Ok(OhlcInterval::Min30),
272            60 => Ok(OhlcInterval::Hour1),
273            240 => Ok(OhlcInterval::Hour4),
274            1440 => Ok(OhlcInterval::Day1),
275            10080 => Ok(OhlcInterval::Week1),
276            21600 => Ok(OhlcInterval::Day15),
277            _ => Err(format!("Invalid OHLC interval: {}", value)),
278        }
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn test_buy_sell_serde() {
288        assert_eq!(
289            serde_json::to_string(&BuySell::Buy).unwrap(),
290            r#""buy""#
291        );
292        assert_eq!(
293            serde_json::from_str::<BuySell>(r#""sell""#).unwrap(),
294            BuySell::Sell
295        );
296    }
297
298    #[test]
299    fn test_order_type_serde() {
300        assert_eq!(
301            serde_json::to_string(&OrderType::StopLoss).unwrap(),
302            r#""stop-loss""#
303        );
304        assert_eq!(
305            serde_json::from_str::<OrderType>(r#""take-profit-limit""#).unwrap(),
306            OrderType::TakeProfitLimit
307        );
308    }
309
310    #[test]
311    fn test_ohlc_interval_conversion() {
312        assert_eq!(u32::from(OhlcInterval::Hour1), 60);
313        assert_eq!(OhlcInterval::try_from(1440).unwrap(), OhlcInterval::Day1);
314        assert!(OhlcInterval::try_from(999).is_err());
315    }
316}