Skip to main content

algo_sdk/
events.rs

1//! Execution events — fills, rejects, extended metadata.
2
3// =============================================================================
4// FILL EVENT
5// =============================================================================
6
7#[derive(Debug, Clone, Copy)]
8#[repr(C)]
9pub struct Fill {
10    pub order_id: u64,
11    pub px_1e9: u64,
12    pub qty_1e8: i64,
13    pub recv_ns: u64, // Timestamp when fill was received
14    pub side: i8,
15    pub _pad: [u8; 7],
16}
17
18impl Fill {
19    /// Elapsed time since a caller-provided start timestamp.
20    /// Typical use: `fill.since_ms(start_ns)` where `start_ns` came from `book.recv_ns`.
21    #[inline(always)]
22    pub fn since_ns(&self, start_ns: u64) -> u64 {
23        self.recv_ns.saturating_sub(start_ns)
24    }
25
26    #[inline(always)]
27    pub fn since_us(&self, start_ns: u64) -> u64 {
28        self.since_ns(start_ns) / 1_000
29    }
30
31    #[inline(always)]
32    pub fn since_ms(&self, start_ns: u64) -> u64 {
33        self.since_ns(start_ns) / 1_000_000
34    }
35
36    /// Symbol identifier (v0.4+). 0 = single-symbol or unset.
37    /// In multi-symbol backtests, matches L2Book.symbol_id.
38    /// Stored in _pad[0..2] as little-endian u16 for ABI backward compat.
39    #[inline(always)]
40    pub fn symbol_id(&self) -> u16 {
41        u16::from_le_bytes([self._pad[0], self._pad[1]])
42    }
43
44    /// Set symbol identifier. Used by sim-engine orchestrator.
45    #[inline(always)]
46    pub fn set_symbol_id(&mut self, id: u16) {
47        let bytes = id.to_le_bytes();
48        self._pad[0] = bytes[0];
49        self._pad[1] = bytes[1];
50    }
51}
52
53// ABI size assertions — these must match the WASM memory layout expectations
54const _: () = assert!(
55    core::mem::size_of::<Fill>() == 40,
56    "Fill size changed — ABI break"
57);
58const _: () = assert!(
59    core::mem::size_of::<Reject>() == 16,
60    "Reject size changed — ABI break"
61);
62
63// =============================================================================
64// FILL EXTENDED METADATA
65// =============================================================================
66
67/// Extended fill metadata (NOT in WASM memory — delivered via separate channel).
68///
69/// Provides additional context about a fill that doesn't fit in the fixed-size
70/// `Fill` struct in WASM shared memory. Delivered out-of-band by the runtime.
71#[derive(Debug, Clone, Copy, Default)]
72pub struct FillExt {
73    /// 1 = maker, 0 = taker, 255 = unknown.
74    pub is_maker: u8,
75    /// Fee in 1e9 units. Negative = rebate.
76    pub fee_1e9: i64,
77    /// Estimated queue depth ahead at time of fill (1e8 units), or -1 if unknown.
78    pub queue_ahead_1e8: i64,
79}
80
81// =============================================================================
82// REJECT EVENT
83// =============================================================================
84
85/// Reject codes from exchange (matches cc_proto::RejectClass).
86pub mod RejectCode {
87    /// Unknown error
88    pub const UNKNOWN: u8 = 0;
89    /// Insufficient balance/funds
90    pub const INSUFFICIENT_BALANCE: u8 = 1;
91    /// Invalid parameters (price, qty, symbol)
92    pub const INVALID_PARAMS: u8 = 2;
93    /// Exchange rate limit hit
94    pub const RATE_LIMIT: u8 = 3;
95    /// Exchange temporarily unavailable
96    pub const EXCHANGE_BUSY: u8 = 4;
97    /// Network error
98    pub const NETWORK: u8 = 5;
99    /// Authentication error (invalid API key/secret)
100    pub const AUTH: u8 = 6;
101
102    // Internal reject codes (from risk engine, >=100)
103    /// Risk check failed
104    pub const RISK: u8 = 100;
105    /// Position limit exceeded
106    pub const POSITION_LIMIT: u8 = 101;
107    /// Kill switch triggered
108    pub const KILL_SWITCH: u8 = 102;
109    /// Fat-finger: price deviates too far from reference (best bid/ask)
110    pub const PRICE_DEVIATION: u8 = 103;
111    /// Daily P&L loss limit breached — algo paused
112    pub const DAILY_LOSS_LIMIT: u8 = 104;
113    /// Venue ID not in registered venue set (multi-venue)
114    pub const BAD_VENUE: u8 = 105;
115    /// Target venue data is stale (multi-venue)
116    pub const STALE_VENUE: u8 = 106;
117
118    /// Get human-readable description for a reject code.
119    pub fn to_str(code: u8) -> &'static str {
120        match code {
121            UNKNOWN => "UNKNOWN",
122            INSUFFICIENT_BALANCE => "INSUFFICIENT_BALANCE",
123            INVALID_PARAMS => "INVALID_PARAMS",
124            RATE_LIMIT => "RATE_LIMIT",
125            EXCHANGE_BUSY => "EXCHANGE_BUSY",
126            NETWORK => "NETWORK",
127            AUTH => "AUTH",
128            RISK => "RISK_CHECK_FAILED",
129            POSITION_LIMIT => "POSITION_LIMIT",
130            KILL_SWITCH => "KILL_SWITCH",
131            PRICE_DEVIATION => "PRICE_DEVIATION",
132            DAILY_LOSS_LIMIT => "DAILY_LOSS_LIMIT",
133            BAD_VENUE => "BAD_VENUE",
134            STALE_VENUE => "STALE_VENUE",
135            _ => "UNKNOWN",
136        }
137    }
138}
139
140#[derive(Debug, Clone, Copy)]
141#[repr(C)]
142pub struct Reject {
143    pub order_id: u64,
144    pub code: u8,
145    pub _pad: [u8; 7],
146}
147
148impl Reject {
149    /// Get human-readable description of the reject reason.
150    #[inline]
151    pub fn reason(&self) -> &'static str {
152        RejectCode::to_str(self.code)
153    }
154
155    /// Symbol identifier (v0.4+). 0 = single-symbol or unset.
156    #[inline(always)]
157    pub fn symbol_id(&self) -> u16 {
158        u16::from_le_bytes([self._pad[0], self._pad[1]])
159    }
160
161    #[inline(always)]
162    pub fn set_symbol_id(&mut self, id: u16) {
163        let bytes = id.to_le_bytes();
164        self._pad[0] = bytes[0];
165        self._pad[1] = bytes[1];
166    }
167}