Skip to main content

nautilus_kraken/common/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Enumerations that model Kraken string/int enums across HTTP and WebSocket payloads.
17
18use nautilus_model::enums::{LiquiditySide, MarketStatusAction, OrderSide, OrderStatus, OrderType};
19use serde::{Deserialize, Serialize};
20use strum::{AsRefStr, Display, EnumString, FromRepr};
21
22/// Kraken API environment (live or demo).
23#[derive(
24    Clone,
25    Copy,
26    Debug,
27    Default,
28    Display,
29    AsRefStr,
30    EnumString,
31    FromRepr,
32    PartialEq,
33    Eq,
34    Hash,
35    Serialize,
36    Deserialize,
37)]
38#[cfg_attr(
39    feature = "python",
40    pyo3::pyclass(
41        module = "nautilus_trader.adapters.kraken",
42        eq,
43        eq_int,
44        frozen,
45        hash,
46        from_py_object,
47        rename_all = "SCREAMING_SNAKE_CASE",
48    )
49)]
50#[cfg_attr(
51    feature = "python",
52    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.kraken")
53)]
54#[serde(rename_all = "lowercase")]
55#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
56pub enum KrakenEnvironment {
57    #[default]
58    Live,
59    Demo,
60}
61
62/// Kraken product type (spot or futures).
63#[derive(
64    Clone,
65    Copy,
66    Debug,
67    Default,
68    Display,
69    AsRefStr,
70    EnumString,
71    FromRepr,
72    PartialEq,
73    Eq,
74    Hash,
75    Serialize,
76    Deserialize,
77)]
78#[cfg_attr(
79    feature = "python",
80    pyo3::pyclass(
81        module = "nautilus_trader.adapters.kraken",
82        eq,
83        eq_int,
84        frozen,
85        hash,
86        from_py_object,
87        rename_all = "SCREAMING_SNAKE_CASE",
88    )
89)]
90#[cfg_attr(
91    feature = "python",
92    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.kraken")
93)]
94#[serde(rename_all = "lowercase")]
95#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
96pub enum KrakenProductType {
97    #[default]
98    Spot,
99    Futures,
100}
101
102/// Kraken spot order type.
103#[derive(
104    Clone,
105    Copy,
106    Debug,
107    Display,
108    AsRefStr,
109    EnumString,
110    FromRepr,
111    PartialEq,
112    Eq,
113    Hash,
114    Serialize,
115    Deserialize,
116)]
117#[cfg_attr(
118    feature = "python",
119    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
120)]
121#[serde(rename_all = "lowercase")]
122#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
123pub enum KrakenOrderType {
124    Market,
125    Limit,
126    #[serde(rename = "stop-loss")]
127    #[strum(serialize = "stop-loss")]
128    StopLoss,
129    #[serde(rename = "take-profit")]
130    #[strum(serialize = "take-profit")]
131    TakeProfit,
132    #[serde(rename = "stop-loss-limit")]
133    #[strum(serialize = "stop-loss-limit")]
134    StopLossLimit,
135    #[serde(rename = "take-profit-limit")]
136    #[strum(serialize = "take-profit-limit")]
137    TakeProfitLimit,
138    #[serde(rename = "trailing-stop")]
139    #[strum(serialize = "trailing-stop")]
140    TrailingStop,
141    #[serde(rename = "trailing-stop-limit")]
142    #[strum(serialize = "trailing-stop-limit")]
143    TrailingStopLimit,
144    #[serde(rename = "settle-position")]
145    #[strum(serialize = "settle-position")]
146    SettlePosition,
147}
148
149/// Kraken order side (buy or sell).
150#[derive(
151    Clone,
152    Copy,
153    Debug,
154    Display,
155    AsRefStr,
156    EnumString,
157    FromRepr,
158    PartialEq,
159    Eq,
160    Hash,
161    Serialize,
162    Deserialize,
163)]
164#[cfg_attr(
165    feature = "python",
166    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
167)]
168#[serde(rename_all = "lowercase")]
169#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
170pub enum KrakenOrderSide {
171    Buy,
172    Sell,
173}
174
175/// Kraken time-in-force for orders.
176#[derive(
177    Clone,
178    Copy,
179    Debug,
180    Display,
181    AsRefStr,
182    EnumString,
183    FromRepr,
184    PartialEq,
185    Eq,
186    Hash,
187    Serialize,
188    Deserialize,
189)]
190#[cfg_attr(
191    feature = "python",
192    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
193)]
194#[serde(rename_all = "UPPERCASE")]
195#[strum(ascii_case_insensitive, serialize_all = "UPPERCASE")]
196pub enum KrakenTimeInForce {
197    #[serde(rename = "IOC")]
198    #[strum(serialize = "IOC")]
199    ImmediateOrCancel,
200    #[serde(rename = "FOK")]
201    #[strum(serialize = "FOK")]
202    FillOrKill,
203    #[serde(rename = "GTC")]
204    #[strum(serialize = "GTC")]
205    GoodTilCancelled,
206    #[serde(rename = "GTD")]
207    #[strum(serialize = "GTD")]
208    GoodTilDate,
209}
210
211/// Kraken order status.
212#[derive(
213    Clone,
214    Copy,
215    Debug,
216    Display,
217    AsRefStr,
218    EnumString,
219    FromRepr,
220    PartialEq,
221    Eq,
222    Hash,
223    Serialize,
224    Deserialize,
225)]
226#[cfg_attr(
227    feature = "python",
228    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
229)]
230#[serde(rename_all = "lowercase")]
231#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
232pub enum KrakenOrderStatus {
233    Pending,
234    Open,
235    Closed,
236    Canceled,
237    Expired,
238}
239
240/// Kraken position side (long or short).
241#[derive(
242    Clone,
243    Copy,
244    Debug,
245    Display,
246    AsRefStr,
247    EnumString,
248    FromRepr,
249    PartialEq,
250    Eq,
251    Hash,
252    Serialize,
253    Deserialize,
254)]
255#[cfg_attr(
256    feature = "python",
257    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
258)]
259#[serde(rename_all = "lowercase")]
260#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
261pub enum KrakenPositionSide {
262    Long,
263    Short,
264}
265
266/// Kraken trading pair status.
267#[derive(
268    Clone,
269    Copy,
270    Debug,
271    Display,
272    AsRefStr,
273    EnumString,
274    FromRepr,
275    PartialEq,
276    Eq,
277    Hash,
278    Serialize,
279    Deserialize,
280)]
281#[cfg_attr(
282    feature = "python",
283    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
284)]
285#[serde(rename_all = "snake_case")]
286#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
287pub enum KrakenPairStatus {
288    Online,
289    #[serde(rename = "cancel_only")]
290    #[strum(serialize = "cancel_only")]
291    CancelOnly,
292    #[serde(rename = "post_only")]
293    #[strum(serialize = "post_only")]
294    PostOnly,
295    #[serde(rename = "limit_only")]
296    #[strum(serialize = "limit_only")]
297    LimitOnly,
298    #[serde(rename = "reduce_only")]
299    #[strum(serialize = "reduce_only")]
300    ReduceOnly,
301}
302
303/// Kraken system status.
304#[derive(
305    Clone,
306    Copy,
307    Debug,
308    Display,
309    AsRefStr,
310    EnumString,
311    FromRepr,
312    PartialEq,
313    Eq,
314    Hash,
315    Serialize,
316    Deserialize,
317)]
318#[cfg_attr(
319    feature = "python",
320    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
321)]
322#[serde(rename_all = "lowercase")]
323#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
324pub enum KrakenSystemStatus {
325    Online,
326    Maintenance,
327    #[serde(rename = "cancel_only")]
328    #[strum(serialize = "cancel_only")]
329    CancelOnly,
330    #[serde(rename = "post_only")]
331    #[strum(serialize = "post_only")]
332    PostOnly,
333}
334
335/// Kraken asset class.
336#[derive(
337    Clone,
338    Copy,
339    Debug,
340    Display,
341    AsRefStr,
342    EnumString,
343    FromRepr,
344    PartialEq,
345    Eq,
346    Hash,
347    Serialize,
348    Deserialize,
349)]
350#[cfg_attr(
351    feature = "python",
352    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
353)]
354#[serde(rename_all = "lowercase")]
355#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
356pub enum KrakenAssetClass {
357    Currency,
358    #[serde(rename = "tokenized_asset")]
359    #[strum(serialize = "tokenized_asset")]
360    TokenizedAsset,
361}
362
363/// Kraken futures order type.
364#[derive(
365    Clone,
366    Copy,
367    Debug,
368    Display,
369    AsRefStr,
370    EnumString,
371    FromRepr,
372    PartialEq,
373    Eq,
374    Hash,
375    Serialize,
376    Deserialize,
377)]
378#[cfg_attr(
379    feature = "python",
380    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
381)]
382#[serde(rename_all = "lowercase")]
383#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
384pub enum KrakenFuturesOrderType {
385    #[serde(rename = "lmt", alias = "limit")]
386    #[strum(serialize = "lmt")]
387    Limit,
388    #[serde(rename = "ioc")]
389    #[strum(serialize = "ioc")]
390    Ioc,
391    #[serde(rename = "post")]
392    #[strum(serialize = "post")]
393    Post,
394    #[serde(rename = "mkt", alias = "market")]
395    #[strum(serialize = "mkt")]
396    Market,
397    #[serde(rename = "stp")]
398    #[strum(serialize = "stp")]
399    Stop,
400    #[serde(rename = "stop")]
401    #[strum(serialize = "stop")]
402    StopLower,
403    #[serde(rename = "take_profit")]
404    #[strum(serialize = "take_profit")]
405    TakeProfit,
406    #[serde(rename = "stop_loss")]
407    #[strum(serialize = "stop_loss")]
408    StopLoss,
409    #[serde(rename = "unknown")]
410    Unknown,
411}
412
413/// Event types from Kraken Futures sendorder/editorder responses.
414#[derive(
415    Clone,
416    Copy,
417    Debug,
418    Display,
419    AsRefStr,
420    EnumString,
421    FromRepr,
422    PartialEq,
423    Eq,
424    Hash,
425    Serialize,
426    Deserialize,
427)]
428#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
429#[strum(ascii_case_insensitive, serialize_all = "SCREAMING_SNAKE_CASE")]
430pub enum KrakenFuturesOrderEventType {
431    /// Order was placed.
432    Place,
433    /// Legacy history endpoint fill event.
434    Fill,
435    /// Send-order execution event.
436    Execution,
437    /// Order was rejected.
438    Reject,
439    /// Order was cancelled.
440    Cancel,
441    /// Order was edited.
442    Edit,
443    /// Order expired.
444    #[serde(alias = "EXPIRED")]
445    #[strum(serialize = "EXPIRED")]
446    Expire,
447}
448
449/// Kraken futures order status.
450#[derive(
451    Clone,
452    Copy,
453    Debug,
454    Display,
455    AsRefStr,
456    EnumString,
457    FromRepr,
458    PartialEq,
459    Eq,
460    Hash,
461    Serialize,
462    Deserialize,
463)]
464#[cfg_attr(
465    feature = "python",
466    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
467)]
468#[serde(rename_all = "camelCase")]
469#[strum(ascii_case_insensitive, serialize_all = "camelCase")]
470pub enum KrakenFuturesOrderStatus {
471    Untouched,
472    PartiallyFilled,
473    Filled,
474    Cancelled,
475    Expired,
476}
477
478/// Kraken futures trigger signal type.
479#[derive(
480    Clone,
481    Copy,
482    Debug,
483    Display,
484    AsRefStr,
485    EnumString,
486    FromRepr,
487    PartialEq,
488    Eq,
489    Hash,
490    Serialize,
491    Deserialize,
492)]
493#[cfg_attr(
494    feature = "python",
495    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
496)]
497#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
498pub enum KrakenTriggerSignal {
499    #[serde(rename = "last", alias = "last_price")]
500    Last,
501    #[serde(rename = "mark", alias = "mark_price")]
502    Mark,
503    #[serde(
504        rename = "spot",
505        alias = "spot_price",
506        alias = "index",
507        alias = "index_price"
508    )]
509    #[strum(
510        serialize = "spot",
511        serialize = "spot_price",
512        serialize = "index",
513        serialize = "index_price"
514    )]
515    Index,
516    #[serde(rename = "unknown")]
517    Unknown,
518}
519
520/// Trigger reference price for Kraken spot conditional orders.
521#[derive(
522    Clone,
523    Copy,
524    Debug,
525    Display,
526    AsRefStr,
527    EnumString,
528    FromRepr,
529    PartialEq,
530    Eq,
531    Hash,
532    Serialize,
533    Deserialize,
534)]
535#[cfg_attr(
536    feature = "python",
537    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
538)]
539#[serde(rename_all = "lowercase")]
540#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
541pub enum KrakenSpotTrigger {
542    /// Last traded price in the order book.
543    Last,
544    /// Index price for the broader market.
545    Index,
546}
547
548/// Kraken Futures fill classification.
549#[derive(
550    Clone,
551    Copy,
552    Debug,
553    Display,
554    AsRefStr,
555    EnumString,
556    FromRepr,
557    PartialEq,
558    Eq,
559    Hash,
560    Serialize,
561    Deserialize,
562)]
563#[cfg_attr(
564    feature = "python",
565    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
566)]
567#[serde(rename_all = "camelCase")]
568#[strum(ascii_case_insensitive, serialize_all = "camelCase")]
569pub enum KrakenFillType {
570    Maker,
571    Taker,
572    Liquidation,
573    PartialLiquidation,
574    Assignor,
575    Assignee,
576    TakerAfterEdit,
577    UnwindBankrupt,
578    UnwindCounterparty,
579}
580
581/// Kraken API result status.
582#[derive(
583    Clone,
584    Copy,
585    Debug,
586    Display,
587    AsRefStr,
588    EnumString,
589    FromRepr,
590    PartialEq,
591    Eq,
592    Hash,
593    Serialize,
594    Deserialize,
595)]
596#[cfg_attr(
597    feature = "python",
598    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
599)]
600#[serde(rename_all = "lowercase")]
601#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
602pub enum KrakenApiResult {
603    Success,
604    Error,
605}
606
607/// Kraken futures instrument type.
608#[derive(
609    Clone,
610    Copy,
611    Debug,
612    Display,
613    AsRefStr,
614    EnumString,
615    FromRepr,
616    PartialEq,
617    Eq,
618    Hash,
619    Serialize,
620    Deserialize,
621)]
622#[cfg_attr(
623    feature = "python",
624    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
625)]
626#[serde(rename_all = "snake_case")]
627#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
628pub enum KrakenInstrumentType {
629    /// Inverse perpetual futures (e.g., PI_XBTUSD).
630    FuturesInverse,
631    /// Flexible/linear perpetual futures (e.g., PF_XBTUSD).
632    FlexibleFutures,
633}
634
635/// Kraken futures send order status.
636#[derive(
637    Clone,
638    Copy,
639    Debug,
640    Display,
641    AsRefStr,
642    EnumString,
643    FromRepr,
644    PartialEq,
645    Eq,
646    Hash,
647    Serialize,
648    Deserialize,
649)]
650#[cfg_attr(
651    feature = "python",
652    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
653)]
654#[serde(rename_all = "camelCase")]
655#[strum(ascii_case_insensitive, serialize_all = "camelCase")]
656pub enum KrakenSendStatus {
657    /// Order was successfully placed.
658    Placed,
659    /// Order was cancelled.
660    Cancelled,
661    /// Order was edited.
662    Edited,
663    /// Order not found.
664    NotFound,
665    /// No orders matched the cancel-all request.
666    ///
667    /// Returned by the Kraken Futures `cancelallorders` endpoint as the
668    /// `cancelStatus.status` field. The accompanying `cancelledOrders` array
669    /// may still be populated for orders that were canceled in the same call,
670    /// so callers must inspect that array rather than treating this status
671    /// as an error.
672    NoOrdersToCancel,
673    /// Insufficient available funds.
674    InsufficientAvailableFunds,
675    /// Invalid order type.
676    InvalidOrderType,
677    /// Invalid size.
678    InvalidSize,
679    /// Would cause liquidation.
680    WouldCauseLiquidation,
681    /// Post-only order would have crossed.
682    PostWouldExecute,
683    /// Reduce-only order would increase position.
684    ReduceOnlyWouldIncreasePosition,
685}
686
687/// Kraken futures trigger side for conditional orders.
688#[derive(
689    Clone,
690    Copy,
691    Debug,
692    Display,
693    AsRefStr,
694    EnumString,
695    FromRepr,
696    PartialEq,
697    Eq,
698    Hash,
699    Serialize,
700    Deserialize,
701)]
702#[cfg_attr(
703    feature = "python",
704    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
705)]
706#[serde(rename_all = "snake_case")]
707#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
708pub enum KrakenTriggerSide {
709    /// Trigger when price goes above the trigger price.
710    #[serde(rename = "trigger_above")]
711    #[strum(serialize = "trigger_above")]
712    TriggerAbove,
713    /// Trigger when price goes below the trigger price.
714    #[serde(rename = "trigger_below")]
715    #[strum(serialize = "trigger_below")]
716    TriggerBelow,
717    /// The venue's `"unknown"` sentinel, carrying no directional intent.
718    #[serde(rename = "unknown")]
719    Unknown,
720}
721
722impl From<KrakenOrderSide> for OrderSide {
723    fn from(value: KrakenOrderSide) -> Self {
724        match value {
725            KrakenOrderSide::Buy => Self::Buy,
726            KrakenOrderSide::Sell => Self::Sell,
727        }
728    }
729}
730
731impl From<KrakenFillType> for LiquiditySide {
732    fn from(value: KrakenFillType) -> Self {
733        match value {
734            KrakenFillType::Maker => Self::Maker,
735            KrakenFillType::Taker | KrakenFillType::TakerAfterEdit => Self::Taker,
736            KrakenFillType::Liquidation
737            | KrakenFillType::PartialLiquidation
738            | KrakenFillType::Assignor
739            | KrakenFillType::Assignee
740            | KrakenFillType::UnwindBankrupt
741            | KrakenFillType::UnwindCounterparty => Self::NoLiquiditySide,
742        }
743    }
744}
745
746impl From<KrakenOrderType> for OrderType {
747    /// Maps Kraken order types to Nautilus order types for reconciliation.
748    ///
749    /// Trailing stops map to their non-trailing equivalents because
750    /// Kraken reports lack the offset fields required to reconstruct
751    /// a trailing order during reconciliation.
752    fn from(value: KrakenOrderType) -> Self {
753        match value {
754            KrakenOrderType::Market => Self::Market,
755            KrakenOrderType::Limit => Self::Limit,
756            KrakenOrderType::StopLoss => Self::StopMarket,
757            KrakenOrderType::TakeProfit => Self::MarketIfTouched,
758            KrakenOrderType::StopLossLimit => Self::StopLimit,
759            KrakenOrderType::TakeProfitLimit => Self::LimitIfTouched,
760            KrakenOrderType::TrailingStop => Self::StopMarket,
761            KrakenOrderType::TrailingStopLimit => Self::StopLimit,
762            KrakenOrderType::SettlePosition => Self::Market,
763        }
764    }
765}
766
767impl From<KrakenOrderStatus> for OrderStatus {
768    fn from(value: KrakenOrderStatus) -> Self {
769        match value {
770            KrakenOrderStatus::Pending => Self::Initialized,
771            KrakenOrderStatus::Open => Self::Accepted,
772            KrakenOrderStatus::Closed => Self::Filled,
773            KrakenOrderStatus::Canceled => Self::Canceled,
774            KrakenOrderStatus::Expired => Self::Expired,
775        }
776    }
777}
778
779impl From<KrakenFuturesOrderType> for OrderType {
780    fn from(value: KrakenFuturesOrderType) -> Self {
781        match value {
782            KrakenFuturesOrderType::Limit
783            | KrakenFuturesOrderType::Ioc
784            | KrakenFuturesOrderType::Post => Self::Limit,
785            KrakenFuturesOrderType::Market => Self::Market,
786            KrakenFuturesOrderType::Stop | KrakenFuturesOrderType::StopLower => Self::StopMarket,
787            KrakenFuturesOrderType::TakeProfit => Self::MarketIfTouched,
788            KrakenFuturesOrderType::StopLoss => Self::StopMarket,
789            KrakenFuturesOrderType::Unknown => {
790                log::warn!(
791                    "KrakenFuturesOrderType::Unknown received from venue, defaulting to Market"
792                );
793                Self::Market
794            }
795        }
796    }
797}
798
799impl From<OrderSide> for KrakenOrderSide {
800    fn from(value: OrderSide) -> Self {
801        match value {
802            OrderSide::Buy => Self::Buy,
803            OrderSide::Sell => Self::Sell,
804        }
805    }
806}
807
808impl From<KrakenFuturesOrderStatus> for OrderStatus {
809    fn from(value: KrakenFuturesOrderStatus) -> Self {
810        match value {
811            KrakenFuturesOrderStatus::Untouched => Self::Accepted,
812            KrakenFuturesOrderStatus::PartiallyFilled => Self::PartiallyFilled,
813            KrakenFuturesOrderStatus::Filled => Self::Filled,
814            KrakenFuturesOrderStatus::Cancelled => Self::Canceled,
815            KrakenFuturesOrderStatus::Expired => Self::Expired,
816        }
817    }
818}
819
820impl From<KrakenPairStatus> for MarketStatusAction {
821    fn from(value: KrakenPairStatus) -> Self {
822        match value {
823            KrakenPairStatus::Online => Self::Trading,
824            KrakenPairStatus::CancelOnly => Self::Halt,
825            KrakenPairStatus::PostOnly => Self::Pause,
826            KrakenPairStatus::LimitOnly => Self::Pause,
827            KrakenPairStatus::ReduceOnly => Self::Pause,
828        }
829    }
830}
831
832/// Determines the product type from a Kraken symbol.
833///
834/// Futures symbols have the following prefixes:
835/// - `PI_` - Perpetual Inverse futures (e.g., `PI_XBTUSD`)
836/// - `PF_` - Perpetual Fixed-margin futures (e.g., `PF_XBTUSD`)
837/// - `PV_` - Perpetual Vanilla futures (e.g., `PV_XRPXBT`)
838/// - `FI_` - Fixed maturity Inverse futures (e.g., `FI_XBTUSD_230929`)
839/// - `FF_` - Flex futures
840///
841/// All other symbols are considered spot.
842#[must_use]
843pub fn product_type_from_symbol(symbol: &str) -> KrakenProductType {
844    if symbol.starts_with("PI_")
845        || symbol.starts_with("PF_")
846        || symbol.starts_with("PV_")
847        || symbol.starts_with("FI_")
848        || symbol.starts_with("FF_")
849    {
850        KrakenProductType::Futures
851    } else {
852        KrakenProductType::Spot
853    }
854}
855
856#[cfg(test)]
857mod tests {
858    use nautilus_model::enums::{LiquiditySide, MarketStatusAction, OrderType};
859    use rstest::rstest;
860
861    use super::*;
862
863    #[rstest]
864    #[case::online(KrakenPairStatus::Online, MarketStatusAction::Trading)]
865    #[case::cancel_only(KrakenPairStatus::CancelOnly, MarketStatusAction::Halt)]
866    #[case::post_only(KrakenPairStatus::PostOnly, MarketStatusAction::Pause)]
867    #[case::limit_only(KrakenPairStatus::LimitOnly, MarketStatusAction::Pause)]
868    #[case::reduce_only(KrakenPairStatus::ReduceOnly, MarketStatusAction::Pause)]
869    fn test_pair_status_to_market_status_action(
870        #[case] input: KrakenPairStatus,
871        #[case] expected: MarketStatusAction,
872    ) {
873        assert_eq!(MarketStatusAction::from(input), expected);
874    }
875
876    #[rstest]
877    #[case::trailing_stop(KrakenOrderType::TrailingStop, OrderType::StopMarket)]
878    #[case::trailing_stop_limit(KrakenOrderType::TrailingStopLimit, OrderType::StopLimit)]
879    fn test_trailing_stop_order_type_mapping(
880        #[case] input: KrakenOrderType,
881        #[case] expected: OrderType,
882    ) {
883        assert_eq!(OrderType::from(input), expected);
884    }
885
886    #[rstest]
887    #[case::maker(KrakenFillType::Maker, LiquiditySide::Maker)]
888    #[case::taker(KrakenFillType::Taker, LiquiditySide::Taker)]
889    #[case::liquidation(KrakenFillType::Liquidation, LiquiditySide::NoLiquiditySide)]
890    #[case::partial_liquidation(KrakenFillType::PartialLiquidation, LiquiditySide::NoLiquiditySide)]
891    #[case::assignor(KrakenFillType::Assignor, LiquiditySide::NoLiquiditySide)]
892    #[case::assignee(KrakenFillType::Assignee, LiquiditySide::NoLiquiditySide)]
893    #[case::taker_after_edit(KrakenFillType::TakerAfterEdit, LiquiditySide::Taker)]
894    #[case::unwind_bankrupt(KrakenFillType::UnwindBankrupt, LiquiditySide::NoLiquiditySide)]
895    #[case::unwind_counterparty(KrakenFillType::UnwindCounterparty, LiquiditySide::NoLiquiditySide)]
896    fn test_fill_type_to_liquidity_side(
897        #[case] fill_type: KrakenFillType,
898        #[case] expected: LiquiditySide,
899    ) {
900        assert_eq!(LiquiditySide::from(fill_type), expected);
901    }
902
903    #[rstest]
904    fn test_fill_type_deserialization() {
905        let json = include_str!("../../test_data/futures_fill_types.json");
906        let fill_types: Vec<KrakenFillType> = serde_json::from_str(json).unwrap();
907
908        assert_eq!(
909            fill_types,
910            vec![
911                KrakenFillType::Maker,
912                KrakenFillType::Taker,
913                KrakenFillType::Liquidation,
914                KrakenFillType::PartialLiquidation,
915                KrakenFillType::Assignor,
916                KrakenFillType::Assignee,
917                KrakenFillType::TakerAfterEdit,
918                KrakenFillType::UnwindBankrupt,
919                KrakenFillType::UnwindCounterparty,
920            ]
921        );
922    }
923
924    #[rstest]
925    fn test_time_in_force_deserializes_fok() {
926        // FOK (fill-or-kill) is a valid Kraken timeinforce; inbound executions
927        // must not hard-fail on it.
928        let tif: KrakenTimeInForce = serde_json::from_str("\"FOK\"").unwrap();
929        assert_eq!(tif, KrakenTimeInForce::FillOrKill);
930    }
931
932    #[rstest]
933    #[case("\"placed\"", KrakenSendStatus::Placed)]
934    #[case("\"cancelled\"", KrakenSendStatus::Cancelled)]
935    #[case("\"edited\"", KrakenSendStatus::Edited)]
936    #[case("\"notFound\"", KrakenSendStatus::NotFound)]
937    #[case("\"noOrdersToCancel\"", KrakenSendStatus::NoOrdersToCancel)]
938    #[case(
939        "\"insufficientAvailableFunds\"",
940        KrakenSendStatus::InsufficientAvailableFunds
941    )]
942    #[case("\"invalidOrderType\"", KrakenSendStatus::InvalidOrderType)]
943    #[case("\"invalidSize\"", KrakenSendStatus::InvalidSize)]
944    #[case("\"wouldCauseLiquidation\"", KrakenSendStatus::WouldCauseLiquidation)]
945    #[case("\"postWouldExecute\"", KrakenSendStatus::PostWouldExecute)]
946    #[case(
947        "\"reduceOnlyWouldIncreasePosition\"",
948        KrakenSendStatus::ReduceOnlyWouldIncreasePosition
949    )]
950    fn test_send_status_deserialization(#[case] raw: &str, #[case] expected: KrakenSendStatus) {
951        let parsed: KrakenSendStatus = serde_json::from_str(raw).unwrap();
952        assert_eq!(parsed, expected);
953    }
954
955    #[rstest]
956    #[case("\"last\"", KrakenTriggerSignal::Last)]
957    #[case("\"last_price\"", KrakenTriggerSignal::Last)]
958    #[case("\"mark\"", KrakenTriggerSignal::Mark)]
959    #[case("\"mark_price\"", KrakenTriggerSignal::Mark)]
960    #[case("\"spot\"", KrakenTriggerSignal::Index)]
961    #[case("\"spot_price\"", KrakenTriggerSignal::Index)]
962    #[case("\"index\"", KrakenTriggerSignal::Index)]
963    #[case("\"index_price\"", KrakenTriggerSignal::Index)]
964    #[case("\"unknown\"", KrakenTriggerSignal::Unknown)]
965    fn test_trigger_signal_deserialization(
966        #[case] raw: &str,
967        #[case] expected: KrakenTriggerSignal,
968    ) {
969        let parsed: KrakenTriggerSignal = serde_json::from_str(raw).unwrap();
970        assert_eq!(parsed, expected);
971    }
972
973    #[rstest]
974    #[case("\"trigger_above\"", KrakenTriggerSide::TriggerAbove)]
975    #[case("\"trigger_below\"", KrakenTriggerSide::TriggerBelow)]
976    #[case("\"unknown\"", KrakenTriggerSide::Unknown)]
977    fn test_trigger_side_deserialization(#[case] raw: &str, #[case] expected: KrakenTriggerSide) {
978        let parsed: KrakenTriggerSide = serde_json::from_str(raw).unwrap();
979        assert_eq!(parsed, expected);
980    }
981
982    #[rstest]
983    #[case("\"lmt\"", KrakenFuturesOrderType::Limit)]
984    #[case("\"limit\"", KrakenFuturesOrderType::Limit)]
985    #[case("\"ioc\"", KrakenFuturesOrderType::Ioc)]
986    #[case("\"post\"", KrakenFuturesOrderType::Post)]
987    #[case("\"mkt\"", KrakenFuturesOrderType::Market)]
988    #[case("\"market\"", KrakenFuturesOrderType::Market)]
989    #[case("\"stp\"", KrakenFuturesOrderType::Stop)]
990    #[case("\"stop\"", KrakenFuturesOrderType::StopLower)]
991    #[case("\"take_profit\"", KrakenFuturesOrderType::TakeProfit)]
992    #[case("\"stop_loss\"", KrakenFuturesOrderType::StopLoss)]
993    #[case("\"unknown\"", KrakenFuturesOrderType::Unknown)]
994    fn test_futures_order_type_deserialization(
995        #[case] raw: &str,
996        #[case] expected: KrakenFuturesOrderType,
997    ) {
998        let parsed: KrakenFuturesOrderType = serde_json::from_str(raw).unwrap();
999        assert_eq!(parsed, expected);
1000    }
1001
1002    #[rstest]
1003    fn test_order_metadata_enums_fail_loud_on_unmodeled_value() {
1004        // Only the venue's documented "unknown" sentinel maps to Unknown; any other
1005        // unmodeled value must fail deserialization rather than be silently absorbed.
1006        assert!(serde_json::from_str::<KrakenFuturesOrderType>("\"iceberg\"").is_err());
1007        assert!(serde_json::from_str::<KrakenTriggerSignal>("\"vwap\"").is_err());
1008        assert!(serde_json::from_str::<KrakenTriggerSide>("\"sideways\"").is_err());
1009    }
1010
1011    #[rstest]
1012    fn test_futures_order_type_unknown_maps_to_market_default() {
1013        assert_eq!(
1014            OrderType::from(KrakenFuturesOrderType::Unknown),
1015            OrderType::Market,
1016        );
1017    }
1018
1019    #[rstest]
1020    #[case("\"PLACE\"", KrakenFuturesOrderEventType::Place)]
1021    #[case("\"FILL\"", KrakenFuturesOrderEventType::Fill)]
1022    #[case("\"EXECUTION\"", KrakenFuturesOrderEventType::Execution)]
1023    #[case("\"REJECT\"", KrakenFuturesOrderEventType::Reject)]
1024    #[case("\"CANCEL\"", KrakenFuturesOrderEventType::Cancel)]
1025    #[case("\"EDIT\"", KrakenFuturesOrderEventType::Edit)]
1026    #[case("\"EXPIRE\"", KrakenFuturesOrderEventType::Expire)]
1027    #[case("\"EXPIRED\"", KrakenFuturesOrderEventType::Expire)]
1028    fn test_futures_order_event_type_deserialization(
1029        #[case] raw: &str,
1030        #[case] expected: KrakenFuturesOrderEventType,
1031    ) {
1032        let parsed: KrakenFuturesOrderEventType = serde_json::from_str(raw).unwrap();
1033        assert_eq!(parsed, expected);
1034    }
1035}