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.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}
51
52#[repr(C)]
58#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
59pub struct PolymarketOutcome(Ustr);
60
61impl PolymarketOutcome {
62 #[must_use]
63 pub fn yes() -> Self {
64 Self(Ustr::from("Yes"))
65 }
66
67 #[must_use]
68 pub fn no() -> Self {
69 Self(Ustr::from("No"))
70 }
71
72 #[must_use]
73 pub fn up() -> Self {
74 Self(Ustr::from("Up"))
75 }
76
77 #[must_use]
78 pub fn down() -> Self {
79 Self(Ustr::from("Down"))
80 }
81
82 #[must_use]
83 pub const fn inner(&self) -> Ustr {
84 self.0
85 }
86
87 #[must_use]
88 pub fn as_str(&self) -> &str {
89 self.0.as_str()
90 }
91}
92
93impl Debug for PolymarketOutcome {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(f, "\"{}\"", self.0)
96 }
97}
98
99impl Display for PolymarketOutcome {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{}", self.0)
102 }
103}
104
105impl From<&str> for PolymarketOutcome {
106 fn from(value: &str) -> Self {
107 Self(Ustr::from(value))
108 }
109}
110
111impl From<Ustr> for PolymarketOutcome {
112 fn from(value: Ustr) -> Self {
113 Self(value)
114 }
115}
116
117#[derive(
119 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
120)]
121#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
122#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
123pub enum PolymarketOrderSide {
124 Buy,
125 Sell,
126}
127
128#[derive(
130 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
131)]
132#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
133#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
134pub enum PolymarketLiquiditySide {
135 Maker,
136 Taker,
137}
138
139#[derive(
141 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
142)]
143pub enum PolymarketOrderType {
144 FOK,
145 FAK,
147 GTC,
148 GTD,
149}
150
151#[derive(
153 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
154)]
155#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
156#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
157pub enum PolymarketEventType {
158 Placement,
159 Update,
161 Cancellation,
162 Trade,
163}
164
165#[derive(
167 Clone, Copy, Debug, PartialEq, Eq, Hash, StrumDisplay, EnumString, Serialize, Deserialize,
168)]
169#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
170#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
171pub enum PolymarketOrderStatus {
172 Invalid,
173 Live,
174 Delayed,
176 Matched,
177 Unmatched,
179 Canceled,
180 CanceledMarketResolved,
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 PolymarketTradeStatus {
190 Matched,
192 Mined,
194 Confirmed,
196 Retrying,
198 Failed,
200}
201
202impl PolymarketTradeStatus {
203 #[must_use]
205 pub const fn is_finalized(&self) -> bool {
206 matches!(self, Self::Mined | Self::Confirmed)
207 }
208}
209
210impl From<PolymarketOrderSide> for OrderSide {
211 fn from(value: PolymarketOrderSide) -> Self {
212 match value {
213 PolymarketOrderSide::Buy => Self::Buy,
214 PolymarketOrderSide::Sell => Self::Sell,
215 }
216 }
217}
218
219impl TryFrom<OrderSide> for PolymarketOrderSide {
220 type Error = anyhow::Error;
221
222 fn try_from(value: OrderSide) -> anyhow::Result<Self> {
223 match value {
224 OrderSide::Buy => Ok(Self::Buy),
225 OrderSide::Sell => Ok(Self::Sell),
226 _ => anyhow::bail!("Invalid `OrderSide` for Polymarket: {value:?}"),
227 }
228 }
229}
230
231impl From<PolymarketOrderSide> for AggressorSide {
232 fn from(value: PolymarketOrderSide) -> Self {
233 match value {
234 PolymarketOrderSide::Buy => Self::Buyer,
235 PolymarketOrderSide::Sell => Self::Seller,
236 }
237 }
238}
239
240impl From<PolymarketOrderType> for TimeInForce {
241 fn from(value: PolymarketOrderType) -> Self {
242 match value {
243 PolymarketOrderType::GTC => Self::Gtc,
244 PolymarketOrderType::GTD => Self::Gtd,
245 PolymarketOrderType::FOK => Self::Fok,
246 PolymarketOrderType::FAK => Self::Ioc,
248 }
249 }
250}
251
252impl TryFrom<TimeInForce> for PolymarketOrderType {
253 type Error = anyhow::Error;
254
255 fn try_from(value: TimeInForce) -> anyhow::Result<Self> {
256 match value {
257 TimeInForce::Gtc => Ok(Self::GTC),
258 TimeInForce::Gtd => Ok(Self::GTD),
259 TimeInForce::Fok => Ok(Self::FOK),
260 TimeInForce::Ioc => Ok(Self::FAK),
261 _ => anyhow::bail!("Unsupported `TimeInForce` for Polymarket: {value:?}"),
262 }
263 }
264}
265
266impl From<PolymarketOrderStatus> for OrderStatus {
267 fn from(value: PolymarketOrderStatus) -> Self {
268 match value {
269 PolymarketOrderStatus::Invalid => Self::Rejected,
270 PolymarketOrderStatus::Live => Self::Accepted,
271 PolymarketOrderStatus::Delayed => Self::Accepted,
272 PolymarketOrderStatus::Matched => Self::Filled,
273 PolymarketOrderStatus::Unmatched => Self::Rejected,
275 PolymarketOrderStatus::Canceled => Self::Canceled,
276 PolymarketOrderStatus::CanceledMarketResolved => Self::Expired,
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use rstest::rstest;
285
286 use super::*;
287
288 #[rstest]
289 fn test_signature_type_serializes_as_u8() {
290 assert_eq!(serde_json::to_string(&SignatureType::Eoa).unwrap(), "0");
291 assert_eq!(
292 serde_json::to_string(&SignatureType::PolyProxy).unwrap(),
293 "1"
294 );
295 assert_eq!(
296 serde_json::to_string(&SignatureType::PolyGnosisSafe).unwrap(),
297 "2"
298 );
299 }
300
301 #[rstest]
302 fn test_signature_type_deserializes_from_u8() {
303 assert_eq!(
304 serde_json::from_str::<SignatureType>("0").unwrap(),
305 SignatureType::Eoa
306 );
307 assert_eq!(
308 serde_json::from_str::<SignatureType>("1").unwrap(),
309 SignatureType::PolyProxy
310 );
311 assert_eq!(
312 serde_json::from_str::<SignatureType>("2").unwrap(),
313 SignatureType::PolyGnosisSafe
314 );
315 }
316
317 #[rstest]
318 fn test_order_side_serde_screaming_snake() {
319 assert_eq!(
320 serde_json::to_string(&PolymarketOrderSide::Buy).unwrap(),
321 "\"BUY\""
322 );
323 assert_eq!(
324 serde_json::from_str::<PolymarketOrderSide>("\"SELL\"").unwrap(),
325 PolymarketOrderSide::Sell
326 );
327 }
328
329 #[rstest]
330 fn test_event_type_serde_screaming_snake() {
331 assert_eq!(
332 serde_json::to_string(&PolymarketEventType::Placement).unwrap(),
333 "\"PLACEMENT\""
334 );
335 assert_eq!(
336 serde_json::from_str::<PolymarketEventType>("\"TRADE\"").unwrap(),
337 PolymarketEventType::Trade
338 );
339 }
340
341 #[rstest]
342 fn test_order_status_serde_screaming_snake() {
343 assert_eq!(
344 serde_json::to_string(&PolymarketOrderStatus::Live).unwrap(),
345 "\"LIVE\""
346 );
347 assert_eq!(
348 serde_json::from_str::<PolymarketOrderStatus>("\"CANCELED_MARKET_RESOLVED\"").unwrap(),
349 PolymarketOrderStatus::CanceledMarketResolved
350 );
351 }
352
353 #[rstest]
354 fn test_trade_status_serde_screaming_snake() {
355 assert_eq!(
356 serde_json::to_string(&PolymarketTradeStatus::Confirmed).unwrap(),
357 "\"CONFIRMED\""
358 );
359 assert_eq!(
360 serde_json::from_str::<PolymarketTradeStatus>("\"RETRYING\"").unwrap(),
361 PolymarketTradeStatus::Retrying
362 );
363 }
364
365 #[rstest]
366 #[case(PolymarketOrderSide::Buy, OrderSide::Buy)]
367 #[case(PolymarketOrderSide::Sell, OrderSide::Sell)]
368 fn test_order_side_to_nautilus(#[case] poly: PolymarketOrderSide, #[case] expected: OrderSide) {
369 assert_eq!(OrderSide::from(poly), expected);
370 }
371
372 #[rstest]
373 #[case(OrderSide::Buy, PolymarketOrderSide::Buy)]
374 #[case(OrderSide::Sell, PolymarketOrderSide::Sell)]
375 fn test_nautilus_order_side_to_poly(
376 #[case] nautilus: OrderSide,
377 #[case] expected: PolymarketOrderSide,
378 ) {
379 assert_eq!(PolymarketOrderSide::try_from(nautilus).unwrap(), expected);
380 }
381
382 #[rstest]
383 #[case(PolymarketOrderSide::Buy, AggressorSide::Buyer)]
384 #[case(PolymarketOrderSide::Sell, AggressorSide::Seller)]
385 fn test_order_side_to_aggressor(
386 #[case] poly: PolymarketOrderSide,
387 #[case] expected: AggressorSide,
388 ) {
389 assert_eq!(AggressorSide::from(poly), expected);
390 }
391
392 #[rstest]
393 #[case(PolymarketOrderType::GTC, TimeInForce::Gtc)]
394 #[case(PolymarketOrderType::GTD, TimeInForce::Gtd)]
395 #[case(PolymarketOrderType::FOK, TimeInForce::Fok)]
396 #[case(PolymarketOrderType::FAK, TimeInForce::Ioc)]
397 fn test_order_type_to_time_in_force(
398 #[case] poly: PolymarketOrderType,
399 #[case] expected: TimeInForce,
400 ) {
401 assert_eq!(TimeInForce::from(poly), expected);
402 }
403
404 #[rstest]
405 #[case(TimeInForce::Gtc, PolymarketOrderType::GTC)]
406 #[case(TimeInForce::Gtd, PolymarketOrderType::GTD)]
407 #[case(TimeInForce::Fok, PolymarketOrderType::FOK)]
408 #[case(TimeInForce::Ioc, PolymarketOrderType::FAK)]
409 fn test_time_in_force_to_order_type(
410 #[case] tif: TimeInForce,
411 #[case] expected: PolymarketOrderType,
412 ) {
413 assert_eq!(PolymarketOrderType::try_from(tif).unwrap(), expected);
414 }
415
416 #[rstest]
417 #[case(PolymarketOrderStatus::Invalid, OrderStatus::Rejected)]
418 #[case(PolymarketOrderStatus::Live, OrderStatus::Accepted)]
419 #[case(PolymarketOrderStatus::Delayed, OrderStatus::Accepted)]
420 #[case(PolymarketOrderStatus::Matched, OrderStatus::Filled)]
421 #[case(PolymarketOrderStatus::Unmatched, OrderStatus::Rejected)]
422 #[case(PolymarketOrderStatus::Canceled, OrderStatus::Canceled)]
423 #[case(PolymarketOrderStatus::CanceledMarketResolved, OrderStatus::Expired)]
424 fn test_order_status_to_nautilus(
425 #[case] poly: PolymarketOrderStatus,
426 #[case] expected: OrderStatus,
427 ) {
428 assert_eq!(OrderStatus::from(poly), expected);
429 }
430
431 #[rstest]
432 fn test_trade_status_is_finalized() {
433 assert!(PolymarketTradeStatus::Mined.is_finalized());
434 assert!(PolymarketTradeStatus::Confirmed.is_finalized());
435 assert!(!PolymarketTradeStatus::Matched.is_finalized());
436 assert!(!PolymarketTradeStatus::Retrying.is_finalized());
437 assert!(!PolymarketTradeStatus::Failed.is_finalized());
438 }
439}