1use std::fmt::{Debug, Display};
19
20use nautilus_model::enums::{AggressorSide, OrderSide, OrderStatus, TimeInForce};
21use serde::{Deserialize, Serialize};
22use serde_repr::{Deserialize_repr, Serialize_repr};
23use strum::{Display as StrumDisplay, EnumString};
24use ustr::Ustr;
25
26#[cfg_attr(
28 feature = "python",
29 pyo3::pyclass(
30 frozen,
31 eq,
32 eq_int,
33 module = "nautilus_trader.adapters.polymarket",
34 from_py_object
35 )
36)]
37#[cfg_attr(
38 feature = "python",
39 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.polymarket")
40)]
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub enum PolymarketSignerType {
43 #[default]
44 Owner,
45 Session,
46}
47
48#[cfg(feature = "python")]
49#[pyo3::pymethods]
50#[pyo3_stub_gen::derive::gen_stub_pymethods]
51impl PolymarketSignerType {
52 const fn __hash__(&self) -> isize {
53 *self as isize
54 }
55}
56
57#[cfg_attr(
61 feature = "python",
62 pyo3::pyclass(
63 frozen,
64 eq,
65 eq_int,
66 hash,
67 module = "nautilus_trader.adapters.polymarket",
68 from_py_object,
69 )
70)]
71#[cfg_attr(
72 feature = "python",
73 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.polymarket")
74)]
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
76#[repr(u8)]
77pub enum PolymarketSignatureType {
78 Eoa = 0,
79 PolyProxy = 1,
80 PolyGnosisSafe = 2,
81 Poly1271 = 3,
82}
83
84#[repr(C)]
90#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
91pub struct PolymarketOutcome(Ustr);
92
93impl PolymarketOutcome {
94 #[must_use]
95 pub fn yes() -> Self {
96 Self(Ustr::from("Yes"))
97 }
98
99 #[must_use]
100 pub fn no() -> Self {
101 Self(Ustr::from("No"))
102 }
103
104 #[must_use]
105 pub fn up() -> Self {
106 Self(Ustr::from("Up"))
107 }
108
109 #[must_use]
110 pub fn down() -> Self {
111 Self(Ustr::from("Down"))
112 }
113
114 #[must_use]
115 pub const fn inner(&self) -> Ustr {
116 self.0
117 }
118
119 #[must_use]
120 pub fn as_str(&self) -> &str {
121 self.0.as_str()
122 }
123}
124
125impl Debug for PolymarketOutcome {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(f, "\"{}\"", self.0)
128 }
129}
130
131impl Display for PolymarketOutcome {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 write!(f, "{}", self.0)
134 }
135}
136
137impl From<&str> for PolymarketOutcome {
138 fn from(value: &str) -> Self {
139 Self(Ustr::from(value))
140 }
141}
142
143impl From<Ustr> for PolymarketOutcome {
144 fn from(value: Ustr) -> Self {
145 Self(value)
146 }
147}
148
149#[derive(
151 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
152)]
153#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
154#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
155pub enum PolymarketOrderSide {
156 Buy,
157 Sell,
158}
159
160#[derive(
162 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
163)]
164#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
165#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
166pub enum PolymarketLiquiditySide {
167 Maker,
168 Taker,
169}
170
171#[derive(
173 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
174)]
175pub enum PolymarketOrderType {
176 FOK,
177 FAK,
179 GTC,
180 GTD,
181}
182
183#[derive(
185 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
186)]
187#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
188#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
189pub enum PolymarketEventType {
190 Placement,
191 Update,
193 Cancellation,
194 Trade,
195}
196
197#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize)]
199#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
200#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
201pub enum PolymarketOrderStatus {
202 Invalid,
203 Live,
204 Delayed,
206 Matched,
207 Unmatched,
209 Canceled,
210 CanceledMarketResolved,
211}
212
213impl PolymarketOrderStatus {
214 pub(crate) fn parse_wire(value: &str) -> Option<(Self, Option<&str>)> {
215 const VARIANTS: &[(&str, PolymarketOrderStatus)] = &[
218 (
219 "CANCELED_MARKET_RESOLVED",
220 PolymarketOrderStatus::CanceledMarketResolved,
221 ),
222 ("INVALID", PolymarketOrderStatus::Invalid),
223 ("LIVE", PolymarketOrderStatus::Live),
224 ("DELAYED", PolymarketOrderStatus::Delayed),
225 ("MATCHED", PolymarketOrderStatus::Matched),
226 ("UNMATCHED", PolymarketOrderStatus::Unmatched),
227 ("CANCELED", PolymarketOrderStatus::Canceled),
228 ];
229
230 let value = value.strip_prefix("ORDER_STATUS_").unwrap_or(value);
231
232 if let Ok(status) = <Self as std::str::FromStr>::from_str(value) {
233 return Some((status, None));
234 }
235
236 VARIANTS.iter().find_map(|(prefix, status)| {
237 value
238 .strip_prefix(prefix)
239 .and_then(|suffix| suffix.strip_prefix('_'))
240 .map(|reason| (*status, Some(reason)))
241 })
242 }
243}
244
245impl<'de> Deserialize<'de> for PolymarketOrderStatus {
246 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247 where
248 D: serde::Deserializer<'de>,
249 {
250 let s = String::deserialize(deserializer)?;
251
252 Self::parse_wire(&s)
253 .map(|(status, _)| status)
254 .ok_or_else(|| serde::de::Error::custom(format!("Unknown PolymarketOrderStatus: {s}")))
255 }
256}
257
258#[derive(
260 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
261)]
262#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
263#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
264pub enum PolymarketTradeStatus {
265 #[serde(alias = "TRADE_STATUS_MATCHED")]
267 Matched,
268 #[serde(alias = "TRADE_STATUS_MATCHED_NOT_BROADCASTED")]
270 MatchedNotBroadcasted,
271 #[serde(alias = "TRADE_STATUS_MINED")]
273 Mined,
274 #[serde(alias = "TRADE_STATUS_CONFIRMED")]
276 Confirmed,
277 #[serde(alias = "TRADE_STATUS_RETRYING")]
279 Retrying,
280 #[serde(alias = "TRADE_STATUS_FAILED")]
282 Failed,
283}
284
285impl PolymarketTradeStatus {
286 #[must_use]
288 pub const fn is_finalized(&self) -> bool {
289 matches!(self, Self::Confirmed)
290 }
291
292 #[must_use]
294 pub const fn is_pending_settlement(&self) -> bool {
295 matches!(
296 self,
297 Self::Matched | Self::MatchedNotBroadcasted | Self::Mined | Self::Retrying
298 )
299 }
300}
301
302impl From<PolymarketOrderSide> for OrderSide {
303 fn from(value: PolymarketOrderSide) -> Self {
304 match value {
305 PolymarketOrderSide::Buy => Self::Buy,
306 PolymarketOrderSide::Sell => Self::Sell,
307 }
308 }
309}
310
311impl From<OrderSide> for PolymarketOrderSide {
312 fn from(value: OrderSide) -> Self {
313 match value {
314 OrderSide::Buy => Self::Buy,
315 OrderSide::Sell => Self::Sell,
316 }
317 }
318}
319
320impl From<PolymarketOrderSide> for AggressorSide {
321 fn from(value: PolymarketOrderSide) -> Self {
322 match value {
323 PolymarketOrderSide::Buy => Self::Buy,
324 PolymarketOrderSide::Sell => Self::Sell,
325 }
326 }
327}
328
329impl From<PolymarketOrderType> for TimeInForce {
330 fn from(value: PolymarketOrderType) -> Self {
331 match value {
332 PolymarketOrderType::GTC => Self::Gtc,
333 PolymarketOrderType::GTD => Self::Gtd,
334 PolymarketOrderType::FOK => Self::Fok,
335 PolymarketOrderType::FAK => Self::Ioc,
337 }
338 }
339}
340
341impl TryFrom<TimeInForce> for PolymarketOrderType {
342 type Error = anyhow::Error;
343
344 fn try_from(value: TimeInForce) -> anyhow::Result<Self> {
345 match value {
346 TimeInForce::Gtc => Ok(Self::GTC),
347 TimeInForce::Gtd => Ok(Self::GTD),
348 TimeInForce::Fok => Ok(Self::FOK),
349 TimeInForce::Ioc => Ok(Self::FAK),
350 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket: {value:?}"),
351 }
352 }
353}
354
355impl PolymarketOrderType {
356 pub(crate) fn from_market_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
357 match value {
358 TimeInForce::Fok => Ok(Self::FOK),
359 TimeInForce::Ioc => Ok(Self::FAK),
360 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket market order: {value:?}"),
361 }
362 }
363}
364
365impl From<PolymarketOrderStatus> for OrderStatus {
366 fn from(value: PolymarketOrderStatus) -> Self {
367 match value {
368 PolymarketOrderStatus::Invalid => Self::Rejected,
369 PolymarketOrderStatus::Live => Self::Accepted,
370 PolymarketOrderStatus::Delayed => Self::Accepted,
371 PolymarketOrderStatus::Matched => Self::Filled,
372 PolymarketOrderStatus::Unmatched => Self::Rejected,
374 PolymarketOrderStatus::Canceled => Self::Canceled,
375 PolymarketOrderStatus::CanceledMarketResolved => Self::Expired,
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use rstest::rstest;
384
385 use super::*;
386
387 #[rstest]
388 fn test_signature_type_serializes_as_u8() {
389 assert_eq!(
390 serde_json::to_string(&PolymarketSignatureType::Eoa).unwrap(),
391 "0"
392 );
393 assert_eq!(
394 serde_json::to_string(&PolymarketSignatureType::PolyProxy).unwrap(),
395 "1"
396 );
397 assert_eq!(
398 serde_json::to_string(&PolymarketSignatureType::PolyGnosisSafe).unwrap(),
399 "2"
400 );
401 assert_eq!(
402 serde_json::to_string(&PolymarketSignatureType::Poly1271).unwrap(),
403 "3"
404 );
405 }
406
407 #[rstest]
408 fn test_signature_type_deserializes_from_u8() {
409 assert_eq!(
410 serde_json::from_str::<PolymarketSignatureType>("0").unwrap(),
411 PolymarketSignatureType::Eoa
412 );
413 assert_eq!(
414 serde_json::from_str::<PolymarketSignatureType>("1").unwrap(),
415 PolymarketSignatureType::PolyProxy
416 );
417 assert_eq!(
418 serde_json::from_str::<PolymarketSignatureType>("2").unwrap(),
419 PolymarketSignatureType::PolyGnosisSafe
420 );
421 assert_eq!(
422 serde_json::from_str::<PolymarketSignatureType>("3").unwrap(),
423 PolymarketSignatureType::Poly1271
424 );
425 }
426
427 #[rstest]
428 fn test_order_side_serde_screaming_snake() {
429 assert_eq!(
430 serde_json::to_string(&PolymarketOrderSide::Buy).unwrap(),
431 "\"BUY\""
432 );
433 assert_eq!(
434 serde_json::from_str::<PolymarketOrderSide>("\"SELL\"").unwrap(),
435 PolymarketOrderSide::Sell
436 );
437 }
438
439 #[rstest]
440 fn test_event_type_serde_screaming_snake() {
441 assert_eq!(
442 serde_json::to_string(&PolymarketEventType::Placement).unwrap(),
443 "\"PLACEMENT\""
444 );
445 assert_eq!(
446 serde_json::from_str::<PolymarketEventType>("\"TRADE\"").unwrap(),
447 PolymarketEventType::Trade
448 );
449 }
450
451 #[rstest]
452 fn test_order_status_serde_screaming_snake() {
453 assert_eq!(
454 serde_json::to_string(&PolymarketOrderStatus::Live).unwrap(),
455 "\"LIVE\""
456 );
457 assert_eq!(
458 serde_json::from_str::<PolymarketOrderStatus>("\"CANCELED_MARKET_RESOLVED\"").unwrap(),
459 PolymarketOrderStatus::CanceledMarketResolved
460 );
461 }
462
463 #[rstest]
464 fn test_order_status_deserializes_openapi_prefix() {
465 assert_eq!(
466 serde_json::from_str::<PolymarketOrderStatus>("\"ORDER_STATUS_LIVE\"").unwrap(),
467 PolymarketOrderStatus::Live
468 );
469 assert_eq!(
470 serde_json::from_str::<PolymarketOrderStatus>("\"ORDER_STATUS_CANCELED_reason\"")
471 .unwrap(),
472 PolymarketOrderStatus::Canceled
473 );
474 }
475
476 #[rstest]
477 #[case(
478 "\"CANCELED_order couldn't be fully filled. FOK orders are fully filled or killed.\"",
479 PolymarketOrderStatus::Canceled
480 )]
481 #[case("\"CANCELED_some other reason\"", PolymarketOrderStatus::Canceled)]
482 #[case(
483 "\"CANCELED_MARKET_RESOLVED_resolved at block 12345\"",
484 PolymarketOrderStatus::CanceledMarketResolved
485 )]
486 #[case(
487 "\"UNMATCHED_insufficient liquidity\"",
488 PolymarketOrderStatus::Unmatched
489 )]
490 fn test_order_status_strips_reason_suffix(
491 #[case] raw: &str,
492 #[case] expected: PolymarketOrderStatus,
493 ) {
494 assert_eq!(
495 serde_json::from_str::<PolymarketOrderStatus>(raw).unwrap(),
496 expected,
497 );
498 }
499
500 #[rstest]
501 fn test_order_status_rejects_unknown() {
502 assert!(serde_json::from_str::<PolymarketOrderStatus>("\"UNKNOWN_STATUS\"").is_err());
503 assert!(serde_json::from_str::<PolymarketOrderStatus>("\"\"").is_err());
504 }
505
506 #[rstest]
507 fn test_trade_status_serde_screaming_snake() {
508 assert_eq!(
509 serde_json::to_string(&PolymarketTradeStatus::Confirmed).unwrap(),
510 "\"CONFIRMED\""
511 );
512 assert_eq!(
513 serde_json::from_str::<PolymarketTradeStatus>("\"RETRYING\"").unwrap(),
514 PolymarketTradeStatus::Retrying
515 );
516 }
517
518 #[rstest]
519 fn test_trade_status_deserializes_openapi_prefix() {
520 assert_eq!(
521 serde_json::from_str::<PolymarketTradeStatus>("\"TRADE_STATUS_CONFIRMED\"").unwrap(),
522 PolymarketTradeStatus::Confirmed
523 );
524 }
525
526 #[rstest]
527 #[case("\"MATCHED_NOT_BROADCASTED\"")]
528 #[case("\"TRADE_STATUS_MATCHED_NOT_BROADCASTED\"")]
529 fn test_trade_status_deserializes_matched_not_broadcasted(#[case] wire: &str) {
530 assert_eq!(
531 serde_json::from_str::<PolymarketTradeStatus>(wire).unwrap(),
532 PolymarketTradeStatus::MatchedNotBroadcasted
533 );
534 }
535
536 #[rstest]
537 #[case(PolymarketOrderSide::Buy, OrderSide::Buy)]
538 #[case(PolymarketOrderSide::Sell, OrderSide::Sell)]
539 fn test_order_side_to_nautilus(#[case] poly: PolymarketOrderSide, #[case] expected: OrderSide) {
540 assert_eq!(OrderSide::from(poly), expected);
541 }
542
543 #[rstest]
544 #[case(OrderSide::Buy, PolymarketOrderSide::Buy)]
545 #[case(OrderSide::Sell, PolymarketOrderSide::Sell)]
546 fn test_nautilus_order_side_to_poly(
547 #[case] nautilus: OrderSide,
548 #[case] expected: PolymarketOrderSide,
549 ) {
550 assert_eq!(PolymarketOrderSide::from(nautilus), expected);
551 }
552
553 #[rstest]
554 #[case(PolymarketOrderSide::Buy, AggressorSide::Buy)]
555 #[case(PolymarketOrderSide::Sell, AggressorSide::Sell)]
556 fn test_order_side_to_aggressor(
557 #[case] poly: PolymarketOrderSide,
558 #[case] expected: AggressorSide,
559 ) {
560 assert_eq!(AggressorSide::from(poly), expected);
561 }
562
563 #[rstest]
564 #[case(PolymarketOrderType::GTC, TimeInForce::Gtc)]
565 #[case(PolymarketOrderType::GTD, TimeInForce::Gtd)]
566 #[case(PolymarketOrderType::FOK, TimeInForce::Fok)]
567 #[case(PolymarketOrderType::FAK, TimeInForce::Ioc)]
568 fn test_order_type_to_time_in_force(
569 #[case] poly: PolymarketOrderType,
570 #[case] expected: TimeInForce,
571 ) {
572 assert_eq!(TimeInForce::from(poly), expected);
573 }
574
575 #[rstest]
576 #[case(TimeInForce::Gtc, PolymarketOrderType::GTC)]
577 #[case(TimeInForce::Gtd, PolymarketOrderType::GTD)]
578 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
579 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
580 fn test_time_in_force_to_order_type(
581 #[case] tif: TimeInForce,
582 #[case] expected: PolymarketOrderType,
583 ) {
584 assert_eq!(PolymarketOrderType::try_from(tif).unwrap(), expected);
585 }
586
587 #[rstest]
588 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
589 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
590 fn test_market_time_in_force_to_order_type(
591 #[case] tif: TimeInForce,
592 #[case] expected: PolymarketOrderType,
593 ) {
594 assert_eq!(
595 PolymarketOrderType::from_market_time_in_force(tif).unwrap(),
596 expected,
597 );
598 }
599
600 #[rstest]
601 #[case(TimeInForce::Gtc)]
602 #[case(TimeInForce::Gtd)]
603 fn test_market_time_in_force_to_order_type_rejects_non_market_tif(#[case] tif: TimeInForce) {
604 assert!(PolymarketOrderType::from_market_time_in_force(tif).is_err());
605 }
606
607 #[rstest]
608 #[case(PolymarketOrderStatus::Invalid, OrderStatus::Rejected)]
609 #[case(PolymarketOrderStatus::Live, OrderStatus::Accepted)]
610 #[case(PolymarketOrderStatus::Delayed, OrderStatus::Accepted)]
611 #[case(PolymarketOrderStatus::Matched, OrderStatus::Filled)]
612 #[case(PolymarketOrderStatus::Unmatched, OrderStatus::Rejected)]
613 #[case(PolymarketOrderStatus::Canceled, OrderStatus::Canceled)]
614 #[case(PolymarketOrderStatus::CanceledMarketResolved, OrderStatus::Expired)]
615 fn test_order_status_to_nautilus(
616 #[case] poly: PolymarketOrderStatus,
617 #[case] expected: OrderStatus,
618 ) {
619 assert_eq!(OrderStatus::from(poly), expected);
620 }
621
622 #[rstest]
623 fn test_trade_status_is_finalized() {
624 assert!(!PolymarketTradeStatus::Mined.is_finalized());
625 assert!(PolymarketTradeStatus::Confirmed.is_finalized());
626 assert!(!PolymarketTradeStatus::Matched.is_finalized());
627 assert!(!PolymarketTradeStatus::Retrying.is_finalized());
628 assert!(!PolymarketTradeStatus::Failed.is_finalized());
629 }
630
631 #[rstest]
632 fn test_trade_status_is_pending_settlement() {
633 assert!(PolymarketTradeStatus::Matched.is_pending_settlement());
634 assert!(PolymarketTradeStatus::MatchedNotBroadcasted.is_pending_settlement());
635 assert!(PolymarketTradeStatus::Mined.is_pending_settlement());
636 assert!(PolymarketTradeStatus::Retrying.is_pending_settlement());
637 assert!(!PolymarketTradeStatus::Confirmed.is_pending_settlement());
638 assert!(!PolymarketTradeStatus::Failed.is_pending_settlement());
639 }
640}