nautilus_polymarket/common/
models.rs1use std::fmt::Display;
19
20use nautilus_common::cache::Cache;
21use nautilus_model::{
22 identifiers::InstrumentId,
23 instruments::{Instrument, InstrumentAny},
24};
25use rust_decimal::Decimal;
26use serde::{Deserialize, Serialize};
27use ustr::Ustr;
28
29use crate::common::{
30 enums::{PolymarketOrderSide, PolymarketOutcome},
31 parse::{deserialize_decimal_from_str, serialize_decimal_as_str},
32};
33
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub struct PolymarketMakerOrder {
47 pub asset_id: Ustr,
48 pub maker_address: String,
49 #[serde(
50 serialize_with = "serialize_decimal_as_str",
51 deserialize_with = "deserialize_decimal_from_str"
52 )]
53 pub matched_amount: Decimal,
54 pub order_id: String,
55 pub outcome: PolymarketOutcome,
56 pub owner: String,
57 #[serde(
58 serialize_with = "serialize_decimal_as_str",
59 deserialize_with = "deserialize_decimal_from_str"
60 )]
61 pub price: Decimal,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub side: Option<PolymarketOrderSide>,
64}
65
66#[derive(Debug, Clone)]
68pub struct PolymarketLabel {
69 pub description: String,
70 pub outcome: String,
71}
72
73impl Display for PolymarketLabel {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{} [{}]", self.description, self.outcome)
76 }
77}
78
79impl PolymarketLabel {
80 pub fn from_instrument(instrument: &InstrumentAny) -> Self {
82 if let InstrumentAny::BinaryOption(opt) = instrument {
83 Self {
84 description: opt
85 .description
86 .map_or_else(|| instrument.id().to_string(), |d| d.to_string()),
87 outcome: opt
88 .outcome
89 .map_or_else(|| "?".to_string(), |o| o.to_string()),
90 }
91 } else {
92 Self {
93 description: instrument.id().to_string(),
94 outcome: "?".to_string(),
95 }
96 }
97 }
98
99 pub fn from_cache(instrument_id: &InstrumentId, cache: &Cache) -> Option<Self> {
102 cache.instrument(instrument_id).map(Self::from_instrument)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use rstest::rstest;
109 use rust_decimal_macros::dec;
110
111 use super::*;
112 use crate::{common::enums::PolymarketOutcome, http::models::PolymarketTradeReport};
113
114 fn load<T: serde::de::DeserializeOwned>(filename: &str) -> T {
115 let path = format!("test_data/{filename}");
116 let content = std::fs::read_to_string(path).expect("Failed to read test data");
117 serde_json::from_str(&content).expect("Failed to parse test data")
118 }
119
120 fn sample_maker_order_json() -> &'static str {
121 r#"{
122 "asset_id": "71321045679252212594626385532706912750332728571942532289631379312455583992563",
123 "fee_rate_bps": "10",
124 "maker_address": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
125 "matched_amount": "50.0000",
126 "order_id": "0xorder001",
127 "outcome": "Yes",
128 "owner": "00000000-0000-0000-0000-000000000002",
129 "price": "0.6000"
130 }"#
131 }
132
133 #[rstest]
134 fn test_maker_order_deserialization() {
135 let order: PolymarketMakerOrder = serde_json::from_str(sample_maker_order_json()).unwrap();
136
137 assert_eq!(
138 order.asset_id.as_str(),
139 "71321045679252212594626385532706912750332728571942532289631379312455583992563"
140 );
141 assert_eq!(
142 order.maker_address.as_str(),
143 "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"
144 );
145 assert_eq!(order.matched_amount, dec!(50.0000));
146 assert_eq!(order.order_id, "0xorder001");
147 assert_eq!(order.outcome, PolymarketOutcome::yes());
148 assert_eq!(order.price, dec!(0.6000));
149 }
150
151 #[rstest]
152 fn test_maker_order_roundtrip() {
153 let order: PolymarketMakerOrder = serde_json::from_str(sample_maker_order_json()).unwrap();
154 let json = serde_json::to_string(&order).unwrap();
155 let order2: PolymarketMakerOrder = serde_json::from_str(&json).unwrap();
156 assert_eq!(order, order2);
157 }
158
159 #[rstest]
160 fn test_maker_order_outcome_no() {
161 let json = r#"{
162 "asset_id": "12345",
163 "fee_rate_bps": "0",
164 "maker_address": "0xaddr",
165 "matched_amount": "10.0",
166 "order_id": "order-1",
167 "outcome": "No",
168 "owner": "owner-1",
169 "price": "0.4"
170 }"#;
171 let order: PolymarketMakerOrder = serde_json::from_str(json).unwrap();
172 assert_eq!(order.outcome, PolymarketOutcome::no());
173 }
174
175 #[rstest]
176 fn test_maker_order_decimal_precision() {
177 let order: PolymarketMakerOrder = serde_json::from_str(sample_maker_order_json()).unwrap();
179 let json = serde_json::to_string(&order).unwrap();
180 assert!(
182 json.contains("\"matched_amount\":\"50.0000\"")
183 || json.contains("\"matched_amount\": \"50.0000\"")
184 );
185 }
186
187 #[rstest]
189 fn test_maker_orders_from_trade_report() {
190 let trade: PolymarketTradeReport = load("http_trade_report.json");
191
192 assert_eq!(trade.maker_orders.len(), 2);
193 let m0 = &trade.maker_orders[0];
194 assert_eq!(m0.matched_amount, dec!(25.0000));
195 assert_eq!(m0.outcome, PolymarketOutcome::yes());
196 assert_eq!(m0.side, Some(PolymarketOrderSide::Sell));
197
198 let m1 = &trade.maker_orders[1];
199 assert_eq!(m1.matched_amount, dec!(5.0000));
200 assert_eq!(m1.side, Some(PolymarketOrderSide::Sell));
201 }
202
203 #[rstest]
204 fn test_maker_order_without_side_is_accepted() {
205 let order: PolymarketMakerOrder = serde_json::from_str(sample_maker_order_json()).unwrap();
208 assert!(order.side.is_none());
209 }
210
211 #[rstest]
212 fn test_maker_order_with_side_is_parsed() {
213 let json = r#"{
214 "asset_id": "12345",
215 "fee_rate_bps": "0",
216 "maker_address": "0xaddr",
217 "matched_amount": "10.0",
218 "order_id": "order-1",
219 "outcome": "Yes",
220 "owner": "owner-1",
221 "price": "0.4",
222 "side": "BUY"
223 }"#;
224 let order: PolymarketMakerOrder = serde_json::from_str(json).unwrap();
225 assert_eq!(order.side, Some(PolymarketOrderSide::Buy));
226 }
227
228 #[rstest]
229 fn test_maker_order_with_empty_fee_rate_bps_is_accepted() {
230 let json = r#"{
234 "asset_id": "12345",
235 "fee_rate_bps": "",
236 "maker_address": "0xaddr",
237 "matched_amount": "10.0",
238 "order_id": "order-1",
239 "outcome": "Yes",
240 "owner": "owner-1",
241 "price": "0.4"
242 }"#;
243 let order: PolymarketMakerOrder = serde_json::from_str(json).unwrap();
244 assert_eq!(order.matched_amount, dec!(10.0));
245 }
246}