Skip to main content

rust_okx/ws/model/
public_data.rs

1//! Public reference data channel models (`instruments`, `funding-rate`, `open-interest`, etc.).
2//!
3//! Public channels; no authentication required.
4
5use serde::Deserialize;
6
7use super::ExtraFields;
8use crate::model::NumberString;
9
10fn array_value(values: &[NumberString], i: usize) -> NumberString {
11    values.get(i).cloned().unwrap_or_default()
12}
13
14/// Upcoming instrument-parameter change nested in [`InstrumentUpdate`].
15#[derive(Debug, Clone, Default, Deserialize)]
16#[serde(rename_all = "camelCase")]
17#[non_exhaustive]
18pub struct InstrumentUpcomingParamChange {
19    /// Name of the parameter that will change, e.g., `tickSz`, `lotSz`.
20    #[serde(default)]
21    pub param: String,
22    /// The new value the parameter will take after `eff_time`.
23    #[serde(default)]
24    pub new_value: String,
25    /// Time the new value takes effect (Unix milliseconds).
26    #[serde(default)]
27    pub eff_time: NumberString,
28    /// Unrecognized fields retained for forward compatibility.
29    #[serde(flatten, default)]
30    pub extra: ExtraFields,
31}
32
33/// Public-data `instruments` channel row.
34///
35/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-instruments-channel>
36#[derive(Debug, Clone, Default, Deserialize)]
37#[serde(rename_all = "camelCase")]
38#[non_exhaustive]
39pub struct InstrumentUpdate {
40    /// Instrument type, e.g., `SPOT`, `MARGIN`, `SWAP`, `FUTURES`, `OPTION`.
41    #[serde(default)]
42    pub inst_type: String,
43    /// Series ID (event contracts only).
44    #[serde(default)]
45    pub series_id: String,
46    /// Instrument ID, e.g., `BTC-USDT`.
47    #[serde(default)]
48    pub inst_id: String,
49    /// Internal numeric code for the instrument.
50    #[serde(default)]
51    pub inst_id_code: NumberString,
52    /// Underlying index, e.g., `BTC-USD` (derivatives only).
53    #[serde(default)]
54    pub uly: String,
55    /// Instrument family, e.g., `BTC-USD` (derivatives only).
56    #[serde(default)]
57    pub inst_family: String,
58    /// Fee schedule category.
59    #[serde(default)]
60    pub category: String,
61    /// Base currency, e.g., `BTC` (SPOT/MARGIN only).
62    #[serde(default)]
63    pub base_ccy: String,
64    /// Quote currency, e.g., `USDT` (SPOT/MARGIN only).
65    #[serde(default)]
66    pub quote_ccy: String,
67    /// Settlement currency (derivatives only).
68    #[serde(default)]
69    pub settle_ccy: String,
70    /// Contract value (derivatives only).
71    #[serde(default)]
72    pub ct_val: NumberString,
73    /// Contract multiplier (derivatives only).
74    #[serde(default)]
75    pub ct_mult: NumberString,
76    /// Currency of the contract value (derivatives only).
77    #[serde(default)]
78    pub ct_val_ccy: String,
79    /// Option type: `C` (call) or `P` (put). Empty for non-option instruments.
80    #[serde(default)]
81    pub opt_type: String,
82    /// Strike price (options only).
83    #[serde(default)]
84    pub stk: NumberString,
85    /// Listing time (Unix milliseconds).
86    #[serde(default)]
87    pub list_time: NumberString,
88    /// Time at which the instrument transitions to continuous trading (Unix milliseconds).
89    #[serde(default)]
90    pub cont_td_sw_time: NumberString,
91    /// Pre-market trading start time (Unix milliseconds).
92    #[serde(default)]
93    pub pre_mkt_sw_time: NumberString,
94    /// Expiry/delivery time (Unix milliseconds); empty for perpetuals.
95    #[serde(default)]
96    pub exp_time: NumberString,
97    /// Maximum leverage available.
98    #[serde(default)]
99    pub lever: NumberString,
100    /// Tick size (minimum price increment).
101    #[serde(default)]
102    pub tick_sz: NumberString,
103    /// Lot size (minimum order size increment).
104    #[serde(default)]
105    pub lot_sz: NumberString,
106    /// Minimum order size.
107    #[serde(default)]
108    pub min_sz: NumberString,
109    /// Contract type: `linear` or `inverse` (derivatives only).
110    #[serde(default)]
111    pub ct_type: String,
112    /// Delivery alias: `this_week`, `next_week`, `quarter`, `next_quarter`.
113    #[serde(default)]
114    pub alias: String,
115    /// Instrument state.
116    ///
117    /// Documented values: `live`, `suspend`, `preopen`, `settlement`.
118    #[serde(default)]
119    pub state: String,
120    /// Open interest limit type.
121    ///
122    /// Documented values: `normal` (no limit), `limited`.
123    #[serde(default)]
124    pub open_type: String,
125    /// Applicable trading rules: `normal`, `pre_market`.
126    #[serde(default)]
127    pub rule_type: String,
128    /// Maximum limit order size.
129    #[serde(default)]
130    pub max_lmt_sz: NumberString,
131    /// Maximum limit order amount in USD.
132    #[serde(default)]
133    pub max_lmt_amt: NumberString,
134    /// Maximum market order size.
135    #[serde(default)]
136    pub max_mkt_sz: NumberString,
137    /// Maximum market order amount in USD.
138    #[serde(default)]
139    pub max_mkt_amt: NumberString,
140    /// Maximum TWAP order size.
141    #[serde(default)]
142    pub max_twap_sz: NumberString,
143    /// Maximum iceberg order size.
144    #[serde(default)]
145    pub max_iceberg_sz: NumberString,
146    /// Maximum trigger order size.
147    #[serde(default)]
148    pub max_trigger_sz: NumberString,
149    /// Maximum stop order size.
150    #[serde(default)]
151    pub max_stop_sz: NumberString,
152    /// Call-auction end time (Unix milliseconds); empty when not in auction.
153    #[serde(default)]
154    pub auction_end_time: NumberString,
155    /// Whether futures settlement is enabled for this instrument.
156    #[serde(default)]
157    pub future_settlement: bool,
158    /// List of supported quote currencies for this instrument.
159    #[serde(default)]
160    pub trade_quote_ccy_list: Vec<String>,
161    /// Instrument sub-category.
162    #[serde(default)]
163    pub inst_category: String,
164    /// Maximum total position size (USD amount).
165    #[serde(default)]
166    pub pos_lmt_amt: NumberString,
167    /// Maximum position size as a percentage of total open interest.
168    #[serde(default)]
169    pub pos_lmt_pct: NumberString,
170    /// Remaining long-position quota for this instrument.
171    #[serde(default)]
172    pub long_pos_remaining_quota: NumberString,
173    /// Remaining short-position quota for this instrument.
174    #[serde(default)]
175    pub short_pos_remaining_quota: NumberString,
176    /// Maximum platform-wide open-interest limit.
177    #[serde(default)]
178    pub max_plat_oi_lmt: NumberString,
179    /// Instrument group ID.
180    #[serde(default)]
181    pub group_id: String,
182    /// Upcoming parameter changes scheduled for this instrument.
183    #[serde(default)]
184    pub upc_chg: Vec<InstrumentUpcomingParamChange>,
185    /// Unrecognized fields retained for forward compatibility.
186    #[serde(flatten, default)]
187    pub extra: ExtraFields,
188}
189
190/// `event-contract-markets` channel row.
191///
192/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-event-contract-markets-channel>
193#[derive(Debug, Clone, Default, Deserialize)]
194#[serde(rename_all = "camelCase")]
195#[non_exhaustive]
196pub struct EventContractMarketUpdate {
197    /// Series ID of the event.
198    #[serde(default)]
199    pub series_id: String,
200    /// Event ID.
201    #[serde(default)]
202    pub event_id: String,
203    /// Market ID within the event.
204    #[serde(default)]
205    pub market_id: String,
206    /// Instrument ID, e.g., `EVENT-BTC-USDT`.
207    #[serde(default)]
208    pub inst_id: String,
209    /// Human-readable title of the market.
210    #[serde(default)]
211    pub title: String,
212    /// Market state, e.g., `live`, `suspend`, `settlement`.
213    #[serde(default)]
214    pub state: String,
215    /// Event category.
216    #[serde(default)]
217    pub category: String,
218    /// Settlement rule type.
219    #[serde(default)]
220    pub rule_type: String,
221    /// Settlement outcome of the market (after settlement).
222    #[serde(default)]
223    pub outcome: String,
224    /// Floor (minimum) strike price.
225    #[serde(default)]
226    pub floor_strike: NumberString,
227    /// Settlement value (after settlement).
228    #[serde(default)]
229    pub settle_value: NumberString,
230    /// Whether the outcome is disputed.
231    #[serde(default)]
232    pub disputed: bool,
233    /// Tick size (minimum price increment).
234    #[serde(default)]
235    pub tick_sz: NumberString,
236    /// Minimum order size.
237    #[serde(default)]
238    pub min_sz: NumberString,
239    /// Listing time (Unix milliseconds).
240    #[serde(default)]
241    pub list_time: NumberString,
242    /// Expiry time (Unix milliseconds).
243    #[serde(default)]
244    pub exp_time: NumberString,
245    /// Settlement fixing time (Unix milliseconds).
246    #[serde(default)]
247    pub fix_time: NumberString,
248    /// Last update time (Unix milliseconds).
249    #[serde(default)]
250    pub u_time: NumberString,
251    /// Unrecognized fields retained for forward compatibility.
252    #[serde(flatten, default)]
253    pub extra: ExtraFields,
254}
255
256/// `open-interest` channel row.
257///
258/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-open-interest-channel>
259#[derive(Debug, Clone, Default, Deserialize)]
260#[serde(rename_all = "camelCase")]
261#[non_exhaustive]
262pub struct OpenInterestUpdate {
263    /// Instrument type, e.g., `FUTURES`, `SWAP`, `OPTION`.
264    #[serde(default)]
265    pub inst_type: String,
266    /// Instrument ID, e.g., `BTC-USD-SWAP`.
267    #[serde(default)]
268    pub inst_id: String,
269    /// Open interest in contracts.
270    #[serde(default)]
271    pub oi: NumberString,
272    /// Open interest in coin/currency units.
273    #[serde(default)]
274    pub oi_ccy: NumberString,
275    /// Open interest in USD.
276    #[serde(default)]
277    pub oi_usd: NumberString,
278    /// Push time (Unix milliseconds).
279    #[serde(default)]
280    pub ts: NumberString,
281    /// Unrecognized fields retained for forward compatibility.
282    #[serde(flatten, default)]
283    pub extra: ExtraFields,
284}
285
286/// `funding-rate` channel row.
287///
288/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-funding-rate-channel>
289#[derive(Debug, Clone, Default, Deserialize)]
290#[serde(rename_all = "camelCase")]
291#[non_exhaustive]
292pub struct FundingRateUpdate {
293    /// Instrument type, always `SWAP`.
294    #[serde(default)]
295    pub inst_type: String,
296    /// Instrument ID, e.g., `BTC-USD-SWAP`.
297    #[serde(default)]
298    pub inst_id: String,
299    /// Current funding rate.
300    #[serde(default)]
301    pub funding_rate: NumberString,
302    /// Next estimated funding rate.
303    #[serde(default)]
304    pub next_funding_rate: NumberString,
305    /// Current funding settlement time (Unix milliseconds).
306    #[serde(default)]
307    pub funding_time: NumberString,
308    /// Next funding settlement time (Unix milliseconds).
309    #[serde(default)]
310    pub next_funding_time: NumberString,
311    /// Minimum funding rate cap.
312    #[serde(default)]
313    pub min_funding_rate: NumberString,
314    /// Maximum funding rate cap.
315    #[serde(default)]
316    pub max_funding_rate: NumberString,
317    /// Funding rate calculation method, e.g., `next_period`, `current_period`.
318    #[serde(default)]
319    pub method: String,
320    /// Funding formula type: `noRate` or `withRate`.
321    #[serde(default)]
322    pub formula_type: String,
323    /// Premium index (difference between mark price and index price).
324    #[serde(default)]
325    pub premium: NumberString,
326    /// Interest rate component of the funding rate.
327    #[serde(default)]
328    pub interest_rate: NumberString,
329    /// Impact bid/ask size used in the mark-price calculation.
330    #[serde(default)]
331    pub impact_value: NumberString,
332    /// Settlement state: `processing` during settlement, empty otherwise.
333    #[serde(default)]
334    pub sett_state: String,
335    /// Actual settlement funding rate (available after settlement completes).
336    #[serde(default)]
337    pub sett_funding_rate: NumberString,
338    /// Push time (Unix milliseconds).
339    #[serde(default)]
340    pub ts: NumberString,
341    /// Unrecognized fields retained for forward compatibility.
342    #[serde(flatten, default)]
343    pub extra: ExtraFields,
344}
345
346/// `price-limit` channel row.
347///
348/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-price-limit-channel>
349#[derive(Debug, Clone, Default, Deserialize)]
350#[serde(rename_all = "camelCase")]
351#[non_exhaustive]
352pub struct PriceLimitUpdate {
353    /// Instrument type, e.g., `FUTURES`, `SWAP`, `OPTION`.
354    #[serde(default)]
355    pub inst_type: String,
356    /// Instrument ID, e.g., `BTC-USD-SWAP`.
357    #[serde(default)]
358    pub inst_id: String,
359    /// Highest buy price limit.
360    #[serde(default)]
361    pub buy_lmt: NumberString,
362    /// Lowest sell price limit.
363    #[serde(default)]
364    pub sell_lmt: NumberString,
365    /// Push time (Unix milliseconds).
366    #[serde(default)]
367    pub ts: NumberString,
368    /// Whether price-limit restrictions are currently in effect.
369    #[serde(default)]
370    pub enabled: bool,
371    /// Unrecognized fields retained for forward compatibility.
372    #[serde(flatten, default)]
373    pub extra: ExtraFields,
374}
375
376/// `opt-summary` channel row.
377///
378/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-option-summary-channel>
379#[derive(Debug, Clone, Default, Deserialize)]
380#[serde(rename_all = "camelCase")]
381#[non_exhaustive]
382pub struct OptionSummaryUpdate {
383    /// Instrument type, always `OPTION`.
384    #[serde(default)]
385    pub inst_type: String,
386    /// Instrument ID, e.g., `BTC-USD-240329-40000-C`.
387    #[serde(default)]
388    pub inst_id: String,
389    /// Underlying index, e.g., `BTC-USD`.
390    #[serde(default)]
391    pub uly: String,
392    /// Black-Scholes delta.
393    #[serde(default, rename = "deltaBS")]
394    pub delta_bs: NumberString,
395    /// Portfolio-adjusted delta.
396    pub delta: NumberString,
397    /// Black-Scholes gamma.
398    #[serde(default, rename = "gammaBS")]
399    pub gamma_bs: NumberString,
400    /// Portfolio-adjusted gamma.
401    #[serde(default, rename = "gamma")]
402    pub gamma_pa: NumberString,
403    /// Black-Scholes vega.
404    #[serde(default, rename = "vegaBS")]
405    pub vega_bs: NumberString,
406    /// Portfolio-adjusted vega.
407    #[serde(default, rename = "vega")]
408    pub vega_pa: NumberString,
409    /// Black-Scholes theta.
410    #[serde(default, rename = "thetaBS")]
411    pub theta_bs: NumberString,
412    /// Portfolio-adjusted theta.
413    #[serde(default, rename = "theta")]
414    pub theta: NumberString,
415    /// Leverage (delta / price).
416    #[serde(default)]
417    pub lever: NumberString,
418    /// Mark implied volatility.
419    #[serde(default)]
420    pub mark_vol: NumberString,
421    /// Best bid implied volatility.
422    #[serde(default)]
423    pub bid_vol: NumberString,
424    /// Best ask implied volatility.
425    #[serde(default)]
426    pub ask_vol: NumberString,
427    /// Realized volatility (30-day).
428    #[serde(default)]
429    pub real_vol: NumberString,
430    /// Volatility for a given delta level.
431    #[serde(default)]
432    pub vol_lv: NumberString,
433    /// Forward price used in the Black-Scholes calculation.
434    #[serde(default)]
435    pub fwd_px: NumberString,
436    /// Push time (Unix milliseconds).
437    #[serde(default)]
438    pub ts: NumberString,
439    /// Unrecognized fields retained for forward compatibility.
440    #[serde(flatten, default)]
441    pub extra: ExtraFields,
442}
443
444/// `estimated-price` channel row.
445///
446/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-estimated-delivery-exercise-settlement-price-channel>
447#[derive(Debug, Clone, Default, Deserialize)]
448#[serde(rename_all = "camelCase")]
449#[non_exhaustive]
450pub struct EstimatedPriceUpdate {
451    /// Instrument type, e.g., `FUTURES`, `OPTION`.
452    #[serde(default)]
453    pub inst_type: String,
454    /// Instrument ID, e.g., `BTC-USD-240329-40000-C`.
455    #[serde(default)]
456    pub inst_id: String,
457    /// Settlement type: `delivery` or `exercise`.
458    #[serde(default)]
459    pub settle_type: String,
460    /// Estimated delivery/exercise/settlement price.
461    ///
462    /// OKX uses the JSON key `settlePx`; the alias handles both spellings.
463    #[serde(default)]
464    #[serde(alias = "settPx")]
465    pub settle_px: NumberString,
466    /// Push time (Unix milliseconds).
467    #[serde(default)]
468    pub ts: NumberString,
469    /// Unrecognized fields retained for forward compatibility.
470    #[serde(flatten, default)]
471    pub extra: ExtraFields,
472}
473
474/// `mark-price` channel row.
475///
476/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-mark-price-channel>
477#[derive(Debug, Clone, Default, Deserialize)]
478#[serde(rename_all = "camelCase")]
479#[non_exhaustive]
480pub struct MarkPriceUpdate {
481    /// Instrument type, e.g., `MARGIN`, `SWAP`, `FUTURES`, `OPTION`.
482    #[serde(default)]
483    pub inst_type: String,
484    /// Instrument ID, e.g., `BTC-USD-SWAP`.
485    #[serde(default)]
486    pub inst_id: String,
487    /// Mark price.
488    #[serde(default)]
489    pub mark_px: NumberString,
490    /// Push time (Unix milliseconds).
491    #[serde(default)]
492    pub ts: NumberString,
493    /// Unrecognized fields retained for forward compatibility.
494    #[serde(flatten, default)]
495    pub extra: ExtraFields,
496}
497
498/// `index-tickers` channel row.
499///
500/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-index-tickers-channel>
501#[derive(Debug, Clone, Default, Deserialize)]
502#[serde(rename_all = "camelCase")]
503#[non_exhaustive]
504pub struct IndexTickerUpdate {
505    /// Index instrument ID, e.g., `BTC-USD`.
506    #[serde(default)]
507    pub inst_id: String,
508    /// Current index price.
509    #[serde(default)]
510    pub idx_px: NumberString,
511    /// Highest index price over the last 24 hours.
512    #[serde(default)]
513    pub high24h: NumberString,
514    /// Open index price over the last 24 hours.
515    #[serde(default)]
516    pub open24h: NumberString,
517    /// Lowest index price over the last 24 hours.
518    #[serde(default)]
519    pub low24h: NumberString,
520    /// Open price at start of day (00:00 UTC).
521    #[serde(default)]
522    pub sod_utc0: NumberString,
523    /// Open price at start of day (08:00 UTC+8).
524    #[serde(default)]
525    pub sod_utc8: NumberString,
526    /// Push time (Unix milliseconds).
527    #[serde(default)]
528    pub ts: NumberString,
529    /// Unrecognized fields retained for forward compatibility.
530    #[serde(flatten, default)]
531    pub extra: ExtraFields,
532}
533
534/// Detail row nested in [`LiquidationOrderUpdate`].
535#[derive(Debug, Clone, Default, Deserialize)]
536#[serde(rename_all = "camelCase")]
537#[non_exhaustive]
538pub struct LiquidationOrderDetail {
539    /// Side of the liquidated position: `buy` or `sell`.
540    #[serde(default)]
541    pub side: String,
542    /// Position side of the liquidation: `long`, `short`, or `net`.
543    #[serde(default)]
544    pub pos_side: String,
545    /// Bankruptcy price of the position.
546    #[serde(default)]
547    pub bk_px: NumberString,
548    /// Liquidated size (number of contracts).
549    #[serde(default)]
550    pub sz: NumberString,
551    /// Bankruptcy loss incurred.
552    #[serde(default)]
553    pub bk_loss: NumberString,
554    /// Margin currency of the position.
555    #[serde(default)]
556    pub ccy: String,
557    /// Liquidation timestamp (Unix milliseconds).
558    #[serde(default)]
559    pub ts: NumberString,
560    /// Unrecognized fields retained for forward compatibility.
561    #[serde(flatten, default)]
562    pub extra: ExtraFields,
563}
564
565/// `liquidation-orders` channel row.
566///
567/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-liquidation-orders-channel>
568#[derive(Debug, Clone, Default, Deserialize)]
569#[serde(rename_all = "camelCase")]
570#[non_exhaustive]
571pub struct LiquidationOrderUpdate {
572    /// Instrument ID, e.g., `BTC-USD-SWAP`.
573    #[serde(default)]
574    pub inst_id: String,
575    /// Instrument type, e.g., `SWAP`, `FUTURES`, `OPTION`, `MARGIN`.
576    #[serde(default)]
577    pub inst_type: String,
578    /// Underlying index, e.g., `BTC-USD` (derivatives only).
579    #[serde(default)]
580    pub uly: String,
581    /// Instrument family, e.g., `BTC-USD` (derivatives only).
582    #[serde(default)]
583    pub inst_family: String,
584    /// Per-position liquidation details.
585    #[serde(default)]
586    pub details: Vec<LiquidationOrderDetail>,
587    /// Unrecognized fields retained for forward compatibility.
588    #[serde(flatten, default)]
589    pub extra: ExtraFields,
590}
591
592/// `adl-warning` channel row.
593///
594/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-adl-warning-channel>
595#[derive(Debug, Clone, Default, Deserialize)]
596#[serde(rename_all = "camelCase")]
597#[non_exhaustive]
598pub struct AdlWarningUpdate {
599    /// Instrument type, e.g., `SWAP`, `FUTURES`, `OPTION`.
600    #[serde(default)]
601    pub inst_type: String,
602    /// Instrument family, e.g., `BTC-USD`.
603    #[serde(default)]
604    pub inst_family: String,
605    /// Settlement currency, e.g., `BTC` (inverse) or `USDT` (linear).
606    #[serde(default)]
607    pub ccy: String,
608    /// Account balance at the time when the ADL risk was at its maximum.
609    #[serde(default)]
610    pub max_bal: NumberString,
611    /// Recommended balance to reduce ADL risk.
612    #[serde(default)]
613    pub adl_rec_bal: NumberString,
614    /// Current account balance.
615    #[serde(default)]
616    pub bal: NumberString,
617    /// Time at which `max_bal` was recorded (Unix milliseconds).
618    #[serde(default)]
619    pub max_bal_ts: NumberString,
620    /// ADL type.
621    ///
622    /// Documented values: `position` (position-level ADL), `account` (account-level ADL).
623    #[serde(default)]
624    pub adl_type: String,
625    /// ADL risk state.
626    ///
627    /// Documented values: `1` (ADL will be triggered soon), `2` (ADL is triggered).
628    #[serde(default)]
629    pub state: String,
630    /// Balance at which ADL would be triggered.
631    #[serde(default)]
632    pub adl_bal: NumberString,
633    /// Push time (Unix milliseconds).
634    #[serde(default)]
635    pub ts: NumberString,
636    /// Unrecognized fields retained for forward compatibility.
637    #[serde(flatten, default)]
638    pub extra: ExtraFields,
639}
640
641/// `economic-calendar` channel row.
642///
643/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-economic-calendar-channel>
644#[derive(Debug, Clone, Default, Deserialize)]
645#[serde(rename_all = "camelCase")]
646#[non_exhaustive]
647pub struct EconomicCalendarUpdate {
648    /// Unique calendar event ID.
649    #[serde(default)]
650    pub calendar_id: String,
651    /// Estimated release time (Unix milliseconds).
652    #[serde(default)]
653    pub date: String,
654    /// Reporting region or country, e.g., `United States`.
655    #[serde(default)]
656    pub region: String,
657    /// Event category, e.g., `Manufacturing PMI`.
658    #[serde(default)]
659    pub category: String,
660    /// Event name, e.g., `S&P Global Manufacturing PMI Final`.
661    #[serde(default)]
662    pub event: String,
663    /// Reference date for the data point (may differ from the release date).
664    #[serde(default)]
665    pub ref_date: String,
666    /// Actual reported value (empty until the event is released).
667    #[serde(default)]
668    pub actual: String,
669    /// Previously reported value.
670    #[serde(default)]
671    pub previous: String,
672    /// Consensus forecast value.
673    #[serde(default)]
674    pub forecast: String,
675    /// Market impact importance: `1` low, `2` medium, `3` high.
676    #[serde(default)]
677    pub importance: String,
678    /// Unit of the reported value, e.g., `%`, `K`.
679    #[serde(default)]
680    pub unit: String,
681    /// Crypto-market relevant currency associated with the event, e.g., `BTC`.
682    #[serde(default)]
683    pub ccy: String,
684    /// Preliminary/initial value before revision (empty if not revised).
685    #[serde(default)]
686    pub prev_initial: String,
687    /// Push time / last update time (Unix milliseconds).
688    #[serde(default)]
689    pub ts: NumberString,
690    /// Unrecognized fields retained for forward compatibility.
691    #[serde(flatten, default)]
692    pub extra: ExtraFields,
693}
694
695/// `index-candle*` channel row.
696///
697/// OKX pushes a 6-element array `[ts, o, h, l, c, confirm]` — no volume fields.
698///
699/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-index-candlesticks-channel>
700#[derive(Debug, Clone, Default, Deserialize)]
701#[serde(from = "Vec<NumberString>")]
702#[non_exhaustive]
703pub struct IndexCandleUpdate {
704    /// Opening time of the candlestick (Unix milliseconds).
705    pub ts: NumberString,
706    /// Open price.
707    pub o: NumberString,
708    /// Highest price.
709    pub h: NumberString,
710    /// Lowest price.
711    pub l: NumberString,
712    /// Close price.
713    pub c: NumberString,
714    /// Candlestick state: `0` incomplete (still forming), `1` completed.
715    pub confirm: NumberString,
716}
717
718impl From<Vec<NumberString>> for IndexCandleUpdate {
719    fn from(values: Vec<NumberString>) -> Self {
720        Self {
721            ts: array_value(&values, 0),
722            o: array_value(&values, 1),
723            h: array_value(&values, 2),
724            l: array_value(&values, 3),
725            c: array_value(&values, 4),
726            confirm: array_value(&values, 5),
727        }
728    }
729}
730
731/// `mark-price-candle*` channel row.
732///
733/// OKX pushes a 6-element array `[ts, o, h, l, c, confirm]` — no volume fields.
734///
735/// OKX docs: <https://www.okx.com/docs-v5/en/#order-book-trading-public-data-ws-mark-price-candlesticks-channel>
736pub type MarkPriceCandleUpdate = IndexCandleUpdate;
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741
742    #[test]
743    fn option_summary_greek_fields_deserialize_from_okx_json_keys() {
744        let row: OptionSummaryUpdate = serde_json::from_str(
745            r#"{
746                "instType":"OPTION","instId":"BTC-USD-241013-70000-P","uly":"BTC-USD",
747                "delta":"-1.1180902625","deltaBS":"-0.9999672034",
748                "gamma":"2.2361957091","gammaBS":"0.0000000002",
749                "vega":"0.0000000001","vegaBS":"0.0000114332",
750                "theta":"0.0000032334","thetaBS":"28.2649858387",
751                "lever":"8.465747567","markVol":"0.3675503331","bidVol":"0",
752                "askVol":"1.1669998535","realVol":"","volLv":"0.2044711229",
753                "fwdPx":"62604.6993093463","ts":"1728703155650"
754            }"#,
755        )
756        .unwrap();
757        assert_eq!(row.delta.as_str(), "-1.1180902625");
758        assert_eq!(row.delta_bs.as_str(), "-0.9999672034");
759        assert_eq!(row.gamma_pa.as_str(), "2.2361957091");
760        assert_eq!(row.gamma_bs.as_str(), "0.0000000002");
761        assert_eq!(row.vega_pa.as_str(), "0.0000000001");
762        assert_eq!(row.vega_bs.as_str(), "0.0000114332");
763        assert_eq!(row.theta.as_str(), "0.0000032334");
764        assert_eq!(row.theta_bs.as_str(), "28.2649858387");
765    }
766
767    #[test]
768    fn index_candle_parses_6_element_array() {
769        let row: IndexCandleUpdate = serde_json::from_str(
770            r#"["1597026383085","3811.31","3811.31","3811.31","3811.31","0"]"#,
771        )
772        .unwrap();
773        assert_eq!(row.ts.as_str(), "1597026383085");
774        assert_eq!(row.o.as_str(), "3811.31");
775        assert_eq!(row.c.as_str(), "3811.31");
776        assert_eq!(row.confirm.as_str(), "0");
777    }
778
779    #[test]
780    fn mark_price_candle_parses_6_element_array() {
781        let row: MarkPriceCandleUpdate =
782            serde_json::from_str(r#"["1597026383085","3.721","3.743","3.677","3.708","1"]"#)
783                .unwrap();
784        assert_eq!(row.ts.as_str(), "1597026383085");
785        assert_eq!(row.o.as_str(), "3.721");
786        assert_eq!(row.c.as_str(), "3.708");
787        assert_eq!(row.confirm.as_str(), "1");
788    }
789
790    #[test]
791    fn parses_current_instrument_and_estimated_price_fields() {
792        let instrument: InstrumentUpdate = serde_json::from_str(
793            r#"{"instType":"FUTURES","instId":"BTC-USD-260626","groupId":"g1","auctionEndTime":"1","contTdSwTime":"2","maxLmtAmt":"3","upcChg":[{"param":"tickSz","newValue":"0.1","effTime":"4"}]}"#,
794        )
795        .unwrap();
796        assert_eq!(instrument.group_id, "g1");
797        assert_eq!(instrument.upc_chg[0].new_value, "0.1");
798
799        let estimated: EstimatedPriceUpdate = serde_json::from_str(
800            r#"{"instType":"OPTION","instId":"BTC-USD-260626-100000-C","settlePx":"100","settleType":"exercise","ts":"5"}"#,
801        )
802        .unwrap();
803        assert_eq!(estimated.settle_px.as_str(), "100");
804    }
805}