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.core.nautilus_pyo3.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<'de> Deserialize<'de> for PolymarketOrderStatus {
183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184 where
185 D: serde::Deserializer<'de>,
186 {
187 const VARIANTS: &[(&str, PolymarketOrderStatus)] = &[
192 (
193 "CANCELED_MARKET_RESOLVED",
194 PolymarketOrderStatus::CanceledMarketResolved,
195 ),
196 ("INVALID", PolymarketOrderStatus::Invalid),
197 ("LIVE", PolymarketOrderStatus::Live),
198 ("DELAYED", PolymarketOrderStatus::Delayed),
199 ("MATCHED", PolymarketOrderStatus::Matched),
200 ("UNMATCHED", PolymarketOrderStatus::Unmatched),
201 ("CANCELED", PolymarketOrderStatus::Canceled),
202 ];
203
204 let s = String::deserialize(deserializer)?;
205
206 if let Ok(status) = <Self as std::str::FromStr>::from_str(&s) {
208 return Ok(status);
209 }
210
211 for (prefix, status) in VARIANTS {
212 if s.len() > prefix.len()
213 && s.is_char_boundary(prefix.len())
214 && &s[..prefix.len()] == *prefix
215 && s.as_bytes()[prefix.len()] == b'_'
216 {
217 return Ok(*status);
218 }
219 }
220 Err(serde::de::Error::custom(format!(
221 "Unknown PolymarketOrderStatus: {s}"
222 )))
223 }
224}
225
226#[derive(
228 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
229)]
230#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
231#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
232pub enum PolymarketTradeStatus {
233 Matched,
235 Mined,
237 Confirmed,
239 Retrying,
241 Failed,
243}
244
245impl PolymarketTradeStatus {
246 #[must_use]
248 pub const fn is_finalized(&self) -> bool {
249 matches!(self, Self::Mined | Self::Confirmed)
250 }
251}
252
253impl From<PolymarketOrderSide> for OrderSide {
254 fn from(value: PolymarketOrderSide) -> Self {
255 match value {
256 PolymarketOrderSide::Buy => Self::Buy,
257 PolymarketOrderSide::Sell => Self::Sell,
258 }
259 }
260}
261
262impl TryFrom<OrderSide> for PolymarketOrderSide {
263 type Error = anyhow::Error;
264
265 fn try_from(value: OrderSide) -> anyhow::Result<Self> {
266 match value {
267 OrderSide::Buy => Ok(Self::Buy),
268 OrderSide::Sell => Ok(Self::Sell),
269 _ => anyhow::bail!("Invalid `OrderSide` for Polymarket: {value:?}"),
270 }
271 }
272}
273
274impl From<PolymarketOrderSide> for AggressorSide {
275 fn from(value: PolymarketOrderSide) -> Self {
276 match value {
277 PolymarketOrderSide::Buy => Self::Buyer,
278 PolymarketOrderSide::Sell => Self::Seller,
279 }
280 }
281}
282
283impl From<PolymarketOrderType> for TimeInForce {
284 fn from(value: PolymarketOrderType) -> Self {
285 match value {
286 PolymarketOrderType::GTC => Self::Gtc,
287 PolymarketOrderType::GTD => Self::Gtd,
288 PolymarketOrderType::FOK => Self::Fok,
289 PolymarketOrderType::FAK => Self::Ioc,
291 }
292 }
293}
294
295impl TryFrom<TimeInForce> for PolymarketOrderType {
296 type Error = anyhow::Error;
297
298 fn try_from(value: TimeInForce) -> anyhow::Result<Self> {
299 match value {
300 TimeInForce::Gtc => Ok(Self::GTC),
301 TimeInForce::Gtd => Ok(Self::GTD),
302 TimeInForce::Fok => Ok(Self::FOK),
303 TimeInForce::Ioc => Ok(Self::FAK),
304 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket: {value:?}"),
305 }
306 }
307}
308
309impl PolymarketOrderType {
310 pub(crate) fn from_market_time_in_force(value: TimeInForce) -> anyhow::Result<Self> {
311 match value {
312 TimeInForce::Fok => Ok(Self::FOK),
313 TimeInForce::Ioc => Ok(Self::FAK),
314 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket market order: {value:?}"),
315 }
316 }
317}
318
319impl From<PolymarketOrderStatus> for OrderStatus {
320 fn from(value: PolymarketOrderStatus) -> Self {
321 match value {
322 PolymarketOrderStatus::Invalid => Self::Rejected,
323 PolymarketOrderStatus::Live => Self::Accepted,
324 PolymarketOrderStatus::Delayed => Self::Accepted,
325 PolymarketOrderStatus::Matched => Self::Filled,
326 PolymarketOrderStatus::Unmatched => Self::Rejected,
328 PolymarketOrderStatus::Canceled => Self::Canceled,
329 PolymarketOrderStatus::CanceledMarketResolved => Self::Expired,
331 }
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use rstest::rstest;
338
339 use super::*;
340
341 #[rstest]
342 fn test_signature_type_serializes_as_u8() {
343 assert_eq!(serde_json::to_string(&SignatureType::Eoa).unwrap(), "0");
344 assert_eq!(
345 serde_json::to_string(&SignatureType::PolyProxy).unwrap(),
346 "1"
347 );
348 assert_eq!(
349 serde_json::to_string(&SignatureType::PolyGnosisSafe).unwrap(),
350 "2"
351 );
352 assert_eq!(
353 serde_json::to_string(&SignatureType::Poly1271).unwrap(),
354 "3"
355 );
356 }
357
358 #[rstest]
359 fn test_signature_type_deserializes_from_u8() {
360 assert_eq!(
361 serde_json::from_str::<SignatureType>("0").unwrap(),
362 SignatureType::Eoa
363 );
364 assert_eq!(
365 serde_json::from_str::<SignatureType>("1").unwrap(),
366 SignatureType::PolyProxy
367 );
368 assert_eq!(
369 serde_json::from_str::<SignatureType>("2").unwrap(),
370 SignatureType::PolyGnosisSafe
371 );
372 assert_eq!(
373 serde_json::from_str::<SignatureType>("3").unwrap(),
374 SignatureType::Poly1271
375 );
376 }
377
378 #[rstest]
379 fn test_order_side_serde_screaming_snake() {
380 assert_eq!(
381 serde_json::to_string(&PolymarketOrderSide::Buy).unwrap(),
382 "\"BUY\""
383 );
384 assert_eq!(
385 serde_json::from_str::<PolymarketOrderSide>("\"SELL\"").unwrap(),
386 PolymarketOrderSide::Sell
387 );
388 }
389
390 #[rstest]
391 fn test_event_type_serde_screaming_snake() {
392 assert_eq!(
393 serde_json::to_string(&PolymarketEventType::Placement).unwrap(),
394 "\"PLACEMENT\""
395 );
396 assert_eq!(
397 serde_json::from_str::<PolymarketEventType>("\"TRADE\"").unwrap(),
398 PolymarketEventType::Trade
399 );
400 }
401
402 #[rstest]
403 fn test_order_status_serde_screaming_snake() {
404 assert_eq!(
405 serde_json::to_string(&PolymarketOrderStatus::Live).unwrap(),
406 "\"LIVE\""
407 );
408 assert_eq!(
409 serde_json::from_str::<PolymarketOrderStatus>("\"CANCELED_MARKET_RESOLVED\"").unwrap(),
410 PolymarketOrderStatus::CanceledMarketResolved
411 );
412 }
413
414 #[rstest]
415 #[case(
416 "\"CANCELED_order couldn't be fully filled. FOK orders are fully filled or killed.\"",
417 PolymarketOrderStatus::Canceled
418 )]
419 #[case("\"CANCELED_some other reason\"", PolymarketOrderStatus::Canceled)]
420 #[case(
421 "\"CANCELED_MARKET_RESOLVED_resolved at block 12345\"",
422 PolymarketOrderStatus::CanceledMarketResolved
423 )]
424 #[case(
425 "\"UNMATCHED_insufficient liquidity\"",
426 PolymarketOrderStatus::Unmatched
427 )]
428 fn test_order_status_strips_reason_suffix(
429 #[case] raw: &str,
430 #[case] expected: PolymarketOrderStatus,
431 ) {
432 assert_eq!(
433 serde_json::from_str::<PolymarketOrderStatus>(raw).unwrap(),
434 expected,
435 );
436 }
437
438 #[rstest]
439 fn test_order_status_rejects_unknown() {
440 assert!(serde_json::from_str::<PolymarketOrderStatus>("\"UNKNOWN_STATUS\"").is_err());
441 assert!(serde_json::from_str::<PolymarketOrderStatus>("\"\"").is_err());
442 }
443
444 #[rstest]
445 fn test_trade_status_serde_screaming_snake() {
446 assert_eq!(
447 serde_json::to_string(&PolymarketTradeStatus::Confirmed).unwrap(),
448 "\"CONFIRMED\""
449 );
450 assert_eq!(
451 serde_json::from_str::<PolymarketTradeStatus>("\"RETRYING\"").unwrap(),
452 PolymarketTradeStatus::Retrying
453 );
454 }
455
456 #[rstest]
457 #[case(PolymarketOrderSide::Buy, OrderSide::Buy)]
458 #[case(PolymarketOrderSide::Sell, OrderSide::Sell)]
459 fn test_order_side_to_nautilus(#[case] poly: PolymarketOrderSide, #[case] expected: OrderSide) {
460 assert_eq!(OrderSide::from(poly), expected);
461 }
462
463 #[rstest]
464 #[case(OrderSide::Buy, PolymarketOrderSide::Buy)]
465 #[case(OrderSide::Sell, PolymarketOrderSide::Sell)]
466 fn test_nautilus_order_side_to_poly(
467 #[case] nautilus: OrderSide,
468 #[case] expected: PolymarketOrderSide,
469 ) {
470 assert_eq!(PolymarketOrderSide::try_from(nautilus).unwrap(), expected);
471 }
472
473 #[rstest]
474 #[case(PolymarketOrderSide::Buy, AggressorSide::Buyer)]
475 #[case(PolymarketOrderSide::Sell, AggressorSide::Seller)]
476 fn test_order_side_to_aggressor(
477 #[case] poly: PolymarketOrderSide,
478 #[case] expected: AggressorSide,
479 ) {
480 assert_eq!(AggressorSide::from(poly), expected);
481 }
482
483 #[rstest]
484 #[case(PolymarketOrderType::GTC, TimeInForce::Gtc)]
485 #[case(PolymarketOrderType::GTD, TimeInForce::Gtd)]
486 #[case(PolymarketOrderType::FOK, TimeInForce::Fok)]
487 #[case(PolymarketOrderType::FAK, TimeInForce::Ioc)]
488 fn test_order_type_to_time_in_force(
489 #[case] poly: PolymarketOrderType,
490 #[case] expected: TimeInForce,
491 ) {
492 assert_eq!(TimeInForce::from(poly), expected);
493 }
494
495 #[rstest]
496 #[case(TimeInForce::Gtc, PolymarketOrderType::GTC)]
497 #[case(TimeInForce::Gtd, PolymarketOrderType::GTD)]
498 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
499 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
500 fn test_time_in_force_to_order_type(
501 #[case] tif: TimeInForce,
502 #[case] expected: PolymarketOrderType,
503 ) {
504 assert_eq!(PolymarketOrderType::try_from(tif).unwrap(), expected);
505 }
506
507 #[rstest]
508 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
509 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
510 fn test_market_time_in_force_to_order_type(
511 #[case] tif: TimeInForce,
512 #[case] expected: PolymarketOrderType,
513 ) {
514 assert_eq!(
515 PolymarketOrderType::from_market_time_in_force(tif).unwrap(),
516 expected,
517 );
518 }
519
520 #[rstest]
521 #[case(TimeInForce::Gtc)]
522 #[case(TimeInForce::Gtd)]
523 fn test_market_time_in_force_to_order_type_rejects_non_market_tif(#[case] tif: TimeInForce) {
524 assert!(PolymarketOrderType::from_market_time_in_force(tif).is_err());
525 }
526
527 #[rstest]
528 #[case(PolymarketOrderStatus::Invalid, OrderStatus::Rejected)]
529 #[case(PolymarketOrderStatus::Live, OrderStatus::Accepted)]
530 #[case(PolymarketOrderStatus::Delayed, OrderStatus::Accepted)]
531 #[case(PolymarketOrderStatus::Matched, OrderStatus::Filled)]
532 #[case(PolymarketOrderStatus::Unmatched, OrderStatus::Rejected)]
533 #[case(PolymarketOrderStatus::Canceled, OrderStatus::Canceled)]
534 #[case(PolymarketOrderStatus::CanceledMarketResolved, OrderStatus::Expired)]
535 fn test_order_status_to_nautilus(
536 #[case] poly: PolymarketOrderStatus,
537 #[case] expected: OrderStatus,
538 ) {
539 assert_eq!(OrderStatus::from(poly), expected);
540 }
541
542 #[rstest]
543 fn test_trade_status_is_finalized() {
544 assert!(PolymarketTradeStatus::Mined.is_finalized());
545 assert!(PolymarketTradeStatus::Confirmed.is_finalized());
546 assert!(!PolymarketTradeStatus::Matched.is_finalized());
547 assert!(!PolymarketTradeStatus::Retrying.is_finalized());
548 assert!(!PolymarketTradeStatus::Failed.is_finalized());
549 }
550}