brokapi_bybit/map/
execution.rs1use std::time::Instant;
4
5use brokapi_bybit_protocol::http::models::ExecutionRecord;
6use brokapi_core::{BrokerOrderId, ClientOrderId, Execution, Money, Price, Qty};
7
8use super::{broker_ts, side_of, timestamps};
9use crate::util::instrument_id;
10
11pub fn is_fill(r: &ExecutionRecord) -> bool {
14 matches!(
15 r.exec_type.as_str(),
16 "Trade" | "AdlTrade" | "BustTrade" | ""
17 )
18}
19
20pub fn execution(
23 r: &ExecutionRecord,
24 cid: Option<ClientOrderId>,
25 category: &str,
26 now: Instant,
27) -> Option<Execution> {
28 if !is_fill(r) {
29 return None;
30 }
31 let category = if r.category.is_empty() {
32 category
33 } else {
34 &r.category
35 };
36 Some(Execution {
37 cid,
38 broker_order_id: BrokerOrderId::new(r.order_id.clone()),
39 instrument: instrument_id(&r.symbol, category),
40 side: side_of(&r.side)?,
41 price: Price::new(r.exec_price),
42 qty: Qty::new(r.exec_qty),
43 commission: Money::new(r.exec_fee),
44 trade_id: r.exec_id.clone(),
45 ts: timestamps(now, broker_ts(r.exec_time)),
46 })
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn a_fill_becomes_an_execution() {
55 let r: ExecutionRecord = serde_json::from_str(
56 r#"{"execId":"e1","orderId":"77","symbol":"BTCUSDT","side":"Buy","execPrice":"100.50","execQty":"0.2","execFee":"0.011","execType":"Trade","execTime":"1672282722429"}"#,
57 )
58 .unwrap();
59 let e = execution(&r, None, "linear", Instant::now()).unwrap();
60 assert_eq!(e.qty.to_string(), "0.2");
61 assert_eq!(e.price.to_string(), "100.50");
62 assert_eq!(e.trade_id, "e1");
63 assert_eq!(e.broker_order_id.as_str(), "77");
64 assert_eq!(e.commission.to_string(), "0.011");
65 assert_eq!(
66 e.ts.broker_ts.unwrap().as_nanos(),
67 1_672_282_722_429_000_000
68 );
69 }
70
71 #[test]
72 fn a_funding_payment_is_not_a_fill() {
73 let r: ExecutionRecord = serde_json::from_str(
74 r#"{"execId":"f1","symbol":"BTCUSDT","side":"Buy","execPrice":"1","execQty":"1","execType":"Funding"}"#,
75 )
76 .unwrap();
77 assert!(execution(&r, None, "linear", Instant::now()).is_none());
78 }
79}