Skip to main content

longbridge/market/
types.rs

1#![allow(missing_docs)]
2
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use time::OffsetDateTime;
6
7use crate::{types::Market, utils::counter::deserialize_counter_id_as_symbol};
8
9// ── market_status ─────────────────────────────────────────────────
10
11/// Response for [`crate::MarketContext::market_status`]
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct MarketStatusResponse {
14    /// Per-market trading status items
15    pub market_time: Vec<MarketTimeItem>,
16}
17
18/// Trading status for one market
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct MarketTimeItem {
21    /// Market code
22    pub market: Market,
23    /// Raw trade status code (101=PreOpen, 102/103/105=Trading, 104=LunchBreak,
24    /// 106=PostTrading, 108=Closed, 201=PreMarket, 204=PostMarket)
25    pub trade_status: i32,
26    /// Current market time (unix timestamp string)
27    pub timestamp: String,
28    /// Delayed-quote trade status code
29    pub delay_trade_status: i32,
30    /// Delayed-quote market time (unix timestamp string)
31    pub delay_timestamp: String,
32    /// Sub-status code
33    pub sub_status: i32,
34    /// Delayed-quote sub-status code
35    pub delay_sub_status: i32,
36}
37
38// ── broker_holding ────────────────────────────────────────────────
39
40/// Response for [`crate::MarketContext::broker_holding`]
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct BrokerHoldingTop {
43    /// Top brokers by net buying
44    pub buy: Vec<BrokerHoldingEntry>,
45    /// Top brokers by net selling
46    pub sell: Vec<BrokerHoldingEntry>,
47    /// Last updated (may be empty)
48    #[serde(default)]
49    pub updated_at: String,
50}
51
52/// One broker entry in a top-holding list
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct BrokerHoldingEntry {
55    /// Broker name
56    pub name: String,
57    /// Participant number / broker code
58    pub parti_number: String,
59    /// Net change in shares held
60    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
61    pub chg: Option<Decimal>,
62    /// Whether this is a "strengthening" broker
63    pub strong: bool,
64}
65
66// ── broker_holding_detail ─────────────────────────────────────────
67
68/// Response for [`crate::MarketContext::broker_holding_detail`]
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct BrokerHoldingDetail {
71    /// Full list of broker holdings
72    pub list: Vec<BrokerHoldingDetailItem>,
73    /// Last updated (may be empty)
74    #[serde(default)]
75    pub updated_at: String,
76}
77
78/// One broker's full holding detail
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct BrokerHoldingDetailItem {
81    /// Broker name
82    pub name: String,
83    /// Participant number / broker code
84    pub parti_number: String,
85    /// Holding ratio changes over various periods
86    pub ratio: BrokerHoldingChanges,
87    /// Share count changes over various periods
88    pub shares: BrokerHoldingChanges,
89    /// Whether this is a "strengthening" broker
90    pub strong: bool,
91}
92
93/// Changes in broker holding over 1 / 5 / 20 / 60 day periods
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct BrokerHoldingChanges {
96    /// Current value
97    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
98    pub value: Option<Decimal>,
99    /// 1-day change
100    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
101    pub chg_1: Option<Decimal>,
102    /// 5-day change
103    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
104    pub chg_5: Option<Decimal>,
105    /// 20-day change
106    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
107    pub chg_20: Option<Decimal>,
108    /// 60-day change
109    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
110    pub chg_60: Option<Decimal>,
111}
112
113// ── broker_holding_daily ──────────────────────────────────────────
114
115/// Response for [`crate::MarketContext::broker_holding_daily`]
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct BrokerHoldingDailyHistory {
118    /// Daily broker holding records
119    pub list: Vec<BrokerHoldingDailyItem>,
120}
121
122/// One day's broker holding record
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct BrokerHoldingDailyItem {
125    /// Date in `"2026.05.05"` format
126    pub date: String,
127    /// Total shares held
128    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
129    pub holding: Option<Decimal>,
130    /// Holding ratio as a decimal
131    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
132    pub ratio: Option<Decimal>,
133    /// Change vs previous day
134    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
135    pub chg: Option<Decimal>,
136}
137
138// ── ah_premium ────────────────────────────────────────────────────
139
140/// Response for [`crate::MarketContext::ah_premium`]
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct AhPremiumKlines {
143    /// K-line data points
144    pub klines: Vec<AhPremiumKline>,
145}
146
147/// Response for [`crate::MarketContext::ah_premium_intraday`]
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct AhPremiumIntraday {
150    /// Intraday data points (field name is `klines` in the API)
151    pub klines: Vec<AhPremiumKline>,
152}
153
154/// One A/H premium data point
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct AhPremiumKline {
157    /// A-share price
158    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
159    pub aprice: Decimal,
160    /// A-share previous close
161    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
162    pub apreclose: Decimal,
163    /// H-share price
164    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
165    pub hprice: Decimal,
166    /// H-share previous close
167    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
168    pub hpreclose: Decimal,
169    /// CNY/HKD exchange rate
170    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
171    pub currency_rate: Decimal,
172    /// A/H premium rate (negative = H-share at premium)
173    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
174    pub ahpremium_rate: Decimal,
175    /// Price spread
176    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
177    pub price_spread: Decimal,
178    /// Data point timestamp
179    #[serde(deserialize_with = "crate::serde_utils::deserialize_timestamp")]
180    pub timestamp: OffsetDateTime,
181}
182
183// ── trade_stats ───────────────────────────────────────────────────
184
185/// Response for [`crate::MarketContext::trade_stats`]
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct TradeStatsResponse {
188    /// Summary statistics
189    pub statistics: TradeStatistics,
190    /// Per-price-level breakdown
191    pub trades: Vec<TradePriceLevel>,
192}
193
194/// Summary trade statistics
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct TradeStatistics {
197    /// Volume-weighted average price
198    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
199    pub avgprice: Decimal,
200    /// Total buy volume (shares)
201    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
202    pub buy: Decimal,
203    /// Total neutral / unknown-direction volume
204    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
205    pub neutral: Decimal,
206    /// Previous close price
207    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
208    pub preclose: Decimal,
209    /// Total sell volume (shares)
210    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
211    pub sell: Decimal,
212    /// Data timestamp (unix timestamp string)
213    pub timestamp: String,
214    /// Total trading volume (shares)
215    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
216    pub total_amount: Decimal,
217    /// Unix timestamps for the last 5 trading days
218    pub trade_date: Vec<String>,
219    /// Total number of trades
220    pub trades_count: String,
221}
222
223/// Trade volume at one price level
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct TradePriceLevel {
226    /// Buy volume at this price
227    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
228    pub buy_amount: Decimal,
229    /// Neutral (unknown direction) volume at this price
230    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
231    pub neutral_amount: Decimal,
232    /// Price level
233    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
234    pub price: Decimal,
235    /// Sell volume at this price
236    #[serde(with = "crate::serde_utils::decimal_empty_is_0")]
237    pub sell_amount: Decimal,
238}
239
240// ── anomaly ───────────────────────────────────────────────────────
241
242/// Response for [`crate::MarketContext::anomaly`]
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct AnomalyResponse {
245    /// Whether anomaly alerts are globally disabled
246    pub all_off: bool,
247    /// List of market anomaly events
248    pub changes: Vec<AnomalyItem>,
249}
250
251/// One market anomaly event (e.g. large block trade, margin buying surge)
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct AnomalyItem {
254    /// Security symbol
255    #[serde(
256        rename = "counter_id",
257        deserialize_with = "deserialize_counter_id_as_symbol"
258    )]
259    pub symbol: String,
260    /// Security name
261    pub name: String,
262    /// Anomaly type name, e.g. `"大宗交易"`, `"融资买入"`
263    pub alert_name: String,
264    /// Time of the anomaly (unix timestamp in milliseconds)
265    pub alert_time: i64,
266    /// Change values — items are accessed as strings by the client
267    pub change_values: Vec<String>,
268    /// Sentiment direction: 1 = positive/up, 2 = negative/down
269    pub emotion: i32,
270}
271
272// ── constituent ───────────────────────────────────────────────────
273
274/// Response for [`crate::MarketContext::constituent`]
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct IndexConstituents {
277    /// Number of constituent stocks that fell today
278    pub fall_num: i32,
279    /// Number of constituent stocks unchanged today
280    pub flat_num: i32,
281    /// Number of constituent stocks that rose today
282    pub rise_num: i32,
283    /// Constituent stock details
284    pub stocks: Vec<ConstituentStock>,
285}
286
287/// One constituent stock of an index
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct ConstituentStock {
290    /// Security symbol
291    #[serde(
292        rename = "counter_id",
293        deserialize_with = "deserialize_counter_id_as_symbol"
294    )]
295    pub symbol: String,
296    /// Security name
297    pub name: String,
298    /// Latest price
299    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
300    pub last_done: Option<Decimal>,
301    /// Previous close
302    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
303    pub prev_close: Option<Decimal>,
304    /// Net capital inflow today
305    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
306    pub inflow: Option<Decimal>,
307    /// Turnover amount
308    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
309    pub balance: Option<Decimal>,
310    /// Trading volume (shares)
311    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
312    pub amount: Option<Decimal>,
313    /// Total shares outstanding
314    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
315    pub total_shares: Option<Decimal>,
316    /// Tags, e.g. `["领涨龙头"]`
317    pub tags: Vec<String>,
318    /// Brief description
319    pub intro: String,
320    /// Market, e.g. `"HK"`
321    pub market: String,
322    /// Circulating shares
323    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
324    pub circulating_shares: Option<Decimal>,
325    /// Whether this is a delayed quote
326    pub delay: bool,
327    /// Day change percentage
328    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
329    pub chg: Option<Decimal>,
330    /// Raw trade status code
331    pub trade_status: i32,
332}
333
334// ── enums ─────────────────────────────────────────────────────────
335
336/// Broker holding lookback period
337#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
338pub enum BrokerHoldingPeriod {
339    /// 1-day change
340    #[default]
341    #[serde(rename = "rct_1")]
342    Rct1,
343    /// 5-day change
344    #[serde(rename = "rct_5")]
345    Rct5,
346    /// 20-day change
347    #[serde(rename = "rct_20")]
348    Rct20,
349    /// 60-day change
350    #[serde(rename = "rct_60")]
351    Rct60,
352}
353
354/// A/H premium K-line period
355#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
356pub enum AhPremiumPeriod {
357    /// 1-minute
358    Min1,
359    /// 5-minute
360    Min5,
361    /// 15-minute
362    Min15,
363    /// 30-minute
364    Min30,
365    /// 60-minute
366    Min60,
367    /// Daily
368    #[default]
369    Day,
370    /// Weekly
371    Week,
372    /// Monthly
373    Month,
374    /// Yearly
375    Year,
376}
377
378impl AhPremiumPeriod {
379    /// Convert to the API's `line_type` parameter value
380    pub(crate) fn to_line_type(self) -> &'static str {
381        match self {
382            AhPremiumPeriod::Min1 => "1",
383            AhPremiumPeriod::Min5 => "5",
384            AhPremiumPeriod::Min15 => "15",
385            AhPremiumPeriod::Min30 => "30",
386            AhPremiumPeriod::Min60 => "60",
387            AhPremiumPeriod::Day => "1000",
388            AhPremiumPeriod::Week => "2000",
389            AhPremiumPeriod::Month => "3000",
390            AhPremiumPeriod::Year => "4000",
391        }
392    }
393}