webull_rs/models/
market.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4/// Real-time quote information.
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Quote {
7    /// Symbol of the security
8    pub symbol: String,
9
10    /// Last trade price
11    pub last_price: f64,
12
13    /// Change in price
14    pub change: f64,
15
16    /// Percentage change in price
17    pub change_percent: f64,
18
19    /// Volume of shares traded
20    pub volume: u64,
21
22    /// Average volume
23    pub average_volume: u64,
24
25    /// Bid price
26    pub bid_price: f64,
27
28    /// Bid size
29    pub bid_size: u64,
30
31    /// Ask price
32    pub ask_price: f64,
33
34    /// Ask size
35    pub ask_size: u64,
36
37    /// Day's high price
38    pub high: f64,
39
40    /// Day's low price
41    pub low: f64,
42
43    /// Opening price
44    pub open: f64,
45
46    /// Previous close price
47    pub prev_close: f64,
48
49    /// 52-week high price
50    pub fifty_two_week_high: f64,
51
52    /// 52-week low price
53    pub fifty_two_week_low: f64,
54
55    /// Market cap
56    pub market_cap: Option<f64>,
57
58    /// Price-to-earnings ratio
59    pub pe_ratio: Option<f64>,
60
61    /// Timestamp of the quote
62    pub timestamp: DateTime<Utc>,
63}
64
65/// Parameters for querying snapshot data.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SnapshotParams {
68    /// Symbols to query (comma-separated)
69    #[serde(rename = "symbols")]
70    pub symbols: String,
71
72    /// Security category (e.g., "STK" for stocks)
73    #[serde(rename = "category")]
74    pub category: String,
75}
76
77impl SnapshotParams {
78    /// Create new snapshot query parameters.
79    pub fn new(symbols: impl Into<String>, category: impl Into<String>) -> Self {
80        Self {
81            symbols: symbols.into(),
82            category: category.into(),
83        }
84    }
85
86    /// Create new snapshot query parameters for stocks.
87    pub fn new_stock(symbols: impl Into<String>) -> Self {
88        Self::new(symbols, "STK")
89    }
90
91    /// Create new snapshot query parameters for multiple stock symbols.
92    pub fn new_stocks(symbols: &[&str]) -> Self {
93        let symbols_str = symbols.join(",");
94        Self::new_stock(symbols_str)
95    }
96}
97
98/// Bar data for a security.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct Bar {
101    /// Symbol of the security
102    pub symbol: String,
103
104    /// Opening price
105    pub open: f64,
106
107    /// High price
108    pub high: f64,
109
110    /// Low price
111    pub low: f64,
112
113    /// Closing price
114    pub close: f64,
115
116    /// Volume of shares traded
117    pub volume: u64,
118
119    /// Timestamp of the bar
120    pub timestamp: DateTime<Utc>,
121}
122
123/// Time frame for bar data.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(rename_all = "lowercase")]
126pub enum TimeFrame {
127    /// 1-minute bars
128    #[serde(rename = "m1")]
129    Minute1,
130
131    /// 5-minute bars
132    #[serde(rename = "m5")]
133    Minute5,
134
135    /// 15-minute bars
136    #[serde(rename = "m15")]
137    Minute15,
138
139    /// 30-minute bars
140    #[serde(rename = "m30")]
141    Minute30,
142
143    /// 1-hour bars
144    #[serde(rename = "h1")]
145    Hour1,
146
147    /// 4-hour bars
148    #[serde(rename = "h4")]
149    Hour4,
150
151    /// Daily bars
152    #[serde(rename = "d1")]
153    Day1,
154
155    /// Weekly bars
156    #[serde(rename = "w1")]
157    Week1,
158
159    /// Monthly bars
160    #[serde(rename = "mo1")]
161    Month1,
162}
163
164/// Parameters for querying historical bar data.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct BarQueryParams {
167    /// Symbol to query
168    #[serde(rename = "symbol")]
169    pub symbol: String,
170
171    /// Security category (e.g., "STK" for stocks)
172    #[serde(rename = "category")]
173    pub category: String,
174
175    /// Time frame for the bars
176    #[serde(rename = "timespan")]
177    pub time_frame: TimeFrame,
178
179    /// Number of bars to return (max 1200)
180    #[serde(rename = "count")]
181    pub count: String,
182}
183
184impl BarQueryParams {
185    /// Create new bar query parameters.
186    pub fn new(
187        symbol: impl Into<String>,
188        category: impl Into<String>,
189        time_frame: TimeFrame,
190        count: u32,
191    ) -> Self {
192        Self {
193            symbol: symbol.into(),
194            category: category.into(),
195            time_frame,
196            count: count.to_string(),
197        }
198    }
199
200    /// Create new bar query parameters for stocks with default count of 200.
201    pub fn new_stock(symbol: impl Into<String>, time_frame: TimeFrame) -> Self {
202        Self::new(symbol, "STK", time_frame, 200)
203    }
204
205    /// Set the count of bars to return.
206    pub fn count(mut self, count: u32) -> Self {
207        self.count = count.to_string();
208        self
209    }
210}
211
212/// Option contract information.
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct OptionContract {
215    /// Symbol of the option contract
216    pub symbol: String,
217
218    /// Underlying symbol
219    pub underlying_symbol: String,
220
221    /// Strike price
222    pub strike_price: f64,
223
224    /// Expiration date
225    pub expiration_date: DateTime<Utc>,
226
227    /// Option type (call/put)
228    pub option_type: OptionType,
229
230    /// Last trade price
231    pub last_price: f64,
232
233    /// Change in price
234    pub change: f64,
235
236    /// Percentage change in price
237    pub change_percent: f64,
238
239    /// Volume of contracts traded
240    pub volume: u64,
241
242    /// Open interest
243    pub open_interest: u64,
244
245    /// Bid price
246    pub bid_price: f64,
247
248    /// Bid size
249    pub bid_size: u64,
250
251    /// Ask price
252    pub ask_price: f64,
253
254    /// Ask size
255    pub ask_size: u64,
256
257    /// Implied volatility
258    pub implied_volatility: f64,
259
260    /// Delta
261    pub delta: f64,
262
263    /// Gamma
264    pub gamma: f64,
265
266    /// Theta
267    pub theta: f64,
268
269    /// Vega
270    pub vega: f64,
271
272    /// Rho
273    pub rho: f64,
274}
275
276/// Type of option contract.
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(rename_all = "UPPERCASE")]
279pub enum OptionType {
280    /// Call option
281    Call,
282
283    /// Put option
284    Put,
285}
286
287/// Option chain information.
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct OptionChain {
290    /// Underlying symbol
291    pub underlying_symbol: String,
292
293    /// Expiration dates
294    pub expiration_dates: Vec<DateTime<Utc>>,
295
296    /// Strike prices
297    pub strike_prices: Vec<f64>,
298
299    /// Option contracts
300    pub contracts: Vec<OptionContract>,
301}
302
303/// Parameters for querying option chains.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct OptionChainQueryParams {
306    /// Underlying symbol
307    pub underlying_symbol: String,
308
309    /// Expiration date filter
310    pub expiration_date: Option<DateTime<Utc>>,
311
312    /// Strike price filter
313    pub strike_price: Option<f64>,
314
315    /// Option type filter
316    pub option_type: Option<OptionType>,
317}
318
319impl OptionChainQueryParams {
320    /// Create new option chain query parameters.
321    pub fn new(underlying_symbol: impl Into<String>) -> Self {
322        Self {
323            underlying_symbol: underlying_symbol.into(),
324            expiration_date: None,
325            strike_price: None,
326            option_type: None,
327        }
328    }
329
330    /// Set the expiration date filter.
331    pub fn expiration_date(mut self, expiration_date: DateTime<Utc>) -> Self {
332        self.expiration_date = Some(expiration_date);
333        self
334    }
335
336    /// Set the strike price filter.
337    pub fn strike_price(mut self, strike_price: f64) -> Self {
338        self.strike_price = Some(strike_price);
339        self
340    }
341
342    /// Set the option type filter.
343    pub fn option_type(mut self, option_type: OptionType) -> Self {
344        self.option_type = Some(option_type);
345        self
346    }
347}
348
349/// Market news article.
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct NewsArticle {
352    /// Article ID
353    pub id: String,
354
355    /// Article title
356    pub title: String,
357
358    /// Article summary
359    pub summary: String,
360
361    /// Article URL
362    pub url: String,
363
364    /// Article source
365    pub source: String,
366
367    /// Article publish date
368    pub publish_date: DateTime<Utc>,
369
370    /// Related symbols
371    pub symbols: Vec<String>,
372}
373
374/// Parameters for querying news articles.
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub struct NewsQueryParams {
377    /// Symbol filter
378    pub symbol: Option<String>,
379
380    /// Start date filter
381    pub start_date: Option<DateTime<Utc>>,
382
383    /// End date filter
384    pub end_date: Option<DateTime<Utc>>,
385
386    /// Maximum number of articles to return
387    pub limit: Option<u32>,
388}
389
390impl NewsQueryParams {
391    /// Create new news query parameters.
392    pub fn new() -> Self {
393        Self {
394            symbol: None,
395            start_date: None,
396            end_date: None,
397            limit: None,
398        }
399    }
400
401    /// Set the symbol filter.
402    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
403        self.symbol = Some(symbol.into());
404        self
405    }
406
407    /// Set the start date filter.
408    pub fn start_date(mut self, start_date: DateTime<Utc>) -> Self {
409        self.start_date = Some(start_date);
410        self
411    }
412
413    /// Set the end date filter.
414    pub fn end_date(mut self, end_date: DateTime<Utc>) -> Self {
415        self.end_date = Some(end_date);
416        self
417    }
418
419    /// Set the limit.
420    pub fn limit(mut self, limit: u32) -> Self {
421        self.limit = Some(limit);
422        self
423    }
424}
425
426impl Default for NewsQueryParams {
427    fn default() -> Self {
428        Self::new()
429    }
430}
431
432/// Instrument information.
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct Instrument {
435    /// Instrument ID
436    pub id: String,
437
438    /// Symbol
439    pub symbol: String,
440
441    /// Name
442    pub name: String,
443
444    /// Exchange
445    pub exchange: String,
446
447    /// Security type
448    pub security_type: String,
449
450    /// Region
451    pub region: String,
452
453    /// Currency
454    pub currency: String,
455
456    /// Is tradable
457    pub tradable: bool,
458
459    /// Is shortable
460    pub shortable: bool,
461
462    /// Is marginable
463    pub marginable: bool,
464
465    /// Is fractional tradable
466    pub fractional_tradable: bool,
467}
468
469/// Parameters for querying instrument data.
470#[derive(Debug, Clone, Serialize, Deserialize)]
471pub struct InstrumentParams {
472    /// Symbols to query (comma-separated)
473    #[serde(rename = "symbols")]
474    pub symbols: String,
475
476    /// Security category (e.g., "STK" for stocks)
477    #[serde(rename = "category")]
478    pub category: String,
479}
480
481impl InstrumentParams {
482    /// Create new instrument query parameters.
483    pub fn new(symbols: impl Into<String>, category: impl Into<String>) -> Self {
484        Self {
485            symbols: symbols.into(),
486            category: category.into(),
487        }
488    }
489
490    /// Create new instrument query parameters for stocks.
491    pub fn new_stock(symbols: impl Into<String>) -> Self {
492        Self::new(symbols, "STK")
493    }
494
495    /// Create new instrument query parameters for multiple stock symbols.
496    pub fn new_stocks(symbols: &[&str]) -> Self {
497        let symbols_str = symbols.join(",");
498        Self::new_stock(symbols_str)
499    }
500}
501
502/// Parameters for querying end-of-day bars.
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct EodBarsParams {
505    /// Instrument IDs (comma-separated)
506    #[serde(rename = "instrument_ids")]
507    pub instrument_ids: String,
508
509    /// Date (UTC, format: yyyy-MM-dd)
510    #[serde(rename = "date", skip_serializing_if = "Option::is_none")]
511    pub date: Option<String>,
512
513    /// Number of bars to return (max 800)
514    #[serde(rename = "count")]
515    pub count: String,
516}
517
518impl EodBarsParams {
519    /// Create new EOD bars query parameters.
520    pub fn new(instrument_ids: impl Into<String>, count: u32) -> Self {
521        Self {
522            instrument_ids: instrument_ids.into(),
523            date: None,
524            count: count.to_string(),
525        }
526    }
527
528    /// Set the date filter.
529    pub fn date(mut self, date: impl Into<String>) -> Self {
530        self.date = Some(date.into());
531        self
532    }
533}
534
535/// Corporate action event type.
536#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
537pub enum CorpActionEventType {
538    /// Stock split
539    #[serde(rename = "SPLIT")]
540    Split,
541
542    /// Reverse stock split
543    #[serde(rename = "REVERSE_SPLIT")]
544    ReverseSplit,
545}
546
547/// Parameters for querying corporate actions.
548#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct CorpActionParams {
550    /// Instrument IDs (comma-separated)
551    #[serde(rename = "instrument_ids")]
552    pub instrument_ids: String,
553
554    /// Event types (comma-separated)
555    #[serde(rename = "event_types")]
556    pub event_types: String,
557
558    /// Start date (UTC, format: yyyy-MM-dd)
559    #[serde(rename = "start_date", skip_serializing_if = "Option::is_none")]
560    pub start_date: Option<String>,
561
562    /// End date (UTC, format: yyyy-MM-dd)
563    #[serde(rename = "end_date", skip_serializing_if = "Option::is_none")]
564    pub end_date: Option<String>,
565
566    /// Page number
567    #[serde(rename = "page_number", skip_serializing_if = "Option::is_none")]
568    pub page_number: Option<u32>,
569
570    /// Page size (max 200)
571    #[serde(rename = "page_size", skip_serializing_if = "Option::is_none")]
572    pub page_size: Option<u32>,
573
574    /// Last update time (UTC, format: yyyy-MM-dd HH:mm:ss)
575    #[serde(rename = "last_update_time", skip_serializing_if = "Option::is_none")]
576    pub last_update_time: Option<String>,
577}
578
579impl CorpActionParams {
580    /// Create new corporate action query parameters.
581    pub fn new(instrument_ids: impl Into<String>, event_types: Vec<CorpActionEventType>) -> Self {
582        let event_types_str = event_types
583            .iter()
584            .map(|et| match et {
585                CorpActionEventType::Split => "SPLIT",
586                CorpActionEventType::ReverseSplit => "REVERSE_SPLIT",
587            })
588            .collect::<Vec<_>>()
589            .join(",");
590
591        Self {
592            instrument_ids: instrument_ids.into(),
593            event_types: event_types_str,
594            start_date: None,
595            end_date: None,
596            page_number: None,
597            page_size: None,
598            last_update_time: None,
599        }
600    }
601
602    /// Set the start date filter.
603    pub fn start_date(mut self, start_date: impl Into<String>) -> Self {
604        self.start_date = Some(start_date.into());
605        self
606    }
607
608    /// Set the end date filter.
609    pub fn end_date(mut self, end_date: impl Into<String>) -> Self {
610        self.end_date = Some(end_date.into());
611        self
612    }
613
614    /// Set the page number.
615    pub fn page_number(mut self, page_number: u32) -> Self {
616        self.page_number = Some(page_number);
617        self
618    }
619
620    /// Set the page size.
621    pub fn page_size(mut self, page_size: u32) -> Self {
622        self.page_size = Some(page_size);
623        self
624    }
625
626    /// Set the last update time.
627    pub fn last_update_time(mut self, last_update_time: impl Into<String>) -> Self {
628        self.last_update_time = Some(last_update_time.into());
629        self
630    }
631}