tradestation_rs/market_data/
options.rs

1use crate::{
2    responses::{
3        market_data::{
4            OptionSpreadStrikesResp, OptionSpreadStrikesRespRaw, StreamOptionChainResp,
5            StreamOptionQuotesResp,
6        },
7        MarketData::{
8            GetOptionExpirationsResp, GetOptionExpirationsRespRaw, GetOptionsRiskRewardResp,
9            GetOptionsRiskRewardRespRaw,
10        },
11    },
12    Client, Error,
13};
14use serde::{Deserialize, Serialize};
15use serde_json::json;
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
18#[serde(rename_all = "PascalCase")]
19/// An option contract's expiration date and type.
20pub struct OptionExpiration {
21    /// Timestamp represented as an `RFC3339` formatted date, a profile of the ISO 8601 date standard.
22    /// E.g: `"2021-12-17T00:00:00Z"`.
23    pub date: String,
24    /// The type of expiration for the options contract.
25    /// E.g: `OptionExpirationType::Weekly`
26    pub r#type: OptionExpirationType,
27}
28impl OptionExpiration {
29    /// Fetch available option contract expiration dates for an underlying symbol.
30    ///
31    /// NOTE: `underlying_symbol` must be a valid symbol of the underlying asset
32    /// a option is derived off of. e.g: "SPY" would be the underlying symbol
33    /// for Feburary 28th 2025 $580 strike SPY call options "SPY 250228C580".
34    ///
35    /// NOTE: `strike_price` is optional, and if provided this will only return
36    /// expirations for that strike price.
37    ///
38    /// # Example
39    /// ---
40    ///
41    /// Fetch all expirations for Cloudflare (NET) options.
42    ///
43    /// ```ignore
44    /// let cloudflare_option_expirations = OptionExpiration::fetch("NET", None).await?;
45    /// println!("Cloudflare Option Expirations: {cloudflare_option_expirations:?}");
46    /// ```
47    pub async fn fetch(
48        client: &mut Client,
49        underlying_symbol: &str,
50        strike_price: Option<f64>,
51    ) -> Result<Vec<OptionExpiration>, Error> {
52        let mut endpoint = format!("marketdata/options/expirations/{}", underlying_symbol);
53        if let Some(strike) = strike_price {
54            let query_param = format!("?strikePrice={}", strike);
55            endpoint.push_str(&query_param);
56        }
57
58        let resp: GetOptionExpirationsResp = client
59            .get(&endpoint)
60            .await?
61            .json::<GetOptionExpirationsRespRaw>()
62            .await?
63            .into();
64
65        if let Some(expirations) = resp.expirations {
66            Ok(expirations)
67        } else {
68            Err(resp.error.unwrap_or(Error::UnknownTradeStationAPIError))
69        }
70    }
71}
72impl Client {
73    /// Fetch available option contract expiration dates for an underlying symbol.
74    ///
75    /// NOTE: `underlying_symbol` must be a valid symbol of the underlying asset
76    /// a option is derived off of. e.g: "SPY" would be the underlying symbol
77    /// for Feburary 28th 2025 $580 strike SPY call options "SPY 250228C580".
78    ///
79    /// NOTE: `strike_price` is optional, and if provided this will only return
80    /// expirations for that strike price.
81    ///
82    /// # Example:
83    /// ---
84    ///
85    /// Fetch all expirations for Cloudflare (NET) options.
86    ///
87    /// ```ignore
88    /// let cloudflare_option_expirations = client.get_option_expirations("NET", None).await?;
89    /// println!("Cloudflare Option Expirations: {cloudflare_option_expirations:?}");
90    /// ```
91    pub async fn get_option_expirations(
92        &mut self,
93        underlying_symbol: &str,
94        strike_price: Option<f64>,
95    ) -> Result<Vec<OptionExpiration>, Error> {
96        OptionExpiration::fetch(self, underlying_symbol, strike_price).await
97    }
98}
99
100#[derive(Clone, Debug, Deserialize, Serialize)]
101/// The different types of option expirations.
102pub enum OptionExpirationType {
103    /// An options contract with weekly based expirations.
104    Weekly,
105    /// An options contract with monthly based expirations.
106    Monthly,
107    /// An options contract with quarterly based expirations.
108    Quarterly,
109    /// An options contract with end of month based expirations.
110    EOM,
111    /// An options contract with expirations based on other conditions.
112    Other,
113}
114
115#[derive(Clone, Debug, Deserialize, Serialize)]
116#[serde(rename_all = "PascalCase")]
117///// Information on an options spread.
118/////
119///// e.g: a time spread, buying a long dated call
120///// and hedging some of that duration premium
121///// by selling a shorter dated call. This would
122///// involve a spread with 2 different expirations.
123pub enum OptionSpreadType {
124    /// A single option position, either a call or put.
125    ///
126    /// <div class="warning">NOTE: Selling this spread type has the potential for infinite loss.</div>
127    Single,
128    /// A spread involving a call and a put with
129    /// the same strike price and expiration date.
130    ///
131    /// <div class="warning">NOTE: Selling this spread type has the potential for infinite loss.</div>
132    Straddle,
133    /// A spread involving a call and a put at different
134    /// strike prices, but the same expiration date.
135    Strangle,
136    /// A spread involving buying and selling two options
137    /// of the same type (calls or puts) at different strike
138    /// prices, but the same expiration date.
139    Vertical,
140    /// A spread involving buying one option and selling 2
141    /// options of the same type as the one bought, but with
142    /// different strike prices and the same expiration date.
143    RatioBack1x2,
144    /// A spread involving buying one option and selling 3
145    /// options of the same type as the one bought, but with
146    /// different strike prices and the same expiration date.
147    ///
148    /// NOTE: Similar to `OptionSpreadType::RatioBack1x2`, but
149    /// `RatioBack1x3` offers more leverage.
150    RatioBack1x3,
151    /// A spread involving buying one option and selling 3
152    /// options of the same type as the one bought, but with
153    /// different strike prices and the same expiration date.
154    ///
155    /// NOTE: Similar to `OptionSpreadType::RatioBack1x2` and
156    /// `OptionSpreadType::RatioBack1x3`, but a more balanced
157    /// risk reward.
158    RatioBack2x3,
159    /// A spread involving buying an option at one strike price,
160    /// and selling 2 options of that same option type bought (call or puts).
161    ///
162    /// NOTE: If using call options, then the 2 options to sell should be
163    /// at a higher strike than the call bought. If using put options, then
164    /// the 2 put options to sell should be at a lower strike price then the
165    /// put bought.
166    ///
167    /// <div class="warning">NOTE: This spread type has the potential for infinite loss.</div>
168    Butterfly,
169    /// A spread involving selling an At-The-Money straddle `OptionSpreadType::Straddle`,
170    /// and buying an Out-of-The-Money call and put to hedge risk. Where all options are
171    /// of the same expiration date.
172    IronButterfly,
173    /// A spread involving buying an option at one strike price, selling 2 options
174    /// at middle price, and buying one option at a higher strike price. Where all the
175    /// options are of the same type (call or put), and the same expiration date.
176    Condor,
177    /// A spread involving selling a call spread and a put spread. Where all options
178    /// are of the same expiration date.
179    IronCondor,
180    /// A spread involving the underlying asset and an option.
181    ///
182    /// E.g: Covered Call involves buying 100 shares (or one contract if futures options)
183    /// of the underlying asset, and selling one call option.
184    ///
185    /// E.g: Covered Put involves short selling 100 shares (or one contract if futures options)
186    /// of the underlying asset, and selling one put option.
187    ///
188    /// <div class="warning">NOTE: In a Covered Put, there is potential for infinite loss
189    /// if the underlying asset's price rises indefinitely.</div>
190    Covered,
191    /// A spread involving holding a position in the underlying asset, buying a protective
192    /// option (opposite of your underlying position, put if long or call if short the
193    /// underlying), and selling an option the opposite of the option you bought to reduce
194    /// the cost of the protection, but putting a cap on potential reward.
195    Collar,
196    /// A spread involving buying or selling a mix of option types at a mix of strike prices
197    /// and expiration dates.
198    ///
199    /// <div class="warning">NOTE: Depending on the specific positions, this spread can have the
200    /// potential for infinite loss, especially if it includes uncovered short options.</div>
201    Combo,
202    /// A spread involving buying and selling 2 options of the same type (calls or puts) with
203    /// the same strike price, but different expiration dates.
204    ///
205    /// NOTE: Similar to diagonal spreads `OptionSpreadType::Diagonal`, but with the same
206    /// strike prices allowing for a more neutral position.
207    Calendar,
208    /// A spread involving buying and selling 2 options of the same type (calls or puts), but
209    /// with different strike prices, and expiration dates.
210    ///
211    /// NOTE: Similar to calendar spreads `OptionSpreadType::Calendar`, but with different
212    /// strike prices allowing for more directional biases.
213    ///
214    /// <div class="warning">NOTE: Depending on the strike prices and the extent of coverage
215    /// from long options, this spread type can have the potential for unlimited loss.</div>
216    Diagonal,
217}
218impl OptionSpreadType {
219    /// Get a vector of all the option spread types.
220    ///
221    /// # Example
222    /// ---
223    ///
224    /// Get all the spread types and print information about them:
225    ///
226    /// ```rust
227    /// use tradestation::MarketData::OptionSpreadType;
228    ///
229    /// let option_spread_types = OptionSpreadType::all();
230    /// for spread_type in option_spread_types.iter() {
231    ///     println!(
232    ///         "{spread_type:?} | contains stike interval {} | contains expiration interval: {}",
233    ///         spread_type.involves_strike_interval(),
234    ///         spread_type.involves_expiration_interval()
235    ///     );
236    /// }
237    /// ```
238    pub fn all() -> Vec<OptionSpreadType> {
239        vec![
240            Self::Single,
241            Self::Straddle,
242            Self::Strangle,
243            Self::Vertical,
244            Self::RatioBack1x2,
245            Self::RatioBack1x3,
246            Self::RatioBack2x3,
247            Self::Butterfly,
248            Self::IronButterfly,
249            Self::Condor,
250            Self::IronCondor,
251            Self::Covered,
252            Self::Collar,
253            Self::Combo,
254            Self::Calendar,
255            Self::Diagonal,
256        ]
257    }
258
259    /// Does the `OptionSpreadType` involve an interval of strike prices?
260    ///
261    /// NOTE: This will return false for `OptionSpreadType::Combo` even though
262    /// it may consist of multiple strikes, but TradeStations API returns false.
263    pub fn involves_strike_interval(&self) -> bool {
264        !matches!(
265            self,
266            Self::Calendar | Self::Combo | Self::Covered | Self::Single | Self::Straddle
267        )
268    }
269
270    /// Does the `OptionSpreadType` involve an interval of expirations?
271    ///
272    /// NOTE: This will return false for `OptionSpreadType::Combo` even though
273    /// it may consist of multiple expirations, but TradeStations API returns false.
274    pub fn involves_expiration_interval(&self) -> bool {
275        matches!(self, Self::Calendar | Self::Diagonal)
276    }
277}
278impl Client {
279    /// Get a vector of all the option spread types.
280    ///
281    /// # Example
282    /// ---
283    ///
284    /// Get all the spread types and print information about them:
285    ///
286    /// ```rust
287    /// use tradestation::MarketData::OptionSpreadType;
288    ///
289    /// let option_spread_types = OptionSpreadType::all();
290    /// for spread_type in option_spread_types.iter() {
291    ///     println!(
292    ///         "{spread_type:?} | contains stike interval {} | contains expiration interval: {}",
293    ///         spread_type.involves_strike_interval(),
294    ///         spread_type.involves_expiration_interval()
295    ///     );
296    /// }
297    /// ```
298    pub fn get_option_spread_types(&mut self) -> Vec<OptionSpreadType> {
299        OptionSpreadType::all()
300    }
301}
302
303#[derive(Clone, Debug, Deserialize, Serialize)]
304#[serde(rename_all = "PascalCase")]
305/// An anlaysis on the risk vs reward of an options trade.
306///
307/// NOTE: Analysis is NOT available for trades with spread types
308/// involving multiple expirations, such as Calandar or Diagonal.
309pub struct OptionRiskRewardAnalysis {
310    /// Indicates whether the maximum gain can be infinite.
311    pub max_gain_is_infinite: bool,
312    /// The adjusted maximum gain (if it is not infinite).
313    pub adjusted_max_gain: String,
314    /// Indicates whether the maximum loss can be infinite.
315    pub max_loss_is_infinite: bool,
316    /// The adjusted maximum loss (if it is not infinite).
317    pub adjusted_max_loss: String,
318    /// Market price that the underlying security must reach
319    /// for the trade to avoid a loss.
320    pub breakeven_points: Vec<String>,
321}
322impl OptionRiskRewardAnalysis {
323    /// Run analysis on an options trade given a price and option legs.
324    ///
325    /// NOTE: All the option legs must be the same symbol, and expiration.
326    ///
327    /// # Example
328    /// ---
329    ///
330    /// Analyze the risk vs reward of a long volatility
331    /// trade for TLT at the November 15th expiration via buying
332    /// a call **and** a put at the $99 strike.
333    ///
334    /// NOTE: The call will make money if TLT makes a big move up, and
335    /// the put will make money if TLT makes a big move down. The downside
336    /// of this trade comes from stable, slow, or small price movement.
337    ///
338    /// NOTE: This spread offers unlimited potential profit while defining
339    /// a max potential loss.
340    ///
341    /// ```ignore
342    /// use tradestation::{ClientBuilder, Error, MarketData::options::{OptionsLeg, OptionTradeAction}};
343    ///
344    /// let mut client = ClientBuilder::new()?
345    ///     .credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
346    ///     .authorize("YOUR_AUTHORIZATION_CODE")
347    ///     .await?
348    ///     .build()
349    ///     .await?;
350    ///
351    /// let risk_reward_analysis = client
352    ///     .analyze_options_risk_reward(
353    ///         4.33,
354    ///         vec![
355    ///             OptionsLeg {
356    ///                 symbol: String::from("TLT 241115C99"),
357    ///                 quantity: 5,
358    ///                 trade_action: OptionTradeAction::Buy,
359    ///             },
360    ///             OptionsLeg {
361    ///                 symbol: String::from("TLT 241115P99"),
362    ///                 quantity: 5,
363    ///                 trade_action: OptionTradeAction::Buy,
364    ///             },
365    ///         ],
366    ///     )
367    ///     .await?;
368    ///
369    /// println!(
370    ///     "TLT November 15th Long Volatility Via ATM Straddle
371    ///      Risk vs Reward Analysis: {risk_reward_analysis:?}"
372    /// );
373    /// ```
374    pub async fn run(
375        client: &mut Client,
376        price: f64,
377        legs: Vec<OptionsLeg>,
378    ) -> Result<Self, Error> {
379        let payload = json!({"SpreadPrice": price, "Legs": legs});
380
381        let resp: GetOptionsRiskRewardResp = client
382            .post("marketdata/options/riskreward", &payload)
383            .await?
384            .json::<GetOptionsRiskRewardRespRaw>()
385            .await?
386            .into();
387
388        if let Some(analysis) = resp.analysis {
389            Ok(analysis)
390        } else {
391            Err(resp.error.unwrap_or(Error::UnknownTradeStationAPIError))
392        }
393    }
394}
395impl Client {
396    /// Run analysis on an options trade given a price and option legs.
397    ///
398    /// NOTE: All the option legs must be the same symbol, and expiration.
399    ///
400    /// # Example
401    /// ---
402    ///
403    /// Analyze the risk vs reward of a long volatility
404    /// trade for TLT at the November 15th expiration via buying
405    /// a call **and** a put at the $99 strike.
406    ///
407    /// NOTE: The call will make money if TLT makes a big move up, and
408    /// the put will make money if TLT makes a big move down. The downside
409    /// of this trade comes from stable, slow, or small price movement.
410    ///
411    /// NOTE: This spread offers unlimited potential profit while defining
412    /// a max potential loss.
413    ///
414    /// ```ignore
415    /// use tradestation::{ClientBuilder, Error, MarketData::options::{OptionsLeg, OptionTradeAction}};
416    ///
417    /// let mut client = ClientBuilder::new()?
418    ///     .credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
419    ///     .authorize("YOUR_AUTHORIZATION_CODE")
420    ///     .await?
421    ///     .build()
422    ///     .await?;
423    ///
424    /// let risk_reward_analysis = client
425    ///     .analyze_options_risk_reward(
426    ///         4.33,
427    ///         vec![
428    ///             OptionsLeg {
429    ///                 symbol: String::from("TLT 241115C99"),
430    ///                 quantity: 5,
431    ///                 trade_action: OptionTradeAction::Buy,
432    ///             },
433    ///             OptionsLeg {
434    ///                 symbol: String::from("TLT 241115P99"),
435    ///                 quantity: 5,
436    ///                 trade_action: OptionTradeAction::Buy,
437    ///             },
438    ///         ],
439    ///     )
440    ///     .await?;
441    ///
442    /// println!(
443    ///     "TLT November 15th Long Volatility Via ATM Straddle
444    ///      Risk vs Reward Analysis: {risk_reward_analysis:?}"
445    /// );
446    /// ```
447    pub async fn analyze_options_risk_reward(
448        &mut self,
449        price: f64,
450        legs: Vec<OptionsLeg>,
451    ) -> Result<OptionRiskRewardAnalysis, Error> {
452        OptionRiskRewardAnalysis::run(self, price, legs).await
453    }
454}
455
456#[derive(Clone, Debug, Deserialize, Serialize)]
457#[serde(rename_all = "PascalCase")]
458/// An options position that is apart of a bigger
459/// overall options trade.
460pub struct OptionsLeg {
461    /// Option contract symbol or underlying symbol
462    /// to be traded for this leg.
463    pub symbol: String,
464    /// The number of option contracts to buy or sell for this leg.
465    ///
466    /// NOTE: The value cannot be zero.
467    pub quantity: i32,
468    /// The kind of options trade (buying or selling).
469    pub trade_action: OptionTradeAction,
470}
471
472#[derive(Clone, Debug, Deserialize, Serialize)]
473/// The different types of option trade actions.
474pub enum OptionTradeAction {
475    #[serde(rename = "BUY")]
476    /// Buying an option contract
477    Buy,
478    #[serde(rename = "SELL")]
479    /// Selling an option contract
480    Sell,
481}
482
483#[derive(Clone, Debug, Deserialize, Serialize)]
484#[serde(rename_all = "PascalCase")]
485/// The available strikes for an options spread.
486pub struct OptionSpreadStrikes {
487    /// Name of the spread type for these strikes.
488    pub spread_type: OptionSpreadType,
489    /// Vector of the strike prices for this spread type.
490    ///
491    /// NOTE: Each element in the Strikes vector is a vector
492    /// of strike prices for a single spread.
493    pub strikes: Vec<Vec<String>>,
494}
495impl OptionSpreadStrikes {
496    /// Fetch the available strike prices for a spread type and expiration date.
497    ///
498    /// # Example
499    /// ---
500    ///
501    /// Fetch all the availble strikes for an iron condor on Amazon
502    /// `"AMZN"` for the Dec 20th 2024 expiration `"12-20-2024"`.
503    ///
504    /// ```ignore
505    /// let query = OptionSpreadStrikesQueryBuilder::new()
506    ///     .underlying("AMZN")
507    ///     .spread_type(OptionSpreadType::IronCondor)
508    ///     .expiration("12-20-2024")
509    ///     .build()?;
510    ///
511    /// let availble_strikes = client.get_option_spread_strikes(query).await?;
512    ///
513    /// println!("Amazon Dec 20th Iron Condor Strikes Availble: {availble_strikes:?}");
514    /// ```
515    pub async fn fetch(
516        client: &mut Client,
517        query: OptionSpreadStrikesQuery,
518    ) -> Result<OptionSpreadStrikes, Error> {
519        let mut endpoint = format!(
520            "marketdata/options/strikes/{}?spreadType={:?}&strikeInterval={}",
521            query.underlying, query.spread_type, query.strike_interval,
522        );
523
524        if let Some(date) = query.expiration {
525            let query_param = format!("&expiration={}", date);
526            endpoint.push_str(&query_param);
527
528            if let Some(date_2) = query.expiration2 {
529                let query_param_2 = format!("&expiration2={}", date_2);
530                endpoint.push_str(&query_param_2);
531            }
532        }
533
534        let resp: OptionSpreadStrikesResp = client
535            .get(&endpoint)
536            .await?
537            .json::<OptionSpreadStrikesRespRaw>()
538            .await?
539            .into();
540
541        if let Some(spread_strikes) = resp.spread_strikes {
542            Ok(spread_strikes)
543        } else {
544            eprintln!("{:?}", resp.error);
545            Err(resp.error.unwrap_or(Error::UnknownTradeStationAPIError))
546        }
547    }
548}
549impl Client {
550    /// Fetch the available strike prices for a spread type and expiration date.
551    ///
552    /// # Example
553    /// ---
554    ///
555    /// Fetch all the availble strikes for an iron condor on Amazon
556    /// `"AMZN"` for the Dec 20th 2024 expiration `"12-20-2024"`.
557    ///
558    /// ```ignore
559    /// let query = OptionSpreadStrikesQueryBuilder::new()
560    ///     .underlying("AMZN")
561    ///     .spread_type(OptionSpreadType::IronCondor)
562    ///     .expiration("12-20-2024")
563    ///     .build()?;
564    ///
565    /// let availble_strikes = client.get_option_spread_strikes(query).await?;
566    ///
567    /// println!("Amazon Dec 20th Iron Condor Strikes Availble: {availble_strikes:?}");
568    /// ```
569    pub async fn get_option_spread_strikes(
570        &mut self,
571        query: OptionSpreadStrikesQuery,
572    ) -> Result<OptionSpreadStrikes, Error> {
573        OptionSpreadStrikes::fetch(self, query).await
574    }
575}
576
577#[derive(Clone, Debug, Deserialize, Serialize)]
578#[serde(rename_all = "PascalCase")]
579/// The query required to fetch `OptionSpreadStrikes`.
580pub struct OptionSpreadStrikesQuery {
581    /// The symbol for the underlying security
582    /// on which the option contracts are based.
583    ///
584    /// NOTE: The underlying symbol must be an equity or index.
585    pub underlying: String,
586    /// The type of spread `MarketData::OptionSpreadType`
587    pub spread_type: OptionSpreadType,
588    /// The desired interval between the strike prices
589    /// in a spread. It must be greater than or equal to 1.
590    /// A value of 1 uses consecutive strikes; a value of 2
591    /// skips one between strikes; and so on.
592    pub strike_interval: i32,
593    /// The date on which the option contract expires;
594    /// must be a valid expiration date.
595    ///
596    /// NOTE: Defaults to the next contract expiration date.
597    pub expiration: Option<String>,
598    /// The second contract expiration date required
599    /// for Calendar and Diagonal spreads.
600    pub expiration2: Option<String>,
601}
602
603#[derive(Clone, Debug, Deserialize, Serialize, Default)]
604#[serde(rename_all = "PascalCase")]
605/// Builder for `OptionSpreadStrikesQuery`
606pub struct OptionSpreadStrikesQueryBuilder {
607    underlying: Option<String>,
608    spread_type: Option<OptionSpreadType>,
609    strike_interval: Option<i32>,
610    expiration: Option<String>,
611    expiration2: Option<String>,
612}
613impl OptionSpreadStrikesQueryBuilder {
614    /// Create a new `OptionSpreadStrikesQueryBuilder`.
615    pub fn new() -> Self {
616        Self::default()
617    }
618
619    /// Set the symbol for the underlying security
620    /// on which the option contracts are based.
621    ///
622    /// NOTE: The underlying symbol must be an equity or index.
623    pub fn underlying<S: Into<String>>(mut self, symbol: S) -> Self {
624        self.underlying = Some(symbol.into());
625
626        self
627    }
628
629    /// Set the type of spread `MarketData::OptionSpreadType`
630    pub fn spread_type(mut self, spread: OptionSpreadType) -> Self {
631        self.spread_type = Some(spread);
632
633        self
634    }
635
636    /// Set the desired interval between the strike prices in a spread.
637    /// It must be greater than or equal to 1. A value of 1 uses consecutive strikes;
638    /// a value of 2 skips one between strikes; and so on.
639    pub fn strike_interval(mut self, interval: i32) -> Self {
640        self.strike_interval = Some(interval);
641
642        self
643    }
644
645    /// Set the date on which the option contract expires; must be a valid expiration date.
646    ///
647    /// NOTE: Defaults to the next contract expiration date.
648    pub fn expiration<S: Into<String>>(mut self, date: S) -> Self {
649        self.expiration = Some(date.into());
650
651        self
652    }
653
654    /// Set the second contract expiration date required
655    /// for Calendar and Diagonal spreads.
656    pub fn expiration2<S: Into<String>>(mut self, date: S) -> Self {
657        self.expiration2 = Some(date.into());
658
659        self
660    }
661
662    /// Finish building, returning a `OptionSpreadStrikesQuery`.
663    ///
664    /// NOTE: You must set `symbol` before calling `build`.
665    pub fn build(self) -> Result<OptionSpreadStrikesQuery, Error> {
666        Ok(OptionSpreadStrikesQuery {
667            underlying: self.underlying.ok_or_else(|| Error::SymbolNotSet)?,
668            spread_type: self.spread_type.unwrap_or(OptionSpreadType::Single),
669            strike_interval: self.strike_interval.unwrap_or(1),
670            expiration: self.expiration,
671            expiration2: self.expiration2,
672        })
673    }
674}
675
676#[derive(Clone, Debug, Deserialize, Serialize)]
677#[serde(rename_all = "PascalCase")]
678/// A chain of option spreads for a given underlying symbol,
679/// spread type, and expiration.
680pub struct OptionChain {
681    /// The expected change in an option position’s value resulting
682    /// from a one point increase in the price of the underlying security.
683    pub delta: Option<String>,
684    /// The expected decline in an option position’s value resulting
685    /// from the passage of one day’s time, holding all other variables
686    /// (price of the underlying, volatility, etc.) constant.
687    pub theta: Option<String>,
688    /// The expected change in an option position’s delta resulting
689    /// from a one point increase in the price of the underlying security.
690    pub gamma: Option<String>,
691    /// The expected change in an option position’s value resulting
692    /// from an increase of one percentage point in the risk-free
693    /// interest rate (e.g. an increase from 3% to 4%).
694    pub rho: Option<String>,
695    /// The expected change in an option position’s value resulting
696    /// from an increase of one percentage point in the volatility of
697    /// the underlying security (e.g. an increase from 26% to 27%).
698    pub vega: Option<String>,
699    /// The volatility of the underlying implied by an option
700    /// position’s current price.
701    pub implied_volatility: Option<String>,
702    /// The value of an option position exclusive of the position’s
703    /// time value. The value of the option position if it were to
704    /// expire immediately.
705    pub intrinsic_value: String,
706    /// The time value of an option position.
707    ///
708    /// NOTE: The market value of an option position minus
709    /// the position’s intrinsic value.
710    pub extrinsic_value: String,
711    /// The value of an option position based on a theoretical model
712    /// of option prices (the Bjerksund-Stensland model).
713    ///
714    /// NOTE: Calculated using volatility of the underlying.
715    pub theoretical_value: String,
716    #[serde(rename = "ProbabilityITM")]
717    /// The calculated probability that an option position will have
718    /// intrinsic value at expiration.
719    ///
720    /// NOTE: Calculated using volatility of the underlying.
721    pub probability_itm: Option<String>,
722    #[serde(rename = "ProbabilityOTM")]
723    /// The calculated probability that an option position will not have
724    /// intrinsic value at expiration.
725    ///
726    /// NOTE: Calculated using volatility of the underlying.
727    pub probability_otm: Option<String>,
728    #[serde(rename = "ProbabilityBE")]
729    /// The calculated probability that an option position will have
730    /// a value at expiration that is equal to or greater than the
731    /// position’s current cost.
732    ///
733    /// NOTE: Calculated using volatility of the underlying.
734    pub probability_be: Option<String>,
735    #[serde(rename = "ProbabilityITM_IV")]
736    /// The calculated probability that an option position will have
737    /// intrinsic value at expiration.
738    ///
739    /// NOTE: Calculated using implied volatility.
740    pub probability_itm_iv: Option<String>,
741    #[serde(rename = "ProbabilityOTM_IV")]
742    /// The calculated probability that an option position will not
743    /// have intrinsic value at expiration.
744    ///
745    /// NOTE: Calculated using implied volatility.
746    pub probability_otm_iv: Option<String>,
747    #[serde(rename = "ProbabilityBE_IV")]
748    /// The calculated probability that an option position will have a
749    /// value at expiration that is equal to or greater than the position’s
750    /// current cost.
751    ///
752    /// NOTE: Calculated using implied volatility.
753    pub probability_be_iv: Option<String>,
754    #[serde(rename = "TheoreticalValueIV")]
755    /// The value of an option position based on a theoretical model of
756    /// option prices (the Bjerksund-Stensland model).
757    ///
758    /// NOTE: Calculated using implied volatility.
759    pub theoretical_value_iv: Option<String>,
760    /// Total number of open contracts for the option spread.
761    ///
762    /// NOTE: This value is updated daily.
763    pub daily_open_interest: i32,
764    /// Ask price. The price a seller is willing to accept for the option spread.
765    pub ask: String,
766    /// Bid price. The price a buyer is willing to pay for the option spread.
767    pub bid: String,
768    /// Average between `ask` and `bid`.
769    pub mid: String,
770    /// Amount of contracts at the given `ask` price.
771    pub ask_size: i32,
772    /// Amount of contracts at the given `bid` price.
773    pub bid_size: i32,
774    /// The last traded price for the option spread.
775    ///
776    /// NOTE: This value only updates during the official market session.
777    pub close: String,
778    /// Today's highest price for the option spread.
779    pub high: String,
780    /// The last traded price for the option spread.
781    pub last: String,
782    /// Today's lowest traded price for the option spread.
783    pub low: String,
784    /// Difference between prior Close price and current Close price for the
785    /// option spread.
786    pub net_change: String,
787    /// Percentage changed between prior `close` price and current `close` price
788    /// for the option spread.
789    pub net_change_pct: String,
790    /// The initial price for the option spread during the official market session.
791    pub open: String,
792    /// Prior day's Closing price.
793    pub previous_close: String,
794    /// The number of contracts traded today.
795    pub volume: i32,
796    /// The side of the option chain.
797    pub side: OptionChainSide,
798    /// The strike prices for the option contracts in the legs of this spread.
799    pub strikes: Vec<String>,
800    /// The legs of the option spread.
801    pub legs: Vec<OptionSpreadLeg>,
802}
803impl OptionChain {
804    /// Stream an options chain for a given query `OptionChainQuery`.
805    ///
806    /// NOTE: You need to provide a function to handle each stream chunk.
807    ///
808    /// # Example
809    /// ---
810    ///
811    /// Example: Stream an option chain for Apple `"AAPL"`.
812    ///
813    /// ```ignore
814    /// let stream_aapl_option_chain_query = MarketData::OptionChainQueryBuilder::new()
815    ///     .underlying("AAPL")
816    ///     .build()?;
817    ///
818    /// let streamed_chains = client
819    ///     .stream_option_chain(&stream_aapl_option_chain_query, |stream_data| {
820    ///         // The response type is `responses::market_data::StreamOptionChainResp`
821    ///         // which has multiple variants the main one you care about is `OptionChain`
822    ///         // which will contain option chain data sent from the stream.
823    ///         match stream_data {
824    ///             StreamOptionChainResp::OptionChain(chain) => {
825    ///                 // Do something with the option chain like
826    ///                 // display it with a table on a website.
827    ///                 println!("{chain:?}")
828    ///             }
829    ///             StreamOptionChainResp::Heartbeat(heartbeat) => {
830    ///                 // Response for periodic signals letting you know the connection is
831    ///                 // still alive. A heartbeat is sent every 5 seconds of inactivity.
832    ///                 println!("{heartbeat:?}");
833    ///
834    ///                 // for the sake of this example after we recieve the
835    ///                 // tenth heartbeat, we will stop the stream session.
836    ///                 if heartbeat.heartbeat > 10 {
837    ///                     // Example: stopping a stream connection
838    ///                     return Err(Error::StopStream);
839    ///                 }
840    ///             }
841    ///             StreamOptionChainResp::Status(status) => {
842    ///                 // Signal sent on state changes in the stream
843    ///                 // (closed, opened, paused, resumed)
844    ///                 println!("{status:?}");
845    ///             }
846    ///             StreamOptionChainResp::Error(err) => {
847    ///                 // Response for when an error was encountered,
848    ///                 // with details on the error
849    ///                 println!("{err:?}");
850    ///             }
851    ///         }
852    ///
853    ///         Ok(())
854    ///     })
855    ///     .await?;
856    ///
857    /// // After the stream ends print all the collected option chains
858    /// println!("{streamed_chains:?}");
859    /// ```
860    pub async fn stream<F>(
861        client: &mut Client,
862        query: &OptionChainQuery,
863        mut on_chunk: F,
864    ) -> Result<Vec<OptionChain>, Error>
865    where
866        F: FnMut(StreamOptionChainResp) -> Result<(), Error>,
867    {
868        let endpoint = format!(
869            "marketdata/stream/options/chains/{}{}",
870            query.underlying,
871            query.as_query_string()
872        );
873
874        let mut collected_chains: Vec<OptionChain> = Vec::new();
875        client
876            .stream(&endpoint, |chunk| {
877                let parsed_chunk = serde_json::from_value::<StreamOptionChainResp>(chunk)?;
878                on_chunk(parsed_chunk.clone())?;
879
880                // Only collect orders, so when the stream is done
881                // all the orders that were streamed can be returned
882                if let StreamOptionChainResp::OptionChain(option_chain) = parsed_chunk {
883                    collected_chains.push(*option_chain);
884                }
885
886                Ok(())
887            })
888            .await?;
889
890        Ok(collected_chains)
891    }
892}
893impl Client {
894    /// Stream an options chain for a given query `OptionChainQuery`.
895    ///
896    /// NOTE: You need to provide a function to handle each stream chunk.
897    ///
898    /// # Example
899    /// ---
900    ///
901    /// Example: Stream an option chain for Apple `"AAPL"`.
902    ///
903    /// ```ignore
904    /// let stream_aapl_option_chain_query = MarketData::OptionChainQueryBuilder::new()
905    ///     .underlying("AAPL")
906    ///     .build()?;
907    ///
908    /// let streamed_chains = client
909    ///     .stream_option_chain(&stream_aapl_option_chain_query, |stream_data| {
910    ///         // The response type is `responses::market_data::StreamOptionChainResp`
911    ///         // which has multiple variants the main one you care about is `OptionChain`
912    ///         // which will contain option chain data sent from the stream.
913    ///         match stream_data {
914    ///             StreamOptionChainResp::OptionChain(chain) => {
915    ///                 // Do something with the option chain like
916    ///                 // display it with a table on a website.
917    ///                 println!("{chain:?}")
918    ///             }
919    ///             StreamOptionChainResp::Heartbeat(heartbeat) => {
920    ///                 // Response for periodic signals letting you know the connection is
921    ///                 // still alive. A heartbeat is sent every 5 seconds of inactivity.
922    ///                 println!("{heartbeat:?}");
923    ///
924    ///                 // for the sake of this example after we recieve the
925    ///                 // tenth heartbeat, we will stop the stream session.
926    ///                 if heartbeat.heartbeat > 10 {
927    ///                     // Example: stopping a stream connection
928    ///                     return Err(Error::StopStream);
929    ///                 }
930    ///             }
931    ///             StreamOptionChainResp::Status(status) => {
932    ///                 // Signal sent on state changes in the stream
933    ///                 // (closed, opened, paused, resumed)
934    ///                 println!("{status:?}");
935    ///             }
936    ///             StreamOptionChainResp::Error(err) => {
937    ///                 // Response for when an error was encountered,
938    ///                 // with details on the error
939    ///                 println!("{err:?}");
940    ///             }
941    ///         }
942    ///
943    ///         Ok(())
944    ///     })
945    ///     .await?;
946    ///
947    /// // After the stream ends print all the collected option chains
948    /// println!("{streamed_chains:?}");
949    /// ```
950    pub async fn stream_option_chain<F>(
951        &mut self,
952        query: &OptionChainQuery,
953        on_chunk: F,
954    ) -> Result<Vec<OptionChain>, Error>
955    where
956        F: FnMut(StreamOptionChainResp) -> Result<(), Error>,
957    {
958        OptionChain::stream(self, query, on_chunk).await
959    }
960}
961
962#[derive(Clone, Debug, Deserialize, Serialize)]
963#[serde(rename_all = "PascalCase")]
964/// The query used to sream an options chain.
965pub struct OptionChainQuery {
966    /// The symbol for the underlying security on which the option contracts are based.
967    pub underlying: String,
968    /// Date on which the option contract expires; must be a valid expiration date.
969    ///
970    /// NOTE: Defaults to the next contract expiration date.
971    pub expiration: Option<String>,
972    /// Second contract expiration date required
973    /// for Calendar and Diagonal spreads.
974    pub expiration2: Option<String>,
975    /// Specifies the number of spreads to display above and below the price center.
976    ///
977    /// NOTE: Defaults to a proximity of `5` strikes above and below the price center.
978    pub strike_proximity: i32,
979    /// Specifies the name of the spread type to use.
980    pub spread_type: OptionSpreadType,
981    /// The theoretical rate of return of an investment with zero risk.
982    /// NOTE: Defaults to the current quote for `$IRX.X`.
983    ///
984    /// NOTE: The percentage rate should be specified as a decimal value.
985    /// E.g, to use 2% for the rate, pass in `0.02`.
986    pub risk_free_rate: Option<f64>,
987    /// Specifies the strike price center.
988    ///
989    /// NOTE: Defaults to the last quoted price for the underlying security.
990    pub price_center: Option<f64>,
991    /// Specifies the desired interval between the strike prices in a spread.
992    ///
993    /// NOTE: It must be greater than or equal to 1. A value of 1 uses consecutive strikes;
994    /// a value of 2 skips one between strikes; and so on.
995    ///
996    /// NOTE: Defaults to `1`.
997    pub strike_interval: i32,
998    /// Specifies whether or not greeks properties are returned.
999    ///
1000    /// NOTE: Defaults to `true`.
1001    pub enable_greeks: bool,
1002    /// Set the option chain filter for specific range of options.
1003    ///
1004    /// NOTE: Defaults to all `OptionStrikeRange::All`.
1005    ///
1006    /// E.g: Filter the chain for out of the money options:
1007    /// `OptionStrikeRange::OTM`.
1008    pub strike_range: OptionStrikeRange,
1009    /// Filters the spreads by a specific option type.
1010    pub option_type: OptionType,
1011}
1012impl OptionChainQuery {
1013    pub fn as_query_string(&self) -> String {
1014        let mut query_string = String::from("?");
1015
1016        query_string.push_str(&format!("strikeInterval={}&", self.strike_interval));
1017        query_string.push_str(&format!("strikeProximity={}&", self.strike_proximity));
1018        query_string.push_str(&format!("spreadType={:?}&", self.spread_type));
1019        query_string.push_str(&format!("enableGreeks={}&", self.enable_greeks));
1020        query_string.push_str(&format!("strikeRange={:?}&", self.strike_range));
1021        query_string.push_str(&format!("optionType={:?}&", self.option_type));
1022
1023        if let Some(expiration) = &self.expiration {
1024            query_string.push_str(&format!("expiration={}&", expiration));
1025        }
1026        if let Some(expiration) = &self.expiration2 {
1027            query_string.push_str(&format!("expiration2={}&", expiration));
1028        }
1029        if let Some(rate) = self.risk_free_rate {
1030            query_string.push_str(&format!("riskFreeRate={}&", rate));
1031        }
1032        if let Some(price) = self.price_center {
1033            query_string.push_str(&format!("priceCenter={}&", price));
1034        }
1035
1036        if query_string.ends_with('&') {
1037            query_string.pop();
1038        }
1039
1040        query_string
1041    }
1042}
1043
1044#[derive(Clone, Debug, Deserialize, Serialize, Default)]
1045#[serde(rename_all = "PascalCase")]
1046/// Builder for `OptionChainQuery`.
1047pub struct OptionChainQueryBuilder {
1048    /// The symbol for the underlying security on which the option contracts are based.
1049    underlying: Option<String>,
1050    /// Date on which the option contract expires; must be a valid expiration date.
1051    ///
1052    /// NOTE: Defaults to the next contract expiration date.
1053    expiration: Option<String>,
1054    /// Second contract expiration date required
1055    /// for Calendar and Diagonal spreads.
1056    expiration2: Option<String>,
1057    /// Specifies the number of spreads to display above and below the price center.
1058    ///
1059    /// NOTE: Defaults to a proximity of `5` strikes above and below the price center.
1060    strike_proximity: Option<i32>,
1061    /// Specifies the name of the spread type to use.
1062    spread_type: Option<OptionSpreadType>,
1063    /// The theoretical rate of return of an investment with zero risk.
1064    /// NOTE: Defaults to the current quote for `$IRX.X`.
1065    ///
1066    /// NOTE: The percentage rate should be specified as a decimal value.
1067    /// E.g, to use 2% for the rate, pass in `0.02`.
1068    risk_free_rate: Option<f64>,
1069    /// Specifies the strike price center.
1070    ///
1071    /// NOTE: Defaults to the last quoted price for the underlying security.
1072    price_center: Option<f64>,
1073    /// Specifies the desired interval between the strike prices in a spread.
1074    ///
1075    /// NOTE: It must be greater than or equal to 1. A value of 1 uses consecutive strikes;
1076    /// a value of 2 skips one between strikes; and so on.
1077    ///
1078    /// NOTE: Defaults to `1`.
1079    strike_interval: Option<i32>,
1080    /// Specifies whether or not greeks properties are returned.
1081    ///
1082    /// NOTE: Defaults to `true`.
1083    enable_greeks: Option<bool>,
1084    /// Set the option chain filter for specific range of options.
1085    ///
1086    /// NOTE: Defaults to all `OptionStrikeRange::All`.
1087    ///
1088    /// E.g: Filter the chain for out of the money options:
1089    /// `OptionStrikeRange::OTM`.
1090    strike_range: Option<OptionStrikeRange>,
1091    /// Filters the spreads by a specific option type.
1092    option_type: Option<OptionType>,
1093}
1094impl OptionChainQueryBuilder {
1095    /// Create a new builder for `OptionChainQuery`
1096    pub fn new() -> Self {
1097        OptionChainQueryBuilder::default()
1098    }
1099
1100    /// Set the expiration date for an option chain.
1101    ///
1102    /// NOTE: This is required to be set before calling
1103    /// `OptionChainQueryBuilder::build()`.
1104    pub fn underlying<S: Into<String>>(mut self, symbol: S) -> Self {
1105        self.underlying = Some(symbol.into());
1106
1107        self
1108    }
1109
1110    /// Set the expiration date for an option chain.
1111    pub fn expiration<S: Into<String>>(mut self, date: S) -> Self {
1112        self.expiration = Some(date.into());
1113
1114        self
1115    }
1116
1117    /// Set the second expiration date for an option chain.
1118    ///
1119    /// NOTE: This is required for `OptionSpreadType::Calendar` and
1120    /// `OptionSpreadType::Diagonal` option spreads.
1121    pub fn expiration2<S: Into<String>>(mut self, date: S) -> Self {
1122        self.expiration2 = Some(date.into());
1123
1124        self
1125    }
1126
1127    /// Set the strike proximity (of the center price) for the option chain.
1128    pub fn strike_proximity(mut self, proximity: i32) -> Self {
1129        self.strike_proximity = Some(proximity);
1130
1131        self
1132    }
1133
1134    /// Set the spread type for the option chain
1135    pub fn spread_type(mut self, spread_type: OptionSpreadType) -> Self {
1136        self.spread_type = Some(spread_type);
1137
1138        self
1139    }
1140
1141    /// Set your risk free rate.
1142    pub fn risk_free_rate(mut self, rate: f64) -> Self {
1143        self.risk_free_rate = Some(rate);
1144
1145        self
1146    }
1147
1148    /// Set the center price point for the option chain.
1149    pub fn price_center(mut self, price: f64) -> Self {
1150        self.price_center = Some(price);
1151
1152        self
1153    }
1154
1155    /// Set the interval of strikes for the option chain.
1156    pub fn strike_interval(mut self, interval: i32) -> Self {
1157        self.strike_interval = Some(interval);
1158
1159        self
1160    }
1161
1162    /// Set if the option chain should contain greeks.
1163    pub fn enable_greeks(mut self, val: bool) -> Self {
1164        self.enable_greeks = Some(val);
1165
1166        self
1167    }
1168
1169    /// Set the option chain filter for specific range of options.
1170    pub fn strike_range(mut self, range: OptionStrikeRange) -> Self {
1171        self.strike_range = Some(range);
1172
1173        self
1174    }
1175
1176    /// Set the option chain filter for a specific option type.
1177    pub fn option_type(mut self, opt_type: OptionType) -> Self {
1178        self.option_type = Some(opt_type);
1179
1180        self
1181    }
1182
1183    /// Finish building `OptionChainQuery`.
1184    ///
1185    /// NOTE: Must set the `underlying` symbol before calling `build`.
1186    pub fn build(self) -> Result<OptionChainQuery, Error> {
1187        Ok(OptionChainQuery {
1188            underlying: self.underlying.ok_or_else(|| Error::SymbolNotSet)?,
1189            expiration: self.expiration,
1190            expiration2: self.expiration2,
1191            strike_range: self.strike_range.unwrap_or(OptionStrikeRange::All),
1192            option_type: self.option_type.unwrap_or(OptionType::All),
1193            enable_greeks: self.enable_greeks.unwrap_or(true),
1194            strike_interval: self.strike_interval.unwrap_or(1),
1195            spread_type: self.spread_type.unwrap_or(OptionSpreadType::Single),
1196            strike_proximity: self.strike_proximity.unwrap_or(5),
1197            price_center: self.price_center,
1198            risk_free_rate: self.risk_free_rate,
1199        })
1200    }
1201}
1202
1203#[derive(Clone, Debug, Deserialize, Serialize)]
1204/// The different types of option strike ranges.
1205pub enum OptionStrikeRange {
1206    /// A range containing all strikes
1207    All,
1208    /// A range containing In-The-Money strikes
1209    ITM,
1210    /// A range containing Out-of-The-Money strikes
1211    OTM,
1212}
1213
1214#[derive(Clone, Debug, Deserialize, Serialize)]
1215#[serde(rename_all = "PascalCase")]
1216/// A component of a larger options trade.
1217pub struct OptionSpreadLeg {
1218    /// Option contract symbol or underlying symbol to be traded for this leg.
1219    pub symbol: String,
1220    /// The number of option contracts or underlying shares for this leg,
1221    /// relative to the other legs. A positive number represents a buy trade
1222    /// and a negative number represents a sell trade.
1223    ///
1224    /// E.g, a Butterfly spread can be represented using ratios of 1, -2, and 1:
1225    /// buy 1 contract of the first leg, sell 2 contracts of the second leg, and
1226    /// buy 1 contract of the third leg.
1227    pub ratio: i32,
1228    /// The strike price of the option contract for this leg.
1229    pub strike_price: String,
1230    /// Date on which the contract expires.
1231    /// E.g: `2021-12-17T00:00:00Z`.
1232    pub expiration: String,
1233    /// The option type `MarketData::OptionType`
1234    pub option_type: OptionType,
1235    /// The asset category for this leg.
1236    pub asset_type: String,
1237}
1238
1239#[derive(Clone, Debug, Deserialize, Serialize)]
1240/// The different sides of the option chain.
1241pub enum OptionChainSide {
1242    /// The side of the option chain with call options.
1243    Call,
1244    /// The side of the option chain with put options.
1245    Put,
1246    /// The side of the option chain with
1247    /// both call and put options.
1248    Both,
1249}
1250
1251#[derive(Clone, Debug, Deserialize, Serialize)]
1252/// The type of option.
1253pub enum OptionType {
1254    /// Call Option
1255    Call,
1256    /// Put Option
1257    Put,
1258    /// All Options (Calls, and Puts)
1259    All,
1260}
1261
1262#[derive(Clone, Debug, Deserialize, Serialize)]
1263#[serde(rename_all = "PascalCase")]
1264pub struct OptionQuote {
1265    /// The expected change in an option position’s value resulting
1266    /// from a one point increase in the price of the underlying security.
1267    pub delta: Option<String>,
1268    /// The expected decline in an option position’s value resulting
1269    /// from the passage of one day’s time, holding all other variables
1270    /// (price of the underlying, volatility, etc.) constant.
1271    pub theta: Option<String>,
1272    /// The expected change in an option position’s delta resulting
1273    /// from a one point increase in the price of the underlying security.
1274    pub gamma: Option<String>,
1275    /// The expected change in an option position’s value resulting
1276    /// from an increase of one percentage point in the risk-free
1277    /// interest rate (e.g. an increase from 3% to 4%).
1278    pub rho: Option<String>,
1279    /// The expected change in an option position’s value resulting
1280    /// from an increase of one percentage point in the volatility of
1281    /// the underlying security (e.g. an increase from 26% to 27%).
1282    pub vega: Option<String>,
1283    /// The volatility of the underlying implied by an option
1284    /// position’s current price.
1285    pub implied_volatility: Option<String>,
1286    /// The value of an option position exclusive of the position’s
1287    /// time value. The value of the option position if it were to
1288    /// expire immediately.
1289    pub intrinsic_value: String,
1290    /// The time value of an option position.
1291    ///
1292    /// NOTE: The market value of an option position minus
1293    /// the position’s intrinsic value.
1294    pub extrinsic_value: String,
1295    /// The value of an option position based on a theoretical model
1296    /// of option prices (the Bjerksund-Stensland model).
1297    ///
1298    /// NOTE: Calculated using volatility of the underlying.
1299    pub theoretical_value: String,
1300    #[serde(rename = "ProbabilityITM")]
1301    /// The calculated probability that an option position will have
1302    /// intrinsic value at expiration.
1303    ///
1304    /// NOTE: Calculated using volatility of the underlying.
1305    pub probability_itm: Option<String>,
1306    #[serde(rename = "ProbabilityOTM")]
1307    /// The calculated probability that an option position will not have
1308    /// intrinsic value at expiration.
1309    ///
1310    /// NOTE: Calculated using volatility of the underlying.
1311    pub probability_otm: Option<String>,
1312    #[serde(rename = "ProbabilityBE")]
1313    /// The calculated probability that an option position will have
1314    /// a value at expiration that is equal to or greater than the
1315    /// position’s current cost.
1316    ///
1317    /// NOTE: Calculated using volatility of the underlying.
1318    pub probability_be: Option<String>,
1319    #[serde(rename = "ProbabilityITM_IV")]
1320    /// The calculated probability that an option position will have
1321    /// intrinsic value at expiration.
1322    ///
1323    /// NOTE: Calculated using implied volatility.
1324    pub probability_itm_iv: Option<String>,
1325    #[serde(rename = "ProbabilityOTM_IV")]
1326    /// The calculated probability that an option position will not
1327    /// have intrinsic value at expiration.
1328    ///
1329    /// NOTE: Calculated using implied volatility.
1330    pub probability_otm_iv: Option<String>,
1331    #[serde(rename = "ProbabilityBE_IV")]
1332    /// The calculated probability that an option position will have a
1333    /// value at expiration that is equal to or greater than the position’s
1334    /// current cost.
1335    ///
1336    /// NOTE: Calculated using implied volatility.
1337    pub probability_be_iv: Option<String>,
1338    #[serde(rename = "TheoreticalValueIV")]
1339    /// The value of an option position based on a theoretical model of
1340    /// option prices (the Bjerksund-Stensland model).
1341    ///
1342    /// NOTE: Calculated using implied volatility.
1343    pub theoretical_value_iv: Option<String>,
1344    /// Total number of open contracts for the option spread.
1345    ///
1346    /// NOTE: This value is updated daily.
1347    pub daily_open_interest: i32,
1348    /// Ask price. The price a seller is willing to accept for the option spread.
1349    pub ask: String,
1350    /// Bid price. The price a buyer is willing to pay for the option spread.
1351    pub bid: String,
1352    /// Average between `ask` and `bid`.
1353    pub mid: String,
1354    /// Amount of contracts at the given `ask` price.
1355    pub ask_size: i32,
1356    /// Amount of contracts at the given `bid` price.
1357    pub bid_size: i32,
1358    /// The last traded price for the option spread.
1359    ///
1360    /// NOTE: This value only updates during the official market session.
1361    pub close: String,
1362    /// Today's highest price for the option spread.
1363    pub high: String,
1364    /// The last traded price for the option spread.
1365    pub last: String,
1366    /// Today's lowest traded price for the option spread.
1367    pub low: String,
1368    /// Difference between prior Close price and current Close price for the
1369    /// option spread.
1370    pub net_change: String,
1371    /// Percentage changed between prior `close` price and current `close` price
1372    /// for the option spread.
1373    pub net_change_pct: String,
1374    /// The initial price for the option spread during the official market session.
1375    pub open: String,
1376    /// Prior day's Closing price.
1377    pub previous_close: String,
1378    /// The number of contracts traded today.
1379    pub volume: i32,
1380    /// The side of the option chain.
1381    pub side: OptionChainSide,
1382    /// The strike prices for the option contracts in the legs of this spread.
1383    pub strikes: Vec<String>,
1384    /// The legs of the option spread.
1385    pub legs: Vec<OptionSpreadLeg>,
1386}
1387impl OptionQuote {
1388    /// Stream quotes of an options spread for given a query `OptionQuoteQuery`.
1389    ///
1390    /// <div class="warning">WARNING: There's a max of 10 concurrent streams allowed.</div>
1391    ///
1392    /// NOTE: You need to provide a function to handle each stream chunk.
1393    ///
1394    /// # Example
1395    /// ---
1396    ///
1397    /// Example: Stream quotes on Iron Butterfly options trade on `"TLT"`
1398    /// expiring October 11th 2024. E.g: Say you just bought this iron buttefly
1399    /// now you can stream quotes on it to watch profit/loss, or take some kind
1400    /// of action based on market conditions.
1401    ///
1402    /// ```ignore
1403    /// let stream_tlt_iron_butterfly_query = MarketData::OptionQuoteQueryBuilder::new()
1404    ///     .legs(vec![
1405    ///         OptionQouteLeg {
1406    ///             symbol: "TLT 241011P93".into(),
1407    ///             ratio: -10,
1408    ///         },
1409    ///         OptionQouteLeg {
1410    ///             symbol: "TLT 241011P95.5".into(),
1411    ///             ratio: 10,
1412    ///         },
1413    ///         OptionQouteLeg {
1414    ///             symbol: "TLT 241011C95.5".into(),
1415    ///             ratio: 10,
1416    ///         },
1417    ///         OptionQouteLeg {
1418    ///             symbol: "TLT 241011C98".into(),
1419    ///             ratio: -10,
1420    ///         },
1421    ///     ])
1422    ///     // Using the 1 month us treasury
1423    ///     // to base the risk free rate off
1424    ///     // which is currently 4.85%
1425    ///     .risk_free_rate(0.0485)
1426    ///     .build()?;
1427    ///
1428    /// let streamed_quotes = client
1429    ///     .stream_option_quotes(&stream_tlt_iron_butterfly_query, |stream_data| {
1430    ///         // The response type is `responses::market_data::StreamOptionQuotesResp`
1431    ///         // which has multiple variants the main one you care about is `OptionQuotes`
1432    ///         // which will contain option chain data sent from the stream.
1433    ///         match stream_data {
1434    ///             StreamOptionQuotesResp::OptionQuotes(quote) => {
1435    ///                 // Do something with the option quote like
1436    ///                 // send a text / email alert based on some
1437    ///                 // data from the quote like a certain price,
1438    ///                 // market spread, volatility change, or something.
1439    ///                 println!("{quote:?}")
1440    ///             }
1441    ///             StreamOptionQuotesResp::Heartbeat(heartbeat) => {
1442    ///                 // Response for periodic signals letting you know the connection is
1443    ///                 // still alive. A heartbeat is sent every 5 seconds of inactivity.
1444    ///                 println!("{heartbeat:?}");
1445    ///
1446    ///                 // for the sake of this example after we recieve the
1447    ///                 // tenth heartbeat, we will stop the stream session.
1448    ///                 if heartbeat.heartbeat > 10 {
1449    ///                     // Example: stopping a stream connection
1450    ///                     return Err(Error::StopStream);
1451    ///                 }
1452    ///             }
1453    ///             StreamOptionQuotesResp::Status(status) => {
1454    ///                 // Signal sent on state changes in the stream
1455    ///                 // (closed, opened, paused, resumed)
1456    ///                 println!("{status:?}");
1457    ///             }
1458    ///             StreamOptionQuotesResp::Error(err) => {
1459    ///                 // Response for when an error was encountered,
1460    ///                 // with details on the error
1461    ///                 println!("{err:?}");
1462    ///             }
1463    ///         }
1464    ///
1465    ///         Ok(())
1466    ///     })
1467    ///     .await?;
1468    ///
1469    /// // After the stream ends print all the collected option quotes
1470    /// println!("{streamed_quotes:?}");
1471    /// ```
1472    pub async fn stream<F>(
1473        client: &mut Client,
1474        query: &OptionQuoteQuery,
1475        mut on_chunk: F,
1476    ) -> Result<Vec<OptionQuote>, Error>
1477    where
1478        F: FnMut(StreamOptionQuotesResp) -> Result<(), Error>,
1479    {
1480        let endpoint = format!(
1481            "marketdata/stream/options/quotes{}",
1482            query.as_query_string()
1483        );
1484        println!("endpoint: {endpoint}");
1485
1486        let mut collected_quotes: Vec<OptionQuote> = Vec::new();
1487        client
1488            .stream(&endpoint, |chunk| {
1489                let parsed_chunk = serde_json::from_value::<StreamOptionQuotesResp>(chunk)?;
1490                on_chunk(parsed_chunk.clone())?;
1491
1492                // Only collect orders, so when the stream is done
1493                // all the orders that were streamed can be returned
1494                if let StreamOptionQuotesResp::OptionQuotes(option_chain) = parsed_chunk {
1495                    collected_quotes.push(*option_chain);
1496                }
1497
1498                Ok(())
1499            })
1500            .await?;
1501
1502        Ok(collected_quotes)
1503    }
1504}
1505impl Client {
1506    /// Stream quotes of an options spread for given a query `OptionQuoteQuery`.
1507    ///
1508    /// <div class="warning">WARNING: There's a max of 10 concurrent streams allowed.</div>
1509    ///
1510    /// NOTE: You need to provide a function to handle each stream chunk.
1511    ///
1512    /// # Example
1513    /// ---
1514    ///
1515    /// Example: Stream quotes on Iron Butterfly options trade on `"TLT"`
1516    /// expiring October 11th 2024. E.g: Say you just bought this iron buttefly
1517    /// now you can stream quotes on it to watch profit/loss, or take some kind
1518    /// of action based on market conditions.
1519    ///
1520    /// ```ignore
1521    /// let stream_tlt_iron_butterfly_query = MarketData::OptionQuoteQueryBuilder::new()
1522    ///     .legs(vec![
1523    ///         OptionQouteLeg {
1524    ///             symbol: "TLT 241011P93".into(),
1525    ///             ratio: -10,
1526    ///         },
1527    ///         OptionQouteLeg {
1528    ///             symbol: "TLT 241011P95.5".into(),
1529    ///             ratio: 10,
1530    ///         },
1531    ///         OptionQouteLeg {
1532    ///             symbol: "TLT 241011C95.5".into(),
1533    ///             ratio: 10,
1534    ///         },
1535    ///         OptionQouteLeg {
1536    ///             symbol: "TLT 241011C98".into(),
1537    ///             ratio: -10,
1538    ///         },
1539    ///     ])
1540    ///     // Using the 1 month us treasury
1541    ///     // to base the risk free rate off
1542    ///     // which is currently 4.85%
1543    ///     .risk_free_rate(0.0485)
1544    ///     .build()?;
1545    ///
1546    /// let streamed_quotes = client
1547    ///     .stream_option_quotes(&stream_tlt_iron_butterfly_query, |stream_data| {
1548    ///         // The response type is `responses::market_data::StreamOptionQuotesResp`
1549    ///         // which has multiple variants the main one you care about is `OptionQuotes`
1550    ///         // which will contain option chain data sent from the stream.
1551    ///         match stream_data {
1552    ///             StreamOptionQuotesResp::OptionQuotes(quote) => {
1553    ///                 // Do something with the option quote like
1554    ///                 // send a text / email alert based on some
1555    ///                 // data from the quote like a certain price,
1556    ///                 // market spread, volatility change, or something.
1557    ///                 println!("{quote:?}")
1558    ///             }
1559    ///             StreamOptionQuotesResp::Heartbeat(heartbeat) => {
1560    ///                 // Response for periodic signals letting you know the connection is
1561    ///                 // still alive. A heartbeat is sent every 5 seconds of inactivity.
1562    ///                 println!("{heartbeat:?}");
1563    ///
1564    ///                 // for the sake of this example after we recieve the
1565    ///                 // tenth heartbeat, we will stop the stream session.
1566    ///                 if heartbeat.heartbeat > 10 {
1567    ///                     // Example: stopping a stream connection
1568    ///                     return Err(Error::StopStream);
1569    ///                 }
1570    ///             }
1571    ///             StreamOptionQuotesResp::Status(status) => {
1572    ///                 // Signal sent on state changes in the stream
1573    ///                 // (closed, opened, paused, resumed)
1574    ///                 println!("{status:?}");
1575    ///             }
1576    ///             StreamOptionQuotesResp::Error(err) => {
1577    ///                 // Response for when an error was encountered,
1578    ///                 // with details on the error
1579    ///                 println!("{err:?}");
1580    ///             }
1581    ///         }
1582    ///
1583    ///         Ok(())
1584    ///     })
1585    ///     .await?;
1586    ///
1587    /// // After the stream ends print all the collected option quotes
1588    /// println!("{streamed_quotes:?}");
1589    /// ```
1590    pub async fn stream_option_quotes<F>(
1591        &mut self,
1592        query: &OptionQuoteQuery,
1593        on_chunk: F,
1594    ) -> Result<Vec<OptionQuote>, Error>
1595    where
1596        F: FnMut(StreamOptionQuotesResp) -> Result<(), Error>,
1597    {
1598        OptionQuote::stream(self, query, on_chunk).await
1599    }
1600}
1601
1602#[derive(Clone, Debug, Deserialize, Serialize)]
1603#[serde(rename_all = "PascalCase")]
1604/// The query to stream quotes on an options spread.
1605pub struct OptionQuoteQuery {
1606    /// The individual positions making up a larger trade.
1607    pub legs: Vec<OptionQouteLeg>,
1608    /// The theoretical rate of return of an
1609    /// investment with zero risk.
1610    ///
1611    /// NOTE: Defaults to the current quote for `"$IRX.X"`
1612    /// (The 13 Week US Treasury).
1613    ///
1614    /// NOTE: The percentage rate should be specified as a decimal value.
1615    /// E.g: 2% = `0.02`.
1616    pub risk_free_rate: Option<f64>,
1617    /// Specifies whether or not greeks properties are returned.
1618    ///
1619    /// NOTE: Defaults to `true`.
1620    pub enable_greeks: bool,
1621}
1622impl OptionQuoteQuery {
1623    /// Convert the query into a string
1624    pub fn as_query_string(&self) -> String {
1625        let legs: Vec<String> = self
1626            .legs
1627            .iter()
1628            .enumerate()
1629            .map(|(idx, leg)| {
1630                format!(
1631                    "legs[{idx}].Symbol={}&legs[{idx}].Ratio={}",
1632                    leg.symbol, leg.ratio
1633                )
1634            })
1635            .collect();
1636
1637        let legs_string = legs.join("&");
1638
1639        if let Some(rate) = self.risk_free_rate {
1640            format!(
1641                "?{legs_string}&riskFreeRate={rate}&enableGreeks={}",
1642                self.enable_greeks
1643            )
1644        } else {
1645            format!("?{legs_string}&enableGreeks={}", self.enable_greeks)
1646        }
1647    }
1648}
1649
1650#[derive(Clone, Debug, Deserialize, Serialize)]
1651#[serde(rename_all = "PascalCase")]
1652/// The leg for an `OptionQuoteQuery`
1653pub struct OptionQouteLeg {
1654    /// Option contract symbol or underlying
1655    /// symbol to be traded for this leg.
1656    pub symbol: String,
1657    /// The number of option contracts or underlying
1658    /// shares for this leg, relative to the other legs.
1659    ///
1660    /// NOTE: Use a positive number to represent a buy trade
1661    /// and a negative number to represent a sell trade.
1662    pub ratio: i32,
1663}
1664
1665#[derive(Default)]
1666/// Builder for `OptionQuoteQuery`
1667pub struct OptionQuoteQueryBuilder {
1668    legs: Option<Vec<OptionQouteLeg>>,
1669    risk_free_rate: Option<f64>,
1670    enable_greeks: Option<bool>,
1671}
1672impl OptionQuoteQueryBuilder {
1673    /// Create a new builder for `OptionQuoteQuery`.
1674    pub fn new() -> Self {
1675        Self::default()
1676    }
1677
1678    /// Set the option legs (each option position in the overall trade).
1679    ///
1680    /// NOTE: For a trade with just a single option you still need to pass
1681    /// the option as a leg.
1682    pub fn legs(mut self, option_legs: Vec<OptionQouteLeg>) -> Self {
1683        self.legs = Some(option_legs);
1684
1685        self
1686    }
1687
1688    /// Set your risk free rate.
1689    pub fn risk_free_rate(mut self, rate: f64) -> Self {
1690        self.risk_free_rate = Some(rate);
1691
1692        self
1693    }
1694
1695    /// Set if the option chain should contain greeks.
1696    pub fn enable_greeks(mut self, enable: bool) -> Self {
1697        self.enable_greeks = Some(enable);
1698
1699        self
1700    }
1701
1702    /// Finish building `OptionQuoteQuery`
1703    pub fn build(self) -> Result<OptionQuoteQuery, Error> {
1704        Ok(OptionQuoteQuery {
1705            legs: self.legs.ok_or_else(|| Error::OptionLegsNotSet)?,
1706            enable_greeks: self.enable_greeks.unwrap_or(true),
1707            risk_free_rate: self.risk_free_rate,
1708        })
1709    }
1710}