Skip to main content

brokapi_bybit/
cid.rs

1//! The client order id codec: Bybit keeps a client order id (`orderLinkId`) of up to 36 characters on every order,
2//! and a full hyphenated UUID is exactly 36.
3
4use brokapi_core::{Charset, CidCaps, CidCodec, CidError, ClientOrderId, NativeCid};
5use uuid::Uuid;
6
7/// The cid is written into the `orderLinkId` of the order as a hyphenated UUID (lossless, 128-bit, 36 characters of
8/// digits, letters and dashes, which the exchange allows). The exchange returns it in the order list, in the order
9/// stream and in the fills, which is how a cid comes back. The exchange refuses a second order with the same id while
10/// the first one is working, which is what makes a repeated place visible.
11#[derive(Debug, Clone, Copy, Default)]
12pub struct BybitCidCodec;
13
14impl CidCodec for BybitCidCodec {
15    fn caps(&self) -> CidCaps {
16        CidCaps {
17            max_len: 36,
18            charset: Charset::Ascii,
19            native_bits: 128,
20            lossless: true,
21        }
22    }
23
24    fn encode(&self, cid: ClientOrderId) -> Result<NativeCid, CidError> {
25        let s = cid.hyphenated().to_string();
26        if s.len() > self.caps().max_len {
27            return Err(CidError::TooLong {
28                len: s.len(),
29                max: self.caps().max_len,
30            });
31        }
32        Ok(NativeCid::new(s))
33    }
34
35    fn decode(&self, native: &NativeCid) -> Option<ClientOrderId> {
36        Uuid::parse_str(native.as_str()).ok()
37    }
38}
39
40/// The cid in the `orderLinkId` of an order: a whole UUID, else none (an order placed in the app of the exchange, or by
41/// another program).
42pub fn cid_from_link_id(link_id: Option<&str>) -> Option<ClientOrderId> {
43    Uuid::parse_str(link_id?.trim()).ok()
44}
45
46/// Namespace of the stable cids given to orders that are not ours.
47const FOREIGN_NS: Uuid = Uuid::from_u128(0x3d19_c0b2_6e7f_4a58_8c41_b7a2_5e90_0002);
48
49/// Stable cid for an order of this account that this connector did not place: the same order number always
50/// gives the same cid, so it can be told apart from ours and found again.
51pub fn foreign_cid(order_id: &str) -> ClientOrderId {
52    Uuid::new_v5(&FOREIGN_NS, order_id.as_bytes())
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use proptest::prelude::*;
59
60    #[test]
61    fn foreign_cids_are_stable_and_distinct() {
62        assert_eq!(
63            foreign_cid("1616024329462743808"),
64            foreign_cid("1616024329462743808")
65        );
66        assert_ne!(
67            foreign_cid("1616024329462743808"),
68            foreign_cid("1616024329462743809")
69        );
70        assert_eq!(foreign_cid("x").get_version_num(), 5);
71    }
72
73    #[test]
74    fn only_a_whole_uuid_in_the_link_id_is_a_cid() {
75        let cid = Uuid::new_v4();
76        assert_eq!(cid_from_link_id(Some(&cid.to_string())), Some(cid));
77        assert_eq!(cid_from_link_id(Some("my-first-order")), None);
78        assert_eq!(cid_from_link_id(None), None);
79    }
80
81    proptest! {
82        #[test]
83        fn bybit_cid_roundtrip(bytes in any::<[u8; 16]>()) {
84            let cid = ClientOrderId::from_bytes(bytes);
85            let native = BybitCidCodec.encode(cid).expect("encode");
86            prop_assert_eq!(BybitCidCodec.decode(&native), Some(cid));
87        }
88    }
89}