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