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}