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_MINED")]
270 Mined,
271 #[serde(alias = "TRADE_STATUS_CONFIRMED")]
273 Confirmed,
274 #[serde(alias = "TRADE_STATUS_RETRYING")]
276 Retrying,
277 #[serde(alias = "TRADE_STATUS_FAILED")]
279 Failed,
280}
281
282impl PolymarketTradeStatus {
283 #[must_use]
285 pub const fn is_finalized(&self) -> bool {
286 matches!(self, Self::Confirmed)
287 }
288
289 #[must_use]
291 pub const fn is_pending_settlement(&self) -> bool {
292 matches!(self, Self::Matched | Self::Mined | Self::Retrying)
293 }
294}
295
296impl From<PolymarketOrderSide> for OrderSide {
297 fn from(value: PolymarketOrderSide) -> Self {
298 match value {
299 PolymarketOrderSide::Buy => Self::Buy,
300 PolymarketOrderSide::Sell => Self::Sell,
301 }
302 }
303}
304
305impl From<OrderSide> for PolymarketOrderSide {
306 fn from(value: OrderSide) -> Self {
307 match value {
308 OrderSide::Buy => Self::Buy,
309 OrderSide::Sell => Self::Sell,
310 }
311 }
312}
313
314impl From<PolymarketOrderSide> for AggressorSide {
315 fn from(value: PolymarketOrderSide) -> Self {
316 match value {
317 PolymarketOrderSide::Buy => Self::Buy,
318 PolymarketOrderSide::Sell => Self::Sell,
319 }
320 }
321}
322
323impl From<PolymarketOrderType> for TimeInForce {
324 fn from(value: PolymarketOrderType) -> Self {
325 match value {
326 PolymarketOrderType::GTC => Self::Gtc,
327 PolymarketOrderType::GTD => Self::Gtd,
328 PolymarketOrderType::FOK => Self::Fok,
329 PolymarketOrderType::FAK => Self::Ioc,
331 }
332 }
333}
334
335impl TryFrom<TimeInForce> for PolymarketOrderType {
336 type Error = anyhow::Error;
337
338 fn try_from(value: TimeInForce) -> anyhow::Result<Self> {
339 match value {
340 TimeInForce::Gtc => Ok(Self::GTC),
341 TimeInForce::Gtd => Ok(Self::GTD),
342 TimeInForce::Fok => Ok(Self::FOK),
343 TimeInForce::Ioc => Ok(Self::FAK),
344 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket: {value:?}"),
345 }
346 }
347}
348
349impl PolymarketOrderType {
350 pub(crate) fn from_market_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
351 match value {
352 TimeInForce::Fok => Ok(Self::FOK),
353 TimeInForce::Ioc => Ok(Self::FAK),
354 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket market order: {value:?}"),
355 }
356 }
357}
358
359impl From<PolymarketOrderStatus> for OrderStatus {
360 fn from(value: PolymarketOrderStatus) -> Self {
361 match value {
362 PolymarketOrderStatus::Invalid => Self::Rejected,
363 PolymarketOrderStatus::Live => Self::Accepted,
364 PolymarketOrderStatus::Delayed => Self::Accepted,
365 PolymarketOrderStatus::Matched => Self::Filled,
366 PolymarketOrderStatus::Unmatched => Self::Rejected,
368 PolymarketOrderStatus::Canceled => Self::Canceled,
369 PolymarketOrderStatus::CanceledMarketResolved => Self::Expired,
371 }
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use rstest::rstest;
378
379 use super::*;
380
381 #[rstest]
382 fn test_signature_type_serializes_as_u8() {
383 assert_eq!(
384 serde_json::to_string(&PolymarketSignatureType::Eoa).unwrap(),
385 "0"
386 );
387 assert_eq!(
388 serde_json::to_string(&PolymarketSignatureType::PolyProxy).unwrap(),
389 "1"
390 );
391 assert_eq!(
392 serde_json::to_string(&PolymarketSignatureType::PolyGnosisSafe).unwrap(),
393 "2"
394 );
395 assert_eq!(
396 serde_json::to_string(&PolymarketSignatureType::Poly1271).unwrap(),
397 "3"
398 );
399 }
400
401 #[rstest]
402 fn test_signature_type_deserializes_from_u8() {
403 assert_eq!(
404 serde_json::from_str::<PolymarketSignatureType>("0").unwrap(),
405 PolymarketSignatureType::Eoa
406 );
407 assert_eq!(
408 serde_json::from_str::<PolymarketSignatureType>("1").unwrap(),
409 PolymarketSignatureType::PolyProxy
410 );
411 assert_eq!(
412 serde_json::from_str::<PolymarketSignatureType>("2").unwrap(),
413 PolymarketSignatureType::PolyGnosisSafe
414 );
415 assert_eq!(
416 serde_json::from_str::<PolymarketSignatureType>("3").unwrap(),
417 PolymarketSignatureType::Poly1271
418 );
419 }
420
421 #[rstest]
422 fn test_order_side_serde_screaming_snake() {
423 assert_eq!(
424 serde_json::to_string(&PolymarketOrderSide::Buy).unwrap(),
425 "\"BUY\""
426 );
427 assert_eq!(
428 serde_json::from_str::<PolymarketOrderSide>("\"SELL\"").unwrap(),
429 PolymarketOrderSide::Sell
430 );
431 }
432
433 #[rstest]
434 fn test_event_type_serde_screaming_snake() {
435 assert_eq!(
436 serde_json::to_string(&PolymarketEventType::Placement).unwrap(),
437 "\"PLACEMENT\""
438 );
439 assert_eq!(
440 serde_json::from_str::<PolymarketEventType>("\"TRADE\"").unwrap(),
441 PolymarketEventType::Trade
442 );
443 }
444
445 #[rstest]
446 fn test_order_status_serde_screaming_snake() {
447 assert_eq!(
448 serde_json::to_string(&PolymarketOrderStatus::Live).unwrap(),
449 "\"LIVE\""
450 );
451 assert_eq!(
452 serde_json::from_str::<PolymarketOrderStatus>("\"CANCELED_MARKET_RESOLVED\"").unwrap(),
453 PolymarketOrderStatus::CanceledMarketResolved
454 );
455 }
456
457 #[rstest]
458 fn test_order_status_deserializes_openapi_prefix() {
459 assert_eq!(
460 serde_json::from_str::<PolymarketOrderStatus>("\"ORDER_STATUS_LIVE\"").unwrap(),
461 PolymarketOrderStatus::Live
462 );
463 assert_eq!(
464 serde_json::from_str::<PolymarketOrderStatus>("\"ORDER_STATUS_CANCELED_reason\"")
465 .unwrap(),
466 PolymarketOrderStatus::Canceled
467 );
468 }
469
470 #[rstest]
471 #[case(
472 "\"CANCELED_order couldn't be fully filled. FOK orders are fully filled or killed.\"",
473 PolymarketOrderStatus::Canceled
474 )]
475 #[case("\"CANCELED_some other reason\"", PolymarketOrderStatus::Canceled)]
476 #[case(
477 "\"CANCELED_MARKET_RESOLVED_resolved at block 12345\"",
478 PolymarketOrderStatus::CanceledMarketResolved
479 )]
480 #[case(
481 "\"UNMATCHED_insufficient liquidity\"",
482 PolymarketOrderStatus::Unmatched
483 )]
484 fn test_order_status_strips_reason_suffix(
485 #[case] raw: &str,
486 #[case] expected: PolymarketOrderStatus,
487 ) {
488 assert_eq!(
489 serde_json::from_str::<PolymarketOrderStatus>(raw).unwrap(),
490 expected,
491 );
492 }
493
494 #[rstest]
495 fn test_order_status_rejects_unknown() {
496 assert!(serde_json::from_str::<PolymarketOrderStatus>("\"UNKNOWN_STATUS\"").is_err());
497 assert!(serde_json::from_str::<PolymarketOrderStatus>("\"\"").is_err());
498 }
499
500 #[rstest]
501 fn test_trade_status_serde_screaming_snake() {
502 assert_eq!(
503 serde_json::to_string(&PolymarketTradeStatus::Confirmed).unwrap(),
504 "\"CONFIRMED\""
505 );
506 assert_eq!(
507 serde_json::from_str::<PolymarketTradeStatus>("\"RETRYING\"").unwrap(),
508 PolymarketTradeStatus::Retrying
509 );
510 }
511
512 #[rstest]
513 fn test_trade_status_deserializes_openapi_prefix() {
514 assert_eq!(
515 serde_json::from_str::<PolymarketTradeStatus>("\"TRADE_STATUS_CONFIRMED\"").unwrap(),
516 PolymarketTradeStatus::Confirmed
517 );
518 }
519
520 #[rstest]
521 #[case(PolymarketOrderSide::Buy, OrderSide::Buy)]
522 #[case(PolymarketOrderSide::Sell, OrderSide::Sell)]
523 fn test_order_side_to_nautilus(#[case] poly: PolymarketOrderSide, #[case] expected: OrderSide) {
524 assert_eq!(OrderSide::from(poly), expected);
525 }
526
527 #[rstest]
528 #[case(OrderSide::Buy, PolymarketOrderSide::Buy)]
529 #[case(OrderSide::Sell, PolymarketOrderSide::Sell)]
530 fn test_nautilus_order_side_to_poly(
531 #[case] nautilus: OrderSide,
532 #[case] expected: PolymarketOrderSide,
533 ) {
534 assert_eq!(PolymarketOrderSide::from(nautilus), expected);
535 }
536
537 #[rstest]
538 #[case(PolymarketOrderSide::Buy, AggressorSide::Buy)]
539 #[case(PolymarketOrderSide::Sell, AggressorSide::Sell)]
540 fn test_order_side_to_aggressor(
541 #[case] poly: PolymarketOrderSide,
542 #[case] expected: AggressorSide,
543 ) {
544 assert_eq!(AggressorSide::from(poly), expected);
545 }
546
547 #[rstest]
548 #[case(PolymarketOrderType::GTC, TimeInForce::Gtc)]
549 #[case(PolymarketOrderType::GTD, TimeInForce::Gtd)]
550 #[case(PolymarketOrderType::FOK, TimeInForce::Fok)]
551 #[case(PolymarketOrderType::FAK, TimeInForce::Ioc)]
552 fn test_order_type_to_time_in_force(
553 #[case] poly: PolymarketOrderType,
554 #[case] expected: TimeInForce,
555 ) {
556 assert_eq!(TimeInForce::from(poly), expected);
557 }
558
559 #[rstest]
560 #[case(TimeInForce::Gtc, PolymarketOrderType::GTC)]
561 #[case(TimeInForce::Gtd, PolymarketOrderType::GTD)]
562 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
563 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
564 fn test_time_in_force_to_order_type(
565 #[case] tif: TimeInForce,
566 #[case] expected: PolymarketOrderType,
567 ) {
568 assert_eq!(PolymarketOrderType::try_from(tif).unwrap(), expected);
569 }
570
571 #[rstest]
572 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
573 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
574 fn test_market_time_in_force_to_order_type(
575 #[case] tif: TimeInForce,
576 #[case] expected: PolymarketOrderType,
577 ) {
578 assert_eq!(
579 PolymarketOrderType::from_market_time_in_force(tif).unwrap(),
580 expected,
581 );
582 }
583
584 #[rstest]
585 #[case(TimeInForce::Gtc)]
586 #[case(TimeInForce::Gtd)]
587 fn test_market_time_in_force_to_order_type_rejects_non_market_tif(#[case] tif: TimeInForce) {
588 assert!(PolymarketOrderType::from_market_time_in_force(tif).is_err());
589 }
590
591 #[rstest]
592 #[case(PolymarketOrderStatus::Invalid, OrderStatus::Rejected)]
593 #[case(PolymarketOrderStatus::Live, OrderStatus::Accepted)]
594 #[case(PolymarketOrderStatus::Delayed, OrderStatus::Accepted)]
595 #[case(PolymarketOrderStatus::Matched, OrderStatus::Filled)]
596 #[case(PolymarketOrderStatus::Unmatched, OrderStatus::Rejected)]
597 #[case(PolymarketOrderStatus::Canceled, OrderStatus::Canceled)]
598 #[case(PolymarketOrderStatus::CanceledMarketResolved, OrderStatus::Expired)]
599 fn test_order_status_to_nautilus(
600 #[case] poly: PolymarketOrderStatus,
601 #[case] expected: OrderStatus,
602 ) {
603 assert_eq!(OrderStatus::from(poly), expected);
604 }
605
606 #[rstest]
607 fn test_trade_status_is_finalized() {
608 assert!(!PolymarketTradeStatus::Mined.is_finalized());
609 assert!(PolymarketTradeStatus::Confirmed.is_finalized());
610 assert!(!PolymarketTradeStatus::Matched.is_finalized());
611 assert!(!PolymarketTradeStatus::Retrying.is_finalized());
612 assert!(!PolymarketTradeStatus::Failed.is_finalized());
613 }
614
615 #[rstest]
616 fn test_trade_status_is_pending_settlement() {
617 assert!(PolymarketTradeStatus::Matched.is_pending_settlement());
618 assert!(PolymarketTradeStatus::Mined.is_pending_settlement());
619 assert!(PolymarketTradeStatus::Retrying.is_pending_settlement());
620 assert!(!PolymarketTradeStatus::Confirmed.is_pending_settlement());
621 assert!(!PolymarketTradeStatus::Failed.is_pending_settlement());
622 }
623}