Skip to main content

nautilus_bitmex/http/
models.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Data structures representing BitMEX REST API payloads.
17
18use jiff::Timestamp;
19use nautilus_core::string::secret::SecretString;
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use ustr::Ustr;
23use uuid::Uuid;
24use zeroize::Zeroize;
25
26use crate::common::{
27    enums::{
28        BitmexContingencyType, BitmexExecInstruction, BitmexExecType, BitmexFairMethod,
29        BitmexInstrumentState, BitmexInstrumentType, BitmexLiquidityIndicator, BitmexMarkMethod,
30        BitmexOrderStatus, BitmexOrderType, BitmexPegPriceType, BitmexSide, BitmexTickDirection,
31        BitmexTimeInForce,
32    },
33    serialization::optional_decimal,
34};
35
36/// Custom deserializer for comma-separated `ExecInstruction` values
37fn deserialize_exec_instructions<'de, D>(
38    deserializer: D,
39) -> Result<Option<Vec<BitmexExecInstruction>>, D::Error>
40where
41    D: serde::Deserializer<'de>,
42{
43    let s: Option<String> = Option::deserialize(deserializer)?;
44    match s {
45        None => Ok(None),
46        Some(ref s) if s.is_empty() => Ok(None),
47        Some(s) => {
48            let instructions: Result<Vec<BitmexExecInstruction>, _> = s
49                .split(',')
50                .map(|inst| {
51                    let trimmed = inst.trim();
52                    match trimmed {
53                        "ParticipateDoNotInitiate" => {
54                            Ok(BitmexExecInstruction::ParticipateDoNotInitiate)
55                        }
56                        "AllOrNone" => Ok(BitmexExecInstruction::AllOrNone),
57                        "MarkPrice" => Ok(BitmexExecInstruction::MarkPrice),
58                        "IndexPrice" => Ok(BitmexExecInstruction::IndexPrice),
59                        "LastPrice" => Ok(BitmexExecInstruction::LastPrice),
60                        "Close" => Ok(BitmexExecInstruction::Close),
61                        "ReduceOnly" => Ok(BitmexExecInstruction::ReduceOnly),
62                        "Fixed" => Ok(BitmexExecInstruction::Fixed),
63                        "" => Ok(BitmexExecInstruction::Unknown),
64                        _ => Err(serde::de::Error::custom(format!(
65                            "Unknown ExecInstruction: {trimmed}"
66                        ))),
67                    }
68                })
69                .collect();
70            instructions.map(Some)
71        }
72    }
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct BitmexInstrument {
78    pub symbol: Ustr,
79    pub root_symbol: Ustr,
80    pub state: BitmexInstrumentState,
81    #[serde(rename = "typ")]
82    pub instrument_type: BitmexInstrumentType,
83    pub listing: Option<Timestamp>,
84    pub front: Option<Timestamp>,
85    pub expiry: Option<Timestamp>,
86    pub settle: Option<Timestamp>,
87    pub listed_settle: Option<Timestamp>,
88    pub position_currency: Option<Ustr>,
89    pub underlying: Ustr,
90    pub quote_currency: Ustr,
91    pub underlying_symbol: Option<Ustr>,
92    pub reference: Option<Ustr>,
93    pub reference_symbol: Option<Ustr>,
94    pub calc_interval: Option<Timestamp>,
95    pub publish_interval: Option<Timestamp>,
96    pub publish_time: Option<Timestamp>,
97    pub max_order_qty: Option<f64>,
98    pub max_price: Option<f64>,
99    pub min_price: Option<f64>,
100    pub lot_size: Option<f64>,
101    pub tick_size: f64,
102    pub multiplier: f64,
103    pub settl_currency: Option<Ustr>,
104    pub underlying_to_position_multiplier: Option<f64>,
105    pub underlying_to_settle_multiplier: Option<f64>,
106    pub quote_to_settle_multiplier: Option<f64>,
107    pub is_quanto: bool,
108    pub is_inverse: bool,
109    pub init_margin: Option<f64>,
110    pub maint_margin: Option<f64>,
111    pub risk_limit: Option<f64>,
112    pub risk_step: Option<f64>,
113    pub limit: Option<f64>,
114    pub taxed: Option<bool>,
115    pub deleverage: Option<bool>,
116    pub maker_fee: Option<f64>,
117    pub taker_fee: Option<f64>,
118    pub settlement_fee: Option<f64>,
119    pub funding_base_symbol: Option<Ustr>,
120    pub funding_quote_symbol: Option<Ustr>,
121    pub funding_premium_symbol: Option<Ustr>,
122    pub funding_timestamp: Option<Timestamp>,
123    pub funding_interval: Option<Timestamp>,
124    #[serde(default, with = "rust_decimal::serde::float_option")]
125    pub funding_rate: Option<Decimal>,
126    #[serde(default, with = "rust_decimal::serde::float_option")]
127    pub indicative_funding_rate: Option<Decimal>,
128    pub rebalance_timestamp: Option<Timestamp>,
129    pub rebalance_interval: Option<Timestamp>,
130    pub prev_close_price: Option<f64>,
131    pub limit_down_price: Option<f64>,
132    pub limit_up_price: Option<f64>,
133    pub prev_total_volume: Option<f64>,
134    pub total_volume: Option<f64>,
135    pub volume: Option<f64>,
136    #[serde(rename = "volume24h")]
137    pub volume_24h: Option<f64>,
138    pub prev_total_turnover: Option<f64>,
139    pub total_turnover: Option<f64>,
140    pub turnover: Option<f64>,
141    #[serde(rename = "turnover24h")]
142    pub turnover_24h: Option<f64>,
143    #[serde(rename = "homeNotional24h")]
144    pub home_notional_24h: Option<f64>,
145    #[serde(rename = "foreignNotional24h")]
146    pub foreign_notional_24h: Option<f64>,
147    #[serde(rename = "prevPrice24h")]
148    pub prev_price_24h: Option<f64>,
149    pub vwap: Option<f64>,
150    pub high_price: Option<f64>,
151    pub low_price: Option<f64>,
152    pub last_price: Option<f64>,
153    pub last_price_protected: Option<f64>,
154    pub last_tick_direction: Option<BitmexTickDirection>,
155    pub last_change_pcnt: Option<f64>,
156    pub bid_price: Option<f64>,
157    pub mid_price: Option<f64>,
158    pub ask_price: Option<f64>,
159    pub impact_bid_price: Option<f64>,
160    pub impact_mid_price: Option<f64>,
161    pub impact_ask_price: Option<f64>,
162    pub has_liquidity: Option<bool>,
163    pub open_interest: Option<f64>,
164    pub open_value: Option<f64>,
165    pub fair_method: Option<BitmexFairMethod>,
166    pub fair_basis_rate: Option<f64>,
167    pub fair_basis: Option<f64>,
168    pub fair_price: Option<f64>,
169    pub mark_method: Option<BitmexMarkMethod>,
170    pub mark_price: Option<f64>,
171    pub indicative_settle_price: Option<f64>,
172    pub settled_price_adjustment_rate: Option<f64>,
173    pub settled_price: Option<f64>,
174    pub instant_pnl: bool,
175    pub min_tick: Option<f64>,
176    pub funding_base_rate: Option<f64>,
177    pub funding_quote_rate: Option<f64>,
178    pub capped: Option<bool>,
179    pub opening_timestamp: Option<Timestamp>,
180    pub closing_timestamp: Option<Timestamp>,
181    pub timestamp: Timestamp,
182}
183
184/// Raw Order and Balance Data.
185#[derive(Clone, Debug, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct BitmexExecution {
188    #[serde(rename = "execID")]
189    pub exec_id: Uuid,
190    #[serde(rename = "orderID")]
191    pub order_id: Option<Uuid>,
192    #[serde(rename = "clOrdID")]
193    pub cl_ord_id: Option<Ustr>,
194    #[serde(rename = "clOrdLinkID")]
195    pub cl_ord_link_id: Option<Ustr>,
196    pub account: i64,
197    pub symbol: Option<Ustr>,
198    pub side: Option<BitmexSide>,
199    pub last_qty: i64,
200    pub last_px: f64,
201    pub underlying_last_px: Option<f64>,
202    pub last_mkt: Option<Ustr>,
203    pub last_liquidity_ind: Option<BitmexLiquidityIndicator>,
204    pub order_qty: Option<i64>,
205    pub price: Option<f64>,
206    pub display_qty: Option<i64>,
207    pub stop_px: Option<f64>,
208    pub peg_offset_value: Option<f64>,
209    pub peg_price_type: Option<BitmexPegPriceType>,
210    pub currency: Option<Ustr>,
211    pub settl_currency: Option<Ustr>,
212    pub exec_type: BitmexExecType,
213    pub ord_type: BitmexOrderType,
214    pub time_in_force: BitmexTimeInForce,
215    #[serde(default, deserialize_with = "deserialize_exec_instructions")]
216    pub exec_inst: Option<Vec<BitmexExecInstruction>>,
217    pub contingency_type: Option<BitmexContingencyType>,
218    pub ex_destination: Option<Ustr>,
219    pub ord_status: Option<BitmexOrderStatus>,
220    pub triggered: Option<Ustr>,
221    pub working_indicator: Option<bool>,
222    pub ord_rej_reason: Option<Ustr>,
223    pub leaves_qty: Option<i64>,
224    pub cum_qty: Option<i64>,
225    pub avg_px: Option<f64>,
226    pub commission: Option<f64>,
227    pub trade_publish_indicator: Option<Ustr>,
228    pub multi_leg_reporting_type: Option<Ustr>,
229    pub text: Option<Ustr>,
230    pub trd_match_id: Option<Uuid>,
231    pub exec_cost: Option<i64>,
232    pub exec_comm: Option<i64>,
233    pub home_notional: Option<f64>,
234    pub foreign_notional: Option<f64>,
235    pub transact_time: Option<Timestamp>,
236    pub timestamp: Option<Timestamp>,
237}
238
239/// Swap Funding History.
240#[derive(Clone, Debug, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct BitmexFunding {
243    pub timestamp: Timestamp,
244    pub symbol: Ustr,
245    pub funding_interval: Option<Timestamp>,
246    #[serde(default, with = "rust_decimal::serde::float_option")]
247    pub funding_rate: Option<Decimal>,
248    #[serde(default, with = "rust_decimal::serde::float_option")]
249    pub funding_rate_daily: Option<Decimal>,
250}
251
252#[derive(Clone, Debug, Serialize, Deserialize, Default)]
253#[serde(rename_all = "camelCase")]
254pub struct BitmexInstrumentInterval {
255    pub intervals: Vec<String>,
256    pub symbols: Vec<String>,
257}
258
259#[derive(Clone, Debug, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct BitmexIndexComposite {
262    pub timestamp: Timestamp,
263    pub symbol: Option<String>,
264    pub index_symbol: Option<String>,
265    pub reference: Option<String>,
266    pub last_price: Option<f64>,
267    pub weight: Option<f64>,
268    pub logged: Option<Timestamp>,
269}
270
271/// Insurance Fund Data.
272#[derive(Clone, Debug, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct BitmexInsurance {
275    pub currency: Ustr,
276    pub timestamp: Timestamp,
277    pub wallet_balance: Option<i64>,
278}
279
280/// Active Liquidations.
281#[derive(Clone, Debug, Serialize, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub struct BitmexLiquidation {
284    #[serde(rename = "orderID")]
285    pub order_id: Uuid,
286    pub symbol: Option<String>,
287    pub side: Option<BitmexSide>,
288    pub price: Option<f64>,
289    pub leaves_qty: Option<i64>,
290}
291
292/// Placement, Cancellation, Amending, and History.
293#[derive(Clone, Debug, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct BitmexOrder {
296    #[serde(rename = "orderID")]
297    pub order_id: Uuid,
298    #[serde(rename = "clOrdID")]
299    pub cl_ord_id: Option<Ustr>,
300    #[serde(rename = "clOrdLinkID")]
301    pub cl_ord_link_id: Option<Ustr>,
302    pub account: i64,
303    pub symbol: Option<Ustr>,
304    pub side: Option<BitmexSide>,
305    pub order_qty: Option<i64>,
306    pub price: Option<f64>,
307    pub display_qty: Option<i64>,
308    pub stop_px: Option<f64>,
309    pub peg_offset_value: Option<f64>,
310    pub peg_price_type: Option<BitmexPegPriceType>,
311    pub currency: Option<Ustr>,
312    pub settl_currency: Option<Ustr>,
313    pub ord_type: Option<BitmexOrderType>,
314    pub time_in_force: Option<BitmexTimeInForce>,
315    #[serde(default, deserialize_with = "deserialize_exec_instructions")]
316    pub exec_inst: Option<Vec<BitmexExecInstruction>>,
317    pub contingency_type: Option<BitmexContingencyType>,
318    pub ex_destination: Option<Ustr>,
319    pub ord_status: Option<BitmexOrderStatus>,
320    pub triggered: Option<Ustr>,
321    pub working_indicator: Option<bool>,
322    pub ord_rej_reason: Option<Ustr>,
323    pub leaves_qty: Option<i64>,
324    pub cum_qty: Option<i64>,
325    #[serde(default, with = "optional_decimal")]
326    pub avg_px: Option<Decimal>,
327    pub multi_leg_reporting_type: Option<Ustr>,
328    pub text: Option<Ustr>,
329    pub transact_time: Option<Timestamp>,
330    pub timestamp: Option<Timestamp>,
331}
332
333#[derive(Clone, Debug, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct BitmexOrderBookL2 {
336    pub symbol: Ustr,
337    pub id: i64,
338    pub side: BitmexSide,
339    pub size: Option<i64>,
340    pub price: Option<f64>,
341}
342
343/// Position status.
344#[derive(Clone, Debug, Serialize, Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct BitmexPosition {
347    pub account: i64,
348    pub symbol: Ustr,
349    pub currency: Option<Ustr>,
350    pub underlying: Option<Ustr>,
351    pub quote_currency: Option<Ustr>,
352    pub commission: Option<f64>,
353    pub init_margin_req: Option<f64>,
354    pub maint_margin_req: Option<f64>,
355    pub risk_limit: Option<i64>,
356    pub leverage: Option<f64>,
357    pub cross_margin: Option<bool>,
358    pub deleverage_percentile: Option<f64>,
359    pub rebalanced_pnl: Option<i64>,
360    pub prev_realised_pnl: Option<i64>,
361    pub prev_unrealised_pnl: Option<i64>,
362    pub prev_close_price: Option<f64>,
363    pub opening_timestamp: Option<Timestamp>,
364    pub opening_qty: Option<i64>,
365    pub opening_cost: Option<i64>,
366    pub opening_comm: Option<i64>,
367    pub open_order_buy_qty: Option<i64>,
368    pub open_order_buy_cost: Option<i64>,
369    pub open_order_buy_premium: Option<i64>,
370    pub open_order_sell_qty: Option<i64>,
371    pub open_order_sell_cost: Option<i64>,
372    pub open_order_sell_premium: Option<i64>,
373    pub exec_buy_qty: Option<i64>,
374    pub exec_buy_cost: Option<i64>,
375    pub exec_sell_qty: Option<i64>,
376    pub exec_sell_cost: Option<i64>,
377    pub exec_qty: Option<i64>,
378    pub exec_cost: Option<i64>,
379    pub exec_comm: Option<i64>,
380    pub current_timestamp: Option<Timestamp>,
381    pub current_qty: Option<i64>,
382    pub current_cost: Option<i64>,
383    pub current_comm: Option<i64>,
384    pub realised_cost: Option<i64>,
385    pub unrealised_cost: Option<i64>,
386    pub gross_open_cost: Option<i64>,
387    pub gross_open_premium: Option<i64>,
388    pub gross_exec_cost: Option<i64>,
389    pub is_open: Option<bool>,
390    pub mark_price: Option<f64>,
391    pub mark_value: Option<i64>,
392    pub risk_value: Option<i64>,
393    pub home_notional: Option<f64>,
394    pub foreign_notional: Option<f64>,
395    pub pos_state: Option<Ustr>,
396    pub pos_cost: Option<i64>,
397    pub pos_cost2: Option<i64>,
398    pub pos_cross: Option<i64>,
399    pub pos_init: Option<i64>,
400    pub pos_comm: Option<i64>,
401    pub pos_loss: Option<i64>,
402    pub pos_margin: Option<i64>,
403    pub pos_maint: Option<i64>,
404    pub pos_allowance: Option<i64>,
405    pub taxable_margin: Option<i64>,
406    pub init_margin: Option<i64>,
407    pub maint_margin: Option<i64>,
408    pub session_margin: Option<i64>,
409    pub target_excess_margin: Option<i64>,
410    pub var_margin: Option<i64>,
411    pub realised_gross_pnl: Option<i64>,
412    pub realised_tax: Option<i64>,
413    pub realised_pnl: Option<i64>,
414    pub unrealised_gross_pnl: Option<i64>,
415    pub long_bankrupt: Option<i64>,
416    pub short_bankrupt: Option<i64>,
417    pub tax_base: Option<i64>,
418    pub indicative_tax_rate: Option<f64>,
419    pub indicative_tax: Option<i64>,
420    pub unrealised_tax: Option<i64>,
421    pub unrealised_pnl: Option<i64>,
422    pub unrealised_pnl_pcnt: Option<f64>,
423    pub unrealised_roe_pcnt: Option<f64>,
424    pub avg_cost_price: Option<f64>,
425    pub avg_entry_price: Option<f64>,
426    pub break_even_price: Option<f64>,
427    pub margin_call_price: Option<f64>,
428    pub liquidation_price: Option<f64>,
429    pub bankrupt_price: Option<f64>,
430    pub timestamp: Option<Timestamp>,
431    pub last_price: Option<f64>,
432    pub last_value: Option<i64>,
433}
434
435/// Best Bid/Offer Snapshots & Historical Bins.
436#[derive(Clone, Debug, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct BitmexQuote {
439    pub timestamp: Timestamp,
440    pub symbol: Ustr,
441    pub bid_size: Option<i64>,
442    pub bid_price: Option<f64>,
443    pub ask_price: Option<f64>,
444    pub ask_size: Option<i64>,
445}
446
447/// Historical Settlement Data.
448#[derive(Clone, Debug, Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct BitmexSettlement {
451    pub timestamp: Timestamp,
452    pub symbol: Ustr,
453    pub settlement_type: Option<String>,
454    pub settled_price: Option<f64>,
455    pub option_strike_price: Option<f64>,
456    pub option_underlying_price: Option<f64>,
457    pub bankrupt: Option<i64>,
458    pub tax_base: Option<i64>,
459    pub tax_rate: Option<f64>,
460}
461
462/// Exchange Statistics.
463#[derive(Clone, Debug, Deserialize)]
464#[serde(rename_all = "camelCase")]
465pub struct BitmexStats {
466    pub root_symbol: Ustr,
467    pub currency: Option<String>,
468    pub volume24h: Option<i64>,
469    pub turnover24h: Option<i64>,
470    pub open_interest: Option<i64>,
471    pub open_value: Option<i64>,
472}
473
474#[derive(Clone, Debug, Deserialize)]
475#[serde(rename_all = "camelCase")]
476pub struct BitmexStatsHistory {
477    pub date: Timestamp,
478    pub root_symbol: Ustr,
479    pub currency: Option<String>,
480    pub volume: Option<i64>,
481    pub turnover: Option<i64>,
482}
483
484#[derive(Clone, Debug, Deserialize)]
485#[serde(rename_all = "camelCase")]
486pub struct BitmexStatsUSD {
487    pub root_symbol: Ustr,
488    pub currency: Option<String>,
489    pub turnover24h: Option<i64>,
490    pub turnover30d: Option<i64>,
491    pub turnover365d: Option<i64>,
492    pub turnover: Option<i64>,
493}
494
495/// Individual & Bucketed Trades.
496#[derive(Clone, Debug, Deserialize)]
497#[serde(rename_all = "camelCase")]
498pub struct BitmexTrade {
499    pub timestamp: Timestamp,
500    pub symbol: Ustr,
501    pub side: Option<BitmexSide>,
502    pub size: i64,
503    pub price: f64,
504    pub tick_direction: Option<String>,
505    #[serde(rename = "trdMatchID")]
506    pub trd_match_id: Option<Uuid>,
507    pub gross_value: Option<i64>,
508    pub home_notional: Option<f64>,
509    pub foreign_notional: Option<f64>,
510}
511
512#[derive(Clone, Debug, Deserialize)]
513#[serde(rename_all = "camelCase")]
514pub struct BitmexTradeBin {
515    pub timestamp: Timestamp,
516    pub symbol: Ustr,
517    pub open: Option<f64>,
518    pub high: Option<f64>,
519    pub low: Option<f64>,
520    pub close: Option<f64>,
521    pub trades: Option<i64>,
522    pub volume: Option<i64>,
523    pub vwap: Option<f64>,
524    pub last_size: Option<i64>,
525    pub turnover: Option<i64>,
526    pub home_notional: Option<f64>,
527    pub foreign_notional: Option<f64>,
528}
529
530#[derive(Clone, Debug, Deserialize)]
531#[serde(rename_all = "camelCase")]
532pub struct BitmexWallet {
533    pub account: i64,
534    pub currency: Ustr,
535    pub prev_deposited: Option<i64>,
536    pub prev_withdrawn: Option<i64>,
537    pub prev_transfer_in: Option<i64>,
538    pub prev_transfer_out: Option<i64>,
539    pub prev_amount: Option<i64>,
540    pub prev_timestamp: Option<Timestamp>,
541    pub delta_deposited: Option<i64>,
542    pub delta_withdrawn: Option<i64>,
543    pub delta_transfer_in: Option<i64>,
544    pub delta_transfer_out: Option<i64>,
545    pub delta_amount: Option<i64>,
546    pub deposited: Option<i64>,
547    pub withdrawn: Option<i64>,
548    pub transfer_in: Option<i64>,
549    pub transfer_out: Option<i64>,
550    pub amount: Option<i64>,
551    pub pending_credit: Option<i64>,
552    pub pending_debit: Option<i64>,
553    pub confirmed_debit: Option<i64>,
554    pub timestamp: Option<Timestamp>,
555    pub addr: Option<Ustr>,
556    pub script: Option<Ustr>,
557    pub withdrawal_lock: Option<Vec<Ustr>>,
558}
559
560#[derive(Clone, Debug, Deserialize, Default)]
561#[serde(rename_all = "camelCase")]
562pub struct BitmexTransaction {
563    pub transact_id: Option<Uuid>,
564    pub account: Option<i64>,
565    pub currency: Option<Ustr>,
566    pub transact_type: Option<Ustr>,
567    pub amount: Option<i64>,
568    pub fee: Option<i64>,
569    pub transact_status: Option<Ustr>,
570    pub address: Option<Ustr>,
571    pub tx: Option<Ustr>,
572    pub text: Option<Ustr>,
573    pub transact_time: Option<Timestamp>,
574    pub timestamp: Option<Timestamp>,
575}
576
577/// Public Announcements.
578#[derive(Clone, Debug, Deserialize)]
579#[serde(rename_all = "camelCase")]
580pub struct BitmexAnnouncement {
581    pub id: i32,
582    pub link: Option<String>,
583    pub title: Option<String>,
584    pub content: Option<String>,
585    pub date: Option<Timestamp>,
586}
587
588/// Persistent API Keys for Developers.
589#[derive(Debug, Clone, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct BitmexAPIKey {
592    pub id: SecretString,
593    pub secret: Option<SecretString>,
594    pub name: String,
595    pub nonce: i64,
596    pub cidr: Option<String>,
597    pub permissions: Vec<serde_json::Value>,
598    pub enabled: Option<bool>,
599    pub user_id: i32,
600    pub created: Option<Timestamp>,
601}
602
603impl Zeroize for BitmexAPIKey {
604    fn zeroize(&mut self) {
605        self.id.zeroize();
606        self.secret.zeroize();
607    }
608}
609
610/// Account Notifications.
611#[derive(Clone, Debug, Deserialize)]
612#[serde(rename_all = "camelCase")]
613pub struct BitmexGlobalNotification {
614    pub id: Option<i32>,
615    pub date: Timestamp,
616    pub title: String,
617    pub body: String,
618    pub ttl: i32,
619    pub r#type: Option<String>,
620    pub closable: Option<bool>,
621    pub persist: Option<bool>,
622    pub wait_for_visibility: Option<bool>,
623    pub sound: Option<String>,
624}
625
626#[derive(Debug, Clone, Deserialize)]
627#[serde(rename_all = "camelCase")]
628pub struct BitmexAccessToken {
629    pub id: SecretString,
630    /// The time to live in seconds (2 weeks by default).
631    pub ttl: Option<f64>,
632    pub created: Option<Timestamp>,
633    pub user_id: Option<f64>,
634}
635
636impl Zeroize for BitmexAccessToken {
637    fn zeroize(&mut self) {
638        self.id.zeroize();
639    }
640}
641
642/// Daily Quote Fill Ratio Statistic.
643#[derive(Clone, Debug, Deserialize)]
644#[serde(rename_all = "camelCase")]
645pub struct BitmexQuoteFillRatio {
646    pub date: Timestamp,
647    pub account: Option<f64>,
648    pub quote_count: Option<f64>,
649    pub dealt_count: Option<f64>,
650    pub quotes_mavg7: Option<f64>,
651    pub dealt_mavg7: Option<f64>,
652    pub quote_fill_ratio_mavg7: Option<f64>,
653}
654
655/// Account Operations.
656#[derive(Clone, Debug, Deserialize)]
657#[serde(rename_all = "camelCase")]
658pub struct BitmexUser {
659    pub id: Option<i32>,
660    pub owner_id: Option<i32>,
661    pub firstname: Option<String>,
662    pub lastname: Option<String>,
663    pub username: String,
664    pub email: String,
665    pub phone: Option<String>,
666    pub created: Option<Timestamp>,
667    pub last_updated: Option<Timestamp>,
668    pub preferences: BitmexUserPreferences,
669    #[serde(rename = "TFAEnabled")]
670    pub tfa_enabled: Option<String>,
671    #[serde(rename = "affiliateID")]
672    pub affiliate_id: Option<String>,
673    pub pgp_pub_key: Option<String>,
674    pub country: Option<String>,
675    pub geoip_country: Option<String>,
676    pub geoip_region: Option<String>,
677    pub typ: Option<String>,
678}
679
680#[derive(Clone, Debug, Deserialize)]
681#[serde(rename_all = "camelCase")]
682pub struct BitmexMargin {
683    pub account: i64,
684    pub currency: Ustr,
685    pub risk_limit: Option<i64>,
686    pub prev_state: Option<String>,
687    pub state: Option<String>,
688    pub action: Option<String>,
689    pub amount: Option<i64>,
690    pub pending_credit: Option<i64>,
691    pub pending_debit: Option<i64>,
692    pub confirmed_debit: Option<i64>,
693    pub prev_realised_pnl: Option<i64>,
694    pub prev_unrealised_pnl: Option<i64>,
695    pub gross_comm: Option<i64>,
696    pub gross_open_cost: Option<i64>,
697    pub gross_open_premium: Option<i64>,
698    pub gross_exec_cost: Option<i64>,
699    pub gross_mark_value: Option<i64>,
700    pub risk_value: Option<i64>,
701    pub taxable_margin: Option<i64>,
702    pub init_margin: Option<i64>,
703    pub maint_margin: Option<i64>,
704    pub session_margin: Option<i64>,
705    pub target_excess_margin: Option<i64>,
706    pub var_margin: Option<i64>,
707    pub realised_pnl: Option<i64>,
708    pub unrealised_pnl: Option<i64>,
709    pub indicative_tax: Option<i64>,
710    pub unrealised_profit: Option<i64>,
711    pub synthetic_margin: Option<i64>,
712    pub wallet_balance: Option<i64>,
713    pub margin_balance: Option<i64>,
714    pub margin_balance_pcnt: Option<f64>,
715    pub margin_leverage: Option<f64>,
716    pub margin_used_pcnt: Option<f64>,
717    pub excess_margin: Option<i64>,
718    pub excess_margin_pcnt: Option<f64>,
719    pub available_margin: Option<i64>,
720    pub withdrawable_margin: Option<i64>,
721    pub timestamp: Option<Timestamp>,
722    pub gross_last_value: Option<i64>,
723    pub commission: Option<f64>,
724}
725
726/// User communication SNS token.
727#[derive(Debug, Clone, Deserialize)]
728#[serde(rename_all = "camelCase")]
729pub struct BitmexCommunicationToken {
730    pub id: SecretString,
731    #[serde(rename = "userId")]
732    pub user_id: i32,
733    #[serde(rename = "deviceToken")]
734    pub device_token: SecretString,
735    pub channel: String,
736}
737
738impl Zeroize for BitmexCommunicationToken {
739    fn zeroize(&mut self) {
740        self.id.zeroize();
741        self.device_token.zeroize();
742    }
743}
744
745/// User Events for auditing.
746#[derive(Clone, Debug, Deserialize)]
747#[serde(rename_all = "camelCase")]
748pub struct BitmexUserEvent {
749    pub id: Option<f64>,
750    #[serde(rename = "type")]
751    pub r#type: String,
752    pub status: String,
753    #[serde(rename = "userId")]
754    pub user_id: f64,
755    #[serde(rename = "createdById")]
756    pub created_by_id: Option<f64>,
757    pub ip: Option<String>,
758    #[serde(rename = "geoipCountry")]
759    pub geoip_country: Option<String>,
760    #[serde(rename = "geoipRegion")]
761    pub geoip_region: Option<String>,
762    #[serde(rename = "geoipSubRegion")]
763    pub geoip_sub_region: Option<String>,
764    #[serde(rename = "eventMeta")]
765    pub event_meta: Option<BitmexEventMetaEventMeta>,
766    pub created: Timestamp,
767}
768
769#[derive(Clone, Debug, Deserialize, Default)]
770#[allow(dead_code)]
771pub struct BitmexEventMetaEventMeta(serde_json::Value);
772
773#[derive(Clone, Debug, Deserialize, Default)]
774#[serde(rename_all = "camelCase")]
775pub struct BitmexUserPreferences {
776    pub alert_on_liquidations: Option<bool>,
777    pub animations_enabled: Option<bool>,
778    pub announcements_last_seen: Option<Timestamp>,
779    pub chat_channel_id: Option<f64>,
780    pub color_theme: Option<String>,
781    pub currency: Option<Ustr>,
782    pub debug: Option<bool>,
783    pub disable_emails: Option<Vec<String>>,
784    pub disable_push: Option<Vec<String>>,
785    pub hide_confirm_dialogs: Option<Vec<String>>,
786    pub hide_connection_modal: Option<bool>,
787    pub hide_from_leaderboard: Option<bool>,
788    pub hide_name_from_leaderboard: Option<bool>,
789    pub hide_notifications: Option<Vec<String>>,
790    pub locale: Option<String>,
791    pub msgs_seen: Option<Vec<String>>,
792    pub order_book_binning: Option<BitmexOrderBookBinning>,
793    pub order_book_type: Option<String>,
794    pub order_clear_immediate: Option<bool>,
795    pub order_controls_plus_minus: Option<bool>,
796    pub show_locale_numbers: Option<bool>,
797    pub sounds: Option<Vec<String>>,
798    #[serde(rename = "strictIPCheck")]
799    pub strict_ip_check: Option<bool>,
800    pub strict_timeout: Option<bool>,
801    pub ticker_group: Option<String>,
802    pub ticker_pinned: Option<bool>,
803    pub trade_layout: Option<String>,
804}
805
806#[derive(Clone, Debug, Deserialize, Default)]
807#[allow(dead_code)]
808pub struct BitmexOrderBookBinning(serde_json::Value);
809
810/// Represents the response from `GET /api/v1` (root endpoint).
811#[derive(Clone, Debug, Deserialize)]
812#[serde(rename_all = "camelCase")]
813pub struct BitmexApiInfo {
814    /// API name.
815    pub name: String,
816    /// API version.
817    pub version: String,
818    /// Server timestamp in milliseconds.
819    pub timestamp: u64,
820}
821
822#[cfg(test)]
823mod tests {
824    use rstest::rstest;
825    use serde_json::json;
826
827    use super::*;
828
829    #[rstest]
830    #[case(json!(null), None)]
831    #[case(json!(""), None)]
832    #[case(json!("ParticipateDoNotInitiate"), Some(vec![BitmexExecInstruction::ParticipateDoNotInitiate]))]
833    #[case(json!("ReduceOnly"), Some(vec![BitmexExecInstruction::ReduceOnly]))]
834    #[case(json!("LastPrice,Close"), Some(vec![BitmexExecInstruction::LastPrice, BitmexExecInstruction::Close]))]
835    #[case(
836        json!("ParticipateDoNotInitiate,ReduceOnly"),
837        Some(vec![BitmexExecInstruction::ParticipateDoNotInitiate, BitmexExecInstruction::ReduceOnly])
838    )]
839    #[case(
840        json!("MarkPrice,IndexPrice,AllOrNone"),
841        Some(vec![BitmexExecInstruction::MarkPrice, BitmexExecInstruction::IndexPrice, BitmexExecInstruction::AllOrNone])
842    )]
843    #[case(json!("Fixed"), Some(vec![BitmexExecInstruction::Fixed]))]
844    fn test_deserialize_exec_instructions(
845        #[case] input: serde_json::Value,
846        #[case] expected: Option<Vec<BitmexExecInstruction>>,
847    ) {
848        #[derive(Deserialize)]
849        struct TestStruct {
850            #[serde(default, deserialize_with = "deserialize_exec_instructions")]
851            exec_inst: Option<Vec<BitmexExecInstruction>>,
852        }
853
854        let test_json = json!({
855            "exec_inst": input
856        });
857
858        let result: TestStruct = serde_json::from_value(test_json).unwrap();
859        assert_eq!(result.exec_inst, expected);
860    }
861
862    #[rstest]
863    fn test_deserialize_exec_instructions_with_spaces() {
864        #[derive(Deserialize)]
865        struct TestStruct {
866            #[serde(default, deserialize_with = "deserialize_exec_instructions")]
867            exec_inst: Option<Vec<BitmexExecInstruction>>,
868        }
869
870        let test_json = json!({
871            "exec_inst": "LastPrice , Close , ReduceOnly"
872        });
873
874        let result: TestStruct = serde_json::from_value(test_json).unwrap();
875        assert_eq!(
876            result.exec_inst,
877            Some(vec![
878                BitmexExecInstruction::LastPrice,
879                BitmexExecInstruction::Close,
880                BitmexExecInstruction::ReduceOnly,
881            ])
882        );
883    }
884
885    #[rstest]
886    fn test_deserialize_order_with_exec_instructions() {
887        let order_json = json!({
888            "account": 123456,
889            "symbol": "XBTUSD",
890            "orderID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
891            "side": "Buy",
892            "ordType": "Limit",
893            "timeInForce": "GoodTillCancel",
894            "ordStatus": "New",
895            "orderQty": 100,
896            "cumQty": 0,
897            "price": 50000.0,
898            "execInst": "ParticipateDoNotInitiate,ReduceOnly",
899            "transactTime": "2024-01-01T00:00:00.000Z",
900            "timestamp": "2024-01-01T00:00:00.000Z"
901        });
902
903        let order: BitmexOrder = serde_json::from_value(order_json).unwrap();
904        assert_eq!(
905            order.exec_inst,
906            Some(vec![
907                BitmexExecInstruction::ParticipateDoNotInitiate,
908                BitmexExecInstruction::ReduceOnly,
909            ])
910        );
911    }
912
913    #[rstest]
914    fn test_credential_models_redact_and_zeroize() {
915        let mut api_key = BitmexAPIKey {
916            id: SecretString::from("api-key-sentinel"),
917            secret: Some(SecretString::from("api-secret-sentinel")),
918            name: "test".to_string(),
919            nonce: 1,
920            cidr: None,
921            permissions: vec![],
922            enabled: Some(true),
923            user_id: 2,
924            created: None,
925        };
926        let mut access_token = BitmexAccessToken {
927            id: SecretString::from("access-token-sentinel"),
928            ttl: Some(60.0),
929            created: None,
930            user_id: Some(2.0),
931        };
932        let mut communication_token = BitmexCommunicationToken {
933            id: SecretString::from("communication-id-sentinel"),
934            user_id: 2,
935            device_token: SecretString::from("device-token-sentinel"),
936            channel: "push".to_string(),
937        };
938
939        let debug = format!("{api_key:?} {access_token:?} {communication_token:?}");
940        api_key.zeroize();
941        access_token.zeroize();
942        communication_token.zeroize();
943
944        assert!(!debug.contains("api-key-sentinel"));
945        assert!(!debug.contains("api-secret-sentinel"));
946        assert!(!debug.contains("access-token-sentinel"));
947        assert!(!debug.contains("communication-id-sentinel"));
948        assert!(!debug.contains("device-token-sentinel"));
949        assert_eq!(api_key.id.expose_secret(), "");
950        assert_eq!(api_key.secret, None);
951        assert_eq!(access_token.id.expose_secret(), "");
952        assert_eq!(communication_token.id.expose_secret(), "");
953        assert_eq!(communication_token.device_token.expose_secret(), "");
954    }
955}