Skip to main content

brokapi_bybit/map/
execution.rs

1//! Fills.
2
3use 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
11/// Whether the record is a fill of an order. The funding payments the exchange lists among the executions
12/// (`execType` `Funding`) and the settlement of a delivery are not fills of an order of the bot.
13pub fn is_fill(r: &ExecutionRecord) -> bool {
14    matches!(
15        r.exec_type.as_str(),
16        "Trade" | "AdlTrade" | "BustTrade" | ""
17    )
18}
19
20/// The fill. The commission is the fee as the exchange states it: a rebate is negative. The currency of the fee is the
21/// settle coin of the instrument.
22pub 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}