Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements security definition options parameters #153

Merged
merged 6 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/contract_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn main() -> anyhow::Result<()> {
println!("connection_time: {:?}", client.connection_time());
println!("next_order_id: {}", client.next_order_id());

let mut contract = Contract::stock("TSLA");
let mut contract = Contract::stock("AAPL");
contract.currency = "USD".to_string();

let results = client.contract_details(&contract)?;
Expand Down
22 changes: 22 additions & 0 deletions examples/option_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use ibapi::{contracts::SecurityType, Client};

// This example demonstrates requesting option chain data from the TWS.

fn main() {
env_logger::init();

let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");

let symbol = "AAPL";
let exchange = ""; // all exchanges
let security_type = SecurityType::Stock;
let contract_id = 265598;

let subscription = client
.option_chain(symbol, exchange, security_type, contract_id)
.expect("request option chain failed!");

for option_chain in &subscription {
println!("{option_chain:?}")
}
}
40 changes: 39 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use time::OffsetDateTime;
use time_tz::Tz;

use crate::accounts::{AccountSummaries, AccountUpdate, AccountUpdateMulti, FamilyCode, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti};
use crate::contracts::{Contract, OptionComputation};
use crate::contracts::{Contract, OptionComputation, SecurityType};
use crate::errors::Error;
use crate::market_data::historical::{self, HistogramEntry};
use crate::market_data::realtime::{self, Bar, BarSize, DepthMarketDataDescription, MarketDepths, MidPoint, TickTypes, WhatToShow};
Expand Down Expand Up @@ -440,6 +440,44 @@ impl Client {
contracts::calculate_implied_volatility(self, contract, option_price, underlying_price)
}

/// Requests security definition option parameters for viewing a contract’s option chain.
///
/// # Arguments
/// `symbol` - Contract symbol of the underlying.
/// `exchange` - The exchange on which the returned options are trading. Can be set to the empty string for all exchanges.
/// `security_type` - The type of the underlying security, i.e. STK
/// `contract_id` - The contract ID of the underlying security.
///
/// # Examples
///
/// ```no_run
/// use ibapi::{contracts::SecurityType, Client};
///
/// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
///
/// let symbol = "AAPL";
/// let exchange = ""; // all exchanges
/// let security_type = SecurityType::Stock;
/// let contract_id = 265598;
///
/// let subscription = client
/// .option_chain(symbol, exchange, security_type, contract_id)
/// .expect("request option chain failed!");
///
/// for option_chain in &subscription {
/// println!("{option_chain:?}")
/// }
/// ```
pub fn option_chain(
&self,
symbol: &str,
exchange: &str,
security_type: SecurityType,
contract_id: i32,
) -> Result<Subscription<contracts::OptionChain>, Error> {
contracts::option_chain(self, symbol, exchange, security_type, contract_id)
}

// === Orders ===

/// Requests all *current* open orders in associated accounts at the current moment.
Expand Down
67 changes: 58 additions & 9 deletions src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use tick_types::TickType;

use crate::client::DataStream;
use crate::client::ResponseContext;
use crate::client::Subscription;
use crate::encode_option_field;
use crate::messages::IncomingMessages;
use crate::messages::OutgoingMessages;
Expand Down Expand Up @@ -448,6 +449,32 @@ impl DataStream<OptionComputation> for OptionComputation {
}
}

#[derive(Debug, Default)]
pub struct OptionChain {
/// The contract ID of the underlying security.
pub underlying_contract_id: i32,
/// The option trading class.
pub trading_class: String,
/// The option multiplier.
pub multiplier: String,
/// Exchange for which the derivative is hosted.
pub exchange: String,
/// A list of the expiries for the options of this underlying on this exchange.
pub expirations: Vec<String>,
/// A list of the possible strikes for options of this underlying on this exchange.
pub strikes: Vec<f64>,
}

impl DataStream<OptionChain> for OptionChain {
fn decode(_client: &Client, message: &mut ResponseMessage) -> Result<OptionChain, Error> {
match message.message_type() {
IncomingMessages::SecurityDefinitionOptionParameter => Ok(decoders::decode_option_chain(message)?),
IncomingMessages::SecurityDefinitionOptionParameterEnd => Err(Error::EndOfStream),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}

// === API ===

// Requests contract information.
Expand All @@ -457,7 +484,7 @@ impl DataStream<OptionComputation> for OptionComputation {
// # Arguments
// * `client` - [Client] with an active connection to gateway.
// * `contract` - The [Contract] used as sample to query the available contracts. Typically, it will contain the [Contract]'s symbol, currency, security_type, and exchange.
pub(crate) fn contract_details(client: &Client, contract: &Contract) -> Result<Vec<ContractDetails>, Error> {
pub(super) fn contract_details(client: &Client, contract: &Contract) -> Result<Vec<ContractDetails>, Error> {
verify_contract(client, contract)?;

let request_id = client.next_request_id();
Expand Down Expand Up @@ -534,7 +561,7 @@ pub struct ContractDescription {
// # Arguments
// * `client` - [Client] with an active connection to gateway.
// * `pattern` - Either start of ticker symbol or (for larger strings) company name.
pub(crate) fn matching_symbols(client: &Client, pattern: &str) -> Result<Vec<ContractDescription>, Error> {
pub(super) fn matching_symbols(client: &Client, pattern: &str) -> Result<Vec<ContractDescription>, Error> {
client.check_server_version(server_versions::REQ_MATCHING_SYMBOLS, "It does not support matching symbols requests.")?;

let request_id = client.next_request_id();
Expand Down Expand Up @@ -562,8 +589,11 @@ pub(crate) fn matching_symbols(client: &Client, pattern: &str) -> Result<Vec<Con
}

#[derive(Debug, Default)]
/// Minimum price increment structure for a particular market rule ID.
pub struct MarketRule {
/// Market Rule ID requested.
pub market_rule_id: i32,
/// Returns the available price increments based on the market rule.
pub price_increments: Vec<PriceIncrement>,
}

Expand All @@ -573,11 +603,11 @@ pub struct PriceIncrement {
pub increment: f64,
}

/// Requests details about a given market rule
///
/// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
/// A list of market rule ids can be obtained by invoking [request_contract_details] on a particular contract. The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in [ContractDetails].
pub(crate) fn market_rule(client: &Client, market_rule_id: i32) -> Result<MarketRule, Error> {
// Requests details about a given market rule
//
// The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price.
// A list of market rule ids can be obtained by invoking [request_contract_details] on a particular contract. The returned market rule ID list will provide the market rule ID for the instrument in the correspond valid exchange list in [ContractDetails].
pub(super) fn market_rule(client: &Client, market_rule_id: i32) -> Result<MarketRule, Error> {
client.check_server_version(server_versions::MARKET_RULES, "It does not support market rule requests.")?;

let request = encoders::encode_request_market_rule(market_rule_id)?;
Expand All @@ -596,7 +626,7 @@ pub(crate) fn market_rule(client: &Client, market_rule_id: i32) -> Result<Market
// * `contract` - The [Contract] object for which the depth is being requested.
// * `volatility` - Hypothetical volatility.
// * `underlying_price` - Hypothetical option’s underlying price.
pub(crate) fn calculate_option_price(
pub(super) fn calculate_option_price(
client: &Client,
contract: &Contract,
volatility: f64,
Expand All @@ -621,7 +651,7 @@ pub(crate) fn calculate_option_price(
// * `contract` - The [Contract] object for which the depth is being requested.
// * `option_price` - Hypothetical option price.
// * `underlying_price` - Hypothetical option’s underlying price.
pub(crate) fn calculate_implied_volatility(
pub(super) fn calculate_implied_volatility(
client: &Client,
contract: &Contract,
option_price: f64,
Expand All @@ -642,3 +672,22 @@ pub(crate) fn calculate_implied_volatility(
None => Err(Error::Simple("no data for option calculation".into())),
}
}

pub(super) fn option_chain<'a>(
client: &'a Client,
symbol: &str,
exchange: &str,
security_type: SecurityType,
contract_id: i32,
) -> Result<Subscription<'a, OptionChain>, Error> {
client.check_server_version(
server_versions::SEC_DEF_OPT_PARAMS_REQ,
"It does not support security definition option parameters.",
)?;

let request_id = client.next_request_id();
let request = encoders::encode_request_option_chain(request_id, symbol, exchange, security_type, contract_id)?;
let subscription = client.send_request(request_id, request)?;

Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
33 changes: 30 additions & 3 deletions src/contracts/decoders.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{contracts::tick_types::TickType, contracts::SecurityType, messages::ResponseMessage, orders::TagValue, server_versions, Error};

use super::{Contract, ContractDescription, ContractDetails, MarketRule, OptionComputation, PriceIncrement};
use super::{Contract, ContractDescription, ContractDetails, MarketRule, OptionChain, OptionComputation, PriceIncrement};

#[cfg(test)]
mod tests;

pub(super) fn decode_contract_details(server_version: i32, message: &mut ResponseMessage) -> Result<ContractDetails, Error> {
message.skip(); // message type
Expand Down Expand Up @@ -232,5 +235,29 @@ fn next_optional_double(message: &mut ResponseMessage, none_value: f64) -> Resul
}
}

#[cfg(test)]
mod tests;
pub(super) fn decode_option_chain(message: &mut ResponseMessage) -> Result<OptionChain, Error> {
message.skip(); // message type
message.skip(); // request id

let mut option_chain = OptionChain {
exchange: message.next_string()?,
underlying_contract_id: message.next_int()?,
trading_class: message.next_string()?,
multiplier: message.next_string()?,
..Default::default()
};

let expirations_count = message.next_int()?;
option_chain.expirations.reserve(expirations_count as usize);
for _ in 0..expirations_count {
option_chain.expirations.push(message.next_string()?);
}

let strikes_count = message.next_int()?;
option_chain.strikes.reserve(strikes_count as usize);
for _ in 0..strikes_count {
option_chain.strikes.push(message.next_double()?);
}

Ok(option_chain)
}
24 changes: 22 additions & 2 deletions src/contracts/encoders.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use super::Contract;
use super::SecurityType;
use crate::messages::OutgoingMessages;
use crate::messages::RequestMessage;
use crate::{server_versions, Error};

#[cfg(test)]
mod tests;

pub(crate) fn encode_request_contract_data(server_version: i32, request_id: i32, contract: &Contract) -> Result<RequestMessage, Error> {
const VERSION: i32 = 8;

Expand Down Expand Up @@ -152,5 +156,21 @@ pub(crate) fn encode_cancel_option_computation(message_type: OutgoingMessages, r
Ok(message)
}

#[cfg(test)]
mod tests;
pub(super) fn encode_request_option_chain(
request_id: i32,
symbol: &str,
exchange: &str,
security_type: SecurityType,
contract_id: i32,
) -> Result<RequestMessage, Error> {
let mut message = RequestMessage::default();

message.push_field(&OutgoingMessages::RequestSecurityDefinitionOptionalParameters);
message.push_field(&request_id);
message.push_field(&symbol);
message.push_field(&exchange);
message.push_field(&security_type);
message.push_field(&contract_id);

Ok(message)
}
2 changes: 2 additions & 0 deletions src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ pub fn request_id_index(kind: IncomingMessages) -> Option<usize> {
| IncomingMessages::TickNews
| IncomingMessages::PnL
| IncomingMessages::PnLSingle
| IncomingMessages::SecurityDefinitionOptionParameter
| IncomingMessages::SecurityDefinitionOptionParameterEnd
| IncomingMessages::HistogramData
| IncomingMessages::TickOptionComputation => Some(1),
IncomingMessages::ContractDataEnd
Expand Down
Loading