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 order lifecycle status from `POST /orders/status`, which
479/// reports orders open or with a fill/cancel event in the last 5 seconds.
480#[derive(
481    Clone,
482    Copy,
483    Debug,
484    Display,
485    AsRefStr,
486    EnumString,
487    FromRepr,
488    PartialEq,
489    Eq,
490    Hash,
491    Serialize,
492    Deserialize,
493)]
494#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
495#[strum(ascii_case_insensitive, serialize_all = "SCREAMING_SNAKE_CASE")]
496pub enum KrakenFuturesOrderLifecycleStatus {
497    /// Order entered the book and is open.
498    EnteredBook,
499    /// Order fully executed.
500    FullyExecuted,
501    /// Order rejected.
502    Rejected,
503    /// Order cancelled, including a part-filled removal whose remainder was
504    /// discarded.
505    Cancelled,
506    /// Trigger order placed and waiting.
507    TriggerPlaced,
508    /// Trigger order failed to activate.
509    TriggerActivationFailure,
510}
511
512/// Kraken futures trigger signal type.
513#[derive(
514    Clone,
515    Copy,
516    Debug,
517    Display,
518    AsRefStr,
519    EnumString,
520    FromRepr,
521    PartialEq,
522    Eq,
523    Hash,
524    Serialize,
525    Deserialize,
526)]
527#[cfg_attr(
528    feature = "python",
529    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
530)]
531#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
532pub enum KrakenTriggerSignal {
533    #[serde(rename = "last", alias = "last_price")]
534    Last,
535    #[serde(rename = "mark", alias = "mark_price")]
536    Mark,
537    #[serde(
538        rename = "spot",
539        alias = "spot_price",
540        alias = "index",
541        alias = "index_price"
542    )]
543    #[strum(
544        serialize = "spot",
545        serialize = "spot_price",
546        serialize = "index",
547        serialize = "index_price"
548    )]
549    Index,
550    #[serde(rename = "unknown")]
551    Unknown,
552}
553
554/// Trigger reference price for Kraken spot conditional orders.
555#[derive(
556    Clone,
557    Copy,
558    Debug,
559    Display,
560    AsRefStr,
561    EnumString,
562    FromRepr,
563    PartialEq,
564    Eq,
565    Hash,
566    Serialize,
567    Deserialize,
568)]
569#[cfg_attr(
570    feature = "python",
571    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
572)]
573#[serde(rename_all = "lowercase")]
574#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
575pub enum KrakenSpotTrigger {
576    /// Last traded price in the order book.
577    Last,
578    /// Index price for the broader market.
579    Index,
580}
581
582/// Kraken Futures fill classification.
583#[derive(
584    Clone,
585    Copy,
586    Debug,
587    Display,
588    AsRefStr,
589    EnumString,
590    FromRepr,
591    PartialEq,
592    Eq,
593    Hash,
594    Serialize,
595    Deserialize,
596)]
597#[cfg_attr(
598    feature = "python",
599    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
600)]
601#[serde(rename_all = "camelCase")]
602#[strum(ascii_case_insensitive, serialize_all = "camelCase")]
603pub enum KrakenFillType {
604    Maker,
605    Taker,
606    Liquidation,
607    PartialLiquidation,
608    Assignor,
609    Assignee,
610    TakerAfterEdit,
611    UnwindBankrupt,
612    UnwindCounterparty,
613}
614
615/// Kraken API result status.
616#[derive(
617    Clone,
618    Copy,
619    Debug,
620    Display,
621    AsRefStr,
622    EnumString,
623    FromRepr,
624    PartialEq,
625    Eq,
626    Hash,
627    Serialize,
628    Deserialize,
629)]
630#[cfg_attr(
631    feature = "python",
632    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
633)]
634#[serde(rename_all = "lowercase")]
635#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
636pub enum KrakenApiResult {
637    Success,
638    Error,
639}
640
641/// Kraken futures instrument type.
642#[derive(
643    Clone,
644    Copy,
645    Debug,
646    Display,
647    AsRefStr,
648    EnumString,
649    FromRepr,
650    PartialEq,
651    Eq,
652    Hash,
653    Serialize,
654    Deserialize,
655)]
656#[cfg_attr(
657    feature = "python",
658    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
659)]
660#[serde(rename_all = "snake_case")]
661#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
662pub enum KrakenInstrumentType {
663    /// Inverse perpetual futures (e.g., PI_XBTUSD).
664    FuturesInverse,
665    /// Flexible/linear perpetual futures (e.g., PF_XBTUSD).
666    FlexibleFutures,
667}
668
669/// Kraken futures send order status.
670#[derive(
671    Clone,
672    Copy,
673    Debug,
674    Display,
675    AsRefStr,
676    EnumString,
677    FromRepr,
678    PartialEq,
679    Eq,
680    Hash,
681    Serialize,
682    Deserialize,
683)]
684#[cfg_attr(
685    feature = "python",
686    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
687)]
688#[serde(rename_all = "camelCase")]
689#[strum(ascii_case_insensitive, serialize_all = "camelCase")]
690pub enum KrakenSendStatus {
691    /// Order was successfully placed.
692    Placed,
693    /// Order was cancelled.
694    Cancelled,
695    /// Order was edited.
696    Edited,
697    /// Order not found.
698    NotFound,
699    /// No orders matched the cancel-all request.
700    ///
701    /// Returned by the Kraken Futures `cancelallorders` endpoint as the
702    /// `cancelStatus.status` field. The accompanying `cancelledOrders` array
703    /// may still be populated for orders that were canceled in the same call,
704    /// so callers must inspect that array rather than treating this status
705    /// as an error.
706    NoOrdersToCancel,
707    /// Insufficient available funds.
708    InsufficientAvailableFunds,
709    /// Invalid order type.
710    InvalidOrderType,
711    /// Invalid size.
712    InvalidSize,
713    /// Would cause liquidation.
714    WouldCauseLiquidation,
715    /// Post-only order would have crossed.
716    PostWouldExecute,
717    /// Immediate-or-cancel order would not execute.
718    ///
719    /// Also the final outcome of a Maker Protection hold that was converted
720    /// by a cancel and cannot trade at release: the venue releases the order
721    /// as immediate-or-cancel and reports this status when nothing fills.
722    IocWouldNotExecute,
723    /// Reduce-only order would increase position.
724    ReduceOnlyWouldIncreasePosition,
725}
726
727/// Kraken futures trigger side for conditional orders.
728#[derive(
729    Clone,
730    Copy,
731    Debug,
732    Display,
733    AsRefStr,
734    EnumString,
735    FromRepr,
736    PartialEq,
737    Eq,
738    Hash,
739    Serialize,
740    Deserialize,
741)]
742#[cfg_attr(
743    feature = "python",
744    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
745)]
746#[serde(rename_all = "snake_case")]
747#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
748pub enum KrakenTriggerSide {
749    /// Trigger when price goes above the trigger price.
750    #[serde(rename = "trigger_above")]
751    #[strum(serialize = "trigger_above")]
752    TriggerAbove,
753    /// Trigger when price goes below the trigger price.
754    #[serde(rename = "trigger_below")]
755    #[strum(serialize = "trigger_below")]
756    TriggerBelow,
757    /// The venue's `"unknown"` sentinel, carrying no directional intent.
758    #[serde(rename = "unknown")]
759    Unknown,
760}
761
762impl From<KrakenOrderSide> for OrderSide {
763    fn from(value: KrakenOrderSide) -> Self {
764        match value {
765            KrakenOrderSide::Buy => Self::Buy,
766            KrakenOrderSide::Sell => Self::Sell,
767        }
768    }
769}
770
771impl From<KrakenFillType> for LiquiditySide {
772    fn from(value: KrakenFillType) -> Self {
773        match value {
774            KrakenFillType::Maker => Self::Maker,
775            KrakenFillType::Taker | KrakenFillType::TakerAfterEdit => Self::Taker,
776            KrakenFillType::Liquidation
777            | KrakenFillType::PartialLiquidation
778            | KrakenFillType::Assignor
779            | KrakenFillType::Assignee
780            | KrakenFillType::UnwindBankrupt
781            | KrakenFillType::UnwindCounterparty => Self::NoLiquiditySide,
782        }
783    }
784}
785
786impl From<KrakenOrderType> for OrderType {
787    /// Maps Kraken order types to Nautilus order types for reconciliation.
788    ///
789    /// Trailing stops map to their non-trailing equivalents because
790    /// Kraken reports lack the offset fields required to reconstruct
791    /// a trailing order during reconciliation.
792    fn from(value: KrakenOrderType) -> Self {
793        match value {
794            KrakenOrderType::Market => Self::Market,
795            KrakenOrderType::Limit => Self::Limit,
796            KrakenOrderType::StopLoss => Self::StopMarket,
797            KrakenOrderType::TakeProfit => Self::MarketIfTouched,
798            KrakenOrderType::StopLossLimit => Self::StopLimit,
799            KrakenOrderType::TakeProfitLimit => Self::LimitIfTouched,
800            KrakenOrderType::TrailingStop => Self::StopMarket,
801            KrakenOrderType::TrailingStopLimit => Self::StopLimit,
802            KrakenOrderType::SettlePosition => Self::Market,
803        }
804    }
805}
806
807impl From<KrakenOrderStatus> for OrderStatus {
808    fn from(value: KrakenOrderStatus) -> Self {
809        match value {
810            KrakenOrderStatus::Pending => Self::Initialized,
811            KrakenOrderStatus::Open => Self::Accepted,
812            KrakenOrderStatus::Closed => Self::Filled,
813            KrakenOrderStatus::Canceled => Self::Canceled,
814            KrakenOrderStatus::Expired => Self::Expired,
815        }
816    }
817}
818
819impl From<KrakenFuturesOrderType> for OrderType {
820    fn from(value: KrakenFuturesOrderType) -> Self {
821        match value {
822            KrakenFuturesOrderType::Limit
823            | KrakenFuturesOrderType::Ioc
824            | KrakenFuturesOrderType::Post => Self::Limit,
825            KrakenFuturesOrderType::Market => Self::Market,
826            KrakenFuturesOrderType::Stop | KrakenFuturesOrderType::StopLower => Self::StopMarket,
827            KrakenFuturesOrderType::TakeProfit => Self::MarketIfTouched,
828            KrakenFuturesOrderType::StopLoss => Self::StopMarket,
829            KrakenFuturesOrderType::Unknown => {
830                log::warn!(
831                    "KrakenFuturesOrderType::Unknown received from venue, defaulting to Market"
832                );
833                Self::Market
834            }
835        }
836    }
837}
838
839impl From<OrderSide> for KrakenOrderSide {
840    fn from(value: OrderSide) -> Self {
841        match value {
842            OrderSide::Buy => Self::Buy,
843            OrderSide::Sell => Self::Sell,
844        }
845    }
846}
847
848impl From<KrakenFuturesOrderStatus> for OrderStatus {
849    fn from(value: KrakenFuturesOrderStatus) -> Self {
850        match value {
851            KrakenFuturesOrderStatus::Untouched => Self::Accepted,
852            KrakenFuturesOrderStatus::PartiallyFilled => Self::PartiallyFilled,
853            KrakenFuturesOrderStatus::Filled => Self::Filled,
854            KrakenFuturesOrderStatus::Cancelled => Self::Canceled,
855            KrakenFuturesOrderStatus::Expired => Self::Expired,
856        }
857    }
858}
859
860impl From<KrakenPairStatus> for MarketStatusAction {
861    fn from(value: KrakenPairStatus) -> Self {
862        match value {
863            KrakenPairStatus::Online => Self::Trading,
864            KrakenPairStatus::CancelOnly => Self::Halt,
865            KrakenPairStatus::PostOnly => Self::Pause,
866            KrakenPairStatus::LimitOnly => Self::Pause,
867            KrakenPairStatus::ReduceOnly => Self::Pause,
868        }
869    }
870}
871
872/// Determines the product type from a Kraken symbol.
873///
874/// Futures symbols have the following prefixes:
875/// - `PI_` - Perpetual Inverse futures (e.g., `PI_XBTUSD`)
876/// - `PF_` - Perpetual Fixed-margin futures (e.g., `PF_XBTUSD`)
877/// - `PV_` - Perpetual Vanilla futures (e.g., `PV_XRPXBT`)
878/// - `FI_` - Fixed maturity Inverse futures (e.g., `FI_XBTUSD_230929`)
879/// - `FF_` - Flex futures
880///
881/// All other symbols are considered spot.
882#[must_use]
883pub fn product_type_from_symbol(symbol: &str) -> KrakenProductType {
884    if symbol.starts_with("PI_")
885        || symbol.starts_with("PF_")
886        || symbol.starts_with("PV_")
887        || symbol.starts_with("FI_")
888        || symbol.starts_with("FF_")
889    {
890        KrakenProductType::Futures
891    } else {
892        KrakenProductType::Spot
893    }
894}
895
896#[cfg(test)]
897mod tests {
898    use nautilus_model::enums::{LiquiditySide, MarketStatusAction, OrderType};
899    use rstest::rstest;
900
901    use super::*;
902
903    #[rstest]
904    #[case::online(KrakenPairStatus::Online, MarketStatusAction::Trading)]
905    #[case::cancel_only(KrakenPairStatus::CancelOnly, MarketStatusAction::Halt)]
906    #[case::post_only(KrakenPairStatus::PostOnly, MarketStatusAction::Pause)]
907    #[case::limit_only(KrakenPairStatus::LimitOnly, MarketStatusAction::Pause)]
908    #[case::reduce_only(KrakenPairStatus::ReduceOnly, MarketStatusAction::Pause)]
909    fn test_pair_status_to_market_status_action(
910        #[case] input: KrakenPairStatus,
911        #[case] expected: MarketStatusAction,
912    ) {
913        assert_eq!(MarketStatusAction::from(input), expected);
914    }
915
916    #[rstest]
917    #[case::trailing_stop(KrakenOrderType::TrailingStop, OrderType::StopMarket)]
918    #[case::trailing_stop_limit(KrakenOrderType::TrailingStopLimit, OrderType::StopLimit)]
919    fn test_trailing_stop_order_type_mapping(
920        #[case] input: KrakenOrderType,
921        #[case] expected: OrderType,
922    ) {
923        assert_eq!(OrderType::from(input), expected);
924    }
925
926    #[rstest]
927    #[case::maker(KrakenFillType::Maker, LiquiditySide::Maker)]
928    #[case::taker(KrakenFillType::Taker, LiquiditySide::Taker)]
929    #[case::liquidation(KrakenFillType::Liquidation, LiquiditySide::NoLiquiditySide)]
930    #[case::partial_liquidation(KrakenFillType::PartialLiquidation, LiquiditySide::NoLiquiditySide)]
931    #[case::assignor(KrakenFillType::Assignor, LiquiditySide::NoLiquiditySide)]
932    #[case::assignee(KrakenFillType::Assignee, LiquiditySide::NoLiquiditySide)]
933    #[case::taker_after_edit(KrakenFillType::TakerAfterEdit, LiquiditySide::Taker)]
934    #[case::unwind_bankrupt(KrakenFillType::UnwindBankrupt, LiquiditySide::NoLiquiditySide)]
935    #[case::unwind_counterparty(KrakenFillType::UnwindCounterparty, LiquiditySide::NoLiquiditySide)]
936    fn test_fill_type_to_liquidity_side(
937        #[case] fill_type: KrakenFillType,
938        #[case] expected: LiquiditySide,
939    ) {
940        assert_eq!(LiquiditySide::from(fill_type), expected);
941    }
942
943    #[rstest]
944    fn test_fill_type_deserialization() {
945        let json = include_str!("../../test_data/futures_fill_types.json");
946        let fill_types: Vec<KrakenFillType> = serde_json::from_str(json).unwrap();
947
948        assert_eq!(
949            fill_types,
950            vec![
951                KrakenFillType::Maker,
952                KrakenFillType::Taker,
953                KrakenFillType::Liquidation,
954                KrakenFillType::PartialLiquidation,
955                KrakenFillType::Assignor,
956                KrakenFillType::Assignee,
957                KrakenFillType::TakerAfterEdit,
958                KrakenFillType::UnwindBankrupt,
959                KrakenFillType::UnwindCounterparty,
960            ]
961        );
962    }
963
964    #[rstest]
965    fn test_time_in_force_deserializes_fok() {
966        // FOK (fill-or-kill) is a valid Kraken timeinforce; inbound executions
967        // must not hard-fail on it.
968        let tif: KrakenTimeInForce = serde_json::from_str("\"FOK\"").unwrap();
969        assert_eq!(tif, KrakenTimeInForce::FillOrKill);
970    }
971
972    #[rstest]
973    #[case("\"placed\"", KrakenSendStatus::Placed)]
974    #[case("\"cancelled\"", KrakenSendStatus::Cancelled)]
975    #[case("\"edited\"", KrakenSendStatus::Edited)]
976    #[case("\"notFound\"", KrakenSendStatus::NotFound)]
977    #[case("\"noOrdersToCancel\"", KrakenSendStatus::NoOrdersToCancel)]
978    #[case(
979        "\"insufficientAvailableFunds\"",
980        KrakenSendStatus::InsufficientAvailableFunds
981    )]
982    #[case("\"invalidOrderType\"", KrakenSendStatus::InvalidOrderType)]
983    #[case("\"invalidSize\"", KrakenSendStatus::InvalidSize)]
984    #[case("\"wouldCauseLiquidation\"", KrakenSendStatus::WouldCauseLiquidation)]
985    #[case("\"postWouldExecute\"", KrakenSendStatus::PostWouldExecute)]
986    #[case("\"iocWouldNotExecute\"", KrakenSendStatus::IocWouldNotExecute)]
987    #[case(
988        "\"reduceOnlyWouldIncreasePosition\"",
989        KrakenSendStatus::ReduceOnlyWouldIncreasePosition
990    )]
991    fn test_send_status_deserialization(#[case] raw: &str, #[case] expected: KrakenSendStatus) {
992        let parsed: KrakenSendStatus = serde_json::from_str(raw).unwrap();
993        assert_eq!(parsed, expected);
994    }
995
996    #[rstest]
997    #[case("\"last\"", KrakenTriggerSignal::Last)]
998    #[case("\"last_price\"", KrakenTriggerSignal::Last)]
999    #[case("\"mark\"", KrakenTriggerSignal::Mark)]
1000    #[case("\"mark_price\"", KrakenTriggerSignal::Mark)]
1001    #[case("\"spot\"", KrakenTriggerSignal::Index)]
1002    #[case("\"spot_price\"", KrakenTriggerSignal::Index)]
1003    #[case("\"index\"", KrakenTriggerSignal::Index)]
1004    #[case("\"index_price\"", KrakenTriggerSignal::Index)]
1005    #[case("\"unknown\"", KrakenTriggerSignal::Unknown)]
1006    fn test_trigger_signal_deserialization(
1007        #[case] raw: &str,
1008        #[case] expected: KrakenTriggerSignal,
1009    ) {
1010        let parsed: KrakenTriggerSignal = serde_json::from_str(raw).unwrap();
1011        assert_eq!(parsed, expected);
1012    }
1013
1014    #[rstest]
1015    #[case("\"trigger_above\"", KrakenTriggerSide::TriggerAbove)]
1016    #[case("\"trigger_below\"", KrakenTriggerSide::TriggerBelow)]
1017    #[case("\"unknown\"", KrakenTriggerSide::Unknown)]
1018    fn test_trigger_side_deserialization(#[case] raw: &str, #[case] expected: KrakenTriggerSide) {
1019        let parsed: KrakenTriggerSide = serde_json::from_str(raw).unwrap();
1020        assert_eq!(parsed, expected);
1021    }
1022
1023    #[rstest]
1024    #[case("\"lmt\"", KrakenFuturesOrderType::Limit)]
1025    #[case("\"limit\"", KrakenFuturesOrderType::Limit)]
1026    #[case("\"ioc\"", KrakenFuturesOrderType::Ioc)]
1027    #[case("\"post\"", KrakenFuturesOrderType::Post)]
1028    #[case("\"mkt\"", KrakenFuturesOrderType::Market)]
1029    #[case("\"market\"", KrakenFuturesOrderType::Market)]
1030    #[case("\"stp\"", KrakenFuturesOrderType::Stop)]
1031    #[case("\"stop\"", KrakenFuturesOrderType::StopLower)]
1032    #[case("\"take_profit\"", KrakenFuturesOrderType::TakeProfit)]
1033    #[case("\"stop_loss\"", KrakenFuturesOrderType::StopLoss)]
1034    #[case("\"unknown\"", KrakenFuturesOrderType::Unknown)]
1035    fn test_futures_order_type_deserialization(
1036        #[case] raw: &str,
1037        #[case] expected: KrakenFuturesOrderType,
1038    ) {
1039        let parsed: KrakenFuturesOrderType = serde_json::from_str(raw).unwrap();
1040        assert_eq!(parsed, expected);
1041    }
1042
1043    #[rstest]
1044    fn test_order_metadata_enums_fail_loud_on_unmodeled_value() {
1045        // Only the venue's documented "unknown" sentinel maps to Unknown; any other
1046        // unmodeled value must fail deserialization rather than be silently absorbed.
1047        assert!(serde_json::from_str::<KrakenFuturesOrderType>("\"iceberg\"").is_err());
1048        assert!(serde_json::from_str::<KrakenTriggerSignal>("\"vwap\"").is_err());
1049        assert!(serde_json::from_str::<KrakenTriggerSide>("\"sideways\"").is_err());
1050    }
1051
1052    #[rstest]
1053    fn test_futures_order_type_unknown_maps_to_market_default() {
1054        assert_eq!(
1055            OrderType::from(KrakenFuturesOrderType::Unknown),
1056            OrderType::Market,
1057        );
1058    }
1059
1060    #[rstest]
1061    #[case("\"PLACE\"", KrakenFuturesOrderEventType::Place)]
1062    #[case("\"FILL\"", KrakenFuturesOrderEventType::Fill)]
1063    #[case("\"EXECUTION\"", KrakenFuturesOrderEventType::Execution)]
1064    #[case("\"REJECT\"", KrakenFuturesOrderEventType::Reject)]
1065    #[case("\"CANCEL\"", KrakenFuturesOrderEventType::Cancel)]
1066    #[case("\"EDIT\"", KrakenFuturesOrderEventType::Edit)]
1067    #[case("\"EXPIRE\"", KrakenFuturesOrderEventType::Expire)]
1068    #[case("\"EXPIRED\"", KrakenFuturesOrderEventType::Expire)]
1069    fn test_futures_order_event_type_deserialization(
1070        #[case] raw: &str,
1071        #[case] expected: KrakenFuturesOrderEventType,
1072    ) {
1073        let parsed: KrakenFuturesOrderEventType = serde_json::from_str(raw).unwrap();
1074        assert_eq!(parsed, expected);
1075    }
1076}