1use std::num::NonZeroU16;
2
3use alloy::primitives::U256;
4use fastnum::UD64;
5use thiserror::Error;
6
7use super::{event, types};
8use crate::{abi::dex, num};
9
10#[derive(Debug, Clone, Error)]
12pub enum OrderParseError {
13 #[error("order has invalid id 0")]
16 ZeroOrderId,
17}
18
19#[derive(Clone, Copy, derive_more::Debug)]
44pub struct Order {
45 instant: types::StateInstant,
46 request_id: Option<types::RequestId>,
47 client_order_id: Option<types::RequestId>,
48 order_id: types::OrderId,
49 r#type: types::OrderType,
50 account_id: types::AccountId,
51 #[debug("{price}")]
52 price: UD64, #[debug("{size}")]
54 size: UD64, placed_size: Option<UD64>, expiry_block: u64,
57 #[debug("{leverage}")]
58 leverage: UD64,
59 post_only: Option<bool>,
60 fill_or_kill: Option<bool>,
61 immediate_or_cancel: Option<bool>,
62 builder: Option<types::BuilderAttribution>,
66 prev_order_id: Option<types::OrderId>,
69 next_order_id: Option<types::OrderId>,
70}
71
72impl Order {
73 pub(crate) fn from_snapshot(
74 instant: types::StateInstant,
75 order: dex::Exchange::OrderV2,
76 base_price: UD64,
77 price_converter: num::Converter,
78 size_converter: num::Converter,
79 leverage_converter: num::Converter,
80 ) -> Result<Self, OrderParseError> {
81 let order_id = NonZeroU16::new(order.orderId).ok_or(OrderParseError::ZeroOrderId)?;
83
84 let prev_order_id = NonZeroU16::new(order.prevOrderId);
87 let next_order_id = NonZeroU16::new(order.nextOrderId);
88
89 Ok(Self {
90 instant,
91 request_id: None,
92 client_order_id: None, order_id,
94 r#type: order.orderType.into(),
95 account_id: order.accountId,
96 price: base_price + price_converter.from_unsigned(order.priceONS.to()),
97 size: size_converter.from_unsigned(order.lotLNS.to()),
98 placed_size: None,
99 expiry_block: order.expiryBlock as u64,
100 leverage: leverage_converter.from_u64(order.leverageHdths as u64),
101 post_only: None,
102 fill_or_kill: None,
103 immediate_or_cancel: None,
104 builder: (order.builderId != 0).then(|| {
107 types::BuilderAttribution::from_raw(
108 order.builderId,
109 U256::from(order.builderFeePer100K),
110 )
111 }),
112 prev_order_id,
113 next_order_id,
114 })
115 }
116
117 pub(crate) fn placed(
118 instant: types::StateInstant,
119 ctx: &event::OrderContext,
120 order_id: types::OrderId,
121 size: UD64,
122 price_converter: num::Converter,
123 leverage_converter: num::Converter,
124 ) -> Self {
125 Self {
126 instant,
127 request_id: Some(ctx.request_id),
128 client_order_id: Some(ctx.request_id),
130 order_id,
131 r#type: ctx.r#type.into(),
132 account_id: ctx.account_id,
133 price: price_converter.from_unsigned(ctx.price),
134 size,
135 placed_size: Some(size),
136 expiry_block: ctx.expiry_block,
137 leverage: leverage_converter.from_unsigned(ctx.leverage),
138 post_only: Some(ctx.post_only),
139 fill_or_kill: Some(ctx.fill_or_kill),
140 immediate_or_cancel: Some(ctx.immediate_or_cancel),
141 builder: ctx.builder,
142 prev_order_id: None,
144 next_order_id: None,
145 }
146 }
147
148 pub(crate) fn updated(
149 &self,
150 instant: types::StateInstant,
151 ctx: &Option<event::OrderContext>,
152 price: Option<UD64>,
153 size: Option<UD64>,
154 placed_size: Option<UD64>,
155 expiry_block: Option<u64>,
156 ) -> Self {
157 Self {
158 instant,
159 request_id: ctx.as_ref().map(|c| c.request_id),
160 client_order_id: self.client_order_id,
162 order_id: self.order_id,
163 r#type: self.r#type,
164 account_id: self.account_id,
165 price: price.unwrap_or(self.price),
166 size: size.unwrap_or(self.size),
167 placed_size: placed_size.or(self.placed_size),
168 expiry_block: expiry_block.unwrap_or(self.expiry_block),
169 leverage: self.leverage,
170 post_only: self.post_only,
171 fill_or_kill: self.fill_or_kill,
172 immediate_or_cancel: self.immediate_or_cancel,
173 builder: self.builder,
174 prev_order_id: self.prev_order_id,
177 next_order_id: self.next_order_id,
178 }
179 }
180
181 pub(crate) fn update_if_expired(&mut self, instant: types::StateInstant) -> bool {
182 if self.expiry_block != 0
183 && self.expiry_block <= instant.block_number()
184 && !self.is_expired()
185 {
186 self.instant = instant;
188 true
189 } else {
190 false
191 }
192 }
193
194 #[allow(unused)]
195 pub(crate) fn for_testing(r#type: types::OrderType, price: UD64, size: UD64) -> Self {
196 Self {
197 instant: types::StateInstant::new(0, 0),
198 request_id: None,
199 client_order_id: None,
200 order_id: NonZeroU16::MIN,
201 r#type,
202 account_id: 0,
203 price,
204 size,
205 placed_size: Some(size),
206 expiry_block: 0,
207 leverage: UD64::ZERO,
208 post_only: None,
209 fill_or_kill: None,
210 immediate_or_cancel: None,
211 builder: None,
212 prev_order_id: None,
213 next_order_id: None,
214 }
215 }
216
217 #[allow(unused)]
220 pub(crate) fn for_l3_testing(
221 r#type: types::OrderType,
222 price: UD64,
223 size: UD64,
224 block_number: u64,
225 order_id: types::OrderId,
226 account_id: types::AccountId,
227 ) -> Self {
228 Self {
229 instant: types::StateInstant::new(block_number, 0),
230 request_id: None,
231 client_order_id: None,
232 order_id,
233 r#type,
234 account_id,
235 price,
236 size,
237 placed_size: Some(size),
238 expiry_block: 0,
239 leverage: UD64::ZERO,
240 post_only: None,
241 fill_or_kill: None,
242 immediate_or_cancel: None,
243 builder: None,
244 prev_order_id: None,
245 next_order_id: None,
246 }
247 }
248
249 #[allow(unused, clippy::too_many_arguments)]
252 pub(crate) fn for_l3_testing_with_links(
253 r#type: types::OrderType,
254 price: UD64,
255 size: UD64,
256 block_number: u64,
257 order_id: types::OrderId,
258 account_id: types::AccountId,
259 prev_order_id: Option<types::OrderId>,
260 next_order_id: Option<types::OrderId>,
261 ) -> Self {
262 Self {
263 instant: types::StateInstant::new(block_number, 0),
264 request_id: None,
265 client_order_id: None,
266 order_id,
267 r#type,
268 account_id,
269 price,
270 size,
271 placed_size: Some(size),
272 expiry_block: 0,
273 leverage: UD64::ZERO,
274 post_only: None,
275 fill_or_kill: None,
276 immediate_or_cancel: None,
277 builder: None,
278 prev_order_id,
279 next_order_id,
280 }
281 }
282
283 #[allow(unused)]
285 pub(crate) fn with_size(&self, size: UD64) -> Self {
286 Self {
287 instant: self.instant,
288 request_id: self.request_id,
289 client_order_id: self.client_order_id,
290 order_id: self.order_id,
291 r#type: self.r#type,
292 account_id: self.account_id,
293 price: self.price,
294 size,
295 placed_size: self.placed_size,
296 expiry_block: self.expiry_block,
297 leverage: self.leverage,
298 post_only: self.post_only,
299 fill_or_kill: self.fill_or_kill,
300 immediate_or_cancel: self.immediate_or_cancel,
301 builder: self.builder,
302 prev_order_id: self.prev_order_id,
303 next_order_id: self.next_order_id,
304 }
305 }
306
307 #[allow(unused)]
309 pub(crate) fn with_price(&self, price: UD64) -> Self {
310 Self {
311 instant: self.instant,
312 request_id: self.request_id,
313 client_order_id: self.client_order_id,
314 order_id: self.order_id,
315 r#type: self.r#type,
316 account_id: self.account_id,
317 price,
318 size: self.size,
319 placed_size: self.placed_size,
320 expiry_block: self.expiry_block,
321 leverage: self.leverage,
322 post_only: self.post_only,
323 fill_or_kill: self.fill_or_kill,
324 immediate_or_cancel: self.immediate_or_cancel,
325 builder: self.builder,
326 prev_order_id: self.prev_order_id,
327 next_order_id: self.next_order_id,
328 }
329 }
330
331 #[allow(unused)]
333 pub(crate) fn with_expiry_block(&self, expiry_block: u64) -> Self {
334 Self {
335 instant: self.instant,
336 request_id: self.request_id,
337 client_order_id: self.client_order_id,
338 order_id: self.order_id,
339 r#type: self.r#type,
340 account_id: self.account_id,
341 price: self.price,
342 size: self.size,
343 placed_size: self.placed_size,
344 expiry_block,
345 leverage: self.leverage,
346 post_only: self.post_only,
347 fill_or_kill: self.fill_or_kill,
348 immediate_or_cancel: self.immediate_or_cancel,
349 builder: self.builder,
350 prev_order_id: self.prev_order_id,
351 next_order_id: self.next_order_id,
352 }
353 }
354
355 #[allow(unused)]
358 pub(crate) fn with_links(
359 &self,
360 prev_order_id: Option<types::OrderId>,
361 next_order_id: Option<types::OrderId>,
362 ) -> Self {
363 Self {
364 instant: self.instant,
365 request_id: self.request_id,
366 client_order_id: self.client_order_id,
367 order_id: self.order_id,
368 r#type: self.r#type,
369 account_id: self.account_id,
370 price: self.price,
371 size: self.size,
372 placed_size: self.placed_size,
373 expiry_block: self.expiry_block,
374 leverage: self.leverage,
375 post_only: self.post_only,
376 fill_or_kill: self.fill_or_kill,
377 immediate_or_cancel: self.immediate_or_cancel,
378 builder: self.builder,
379 prev_order_id,
380 next_order_id,
381 }
382 }
383
384 #[allow(unused)]
386 pub(crate) fn with_client_order_id(&self, client_order_id: types::RequestId) -> Self {
387 Self {
388 instant: self.instant,
389 request_id: self.request_id,
390 client_order_id: Some(client_order_id),
391 order_id: self.order_id,
392 r#type: self.r#type,
393 account_id: self.account_id,
394 price: self.price,
395 size: self.size,
396 placed_size: self.placed_size,
397 expiry_block: self.expiry_block,
398 leverage: self.leverage,
399 post_only: self.post_only,
400 fill_or_kill: self.fill_or_kill,
401 immediate_or_cancel: self.immediate_or_cancel,
402 builder: self.builder,
403 prev_order_id: self.prev_order_id,
404 next_order_id: self.next_order_id,
405 }
406 }
407
408 pub fn instant(&self) -> types::StateInstant { self.instant }
410
411 pub fn request_id(&self) -> Option<types::RequestId> { self.request_id }
414
415 pub fn client_order_id(&self) -> Option<types::RequestId> { self.client_order_id }
418
419 pub fn order_id(&self) -> types::OrderId { self.order_id }
421
422 pub fn r#type(&self) -> types::OrderType { self.r#type }
424
425 pub fn account_id(&self) -> types::AccountId { self.account_id }
427
428 pub fn price(&self) -> UD64 { self.price }
430
431 pub fn size(&self) -> UD64 { self.size }
433
434 pub fn placed_size(&self) -> Option<UD64> { self.placed_size }
437
438 pub fn filled_size(&self) -> Option<UD64> {
441 self.placed_size.map(|placed_size| placed_size - self.size)
442 }
443
444 pub fn expiry_block(&self) -> u64 { self.expiry_block }
446
447 pub fn is_expired(&self) -> bool {
450 self.expiry_block != 0 && self.expiry_block <= self.instant.block_number()
451 }
452
453 pub fn leverage(&self) -> UD64 { self.leverage }
455
456 pub fn post_only(&self) -> Option<bool> { self.post_only }
459
460 pub fn fill_or_kill(&self) -> Option<bool> { self.fill_or_kill }
463
464 pub fn immediate_or_cancel(&self) -> Option<bool> { self.immediate_or_cancel }
467
468 pub fn builder(&self) -> Option<types::BuilderAttribution> { self.builder }
478
479 pub fn prev_order_id(&self) -> Option<types::OrderId> { self.prev_order_id }
483
484 pub fn next_order_id(&self) -> Option<types::OrderId> { self.next_order_id }
488}
489
490impl std::fmt::Display for Order {
491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492 if f.alternate() {
493 let mut short = format!(
495 "{} {:#} #{} 👤{}",
496 self.size(),
497 self.r#type(),
498 self.order_id(),
499 self.account_id(),
500 );
501 if self.expiry_block > 0 {
502 short.push_str(format!(" ⏳{}", self.expiry_block).as_str());
503 }
504 write!(f, "[{}]", short)
505 } else {
506 write!(
507 f,
508 "[{}@{} {:#} #{} acc:{} rq:{} exp:{}{} lev:{}]",
509 self.size(),
510 self.price(),
511 self.r#type(),
512 self.order_id(),
513 self.account_id(),
514 self.request_id().unwrap_or_default(),
515 self.expiry_block(),
516 if self.is_expired() { " (expired)" } else { "" },
517 self.leverage(),
518 )
519 }
520 }
521}
522
523#[cfg(feature = "display")]
524impl tabled::Tabled for Order {
525 const LENGTH: usize = 13;
526
527 fn fields(&self) -> Vec<std::borrow::Cow<'_, str>> {
528 use colored::Colorize;
529
530 use crate::types::OrderSide;
531
532 vec![
533 match self.r#type.side() {
534 OrderSide::Ask => self.price().to_string().red().to_string().into(),
535 OrderSide::Bid => self.price().to_string().green().to_string().into(),
536 },
537 match self.r#type.side() {
538 OrderSide::Ask => self.size().to_string().red().to_string().into(),
539 OrderSide::Bid => self.size().to_string().green().to_string().into(),
540 },
541 match self.r#type.side() {
542 OrderSide::Ask => self.r#type().to_string().red().to_string().into(),
543 OrderSide::Bid => self.r#type().to_string().green().to_string().into(),
544 },
545 self.order_id().to_string().into(),
546 self.account_id().to_string().into(),
547 if let Some(request_id) = self.request_id() {
548 request_id.to_string().into()
549 } else {
550 "-".to_string().into()
551 },
552 if let Some(client_order_id) = self.client_order_id() {
553 client_order_id.to_string().into()
554 } else {
555 "-".to_string().into()
556 },
557 if self.expiry_block() > 0 {
558 if self.is_expired() {
559 (self.expiry_block().to_string() + " (expired)")
560 .bright_red()
561 .to_string()
562 .into()
563 } else {
564 self.expiry_block().to_string().into()
565 }
566 } else {
567 "-".to_string().into()
568 },
569 self.leverage().to_string().into(),
570 if self.post_only.unwrap_or_default() { "+" } else { "" }
571 .to_string()
572 .into(),
573 if self.fill_or_kill.unwrap_or_default() { "+" } else { "" }
574 .to_string()
575 .into(),
576 if self.immediate_or_cancel.unwrap_or_default() { "+" } else { "" }
577 .to_string()
578 .into(),
579 match self.builder {
580 Some(builder) => format!("{}@{}", builder.builder_id(), builder.fee()).into(),
581 None => "-".to_string().into(),
582 },
583 ]
584 }
585
586 fn headers() -> Vec<std::borrow::Cow<'static, str>> {
587 vec![
588 "Price".into(),
589 "Size".into(),
590 "Type".into(),
591 "Order ID".into(),
592 "Account ID".into(),
593 "Request ID".into(),
594 "Client Order ID".into(),
595 "Expiry Block".into(),
596 "Leverage".into(),
597 "PO".into(),
598 "FoK".into(),
599 "IoC".into(),
600 "Builder".into(),
601 ]
602 }
603}