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    /// Fill event, which the order history does not produce.
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    /// Another order already carries the client order ID.
726    ClientOrderIdAlreadyExist,
727    /// The client order ID is longer than the venue accepts, as the send-order status enum spells
728    /// it.
729    ClientOrderIdTooBig,
730    /// The client order ID is longer than the venue accepts, as the batch-order status enum and the
731    /// send-order status descriptions spell it.
732    ClientOrderIdTooLong,
733    /// The venue does not accept the client order ID.
734    ClientOrderIdInvalid,
735}
736
737/// Kraken futures trigger side for conditional orders.
738#[derive(
739    Clone,
740    Copy,
741    Debug,
742    Display,
743    AsRefStr,
744    EnumString,
745    FromRepr,
746    PartialEq,
747    Eq,
748    Hash,
749    Serialize,
750    Deserialize,
751)]
752#[cfg_attr(
753    feature = "python",
754    pyo3::pyclass(module = "nautilus_trader.adapters.kraken", eq, eq_int, from_py_object)
755)]
756#[serde(rename_all = "snake_case")]
757#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
758pub enum KrakenTriggerSide {
759    /// Trigger when price goes above the trigger price.
760    #[serde(rename = "trigger_above")]
761    #[strum(serialize = "trigger_above")]
762    TriggerAbove,
763    /// Trigger when price goes below the trigger price.
764    #[serde(rename = "trigger_below")]
765    #[strum(serialize = "trigger_below")]
766    TriggerBelow,
767    /// The venue's `"unknown"` sentinel, carrying no directional intent.
768    #[serde(rename = "unknown")]
769    Unknown,
770}
771
772impl From<KrakenOrderSide> for OrderSide {
773    fn from(value: KrakenOrderSide) -> Self {
774        match value {
775            KrakenOrderSide::Buy => Self::Buy,
776            KrakenOrderSide::Sell => Self::Sell,
777        }
778    }
779}
780
781impl From<KrakenFillType> for LiquiditySide {
782    fn from(value: KrakenFillType) -> Self {
783        match value {
784            KrakenFillType::Maker => Self::Maker,
785            KrakenFillType::Taker | KrakenFillType::TakerAfterEdit => Self::Taker,
786            KrakenFillType::Liquidation
787            | KrakenFillType::PartialLiquidation
788            | KrakenFillType::Assignor
789            | KrakenFillType::Assignee
790            | KrakenFillType::UnwindBankrupt
791            | KrakenFillType::UnwindCounterparty => Self::NoLiquiditySide,
792        }
793    }
794}
795
796impl From<KrakenOrderType> for OrderType {
797    /// Maps Kraken order types to Nautilus order types for reconciliation.
798    ///
799    /// Trailing stops map to their non-trailing equivalents because
800    /// Kraken reports lack the offset fields required to reconstruct
801    /// a trailing order during reconciliation.
802    fn from(value: KrakenOrderType) -> Self {
803        match value {
804            KrakenOrderType::Market => Self::Market,
805            KrakenOrderType::Limit => Self::Limit,
806            KrakenOrderType::StopLoss => Self::StopMarket,
807            KrakenOrderType::TakeProfit => Self::MarketIfTouched,
808            KrakenOrderType::StopLossLimit => Self::StopLimit,
809            KrakenOrderType::TakeProfitLimit => Self::LimitIfTouched,
810            KrakenOrderType::TrailingStop => Self::StopMarket,
811            KrakenOrderType::TrailingStopLimit => Self::StopLimit,
812            KrakenOrderType::SettlePosition => Self::Market,
813        }
814    }
815}
816
817impl From<KrakenOrderStatus> for OrderStatus {
818    fn from(value: KrakenOrderStatus) -> Self {
819        match value {
820            KrakenOrderStatus::Pending => Self::Initialized,
821            KrakenOrderStatus::Open => Self::Accepted,
822            KrakenOrderStatus::Closed => Self::Filled,
823            KrakenOrderStatus::Canceled => Self::Canceled,
824            KrakenOrderStatus::Expired => Self::Expired,
825        }
826    }
827}
828
829/// How Kraken Futures applies an edit's size: `Absolute` sets the order's total size, fills
830/// included, and `Relative`, the venue's default, sets its open size.
831#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
832#[serde(rename_all = "UPPERCASE")]
833pub enum KrakenFuturesQtyMode {
834    Absolute,
835    Relative,
836}
837
838/// Order direction as the Kraken Futures order history reports it.
839#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
840pub enum KrakenFuturesHistoryDirection {
841    Buy,
842    Sell,
843    /// The venue could not decode the source value.
844    #[serde(other)]
845    Unknown,
846}
847
848/// Order type as the Kraken Futures order history reports it.
849///
850/// The venue-initiated kinds (liquidation, assignment, hedge assignment, unwind, block and RFQ)
851/// execute against the account at market, so they map to a market order; the hedge
852/// immediate-or-cancel kind keeps its limit price as an IOC order.
853#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
854pub enum KrakenFuturesHistoryOrderType {
855    Limit,
856    #[serde(rename = "IoC")]
857    Ioc,
858    Post,
859    Market,
860    Stop,
861    FillOrKill,
862    Liquidation,
863    PartialLiquidation,
864    CoveredLiquidation,
865    Assignment,
866    HedgeAssignment,
867    HedgeImmediateOrCancel,
868    Unwind,
869    Block,
870    Rfq,
871    #[serde(other)]
872    Unknown,
873}
874
875impl From<KrakenFuturesHistoryOrderType> for KrakenFuturesOrderType {
876    fn from(value: KrakenFuturesHistoryOrderType) -> Self {
877        match value {
878            KrakenFuturesHistoryOrderType::Limit => Self::Limit,
879            KrakenFuturesHistoryOrderType::Ioc
880            | KrakenFuturesHistoryOrderType::FillOrKill
881            | KrakenFuturesHistoryOrderType::HedgeImmediateOrCancel => Self::Ioc,
882            KrakenFuturesHistoryOrderType::Post => Self::Post,
883            KrakenFuturesHistoryOrderType::Stop => Self::Stop,
884            KrakenFuturesHistoryOrderType::Market
885            | KrakenFuturesHistoryOrderType::Liquidation
886            | KrakenFuturesHistoryOrderType::PartialLiquidation
887            | KrakenFuturesHistoryOrderType::CoveredLiquidation
888            | KrakenFuturesHistoryOrderType::Assignment
889            | KrakenFuturesHistoryOrderType::HedgeAssignment
890            | KrakenFuturesHistoryOrderType::Unwind
891            | KrakenFuturesHistoryOrderType::Block
892            | KrakenFuturesHistoryOrderType::Rfq => Self::Market,
893            KrakenFuturesHistoryOrderType::Unknown => Self::Unknown,
894        }
895    }
896}
897
898impl From<KrakenFuturesOrderType> for OrderType {
899    fn from(value: KrakenFuturesOrderType) -> Self {
900        match value {
901            KrakenFuturesOrderType::Limit
902            | KrakenFuturesOrderType::Ioc
903            | KrakenFuturesOrderType::Post => Self::Limit,
904            KrakenFuturesOrderType::Market => Self::Market,
905            KrakenFuturesOrderType::Stop | KrakenFuturesOrderType::StopLower => Self::StopMarket,
906            KrakenFuturesOrderType::TakeProfit => Self::MarketIfTouched,
907            KrakenFuturesOrderType::StopLoss => Self::StopMarket,
908            KrakenFuturesOrderType::Unknown => {
909                log::warn!(
910                    "KrakenFuturesOrderType::Unknown received from venue, defaulting to Market"
911                );
912                Self::Market
913            }
914        }
915    }
916}
917
918impl From<OrderSide> for KrakenOrderSide {
919    fn from(value: OrderSide) -> Self {
920        match value {
921            OrderSide::Buy => Self::Buy,
922            OrderSide::Sell => Self::Sell,
923        }
924    }
925}
926
927impl From<KrakenFuturesOrderStatus> for OrderStatus {
928    fn from(value: KrakenFuturesOrderStatus) -> Self {
929        match value {
930            KrakenFuturesOrderStatus::Untouched => Self::Accepted,
931            KrakenFuturesOrderStatus::PartiallyFilled => Self::PartiallyFilled,
932            KrakenFuturesOrderStatus::Filled => Self::Filled,
933            KrakenFuturesOrderStatus::Cancelled => Self::Canceled,
934            KrakenFuturesOrderStatus::Expired => Self::Expired,
935        }
936    }
937}
938
939impl From<KrakenPairStatus> for MarketStatusAction {
940    fn from(value: KrakenPairStatus) -> Self {
941        match value {
942            KrakenPairStatus::Online => Self::Trading,
943            KrakenPairStatus::CancelOnly => Self::Halt,
944            KrakenPairStatus::PostOnly => Self::Pause,
945            KrakenPairStatus::LimitOnly => Self::Pause,
946            KrakenPairStatus::ReduceOnly => Self::Pause,
947        }
948    }
949}
950
951/// Determines the product type from a Kraken symbol.
952///
953/// Futures symbols have the following prefixes:
954/// - `PI_` - Perpetual Inverse futures (e.g., `PI_XBTUSD`)
955/// - `PF_` - Perpetual Fixed-margin futures (e.g., `PF_XBTUSD`)
956/// - `PV_` - Perpetual Vanilla futures (e.g., `PV_XRPXBT`)
957/// - `FI_` - Fixed maturity Inverse futures (e.g., `FI_XBTUSD_230929`)
958/// - `FF_` - Flex futures
959///
960/// All other symbols are considered spot.
961#[must_use]
962pub fn product_type_from_symbol(symbol: &str) -> KrakenProductType {
963    if symbol.starts_with("PI_")
964        || symbol.starts_with("PF_")
965        || symbol.starts_with("PV_")
966        || symbol.starts_with("FI_")
967        || symbol.starts_with("FF_")
968    {
969        KrakenProductType::Futures
970    } else {
971        KrakenProductType::Spot
972    }
973}
974
975#[cfg(test)]
976mod tests {
977    use nautilus_model::enums::{LiquiditySide, MarketStatusAction, OrderType};
978    use rstest::rstest;
979
980    use super::*;
981
982    #[rstest]
983    #[case::online(KrakenPairStatus::Online, MarketStatusAction::Trading)]
984    #[case::cancel_only(KrakenPairStatus::CancelOnly, MarketStatusAction::Halt)]
985    #[case::post_only(KrakenPairStatus::PostOnly, MarketStatusAction::Pause)]
986    #[case::limit_only(KrakenPairStatus::LimitOnly, MarketStatusAction::Pause)]
987    #[case::reduce_only(KrakenPairStatus::ReduceOnly, MarketStatusAction::Pause)]
988    fn test_pair_status_to_market_status_action(
989        #[case] input: KrakenPairStatus,
990        #[case] expected: MarketStatusAction,
991    ) {
992        assert_eq!(MarketStatusAction::from(input), expected);
993    }
994
995    #[rstest]
996    #[case::trailing_stop(KrakenOrderType::TrailingStop, OrderType::StopMarket)]
997    #[case::trailing_stop_limit(KrakenOrderType::TrailingStopLimit, OrderType::StopLimit)]
998    fn test_trailing_stop_order_type_mapping(
999        #[case] input: KrakenOrderType,
1000        #[case] expected: OrderType,
1001    ) {
1002        assert_eq!(OrderType::from(input), expected);
1003    }
1004
1005    #[rstest]
1006    #[case::maker(KrakenFillType::Maker, LiquiditySide::Maker)]
1007    #[case::taker(KrakenFillType::Taker, LiquiditySide::Taker)]
1008    #[case::liquidation(KrakenFillType::Liquidation, LiquiditySide::NoLiquiditySide)]
1009    #[case::partial_liquidation(KrakenFillType::PartialLiquidation, LiquiditySide::NoLiquiditySide)]
1010    #[case::assignor(KrakenFillType::Assignor, LiquiditySide::NoLiquiditySide)]
1011    #[case::assignee(KrakenFillType::Assignee, LiquiditySide::NoLiquiditySide)]
1012    #[case::taker_after_edit(KrakenFillType::TakerAfterEdit, LiquiditySide::Taker)]
1013    #[case::unwind_bankrupt(KrakenFillType::UnwindBankrupt, LiquiditySide::NoLiquiditySide)]
1014    #[case::unwind_counterparty(KrakenFillType::UnwindCounterparty, LiquiditySide::NoLiquiditySide)]
1015    fn test_fill_type_to_liquidity_side(
1016        #[case] fill_type: KrakenFillType,
1017        #[case] expected: LiquiditySide,
1018    ) {
1019        assert_eq!(LiquiditySide::from(fill_type), expected);
1020    }
1021
1022    #[rstest]
1023    fn test_fill_type_deserialization() {
1024        let json = include_str!("../../test_data/futures_fill_types.json");
1025        let fill_types: Vec<KrakenFillType> = serde_json::from_str(json).unwrap();
1026
1027        assert_eq!(
1028            fill_types,
1029            vec![
1030                KrakenFillType::Maker,
1031                KrakenFillType::Taker,
1032                KrakenFillType::Liquidation,
1033                KrakenFillType::PartialLiquidation,
1034                KrakenFillType::Assignor,
1035                KrakenFillType::Assignee,
1036                KrakenFillType::TakerAfterEdit,
1037                KrakenFillType::UnwindBankrupt,
1038                KrakenFillType::UnwindCounterparty,
1039            ]
1040        );
1041    }
1042
1043    #[rstest]
1044    fn test_time_in_force_deserializes_fok() {
1045        // FOK (fill-or-kill) is a valid Kraken timeinforce; inbound executions
1046        // must not hard-fail on it.
1047        let tif: KrakenTimeInForce = serde_json::from_str("\"FOK\"").unwrap();
1048        assert_eq!(tif, KrakenTimeInForce::FillOrKill);
1049    }
1050
1051    #[rstest]
1052    #[case("\"placed\"", KrakenSendStatus::Placed)]
1053    #[case("\"cancelled\"", KrakenSendStatus::Cancelled)]
1054    #[case("\"edited\"", KrakenSendStatus::Edited)]
1055    #[case("\"notFound\"", KrakenSendStatus::NotFound)]
1056    #[case("\"noOrdersToCancel\"", KrakenSendStatus::NoOrdersToCancel)]
1057    #[case(
1058        "\"insufficientAvailableFunds\"",
1059        KrakenSendStatus::InsufficientAvailableFunds
1060    )]
1061    #[case("\"invalidOrderType\"", KrakenSendStatus::InvalidOrderType)]
1062    #[case("\"invalidSize\"", KrakenSendStatus::InvalidSize)]
1063    #[case("\"wouldCauseLiquidation\"", KrakenSendStatus::WouldCauseLiquidation)]
1064    #[case("\"postWouldExecute\"", KrakenSendStatus::PostWouldExecute)]
1065    #[case("\"iocWouldNotExecute\"", KrakenSendStatus::IocWouldNotExecute)]
1066    #[case(
1067        "\"reduceOnlyWouldIncreasePosition\"",
1068        KrakenSendStatus::ReduceOnlyWouldIncreasePosition
1069    )]
1070    fn test_send_status_deserialization(#[case] raw: &str, #[case] expected: KrakenSendStatus) {
1071        let parsed: KrakenSendStatus = serde_json::from_str(raw).unwrap();
1072        assert_eq!(parsed, expected);
1073    }
1074
1075    #[rstest]
1076    #[case("\"last\"", KrakenTriggerSignal::Last)]
1077    #[case("\"last_price\"", KrakenTriggerSignal::Last)]
1078    #[case("\"mark\"", KrakenTriggerSignal::Mark)]
1079    #[case("\"mark_price\"", KrakenTriggerSignal::Mark)]
1080    #[case("\"spot\"", KrakenTriggerSignal::Index)]
1081    #[case("\"spot_price\"", KrakenTriggerSignal::Index)]
1082    #[case("\"index\"", KrakenTriggerSignal::Index)]
1083    #[case("\"index_price\"", KrakenTriggerSignal::Index)]
1084    #[case("\"unknown\"", KrakenTriggerSignal::Unknown)]
1085    fn test_trigger_signal_deserialization(
1086        #[case] raw: &str,
1087        #[case] expected: KrakenTriggerSignal,
1088    ) {
1089        let parsed: KrakenTriggerSignal = serde_json::from_str(raw).unwrap();
1090        assert_eq!(parsed, expected);
1091    }
1092
1093    #[rstest]
1094    #[case("\"trigger_above\"", KrakenTriggerSide::TriggerAbove)]
1095    #[case("\"trigger_below\"", KrakenTriggerSide::TriggerBelow)]
1096    #[case("\"unknown\"", KrakenTriggerSide::Unknown)]
1097    fn test_trigger_side_deserialization(#[case] raw: &str, #[case] expected: KrakenTriggerSide) {
1098        let parsed: KrakenTriggerSide = serde_json::from_str(raw).unwrap();
1099        assert_eq!(parsed, expected);
1100    }
1101
1102    #[rstest]
1103    #[case("\"lmt\"", KrakenFuturesOrderType::Limit)]
1104    #[case("\"limit\"", KrakenFuturesOrderType::Limit)]
1105    #[case("\"ioc\"", KrakenFuturesOrderType::Ioc)]
1106    #[case("\"post\"", KrakenFuturesOrderType::Post)]
1107    #[case("\"mkt\"", KrakenFuturesOrderType::Market)]
1108    #[case("\"market\"", KrakenFuturesOrderType::Market)]
1109    #[case("\"stp\"", KrakenFuturesOrderType::Stop)]
1110    #[case("\"stop\"", KrakenFuturesOrderType::StopLower)]
1111    #[case("\"take_profit\"", KrakenFuturesOrderType::TakeProfit)]
1112    #[case("\"stop_loss\"", KrakenFuturesOrderType::StopLoss)]
1113    #[case("\"unknown\"", KrakenFuturesOrderType::Unknown)]
1114    fn test_futures_order_type_deserialization(
1115        #[case] raw: &str,
1116        #[case] expected: KrakenFuturesOrderType,
1117    ) {
1118        let parsed: KrakenFuturesOrderType = serde_json::from_str(raw).unwrap();
1119        assert_eq!(parsed, expected);
1120    }
1121
1122    #[rstest]
1123    fn test_order_metadata_enums_fail_loud_on_unmodeled_value() {
1124        // Only the venue's documented "unknown" sentinel maps to Unknown; any other
1125        // unmodeled value must fail deserialization rather than be silently absorbed.
1126        assert!(serde_json::from_str::<KrakenFuturesOrderType>("\"iceberg\"").is_err());
1127        assert!(serde_json::from_str::<KrakenTriggerSignal>("\"vwap\"").is_err());
1128        assert!(serde_json::from_str::<KrakenTriggerSide>("\"sideways\"").is_err());
1129    }
1130
1131    #[rstest]
1132    fn test_futures_order_type_unknown_maps_to_market_default() {
1133        assert_eq!(
1134            OrderType::from(KrakenFuturesOrderType::Unknown),
1135            OrderType::Market,
1136        );
1137    }
1138
1139    #[rstest]
1140    #[case("\"PLACE\"", KrakenFuturesOrderEventType::Place)]
1141    #[case("\"FILL\"", KrakenFuturesOrderEventType::Fill)]
1142    #[case("\"EXECUTION\"", KrakenFuturesOrderEventType::Execution)]
1143    #[case("\"REJECT\"", KrakenFuturesOrderEventType::Reject)]
1144    #[case("\"CANCEL\"", KrakenFuturesOrderEventType::Cancel)]
1145    #[case("\"EDIT\"", KrakenFuturesOrderEventType::Edit)]
1146    #[case("\"EXPIRE\"", KrakenFuturesOrderEventType::Expire)]
1147    #[case("\"EXPIRED\"", KrakenFuturesOrderEventType::Expire)]
1148    fn test_futures_order_event_type_deserialization(
1149        #[case] raw: &str,
1150        #[case] expected: KrakenFuturesOrderEventType,
1151    ) {
1152        let parsed: KrakenFuturesOrderEventType = serde_json::from_str(raw).unwrap();
1153        assert_eq!(parsed, expected);
1154    }
1155}