databento 0.56.0

Official Databento client library
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
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
//! The corporate actions API.

use std::{collections::HashMap, fmt::Display};

use dbn::{Compression, SType};
use serde::Deserialize;
use time::{Date, OffsetDateTime};
use tracing::instrument;

use crate::{
    deserialize::{deserialize_date_time, deserialize_opt_date_time_hash_map},
    historical::{handle_response, handle_zstd_jsonl_response, AddToForm, ReqwestForm},
    reference::{
        Action, Country, Currency, End, Event, EventCategory, EventLevel, EventSubType, FieldGroup,
        Fraction, GlobalStatus, ListingSource, ListingStatus, MandVolu, OutturnStyle, PaymentType,
        SecurityType, Start,
    },
    DateTimeLike, Symbols,
};

/// A client for the corporate actions group of Reference API endpoints.
#[derive(Debug)]
pub struct CorporateActionsClient<'a> {
    pub(crate) inner: &'a mut super::Client,
}

impl CorporateActionsClient<'_> {
    /// Requests a new corporate actions time series from Databento.
    ///
    /// # Errors
    /// This function returns an error when it fails to communicate with the Databento API
    /// or the API indicates there's an issue with the request.
    #[instrument(name = "corporate_actions.get_range")]
    pub async fn get_range(
        &mut self,
        params: &GetRangeParams,
    ) -> crate::Result<Vec<CorporateAction>> {
        let form = vec![
            ("index", params.index.to_string()),
            ("stype_in", params.stype_in.to_string()),
            ("symbols", params.symbols.to_api_string()),
            ("compression", Compression::Zstd.to_string()),
        ]
        .add_to_form(&Start(params.start))
        .add_to_form(&End(params.end))
        .add_to_form(&params.events)
        .add_to_form(&params.countries)
        .add_to_form(&Exchanges(&params.exchanges))
        .add_to_form(&params.security_types);

        let resp = self
            .inner
            .post("corporate_actions.get_range")?
            .form(&form)
            .send()
            .await?;
        let mut corporate_actions: Vec<CorporateAction> = handle_zstd_jsonl_response(resp).await?;
        match params.index {
            Index::EventDate => corporate_actions.sort_by_key(|a| a.event_date),
            Index::ExDate => corporate_actions.sort_by_key(|a| a.ex_date),
            Index::TsRecord => corporate_actions.sort_by_key(|a| a.ts_record),
        };
        Ok(corporate_actions)
    }

    /// Requests documentation for all supported corporate action events, keyed by
    /// event code as a `String` (e.g. `AGM`).
    ///
    /// # Errors
    /// This function returns an error when it fails to communicate with the Databento API
    /// or the API indicates there's an issue with the request.
    #[instrument(name = "corporate_actions.list_events")]
    pub async fn list_events(&mut self) -> crate::Result<HashMap<String, EventDoc>> {
        let resp = self
            .inner
            .get("corporate_actions.list_events")?
            .send()
            .await?;
        handle_response(resp).await
    }

    /// Requests documentation for the full set of enumerations used across corporate
    /// actions data, keyed by enum group name (e.g. `ACTION`).
    ///
    /// # Errors
    /// This function returns an error when it fails to communicate with the Databento API
    /// or the API indicates there's an issue with the request.
    #[instrument(name = "corporate_actions.list_enums")]
    pub async fn list_enums(&mut self) -> crate::Result<HashMap<String, Vec<EventEnumVariant>>> {
        let resp = self
            .inner
            .get("corporate_actions.list_enums")?
            .send()
            .await?;
        handle_response(resp).await
    }
}

struct Exchanges<'a>(&'a Vec<String>);

impl<'a> AddToForm<Exchanges<'a>> for ReqwestForm {
    fn add_to_form(mut self, Exchanges(exchanges): &Exchanges) -> Self {
        if !exchanges.is_empty() {
            self.push(("exchanges", exchanges.join(",")));
        }
        self
    }
}

/// Which field to use for filtering and sorting.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Index {
    /// [`event_date`][CorporateAction::event_date].
    #[default]
    EventDate,
    /// [`ex_date`][CorporateAction::ex_date].
    ExDate,
    /// [`ts_record`][CorporateAction::ts_record].
    TsRecord,
}

/// The parameters for [`CorporateActionsClient::get_range()`]. Use
/// [`GetRangeParams::builder()`] to get a builder type with all the preset defaults.
#[derive(Debug, Clone, bon::Builder, PartialEq, Eq)]
pub struct GetRangeParams {
    /// The inclusive start time of the request range. Filters on `index`.
    #[builder(with = |dt: impl DateTimeLike| dt.to_date_time())]
    pub start: OffsetDateTime,
    /// The exclusive end time of the request range. Filters on `index`.
    ///
    /// If `None`, all data after `start` will be included in the response.
    #[builder(with = |dt: impl DateTimeLike| dt.to_date_time())]
    pub end: Option<OffsetDateTime>,
    /// The timestamp to use for filtering.
    #[builder(default)]
    pub index: Index,
    /// The symbols to filter for.
    #[builder(into)]
    pub symbols: Symbols,
    /// The symbology type of the input `symbols`. Defaults to
    /// [`RawSymbol`](SType::RawSymbol).
    #[builder(default = SType::RawSymbol)]
    pub stype_in: SType,
    /// An optional list of event types to filter for. By default all events are included.
    #[builder(default, into)]
    pub events: Vec<Event>,
    /// An optional list of country codes to filter for. By default all countries are
    /// included.
    #[builder(default, into)]
    pub countries: Vec<Country>,
    /// An optional list of listing exchanges to filter for. By default all exchanges are
    /// included.
    #[builder(default, into)]
    pub exchanges: Vec<String>,
    /// An optional list of security types to filter for. By default all security types
    /// are included.
    #[builder(default, into)]
    pub security_types: Vec<SecurityType>,
}

/// A record in the corporate actions response.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct CorporateAction {
    /*
     * Identifiers
     */
    /// The timestamp (UTC) the record last changed.
    #[serde(deserialize_with = "deserialize_date_time")]
    pub ts_record: OffsetDateTime,
    /// Unique corporate actions record identifier. Can be used to deduplicate records
    /// for the same event.
    pub event_unique_id: String,
    /// Event identifier unique at the event level.
    pub event_id: String,
    /// Unique listing numerical ID. Concatenation of a sequence number and the
    /// `listing_group_id`.
    pub listing_id: String,
    /// Groups all listings for the same security on a specific exchange, often in
    /// different trading currencies.
    pub listing_group_id: String,
    /// Security level numerical ID. Can be used to link all multiple listings together.
    pub security_id: String,
    /// Issuer level numerical ID. Can be used to link all securities of a company
    /// together.
    pub issuer_id: String,

    /*
     * Event / dates
     */
    /// Record action status code.
    pub event_action: Action,
    /// Event type. The event_id where applicable links all payments rows of an event
    /// together to show all the payment options.
    pub event: Event,
    /// Event subtype. This is only used for a limited number of events where the event
    /// data supplied falls into distinct sub-groupings.
    pub event_subtype: Option<EventSubType>,
    /// The main calendar date name for the event.
    pub event_date_label: String,
    /// The primary date associated with the event, often marking when the event is
    /// scheduled to occur or take effect.
    pub event_date: Option<Date>,
    /// The date on which the event was officially created or recorded in the system.
    pub event_created_date: Date,
    /// The date on which the event becomes effective or is executed, signifying when
    /// the changes or actions are officially recognized.
    pub effective_date: Option<Date>,
    /// The ex-dividend date.
    pub ex_date: Option<Date>,
    /// The date on which a company reviews its records to determine the eligible
    /// shareholders entitled to receive dividends or participate in the event. The
    /// record date is one business day after the ex-date.
    pub record_date: Option<Date>,
    /// Record date ID. This ID links all events for the same security that share the
    /// same record date.
    pub record_date_id: Option<String>,
    /// Related event type.
    pub related_event: Option<Event>,
    /// Direct link to another event.
    pub related_event_id: Option<String>,

    /*
     * Listing
     */
    /// Global status code. Indicates the global listing activity status of a security.
    pub global_status: GlobalStatus,
    /// Listing status code. Indicates the listing activity status at market level.
    pub listing_status: ListingStatus,
    /// ndicates if the listing level data in the record.
    pub listing_source: ListingSource,
    /// The date when the security is officially listed on the exchange and becomes
    /// available for trading.
    pub listing_date: Option<Date>,
    /// The date when the security is officially removed from the exchange and ceases to
    /// be traded.
    pub delisting_date: Option<Date>,

    /*
     * Exchange/issuer
     */
    /// The issuer name.
    pub issuer_name: String,
    /// The security type.
    pub security_type: Option<SecurityType>,
    /// The security description.
    pub security_description: String,
    /// Exchange code for the primary security.
    pub primary_exchange: Option<String>,
    /// Exchange code for the listing.
    pub exchange: String,
    /// Market Identifier Code (MIC) as an ISO 10383 string.
    pub operating_mic: Option<String>,

    /*
     * Symbology
     */
    /// The query input symbol which matched the record.
    pub symbol: Option<String>,
    /// Nasdaq Integrated Platform Suffix convention symbol.
    pub nasdaq_symbol: Option<String>,
    /// Local Code. Usually unique at market level but there are exceptions to this
    /// rule. Either an alpha string, or a number.
    pub local_code: Option<String>,
    /// ISIN global level identifier as an ISO 6166 string.
    pub isin: Option<String>,
    /// US domestic CUSIP.
    pub us_code: Option<String>,
    /// Bloomberg composite global ID.
    pub bbg_comp_id: Option<String>,
    /// Bloomberg composite ticker.
    pub bbg_comp_ticker: Option<String>,
    /// Bloomberg FIGI, that is exchange level ID.
    pub figi: Option<String>,
    /// Bloomberg exchange level ticker.
    pub figi_ticker: Option<String>,

    /*
     * Country
     */
    /// Listing country.
    pub listing_country: Option<Country>,
    /// Register country.
    pub register_country: Option<Country>,
    /// Trading currency.
    pub trading_currency: Option<Currency>,
    /// `true` if there is currently more than one listing in the market.
    pub multi_currency: bool,
    /// Market Segment Name.
    pub segment_mic_name: Option<String>,
    /// Market Identifier Code (MIC) as an ISO 10383 string.
    pub segment_mic: Option<String>,

    /*
     * Event / financials
     */
    /// Indicates mandatory and/or voluntary event participation requirement.
    pub mand_volu_flag: MandVolu,
    /// The priority order number sequence where populated is necessary to correctly
    /// calculate resultant cash and stock outcomes.
    pub rd_priority: Option<u32>,
    /// Lot Size. Indicates the minimum number of shares that can be acquired in one
    /// transaction.
    pub lot_size: Option<u32>,
    /// Par value amount.
    pub par_value: Option<f64>,
    /// Par value currency.
    pub par_value_currency: Option<Currency>,
    /// The date on which the dividend or payment is made to eligible shareholders.
    pub payment_date: Option<Date>,
    /// Due bills redemption date.
    pub duebills_redemption_date: Option<Date>,
    /// The earliest date from which a specific corporate action or event is valid,
    /// active, or can be exercised.
    pub from_date: Option<Date>,
    /// The final date by which the specific corporate action or event is valid, active,
    /// or must be completed.
    pub to_date: Option<Date>,
    /// Registration date.
    pub registration_date: Option<Date>,
    /// The date on which the specific corporate action or event begins or becomes
    /// effective.
    pub start_date: Option<Date>,
    /// The final date by which the corporate action or event is valid, active, or must
    /// be completed.
    pub end_date: Option<Date>,
    /// The date when the corporate action or event opens for participation or becomes
    /// available to stakeholders.
    pub open_date: Option<Date>,
    /// The final date by which participation in the corporate action or event must
    /// be completed.
    pub close_date: Option<Date>,
    /// The date when the subscription period for the event begins.
    pub start_subscription_date: Option<Date>,
    /// The date when the subscription period for the event ends.
    pub end_subscription_date: Option<Date>,
    /// The deadline by which participants must choose or elect an option related to the event.
    pub option_election_date: Option<Date>,
    /// The date from which withdrawal rights become effective, allowing participants to
    /// retract their election or participation.
    pub withdrawal_rights_from_date: Option<Date>,
    /// The final date by which participants can exercise their withdrawal rights,
    /// retracting their election or participation.
    pub withdrawal_rights_to_date: Option<Date>,
    /// The date on which the event notification is issued or made public.
    pub notification_date: Option<Date>,
    /// The closing date of the company's financial year.
    pub financial_year_end_date: Option<Date>,
    /// The anticipated date when the event or related transaction is expected to be
    /// completed.
    pub exp_completion_date: Option<Date>,
    /// Payment type.
    pub payment_type: Option<PaymentType>,
    /// Option number of the event where applicable - options are ORs.
    pub option_id: Option<String>,
    /// Serial number of the event where applicable - serials are ANDs.
    pub serial_id: Option<String>,
    /// Flag indicating the benefit the shareholder would receive by default in case of
    /// several options being offered to them.
    pub default_option_flag: Option<bool>,
    /// Payment currency.
    pub rate_currency: Option<Currency>,
    /// Ratio denominator is the existing holding.
    pub ratio_old: Option<f64>,
    /// Ratio numerator is the new holding.
    pub ratio_new: Option<f64>,
    /// Describes how fractions are handled in settlement calculations.
    pub fraction: Option<Fraction>,
    /// Style of outturn security.
    pub outturn_style: Option<OutturnStyle>,
    /// Security asset type.
    pub outturn_security_type: Option<SecurityType>,
    /// Outturn security.
    pub outturn_security_id: Option<String>,
    /// Outturn ISIN.
    pub outturn_isin: Option<String>,
    /// Outturn CUSIP.
    pub outturn_us_code: Option<String>,
    /// Outturn local code.
    pub outturn_local_code: Option<String>,
    /// Outturn Bloomberg composite ID.
    pub outturn_bbg_comp_id: Option<String>,
    /// Outturn Bloomberg composite ticker.
    pub outturn_bbg_comp_ticker: Option<String>,
    /// Outturn FIGI - Bloomberg exchange level ID.
    pub outturn_figi: Option<String>,
    /// Outturn FIGI - Bloomberg exchange level ticker.
    pub outturn_figi_ticker: Option<String>,
    /// The quantity range within which the shareholder can offer from their total
    /// holding in the event.
    pub min_offer_qty: Option<u64>,
    /// The quantity range within which the shareholder can offer from their total
    /// holding in the event.
    pub max_offer_qty: Option<u64>,
    /// If the quantity held by shareholder is within this range then they qualify for
    /// taking part in the event.
    pub min_qualify_qty: Option<u64>,
    /// If the quantity held by shareholder is within this range then they qualify for
    /// taking part in the event.
    pub max_qualify_qty: Option<u64>,
    /// The total quantity the company will accept from all the shareholders tendering
    /// their shares in the event for the event to be binding on the offeror.
    pub min_accept_qty: Option<u64>,
    /// The total quantity the company will accept from all the shareholders tendering
    /// their shares in the event for the event to be binding on the offeror.
    pub max_accept_qty: Option<u64>,
    /// If the event is via a tender process it is the cut off price at which all the
    /// bids are accepted.
    pub tender_strike_price: Option<f64>,
    /// If the event is via a tender then there is a price step in which one can put in
    /// the bids.
    pub tender_price_step: Option<f64>,
    /// Option expiry time.
    pub option_expiry_time: Option<String>,
    /// Option expiry time zone.
    pub option_expiry_tz: Option<String>,
    /// Withdrawal rights flag.
    pub withdrawal_rights_flag: Option<bool>,
    /// Withdrawal rights expiry time .
    pub withdrawal_rights_expiry_time: Option<String>,
    /// Withdrawal rights expiry time zone.
    pub withdrawal_rights_expiry_tz: Option<String>,
    /// Expiry time.
    pub expiry_time: Option<String>,
    /// Expiry time zone.
    pub expiry_tz: Option<String>,
    /// Event-specific date information.
    #[serde(deserialize_with = "deserialize_opt_date_time_hash_map")]
    pub date_info: HashMap<String, Option<OffsetDateTime>>,
    /// Event-specific payment information.
    pub rate_info: HashMap<String, Option<f64>>,
    /// Additional event-specific information.
    pub event_info: HashMap<String, Option<String>>,
    /// The timestamp (UTC) the record was added by Databento.
    #[serde(deserialize_with = "deserialize_date_time")]
    pub ts_created: OffsetDateTime,
}

impl Display for Index {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Index::EventDate => write!(f, "event_date"),
            Index::ExDate => write!(f, "ex_date"),
            Index::TsRecord => write!(f, "ts_record"),
        }
    }
}

/// Corporate Actions Event docs calendar date entry.
/// Designates the important calendar dates for a corporate actions event.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct EventDocCalendarDates {
    /// Alias of the calendar date under the given name e.g. (meeting_date in place of event_date).
    pub alias: Option<String>,
    /// Name of the calendar date (e.g. event_date).
    pub name: String,
}

/// Corporate Action Events docs event subtype entry.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct EventDocSubType {
    /// Code for the event subtype.
    pub code: Option<EventSubType>,
    /// Description of the event subtype.
    pub description: String,
}

/// Corporate action events doc fields.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct EventDocField {
    /// Description of the field.
    pub description: String,
    /// Group field will belong to (event_info, date_info, rate_info).
    pub group: FieldGroup,
    /// Name of field.
    pub name: String,
}

/// A record in the corporate actions event documentation response.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct EventDoc {
    /// The calendar dates relevant to the event.
    pub calendar_dates: Option<Vec<EventDocCalendarDates>>,
    /// The category of the event (e.g. distribution, proposals).
    pub category: Option<EventCategory>,
    /// The code for the event (e.g. AGM).
    pub code: Option<Event>,
    /// Description of the event.
    pub description: Option<String>,
    /// The fields relevant to the event.
    pub fields: Option<Vec<EventDocField>>,
    /// The level the event applies to (e.g. issuer, security).
    pub level: EventLevel,
    /// Name of the event.
    pub name: String,
    /// Whether the event is mandatory, voluntary, or a mix of the two.
    pub participation: Option<String>,
    /// The subtypes relevant to the event.
    pub subtypes: Option<Vec<EventDocSubType>>,
}

/// A single code/description variant of a corporate action enum.
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct EventEnumVariant {
    /// Code for the enum variant.
    pub code: Option<String>,
    /// Description of the enum variant.
    pub description: String,
}

#[cfg(test)]
mod tests {
    use std::io;

    use reqwest::StatusCode;
    use time::macros::{date, datetime};
    use wiremock::{
        matchers::{basic_auth, method, path},
        Mock, MockServer, ResponseTemplate,
    };

    use super::*;
    use crate::{
        body_contains,
        historical::{test_infra::API_KEY, API_VERSION},
        reference::test_infra::client,
    };

    #[tokio::test]
    async fn test_get_range() {
        let _ = tracing_subscriber::FmtSubscriber::builder()
            .with_test_writer()
            .try_init();
        let start = datetime!(2023- 10 - 10 00:00 UTC);

        let bytes = zstd::encode_all(
            io::Cursor::new(concat!(
                r#"{"ts_record": "2023-10-10T03:37:14Z","#,
                r#""event_unique_id": "U-40179751345-16556634","#,
                r#""event_id": "E-9751345-RSPLT","#,
                r#""listing_id": "L-16556634","#,
                r#""listing_group_id": "LG-6556634","#,
                r#""security_id": "S-4633970","#,
                r#""issuer_id": "I-175515","#,
                r#""event_action": "U","#,
                r#""event": "RSPLT","#,
                r#""event_subtype": "CONSD","#,
                r#""event_date_label": "ex_date","#,
                r#""event_date": "2023-10-10","#,
                r#""event_created_date": "1929-09-30","#,
                r#""effective_date": null,"#,
                r#""ex_date": "2023-10-10","#,
                r#""record_date": null,"#,
                r#""record_date_id": "D-9751345","#,
                r#""related_event": null,"#,
                r#""related_event_id": null,"#,
                r#""global_status": "A","#,
                r#""listing_status": "L","#,
                r#""listing_source": "M","#,
                r#""listing_date": "2015-10-29","#,
                r#""delisting_date": null,"#,
                r#""issuer_name": "Borqs Technologies Inc","#,
                r#""security_type": "EQS","#,
                r#""security_description": "Ordinary Shares","#,
                r#""primary_exchange": "USNASD","#,
                r#""exchange": "USNASD","#,
                r#""operating_mic": "XNAS","#,
                r#""symbol": "BRQS","#,
                r#""nasdaq_symbol": "BRQS","#,
                r#""local_code": "BRQS","#,
                r#""isin": "VGG1466B1452","#,
                r#""us_code": "G1466B145","#,
                r#""bbg_comp_id": "BBG00B9RG1J6","#,
                r#""bbg_comp_ticker": "BRQS US","#,
                r#""figi": "BBG00B9RG1W1","#,
                r#""figi_ticker": "BRQS UR","#,
                r#""listing_country": "US","#,
                r#""register_country": "VG","#,
                r#""trading_currency": "USD","#,
                r#""multi_currency": false,"#,
                r#""segment_mic_name": "Capital Market","#,
                r#""segment_mic": "XNCM","#,
                r#""mand_volu_flag": "M","#,
                r#""rd_priority": 1,"#,
                r#""lot_size": 100,"#,
                r#""par_value": null,"#,
                r#""par_value_currency": "USD","#,
                r#""payment_date": null,"#,
                r#""duebills_redemption_date": null,"#,
                r#""from_date": null,"#,
                r#""to_date": null,"#,
                r#""registration_date": null,"#,
                r#""start_date": null,"#,
                r#""end_date": null,"#,
                r#""open_date": null,"#,
                r#""close_date": null,"#,
                r#""start_subscription_date": null,"#,
                r#""end_subscription_date": null,"#,
                r#""option_election_date": null,"#,
                r#""withdrawal_rights_from_date": null,"#,
                r#""withdrawal_rights_to_date": null,"#,
                r#""notification_date": null,"#,
                r#""financial_year_end_date": null,"#,
                r#""exp_completion_date": null,"#,
                r#""payment_type": "S","#,
                r#""option_id": "1","#,
                r#""serial_id": "1","#,
                r#""default_option_flag": true,"#,
                r#""rate_currency": "USD","#,
                r#""ratio_old": 12.0,"#,
                r#""ratio_new": 1.0,"#,
                r#""fraction": "U","#,
                r#""outturn_style": "NEWO","#,
                r#""outturn_security_type": "EQS","#,
                r#""outturn_security_id": "S-4633970","#,
                r#""outturn_isin": "VGG1466B1452","#,
                r#""outturn_us_code": "G1466B145","#,
                r#""outturn_local_code": "BRQS","#,
                r#""outturn_bbg_comp_id": "BBG00B9RG1J6","#,
                r#""outturn_bbg_comp_ticker": "BRQS US","#,
                r#""outturn_figi": "BBG00B9RG1W1","#,
                r#""outturn_figi_ticker": "BRQS UR","#,
                r#""min_offer_qty": null,"#,
                r#""max_offer_qty": null,"#,
                r#""min_qualify_qty": null,"#,
                r#""max_qualify_qty": null,"#,
                r#""min_accept_qty": null,"#,
                r#""max_accept_qty": null,"#,
                r#""tender_strike_price": null,"#,
                r#""tender_price_step": null,"#,
                r#""option_expiry_time": null,"#,
                r#""option_expiry_tz": null,"#,
                r#""withdrawal_rights_flag": null,"#,
                r#""withdrawal_rights_expiry_time": null,"#,
                r#""withdrawal_rights_expiry_tz": null,"#,
                r#""expiry_time": null,"#,
                r#""expiry_tz": null,"#,
                r#""date_info": {},"#,
                r#""rate_info": {"par_value_old": null, "par_value_new": null},"#,
                r#""event_info": {},"#,
                r#""ts_created": "1970-01-01T00:00:00.000000000Z"}
"#,
            )),
            0,
        )
        .unwrap();

        let mock_server = MockServer::start().await;
        Mock::given(method("POST"))
            .and(basic_auth(API_KEY, ""))
            .and(path(format!("/v{API_VERSION}/corporate_actions.get_range")))
            .and(body_contains("index", "ts_record"))
            .and(body_contains("stype_in", "raw_symbol"))
            .and(body_contains("symbols", "MSFT"))
            .and(body_contains("events", "SHOCH"))
            .and(body_contains("countries", "US%2CCA"))
            .and(body_contains(
                "start",
                start.unix_timestamp_nanos().to_string(),
            ))
            .respond_with(ResponseTemplate::new(StatusCode::OK.as_u16()).set_body_bytes(bytes))
            .mount(&mock_server)
            .await;

        let mut client = client(&mock_server);
        let res = client
            .corporate_actions()
            .get_range(
                &GetRangeParams::builder()
                    .start(start)
                    .events([Event::Shoch])
                    .countries([Country::Us, Country::Ca])
                    .index(Index::TsRecord)
                    .symbols("MSFT")
                    .build(),
            )
            .await
            .unwrap();
        assert_eq!(res.len(), 1);
        let res = &res[0];
        assert_eq!(res.event, Event::Rsplt);
        assert_eq!(res.event_action, Action::Updated);
        assert_eq!(res.global_status, GlobalStatus::Active);
        assert_eq!(res.listing_date, Some(date!(2015 - 10 - 29)));
        assert_eq!(res.security_type, Some(SecurityType::Eqs));
        assert_eq!(res.register_country, Some(Country::Vg));
        assert!(res.rate_info.contains_key("par_value_old"));
        assert!(res.rate_info.contains_key("par_value_new"));
    }

    #[test]
    fn test_deserialize_unknown_related_event() {
        let json = r#"{
            "ts_record": "2023-10-10T03:37:14Z",
            "event_unique_id": "U-123",
            "event_id": "E-123",
            "listing_id": "L-123",
            "listing_group_id": "LG-123",
            "security_id": "S-123",
            "issuer_id": "I-123",
            "event_action": "I",
            "event": "DIV",
            "event_date_label": "ex_date",
            "event_created_date": "2023-01-01",
            "related_event": "CORR",
            "global_status": "A",
            "listing_status": "L",
            "listing_source": "M",
            "issuer_name": "Test Inc",
            "security_description": "Test",
            "exchange": "TEST",
            "multi_currency": false,
            "mand_volu_flag": "M",
            "date_info": {},
            "rate_info": {},
            "event_info": {},
            "ts_created": "2023-01-01T00:00:00Z"
        }"#;

        let action: CorporateAction = serde_json::from_str(json).unwrap();
        assert_eq!(
            action.related_event,
            Some(Event::Unknown("CORR".to_owned()))
        );
    }

    #[tokio::test]
    async fn test_list_events() {
        let mock_server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(basic_auth(API_KEY, ""))
            .and(path(format!(
                "/v{API_VERSION}/corporate_actions.list_events"
            )))
            .respond_with(
                ResponseTemplate::new(StatusCode::OK.as_u16()).set_body_json(serde_json::json!({
                    "AGM": {
                        "calendar_dates": [
                            {"alias": "meeting_date", "name": "event_date"},
                            {"alias": null, "name": "record_date"},
                        ],
                        "category": "proposals",
                        "code": "AGM",
                        "description": "Annual General meeting of shareholders.",
                        "fields": [
                            {
                                "description": "Company Meeting Number",
                                "group": "event_info",
                                "name": "meeting_number",
                            },
                        ],
                        "level": "issuer",
                        "name": "Company Meeting",
                        "participation": "voluntary",
                        "subtypes": [
                            {"code": "AGM", "description": "Annual General Meeting"},
                        ],
                    },
                })),
            )
            .mount(&mock_server)
            .await;

        let mut client = client(&mock_server);
        let docs = client.corporate_actions().list_events().await.unwrap();
        assert_eq!(docs.len(), 1);
        let agm = docs.get("AGM").unwrap();
        assert_eq!(agm.name, "Company Meeting");
        assert_eq!(agm.level, EventLevel::Issuer);
        assert_eq!(agm.category, Some(EventCategory::Proposals));
        assert_eq!(agm.code, Some(Event::Agm));
        assert_eq!(agm.participation.as_deref(), Some("voluntary"));

        let calendar_dates = agm.calendar_dates.as_ref().unwrap();
        assert_eq!(calendar_dates.len(), 2);
        assert_eq!(calendar_dates[0].name, "event_date");
        assert_eq!(calendar_dates[0].alias.as_deref(), Some("meeting_date"));
        assert_eq!(calendar_dates[1].name, "record_date");
        assert_eq!(calendar_dates[1].alias, None);

        let fields = agm.fields.as_ref().unwrap();
        assert_eq!(fields.len(), 1);
        assert_eq!(fields[0].name, "meeting_number");
        assert_eq!(fields[0].group, FieldGroup::EventInfo);

        let subtypes = agm.subtypes.as_ref().unwrap();
        assert_eq!(subtypes.len(), 1);
        assert_eq!(subtypes[0].code, Some(EventSubType::Agm));
        assert_eq!(subtypes[0].description, "Annual General Meeting");
    }

    #[tokio::test]
    async fn test_list_enums() {
        let mock_server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(basic_auth(API_KEY, ""))
            .and(path(format!(
                "/v{API_VERSION}/corporate_actions.list_enums"
            )))
            .respond_with(
                ResponseTemplate::new(StatusCode::OK.as_u16()).set_body_json(serde_json::json!({
                    "ACTION": [
                        {"code": "C", "description": "Cancelled"},
                        {"code": "I", "description": "Inserted"},
                    ],
                })),
            )
            .mount(&mock_server)
            .await;

        let mut client = client(&mock_server);
        let enums = client.corporate_actions().list_enums().await.unwrap();
        assert_eq!(enums.len(), 1);
        let action = enums.get("ACTION").unwrap();
        assert_eq!(action.len(), 2);
        assert_eq!(action[0].code.as_deref(), Some("C"));
        assert_eq!(action[0].description, "Cancelled");
        assert_eq!(action[1].code.as_deref(), Some("I"));
        assert_eq!(action[1].description, "Inserted");
    }
}