tqsdk_rs/
types.rs

1//! 数据结构定义
2//!
3//! 定义所有 TQSDK 使用的数据结构,包括:
4//! - Quote: 行情报价
5//! - Kline: K线数据
6//! - Tick: Tick数据
7//! - Account: 账户信息
8//! - Position: 持仓信息
9//! - Order: 委托单
10//! - Trade: 成交记录
11//! - Chart: 图表状态
12//! - SeriesData: 序列数据
13
14use chrono::{DateTime, Utc};
15use serde::{Deserialize, Deserializer, Serialize};
16use std::collections::HashMap;
17
18// ==================== 自定义反序列化辅助函数 ====================
19
20/// 返回 NaN 作为默认值
21fn default_nan() -> f64 {
22    f64::NAN
23}
24
25/// 将 null 转换为 NaN
26fn deserialize_f64_or_nan<'de, D>(deserializer: D) -> Result<f64, D::Error>
27where
28    D: Deserializer<'de>,
29{
30    let opt = Option::<f64>::deserialize(deserializer)?;
31    Ok(opt.unwrap_or(f64::NAN))
32}
33
34/// 将 null 转换为 0
35fn deserialize_i64_or_zero<'de, D>(deserializer: D) -> Result<i64, D::Error>
36where
37    D: Deserializer<'de>,
38{
39    let opt = Option::<i64>::deserialize(deserializer)?;
40    Ok(opt.unwrap_or(0))
41}
42
43/// 将 null 或 "-" 转换为 NaN
44fn deserialize_f64_or_nan_or_dash<'de, D>(deserializer: D) -> Result<f64, D::Error>
45where
46    D: Deserializer<'de>,
47{
48    use serde::de::Error;
49    let value = serde_json::Value::deserialize(deserializer)?;
50    match value {
51        serde_json::Value::Number(n) => n.as_f64().ok_or_else(|| Error::custom("invalid number")),
52        serde_json::Value::String(s) if s == "-" => Ok(f64::NAN),
53        serde_json::Value::Null => Ok(f64::NAN),
54        _ => Err(Error::custom("expected number, string \"-\", or null")),
55    }
56}
57
58// ==================== Quote 行情报价 ====================
59
60/// 行情报价数据
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Quote {
63    /// 合约代码
64    pub instrument_id: String,
65    /// 行情时间
66    pub datetime: String,
67    /// 最新价
68    #[serde(default = "default_nan")]
69    pub last_price: f64,
70
71    // 买卖盘口
72    /// 卖一价
73    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
74    pub ask_price1: f64,
75    /// 卖一量
76    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
77    pub ask_volume1: i64,
78    /// 卖二价
79    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
80    pub ask_price2: f64,
81    /// 卖二量
82    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
83    pub ask_volume2: i64,
84    /// 卖三价
85    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
86    pub ask_price3: f64,
87    /// 卖三量
88    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
89    pub ask_volume3: i64,
90    /// 卖四价
91    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
92    pub ask_price4: f64,
93    /// 卖四量
94    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
95    pub ask_volume4: i64,
96    /// 卖五价
97    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
98    pub ask_price5: f64,
99    /// 卖五量
100    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
101    pub ask_volume5: i64,
102
103    /// 买一价
104    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
105    pub bid_price1: f64,
106    /// 买一量
107    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
108    pub bid_volume1: i64,
109    /// 买二价
110    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
111    pub bid_price2: f64,
112    /// 买二量
113    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
114    pub bid_volume2: i64,
115    /// 买三价
116    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
117    pub bid_price3: f64,
118    /// 买三量
119    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
120    pub bid_volume3: i64,
121    /// 买四价
122    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
123    pub bid_price4: f64,
124    /// 买四量
125    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
126    pub bid_volume4: i64,
127    /// 买五价
128    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
129    pub bid_price5: f64,
130    /// 买五量
131    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
132    pub bid_volume5: i64,
133
134    // 当日统计
135    /// 最高价
136    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
137    pub highest: f64,
138    /// 最低价
139    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
140    pub lowest: f64,
141    /// 开盘价
142    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
143    pub open: f64,
144    /// 收盘价
145    #[serde(
146        default = "default_nan",
147        deserialize_with = "deserialize_f64_or_nan_or_dash"
148    )]
149    pub close: f64,
150    /// 均价
151    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
152    pub average: f64,
153    /// 成交量
154    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
155    pub volume: i64,
156    /// 成交额
157    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
158    pub amount: f64,
159    /// 持仓量
160    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
161    pub open_interest: i64,
162
163    // 涨跌停
164    /// 跌停价
165    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
166    pub lower_limit: f64,
167    /// 涨停价
168    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
169    pub upper_limit: f64,
170
171    // 结算价
172    /// 结算价
173    #[serde(
174        default = "default_nan",
175        deserialize_with = "deserialize_f64_or_nan_or_dash"
176    )]
177    pub settlement: f64,
178    /// 昨结算价
179    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
180    pub pre_settlement: f64,
181
182    // 涨跌
183    /// 涨跌
184    #[serde(default = "default_nan")]
185    pub change: f64,
186    /// 涨跌幅
187    #[serde(default = "default_nan")]
188    pub change_percent: f64,
189
190    // 期权相关
191    /// 行权价
192    #[serde(default = "default_nan")]
193    pub strike_price: f64,
194
195    // 昨日数据
196    /// 昨持仓量
197    #[serde(default, deserialize_with = "deserialize_i64_or_zero")]
198    pub pre_open_interest: i64,
199    /// 昨收盘价
200    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
201    pub pre_close: f64,
202    /// 昨成交量
203    #[serde(default)]
204    pub pre_volume: i64,
205
206    // 保证金和手续费
207    /// 每手保证金
208    #[serde(default = "default_nan")]
209    pub margin: f64,
210    /// 每手手续费
211    #[serde(default = "default_nan")]
212    pub commission: f64,
213
214    // 合约信息
215    /// 合约类型
216    #[serde(default)]
217    pub class: String,
218    /// 交易所代码
219    #[serde(default)]
220    pub exchange_id: String,
221    /// 品种代码
222    #[serde(default)]
223    pub product_id: String,
224    /// 品种简称
225    #[serde(default)]
226    pub product_short_name: String,
227    /// 标的产品
228    #[serde(default)]
229    pub underlying_product: String,
230    /// 标的合约
231    #[serde(default)]
232    pub underlying_symbol: String,
233    /// 交割年份
234    #[serde(default)]
235    pub delivery_year: i32,
236    /// 交割月份
237    #[serde(default)]
238    pub delivery_month: i32,
239    /// 到期时间
240    #[serde(default)]
241    pub expire_datetime: i64,
242    /// 合约乘数
243    #[serde(default)]
244    pub volume_multiple: i32,
245    /// 最小变动价位
246    #[serde(default = "default_nan")]
247    pub price_tick: f64,
248    /// 价格小数位数
249    #[serde(default)]
250    pub price_decs: i32,
251    /// 市价单最大下单量
252    #[serde(default)]
253    pub max_market_order_vol: i32,
254    /// 市价单最小下单量
255    #[serde(default)]
256    pub min_market_order_vol: i32,
257    /// 限价单最大下单量
258    #[serde(default)]
259    pub max_limit_order_vol: i32,
260    /// 限价单最小下单量
261    #[serde(default)]
262    pub min_limit_order_vol: i32,
263    /// 是否已下市
264    #[serde(default)]
265    pub expired: bool,
266    /// 拼音
267    #[serde(default)]
268    pub py: String,
269
270    // 内部字段(不序列化到 JSON)
271    #[serde(skip_serializing_if = "Option::is_none")]
272    #[serde(rename = "_epoch")]
273    pub epoch: Option<i64>,
274}
275
276impl Default for Quote {
277    fn default() -> Self {
278        Quote {
279            instrument_id: String::new(),
280            datetime: String::new(),
281            last_price: f64::NAN,
282            ask_price1: f64::NAN,
283            ask_volume1: 0,
284            ask_price2: f64::NAN,
285            ask_volume2: 0,
286            ask_price3: f64::NAN,
287            ask_volume3: 0,
288            ask_price4: f64::NAN,
289            ask_volume4: 0,
290            ask_price5: f64::NAN,
291            ask_volume5: 0,
292            bid_price1: f64::NAN,
293            bid_volume1: 0,
294            bid_price2: f64::NAN,
295            bid_volume2: 0,
296            bid_price3: f64::NAN,
297            bid_volume3: 0,
298            bid_price4: f64::NAN,
299            bid_volume4: 0,
300            bid_price5: f64::NAN,
301            bid_volume5: 0,
302            highest: f64::NAN,
303            lowest: f64::NAN,
304            open: f64::NAN,
305            close: f64::NAN,
306            average: f64::NAN,
307            volume: 0,
308            amount: f64::NAN,
309            open_interest: 0,
310            lower_limit: f64::NAN,
311            upper_limit: f64::NAN,
312            settlement: f64::NAN,
313            pre_settlement: f64::NAN,
314            change: f64::NAN,
315            change_percent: f64::NAN,
316            strike_price: f64::NAN,
317            pre_open_interest: 0,
318            pre_close: f64::NAN,
319            pre_volume: 0,
320            margin: f64::NAN,
321            commission: f64::NAN,
322            class: String::new(),
323            exchange_id: String::new(),
324            product_id: String::new(),
325            product_short_name: String::new(),
326            underlying_product: String::new(),
327            underlying_symbol: String::new(),
328            delivery_year: 0,
329            delivery_month: 0,
330            expire_datetime: 0,
331            volume_multiple: 0,
332            price_tick: f64::NAN,
333            price_decs: 0,
334            max_market_order_vol: 0,
335            min_market_order_vol: 0,
336            max_limit_order_vol: 0,
337            min_limit_order_vol: 0,
338            expired: false,
339            py: String::new(),
340            epoch: None,
341        }
342    }
343}
344
345impl Quote {
346    /// 更新涨跌和涨跌幅
347    pub fn update_change(&mut self) {
348        if !self.last_price.is_nan() && !self.pre_settlement.is_nan() && self.pre_settlement != 0.0
349        {
350            self.change = self.last_price - self.pre_settlement;
351            self.change_percent = self.change / self.pre_settlement * 100.0;
352        }
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn test_quote_deserialize_with_nulls() {
362        let json_data = r#"{
363            "instrument_id":"DCE.m2512",
364            "datetime":"2025-11-24 22:59:59.000001",
365            "ask_price1":3005.0,
366            "ask_volume1":1,
367            "ask_price2":null,
368            "ask_volume2":null,
369            "bid_price1":2995.0,
370            "bid_volume1":2,
371            "bid_price2":null,
372            "bid_volume2":null,
373            "last_price":3000.0,
374            "highest":3000.0,
375            "lowest":2986.0,
376            "open":2998.0,
377            "close":"-",
378            "average":2995.0,
379            "volume":688,
380            "amount":20609740.0,
381            "open_interest":5278,
382            "settlement":"-",
383            "upper_limit":3181.0,
384            "lower_limit":2821.0,
385            "pre_open_interest":5729,
386            "pre_settlement":3001.0,
387            "pre_close":3001.0
388        }"#;
389
390        let result = serde_json::from_str::<Quote>(json_data);
391        assert!(result.is_ok(), "Quote 解析失败: {:?}", result.err());
392
393        let quote = result.unwrap();
394        assert_eq!(quote.instrument_id, "DCE.m2512");
395        assert_eq!(quote.last_price, 3000.0);
396        assert_eq!(quote.ask_price1, 3005.0);
397        assert!(quote.ask_price2.is_nan(), "null 应该被转换为 NaN");
398        assert_eq!(quote.ask_volume2, 0, "null 应该被转换为 0");
399        assert!(quote.close.is_nan(), "dash 应该被转换为 NaN");
400        assert!(quote.settlement.is_nan(), "dash 应该被转换为 NaN");
401    }
402}
403
404// ==================== Kline K线数据 ====================
405
406/// K线数据
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct Kline {
409    /// K线ID
410    #[serde(default)]
411    pub id: i64,
412    /// K线起点时间(纳秒)
413    pub datetime: i64,
414    /// 开盘价
415    pub open: f64,
416    /// 收盘价
417    pub close: f64,
418    /// 最高价
419    pub high: f64,
420    /// 最低价
421    pub low: f64,
422    /// 起始持仓量
423    pub open_oi: i64,
424    /// 结束持仓量
425    pub close_oi: i64,
426    /// 成交量
427    pub volume: i64,
428
429    // 内部字段
430    #[serde(skip_serializing_if = "Option::is_none")]
431    #[serde(rename = "_epoch")]
432    pub epoch: Option<i64>,
433}
434
435impl Default for Kline {
436    fn default() -> Self {
437        Kline {
438            id: 0,
439            datetime: 0,
440            open: f64::NAN,
441            close: f64::NAN,
442            high: f64::NAN,
443            low: f64::NAN,
444            open_oi: 0,
445            close_oi: 0,
446            volume: 0,
447            epoch: None,
448        }
449    }
450}
451
452// ==================== Tick Tick数据 ====================
453
454/// Tick数据
455#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct Tick {
457    /// Tick ID
458    #[serde(default)]
459    pub id: i64,
460    /// tick时间(纳秒)
461    pub datetime: i64,
462    /// 最新价
463    pub last_price: f64,
464    /// 均价
465    pub average: f64,
466    /// 最高价
467    pub highest: f64,
468    /// 最低价
469    pub lowest: f64,
470
471    // 盘口
472    pub ask_price1: f64,
473    pub ask_volume1: i64,
474
475    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
476    pub ask_price2: f64,
477
478    pub ask_volume2: i64,
479
480    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
481    pub ask_price3: f64,
482    pub ask_volume3: i64,
483
484    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
485    pub ask_price4: f64,
486    pub ask_volume4: i64,
487
488    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
489    pub ask_price5: f64,
490    pub ask_volume5: i64,
491
492    pub bid_price1: f64,
493    pub bid_volume1: i64,
494
495    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
496    pub bid_price2: f64,
497    pub bid_volume2: i64,
498
499    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
500    pub bid_price3: f64,
501    pub bid_volume3: i64,
502
503    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
504    pub bid_price4: f64,
505    pub bid_volume4: i64,
506
507    #[serde(default = "default_nan", deserialize_with = "deserialize_f64_or_nan")]
508    pub bid_price5: f64,
509    pub bid_volume5: i64,
510
511    /// 成交量
512    pub volume: i64,
513    /// 成交额
514    pub amount: f64,
515    /// 持仓量
516    pub open_interest: i64,
517
518    // 内部字段
519    #[serde(skip_serializing_if = "Option::is_none")]
520    #[serde(rename = "_epoch")]
521    pub epoch: Option<i64>,
522}
523
524impl Default for Tick {
525    fn default() -> Self {
526        Tick {
527            id: 0,
528            datetime: 0,
529            last_price: f64::NAN,
530            average: f64::NAN,
531            highest: f64::NAN,
532            lowest: f64::NAN,
533            ask_price1: f64::NAN,
534            ask_volume1: 0,
535            ask_price2: f64::NAN,
536            ask_volume2: 0,
537            ask_price3: f64::NAN,
538            ask_volume3: 0,
539            ask_price4: f64::NAN,
540            ask_volume4: 0,
541            ask_price5: f64::NAN,
542            ask_volume5: 0,
543            bid_price1: f64::NAN,
544            bid_volume1: 0,
545            bid_price2: f64::NAN,
546            bid_volume2: 0,
547            bid_price3: f64::NAN,
548            bid_volume3: 0,
549            bid_price4: f64::NAN,
550            bid_volume4: 0,
551            bid_price5: f64::NAN,
552            bid_volume5: 0,
553            volume: 0,
554            amount: f64::NAN,
555            open_interest: 0,
556            epoch: None,
557        }
558    }
559}
560
561// ==================== Chart 图表状态 ====================
562
563/// 图表状态
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct Chart {
566    /// 左边界K线ID
567    pub left_id: i64,
568    /// 右边界K线ID
569    pub right_id: i64,
570    /// 是否有更多数据
571    pub more_data: bool,
572    /// 数据是否已准备好(分片传输完成)
573    pub ready: bool,
574    /// 图表状态
575    #[serde(default)]
576    pub state: HashMap<String, serde_json::Value>,
577
578    // 内部字段
579    #[serde(skip_serializing_if = "Option::is_none")]
580    #[serde(rename = "_epoch")]
581    pub epoch: Option<i64>,
582}
583
584impl Default for Chart {
585    fn default() -> Self {
586        Chart {
587            left_id: -1,
588            right_id: -1,
589            more_data: true,
590            ready: false,
591            state: HashMap::new(),
592            epoch: None,
593        }
594    }
595}
596
597/// Chart 信息
598#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct ChartInfo {
600    #[serde(default)]
601    pub chart_id: String,
602    pub left_id: i64,
603    pub right_id: i64,
604    pub more_data: bool,
605    pub ready: bool,
606    #[serde(default)]
607    pub view_width: usize,
608}
609
610// ==================== K线序列数据 ====================
611
612/// K线序列数据(带Chart信息)
613#[derive(Debug, Clone)]
614pub struct KlineSeriesData {
615    /// 合约代码
616    pub symbol: String,
617    /// K线周期(纳秒)
618    pub duration: i64,
619    /// 关联的Chart ID
620    pub chart_id: String,
621    /// Chart 信息
622    pub chart: Option<ChartInfo>,
623    /// 最新K线ID
624    pub last_id: i64,
625    /// 交易日起始ID
626    pub trading_day_start_id: i64,
627    /// 交易日结束ID
628    pub trading_day_end_id: i64,
629    /// K线数组(仅保留 ViewWidth 长度)
630    pub data: Vec<Kline>,
631    /// 是否有新K线
632    pub has_new_bar: bool,
633}
634
635/// Tick序列数据
636#[derive(Debug, Clone)]
637pub struct TickSeriesData {
638    /// 合约代码
639    pub symbol: String,
640    /// 关联的Chart ID
641    pub chart_id: String,
642    /// Chart 信息
643    pub chart: Option<ChartInfo>,
644    /// 最新Tick ID
645    pub last_id: i64,
646    /// Tick数组
647    pub data: Vec<Tick>,
648    /// 是否有新Tick
649    pub has_new_bar: bool,
650}
651
652// ==================== 多合约对齐K线 ====================
653
654/// 对齐的K线集合(一个时间点的多个合约)
655#[derive(Debug, Clone)]
656pub struct AlignedKlineSet {
657    /// 主合约的K线ID
658    pub main_id: i64,
659    /// 时间戳
660    pub timestamp: DateTime<Utc>,
661    /// symbol -> Kline
662    pub klines: HashMap<String, Kline>,
663}
664
665/// K线元数据
666#[derive(Debug, Clone)]
667pub struct KlineMetadata {
668    pub symbol: String,
669    pub last_id: i64,
670    pub trading_day_start_id: i64,
671    pub trading_day_end_id: i64,
672}
673
674/// 多合约K线序列数据(已对齐)
675#[derive(Debug, Clone)]
676pub struct MultiKlineSeriesData {
677    /// 图表ID
678    pub chart_id: String,
679    /// K线周期(纳秒)
680    pub duration: i64,
681    /// 主合约(第一个合约)
682    pub main_symbol: String,
683    /// 所有合约列表
684    pub symbols: Vec<String>,
685    /// 左边界ID
686    pub left_id: i64,
687    /// 右边界ID
688    pub right_id: i64,
689    /// 视图宽度
690    pub view_width: usize,
691    /// 对齐的K线数据集
692    pub data: Vec<AlignedKlineSet>,
693    /// 是否有新K线产生
694    pub has_new_bar: bool,
695    /// 每个合约的元数据
696    pub metadata: HashMap<String, KlineMetadata>,
697}
698
699// ==================== 序列数据统一接口 ====================
700
701/// 序列数据(统一接口)
702#[derive(Debug, Clone)]
703pub struct SeriesData {
704    /// 是否为多合约
705    pub is_multi: bool,
706    /// 是否为Tick数据
707    pub is_tick: bool,
708    /// 合约列表
709    pub symbols: Vec<String>,
710    /// 单合约K线数据
711    pub single: Option<KlineSeriesData>,
712    /// 多合约K线数据
713    pub multi: Option<MultiKlineSeriesData>,
714    /// Tick数据
715    pub tick_data: Option<TickSeriesData>,
716}
717
718impl SeriesData {
719    /// 获取指定合约的K线数据
720    pub fn get_symbol_klines(&self, symbol: &str) -> Option<&KlineSeriesData> {
721        if self.is_multi || self.symbols.is_empty() || self.symbols[0] != symbol {
722            return None;
723        }
724        self.single.as_ref()
725    }
726}
727
728/// 数据更新信息
729#[derive(Debug, Clone)]
730pub struct UpdateInfo {
731    /// 是否有新 K线/Tick
732    pub has_new_bar: bool,
733    /// 新 K线的 ID(symbol -> id)
734    pub new_bar_ids: HashMap<String, i64>,
735    /// 是否有 K线更新(最后一根)
736    pub has_bar_update: bool,
737    /// Chart 范围是否变化
738    pub chart_range_changed: bool,
739    /// 旧左边界
740    pub old_left_id: i64,
741    /// 旧右边界
742    pub old_right_id: i64,
743    /// 新左边界
744    pub new_left_id: i64,
745    /// 新右边界
746    pub new_right_id: i64,
747    /// Chart 是否同步完成
748    pub has_chart_sync: bool,
749    /// Chart数据传输是否完成(分片传输场景)
750    pub chart_ready: bool,
751}
752
753impl Default for UpdateInfo {
754    fn default() -> Self {
755        UpdateInfo {
756            has_new_bar: false,
757            new_bar_ids: HashMap::new(),
758            has_bar_update: false,
759            chart_range_changed: false,
760            old_left_id: -1,
761            old_right_id: -1,
762            new_left_id: -1,
763            new_right_id: -1,
764            has_chart_sync: false,
765            chart_ready: false,
766        }
767    }
768}
769
770// ==================== 交易相关数据结构 ====================
771
772/// 账户资金信息
773#[derive(Debug, Clone, Serialize, Deserialize)]
774pub struct Account {
775    /// 用户ID
776    #[serde(default)]
777    pub user_id: String,
778    /// 货币类型(默认 CNY)
779    #[serde(default = "default_currency")]
780    pub currency: String,
781    /// 可用资金
782    #[serde(default)]
783    pub available: f64,
784    /// 账户权益
785    #[serde(default)]
786    pub balance: f64,
787    /// 本交易日内平仓盈亏
788    #[serde(default)]
789    pub close_profit: f64,
790    /// 手续费 - 本交易日内交纳的手续费
791    #[serde(default)]
792    pub commission: f64,
793    /// CTP可用资金
794    #[serde(default)]
795    pub ctp_available: f64,
796    /// CTP账户权益
797    #[serde(default)]
798    pub ctp_balance: f64,
799    /// 入金金额 - 本交易日内的入金金额
800    #[serde(default)]
801    pub deposit: f64,
802    /// 浮动盈亏
803    #[serde(default)]
804    pub float_profit: f64,
805    /// 冻结手续费
806    #[serde(default)]
807    pub frozen_commission: f64,
808    /// 冻结保证金
809    #[serde(default)]
810    pub frozen_margin: f64,
811    /// 冻结权利金
812    #[serde(default)]
813    pub frozen_premium: f64,
814    /// 保证金占用
815    #[serde(default)]
816    pub margin: f64,
817    /// 期权市值
818    #[serde(default)]
819    pub market_value: f64,
820    /// 持仓盈亏
821    #[serde(default)]
822    pub position_profit: f64,
823    /// 昨日账户权益
824    #[serde(default)]
825    pub pre_balance: f64,
826    /// 权利金 - 本交易日内交纳的权利金
827    #[serde(default)]
828    pub premium: f64,
829    /// 风险度 = 1 - available / balance
830    #[serde(default)]
831    pub risk_ratio: f64,
832    /// 静态权益
833    #[serde(default)]
834    pub static_balance: f64,
835    /// 出金金额 - 本交易日内的出金金额
836    #[serde(default)]
837    pub withdraw: f64,
838
839    #[serde(skip_serializing_if = "Option::is_none")]
840    #[serde(rename = "_epoch")]
841    pub epoch: Option<i64>,
842}
843
844fn default_currency() -> String {
845    "CNY".to_string()
846}
847
848impl Account {
849    /// curr_margin 别名(返回 margin)
850    pub fn curr_margin(&self) -> f64 {
851        self.margin
852    }
853
854    /// _epoch 字段访问
855    pub fn _epoch(&self) -> Option<i64> {
856        self.epoch
857    }
858}
859
860/// 持仓信息
861#[derive(Debug, Clone, Serialize, Deserialize)]
862pub struct Position {
863    /// 用户ID
864    #[serde(default)]
865    pub user_id: String,
866    /// 交易所代码
867    #[serde(default)]
868    pub exchange_id: String,
869    /// 合约代码
870    #[serde(default)]
871    pub instrument_id: String,
872    /// 多头今仓持仓手数
873    #[serde(default)]
874    pub volume_long_today: i64,
875    /// 多头老仓持仓手数
876    #[serde(default)]
877    pub volume_long_his: i64,
878    /// 多头持仓手数
879    #[serde(default)]
880    pub volume_long: i64,
881    /// 多头今仓冻结手数
882    #[serde(default)]
883    pub volume_long_frozen_today: i64,
884    /// 多头老仓冻结手数
885    #[serde(default)]
886    pub volume_long_frozen_his: i64,
887    /// 多头持仓冻结
888    #[serde(default)]
889    pub volume_long_frozen: i64,
890    /// 空头今仓持仓手数
891    #[serde(default)]
892    pub volume_short_today: i64,
893    /// 空头老仓持仓手数
894    #[serde(default)]
895    pub volume_short_his: i64,
896    /// 空头持仓手数
897    #[serde(default)]
898    pub volume_short: i64,
899    /// 空头今仓冻结手数
900    #[serde(default)]
901    pub volume_short_frozen_today: i64,
902    /// 空头老仓冻结手数
903    #[serde(default)]
904    pub volume_short_frozen_his: i64,
905    /// 空头持仓冻结
906    #[serde(default)]
907    pub volume_short_frozen: i64,
908    /// 多头昨仓手数
909    #[serde(default)]
910    pub volume_long_yd: i64,
911    /// 空头昨仓手数
912    #[serde(default)]
913    pub volume_short_yd: i64,
914    /// 多头老仓手数
915    #[serde(default)]
916    pub pos_long_his: i64,
917    /// 多头今仓手数
918    #[serde(default)]
919    pub pos_long_today: i64,
920    /// 空头老仓手数
921    #[serde(default)]
922    pub pos_short_his: i64,
923    /// 空头今仓手数
924    #[serde(default)]
925    pub pos_short_today: i64,
926    /// 多头开仓均价
927    #[serde(default)]
928    pub open_price_long: f64,
929    /// 空头开仓均价
930    #[serde(default)]
931    pub open_price_short: f64,
932    /// 多头开仓市值
933    #[serde(default)]
934    pub open_cost_long: f64,
935    /// 空头开仓市值
936    #[serde(default)]
937    pub open_cost_short: f64,
938    /// 多头持仓均价
939    #[serde(default)]
940    pub position_price_long: f64,
941    /// 空头持仓均价
942    #[serde(default)]
943    pub position_price_short: f64,
944    /// 多头持仓市值
945    #[serde(default)]
946    pub position_cost_long: f64,
947    /// 空头持仓市值
948    #[serde(default)]
949    pub position_cost_short: f64,
950    /// 最新价
951    #[serde(default)]
952    pub last_price: f64,
953    /// 多头浮动盈亏
954    #[serde(default)]
955    pub float_profit_long: f64,
956    /// 空头浮动盈亏
957    #[serde(default)]
958    pub float_profit_short: f64,
959    /// 浮动盈亏 = floatProfitLong + floatProfitShort
960    #[serde(default)]
961    pub float_profit: f64,
962    /// 多头持仓盈亏
963    #[serde(default)]
964    pub position_profit_long: f64,
965    /// 空头持仓盈亏
966    #[serde(default)]
967    pub position_profit_short: f64,
968    /// 持仓盈亏 = positionProfitLong + positionProfitShort
969    #[serde(default)]
970    pub position_profit: f64,
971    /// 多头持仓占用保证金
972    #[serde(default)]
973    pub margin_long: f64,
974    /// 空头持仓占用保证金
975    #[serde(default)]
976    pub margin_short: f64,
977    /// 持仓占用保证金 = marginLong + marginShort
978    #[serde(default)]
979    pub margin: f64,
980    /// 期权权利方市值(始终 >= 0)
981    #[serde(default)]
982    pub market_value_long: f64,
983    /// 期权义务方市值(始终 <= 0)
984    #[serde(default)]
985    pub market_value_short: f64,
986    /// 期权市值
987    #[serde(default)]
988    pub market_value: f64,
989
990    #[serde(skip_serializing_if = "Option::is_none")]
991    #[serde(rename = "_epoch")]
992    pub epoch: Option<i64>,
993}
994
995/// 委托单信息
996#[derive(Debug, Clone, Serialize, Deserialize)]
997pub struct Order {
998    /// 内部序号
999    #[serde(default)]
1000    pub seqno: i64,
1001    /// 用户ID
1002    #[serde(default)]
1003    pub user_id: String,
1004    /// 委托单ID, 对于一个user, orderId 是永远不重复的
1005    #[serde(default)]
1006    pub order_id: String,
1007    /// 交易所代码
1008    #[serde(default)]
1009    pub exchange_id: String,
1010    /// 在交易所中的合约代码
1011    #[serde(default)]
1012    pub instrument_id: String,
1013    /// 下单方向 (buy=买, sell=卖)
1014    #[serde(default)]
1015    pub direction: String,
1016    /// 开平标志 (open=开仓, close=平仓, closetoday=平今)
1017    #[serde(default)]
1018    pub offset: String,
1019    /// 总报单手数
1020    #[serde(default)]
1021    pub volume_orign: i64,
1022    /// 指令类型 (any=市价, limit=限价)
1023    #[serde(default)]
1024    pub price_type: String,
1025    /// 委托价格, 仅当 priceType = limit 时有效
1026    #[serde(default)]
1027    pub limit_price: f64,
1028    /// 时间条件 (ioc=立即完成,否则撤销, gfs=本节有效, *gfd=当日有效, gtc=撤销前有效, gfa=集合竞价有效)
1029    #[serde(default)]
1030    pub time_condition: String,
1031    /// 数量条件 (any=任何数量, min=最小数量, all=全部数量)
1032    #[serde(default)]
1033    pub volume_condition: String,
1034    /// 下单时间(按北京时间),自unix epoch(1970-01-01 00:00:00 gmt)以来的纳秒数
1035    #[serde(default)]
1036    pub insert_date_time: i64,
1037    /// 交易所单号
1038    #[serde(default)]
1039    pub exchange_order_id: String,
1040    /// 委托单状态, (alive=有效, finished=已完)
1041    #[serde(default)]
1042    pub status: String,
1043    /// 未成交手数
1044    #[serde(default)]
1045    pub volume_left: i64,
1046    /// 冻结保证金
1047    #[serde(default)]
1048    pub frozen_margin: f64,
1049    /// 委托单状态信息
1050    #[serde(default)]
1051    pub last_msg: String,
1052
1053    #[serde(skip_serializing_if = "Option::is_none")]
1054    #[serde(rename = "_epoch")]
1055    pub epoch: Option<i64>,
1056}
1057
1058impl Order {
1059    /// volume 别名(返回 volume_orign)
1060    pub fn volume(&self) -> i64 {
1061        self.volume_orign
1062    }
1063
1064    /// price 别名(返回 limit_price)
1065    pub fn price(&self) -> f64 {
1066        self.limit_price
1067    }
1068}
1069
1070/// 成交记录
1071#[derive(Debug, Clone, Serialize, Deserialize)]
1072pub struct Trade {
1073    /// 内部序号
1074    #[serde(default)]
1075    pub seqno: i64,
1076    /// 账户号
1077    #[serde(default)]
1078    pub user_id: String,
1079    /// 成交ID, 对于一个用户的所有成交,这个ID都是不重复的
1080    #[serde(default)]
1081    pub trade_id: String,
1082    /// 交易所
1083    #[serde(default)]
1084    pub exchange_id: String,
1085    /// 交易所内的合约代码
1086    #[serde(default)]
1087    pub instrument_id: String,
1088    /// 委托单ID, 对于一个用户的所有委托单,这个ID都是不重复的
1089    #[serde(default)]
1090    pub order_id: String,
1091    /// 交易所成交单号
1092    #[serde(default)]
1093    pub exchange_trade_id: String,
1094    /// 下单方向 (BUY=买, SELL=卖)
1095    #[serde(default)]
1096    pub direction: String,
1097    /// 开平标志 (OPEN=开仓, CLOSE=平仓, CLOSETODAY=平今)
1098    #[serde(default)]
1099    pub offset: String,
1100    /// 成交手数
1101    #[serde(default)]
1102    pub volume: i64,
1103    /// 成交价格
1104    #[serde(default)]
1105    pub price: f64,
1106    /// 成交时间, epoch nano
1107    #[serde(default)]
1108    pub trade_date_time: i64,
1109    /// 成交手续费
1110    #[serde(default)]
1111    pub commission: f64,
1112
1113    #[serde(skip_serializing_if = "Option::is_none")]
1114    #[serde(rename = "_epoch")]
1115    pub epoch: Option<i64>,
1116}
1117
1118/// 通知事件
1119#[derive(Debug, Clone, Serialize, Deserialize)]
1120pub struct NotifyEvent {
1121    /// 通知代码
1122    pub code: String,
1123    /// 通知级别
1124    pub level: String,
1125    /// 通知类型
1126    pub r#type: String,
1127    /// 通知内容
1128    pub content: String,
1129    /// 期货公司
1130    pub bid: String,
1131    /// 用户ID
1132    pub user_id: String,
1133}
1134
1135/// 通知
1136#[derive(Debug, Clone, Serialize, Deserialize)]
1137pub struct Notification {
1138    /// 通知代码
1139    pub code: String,
1140    /// 通知级别
1141    pub level: String,
1142    /// 通知类型
1143    pub r#type: String,
1144    /// 通知内容
1145    pub content: String,
1146    /// 期货公司
1147    pub bid: String,
1148    /// 用户ID
1149    pub user_id: String,
1150}
1151
1152/// 持仓更新
1153#[derive(Debug, Clone)]
1154pub struct PositionUpdate {
1155    /// 合约代码
1156    pub symbol: String,
1157    /// 持仓信息
1158    pub position: Position,
1159}
1160
1161/// 下单请求
1162#[derive(Debug, Clone)]
1163pub struct InsertOrderRequest {
1164    /// 合约代码(格式:EXCHANGE.INSTRUMENT,如 SHFE.au2512)
1165    pub symbol: String,
1166    /// 交易所代码(可选,如果提供 symbol 则自动拆分)
1167    pub exchange_id: Option<String>,
1168    /// 合约代码(可选,如果提供 symbol 则自动拆分)
1169    pub instrument_id: Option<String>,
1170    /// 下单方向 BUY/SELL
1171    pub direction: String,
1172    /// 开平标志 OPEN/CLOSE/CLOSETODAY
1173    pub offset: String,
1174    /// 价格类型 LIMIT/ANY
1175    pub price_type: String,
1176    /// 委托价格
1177    pub limit_price: f64,
1178    /// 下单手数
1179    pub volume: i64,
1180}
1181
1182impl InsertOrderRequest {
1183    /// 获取交易所代码(从 symbol 拆分或使用 exchange_id)
1184    pub fn get_exchange_id(&self) -> String {
1185        if let Some(ref exchange) = self.exchange_id {
1186            return exchange.clone();
1187        }
1188        // 从 symbol 拆分:EXCHANGE.INSTRUMENT
1189        if let Some(dot_pos) = self.symbol.find('.') {
1190            self.symbol[..dot_pos].to_string()
1191        } else {
1192            String::new()
1193        }
1194    }
1195
1196    /// 获取合约代码(从 symbol 拆分或使用 instrument_id)
1197    pub fn get_instrument_id(&self) -> String {
1198        if let Some(ref instrument) = self.instrument_id {
1199            return instrument.clone();
1200        }
1201        // 从 symbol 拆分:EXCHANGE.INSTRUMENT
1202        if let Some(dot_pos) = self.symbol.find('.') {
1203            self.symbol[dot_pos + 1..].to_string()
1204        } else {
1205            self.symbol.clone()
1206        }
1207    }
1208}
1209
1210// ==================== 常量定义 ====================
1211
1212/// 方向 - 买入
1213pub const DIRECTION_BUY: &str = "BUY";
1214/// 方向 - 卖出
1215pub const DIRECTION_SELL: &str = "SELL";
1216
1217/// 开平 - 开仓
1218pub const OFFSET_OPEN: &str = "OPEN";
1219/// 开平 - 平仓
1220pub const OFFSET_CLOSE: &str = "CLOSE";
1221/// 开平 - 平今
1222pub const OFFSET_CLOSETODAY: &str = "CLOSETODAY";
1223
1224/// 价格类型 - 限价单
1225pub const PRICE_TYPE_LIMIT: &str = "LIMIT";
1226/// 价格类型 - 市价单
1227pub const PRICE_TYPE_ANY: &str = "ANY";
1228
1229/// 订单状态 - 活动(未成交或部分成交)
1230pub const ORDER_STATUS_ALIVE: &str = "ALIVE";
1231/// 订单状态 - 已完成(全部成交或已撤销)
1232pub const ORDER_STATUS_FINISHED: &str = "FINISHED";
1233
1234// ==================== 序列订阅选项 ====================
1235
1236/// 序列订阅选项
1237#[derive(Debug, Clone)]
1238pub struct SeriesOptions {
1239    /// 合约列表
1240    pub symbols: Vec<String>,
1241    /// K线周期(纳秒,0表示Tick)
1242    pub duration: i64,
1243    /// 视图宽度(最大 10000)
1244    pub view_width: usize,
1245    /// 图表ID(可选)
1246    pub chart_id: Option<String>,
1247    /// 左边界 K线 ID(可选,优先级最高)
1248    pub left_kline_id: Option<i64>,
1249    /// 焦点时间(可选,需配合 focus_position 使用)
1250    pub focus_datetime: Option<DateTime<Utc>>,
1251    /// 焦点位置(可选,需配合 focus_datetime 使用,1=右侧,-1=左侧)
1252    pub focus_position: Option<i32>,
1253}
1254
1255impl Default for SeriesOptions {
1256    fn default() -> Self {
1257        SeriesOptions {
1258            symbols: Vec::new(),
1259            duration: 0,
1260            view_width: 10000,
1261            chart_id: None,
1262            left_kline_id: None,
1263            focus_datetime: None,
1264            focus_position: None,
1265        }
1266    }
1267}