tenk 0.2.0

10K - A Rust library for fetching market data from multiple sources
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
//! Stock data structures.

use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};

/// Stock exchange.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Exchange {
    /// Shanghai Stock Exchange
    SH,
    /// Shenzhen Stock Exchange
    SZ,
    /// Beijing Stock Exchange
    BJ,
    /// Unknown exchange
    Unknown,
}

impl Exchange {
    /// Determines exchange from stock code prefix.
    pub fn from_stock_code(code: &str) -> Self {
        let first_two: String = code.chars().take(2).collect();
        match first_two.as_str() {
            // Shanghai: 6xxxxx stocks, 11xxxx bonds, 5xxxxx ETFs
            s if s.starts_with('6') => Exchange::SH,
            "11" => Exchange::SH,
            s if s.starts_with('5') => Exchange::SH,
            // Shenzhen: 0xxxxx/3xxxxx stocks, 12xxxx bonds, 1xxxxx ETFs
            s if s.starts_with('0') || s.starts_with('3') => Exchange::SZ,
            "12" => Exchange::SZ,
            s if s.starts_with('1') && !s.starts_with("11") => Exchange::SZ,
            // Beijing: 4xxxxx/8xxxxx
            s if s.starts_with('4') || s.starts_with('8') => Exchange::BJ,
            _ => Exchange::Unknown,
        }
    }

    /// Returns the market prefix string.
    pub fn market_prefix(&self) -> &'static str {
        match self {
            Exchange::SH => "sh",
            Exchange::SZ => "sz",
            Exchange::BJ => "bj",
            Exchange::Unknown => "",
        }
    }
}

impl std::fmt::Display for Exchange {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Exchange::SH => write!(f, "SH"),
            Exchange::SZ => write!(f, "SZ"),
            Exchange::BJ => write!(f, "BJ"),
            Exchange::Unknown => write!(f, "Unknown"),
        }
    }
}

/// K-line (candlestick) time period.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum KLineType {
    /// Daily K-line
    Daily = 1,
    /// Weekly K-line
    Weekly = 2,
    /// Monthly K-line
    Monthly = 3,
    /// Quarterly K-line
    Quarterly = 4,
    /// 5-minute K-line
    Min5 = 5,
    /// 15-minute K-line
    Min15 = 15,
    /// 30-minute K-line
    Min30 = 30,
    /// 60-minute K-line
    Min60 = 60,
}

impl KLineType {
    /// Converts to API value code.
    pub fn to_api_value(&self) -> u32 {
        match self {
            KLineType::Daily => 101,
            KLineType::Weekly => 102,
            KLineType::Monthly => 103,
            KLineType::Quarterly => 104,
            KLineType::Min5 => 5,
            KLineType::Min15 => 15,
            KLineType::Min30 => 30,
            KLineType::Min60 => 60,
        }
    }
}

/// Price adjustment type for historical data.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum AdjustType {
    /// No adjustment
    None = 0,
    /// Forward adjusted
    #[default]
    Forward = 1,
    /// Backward adjusted
    Backward = 2,
}

/// Stock basic information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StockCode {
    /// Stock code
    pub stock_code: String,
    /// Short name
    pub short_name: String,
    /// Exchange
    pub exchange: Exchange,
    /// Listing date
    pub list_date: Option<NaiveDate>,
}

impl StockCode {
    /// Creates a new stock code.
    pub fn new(stock_code: String, short_name: String, exchange: Exchange) -> Self {
        Self {
            stock_code,
            short_name,
            exchange,
            list_date: None,
        }
    }

    /// Returns the full symbol with exchange prefix.
    pub fn full_symbol(&self) -> String {
        format!("{}{}", self.exchange.market_prefix(), self.stock_code)
    }
}

/// Historical market data (K-line).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketData {
    /// Stock code
    pub stock_code: String,
    /// Trade time
    pub trade_time: DateTime<Utc>,
    /// Trade date
    pub trade_date: NaiveDate,
    /// Open price
    pub open: f64,
    /// Close price
    pub close: f64,
    /// High price
    pub high: f64,
    /// Low price
    pub low: f64,
    /// Volume
    pub volume: u64,
    /// Amount
    pub amount: f64,
    /// Price change
    pub change: f64,
    /// Change percentage
    pub change_pct: f64,
    /// Turnover ratio
    pub turnover_ratio: f64,
    /// Previous close
    pub pre_close: f64,
}

impl MarketData {
    /// Returns true if data is valid.
    pub fn is_valid(&self) -> bool {
        self.volume > 0 && self.amount > 0.0
    }
}

/// Real-time stock market data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurrentMarketData {
    /// Stock code
    pub stock_code: String,
    /// Short name
    pub short_name: String,
    /// Current price
    pub price: f64,
    /// Price change
    pub change: f64,
    /// Change percentage
    pub change_pct: f64,
    /// Volume
    pub volume: u64,
    /// Amount
    pub amount: f64,
    /// Open price
    pub open: Option<f64>,
    /// High price
    pub high: Option<f64>,
    /// Low price
    pub low: Option<f64>,
    /// Previous close
    pub pre_close: Option<f64>,
}

/// Intraday minute-level data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MinuteData {
    /// Stock code
    pub stock_code: String,
    /// Trade time
    pub trade_time: DateTime<Utc>,
    /// Current price
    pub price: f64,
    /// Price change
    pub change: f64,
    /// Change percentage
    pub change_pct: f64,
    /// Volume
    pub volume: u64,
    /// Average price
    pub avg_price: f64,
    /// Amount
    pub amount: f64,
}

/// Level 1 order book data (5 levels).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookData {
    /// Stock code
    pub stock_code: String,
    /// Short name
    pub short_name: String,
    /// Sell prices (5 levels)
    pub sell_prices: [f64; 5],
    /// Sell volumes (5 levels)
    pub sell_volumes: [u64; 5],
    /// Buy prices (5 levels)
    pub buy_prices: [f64; 5],
    /// Buy volumes (5 levels)
    pub buy_volumes: [u64; 5],
}

/// Tick-by-tick transaction data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TickData {
    /// Stock code
    pub stock_code: String,
    /// Trade time
    pub trade_time: DateTime<Utc>,
    /// Trade price
    pub price: f64,
    /// Trade volume
    pub volume: u64,
    /// Trade direction
    pub direction: char,
}

/// Detailed stock information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StockInfo {
    /// Stock code
    pub stock_code: String,
    /// Full company name
    pub full_name: String,
    /// Short name
    pub short_name: String,
    /// Exchange
    pub exchange: Exchange,
    /// Industry sector
    pub industry: Option<String>,
    /// Total shares
    pub total_shares: Option<u64>,
    /// Circulating shares
    pub circulating_shares: Option<u64>,
    /// Listing date
    pub list_date: Option<NaiveDate>,
}

/// Capital flow data for a stock.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapitalFlowData {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Main capital net inflow
    pub main_net_inflow: f64,
    /// Main capital inflow
    pub main_inflow: f64,
    /// Main capital outflow
    pub main_outflow: f64,
    /// Super large order net inflow
    pub super_large_net_inflow: f64,
    /// Large order net inflow
    pub large_net_inflow: f64,
    /// Medium order net inflow
    pub medium_net_inflow: f64,
    /// Small order net inflow
    pub small_net_inflow: f64,
    /// Main capital net inflow ratio
    pub main_net_ratio: f64,
}

/// Historical capital flow data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapitalFlowHistory {
    /// Stock code
    pub stock_code: String,
    /// Trade date
    pub trade_date: NaiveDate,
    /// Main capital net inflow
    pub main_net_inflow: f64,
    /// Small order net inflow
    pub small_net_inflow: f64,
    /// Medium order net inflow
    pub medium_net_inflow: f64,
    /// Large order net inflow
    pub large_net_inflow: f64,
    /// Super large order net inflow
    pub super_large_net_inflow: f64,
    /// Close price
    pub close: f64,
    /// Change percentage
    pub change_pct: f64,
}

/// Billboard item.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BillboardItem {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Trade date
    pub trade_date: NaiveDate,
    /// Close price
    pub close: f64,
    /// Change percentage
    pub change_pct: f64,
    /// Turnover ratio
    pub turnover_ratio: f64,
    /// Net buy amount
    pub net_buy_amount: f64,
    /// Buy amount
    pub buy_amount: f64,
    /// Sell amount
    pub sell_amount: f64,
    /// Reason for listing
    pub reason: String,
}

/// Billboard institution detail.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BillboardDetail {
    /// Stock code
    pub stock_code: String,
    /// Trade date
    pub trade_date: NaiveDate,
    /// Institution/broker name
    pub trader_name: String,
    /// Buy amount
    pub buy_amount: f64,
    /// Sell amount
    pub sell_amount: f64,
    /// Net amount
    pub net_amount: f64,
    /// Trade direction
    pub direction: String,
}

/// Earnings forecast data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EarningsForecast {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Forecast type
    pub forecast_type: String,
    /// Forecast net profit lower bound
    pub profit_min: Option<f64>,
    /// Forecast net profit upper bound
    pub profit_max: Option<f64>,
    /// YoY change lower bound
    pub change_min: Option<f64>,
    /// YoY change upper bound
    pub change_max: Option<f64>,
    /// Report period
    pub report_period: String,
    /// Announcement date
    pub announce_date: NaiveDate,
    /// Forecast summary
    pub summary: Option<String>,
}

/// Stock Connect flow data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StockConnectData {
    /// Trade date
    pub trade_date: NaiveDate,
    /// Northbound net buy
    pub north_net_buy: f64,
    /// Shanghai Connect net buy
    pub sh_net_buy: f64,
    /// Shenzhen Connect net buy
    pub sz_net_buy: f64,
    /// Northbound buy amount
    pub north_buy: f64,
    /// Northbound sell amount
    pub north_sell: f64,
}

/// Margin trading data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarginTradingData {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Trade date
    pub trade_date: NaiveDate,
    /// Margin balance
    pub margin_balance: f64,
    /// Margin buy amount
    pub margin_buy: f64,
    /// Margin repay amount
    pub margin_repay: f64,
    /// Short selling balance
    pub short_balance: f64,
    /// Short selling volume
    pub short_volume: u64,
    /// Total balance
    pub total_balance: f64,
}

/// IPO information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IPOData {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Issue price
    pub issue_price: f64,
    /// Subscription date
    pub sub_date: NaiveDate,
    /// Listing date
    pub list_date: Option<NaiveDate>,
    /// Winning rate
    pub winning_rate: Option<f64>,
    /// Issue quantity
    pub issue_quantity: Option<u64>,
    /// Online issue quantity
    pub online_quantity: Option<u64>,
    /// PE ratio
    pub pe_ratio: Option<f64>,
}

/// Block trade data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockTradeData {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Trade date
    pub trade_date: NaiveDate,
    /// Trade price
    pub price: f64,
    /// Close price
    pub close_price: f64,
    /// Premium rate
    pub premium_rate: f64,
    /// Trade volume
    pub volume: u64,
    /// Trade amount
    pub amount: f64,
    /// Buyer broker
    pub buyer: String,
    /// Seller broker
    pub seller: String,
}

/// Institutional research data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstitutionalResearchData {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Research date
    pub research_date: NaiveDate,
    /// Institution count
    pub institution_count: u32,
    /// Institution names
    pub institutions: String,
    /// Research type
    pub research_type: String,
    /// Researchers
    pub researchers: Option<String>,
}

/// Research report data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResearchReportData {
    /// Report ID
    pub report_id: String,
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Report title
    pub title: String,
    /// Institution name
    pub institution: String,
    /// Analyst names
    pub analysts: String,
    /// Rating
    pub rating: Option<String>,
    /// Publish date
    pub publish_date: NaiveDate,
}

/// Stock valuation metrics.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StockValuation {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Current price
    pub price: f64,
    /// Market capitalization
    pub market_cap: f64,
    /// Circulating market cap
    pub float_cap: f64,
    /// PE ratio (TTM)
    pub pe_ttm: Option<f64>,
    /// PE ratio (static)
    pub pe_static: Option<f64>,
    /// PB ratio
    pub pb: Option<f64>,
    /// PS ratio
    pub ps: Option<f64>,
    /// EPS
    pub eps: Option<f64>,
    /// BPS
    pub bps: Option<f64>,
    /// ROE
    pub roe: Option<f64>,
    /// Gross margin
    pub gross_margin: Option<f64>,
    /// Net margin
    pub net_margin: Option<f64>,
    /// Revenue
    pub revenue: Option<f64>,
    /// Net profit
    pub net_profit: Option<f64>,
    /// Revenue YoY growth
    pub revenue_yoy: Option<f64>,
    /// Net profit YoY growth
    pub profit_yoy: Option<f64>,
}

/// Top shareholder data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopHolder {
    /// Stock code
    pub stock_code: String,
    /// Report date
    pub report_date: NaiveDate,
    /// Holder rank
    pub rank: u32,
    /// Holder name
    pub holder_name: String,
    /// Hold quantity
    pub hold_quantity: u64,
    /// Hold ratio
    pub hold_ratio: f64,
    /// Change quantity
    pub change_quantity: Option<i64>,
    /// Holder type
    pub holder_type: String,
}

/// Fund holding data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FundHolding {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Report date
    pub report_date: NaiveDate,
    /// Fund name
    pub fund_name: String,
    /// Holding shares
    pub hold_shares: u64,
    /// Holding ratio
    pub hold_ratio: f64,
}

/// Dividend history data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DividendData {
    /// Stock code
    pub stock_code: String,
    /// Stock name
    pub stock_name: String,
    /// Report date
    pub report_date: NaiveDate,
    /// Ex-dividend date
    pub ex_date: Option<NaiveDate>,
    /// Record date
    pub record_date: Option<NaiveDate>,
    /// Cash dividend per share
    pub dividend_per_share: f64,
    /// Bonus shares per 10 shares
    pub bonus_shares: f64,
    /// Transfer shares per 10 shares
    pub transfer_shares: f64,
    /// Dividend yield
    pub dividend_yield: Option<f64>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_exchange_from_code() {
        assert_eq!(Exchange::from_stock_code("600000"), Exchange::SH);
        assert_eq!(Exchange::from_stock_code("000001"), Exchange::SZ);
        assert_eq!(Exchange::from_stock_code("300001"), Exchange::SZ);
        assert_eq!(Exchange::from_stock_code("830001"), Exchange::BJ);
    }

    #[test]
    fn test_stock_code_full_symbol() {
        let stock = StockCode::new("600519".to_string(), "贵州茅台".to_string(), Exchange::SH);
        assert_eq!(stock.full_symbol(), "sh600519");
    }

    #[test]
    fn test_kline_type_api_value() {
        assert_eq!(KLineType::Daily.to_api_value(), 101);
        assert_eq!(KLineType::Min5.to_api_value(), 5);
    }
}