Skip to main content

futu_cache/
event_contract.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use arc_swap::ArcSwap;
6use dashmap::DashMap;
7use futu_domain_qot_event_contract::{
8    Category, CategoryGeneration, Competition, ContractExtra, EventExtra, MetadataGeneration,
9    MilestoneExtra, RelatedEvent, Scope, SecurityExtra, SecurityMetadata, SeriesExtra,
10    SubContractRelation, Tag,
11};
12use futu_proto_internal::event_contract::EventContract;
13use futu_proto_internal::ft_string_define::LocaleString;
14use futu_proto_internal::quote_event_contract_svr::{
15    GetCompetitionMixListResponse, GetEventCategoryResponse, GetMvcInfoResponse,
16};
17use futu_proto_internal::stock_information_svr::{
18    GetEventContractExtraInfoRsp, GetEventContractRelationRsp, SecuritiesRsp,
19};
20use parking_lot::Mutex;
21use prost::Message;
22
23mod catalog;
24mod metadata;
25mod runtime;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum EventContractCacheError {
29    Decode(String),
30    Validation(&'static str),
31}
32
33#[derive(Debug, Clone, Copy, Default, PartialEq)]
34pub struct EventContractOrderBookLevel {
35    pub price: f64,
36    pub size: f64,
37}
38
39#[derive(Debug, Clone, Default, PartialEq)]
40pub struct EventContractOrderBook {
41    pub yes_bids: Vec<EventContractOrderBookLevel>,
42    pub yes_asks: Vec<EventContractOrderBookLevel>,
43    pub no_bids: Vec<EventContractOrderBookLevel>,
44    pub no_asks: Vec<EventContractOrderBookLevel>,
45}
46
47#[derive(Debug, Clone, Default, PartialEq)]
48pub struct EventContractOrderBookGeneration {
49    pub generation: u64,
50    pub book: EventContractOrderBook,
51    yes_generation: Option<u64>,
52    no_generation: Option<u64>,
53}
54
55impl EventContractOrderBookGeneration {
56    #[must_use]
57    pub fn is_complete(&self) -> bool {
58        self.yes_generation.is_some() && self.no_generation.is_some()
59    }
60}
61
62#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
63pub struct EventContractMvcEventRule {
64    pub event_id: u64,
65    pub yes_only: bool,
66    pub min_contract_size: i32,
67    pub max_contract_size: i32,
68}
69
70#[derive(Debug, Clone, Default, PartialEq, Eq)]
71pub struct EventContractMvcRule {
72    pub min_contract_size: i32,
73    pub max_contract_size: i32,
74    pub events: Vec<EventContractMvcEventRule>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78struct EventContractRfqQuoteEntry {
79    written_at: Instant,
80    legs: Vec<(u64, i32)>,
81    combo_exchange: String,
82    combo_origin_symbol: String,
83}
84
85pub type EventContractRfqQuoteState = (Instant, Vec<(u64, i32)>, String, String);
86
87// Ref: frozen C++ aec0f6cda1
88// APIServer_Trd_PlaceComboOrder.cpp:19-40,401-420. This local monotonic
89// validity window is a fixed C++ consumer contract, not server configuration.
90const RFQ_QUOTE_VALIDITY: Duration = Duration::from_millis(5_000);
91const RFQ_QUOTE_STALE_AFTER: Duration = Duration::from_millis(60_000);
92
93pub struct EventContractCache {
94    category: ArcSwap<CategoryGeneration>,
95    metadata: ArcSwap<MetadataGeneration>,
96    mvc_rules: ArcSwap<BTreeMap<String, EventContractMvcRule>>,
97    rfq_quotes: ArcSwap<BTreeMap<String, EventContractRfqQuoteEntry>>,
98    order_books: DashMap<u64, Arc<EventContractOrderBookGeneration>>,
99    category_writer: Mutex<()>,
100    metadata_writer: Mutex<()>,
101    mvc_writer: Mutex<()>,
102    rfq_quote_writer: Mutex<()>,
103}
104
105impl EventContractCache {
106    #[must_use]
107    pub fn new() -> Arc<Self> {
108        Arc::new(Self {
109            category: ArcSwap::from_pointee(CategoryGeneration::default()),
110            metadata: ArcSwap::from_pointee(MetadataGeneration::default()),
111            mvc_rules: ArcSwap::from_pointee(BTreeMap::new()),
112            rfq_quotes: ArcSwap::from_pointee(BTreeMap::new()),
113            order_books: DashMap::new(),
114            category_writer: Mutex::new(()),
115            metadata_writer: Mutex::new(()),
116            mvc_writer: Mutex::new(()),
117            rfq_quote_writer: Mutex::new(()),
118        })
119    }
120
121    #[must_use]
122    pub fn category_snapshot(&self) -> Arc<CategoryGeneration> {
123        self.category.load_full()
124    }
125
126    #[must_use]
127    pub fn metadata_snapshot(&self) -> Arc<MetadataGeneration> {
128        self.metadata.load_full()
129    }
130}
131
132impl Default for EventContractCache {
133    fn default() -> Self {
134        Self {
135            category: ArcSwap::from_pointee(CategoryGeneration::default()),
136            metadata: ArcSwap::from_pointee(MetadataGeneration::default()),
137            mvc_rules: ArcSwap::from_pointee(BTreeMap::new()),
138            rfq_quotes: ArcSwap::from_pointee(BTreeMap::new()),
139            order_books: DashMap::new(),
140            category_writer: Mutex::new(()),
141            metadata_writer: Mutex::new(()),
142            mvc_writer: Mutex::new(()),
143            rfq_quote_writer: Mutex::new(()),
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests;