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