├── rustfmt.toml ├── ibkr_rust_macros ├── .gitignore ├── Cargo.toml └── src │ ├── lib.rs │ ├── variant_value.rs │ ├── getters.rs │ ├── debug_trait.rs │ ├── send_trait.rs │ └── security.rs ├── Functions to implement.xlsx ├── .gitignore ├── src ├── constants.rs ├── prelude.rs ├── reader.rs ├── lib.rs ├── currency.rs ├── execution.rs ├── message.rs ├── figi.rs ├── wrapper.rs ├── comm.rs ├── tick.rs ├── payload.rs └── account.rs ├── config.toml ├── README.md ├── Cargo.toml ├── tests ├── historical_data.rs ├── execution_filters.rs ├── wrapper_varieties.rs └── account_portfolio.rs ├── eclient_methods.csv ├── codes and names.csv ├── Functions to implement.csv └── LICENSE /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | -------------------------------------------------------------------------------- /ibkr_rust_macros/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Functions to implement.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wvietor/ibkr_rust/HEAD/Functions to implement.xlsx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # contains binaries and other non-source code 2 | target/ 3 | .idea/ 4 | 5 | # macos files 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const MIN_CLIENT_VERSION: u8 = 180; 2 | pub const MAX_CLIENT_VERSION: u8 = 180; 3 | pub const TO_CLIENT_CHANNEL_SIZE: usize = 10; 4 | pub const TO_WRAPPER_CHANNEL_SIZE: usize = 10; 5 | pub const FROM_READER_CHANNEL_SIZE: usize = 20; 6 | pub const OUT_MESSAGE_SIZE: usize = 512; 7 | pub const ORDER_TUPLE_SIZE: usize = 98; 8 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | address = "127.0.0.1" 2 | 3 | [Ports] 4 | tws_live = 7496 5 | tws_paper = 7497 6 | 7 | gateway_live = 4001 8 | gateway_paper = 4002 9 | 10 | # ============= 11 | # === Usage === 12 | # ============= 13 | # 14 | # address: std::net::Ipv4Addr 15 | # 16 | # [Ports] 17 | # tws_live: u16 18 | # tws_paper: u16 19 | 20 | # gateway_live: u16 21 | # gateway_paper: u16 -------------------------------------------------------------------------------- /ibkr_rust_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibapi_macros" 3 | version = "0.1.0" 4 | authors = ["William Vietor "] 5 | description = "A `cargo generate` template for quick-starting a procedural macro crate" 6 | keywords = ["template", "proc_macro", "procmacro"] 7 | edition = "2021" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | quote = "1.0.37" 14 | proc-macro2 = "1.0.86" 15 | syn = { version = "2.0.77", features = ["full"] } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ibkr-tws-api 2 | Rust port of Interactive Brokers' Trader Workstation API 3 | 4 | # IBKR Platform Compatibility 5 | Most of the functionality should work on any API version >10.16. However, there are some issues with some of the functionality (notably, executions and position_summary) with older versions. At present, tests are passing with API version 10.31. In general, the "Latest" version as specified on [IBKR 's website](https://interactivebrokers.github.io) will be prioritized. 6 | 7 | # Important Notes 8 | The `local` client types are currently unstable due to a weird bug where a tokio channel somehow gets closed. I'm working to fix the bug and will update this when done. 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ibapi" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "A Rust port of the Interactive Brokers TWS API." 7 | repository = "https://github.com/wvietor/ibkr-tws-api" 8 | keywords = ["trading", "interactive_brokers", "IBKR"] 9 | categories = ["finance"] 10 | 11 | 12 | [dependencies] 13 | ibapi_macros = { version="0.1.0", path= "ibkr_rust_macros" } 14 | tokio = { version = "1.40.0", features = ["full"] } 15 | tokio-util = { version = "0.7.12", features = ["full"] } 16 | toml = "0.8.19" 17 | serde = { version = "1.0.210", features = ["derive"] } 18 | chrono = { version = "0.4.38", features = ["serde"] } 19 | chrono-tz = { version = "0.10.0" } 20 | bytes = "1.7.2" 21 | itoa = "1.0.11" 22 | ryu = "1.0.18" 23 | trait-variant = "0.1.2" 24 | thiserror = "1.0.64" 25 | tracing = "0.1.40" 26 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::account::{Attribute, Tag, TagValue}; 2 | pub use crate::client::{ActiveClient, Builder, Client, Host, Mode}; 3 | pub use crate::contract::{ 4 | self, Commodity, Contract, ContractId, ContractType, Crypto, ExchangeProxy, Forex, Index, 5 | NoExchangeProxy, Query, SecFuture, SecOption, SecOptionClass, SecOptionInner, Security, Stock, 6 | }; 7 | pub use crate::currency::Currency; 8 | pub use crate::exchange; 9 | pub use crate::execution::{Exec, Execution, Filter, OrderSide}; 10 | pub use crate::figi::Figi; 11 | pub use crate::market_data::{ 12 | histogram, historical_bar, historical_ticks, live_bar, live_data, live_ticks, 13 | updating_historical_bar, 14 | }; 15 | pub use crate::order::{Limit, Market, Order, TimeInForce}; 16 | pub use crate::payload::{ 17 | Bar, BarCore, BidAsk, ExchangeId, Fill, HistogramEntry, Last, Midpoint, OrderStatus, 18 | OrderStatusCore, Pnl, PnlSingle, Position, PositionSummary, TickData, Trade, 19 | }; 20 | pub use crate::payload::market_depth::{CompleteEntry, Entry, Mpid, Operation, Row}; 21 | pub use crate::tick; 22 | pub use crate::wrapper::{CancelToken, Initializer, Recurring, Wrapper}; 23 | 24 | -------------------------------------------------------------------------------- /ibkr_rust_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use quote::ToTokens; 4 | 5 | mod debug_trait; 6 | mod getters; 7 | mod security; 8 | mod send_trait; 9 | mod variant_value; 10 | 11 | #[allow(clippy::missing_panics_doc)] 12 | #[proc_macro_derive(Security)] 13 | pub fn security_derive(input: TokenStream) -> TokenStream { 14 | let ast = syn::parse(input).unwrap(); 15 | 16 | security::impl_security(&ast).into() 17 | } 18 | 19 | #[allow(clippy::missing_panics_doc)] 20 | #[proc_macro_attribute] 21 | pub fn debug_trait(_attr: TokenStream, item: TokenStream) -> TokenStream { 22 | let mut ast = syn::parse(item).unwrap(); 23 | 24 | debug_trait::impl_debug_trait(&mut ast); 25 | ast.into_token_stream().into() 26 | } 27 | 28 | #[allow(clippy::missing_panics_doc)] 29 | #[proc_macro_attribute] 30 | pub fn make_send(attr: TokenStream, item: TokenStream) -> TokenStream { 31 | send_trait::impl_make_send(attr, item.clone()) 32 | } 33 | 34 | #[allow(clippy::missing_panics_doc)] 35 | #[proc_macro_attribute] 36 | pub fn typed_variants(_attr: TokenStream, item: TokenStream) -> TokenStream { 37 | let mut ast = syn::parse(item).unwrap(); 38 | 39 | variant_value::impl_typed_variants(&mut ast).into() 40 | } 41 | 42 | #[allow(clippy::missing_panics_doc)] 43 | #[proc_macro_attribute] 44 | pub fn make_getters(_attr: TokenStream, item: TokenStream) -> TokenStream { 45 | let mut ast = syn::parse(item).unwrap(); 46 | 47 | getters::impl_make_getters(&mut ast).into() 48 | } 49 | -------------------------------------------------------------------------------- /tests/historical_data.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use tokio_util::time::FutureExt; 4 | 5 | use ibapi::prelude::*; 6 | 7 | struct ChannelWrapper { 8 | tx: tokio::sync::mpsc::Sender<(i64, Vec)>, 9 | } 10 | 11 | impl Wrapper for ChannelWrapper { 12 | fn historical_bars( 13 | &mut self, 14 | req_id: i64, 15 | _start_datetime: chrono::DateTime, 16 | _end_datetime: chrono::DateTime, 17 | bars: Vec, 18 | ) -> impl Future + Send { 19 | async move { 20 | let _ = self.tx.send((req_id, bars)).await; 21 | } 22 | } 23 | } 24 | 25 | impl Recurring for ChannelWrapper { 26 | async fn cycle(&mut self) {} 27 | } 28 | 29 | #[tokio::test] 30 | async fn spy_bars() -> Result<(), Box> { 31 | let (tx, mut rx) = tokio::sync::mpsc::channel(1); 32 | let mut client = Builder::from_config_file(Mode::Paper, Host::Gateway, &Some("config.toml"))? 33 | .connect(8) 34 | .await? 35 | .disaggregated(ChannelWrapper { tx }) 36 | .await; 37 | 38 | let spy = contract::new::(&mut client, "BBG000BDTBL9".parse()?).await?; 39 | let id = client 40 | .req_historical_bar( 41 | &spy, 42 | historical_bar::EndDateTime::Present, 43 | historical_bar::Duration::Week(1), 44 | historical_bar::Size::Minutes(historical_bar::MinuteSize::Fifteen), 45 | historical_bar::Trades, 46 | false, 47 | ) 48 | .await?; 49 | if let Some(msg) = rx 50 | .recv() 51 | .timeout(std::time::Duration::from_secs(15)) 52 | .await? 53 | { 54 | assert_eq!(id, msg.0); 55 | println!("{:?}", msg.1.first()); 56 | } 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /ibkr_rust_macros/src/variant_value.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::{Attribute, Generics, ItemEnum, parse_quote, Token}; 4 | 5 | fn make_struct( 6 | ident: syn::Ident, 7 | attrs: Vec, 8 | vis: syn::Visibility, 9 | fields: syn::Fields, 10 | ) -> syn::ItemStruct { 11 | let span = ident.span(); 12 | let semi_token = match fields { 13 | syn::Fields::Named(_) => None, 14 | _ => Some(Token![;](span)), 15 | }; 16 | syn::ItemStruct { 17 | attrs, 18 | vis, 19 | struct_token: Token![struct](span), 20 | ident, 21 | generics: Generics::default(), 22 | fields, 23 | semi_token, 24 | } 25 | } 26 | 27 | pub fn impl_typed_variants(ast: &mut ItemEnum) -> proc_macro2::TokenStream { 28 | let mut out_stream = TokenStream::new(); 29 | 30 | for new_struct in ast.variants.iter().map(|var| { 31 | let mut attrs = ast 32 | .attrs 33 | .clone() 34 | .into_iter() 35 | .filter(|a| match a.meta { 36 | syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) => !path.is_ident("doc"), 37 | _ => true, 38 | }) 39 | .collect::>(); 40 | attrs.extend(var.attrs.clone()); 41 | make_struct( 42 | var.ident.clone(), 43 | attrs, 44 | ast.vis.clone(), 45 | var.fields.clone(), 46 | ) 47 | }) { 48 | new_struct.to_tokens(&mut out_stream); 49 | } 50 | 51 | for var in &mut ast.variants { 52 | let name = &var.ident; 53 | let mut new: syn::Variant = parse_quote! { #name(#name) }; 54 | new.attrs.clone_from(&var.attrs); 55 | *var = new; 56 | } 57 | ast.to_tokens(&mut out_stream); 58 | 59 | out_stream 60 | } 61 | -------------------------------------------------------------------------------- /tests/execution_filters.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use ibapi::prelude::*; 3 | 4 | 5 | enum ExecutionMessage { 6 | Response(i64, Execution), 7 | Finished(i64), 8 | } 9 | 10 | struct ExecutionWrapper { 11 | tx: tokio::sync::mpsc::Sender, 12 | } 13 | 14 | impl Wrapper for ExecutionWrapper { 15 | fn execution(&mut self, req_id: i64, execution: Execution) -> impl Future + Send { 16 | async move { 17 | self.tx.send(ExecutionMessage::Response(req_id, execution)).await.expect("sending the execution details should succeed"); 18 | } 19 | } 20 | 21 | fn execution_details_end(&mut self, req_id: i64) -> impl Future + Send { 22 | async move { 23 | self.tx.send(ExecutionMessage::Finished(req_id)).await.expect("sending the req_id should succeed") 24 | } 25 | } 26 | } 27 | 28 | #[tokio::test] 29 | async fn execs() -> Result<(), Box> { 30 | let (tx, mut rx) = tokio::sync::mpsc::channel(1); 31 | let mut client = Builder::from_config_file(Mode::Paper, Host::Gateway, &None::)? 32 | .connect(9) 33 | .await? 34 | .disaggregated(ExecutionWrapper { tx }) 35 | .await; 36 | 37 | let id = client.req_executions(Filter { side: Some(OrderSide::Buy), ..Default::default() }).await?; 38 | while let Some(msg) = rx.recv().await { 39 | match msg { 40 | ExecutionMessage::Response(req_id, execution) => { 41 | assert_eq!(req_id, id); 42 | println!("One execution was: {:?}", &execution); 43 | }, 44 | ExecutionMessage::Finished(req_id) => { 45 | assert_eq!(req_id, id); 46 | println!("No more executions to receiver for ID {id}"); 47 | break; 48 | } 49 | } 50 | } 51 | client.disconnect().await.expect("disconnect should succeed."); 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /ibkr_rust_macros/src/getters.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, TokenStream}; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ItemStruct, parse_quote, Type}; 4 | 5 | fn impl_method( 6 | struct_name: &Ident, 7 | meth_name: &Ident, 8 | return_type: &Type, 9 | vis: &syn::Visibility, 10 | can_move: bool, 11 | ) -> TokenStream { 12 | let d = format!("Get the {struct_name}'s {meth_name}.\n\n # Returns\n The {meth_name}"); 13 | let doc: syn::Attribute = parse_quote!(#[doc = #d]); 14 | 15 | let body = if can_move { 16 | quote! { 17 | #vis fn #meth_name(&self) -> #return_type { 18 | self.#meth_name 19 | } 20 | } 21 | } else { 22 | quote! { 23 | #vis fn #meth_name(&self) -> &#return_type { 24 | &self.#meth_name 25 | } 26 | } 27 | }; 28 | 29 | quote! { 30 | #[must_use] 31 | #[inline] 32 | #doc 33 | #body 34 | } 35 | } 36 | 37 | #[allow(clippy::module_name_repetitions)] 38 | pub fn impl_make_getters(ast: &mut ItemStruct) -> TokenStream { 39 | let mut out_stream = TokenStream::new(); 40 | 41 | let ItemStruct { 42 | ident: ref s_name, 43 | ref vis, 44 | ref fields, 45 | .. 46 | } = ast; 47 | let meths = fields 48 | .iter() 49 | .filter_map(|f| { 50 | if let Some(ref meth_name) = f.ident { 51 | let r_type_str = f.ty.to_token_stream().to_string(); 52 | let (r_type, can_move) = match r_type_str.as_str() { 53 | "String" => (parse_quote! { str }, false), 54 | s if s.starts_with("Vec < ") => (f.ty.clone(), false), 55 | _ => (f.ty.clone(), true), 56 | }; 57 | 58 | Some(impl_method(s_name, meth_name, &r_type, vis, can_move)) 59 | } else { 60 | None 61 | } 62 | }) 63 | .collect::>(); 64 | 65 | let getter_impl = quote! { 66 | impl #s_name { 67 | #( #meths )* 68 | } 69 | }; 70 | ast.to_tokens(&mut out_stream); 71 | getter_impl.to_tokens(&mut out_stream); 72 | 73 | out_stream 74 | } 75 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BytesMut}; 2 | use tokio::{io::AsyncReadExt, net::tcp::OwnedReadHalf}; 3 | use tracing::{error, info, warn}; 4 | 5 | #[derive(Debug)] 6 | pub struct Reader { 7 | inner: OwnedReadHalf, 8 | tx: tokio::sync::mpsc::Sender>, 9 | disconnect: tokio_util::sync::CancellationToken, 10 | } 11 | 12 | impl Reader { 13 | pub fn new( 14 | r_reader: OwnedReadHalf, 15 | tx: tokio::sync::mpsc::Sender>, 16 | r_disconnect: tokio_util::sync::CancellationToken, 17 | ) -> Self { 18 | Self { 19 | inner: r_reader, 20 | tx, 21 | disconnect: r_disconnect, 22 | } 23 | } 24 | 25 | #[tracing::instrument(level = tracing::Level::DEBUG)] 26 | pub async fn run(mut self) -> Self { 27 | loop { 28 | tokio::select! { 29 | biased; 30 | () = async { 31 | if let Ok(Ok(len)) = self.inner.read_u32().await.map(usize::try_from) { 32 | let mut buf = BytesMut::with_capacity(len); 33 | let mut total_read = 0; 34 | while total_read < len { 35 | match self.inner.read_buf(&mut buf).await { 36 | Ok(0) => { warn!("TCP Reader read 0 bytes (this should never happen and is likely an error in message parsing)") }, 37 | Ok(n) => { total_read += n; }, 38 | Err(e) => error!(error=%e, "IO Error when receiving message.") 39 | } 40 | } 41 | let msg = buf.chunk() 42 | .split(|b| *b == 0) 43 | .map(|s| core::str::from_utf8(s).unwrap_or("").to_owned()) 44 | .collect::>(); 45 | match self.tx.send(msg).await { 46 | Ok(()) => (), 47 | Err(e) => error!(%e, "IO Error when sending message. Client receiver may have dropped."), 48 | } 49 | } 50 | } => (), 51 | () = self.disconnect.cancelled() => { info!("Reader thread: disconnecting"); break self} , 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ibkr_rust_macros/src/debug_trait.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{FnArg, ItemTrait, Pat, TraitItem}; 4 | 5 | #[allow(clippy::module_name_repetitions)] 6 | pub fn impl_debug_trait(ast: &mut ItemTrait) { 7 | for trait_item in &mut ast.items { 8 | let (signature, default_func) = match trait_item { 9 | TraitItem::Fn(trait_func) => match trait_func.default { 10 | Some(ref mut default_func) => (&trait_func.sig, default_func), 11 | None => continue, 12 | }, 13 | _ => continue, 14 | }; 15 | 16 | let args = signature 17 | .inputs 18 | .iter() 19 | .filter_map(|arg| match arg { 20 | FnArg::Typed(typed_arg) => Some(&*typed_arg.pat), 21 | FnArg::Receiver(_) => None, 22 | }) 23 | .filter_map(|arg_name| match arg_name { 24 | Pat::Ident(ident) => { 25 | let id = &ident.ident; 26 | Some(quote! { &#id, }) 27 | } 28 | _ => None, 29 | }) 30 | .collect::>(); 31 | 32 | let prefix = format!("[Wrapper::{}]", signature.ident); 33 | let args = TokenStream::from_iter(args); 34 | 35 | default_func.stmts.push(syn::Stmt::Expr( 36 | syn::Expr::Async(syn::ExprAsync { 37 | attrs: vec![], 38 | async_token: syn::token::Async::default(), 39 | capture: Some(syn::parse(quote! { move }.into()).unwrap()), 40 | block: syn::Block { 41 | brace_token: syn::token::Brace::default(), 42 | stmts: vec![ 43 | syn::parse(quote! { eprint!("{}", #prefix); }.into()).unwrap(), 44 | syn::parse(quote! { dbg!((#args)); }.into()).unwrap(), 45 | // syn::parse(quote! { return ().into(); }.into()).unwrap() 46 | ], 47 | }, 48 | }), 49 | None, 50 | )); 51 | 52 | // for stmt in vec![ 53 | // syn::parse(quote! { eprint!("{}", #prefix); }.into()).unwrap(), 54 | // syn::parse(quote! { dbg!((#args)); }.into()).unwrap(), 55 | // ] { 56 | // default_func.stmts.push(stmt); 57 | // } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/wrapper_varieties.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use ibapi::client::{ActiveClient, Builder, Host, Mode}; 4 | use ibapi::wrapper::{ 5 | CancelToken, Initializer, LocalInitializer, LocalRecurring, LocalWrapper, Recurring, Wrapper, 6 | }; 7 | 8 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] 9 | struct SendWrapper; 10 | 11 | impl Wrapper for SendWrapper {} 12 | 13 | impl Recurring for SendWrapper { 14 | fn cycle(&mut self) -> impl Future + Send { 15 | async { () } 16 | } 17 | } 18 | 19 | impl Initializer for SendWrapper { 20 | type Wrap<'c> = SendWrapper; 21 | fn build( 22 | self, 23 | client: &mut ActiveClient, 24 | _cancel_loop: CancelToken, 25 | ) -> impl Future> + Send { 26 | async move { 27 | let aapl: ibapi::contract::Stock = 28 | ibapi::contract::new(client, "BBG000B9XRY4".parse().unwrap()) 29 | .await 30 | .unwrap(); 31 | assert_eq!(aapl.symbol(), "AAPL"); 32 | self 33 | } 34 | } 35 | } 36 | 37 | #[derive(Debug, Default, Clone)] 38 | struct NonSendWrapper { 39 | cancel_loop: CancelToken, 40 | _non_send: std::rc::Rc, 41 | } 42 | 43 | impl LocalWrapper for NonSendWrapper {} 44 | 45 | impl LocalRecurring for NonSendWrapper { 46 | fn cycle(&mut self) -> impl Future { 47 | async { 48 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 49 | self.cancel_loop.cancel(); 50 | } 51 | } 52 | } 53 | 54 | impl LocalInitializer for NonSendWrapper { 55 | type Wrap<'c> = NonSendWrapper; 56 | 57 | fn build( 58 | self, 59 | client: &mut ActiveClient, 60 | cancel_loop: CancelToken, 61 | ) -> impl Future> { 62 | async { 63 | let aapl: ibapi::contract::Stock = 64 | ibapi::contract::new(client, "BBG000B9XRY4".parse().unwrap()) 65 | .await 66 | .unwrap(); 67 | assert_eq!(aapl.symbol(), "AAPL"); 68 | NonSendWrapper { 69 | cancel_loop, 70 | ..Self::default() 71 | } 72 | } 73 | } 74 | } 75 | 76 | #[tokio::test] 77 | async fn disaggregated_remote() -> Result<(), Box> { 78 | let mut client = Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 79 | .connect(5) 80 | .await? 81 | .disaggregated(SendWrapper) 82 | .await; 83 | client.req_current_time().await?; 84 | let aapl: ibapi::contract::Stock = 85 | ibapi::contract::new(&mut client, "BBG000B9XRY4".parse()?).await?; 86 | assert_eq!(aapl.symbol(), "AAPL"); 87 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 88 | client.disconnect().await?; 89 | Ok(()) 90 | } 91 | 92 | #[tokio::test] 93 | async fn remote() -> Result<(), Box> { 94 | let cancel_token = 95 | Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 96 | .connect(6) 97 | .await? 98 | .remote(SendWrapper) 99 | .await; 100 | 101 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 102 | cancel_token.cancel(); 103 | Ok(()) 104 | } 105 | 106 | #[tokio::test] 107 | async fn local() -> Result<(), Box> { 108 | Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 109 | .connect(7) 110 | .await? 111 | .local(NonSendWrapper::default(), None) 112 | .await?; 113 | 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A Rust port of the Interactive Brokers (IBKR) TWS API. Its goal is to be as expressive, 2 | //! performant, and as safe as possible. 3 | 4 | #![warn(missing_docs)] 5 | #![allow( 6 | clippy::implicit_return, 7 | clippy::missing_docs_in_private_items, 8 | clippy::exhaustive_enums, 9 | clippy::exhaustive_structs, 10 | clippy::question_mark_used, 11 | clippy::separated_literal_suffix, 12 | clippy::single_char_lifetime_names 13 | )] 14 | 15 | /// Contains types related to account information. 16 | pub mod account; 17 | /// Contains the all-important [`client::Client`] struct and its methods, which facilitate 18 | /// communication with the IBKR. Also contains a [`client::Builder`] struct to manage the 19 | /// creation of new connections. 20 | pub mod client; 21 | mod comm; 22 | mod constants; 23 | /// Contains the definitions of all [`contract::Security`] implementors, which represent tradable 24 | /// contracts. 25 | /// 26 | /// Each variety of financial instrument is represented as its own unique struct or 27 | /// enum. They all implement the [`contract::Security`] trait, which means they are a valid IBKR 28 | /// contract and that they have at least one valid order type. 29 | pub mod contract; 30 | /// Contains the definition of a [`currency::Currency`] enum, which represents the possible trading 31 | /// currencies available in the API. 32 | pub mod currency; 33 | #[allow( 34 | unused_variables, 35 | clippy::print_stdout, 36 | clippy::use_debug, 37 | clippy::too_many_lines, 38 | clippy::unnecessary_wraps, 39 | clippy::unused_async 40 | )] 41 | mod decode; 42 | /// Contains types related to security exchanges and trading venues available in the API. 43 | pub mod exchange; 44 | /// Contains types related to executions, which are produced after a trade is made. 45 | pub mod execution; 46 | /// Contains an implementation of the [FIGI alphanumeric identifier](https://www.openfigi.com/about/figi#!) for use in contract specification. 47 | pub mod figi; 48 | /// Contains modules that each relate to different market data requests. In particular, each module 49 | /// defines: 1) General types used in a given market data query and 2) Optionally, a private 50 | /// indicator trait that defines whether a given [`contract::Security`] allows for the data request 51 | /// and 3) Any types associated with implementors of the indicator types. 52 | pub mod market_data; 53 | mod message; 54 | /// Contains types and traits related to orders. 55 | pub mod order; 56 | /// Contains the types that are parsed from API callbacks. They are used in the [`wrapper::LocalWrapper`] and 57 | /// [`wrapper::Wrapper`] callback functions. 58 | pub mod payload; 59 | /// Convenience module containing commonly-used types, functions, and modules. 60 | pub mod prelude; 61 | mod reader; 62 | /// Contains modules, types, and functions related to live data subscriptions, namely those 63 | /// that are created in [`client::Client::req_market_data`]. 64 | /// 65 | /// IBKR confusingly calls these callbacks "ticks" even though they are entirely separate from 66 | /// tick-by-tick data. Many of these "ticks" (read data types) are returned by default with any 67 | /// [`client::Client::req_market_data`] request; others are received only if they are 68 | /// specified in the `additional_data` field. 69 | /// 70 | /// IBKR groups these ticks into several distinct types. Some of these groups are sensible; others 71 | /// are far too broad. Therefore, our version of the API groups these "ticks" differently. Inside 72 | ///this module, each of our groups gets its own submodule and corresponds one-to-one with a 73 | /// [`wrapper::LocalWrapper`] or [`wrapper::Wrapper`] method. 74 | pub mod tick; 75 | /// Contains the definition of the [`wrapper::LocalWrapper`] and [`wrapper::Wrapper`] traits. Implementing these traits for a 76 | /// type allows users to customize callback behavior. 77 | pub mod wrapper; 78 | 79 | #[macro_export] 80 | /// Match across typed variant values 81 | macro_rules! match_poly { 82 | ($self: expr; $($($pat: pat_param)|* => $meth_call: expr),* $(,)?) => { 83 | match $self { 84 | $($($pat => $meth_call),*),* 85 | } 86 | }; 87 | ($self: expr; $($($pat: pat_param)|* => $meth_call: block),* $(,)?) => { 88 | match $self { 89 | $($($pat => $meth_call),*),* 90 | } 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /src/currency.rs: -------------------------------------------------------------------------------- 1 | use core::str::FromStr; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use thiserror::Error; 5 | 6 | // === Type definitions === 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 8 | /// Represents all the possible currencies available for trading at IBKR. 9 | pub enum Currency { 10 | #[serde(rename = "AUD")] 11 | /// The Australian Dollar (AUD) is the currency of Australia. 12 | AustralianDollar, 13 | #[serde(rename = "GBP")] 14 | /// The Pound Sterling (GBP) is the currency of the United Kingdom. 15 | BritishPound, 16 | #[serde(rename = "CAD")] 17 | /// The Canadian Dollar (CAD) is the currency of Canada. 18 | CanadianDollar, 19 | #[serde(rename = "CNH")] 20 | /// The Chinese Renminbi (RMB / CNH) is the currency of The People's Republic of China. The 21 | /// Yuan is the basic unit of the Renminbi. 22 | ChineseYuan, 23 | #[serde(rename = "DKK")] 24 | /// The Danish Krone (DKK) is the currency of Denmark. 25 | DanishKrone, 26 | #[serde(rename = "EUR")] 27 | /// The Euro (EUR) is the currency of most countries in the European Union 28 | Euro, 29 | #[serde(rename = "HKD")] 30 | /// The Hong Kong Dollar (HKD) is the currency of Hong Kong. 31 | HongKongDollar, 32 | #[serde(rename = "INR")] 33 | /// The Indian Rupee (INR) is the currency of the Republic of India. 34 | IndianRupee, 35 | #[serde(rename = "ILS")] 36 | /// The Israeli New Shekel (ILS / NIS) is the currency of Israel. 37 | IsraeliNewShekel, 38 | #[serde(rename = "JPY")] 39 | /// The Japanese Yen (JPY) is the currency of Japan. 40 | JapaneseYen, 41 | #[serde(rename = "KRW")] 42 | /// The Korean Won (KRW) is the currency of South Korea. 43 | KoreanWon, 44 | #[serde(rename = "MXN")] 45 | /// The Mexican Peso (MXN) is the currency of Mexico. 46 | MexicanPeso, 47 | #[serde(rename = "NZD")] 48 | /// The New Zealand Dollar (NZD) is the currency of New Zealand. 49 | NewZealandDollar, 50 | #[serde(rename = "NOK")] 51 | /// The Norwegian Krone (NOK) is the currency of Norway. 52 | NorwegianKrone, 53 | #[serde(rename = "SEK")] 54 | /// The Swedish Króna (SEK) is the currency of Sweden. 55 | SwedishKrona, 56 | #[serde(rename = "CHF")] 57 | /// The Swiss Franc (CHF) is the currency of Switzerland. 58 | SwissFranc, 59 | #[serde(rename = "USD")] 60 | /// The US Dollar (USD) is the currency of the United States of America. 61 | UsDollar, 62 | } 63 | 64 | #[derive(Error, Default, Debug, Clone)] 65 | #[error( 66 | "Invalid value encountered when attempting to parse currency. No such currency symbol: {0}" 67 | )] 68 | /// An error returned when parsing a [`Currency`] fails. 69 | pub struct ParseCurrencyError(pub String); 70 | 71 | // === Type implementations === 72 | 73 | impl FromStr for Currency { 74 | type Err = ParseCurrencyError; 75 | 76 | #[allow(clippy::too_many_lines)] 77 | fn from_str(s: &str) -> Result { 78 | Ok(match s.to_uppercase().as_str() { 79 | "AUD" => Self::AustralianDollar, 80 | "GBP" => Self::BritishPound, 81 | "CAD" => Self::CanadianDollar, 82 | "CNH" => Self::ChineseYuan, 83 | "DKK" => Self::DanishKrone, 84 | "EUR" => Self::Euro, 85 | "HKD" => Self::HongKongDollar, 86 | "INR" => Self::IndianRupee, 87 | "ILS" => Self::IsraeliNewShekel, 88 | "JPY" => Self::JapaneseYen, 89 | "KRW" => Self::KoreanWon, 90 | "MXN" => Self::MexicanPeso, 91 | "NZD" => Self::NewZealandDollar, 92 | "NOK" => Self::NorwegianKrone, 93 | "SEK" => Self::SwedishKrona, 94 | "CHF" => Self::SwissFranc, 95 | "USD" => Self::UsDollar, 96 | s => return Err(ParseCurrencyError(s.to_owned())), 97 | }) 98 | } 99 | } 100 | 101 | impl std::fmt::Display for Currency { 102 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 103 | let s = match self { 104 | Self::AustralianDollar => "AUD", 105 | Self::BritishPound => "GBP", 106 | Self::CanadianDollar => "CAD", 107 | Self::ChineseYuan => "CNH", 108 | Self::DanishKrone => "DKK", 109 | Self::Euro => "EUR", 110 | Self::HongKongDollar => "HKD", 111 | Self::IndianRupee => "INR", 112 | Self::IsraeliNewShekel => "ILS", 113 | Self::JapaneseYen => "JPY", 114 | Self::KoreanWon => "KRW", 115 | Self::MexicanPeso => "MXN", 116 | Self::NewZealandDollar => "NZD", 117 | Self::NorwegianKrone => "NOK", 118 | Self::SwedishKrona => "SEK", 119 | Self::SwissFranc => "CHF", 120 | Self::UsDollar => "USD", 121 | }; 122 | write!(f, "{s}") 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /ibkr_rust_macros/src/send_trait.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | Generics, Ident, ItemTrait, parenthesized, parse_macro_input, parse_quote, ReturnType, 5 | Signature, Token, TraitItem, TraitItemFn, Type, TypeImplTrait, TypeParamBound, 6 | }; 7 | use syn::parse::{Parse, ParseStream}; 8 | use syn::punctuated::Punctuated; 9 | 10 | #[allow(dead_code)] 11 | #[derive(Clone)] 12 | struct Attr { 13 | name: Ident, 14 | generics: Generics, 15 | paren: syn::token::Paren, 16 | make_traits: Punctuated, 17 | colon: Option, 18 | super_traits: Punctuated, 19 | } 20 | 21 | impl Parse for Attr { 22 | fn parse(input: ParseStream) -> syn::Result { 23 | let make_trait; 24 | Ok(Attr { 25 | name: input.parse()?, 26 | generics: input.parse()?, 27 | paren: parenthesized!(make_trait in input), 28 | make_traits: make_trait 29 | .parse_terminated(syn::TypeParamBound::parse, syn::token::Plus)?, 30 | colon: input.parse()?, 31 | super_traits: input.parse_terminated(syn::TypeParamBound::parse, syn::token::Plus)?, 32 | }) 33 | } 34 | } 35 | 36 | #[allow(clippy::module_name_repetitions)] 37 | pub fn impl_make_send( 38 | attr: proc_macro::TokenStream, 39 | item: proc_macro::TokenStream, 40 | ) -> proc_macro::TokenStream { 41 | let attrs = parse_macro_input!(attr as Attr); 42 | let item = parse_macro_input!(item as ItemTrait); 43 | 44 | let variant = impl_make_variant(&attrs, &item); 45 | let new_item = impl_remove_async(&item); 46 | 47 | quote! { 48 | #new_item 49 | 50 | #variant 51 | } 52 | .into() 53 | } 54 | 55 | fn impl_make_variant(attrs: &Attr, item: &ItemTrait) -> TokenStream { 56 | let make_traits = attrs 57 | .make_traits 58 | .clone() 59 | .into_iter() 60 | .collect::>(); 61 | let var = ItemTrait { 62 | ident: attrs.name.clone(), 63 | generics: attrs.generics.clone(), 64 | colon_token: attrs.colon, 65 | supertraits: attrs.super_traits.clone(), 66 | items: item 67 | .items 68 | .iter() 69 | .map(|t| insert_trait_bounds(t, &make_traits)) 70 | .collect(), 71 | ..item.clone() 72 | }; 73 | 74 | quote! { #var } 75 | } 76 | 77 | fn insert_trait_bounds(item: &TraitItem, make_traits: &[TypeParamBound]) -> TraitItem { 78 | let TraitItem::Fn(func @ TraitItemFn { sig, .. }) = item else { 79 | return item.clone(); 80 | }; 81 | 82 | let output_type = if sig.asyncness.is_some() { 83 | let fut_output = match sig.output { 84 | ReturnType::Default => quote! { () }, 85 | ReturnType::Type(_, ref t) => quote! { #t }, 86 | }; 87 | 88 | Type::ImplTrait(TypeImplTrait { 89 | impl_token: parse_quote! { impl }, 90 | bounds: std::iter::once(TypeParamBound::Trait( 91 | parse_quote! { core::future::Future }, 92 | )) 93 | .chain(make_traits.iter().cloned()) 94 | .collect(), 95 | }) 96 | } else { 97 | match sig.output { 98 | ReturnType::Type(_, ref t) => match *t.to_owned() { 99 | Type::ImplTrait(TypeImplTrait { bounds, .. }) => Type::ImplTrait(TypeImplTrait { 100 | impl_token: parse_quote! { impl }, 101 | bounds: bounds 102 | .into_iter() 103 | .chain(make_traits.iter().cloned()) 104 | .collect(), 105 | }), 106 | _ => { 107 | return item.clone(); 108 | } 109 | }, 110 | ReturnType::Default => { 111 | return item.clone(); 112 | } 113 | } 114 | }; 115 | 116 | TraitItem::Fn(TraitItemFn { 117 | sig: Signature { 118 | asyncness: None, 119 | output: ReturnType::Type(syn::token::RArrow::default(), Box::new(output_type)), 120 | ..sig.clone() 121 | }, 122 | ..func.clone() 123 | }) 124 | } 125 | 126 | fn remove_async_add_impl(item: &TraitItem) -> TraitItem { 127 | let TraitItem::Fn(func @ TraitItemFn { sig, .. }) = item else { 128 | return item.clone(); 129 | }; 130 | 131 | let output_type = if sig.asyncness.is_some() { 132 | let fut_output = match sig.output { 133 | ReturnType::Default => quote! { () }, 134 | ReturnType::Type(_, ref t) => quote! { #t }, 135 | }; 136 | 137 | Type::ImplTrait(TypeImplTrait { 138 | impl_token: parse_quote! { impl }, 139 | bounds: std::iter::once(TypeParamBound::Trait( 140 | parse_quote! { core::future::Future }, 141 | )) 142 | .collect(), 143 | }) 144 | } else { 145 | return item.clone(); 146 | }; 147 | 148 | TraitItem::Fn(TraitItemFn { 149 | sig: Signature { 150 | asyncness: None, 151 | output: ReturnType::Type(syn::token::RArrow::default(), Box::new(output_type)), 152 | ..sig.clone() 153 | }, 154 | ..func.clone() 155 | }) 156 | } 157 | 158 | fn impl_remove_async(item: &ItemTrait) -> TokenStream { 159 | let new = ItemTrait { 160 | items: item.items.iter().map(remove_async_add_impl).collect(), 161 | ..item.clone() 162 | }; 163 | 164 | quote! { #new } 165 | } 166 | -------------------------------------------------------------------------------- /tests/account_portfolio.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use tokio; 4 | 5 | use ibapi::account::Tag; 6 | use ibapi::client::{ActiveClient, Builder, Host, Mode}; 7 | use ibapi::wrapper::{CancelToken, Initializer, Recurring, Wrapper}; 8 | 9 | struct AccountDataWrapper; 10 | 11 | impl Wrapper for AccountDataWrapper {} 12 | 13 | impl Recurring for AccountDataWrapper { 14 | fn cycle(&mut self) -> impl Future + Send { 15 | async { () } 16 | } 17 | } 18 | 19 | struct AccountSummaryInitializer; 20 | 21 | impl Initializer for AccountSummaryInitializer { 22 | type Wrap<'c> = AccountDataWrapper; 23 | 24 | fn build( 25 | self, 26 | client: &mut ActiveClient, 27 | _cancel_loop: CancelToken, 28 | ) -> impl Future> + Send { 29 | async { 30 | let id = client 31 | .req_account_summary(&vec![ 32 | Tag::AccountType, 33 | Tag::NetLiquidation, 34 | Tag::TotalCashValue, 35 | Tag::SettledCash, 36 | Tag::AccruedCash, 37 | Tag::BuyingPower, 38 | Tag::AvailableFunds, 39 | Tag::EquityWithLoanValue, 40 | Tag::PreviousEquityWithLoanValue, 41 | Tag::GrossPositionValue, 42 | Tag::RegTEquity, 43 | Tag::RegTMargin, 44 | Tag::Sma, 45 | Tag::InitMarginReq, 46 | Tag::MaintenanceMarginReq, 47 | Tag::AvailableFunds, 48 | Tag::ExcessLiquidity, 49 | Tag::Cushion, 50 | Tag::FullInitMarginReq, 51 | Tag::FullMaintenanceMarginReq, 52 | Tag::FullAvailableFunds, 53 | Tag::FullExcessLiquidity, 54 | Tag::LookAheadNextChange, 55 | Tag::LookAheadMaintenanceMarginReq, 56 | Tag::LookAheadAvailableFunds, 57 | Tag::LookAheadExcessLiquidity, 58 | Tag::HighestSeverity, 59 | Tag::DayTradesRemaining, 60 | Tag::Leverage, 61 | ]) 62 | .await 63 | .unwrap(); 64 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 65 | client.cancel_account_summary(id).await.unwrap(); 66 | AccountDataWrapper 67 | } 68 | } 69 | } 70 | 71 | #[tokio::test] 72 | async fn account_summary() -> Result<(), Box> { 73 | let discon = Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 74 | .connect(1) 75 | .await? 76 | .remote(AccountSummaryInitializer) 77 | .await; 78 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 79 | discon.cancel(); 80 | Ok(()) 81 | } 82 | 83 | struct AccountUpdateInitializer; 84 | 85 | impl Initializer for AccountUpdateInitializer { 86 | type Wrap<'c> = AccountDataWrapper; 87 | 88 | fn build( 89 | self, 90 | client: &mut ActiveClient, 91 | _cancel_loop: CancelToken, 92 | ) -> impl Future> + Send { 93 | async { 94 | client.req_account_updates(None).await.unwrap(); 95 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 96 | client.cancel_account_updates(None).await.unwrap(); 97 | AccountDataWrapper 98 | } 99 | } 100 | } 101 | 102 | #[tokio::test] 103 | async fn account_update() -> Result<(), Box> { 104 | let discon = Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 105 | .connect(2) 106 | .await? 107 | .remote(AccountUpdateInitializer) 108 | .await; 109 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 110 | discon.cancel(); 111 | Ok(()) 112 | } 113 | 114 | struct PositionInitializer; 115 | 116 | impl Initializer for PositionInitializer { 117 | type Wrap<'c> = AccountDataWrapper; 118 | fn build( 119 | self, 120 | client: &mut ActiveClient, 121 | _cancel_loop: CancelToken, 122 | ) -> impl Future> + Send { 123 | async { 124 | client.req_positions().await.unwrap(); 125 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 126 | client.cancel_positions().await.unwrap(); 127 | AccountDataWrapper 128 | } 129 | } 130 | } 131 | 132 | #[tokio::test] 133 | async fn positions() -> Result<(), Box> { 134 | let discon = Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 135 | .connect(3) 136 | .await? 137 | .remote(PositionInitializer) 138 | .await; 139 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 140 | discon.cancel(); 141 | Ok(()) 142 | } 143 | 144 | struct PnlInitializer; 145 | 146 | impl Initializer for PnlInitializer { 147 | type Wrap<'c> = AccountDataWrapper; 148 | fn build( 149 | self, 150 | client: &mut ActiveClient, 151 | _cancel_loop: CancelToken, 152 | ) -> impl Future> + Send { 153 | async { 154 | let id = client 155 | .req_pnl(&client.get_managed_accounts().iter().next().unwrap().clone()) 156 | .await 157 | .unwrap(); 158 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 159 | client.cancel_pnl(id).await.unwrap(); 160 | AccountDataWrapper 161 | } 162 | } 163 | } 164 | 165 | #[tokio::test] 166 | async fn pnl() -> Result<(), Box> { 167 | let discon = Builder::from_config_file(Mode::Paper, Host::Gateway, &None::<&'static str>)? 168 | .connect(4) 169 | .await 170 | .unwrap() 171 | .remote(PnlInitializer) 172 | .await; 173 | tokio::time::sleep(std::time::Duration::from_secs(3)).await; 174 | discon.cancel(); 175 | Ok(()) 176 | } 177 | -------------------------------------------------------------------------------- /eclient_methods.csv: -------------------------------------------------------------------------------- 1 | EClient (EWrapper wrapper) 2 | SetConnectOptions (string connectOptions) 3 | DisableUseV100Plus () 4 | IsConnected () 5 | startApi () 6 | Close () 7 | eDisconnect (bool resetState=true) 8 | reqCompletedOrders (bool apiOnly) 9 | cancelTickByTickData (int requestId) 10 | "reqTickByTickData (int requestId, Contract contract, string tickType, int numberOfTicks, bool ignoreSize)" 11 | cancelHistoricalData (int reqId) 12 | "calculateImpliedVolatility (int reqId, Contract contract, double optionPrice, double underPrice, List< TagValue > impliedVolatilityOptions)" 13 | "calculateOptionPrice (int reqId, Contract contract, double volatility, double underPrice, List< TagValue > optionPriceOptions)" 14 | cancelAccountSummary (int reqId) 15 | cancelCalculateImpliedVolatility (int reqId) 16 | cancelCalculateOptionPrice (int reqId) 17 | cancelFundamentalData (int reqId) 18 | cancelMktData (int tickerId) 19 | "cancelMktDepth (int tickerId, bool isSmartDepth)" 20 | cancelNewsBulletin () 21 | "cancelOrder (int orderId, string manualOrderCancelTime)" 22 | cancelPositions () 23 | cancelRealTimeBars (int tickerId) 24 | cancelScannerSubscription (int tickerId) 25 | "exerciseOptions (int tickerId, Contract contract, int exerciseAction, int exerciseQuantity, string account, int ovrd)" 26 | "placeOrder (int id, Contract contract, Order order)" 27 | "replaceFA (int reqId, int faDataType, string xml)" 28 | requestFA (int faDataType) 29 | "reqAccountSummary (int reqId, string group, string tags)" 30 | "reqAccountUpdates (bool subscribe, string acctCode)" 31 | reqAllOpenOrders () 32 | reqAutoOpenOrders (bool autoBind) 33 | "reqContractDetails (int reqId, Contract contract)" 34 | reqCurrentTime () 35 | "reqExecutions (int reqId, ExecutionFilter filter)" 36 | "reqFundamentalData (int reqId, Contract contract, string reportType, List< TagValue > fundamentalDataOptions)" 37 | reqGlobalCancel () 38 | "reqHistoricalData (int tickerId, Contract contract, string endDateTime, string durationStr, string barSizeSetting, string whatToShow, int useRTH, int formatDate, bool keepUpToDate, List< TagValue > chartOptions)" 39 | reqIds (int numIds) 40 | reqManagedAccts () 41 | "reqMktData (int tickerId, Contract contract, string genericTickList, bool snapshot, bool regulatorySnaphsot, List< TagValue > mktDataOptions)" 42 | reqMarketDataType (int marketDataType) 43 | "reqMarketDepth (int tickerId, Contract contract, int numRows, bool isSmartDepth, List< TagValue > mktDepthOptions)" 44 | reqNewsBulletins (bool allMessages) 45 | reqOpenOrders () 46 | reqPositions () 47 | "reqRealTimeBars (int tickerId, Contract contract, int barSize, string whatToShow, bool useRTH, List< TagValue > realTimeBarsOptions)" 48 | reqScannerParameters () 49 | "reqScannerSubscription (int reqId, ScannerSubscription subscription, List< TagValue > scannerSubscriptionOptions, List< TagValue > scannerSubscriptionFilterOptions)" 50 | "reqScannerSubscription (int reqId, ScannerSubscription subscription, string scannerSubscriptionOptions, string scannerSubscriptionFilterOptions)" 51 | setServerLogLevel (int logLevel) 52 | "verifyRequest (string apiName, string apiVersion)" 53 | verifyMessage (string apiData) 54 | "verifyAndAuthRequest (string apiName, string apiVersion, string opaqueIsvKey)" 55 | "verifyAndAuthMessage (string apiData, string xyzResponse)" 56 | queryDisplayGroups (int requestId) 57 | "subscribeToGroupEvents (int requestId, int groupId)" 58 | "updateDisplayGroup (int requestId, string contractInfo)" 59 | unsubscribeFromGroupEvents (int requestId) 60 | "reqPositionsMulti (int requestId, string account, string modelCode)" 61 | cancelPositionsMulti (int requestId) 62 | "reqAccountUpdatesMulti (int requestId, string account, string modelCode, bool ledgerAndNLV)" 63 | cancelAccountUpdatesMulti (int requestId) 64 | "reqSecDefOptParams (int reqId, string underlyingSymbol, string futFopExchange, string underlyingSecType, int underlyingConId)" 65 | reqSoftDollarTiers (int reqId) 66 | reqFamilyCodes () 67 | "reqMatchingSymbols (int reqId, string pattern)" 68 | reqMktDepthExchanges () 69 | "reqSmartComponents (int reqId, string bboExchange)" 70 | reqNewsProviders () 71 | "reqNewsArticle (int requestId, string providerCode, string articleId, List< TagValue > newsArticleOptions)" 72 | "reqHistoricalNews (int requestId, int conId, string providerCodes, string startDateTime, string endDateTime, int totalResults, List< TagValue > historicalNewsOptions)" 73 | "reqHeadTimestamp (int tickerId, Contract contract, string whatToShow, int useRTH, int formatDate)" 74 | cancelHeadTimestamp (int tickerId) 75 | "reqHistogramData (int tickerId, Contract contract, bool useRTH, string period)" 76 | cancelHistogramData (int tickerId) 77 | reqMarketRule (int marketRuleId) 78 | "reqPnL (int reqId, string account, string modelCode)" 79 | cancelPnL (int reqId) 80 | "reqPnLSingle (int reqId, string account, string modelCode, int conId)" 81 | cancelPnLSingle (int reqId) 82 | "reqHistoricalTicks (int reqId, Contract contract, string startDateTime, string endDateTime, int numberOfTicks, string whatToShow, int useRth, bool ignoreSize, List< TagValue > miscOptions)" 83 | reqWshMetaData (int reqId) 84 | cancelWshMetaData (int reqId) 85 | "reqWshEventData (int reqId, WshEventData wshEventData)" 86 | cancelWshEventData (int reqId) 87 | reqUserInfo (int reqId) 88 | IsDataAvailable () 89 | ReadInt () 90 | ReadAtLeastNBytes (int msgSize) 91 | ReadByteArray (int msgSize) 92 | prepareBuffer (BinaryWriter paramsList) 93 | sendConnectRequest () 94 | CheckServerVersion (int requiredVersion) 95 | "CheckServerVersion (int requestId, int requiredVersion)" 96 | "CheckServerVersion (int requiredVersion, string updatetail)" 97 | "CheckServerVersion (int tickerId, int requiredVersion, string updatetail)" 98 | "CloseAndSend (BinaryWriter paramsList, uint lengthPos, CodeMsgPair error)" 99 | "CloseAndSend (int reqId, BinaryWriter paramsList, uint lengthPos, CodeMsgPair error)" 100 | "CloseAndSend (BinaryWriter request, uint lengthPos)" 101 | CheckConnection () 102 | "ReportError (int reqId, CodeMsgPair error, string tail)" 103 | "ReportUpdateTWS (int reqId, string tail)" 104 | ReportUpdateTWS (string tail) 105 | "ReportError (int reqId, int code, string message)" 106 | "SendCancelRequest (OutgoingMessages msgType, int version, int reqId, CodeMsgPair errorMessage)" 107 | "SendCancelRequest (OutgoingMessages msgType, int version, CodeMsgPair errorMessage)" 108 | "VerifyOrderContract (Contract contract, int id)" 109 | "VerifyOrder (Order order, int id, bool isBagOrder)" 110 | serverVersion 111 | socketTransport 112 | wrapper 113 | isConnected 114 | clientId 115 | extraAuth 116 | useV100Plus = true 117 | allowRedirect 118 | tcpStream 119 | Wrapper [get] 120 | "AllowRedirect [get, set]" 121 | ServerVersion [get] 122 | "ServerTime [get, set]" 123 | "optionalCapabilities [get, set]" 124 | "AsyncEConnect [get, set]" -------------------------------------------------------------------------------- /src/execution.rs: -------------------------------------------------------------------------------- 1 | use chrono::serde::ts_seconds; 2 | use chrono::Utc; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::contract::{Contract, ContractType, ExchangeProxy}; 6 | use crate::currency::Currency; 7 | use crate::exchange::Primary; 8 | 9 | #[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] 10 | /// A filter for requesting executions that meet only these criteria. 11 | pub struct Filter { 12 | /// Filter by API client id that placed the order. 13 | pub client_id: i64, 14 | /// Filter by account number to which the order was allocated 15 | pub account_number: String, 16 | #[serde(with = "serde_filter_datetime")] 17 | /// Filter by orders placed after this date and time 18 | pub datetime: Option, 19 | /// Filter by contract symbol. 20 | pub symbol: String, 21 | /// Filter by contract type. 22 | pub contract_type: Option, 23 | /// Filter by the exchange at which the execution was produced. 24 | pub exchange: Option, 25 | /// Filter by order side. 26 | pub side: Option, 27 | } 28 | 29 | mod serde_filter_datetime { 30 | use serde::{Serializer, Deserializer, Deserialize}; 31 | use serde::de::Error; 32 | 33 | pub fn serialize(datetime: &Option, ser: S) -> Result { 34 | match datetime { 35 | Some(dt) => ser.serialize_str(&dt.format("%Y%m%d %T").to_string()), 36 | None => ser.serialize_none() 37 | } 38 | } 39 | 40 | pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result, D::Error> { 41 | let s = <&'_ str>::deserialize(de)?; 42 | if s.is_empty() { 43 | Ok(None) 44 | } else { 45 | Ok(Some(chrono::NaiveDateTime::parse_from_str(s, "%Y%m%d %T").map_err(Error::custom)?)) 46 | } 47 | } 48 | 49 | } 50 | 51 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 52 | /// The possible sides for an order 53 | pub enum OrderSide { 54 | #[serde(rename = "BUY")] 55 | /// A buy order 56 | Buy, 57 | #[serde(rename = "SELL")] 58 | /// A sell order 59 | Sell, 60 | } 61 | 62 | #[derive(Debug, Default, Clone, thiserror::Error)] 63 | #[error("Invalid value encountered when attempting to parse an order side. No such order side: {0}. Valid order sides \"BOT\" or \"SLD\".")] 64 | /// An error returned when parsing an [`OrderSide`] fails. 65 | pub struct ParseOrderSideError(String); 66 | 67 | impl std::str::FromStr for OrderSide { 68 | type Err = ParseOrderSideError; 69 | fn from_str(s: &str) -> Result { 70 | match s { 71 | "BOT" => Ok(Self::Buy), 72 | "SLD" => Ok(Self::Sell), 73 | other => Err(ParseOrderSideError(other.to_owned())), 74 | } 75 | } 76 | } 77 | 78 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 79 | /// Contains the core fields relating to an [`Execution`]. which occurs when a trade is made. 80 | pub struct Exec { 81 | /// The contract on which the trade was made. 82 | pub contract: ExchangeProxy, 83 | /// The ID of the order that produced the execution. 84 | pub order_id: i64, 85 | /// The execution ID. 86 | pub execution_id: String, 87 | /// The date and time at which the execution occurred. 88 | #[serde(with = "ts_seconds")] 89 | pub datetime: chrono::DateTime, 90 | /// The account number for which the trade was made. 91 | pub account_number: String, 92 | /// The exchange on which the trade was made. 93 | pub exchange: Primary, 94 | /// The number of contracts traded. 95 | pub quantity: f64, 96 | /// The price at which the trade was made. 97 | pub price: f64, 98 | /// The permanent ID of the order that produced the execution. 99 | pub perm_id: i64, 100 | /// The client ID that placed the order. 101 | pub client_id: i64, 102 | /// Whether the execution was caused by an IBKR-initiated liquidation. 103 | pub liquidation: bool, 104 | /// The cumulative number of contracts traded for the underlying order after this execution. 105 | pub cumulative_quantity: f64, 106 | /// The average price at which contracts for the underlying order after this execution. 107 | pub average_price: f64, 108 | /// Whether the execution is pending a price revision. 109 | pub pending_price_revision: bool, 110 | } 111 | 112 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 113 | #[serde(tag = "action")] 114 | /// A confirmed trade. 115 | pub enum Execution { 116 | /// Contracts were bought. 117 | Bought(Exec), 118 | /// Contracts were sold. 119 | Sold(Exec), 120 | } 121 | 122 | impl Execution { 123 | #[inline] 124 | #[must_use] 125 | /// Return a reference to the inner [`Exec`] 126 | pub fn as_exec(&self) -> &Exec { 127 | match self { 128 | Self::Bought(e) | Self::Sold(e) => e, 129 | } 130 | } 131 | #[inline] 132 | #[must_use] 133 | /// Convert the [`Execution`] into an [`Exec`] and an [`OrderSide`] 134 | pub fn into_exec_tuple(self) -> (Exec, OrderSide) { 135 | match self { 136 | Self::Bought(e) => (e, OrderSide::Buy), 137 | Self::Sold(e) => (e, OrderSide::Sell), 138 | } 139 | } 140 | 141 | #[inline] 142 | #[must_use] 143 | /// Construct a new [`Execution`] from an [`Exec`] and an [`OrderSide`] 144 | pub fn from_exec_tuple(exec: Exec, side: OrderSide) -> Self { 145 | match side { 146 | OrderSide::Buy => Self::Bought(exec), 147 | OrderSide::Sell => Self::Sold(exec), 148 | } 149 | } 150 | 151 | #[inline] 152 | #[must_use] 153 | /// Return `true` if a Buy execution 154 | pub fn is_buy(&self) -> bool { 155 | matches!(self, Execution::Bought(_)) 156 | } 157 | 158 | #[inline] 159 | #[must_use] 160 | /// Return `true` if a Sell execution 161 | pub fn is_sell(&self) -> bool { 162 | matches!(self, Execution::Sold(_)) 163 | } 164 | } 165 | 166 | impl From<(Exec, OrderSide)> for Execution { 167 | #[inline] 168 | fn from(value: (Exec, OrderSide)) -> Self { 169 | Self::from_exec_tuple(value.0, value.1) 170 | } 171 | } 172 | 173 | impl From<(OrderSide, Exec)> for Execution { 174 | #[inline] 175 | fn from(value: (OrderSide, Exec)) -> Self { 176 | Self::from((value.1, value.0)) 177 | } 178 | } 179 | 180 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 181 | /// Details the commissions paid regarding a given [`Execution`] 182 | pub struct CommissionReport { 183 | /// The ID of the [`Execution`] with which the report corresponds 184 | pub exec_id: String, 185 | /// The commission cost 186 | pub commission: f64, 187 | /// The reporting currency 188 | pub currency: Currency, 189 | /// The realized profit and loss 190 | pub realized_pnl: f64, 191 | /// The income return 192 | pub yld: Option, 193 | /// The redemption date for the yield 194 | pub yld_redemption_date: Option, 195 | } 196 | -------------------------------------------------------------------------------- /codes and names.csv: -------------------------------------------------------------------------------- 1 | """Copyright (C) 2023 Interactive Brokers LLC. All rights reserved. This code is subject to the terms and conditions of the IB API Non-Commercial License or the IB API Commercial License` as applicable.""""""The main class to use from API user's point of view.It takes care of almost everything:- implementing the requests- creating the answer decoder- creating the connection to TWS/IBGWThe user just needs to override EWrapper methods to receive the answers."""import loggingimport queueimport socketfrom ibapi import (decoder` reader` comm)from ibapi.connection import Connectionfrom ibapi.message import OUTfrom ibapi.common import * # @UnusedWildImportfrom ibapi.contract import Contractfrom ibapi.order import Order` COMPETE_AGAINST_BEST_OFFSET_UP_TO_MIDfrom ibapi.execution import ExecutionFilterfrom ibapi.scanner import ScannerSubscriptionfrom ibapi.comm import (make_field` make_field_handle_empty)from ibapi.utils import (current_fn_name` BadMessage)from ibapi.errors import * #@UnusedWildImportfrom ibapi.server_versions import * # @UnusedWildImportfrom ibapi.utils import ClientException#TODO: use pylintlogger = logging.getLogger(__name__)class EClient(object, 2 | startApi(self,OUT.START_API 3 | reqCurrentTime(self,OUT.REQ_CURRENT_TIME 4 | ault detail level is ERROR. For more details` see API Logging.""" self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.SET_SERVER_LOGLEVEL 5 | ault value XYZ. """ self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.REQ_MKT_DATA 6 | cancelMktData(self` reqId:TickerId,OUT.CANCEL_MKT_DATA 7 | reqMarketDataType(self` marketDataType:int,OUT.REQ_MARKET_DATA_TYPE 8 | reqSmartComponents(self` reqId: int` bboExchange: str,OUT.REQ_SMART_COMPONENTS 9 | reqMarketRule(self` marketRuleId: int,OUT.REQ_MARKET_RULE 10 | reqTickByTickData(self` reqId: int` contract: Contract` tickType: str` numberOfTicks: int` ignoreSize: bool,OUT.REQ_TICK_BY_TICK_DATA 11 | cancelTickByTickData(self` reqId: int,OUT.CANCEL_TICK_BY_TICK_DATA 12 | calculateImpliedVolatility(self` reqId:TickerId` contract:Contract` optionPrice:float` underPrice:float` implVolOptions:TagValueList,OUT.REQ_CALC_IMPLIED_VOLAT 13 | cancelCalculateImpliedVolatility(self` reqId:TickerId,OUT.CANCEL_CALC_IMPLIED_VOLAT 14 | calculateOptionPrice(self` reqId:TickerId` contract:Contract` volatility:float` underPrice:float` optPrcOptions:TagValueList,OUT.REQ_CALC_OPTION_PRICE 15 | cancelCalculateOptionPrice(self` reqId:TickerId,OUT.CANCEL_CALC_OPTION_PRICE 16 | exerciseOptions(self` reqId:TickerId` contract:Contract` exerciseAction:int` exerciseQuantity:int` account:str` override:int,OUT.EXERCISE_OPTIONS 17 | placeOrder(self` orderId:OrderId ` contract:Contract` order:Order,OUT.PLACE_ORDER 18 | cancelOrder(self` orderId:OrderId` manualCancelOrderTime:str,OUT.CANCEL_ORDER 19 | reqOpenOrders(self,OUT.REQ_OPEN_ORDERS 20 | reqAutoOpenOrders(self` bAutoBind:bool,OUT.REQ_AUTO_OPEN_ORDERS 21 | reqAllOpenOrders(self,OUT.REQ_ALL_OPEN_ORDERS 22 | reqGlobalCancel(self,OUT.REQ_GLOBAL_CANCEL 23 | reqIds(self` numIds:int,OUT.REQ_IDS 24 | reqAccountUpdates(self` subscribe:bool` acctCode:str,OUT.REQ_ACCT_DATA 25 | reqAccountSummary(self` reqId:int` groupName:str` tags:str,OUT.REQ_ACCOUNT_SUMMARY 26 | cancelAccountSummary(self` reqId:int,OUT.CANCEL_ACCOUNT_SUMMARY 27 | reqPositions(self,OUT.REQ_POSITIONS 28 | cancelPositions(self,OUT.CANCEL_POSITIONS 29 | reqPositionsMulti(self` reqId:int` account:str` modelCode:str,OUT.REQ_POSITIONS_MULTI 30 | cancelPositionsMulti(self` reqId:int,OUT.CANCEL_POSITIONS_MULTI 31 | reqAccountUpdatesMulti(self` reqId: int` account:str` modelCode:str` ledgerAndNLV:bool,OUT.REQ_ACCOUNT_UPDATES_MULTI 32 | cancelAccountUpdatesMulti(self` reqId:int,OUT.CANCEL_ACCOUNT_UPDATES_MULTI 33 | reqPnL(self` reqId: int` account: str` modelCode: str,OUT.REQ_PNL 34 | cancelPnL(self` reqId: int,OUT.CANCEL_PNL 35 | reqPnLSingle(self` reqId: int` account: str` modelCode: str` conid: int,OUT.REQ_PNL_SINGLE 36 | cancelPnLSingle(self` reqId: int,OUT.CANCEL_PNL_SINGLE 37 | reqExecutions(self` reqId:int` execFilter:ExecutionFilter,OUT.REQ_EXECUTIONS 38 | reqContractDetails(self` reqId:int ` contract:Contract,OUT.REQ_CONTRACT_DATA 39 | reqMktDepthExchanges(self,OUT.REQ_MKT_DEPTH_EXCHANGES 40 | ault value XYZ.""" self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.REQ_MKT_DEPTH 41 | cancelMktDepth(self` reqId:TickerId` isSmartDepth:bool,OUT.CANCEL_MKT_DEPTH 42 | reqNewsBulletins(self` allMsgs:bool,OUT.REQ_NEWS_BULLETINS 43 | cancelNewsBulletins(self,OUT.CANCEL_NEWS_BULLETINS 44 | reqManagedAccts(self,OUT.REQ_MANAGED_ACCTS 45 | requestFA(self` faData:FaDataType,OUT.REQ_FA 46 | replaceFA(self` reqId:TickerId ` faData:FaDataType ` cxml:str,OUT.REPLACE_FA 47 | ault value XYZ. """ self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.REQ_HISTORICAL_DATA 48 | cancelHistoricalData(self` reqId:TickerId,OUT.CANCEL_HISTORICAL_DATA 49 | reqHeadTimeStamp(self` reqId:TickerId` contract:Contract` whatToShow: str` useRTH: int` formatDate: int,OUT.REQ_HEAD_TIMESTAMP 50 | cancelHeadTimeStamp(self` reqId: TickerId,OUT.CANCEL_HEAD_TIMESTAMP 51 | reqHistogramData(self` tickerId: int` contract: Contract` useRTH: bool` timePeriod: str,OUT.REQ_HISTOGRAM_DATA 52 | cancelHistogramData(self` tickerId: int,OUT.CANCEL_HISTOGRAM_DATA 53 | reqHistoricalTicks(self` reqId: int` contract: Contract` startDateTime: str` endDateTime: str` numberOfTicks: int` whatToShow: str` useRth: int` ignoreSize: bool` miscOptions: TagValueList,OUT.REQ_HISTORICAL_TICKS 54 | reqScannerParameters(self,OUT.REQ_SCANNER_PARAMETERS 55 | ault value XYZ.""" self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.REQ_SCANNER_SUBSCRIPTION 56 | cancelScannerSubscription(self` reqId:int,OUT.CANCEL_SCANNER_SUBSCRIPTION 57 | ault value XYZ.""" self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.REQ_REAL_TIME_BARS 58 | cancelRealTimeBars(self` reqId:TickerId,OUT.CANCEL_REAL_TIME_BARS 59 | reqFundamentalData(self` reqId:TickerId ` contract:Contract` reportType:str` fundamentalDataOptions:TagValueList,OUT.REQ_FUNDAMENTAL_DATA 60 | cancelFundamentalData(self` reqId:TickerId,OUT.CANCEL_FUNDAMENTAL_DATA 61 | reqNewsProviders(self,OUT.REQ_NEWS_PROVIDERS 62 | reqNewsArticle(self` reqId: int` providerCode: str` articleId: str` newsArticleOptions: TagValueList,OUT.REQ_NEWS_ARTICLE 63 | reqHistoricalNews(self` reqId: int` conId: int` providerCodes: str` startDateTime: str` endDateTime: str` totalResults: int` historicalNewsOptions: TagValueList,OUT.REQ_HISTORICAL_NEWS 64 | queryDisplayGroups(self` reqId: int,OUT.QUERY_DISPLAY_GROUPS 65 | subscribeToGroupEvents(self` reqId:int` groupId:int,OUT.SUBSCRIBE_TO_GROUP_EVENTS 66 | updateDisplayGroup(self` reqId:int` contractInfo:str,OUT.UPDATE_DISPLAY_GROUP 67 | unsubscribeFromGroupEvents(self` reqId:int,OUT.UNSUBSCRIBE_FROM_GROUP_EVENTS 68 | verifyRequest(self` apiName:str` apiVersion:str,OUT.VERIFY_REQUEST 69 | verifyMessage(self` apiData:str,OUT.VERIFY_MESSAGE 70 | verifyAndAuthRequest(self` apiName:str` apiVersion:str` opaqueIsvKey:str,OUT.VERIFY_AND_AUTH_REQUEST 71 | verifyAndAuthMessage(self` apiData:str` xyzResponse:str,OUT.VERIFY_AND_AUTH_MESSAGE 72 | inition option request.") return try: flds = [] flds += [make_field(OUT.REQ_SEC_DEF_OPT_PARAMS)` make_field(reqId)` make_field(underlyingSymbol)` make_field(futFopExchange)` make_field(underlyingSecType)` make_field(underlyingConId)] msg = "".join(flds) except ClientException as ex: self.wrapper.error(reqId` ex.code` ex.msg + ex.text) return self.sendMsg(msg),OUT.REQ_SEC_DEF_OPT_PARAMS 73 | ined Soft Dollar Tiers. This is only supported for registered professional advisors and hedge and mutual funds who have configured Soft Dollar Tiers in Account Management.""" self.logRequest(current_fn_name()` vars()) if not self.isConnected(,OUT.REQ_SOFT_DOLLAR_TIERS 74 | reqFamilyCodes(self,OUT.REQ_FAMILY_CODES 75 | reqMatchingSymbols(self` reqId:int` pattern:str,OUT.REQ_MATCHING_SYMBOLS 76 | reqCompletedOrders(self` apiOnly:bool,OUT.REQ_COMPLETED_ORDERS 77 | reqWshMetaData(self` reqId: int,OUT.REQ_WSH_META_DATA 78 | cancelWshMetaData(self` reqId: int,OUT.CANCEL_WSH_META_DATA 79 | reqWshEventData(self` reqId: int` wshEventData: WshEventData,OUT.REQ_WSH_EVENT_DATA 80 | cancelWshEventData(self` reqId: int,OUT.CANCEL_WSH_EVENT_DATA 81 | reqUserInfo(self` reqId: int,OUT.REQ_USER_INFO 82 | -------------------------------------------------------------------------------- /Functions to implement.csv: -------------------------------------------------------------------------------- 1 | void ,reqCompletedOrders (bool apiOnly) 2 | ,Requests completed orders. 3 | ,.  More... 4 | , 5 | void ,"calculateImpliedVolatility (int reqId, Contract contract, double optionPrice, double underPrice, List< TagValue > impliedVolatilityOptions)" 6 | ,Calculate the volatility for an option. 7 | ,Request the calculation of the implied volatility based on hypothetical option and its underlying prices. 8 | ,The calculation will be return in EWrapper's tickOptionComputation callback. 9 | ,.  More... 10 | , 11 | void ,"calculateOptionPrice (int reqId, Contract contract, double volatility, double underPrice, List< TagValue > optionPriceOptions)" 12 | ,Calculates an option's price based on the provided volatility and its underlying's price.  13 | ,The calculation will be return in EWrapper's tickOptionComputation callback. 14 | ,.  More... 15 | , 16 | void ,cancelAccountSummary (int reqId) 17 | ,"Cancels the account's summary request. After requesting an account's summary, invoke this function to cancel it.  More..." 18 | , 19 | void ,cancelCalculateImpliedVolatility (int reqId) 20 | ,Cancels an option's implied volatility calculation request.  More... 21 | , 22 | void ,cancelCalculateOptionPrice (int reqId) 23 | ,Cancels an option's price calculation request.  More... 24 | , 25 | void ,cancelFundamentalData (int reqId) 26 | ,Cancels Fundamental data request.  More... 27 | , 28 | void ,cancelNewsBulletin () 29 | ,Cancels IB's news bulletin subscription.  More... 30 | , 31 | void ,cancelRealTimeBars (int tickerId) 32 | ,Cancels Real Time Bars' subscription.  More... 33 | , 34 | void ,"exerciseOptions (int tickerId, Contract contract, int exerciseAction, int exerciseQuantity, string account, int ovrd)" 35 | ,Exercises an options contract 36 | ,Note: this function is affected by a TWS setting which specifies if an exercise request must be finalized.  More... 37 | , 38 | void ,"replaceFA (int reqId, int faDataType, string xml)" 39 | ,Replaces Financial Advisor's settings A Financial Advisor can define three different configurations:  More... 40 | , 41 | void ,requestFA (int faDataType) 42 | ,Requests the FA configuration A Financial Advisor can define three different configurations:  More... 43 | , 44 | void ,"reqAccountSummary (int reqId, string group, string tags)" 45 | ,Requests a specific account's summary. 46 | ,This method will subscribe to the account summary as presented in the TWS' Account Summary tab. The data is returned at EWrapper::accountSummary 47 | ,https://www.interactivebrokers.com/en/software/tws/accountwindowtop.htm.  More... 48 | , 49 | void ,"reqAccountUpdates (bool subscribe, string acctCode)" 50 | ,"Subscribes to a specific account's information and portfolio. Through this method, a single account's subscription can be started/stopped. As a result from the subscription, the account's information, portfolio and last update time will be received at EWrapper::updateAccountValue, EWrapper::updateAccountPortfolio, EWrapper::updateAccountTime respectively. All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time. A second subscription request for another account when the previous one is still active will cause the first one to be canceled in favour of the second one. Consider user reqPositions if you want to retrieve all your accounts' portfolios directly. More..." 51 | , 52 | void ,reqAllOpenOrders () 53 | ,Requests all current open orders in associated accounts at the current moment. The existing orders will be received via the openOrder and orderStatus events. Open orders are returned once; this function does not initiate a subscription.  More... 54 | , 55 | void ,reqAutoOpenOrders (bool autoBind) 56 | ,Requests status updates about future orders placed from TWS. Can only be used with client ID 0.  More... 57 | , 58 | void ,"reqContractDetails (int reqId, Contract contract)" 59 | ,Requests contract information. 60 | ,This method will provide all the contracts matching the contract provided. It can also be used to retrieve complete options and futures chains. This information will be returned at EWrapper:contractDetails. Though it is now (in API version > 9.72.12) advised to use reqSecDefOptParams for that purpose.  61 | ,.  More... 62 | , 63 | void ,"reqExecutions (int reqId, ExecutionFilter filter)" 64 | ,"Requests current day's (since midnight) executions matching the filter. Only the current day's executions can be retrieved. Along with the executions, the CommissionReport will also be returned. The execution details will arrive at EWrapper:execDetails.  More..." 65 | , 66 | void ,"reqFundamentalData (int reqId, Contract contract, string reportType, List< TagValue > fundamentalDataOptions)" 67 | ,Legacy/DEPRECATED. Requests the contract's fundamental data. Fundamental data is returned at EWrapper::fundamentalData.  More... 68 | , 69 | void ,reqNewsBulletins (bool allMessages) 70 | ,Subscribes to IB's News Bulletins.  More... 71 | , 72 | void ,reqOpenOrders () 73 | ,"Requests all open orders places by this specific API client (identified by the API client id). For client ID 0, this will bind previous manual TWS orders.  More..." 74 | , 75 | void ,reqPositions () 76 | ,"Subscribes to position updates for all accessible accounts. All positions sent initially, and then only updates as positions change.  More..." 77 | , 78 | void ,reqScannerParameters () 79 | ,Requests an XML list of scanner parameters valid in TWS.  80 | ,Not all parameters are valid from API scanner.  More... 81 | , 82 | void ,"reqScannerSubscription (int reqId, ScannerSubscription subscription, List< TagValue > scannerSubscriptionOptions, List< TagValue > scannerSubscriptionFilterOptions)" 83 | ,Starts a subscription to market scan results based on the provided parameters.  More... 84 | , 85 | void ,"reqScannerSubscription (int reqId, ScannerSubscription subscription, string scannerSubscriptionOptions, string scannerSubscriptionFilterOptions)" 86 | , 87 | void ,setServerLogLevel (int logLevel) 88 | ,Changes the TWS/GW log level. The default is 2 = ERROR 89 | ,5 = DETAIL is required for capturing all API messages and troubleshooting API programs 90 | ,Valid values are: 91 | ,1 = SYSTEM 92 | ,2 = ERROR 93 | ,3 = WARNING 94 | ,4 = INFORMATION 95 | ,5 = DETAIL 96 | ,.  97 | , 98 | void ,queryDisplayGroups (int requestId) 99 | ,Requests all available Display Groups in TWS.  More... 100 | , 101 | void ,"subscribeToGroupEvents (int requestId, int groupId)" 102 | ,Integrates API client and TWS window grouping.  More... 103 | , 104 | void ,"updateDisplayGroup (int requestId, string contractInfo)" 105 | ,Updates the contract displayed in a TWS Window Group.  More... 106 | , 107 | void ,unsubscribeFromGroupEvents (int requestId) 108 | ,Cancels a TWS Window Group subscription.  109 | , 110 | void ,"reqPositionsMulti (int requestId, string account, string modelCode)" 111 | ,"Requests position subscription for account and/or model Initially all positions are returned, and then updates are returned for any position changes in real time.  More..." 112 | , 113 | void ,cancelPositionsMulti (int requestId) 114 | ,Cancels positions request for account and/or model.  More... 115 | , 116 | void ,"reqAccountUpdatesMulti (int requestId, string account, string modelCode, bool ledgerAndNLV)" 117 | ,Requests account updates for account and/or model.  More... 118 | , 119 | void ,cancelAccountUpdatesMulti (int requestId) 120 | ,Cancels account updates request for account and/or model.  More... 121 | , 122 | void ,"reqSecDefOptParams (int reqId, string underlyingSymbol, string futFopExchange, string underlyingSecType, int underlyingConId)" 123 | ,Requests security definition option parameters for viewing a contract's option chain.  More... 124 | , 125 | void ,reqSoftDollarTiers (int reqId) 126 | ,Requests pre-defined Soft Dollar Tiers. This is only supported for registered professional advisors and hedge and mutual funds who have configured Soft Dollar Tiers in Account Management. Refer to: https://www.interactivebrokers.com/en/software/am/am/manageaccount/requestsoftdollars.htm?Highlight=soft%20dollar%20tier.  More... 127 | , 128 | void ,reqFamilyCodes () 129 | ,"Requests family codes for an account, for instance if it is a FA, IBroker, or associated account.  More..." 130 | , 131 | void ,"reqMatchingSymbols (int reqId, string pattern)" 132 | ,Requests matching stock symbols.  More... 133 | , 134 | , 135 | void ,"reqSmartComponents (int reqId, string bboExchange)" 136 | ,Returns the mapping of single letter codes to exchange names given the mapping identifier.  More... 137 | , 138 | void ,reqNewsProviders () 139 | ,Requests news providers which the user has subscribed to.  More... 140 | , 141 | void ,"reqNewsArticle (int requestId, string providerCode, string articleId, List< TagValue > newsArticleOptions)" 142 | ,Requests news article body given articleId.  More... 143 | , 144 | void ,"reqHistoricalNews (int requestId, int conId, string providerCodes, string startDateTime, string endDateTime, int totalResults, List< TagValue> historicalNewsOptions)" 145 | ,Requests historical news headlines.  More... 146 | , 147 | void ,reqMarketRule (int marketRuleId) 148 | ,Requests details about a given market rule 149 | ,The market rule for an instrument on a particular exchange provides details about how the minimum price increment changes with price 150 | ,A list of market rule ids can be obtained by invoking reqContractDetails 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. 151 | ,.  More... 152 | , 153 | void ,"reqPnL (int reqId, string account, string modelCode)" 154 | ,Creates subscription for real time daily PnL and unrealized PnL updates.  More... 155 | , 156 | void ,cancelPnL (int reqId) 157 | ,cancels subscription for real time updated daily PnL params reqId  158 | , 159 | void ,"reqPnLSingle (int reqId, string account, string modelCode, int conId)" 160 | ,Requests real time updates for daily PnL of individual positions.  More... 161 | , 162 | void ,cancelPnLSingle (int reqId) 163 | ,Cancels real time subscription for a positions daily PnL information.  More... 164 | , 165 | void ,reqWshMetaData (int reqId) 166 | ,Requests metadata from the WSH calendar.  More... 167 | , 168 | void ,cancelWshMetaData (int reqId) 169 | ,Cancels pending request for WSH metadata.  More... 170 | , 171 | void ,"reqWshEventData (int reqId, WshEventData wshEventData)" 172 | ,Requests event data from the wSH calendar.  More... 173 | , 174 | void ,cancelWshEventData (int reqId) 175 | ,Cancels pending WSH event data request.  More... 176 | , 177 | void ,reqUserInfo (int reqId) 178 | ,Requests user info.  More... 179 | , -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::contract::{Contract, Query}; 4 | 5 | #[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 6 | pub struct InvalidInMsg(pub String); 7 | 8 | impl std::fmt::Display for InvalidInMsg { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | write!(f, "Invalid message received from API: {}", self.0) 11 | } 12 | } 13 | 14 | impl std::error::Error for InvalidInMsg { 15 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 16 | None 17 | } 18 | 19 | fn description(&self) -> &str { 20 | "description() is deprecated; use Display" 21 | } 22 | 23 | fn cause(&self) -> Option<&dyn std::error::Error> { 24 | self.source() 25 | } 26 | } 27 | 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 29 | pub enum In { 30 | TickPrice, 31 | TickSize, 32 | OrderStatus, 33 | ErrMsg, 34 | OpenOrder, 35 | AcctValue, 36 | PortfolioValue, 37 | AcctUpdateTime, 38 | NextValidId, 39 | ContractData, 40 | ExecutionData, 41 | MarketDepth, 42 | MarketDepthL2, 43 | NewsBulletins, 44 | ManagedAccts, 45 | ReceiveFa, 46 | HistoricalData, 47 | BondContractData, 48 | ScannerParameters, 49 | ScannerData, 50 | TickOptionComputation, 51 | TickGeneric, 52 | TickString, 53 | TickEfp, 54 | CurrentTime, 55 | RealTimeBars, 56 | FundamentalData, 57 | ContractDataEnd, 58 | OpenOrderEnd, 59 | AcctDownloadEnd, 60 | ExecutionDataEnd, 61 | DeltaNeutralValidation, 62 | TickSnapshotEnd, 63 | MarketDataType, 64 | CommissionReport, 65 | PositionData, 66 | PositionEnd, 67 | AccountSummary, 68 | AccountSummaryEnd, 69 | VerifyMessageApi, 70 | VerifyCompleted, 71 | DisplayGroupList, 72 | DisplayGroupUpdated, 73 | VerifyAndAuthMessageApi, 74 | VerifyAndAuthCompleted, 75 | PositionMulti, 76 | PositionMultiEnd, 77 | AccountUpdateMulti, 78 | AccountUpdateMultiEnd, 79 | SecurityDefinitionOptionParameter, 80 | SecurityDefinitionOptionParameterEnd, 81 | SoftDollarTiers, 82 | FamilyCodes, 83 | SymbolSamples, 84 | MktDepthExchanges, 85 | TickReqParams, 86 | SmartComponents, 87 | NewsArticle, 88 | TickNews, 89 | NewsProviders, 90 | HistoricalNews, 91 | HistoricalNewsEnd, 92 | HeadTimestamp, 93 | HistogramData, 94 | HistoricalDataUpdate, 95 | RerouteMktDataReq, 96 | RerouteMktDepthReq, 97 | MarketRule, 98 | Pnl, 99 | PnlSingle, 100 | HistoricalTicks, 101 | HistoricalTicksBidAsk, 102 | HistoricalTicksLast, 103 | TickByTick, 104 | OrderBound, 105 | CompletedOrder, 106 | CompletedOrdersEnd, 107 | ReplaceFaEnd, 108 | WshMetaData, 109 | WshEventData, 110 | HistoricalSchedule, 111 | UserInfo, 112 | } 113 | 114 | impl FromStr for In { 115 | type Err = InvalidInMsg; 116 | 117 | fn from_str(s: &str) -> Result { 118 | Ok(match s { 119 | "1" => Self::TickPrice, 120 | "2" => Self::TickSize, 121 | "3" => Self::OrderStatus, 122 | "4" => Self::ErrMsg, 123 | "5" => Self::OpenOrder, 124 | "6" => Self::AcctValue, 125 | "7" => Self::PortfolioValue, 126 | "8" => Self::AcctUpdateTime, 127 | "9" => Self::NextValidId, 128 | "10" => Self::ContractData, 129 | "11" => Self::ExecutionData, 130 | "12" => Self::MarketDepth, 131 | "13" => Self::MarketDepthL2, 132 | "14" => Self::NewsBulletins, 133 | "15" => Self::ManagedAccts, 134 | "16" => Self::ReceiveFa, 135 | "17" => Self::HistoricalData, 136 | "18" => Self::BondContractData, 137 | "19" => Self::ScannerParameters, 138 | "20" => Self::ScannerData, 139 | "21" => Self::TickOptionComputation, 140 | "45" => Self::TickGeneric, 141 | "46" => Self::TickString, 142 | "47" => Self::TickEfp, 143 | "49" => Self::CurrentTime, 144 | "50" => Self::RealTimeBars, 145 | "51" => Self::FundamentalData, 146 | "52" => Self::ContractDataEnd, 147 | "53" => Self::OpenOrderEnd, 148 | "54" => Self::AcctDownloadEnd, 149 | "55" => Self::ExecutionDataEnd, 150 | "56" => Self::DeltaNeutralValidation, 151 | "57" => Self::TickSnapshotEnd, 152 | "58" => Self::MarketDataType, 153 | "59" => Self::CommissionReport, 154 | "61" => Self::PositionData, 155 | "62" => Self::PositionEnd, 156 | "63" => Self::AccountSummary, 157 | "64" => Self::AccountSummaryEnd, 158 | "65" => Self::VerifyMessageApi, 159 | "66" => Self::VerifyCompleted, 160 | "67" => Self::DisplayGroupList, 161 | "68" => Self::DisplayGroupUpdated, 162 | "69" => Self::VerifyAndAuthMessageApi, 163 | "70" => Self::VerifyAndAuthCompleted, 164 | "71" => Self::PositionMulti, 165 | "72" => Self::PositionMultiEnd, 166 | "73" => Self::AccountUpdateMulti, 167 | "74" => Self::AccountUpdateMultiEnd, 168 | "75" => Self::SecurityDefinitionOptionParameter, 169 | "76" => Self::SecurityDefinitionOptionParameterEnd, 170 | "77" => Self::SoftDollarTiers, 171 | "78" => Self::FamilyCodes, 172 | "79" => Self::SymbolSamples, 173 | "80" => Self::MktDepthExchanges, 174 | "81" => Self::TickReqParams, 175 | "82" => Self::SmartComponents, 176 | "83" => Self::NewsArticle, 177 | "84" => Self::TickNews, 178 | "85" => Self::NewsProviders, 179 | "86" => Self::HistoricalNews, 180 | "87" => Self::HistoricalNewsEnd, 181 | "88" => Self::HeadTimestamp, 182 | "89" => Self::HistogramData, 183 | "90" => Self::HistoricalDataUpdate, 184 | "91" => Self::RerouteMktDataReq, 185 | "92" => Self::RerouteMktDepthReq, 186 | "93" => Self::MarketRule, 187 | "94" => Self::Pnl, 188 | "95" => Self::PnlSingle, 189 | "96" => Self::HistoricalTicks, 190 | "97" => Self::HistoricalTicksBidAsk, 191 | "98" => Self::HistoricalTicksLast, 192 | "99" => Self::TickByTick, 193 | "100" => Self::OrderBound, 194 | "101" => Self::CompletedOrder, 195 | "102" => Self::CompletedOrdersEnd, 196 | "103" => Self::ReplaceFaEnd, 197 | "104" => Self::WshMetaData, 198 | "105" => Self::WshEventData, 199 | "106" => Self::HistoricalSchedule, 200 | "107" => Self::UserInfo, 201 | s => return Err(InvalidInMsg(s.to_owned())), 202 | }) 203 | } 204 | } 205 | 206 | // Ok, we haven't implemented all the outgoing client messages 207 | #[allow(dead_code)] 208 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)] 209 | pub enum Out { 210 | #[serde(rename(serialize = "1"))] 211 | ReqMktData, 212 | #[serde(rename(serialize = "2"))] 213 | CancelMktData, 214 | #[serde(rename(serialize = "3"))] 215 | PlaceOrder, 216 | #[serde(rename(serialize = "4"))] 217 | CancelOrder, 218 | #[serde(rename(serialize = "5"))] 219 | ReqOpenOrders, 220 | #[serde(rename(serialize = "6"))] 221 | ReqAcctData, 222 | #[serde(rename(serialize = "7"))] 223 | ReqExecutions, 224 | #[serde(rename(serialize = "8"))] 225 | ReqIds, 226 | #[serde(rename(serialize = "9"))] 227 | ReqContractData, 228 | #[serde(rename(serialize = "10"))] 229 | ReqMktDepth, 230 | #[serde(rename(serialize = "11"))] 231 | CancelMktDepth, 232 | #[serde(rename(serialize = "12"))] 233 | ReqNewsBulletins, 234 | #[serde(rename(serialize = "13"))] 235 | CancelNewsBulletins, 236 | #[serde(rename(serialize = "14"))] 237 | SetServerLoglevel, 238 | #[serde(rename(serialize = "15"))] 239 | ReqAutoOpenOrders, 240 | #[serde(rename(serialize = "16"))] 241 | ReqAllOpenOrders, 242 | #[serde(rename(serialize = "17"))] 243 | ReqManagedAccts, 244 | #[serde(rename(serialize = "18"))] 245 | ReqFa, 246 | #[serde(rename(serialize = "19"))] 247 | ReplaceFa, 248 | #[serde(rename(serialize = "20"))] 249 | ReqHistoricalData, 250 | #[serde(rename(serialize = "21"))] 251 | ExerciseOptions, 252 | #[serde(rename(serialize = "22"))] 253 | ReqScannerSubscription, 254 | #[serde(rename(serialize = "23"))] 255 | CancelScannerSubscription, 256 | #[serde(rename(serialize = "24"))] 257 | ReqScannerParameters, 258 | #[serde(rename(serialize = "25"))] 259 | CancelHistoricalData, 260 | #[serde(rename(serialize = "49"))] 261 | ReqCurrentTime, 262 | #[serde(rename(serialize = "50"))] 263 | ReqRealTimeBars, 264 | #[serde(rename(serialize = "51"))] 265 | CancelRealTimeBars, 266 | #[serde(rename(serialize = "52"))] 267 | ReqFundamentalData, 268 | #[serde(rename(serialize = "53"))] 269 | CancelFundamentalData, 270 | #[serde(rename(serialize = "54"))] 271 | ReqCalcImpliedVolatility, 272 | #[serde(rename(serialize = "55"))] 273 | ReqCalcOptionPrice, 274 | #[serde(rename(serialize = "56"))] 275 | CancelCalcImpliedVolatility, 276 | #[serde(rename(serialize = "57"))] 277 | CancelCalcOptionPrice, 278 | #[serde(rename(serialize = "58"))] 279 | ReqGlobalCancel, 280 | #[serde(rename(serialize = "59"))] 281 | ReqMarketDataType, 282 | #[serde(rename(serialize = "61"))] 283 | ReqPositions, 284 | #[serde(rename(serialize = "62"))] 285 | ReqAccountSummary, 286 | #[serde(rename(serialize = "63"))] 287 | CancelAccountSummary, 288 | #[serde(rename(serialize = "64"))] 289 | CancelPositions, 290 | #[serde(rename(serialize = "65"))] 291 | VerifyRequest, 292 | #[serde(rename(serialize = "66"))] 293 | VerifyMessage, 294 | #[serde(rename(serialize = "67"))] 295 | QueryDisplayGroups, 296 | #[serde(rename(serialize = "68"))] 297 | SubscribeToGroupEvents, 298 | #[serde(rename(serialize = "69"))] 299 | UpdateDisplayGroup, 300 | #[serde(rename(serialize = "70"))] 301 | UnsubscribeFromGroupEvents, 302 | #[serde(rename(serialize = "71"))] 303 | StartApi, 304 | #[serde(rename(serialize = "72"))] 305 | VerifyAndAuthRequest, 306 | #[serde(rename(serialize = "73"))] 307 | VerifyAndAuthMessage, 308 | #[serde(rename(serialize = "74"))] 309 | ReqPositionsMulti, 310 | #[serde(rename(serialize = "75"))] 311 | CancelPositionsMulti, 312 | #[serde(rename(serialize = "76"))] 313 | ReqAccountUpdatesMulti, 314 | #[serde(rename(serialize = "77"))] 315 | CancelAccountUpdatesMulti, 316 | #[serde(rename(serialize = "78"))] 317 | ReqSecDefOptParams, 318 | #[serde(rename(serialize = "79"))] 319 | ReqSoftDollarTiers, 320 | #[serde(rename(serialize = "80"))] 321 | ReqFamilyCodes, 322 | #[serde(rename(serialize = "81"))] 323 | ReqMatchingSymbols, 324 | #[serde(rename(serialize = "82"))] 325 | ReqMktDepthExchanges, 326 | #[serde(rename(serialize = "83"))] 327 | ReqSmartComponents, 328 | #[serde(rename(serialize = "84"))] 329 | ReqNewsArticle, 330 | #[serde(rename(serialize = "85"))] 331 | ReqNewsProviders, 332 | #[serde(rename(serialize = "86"))] 333 | ReqHistoricalNews, 334 | #[serde(rename(serialize = "87"))] 335 | ReqHeadTimestamp, 336 | #[serde(rename(serialize = "88"))] 337 | ReqHistogramData, 338 | #[serde(rename(serialize = "89"))] 339 | CancelHistogramData, 340 | #[serde(rename(serialize = "90"))] 341 | CancelHeadTimestamp, 342 | #[serde(rename(serialize = "91"))] 343 | ReqMarketRule, 344 | #[serde(rename(serialize = "92"))] 345 | ReqPnl, 346 | #[serde(rename(serialize = "93"))] 347 | CancelPnl, 348 | #[serde(rename(serialize = "94"))] 349 | ReqPnlSingle, 350 | #[serde(rename(serialize = "95"))] 351 | CancelPnlSingle, 352 | #[serde(rename(serialize = "96"))] 353 | ReqHistoricalTicks, 354 | #[serde(rename(serialize = "97"))] 355 | ReqTickByTickData, 356 | #[serde(rename(serialize = "98"))] 357 | CancelTickByTickData, 358 | #[serde(rename(serialize = "99"))] 359 | ReqCompletedOrders, 360 | #[serde(rename(serialize = "100"))] 361 | ReqWshMetaData, 362 | #[serde(rename(serialize = "101"))] 363 | CancelWshMetaData, 364 | #[serde(rename(serialize = "102"))] 365 | ReqWshEventData, 366 | #[serde(rename(serialize = "103"))] 367 | CancelWshEventData, 368 | #[serde(rename(serialize = "104"))] 369 | ReqUserInfo, 370 | } 371 | 372 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 373 | pub enum ToWrapper { 374 | ContractQuery((Query, i64)), 375 | } 376 | 377 | #[allow(clippy::redundant_pub_crate)] 378 | #[derive(Debug, Clone, PartialEq)] 379 | pub(crate) enum ToClient { 380 | NewContract(Contract), 381 | } 382 | -------------------------------------------------------------------------------- /ibkr_rust_macros/src/security.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | #[allow(clippy::enum_glob_use)] 6 | use SecType::*; 7 | use syn::{Ident, parse_str}; 8 | 9 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 10 | enum SecType { 11 | Forex, 12 | Crypto, 13 | Stock, 14 | Index, 15 | SecFuture, 16 | SecOption, 17 | Commodity, 18 | } 19 | 20 | impl SecType { 21 | #[inline] 22 | const fn as_str(self) -> &'static str { 23 | match self { 24 | Forex => "Forex", 25 | Crypto => "Crypto", 26 | Stock => "Stock", 27 | Index => "Index", 28 | SecFuture => "SecFuture", 29 | SecOption => "SecOption", 30 | Commodity => "Commodity", 31 | } 32 | } 33 | } 34 | 35 | impl From<&str> for SecType { 36 | fn from(s: &str) -> Self { 37 | match s { 38 | "Forex" => Forex, 39 | "Crypto" => Crypto, 40 | "Stock" => Stock, 41 | "Index" => Index, 42 | "SecFuture" => SecFuture, 43 | "SecOption" => SecOption, 44 | "Commodity" => Commodity, 45 | _ => panic!("Invalid Security name {s}."), 46 | } 47 | } 48 | } 49 | 50 | impl From<&SecType> for &'static str { 51 | fn from(value: &SecType) -> Self { 52 | value.as_str() 53 | } 54 | } 55 | 56 | impl From<&Ident> for SecType { 57 | fn from(value: &Ident) -> Self { 58 | let s = value.to_string(); 59 | s.as_str().into() 60 | } 61 | } 62 | 63 | const CONTRACTS: [SecType; 7] = [Forex, Crypto, Stock, Index, SecFuture, SecOption, Commodity]; 64 | 65 | fn impl_try_from_other_contracts(name: &Ident) -> TokenStream { 66 | let idents = CONTRACTS 67 | .into_iter() 68 | .filter_map(|c| { 69 | if c == name.into() { 70 | None 71 | } else { 72 | parse_str(c.as_str()).unwrap() 73 | } 74 | }) 75 | .collect::>() 76 | .into_iter(); 77 | 78 | quote! { 79 | #(impl TryFrom<#idents> for #name { 80 | type Error = UnexpectedSecurityType; 81 | 82 | fn try_from(_: #idents) -> Result { 83 | Err(UnexpectedSecurityType { 84 | expected: ContractType::#name, 85 | found: ContractType::#idents, 86 | }) 87 | } 88 | })* 89 | } 90 | } 91 | 92 | fn impl_into_contract(name: &Ident) -> TokenStream { 93 | let idents = CONTRACTS 94 | .into_iter() 95 | .filter_map(|c| { 96 | if c == name.into() { 97 | None 98 | } else { 99 | parse_str(c.as_str()).unwrap() 100 | } 101 | }) 102 | .collect::>() 103 | .into_iter(); 104 | 105 | quote! { 106 | impl From<#name> for Contract { 107 | fn from(value: #name) -> Self { 108 | Self::#name(value) 109 | } 110 | } 111 | 112 | impl TryFrom for #name { 113 | type Error = UnexpectedSecurityType; 114 | 115 | fn try_from(value: Contract) -> Result { 116 | match value { 117 | Contract::#name(t) => Ok(t), 118 | #(Contract::#idents(_) => Err( 119 | UnexpectedSecurityType { 120 | expected: ContractType::#name, 121 | found: ContractType::#idents 122 | } 123 | )),* 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | #[allow(clippy::module_name_repetitions, clippy::too_many_lines)] 131 | pub fn impl_security(ast: &syn::DeriveInput) -> TokenStream { 132 | let name = &ast.ident; 133 | let s_name: SecType = name.into(); 134 | 135 | let contract_id = match s_name { 136 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { self.contract_id }, 137 | SecOption => quote! { 138 | match self { 139 | SecOption::Call(inner) | SecOption::Put(inner) => inner.contract_id 140 | } 141 | }, 142 | }; 143 | let symbol = match s_name { 144 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { self.symbol.as_str() }, 145 | SecOption => quote! { 146 | match self { 147 | SecOption::Call(inner) | SecOption::Put(inner) => inner.symbol.as_str() 148 | } 149 | }, 150 | }; 151 | let security_type = match s_name { 152 | Forex => "CASH", 153 | Crypto => "Crypto", 154 | Stock => "STK", 155 | Index => "IND", 156 | SecFuture => "FUT", 157 | SecOption => "OPT", 158 | Commodity => "CMDTY", 159 | }; 160 | let expiration_date = match s_name { 161 | Forex | Crypto | Stock | Index | Commodity => { 162 | quote! { None:: } 163 | } 164 | SecFuture => quote! { Some(self.expiration_date) }, 165 | SecOption => quote! { 166 | match self { 167 | SecOption::Call(inner) | SecOption::Put(inner) => Some(inner.expiration_date) 168 | } 169 | }, 170 | }; 171 | let strike = match s_name { 172 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { None:: }, 173 | SecOption => quote! { 174 | match self { 175 | SecOption::Call(inner) | SecOption::Put(inner) => Some(inner.strike) 176 | } 177 | }, 178 | }; 179 | let right = match s_name { 180 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { None::<&str> }, 181 | SecOption => quote! { 182 | match self { 183 | SecOption::Call(_) => Some("C"), 184 | SecOption::Put(_) => Some("P"), 185 | } 186 | }, 187 | }; 188 | let multiplier = match s_name { 189 | Forex | Crypto | Stock | Index | Commodity => { 190 | quote! { None:: } 191 | } 192 | SecFuture => quote! { Some(self.multiplier) }, 193 | SecOption => quote! { 194 | match self { 195 | SecOption::Call(inner) | SecOption::Put(inner) => Some(inner.multiplier) 196 | } 197 | }, 198 | }; 199 | let exchange = match s_name { 200 | Forex | Stock | Index | SecFuture | Commodity => { 201 | quote! { self.exchange } 202 | } 203 | Crypto => quote! { Routing::Primary(Primary::PaxosCryptoExchange) }, 204 | SecOption => quote! { 205 | match self { 206 | SecOption::Call(inner) | SecOption::Put(inner) => inner.exchange 207 | } 208 | }, 209 | }; 210 | let primary_exchange = match s_name { 211 | Forex | Crypto | Index | SecFuture | SecOption | Commodity => quote! { None:: }, 212 | Stock => quote! { Some(self.primary_exchange) }, 213 | }; 214 | let currency = match s_name { 215 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { self.currency }, 216 | SecOption => quote! { 217 | match self { 218 | SecOption::Call(inner) | SecOption::Put(inner) => inner.currency 219 | } 220 | }, 221 | }; 222 | let local_symbol = match s_name { 223 | Forex | Crypto | Stock | Index | SecFuture | Commodity => { 224 | quote! { self.local_symbol.as_str() } 225 | } 226 | SecOption => quote! { 227 | match self { 228 | SecOption::Call(inner) | SecOption::Put(inner) => inner.local_symbol.as_str() 229 | } 230 | }, 231 | }; 232 | let min_tick = match s_name { 233 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { self.min_tick }, 234 | SecOption => quote! { 235 | match self { 236 | SecOption::Call(inner) | SecOption::Put(inner) => inner.min_tick 237 | } 238 | }, 239 | }; 240 | let trading_class = match s_name { 241 | Forex | Crypto | Stock | SecFuture | Commodity => { 242 | quote! { Some(self.trading_class.as_str()) } 243 | } 244 | Index => quote! { None::<&str> }, 245 | SecOption => quote! { 246 | match self { 247 | SecOption::Call(inner) | SecOption::Put(inner) => Some(inner.trading_class.as_str()) 248 | } 249 | }, 250 | }; 251 | let long_name = match s_name { 252 | Forex | Crypto | Stock | Index | SecFuture | Commodity => { 253 | quote! { self.long_name.as_str() } 254 | } 255 | SecOption => quote! { 256 | match self { 257 | SecOption::Call(inner) | SecOption::Put(inner) => inner.long_name.as_str() 258 | } 259 | }, 260 | }; 261 | let order_types = match s_name { 262 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { &self.order_types }, 263 | SecOption => quote! { 264 | match self { 265 | SecOption::Call(inner) | SecOption::Put(inner) => &inner.order_types 266 | } 267 | }, 268 | }; 269 | let valid_exchanges = match s_name { 270 | Forex | Crypto | Stock | Index | SecFuture | Commodity => quote! { &self.valid_exchanges }, 271 | SecOption => quote! { 272 | match self { 273 | SecOption::Call(inner) | SecOption::Put(inner) => &inner.valid_exchanges 274 | } 275 | }, 276 | }; 277 | 278 | let try_from_impl = impl_try_from_other_contracts(name); 279 | let into_contract_impl = impl_into_contract(name); 280 | 281 | let gen_tokens = quote! { 282 | impl crate::contract::indicators::Valid for #name { 283 | fn as_out_msg(&self) -> crate::contract::indicators::SecurityOutMsg<'_> { 284 | crate::contract::indicators::SecurityOutMsg { 285 | contract_id: #contract_id, 286 | symbol: #symbol, 287 | security_type: #security_type, 288 | expiration_date: #expiration_date, 289 | strike: #strike, 290 | right: #right, 291 | multiplier: #multiplier, 292 | exchange: #exchange, 293 | primary_exchange: #primary_exchange, 294 | currency: #currency, 295 | local_symbol: #local_symbol, 296 | trading_class: #trading_class, 297 | } 298 | } 299 | } 300 | 301 | impl serde::Serialize for #name { 302 | fn serialize(&self, serializer: S) -> Result where S: Serializer { 303 | let mut state = serializer.serialize_struct("Contract", 14)?; 304 | state.serialize_field("contract_id", &#contract_id)?; 305 | state.serialize_field("security_type", &#security_type)?; 306 | state.serialize_field("symbol", &#symbol)?; 307 | state.serialize_field("long_name", &#long_name)?; 308 | state.serialize_field("min_tick", &#min_tick)?; 309 | state.serialize_field("exchange", &#exchange)?; 310 | state.serialize_field("primary_exchange", &#primary_exchange)?; 311 | state.serialize_field("currency", &#currency)?; 312 | state.serialize_field("local_symbol", &#local_symbol)?; 313 | state.serialize_field("trading_class", &#trading_class)?; 314 | state.serialize_field("expiration_date", &#expiration_date.map(|d| d.format("%Y%m%d").to_string()))?; 315 | state.serialize_field("strike", &#strike)?; 316 | state.serialize_field("option_class", &#right)?; 317 | state.serialize_field("multiplier", &#multiplier)?; 318 | state.end() 319 | } 320 | } 321 | 322 | impl Security for #name { 323 | #[inline] 324 | fn contract_id(&self) -> ContractId { 325 | #contract_id 326 | } 327 | #[inline] 328 | fn min_tick(&self) -> f64 { 329 | #min_tick 330 | } 331 | #[inline] 332 | fn symbol(&self) -> &str { 333 | #symbol 334 | } 335 | #[inline] 336 | fn currency(&self) -> Currency { 337 | #currency 338 | } 339 | #[inline] 340 | fn local_symbol(&self) -> &str { 341 | #local_symbol 342 | } 343 | #[inline] 344 | fn long_name(&self) -> &str { 345 | #long_name 346 | } 347 | #[inline] 348 | fn order_types(&self) -> &Vec { 349 | #order_types 350 | } 351 | #[inline] 352 | fn valid_exchanges(&self) -> &Vec { 353 | #valid_exchanges 354 | } 355 | #[inline] 356 | fn contract_type(&self) -> ContractType { 357 | ContractType::#name 358 | } 359 | } 360 | 361 | #try_from_impl 362 | 363 | #into_contract_impl 364 | }; 365 | gen_tokens 366 | } 367 | -------------------------------------------------------------------------------- /src/figi.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[repr(u8)] 4 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 5 | enum Consonant { 6 | B = 11, 7 | C = 12, 8 | D = 13, 9 | F = 15, 10 | G = 16, 11 | H = 17, 12 | J = 19, 13 | K = 20, 14 | L = 21, 15 | M = 22, 16 | N = 23, 17 | P = 25, 18 | Q = 26, 19 | R = 27, 20 | S = 28, 21 | T = 29, 22 | V = 31, 23 | W = 32, 24 | X = 33, 25 | Y = 34, 26 | Z = 35, 27 | } 28 | 29 | #[repr(u8)] 30 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 31 | enum ConsonantOrNumeric { 32 | Zero = 0, 33 | One = 1, 34 | Two = 2, 35 | Three = 3, 36 | Four = 4, 37 | Five = 5, 38 | Six = 6, 39 | Seven = 7, 40 | Eight = 8, 41 | Nine = 9, 42 | B = 11, 43 | C = 12, 44 | D = 13, 45 | F = 15, 46 | G = 16, 47 | H = 17, 48 | J = 19, 49 | K = 20, 50 | L = 21, 51 | M = 22, 52 | N = 23, 53 | P = 25, 54 | Q = 26, 55 | R = 27, 56 | S = 28, 57 | T = 29, 58 | V = 31, 59 | W = 32, 60 | X = 33, 61 | Y = 34, 62 | Z = 35, 63 | } 64 | 65 | impl From for char { 66 | fn from(value: Consonant) -> char { 67 | match value { 68 | Consonant::B => 'B', 69 | Consonant::C => 'C', 70 | Consonant::D => 'D', 71 | Consonant::F => 'F', 72 | Consonant::G => 'G', 73 | Consonant::H => 'H', 74 | Consonant::J => 'J', 75 | Consonant::K => 'K', 76 | Consonant::L => 'L', 77 | Consonant::M => 'M', 78 | Consonant::N => 'N', 79 | Consonant::P => 'P', 80 | Consonant::Q => 'Q', 81 | Consonant::R => 'R', 82 | Consonant::S => 'S', 83 | Consonant::T => 'T', 84 | Consonant::V => 'V', 85 | Consonant::W => 'W', 86 | Consonant::X => 'X', 87 | Consonant::Y => 'Y', 88 | Consonant::Z => 'Z', 89 | } 90 | } 91 | } 92 | 93 | impl From for char { 94 | fn from(value: ConsonantOrNumeric) -> char { 95 | match value { 96 | ConsonantOrNumeric::B => 'B', 97 | ConsonantOrNumeric::C => 'C', 98 | ConsonantOrNumeric::D => 'D', 99 | ConsonantOrNumeric::F => 'F', 100 | ConsonantOrNumeric::G => 'G', 101 | ConsonantOrNumeric::H => 'H', 102 | ConsonantOrNumeric::J => 'J', 103 | ConsonantOrNumeric::K => 'K', 104 | ConsonantOrNumeric::L => 'L', 105 | ConsonantOrNumeric::M => 'M', 106 | ConsonantOrNumeric::N => 'N', 107 | ConsonantOrNumeric::P => 'P', 108 | ConsonantOrNumeric::Q => 'Q', 109 | ConsonantOrNumeric::R => 'R', 110 | ConsonantOrNumeric::S => 'S', 111 | ConsonantOrNumeric::T => 'T', 112 | ConsonantOrNumeric::V => 'V', 113 | ConsonantOrNumeric::W => 'W', 114 | ConsonantOrNumeric::X => 'X', 115 | ConsonantOrNumeric::Y => 'Y', 116 | ConsonantOrNumeric::Z => 'Z', 117 | ConsonantOrNumeric::Zero => '0', 118 | ConsonantOrNumeric::One => '1', 119 | ConsonantOrNumeric::Two => '2', 120 | ConsonantOrNumeric::Three => '3', 121 | ConsonantOrNumeric::Four => '4', 122 | ConsonantOrNumeric::Five => '5', 123 | ConsonantOrNumeric::Six => '6', 124 | ConsonantOrNumeric::Seven => '7', 125 | ConsonantOrNumeric::Eight => '8', 126 | ConsonantOrNumeric::Nine => '9', 127 | } 128 | } 129 | } 130 | 131 | impl From for char { 132 | fn from(_: G) -> Self { 133 | 'G' 134 | } 135 | } 136 | 137 | impl TryFrom for Consonant { 138 | type Error = InvalidConsonant; 139 | 140 | fn try_from(value: char) -> Result { 141 | Ok(match value { 142 | 'B' => Self::B, 143 | 'C' => Self::C, 144 | 'D' => Self::D, 145 | 'F' => Self::F, 146 | 'G' => Self::G, 147 | 'H' => Self::H, 148 | 'J' => Self::J, 149 | 'K' => Self::K, 150 | 'L' => Self::L, 151 | 'M' => Self::M, 152 | 'N' => Self::N, 153 | 'P' => Self::P, 154 | 'Q' => Self::Q, 155 | 'R' => Self::R, 156 | 'S' => Self::S, 157 | 'T' => Self::T, 158 | 'V' => Self::V, 159 | 'W' => Self::W, 160 | 'X' => Self::X, 161 | 'Y' => Self::Y, 162 | 'Z' => Self::Z, 163 | _ => return Err(InvalidConsonant), 164 | }) 165 | } 166 | } 167 | 168 | impl TryFrom for ConsonantOrNumeric { 169 | type Error = InvalidConsonantOrNumeric; 170 | 171 | fn try_from(value: char) -> Result { 172 | Ok(match value { 173 | 'B' => Self::B, 174 | 'C' => Self::C, 175 | 'D' => Self::D, 176 | 'F' => Self::F, 177 | 'G' => Self::G, 178 | 'H' => Self::H, 179 | 'J' => Self::J, 180 | 'K' => Self::K, 181 | 'L' => Self::L, 182 | 'M' => Self::M, 183 | 'N' => Self::N, 184 | 'P' => Self::P, 185 | 'Q' => Self::Q, 186 | 'R' => Self::R, 187 | 'S' => Self::S, 188 | 'T' => Self::T, 189 | 'V' => Self::V, 190 | 'W' => Self::W, 191 | 'X' => Self::X, 192 | 'Y' => Self::Y, 193 | 'Z' => Self::Z, 194 | '0' => Self::Zero, 195 | '1' => Self::One, 196 | '2' => Self::Two, 197 | '3' => Self::Three, 198 | '4' => Self::Four, 199 | '5' => Self::Five, 200 | '6' => Self::Six, 201 | '7' => Self::Seven, 202 | '8' => Self::Eight, 203 | '9' => Self::Nine, 204 | _ => return Err(InvalidConsonantOrNumeric), 205 | }) 206 | } 207 | } 208 | 209 | #[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 210 | struct InvalidConsonant; 211 | 212 | #[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 213 | struct InvalidConsonantOrNumeric; 214 | 215 | impl std::fmt::Display for InvalidConsonant { 216 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 217 | write!( 218 | f, 219 | "Invalid consonant. Must be an uppercase English consonant." 220 | ) 221 | } 222 | } 223 | 224 | impl std::fmt::Display for InvalidConsonantOrNumeric { 225 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 226 | write!(f, "Invalid consonant/number. Must be an uppercase English consonant or a digit 0,1,...,9.") 227 | } 228 | } 229 | 230 | impl std::error::Error for InvalidConsonant {} 231 | 232 | impl std::error::Error for InvalidConsonantOrNumeric {} 233 | 234 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)] 235 | struct G; 236 | 237 | impl From for u8 { 238 | fn from(_: G) -> u8 { 239 | 16 240 | } 241 | } 242 | 243 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)] 244 | #[allow(clippy::struct_field_names)] 245 | #[serde(into = "String", try_from = "String")] 246 | /// A valid FIGI code. See the module level documentation for a link to the official standard. 247 | pub struct Figi { 248 | pos_1: Consonant, 249 | pos_2: Consonant, 250 | pos_3: G, 251 | pos_4_12: [ConsonantOrNumeric; 9], 252 | } 253 | 254 | impl std::fmt::Display for Figi { 255 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 256 | let s = String::from(self); 257 | write!(f, "{s}") 258 | } 259 | } 260 | 261 | impl From for String { 262 | fn from(value: Figi) -> Self { 263 | value.to_string() 264 | } 265 | } 266 | 267 | impl TryFrom for Figi { 268 | type Error = InvalidFigi; 269 | 270 | fn try_from(value: String) -> Result { 271 | value.parse() 272 | } 273 | } 274 | 275 | impl std::str::FromStr for Figi { 276 | type Err = InvalidFigi; 277 | 278 | fn from_str(s: &str) -> Result { 279 | let b: [u8; 12] = s 280 | .as_bytes() 281 | .try_into() 282 | .map_err(|_| InvalidFigi::Length(s.to_owned()))?; 283 | let s = b.map(|c| c as char); 284 | 285 | Self::from_chars(&s) 286 | } 287 | } 288 | 289 | impl<'a> From<&'a Figi> for String { 290 | fn from(value: &Figi) -> Self { 291 | let mut s = String::with_capacity(12); 292 | s.push(value.pos_1.into()); 293 | s.push(value.pos_2.into()); 294 | s.push(value.pos_3.into()); 295 | for c in value.pos_4_12 { 296 | s.push(c.into()); 297 | } 298 | s 299 | } 300 | } 301 | 302 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 303 | #[allow(clippy::module_name_repetitions)] 304 | /// Represents all the possible ways a FIGI code could be invalid 305 | pub enum InvalidFigi { 306 | /// The checksum is invalid 307 | Checksum(String), 308 | /// The first two characters are BS, BM, GG, GB, GH, KY, or VG 309 | FirstTwo(String), 310 | /// The third character is not G. 311 | Third(String), 312 | /// One of the first two characters is not an uppercase English consonant 313 | Consonant(String), 314 | /// One of the fourth through eleventh characters is not an uppercase English consonant or digit 0 through 9. 315 | ConsonantOrNumeric(String), 316 | /// The provided code is not exactly twelve characters. 317 | Length(String), 318 | } 319 | 320 | impl std::fmt::Display for InvalidFigi { 321 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 322 | let msg = match self { 323 | Self::Checksum(s) => format!("Invalid checksum for: {s}"), 324 | Self::FirstTwo(s) => format!("Invalid first two characters for {s}. First two characters cannot be BS, BM, GG, GB, GH, KY, or VG."), 325 | Self::Third(s) => format!("Invalid third character for {s}. Third character must be G"), 326 | Self::Consonant(s) => format!("Invalid consonant found for {s}. {InvalidConsonant}"), 327 | Self::ConsonantOrNumeric(s) => format!("Invalid consonant or numeric found for {s}. {InvalidConsonantOrNumeric}"), 328 | Self::Length(s) => format!("Invalid length. A FIGI code is exactly 12 characters long. {s}"), 329 | }; 330 | write!(f, "Invalid FIGI. {}", &msg) 331 | } 332 | } 333 | 334 | impl std::error::Error for InvalidFigi {} 335 | 336 | impl Figi { 337 | #[inline] 338 | /// Construct a new [`Figi`] from a sequence of 12 characters. 339 | /// 340 | /// # Returns 341 | /// A new, valid [`Figi`] 342 | /// 343 | /// # Errors 344 | /// Will error if the provided characters are not a valid FIGI code. 345 | pub fn from_chars(s: &[char; 12]) -> Result { 346 | let (pos_1, pos_2) = match (s[0], s[1]) { 347 | ('B', 'S' | 'M') | ('G', 'G' | 'B' | 'H') | ('K', 'Y') | ('V', 'G') => { 348 | return Err(InvalidFigi::FirstTwo(s.iter().collect())) 349 | } 350 | (c1, c2) => ( 351 | Consonant::try_from(c1).map_err(|_| InvalidFigi::Consonant(s.iter().collect()))?, 352 | Consonant::try_from(c2).map_err(|_| InvalidFigi::Consonant(s.iter().collect()))?, 353 | ), 354 | }; 355 | let pos_3 = if s[2] == 'G' { 356 | G 357 | } else { 358 | return Err(InvalidFigi::Third(s.iter().collect())); 359 | }; 360 | let pos_4_12 = [ 361 | ConsonantOrNumeric::try_from(s[3]) 362 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 363 | ConsonantOrNumeric::try_from(s[4]) 364 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 365 | ConsonantOrNumeric::try_from(s[5]) 366 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 367 | ConsonantOrNumeric::try_from(s[6]) 368 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 369 | ConsonantOrNumeric::try_from(s[7]) 370 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 371 | ConsonantOrNumeric::try_from(s[8]) 372 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 373 | ConsonantOrNumeric::try_from(s[9]) 374 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 375 | ConsonantOrNumeric::try_from(s[10]) 376 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 377 | ConsonantOrNumeric::try_from(s[11]) 378 | .map_err(|_| InvalidFigi::ConsonantOrNumeric(s.iter().collect()))?, 379 | ]; 380 | 381 | let out = Self { 382 | pos_1, 383 | pos_2, 384 | pos_3, 385 | pos_4_12, 386 | }; 387 | if out.is_valid() { 388 | Ok(out) 389 | } else { 390 | Err(InvalidFigi::Checksum(s.iter().collect())) 391 | } 392 | } 393 | 394 | #[inline] 395 | fn is_valid(&self) -> bool { 396 | let mut sum = sum_digits_sub_100(self.pos_1 as u8) 397 | + sum_digits_sub_100(self.pos_2 as u8 * 2) 398 | + sum_digits_sub_100(G.into()); 399 | 400 | for (i, c) in self.pos_4_12[..self.pos_4_12.len() - 1].iter().enumerate() { 401 | if i % 2 == 0 { 402 | sum += sum_digits_sub_100(2 * *c as u8); 403 | } else { 404 | sum += sum_digits_sub_100(*c as u8); 405 | } 406 | } 407 | self.pos_4_12[self.pos_4_12.len() - 1] as u8 == (10 - sum % 10) % 10 408 | } 409 | } 410 | 411 | #[inline] 412 | const fn sum_digits_sub_100(n: u8) -> u8 { 413 | let rem = n % 10; 414 | rem + (n - rem) / 10 415 | } 416 | 417 | #[test] 418 | fn test_figi() -> Result<(), InvalidFigi> { 419 | let aapl = "BBG000N88V36".parse::()?; // AAPL US Equity 420 | let tsm = "BBG000BD8ZK0".parse::()?; // TSM US Equity 421 | assert!(aapl.is_valid()); 422 | assert!(tsm.is_valid()); 423 | Ok(()) 424 | } 425 | -------------------------------------------------------------------------------- /src/wrapper.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use chrono::{DateTime, NaiveTime, Utc}; 4 | use ibapi_macros::debug_trait; 5 | 6 | use crate::account::{Attribute, TagValue}; 7 | use crate::client::ActiveClient; 8 | use crate::contract::{Contract, ExchangeProxy}; 9 | use crate::execution::{CommissionReport, Execution}; 10 | use crate::payload::{ 11 | self, Bar, ExchangeId, HistogramEntry, OrderStatus, Pnl, PnlSingle, Position, PositionSummary, 12 | TickData, 13 | }; 14 | use crate::tick::{ 15 | self, Accessibility, AuctionData, Class, Dividends, ExtremeValue, Ipo, MarkPrice, News, 16 | OpenInterest, Price, PriceFactor, QuotingExchanges, Rate, RealTimeVolume, 17 | SecOptionCalculationSource, SecOptionVolume, Size, SummaryVolume, TimeStamp, TradeCount, 18 | Volatility, Volume, Yield, 19 | }; 20 | 21 | // todo! Updated Bar payload api to make it more clear that BidAsk callback isn't just a "normal Bar" 22 | 23 | /// Re-export of [`tokio_util::sync::CancellationToken`] 24 | pub type CancelToken = tokio_util::sync::CancellationToken; 25 | 26 | #[allow(clippy::module_name_repetitions)] 27 | #[trait_variant::make(Wrapper: Send)] 28 | #[debug_trait] 29 | /// Contains the "callback functions" that correspond to the requests made by a [`crate::client::Client`]. 30 | pub trait LocalWrapper { 31 | /// The callback that corresponds to any error that encounters after an API request. 32 | /// 33 | /// Errors sent by the TWS are received here. 34 | fn error( 35 | &mut self, 36 | req_id: i64, 37 | error_code: i64, 38 | error_string: String, 39 | advanced_order_reject_json: String, 40 | ) -> impl Future { 41 | } 42 | /// The callback message that corresponds to [`crate::client::Client::req_current_time`]. 43 | /// 44 | /// This is TWS's current time. TWS is synchronized with the server (not local computer) using NTP and this function will receive the current time in TWS. 45 | fn current_time(&mut self, req_id: i64, datetime: DateTime) -> impl Future {} 46 | /// The callback message that corresponds to ETF Net Asset Value (NAV) data. 47 | fn etf_nav(&mut self, req_id: i64, nav: tick::EtfNav) -> impl Future {} 48 | /// The callback message that corresponds to price data from [`crate::client::Client::req_market_data`]. 49 | fn price_data(&mut self, req_id: i64, price: Class) -> impl Future {} 50 | /// The callback message that corresponds to size data from [`crate::client::Client::req_market_data`]. 51 | fn size_data(&mut self, req_id: i64, size: Class) -> impl Future {} 52 | /// The callback message that corresponds to the price (in yield terms) data from [`crate::client::Client::req_market_data`]. 53 | fn yield_data(&mut self, req_id: i64, yld: Yield) -> impl Future {} 54 | /// The callback message that corresponds to the high/low prices over a period from [`crate::client::Client::req_market_data`]. 55 | fn extreme_data(&mut self, req_id: i64, value: ExtremeValue) -> impl Future {} 56 | /// The callback message that corresponds to the results of options computations (implied volatility, greeks, etc.) from [`crate::client::Client::req_market_data`]. 57 | fn sec_option_computation( 58 | &mut self, 59 | req_id: i64, 60 | calc: Class, 61 | ) -> impl Future { 62 | } 63 | /// The callback message that corresponds to the list of exchanges actively quoting the best bid / best offer / last traded prices from [`crate::client::Client::req_market_data`]. 64 | fn quoting_exchanges( 65 | &mut self, 66 | req_id: i64, 67 | quoting_exchanges: QuotingExchanges, 68 | ) -> impl Future { 69 | } 70 | /// The callback message that corresponds to the open interest of various derivatives contracts from [`crate::client::Client::req_market_data`]. 71 | fn open_interest(&mut self, req_id: i64, open_interest: OpenInterest) -> impl Future {} 72 | /// The callback message that corresponds to volatility data from [`crate::client::Client::req_market_data`]. 73 | fn volatility(&mut self, req_id: i64, vol: Volatility) -> impl Future {} 74 | /// The callback message that corresponds to timestamp data from [`crate::client::Client::req_market_data`]. 75 | fn timestamp(&mut self, req_id: i64, timestamp: Class) -> impl Future {} 76 | /// The callback message that corresponds to auction data from [`crate::client::Client::req_market_data`]. 77 | fn auction(&mut self, req_id: i64, auction: AuctionData) -> impl Future {} 78 | /// The callback message associated with mark price data from [`crate::client::Client::req_market_data`]. 79 | fn mark_price(&mut self, req_id: i64, mark: MarkPrice) -> impl Future {} 80 | /// The callback message associated with factors / multipliers related to prices from [`crate::client::Client::req_market_data`]. 81 | fn price_factor(&mut self, req_id: i64, factor: PriceFactor) -> impl Future {} 82 | /// The callback message associated with the ability to short or trade a security from [`crate::client::Client::req_market_data`]. 83 | fn accessibility(&mut self, req_id: i64, access: Accessibility) -> impl Future {} 84 | /// The callback message containing information about dividends from [`crate::client::Client::req_market_data`]. 85 | fn dividends(&mut self, req_id: i64, dividends: Dividends) -> impl Future {} 86 | /// The callback message containing news information from [`crate::client::Client::req_market_data`]. 87 | fn news(&mut self, req_id: i64, news: News) -> impl Future {} 88 | /// The callback message containing information about IPOs from [`crate::client::Client::req_market_data`]. 89 | fn ipo(&mut self, req_id: i64, ipo: Ipo) -> impl Future {} 90 | /// The callback message containing summary information about trading volume throughout a day or 90-day rolling period from [`crate::client::Client::req_market_data`]. 91 | fn summary_volume(&mut self, req_id: i64, volume: SummaryVolume) -> impl Future {} 92 | /// The callback message containing information about daily option volume (and average option volume) from [`crate::client::Client::req_market_data`]. 93 | fn sec_option_volume(&mut self, req_id: i64, volume: SecOptionVolume) -> impl Future {} 94 | /// The callback message containing information about the number of trades performed in a day from [`crate::client::Client::req_market_data`]. 95 | fn trade_count(&mut self, req_id: i64, trade_count: TradeCount) -> impl Future {} 96 | /// The callback message containing information about the rate of trades or volume throughout a day from [`crate::client::Client::req_market_data`]. 97 | fn rate(&mut self, req_id: i64, rate: Rate) -> impl Future {} 98 | /// The callback message containing information about trading volume for the day (live/delayed) from [`crate::client::Client::req_market_data`]. 99 | fn volume(&mut self, req_id: i64, volume: Volume) -> impl Future {} 100 | /// The callback message containing information about real-time volume from [`crate::client::Client::req_market_data`]. 101 | fn real_time_volume(&mut self, req_id: i64, volume: RealTimeVolume) -> impl Future {} 102 | /// The callback message containing information about the parameters of a market data request from [`crate::client::Client::req_market_data`]. 103 | fn tick_params( 104 | &mut self, 105 | req_id: i64, 106 | min_tick: f64, 107 | exchange_id: ExchangeId, 108 | snapshot_permissions: u32, 109 | ) -> impl Future { 110 | } 111 | /// The callback message containing information about the class of data that will be returned from [`crate::client::Client::req_market_data`]. 112 | fn market_data_class(&mut self, req_id: i64, class: payload::MarketDataClass) -> impl Future {} 113 | /// The callback message containing information about updating an existing order book from [`crate::client::Client::req_market_depth`]. 114 | fn update_market_depth( 115 | &mut self, 116 | req_id: i64, 117 | operation: payload::market_depth::Operation, 118 | ) -> impl Future { 119 | } 120 | /// The callback message containing a complete histogram from [`crate::client::Client::req_histogram_data`]. 121 | fn histogram( 122 | &mut self, 123 | req_id: i64, 124 | histogram: std::collections::HashMap, 125 | ) -> impl Future { 126 | } 127 | /// The callback message containing historical bar data from [`crate::client::Client::req_historical_bar`]. 128 | fn historical_bars( 129 | &mut self, 130 | req_id: i64, 131 | start_datetime: DateTime, 132 | end_datetime: DateTime, 133 | bars: Vec, 134 | ) -> impl Future { 135 | } 136 | /// The callback message containing an updated historical bar from [`crate::client::Client::req_updating_historical_bar`]. 137 | fn updating_historical_bar(&mut self, req_id: i64, bar: Bar) -> impl Future {} 138 | /// The callback message containing a timestamp for the beginning of data for a contract and specified data type from [`crate::client::Client::req_head_timestamp`]. 139 | fn head_timestamp(&mut self, req_id: i64, timestamp: DateTime) -> impl Future {} 140 | /// The callback message containing a vector of historical ticks from [`crate::client::Client::req_historical_ticks`] for [`crate::client::Client::req_tick_by_tick_data`]. 141 | fn historical_ticks(&mut self, req_id: i64, ticks: Vec) -> impl Future {} 142 | /// The callback message containing a single tick from [`crate::client::Client::req_tick_by_tick_data`]. 143 | fn live_tick(&mut self, req_id: i64, tick: TickData) -> impl Future {} 144 | /// The callback message containing account attributes from [`crate::client::Client::req_account_updates`]. 145 | fn account_attribute(&mut self, attribute: Attribute, account_number: String) -> impl Future {} 146 | /// The callback message containing information about a single [`Position`] from [`crate::client::Client::req_account_updates`]. 147 | fn portfolio_value(&mut self, position: Position) -> impl Future {} 148 | /// The callback message containing information about the time at which account attribute data is valid. 149 | fn account_attribute_time(&mut self, time: NaiveTime) -> impl Future {} 150 | /// The callback message containing summary information about positions from [`crate::client::Client::req_positions`]. 151 | fn position_summary(&mut self, summary: PositionSummary) -> impl Future {} 152 | /// The callback message containing aggregate P&L information from [`crate::client::Client::req_pnl`]. 153 | fn pnl(&mut self, req_id: i64, pnl: Pnl) -> impl Future {} 154 | /// The callback message that contains information about PNL of a single position from ['crate::client::Client::req_pnl_single']. 155 | fn single_position_pnl(&mut self, req_id: i64, pnl: PnlSingle) -> impl Future {} 156 | /// The callback message indicating that all the information for a given account has been received. 157 | fn account_download_end(&mut self, account_number: String) -> impl Future {} 158 | /// The callback message associated with account summary information from [`crate::client::Client::req_account_summary`]. 159 | fn account_summary( 160 | &mut self, 161 | req_id: i64, 162 | account_number: String, 163 | summary: TagValue, 164 | ) -> impl Future { 165 | } 166 | /// The callback message indicating that all the position information has been received. 167 | fn position_end(&mut self) -> impl Future {} 168 | /// The callback message indicating that all the account summary information has been received. 169 | fn account_summary_end(&mut self, req_id: i64) -> impl Future {} 170 | /// The callback message indicating that all the contract information has been received. 171 | fn contract_data_end(&mut self, req_id: i64) -> impl Future {} 172 | /// The callback message indicating that all order information has been received. 173 | fn open_order_end(&mut self) -> impl Future {} 174 | /// The callback message that contains live bar data from [`crate::client::Client::req_real_time_bars`]. 175 | fn real_time_bar(&mut self, req_id: i64, bar: Bar) -> impl Future {} 176 | /// The callback message that contains order status data from [`crate::client::Client::req_place_order`]. 177 | fn order_status(&mut self, status: OrderStatus) -> impl Future {} 178 | /// The callback message that contains information about currently open orders from [`crate::client::Client::req_place_order`]. 179 | fn open_order( 180 | &mut self, 181 | order_id: i64, 182 | proxy: ExchangeProxy, 183 | client_id: i64, 184 | parent_id: Option, 185 | permanent_id: i64, 186 | ) -> impl Future { 187 | } 188 | /// The callback message that contains information about an execution. 189 | fn execution(&mut self, req_id: i64, execution: Execution) -> impl Future {} 190 | /// The callback message indicating the end of an execution details request 191 | fn execution_details_end(&mut self, req_id: i64) -> impl Future {} 192 | /// The callback message indicating the end of a market data snapshot message 193 | fn tick_snapshot_end(&mut self, req_id: i64) -> impl Future {} 194 | /// The callback message associated with the commission report of an execution. Triggered immediately after a trade execution or by calling [`crate::client::Client::req_executions`] 195 | fn commission_report(&mut self, commission_report: CommissionReport) -> impl Future {} 196 | } 197 | 198 | #[trait_variant::make(Recurring: Send)] 199 | /// A trait with a single method that will be called in the main message loop. 200 | pub trait LocalRecurring { 201 | /// A method that is called in the body of the main message loop. The method is called in 202 | /// a [`tokio::select!`] block. 203 | /// 204 | /// This method needs to have a .await point, or the entire program will block. 205 | /// See [`tokio::task::yield_now`]. 206 | fn cycle(&mut self) -> impl Future; 207 | } 208 | 209 | /// An initializer for a new [`LocalWrapper`]. 210 | pub trait LocalInitializer { 211 | /// The wrapper 212 | type Wrap<'c>: LocalWrapper + LocalRecurring; 213 | /// The method to build the wrapper 214 | fn build( 215 | self, 216 | client: &mut ActiveClient, 217 | cancel_loop: CancelToken, 218 | ) -> impl Future>; 219 | } 220 | 221 | /// An initializer for a new [`Wrapper`]. 222 | pub trait Initializer: Send { 223 | /// The wrapper 224 | type Wrap<'c>: Wrapper + Recurring; 225 | /// The method to build the wrapper 226 | fn build( 227 | self, 228 | client: &mut ActiveClient, 229 | cancel_loop: CancelToken, 230 | ) -> impl Future> + Send; 231 | } 232 | 233 | impl LocalInitializer for I { 234 | type Wrap<'c> = ::Wrap<'c>; 235 | 236 | fn build( 237 | self, 238 | client: &mut ActiveClient, 239 | cancel_loop: CancelToken, 240 | ) -> impl Future> { 241 | ::build(self, client, cancel_loop) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/comm.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::io::{Error, Write}; 3 | 4 | use serde::Serialize; 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct Writer { 8 | buf: Vec, 9 | offset: Option, 10 | inner: tokio::net::tcp::OwnedWriteHalf, 11 | } 12 | 13 | impl Writer { 14 | #[inline] 15 | /// Create a new `Message` with the default capacity specified as [`constants::OUT_MESSAGE_SIZE`] 16 | pub(crate) fn new(writer: tokio::net::tcp::OwnedWriteHalf) -> Self { 17 | Self::with_capacity(writer, crate::constants::OUT_MESSAGE_SIZE) 18 | } 19 | 20 | #[inline] 21 | /// Create a new `Message` with the specified capacity. 22 | pub(crate) fn with_capacity(writer: tokio::net::tcp::OwnedWriteHalf, cap: usize) -> Self { 23 | let buf = Vec::with_capacity(cap); 24 | 25 | Self { 26 | buf, 27 | offset: None, 28 | inner: writer, 29 | } 30 | } 31 | 32 | #[inline] 33 | pub(crate) fn add_prefix(&mut self, prefix: &str) -> Result<(), Error> { 34 | self.buf.write_all(prefix.as_bytes())?; 35 | self.offset = Some(prefix.len()); 36 | 37 | Ok(()) 38 | } 39 | 40 | #[allow(clippy::expect_used)] 41 | #[inline] 42 | /// # Panics 43 | /// This function will panic if the length of the message overflows by exceeding a length of 2^32 - 1 bytes. 44 | pub(crate) fn add_body(&mut self, body: T) -> Result<(), Error> { 45 | const LENGTH_PREFIX: &[u8] = b"\0\0\0\0"; 46 | self.buf.write_all(LENGTH_PREFIX)?; 47 | 48 | body.serialize(&mut *self)?; 49 | let (len, offset) = match self.offset { 50 | Some(o) => (self.buf.len() - o - LENGTH_PREFIX.len(), o), 51 | None => (self.buf.len() - LENGTH_PREFIX.len(), 0), 52 | }; 53 | 54 | self.buf.splice( 55 | offset..LENGTH_PREFIX.len() + offset, 56 | u32::try_from(len) 57 | .expect("Overflow: Message length exceeds the max of 2³² - 1 bytes.") 58 | .to_be_bytes(), 59 | ); 60 | 61 | Ok(()) 62 | } 63 | 64 | #[inline] 65 | pub(crate) async fn send(&mut self) -> Result<(), Error> { 66 | tokio::io::AsyncWriteExt::write_all(&mut self.inner, &self.buf).await?; 67 | self.buf.clear(); 68 | self.offset = None; 69 | 70 | Ok(()) 71 | } 72 | 73 | #[inline] 74 | pub(crate) async fn flush(&mut self) -> Result<(), Error> { 75 | tokio::io::AsyncWriteExt::flush(&mut self.inner).await 76 | } 77 | 78 | #[inline] 79 | pub(crate) async fn shutdown(&mut self) -> Result<(), Error> { 80 | tokio::io::AsyncWriteExt::shutdown(&mut self.inner).await 81 | } 82 | } 83 | 84 | #[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 85 | pub(crate) struct SerializeMessageError(String); 86 | 87 | impl Display for SerializeMessageError { 88 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 89 | write!(f, "Failed to serialize message {}", self.0) 90 | } 91 | } 92 | 93 | impl std::error::Error for SerializeMessageError {} 94 | 95 | impl serde::ser::Error for SerializeMessageError { 96 | fn custom(msg: T) -> Self 97 | where 98 | T: Display, 99 | { 100 | SerializeMessageError(msg.to_string()) 101 | } 102 | } 103 | 104 | impl From for SerializeMessageError { 105 | fn from(value: Error) -> Self { 106 | Self(value.to_string()) 107 | } 108 | } 109 | 110 | impl From for Error { 111 | fn from(value: SerializeMessageError) -> Self { 112 | Error::new(std::io::ErrorKind::InvalidData, value.0) 113 | } 114 | } 115 | 116 | // Don't worry about the "allow." Our serializer doesn't need all the fields it's given 117 | #[allow(unused_variables)] 118 | pub(crate) mod ser { 119 | use std::io::Write; 120 | 121 | use serde::{ 122 | ser::{ 123 | SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, 124 | SerializeTupleStruct, SerializeTupleVariant, 125 | }, 126 | Serialize, Serializer, 127 | }; 128 | 129 | use super::{SerializeMessageError, Writer}; 130 | 131 | #[inline] 132 | fn serialize_int(buf: &mut Vec, int: I) -> Result<(), std::io::Error> { 133 | let mut temp = itoa::Buffer::new(); 134 | buf.write_all(temp.format(int).as_bytes())?; 135 | buf.write_all(b"\0")?; 136 | 137 | Ok(()) 138 | } 139 | 140 | #[inline] 141 | fn serialize_float(buf: &mut Vec, float: F) -> Result<(), std::io::Error> { 142 | let mut temp = ryu::Buffer::new(); 143 | buf.write_all(temp.format(float).as_bytes())?; 144 | buf.write_all(b"\0")?; 145 | 146 | Ok(()) 147 | } 148 | 149 | impl Serializer for &mut Writer { 150 | type Ok = (); 151 | type Error = SerializeMessageError; 152 | type SerializeSeq = Self; 153 | type SerializeTuple = Self; 154 | type SerializeTupleStruct = Self; 155 | type SerializeTupleVariant = Self; 156 | type SerializeMap = Self; 157 | type SerializeStruct = Self; 158 | type SerializeStructVariant = Self; 159 | 160 | #[inline] 161 | fn serialize_bool(self, v: bool) -> Result { 162 | self.buf.write_all(if v { b"1\0" } else { b"0\0" })?; 163 | 164 | Ok(()) 165 | } 166 | 167 | #[inline] 168 | fn serialize_i8(self, v: i8) -> Result { 169 | serialize_int(&mut self.buf, v)?; 170 | Ok(()) 171 | } 172 | 173 | #[inline] 174 | fn serialize_i16(self, v: i16) -> Result { 175 | serialize_int(&mut self.buf, v)?; 176 | Ok(()) 177 | } 178 | 179 | #[inline] 180 | fn serialize_i32(self, v: i32) -> Result { 181 | serialize_int(&mut self.buf, v)?; 182 | Ok(()) 183 | } 184 | 185 | #[inline] 186 | fn serialize_i64(self, v: i64) -> Result { 187 | serialize_int(&mut self.buf, v)?; 188 | Ok(()) 189 | } 190 | 191 | #[inline] 192 | fn serialize_u8(self, v: u8) -> Result { 193 | serialize_int(&mut self.buf, v)?; 194 | Ok(()) 195 | } 196 | 197 | #[inline] 198 | fn serialize_u16(self, v: u16) -> Result { 199 | serialize_int(&mut self.buf, v)?; 200 | Ok(()) 201 | } 202 | 203 | #[inline] 204 | fn serialize_u32(self, v: u32) -> Result { 205 | serialize_int(&mut self.buf, v)?; 206 | Ok(()) 207 | } 208 | 209 | #[inline] 210 | fn serialize_u64(self, v: u64) -> Result { 211 | serialize_int(&mut self.buf, v)?; 212 | Ok(()) 213 | } 214 | 215 | #[inline] 216 | fn serialize_f32(self, v: f32) -> Result { 217 | serialize_float(&mut self.buf, v)?; 218 | Ok(()) 219 | } 220 | 221 | #[inline] 222 | fn serialize_f64(self, v: f64) -> Result { 223 | serialize_float(&mut self.buf, v)?; 224 | Ok(()) 225 | } 226 | 227 | #[inline] 228 | fn serialize_char(self, v: char) -> Result { 229 | let mut temp = [0; 5]; 230 | v.encode_utf8(&mut temp); 231 | self.buf.write_all(&temp[..=v.len_utf8()])?; 232 | 233 | Ok(()) 234 | } 235 | 236 | #[inline] 237 | fn serialize_str(self, v: &str) -> Result { 238 | self.serialize_bytes(v.as_bytes()) 239 | } 240 | 241 | #[inline] 242 | fn serialize_bytes(self, v: &[u8]) -> Result { 243 | self.buf.write_all(v)?; 244 | self.buf.write_all(b"\0")?; 245 | 246 | Ok(()) 247 | } 248 | 249 | #[inline] 250 | fn serialize_none(self) -> Result { 251 | self.buf.write_all(b"\0")?; 252 | 253 | Ok(()) 254 | } 255 | 256 | #[inline] 257 | fn serialize_some(self, value: &T) -> Result 258 | where 259 | T: ?Sized + Serialize, 260 | { 261 | value.serialize(self) 262 | } 263 | 264 | #[inline] 265 | fn serialize_unit(self) -> Result { 266 | Ok(()) 267 | } 268 | 269 | #[inline] 270 | fn serialize_unit_struct(self, name: &'static str) -> Result { 271 | name.serialize(self) 272 | } 273 | 274 | #[inline] 275 | fn serialize_unit_variant( 276 | self, 277 | name: &'static str, 278 | variant_index: u32, 279 | variant: &'static str, 280 | ) -> Result { 281 | variant.serialize(self) 282 | } 283 | 284 | #[inline] 285 | fn serialize_newtype_struct( 286 | self, 287 | name: &'static str, 288 | value: &T, 289 | ) -> Result 290 | where 291 | T: ?Sized + Serialize, 292 | { 293 | value.serialize(self) 294 | } 295 | 296 | #[inline] 297 | fn serialize_newtype_variant( 298 | self, 299 | name: &'static str, 300 | variant_index: u32, 301 | variant: &'static str, 302 | value: &T, 303 | ) -> Result 304 | where 305 | T: ?Sized + Serialize, 306 | { 307 | value.serialize(self) 308 | } 309 | 310 | #[inline] 311 | fn serialize_seq(self, len: Option) -> Result { 312 | Ok(self) 313 | } 314 | 315 | #[inline] 316 | fn serialize_tuple(self, len: usize) -> Result { 317 | Ok(self) 318 | } 319 | 320 | #[inline] 321 | fn serialize_tuple_struct( 322 | self, 323 | name: &'static str, 324 | len: usize, 325 | ) -> Result { 326 | Ok(self) 327 | } 328 | 329 | #[inline] 330 | fn serialize_tuple_variant( 331 | self, 332 | name: &'static str, 333 | variant_index: u32, 334 | variant: &'static str, 335 | len: usize, 336 | ) -> Result { 337 | Ok(self) 338 | } 339 | 340 | #[inline] 341 | fn serialize_map(self, len: Option) -> Result { 342 | Ok(self) 343 | } 344 | 345 | #[inline] 346 | fn serialize_struct( 347 | self, 348 | name: &'static str, 349 | len: usize, 350 | ) -> Result { 351 | Ok(self) 352 | } 353 | 354 | #[inline] 355 | fn serialize_struct_variant( 356 | self, 357 | name: &'static str, 358 | variant_index: u32, 359 | variant: &'static str, 360 | len: usize, 361 | ) -> Result { 362 | Ok(self) 363 | } 364 | } 365 | 366 | impl SerializeSeq for &mut Writer { 367 | type Ok = ::Ok; 368 | type Error = ::Error; 369 | 370 | #[inline] 371 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> 372 | where 373 | T: ?Sized + Serialize, 374 | { 375 | value.serialize(&mut **self)?; 376 | self.buf.splice(self.buf.len() - 1..self.buf.len(), *b","); 377 | Ok(()) 378 | } 379 | 380 | #[inline] 381 | fn end(self) -> Result { 382 | self.buf.splice(self.buf.len() - 1..self.buf.len(), *b"\0"); 383 | Ok(()) 384 | } 385 | } 386 | 387 | impl SerializeTuple for &mut Writer { 388 | type Ok = ::Ok; 389 | type Error = ::Error; 390 | 391 | #[inline] 392 | fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> 393 | where 394 | T: ?Sized + Serialize, 395 | { 396 | value.serialize(&mut **self) 397 | } 398 | 399 | #[inline] 400 | fn end(self) -> Result { 401 | Ok(()) 402 | } 403 | } 404 | 405 | impl SerializeTupleStruct for &mut Writer { 406 | type Ok = ::Ok; 407 | type Error = ::Error; 408 | 409 | #[inline] 410 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> 411 | where 412 | T: ?Sized + Serialize, 413 | { 414 | value.serialize(&mut **self) 415 | } 416 | 417 | #[inline] 418 | fn end(self) -> Result { 419 | Ok(()) 420 | } 421 | } 422 | 423 | impl SerializeTupleVariant for &mut Writer { 424 | type Ok = ::Ok; 425 | type Error = ::Error; 426 | 427 | #[inline] 428 | fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> 429 | where 430 | T: ?Sized + Serialize, 431 | { 432 | value.serialize(&mut **self) 433 | } 434 | 435 | #[inline] 436 | fn end(self) -> Result { 437 | Ok(()) 438 | } 439 | } 440 | 441 | impl SerializeMap for &mut Writer { 442 | type Ok = ::Ok; 443 | type Error = ::Error; 444 | 445 | #[inline] 446 | fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> 447 | where 448 | T: ?Sized + Serialize, 449 | { 450 | key.serialize(&mut **self) 451 | } 452 | 453 | #[inline] 454 | fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> 455 | where 456 | T: ?Sized + Serialize, 457 | { 458 | value.serialize(&mut **self) 459 | } 460 | 461 | #[inline] 462 | fn end(self) -> Result { 463 | Ok(()) 464 | } 465 | } 466 | 467 | impl SerializeStruct for &mut Writer { 468 | type Ok = ::Ok; 469 | type Error = ::Error; 470 | 471 | #[inline] 472 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> 473 | where 474 | T: ?Sized + Serialize, 475 | { 476 | value.serialize(&mut **self) 477 | } 478 | 479 | #[inline] 480 | fn end(self) -> Result { 481 | Ok(()) 482 | } 483 | } 484 | 485 | impl SerializeStructVariant for &mut Writer { 486 | type Ok = ::Ok; 487 | type Error = ::Error; 488 | 489 | #[inline] 490 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> 491 | where 492 | T: ?Sized + Serialize, 493 | { 494 | key.serialize(&mut **self)?; 495 | value.serialize(&mut **self) 496 | } 497 | 498 | #[inline] 499 | fn end(self) -> Result { 500 | Ok(()) 501 | } 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /src/tick.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Formatter; 2 | use std::num::ParseFloatError; 3 | use std::str::FromStr; 4 | 5 | use chrono::{DateTime, NaiveDate, Utc}; 6 | use chrono::serde::{ts_milliseconds, ts_seconds}; 7 | use serde::{Deserialize, Deserializer, ser::SerializeTuple, Serialize, Serializer}; 8 | 9 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 10 | #[serde(tag = "etf_nav")] 11 | /// The types of ticks related to ETF Net Asset Value (NAV). 12 | pub enum EtfNav { 13 | /// Today's closing price of ETF's Net Asset Value (NAV). Calculation is based on prices of ETF's underlying securities. 14 | Close(f64), 15 | /// Yesterday's closing price of ETF's Net Asset Value (NAV). Calculation is based on prices of ETF's underlying securities. 16 | PriorClose(f64), 17 | /// The bid price of ETF's Net Asset Value (NAV). Calculation is based on prices of ETF's underlying securities. 18 | Bid(f64), 19 | /// The ask price of ETF's Net Asset Value (NAV). Calculation is based on prices of ETF's underlying securities. 20 | Ask(f64), 21 | /// The last price of Net Asset Value (NAV). For ETFs: Calculation is based on prices of ETF's underlying securities. For `NextShares`: Value is provided by NASDAQ. 22 | Last(f64), 23 | /// ETF Nav Last for Frozen data. 24 | FrozenLast(f64), 25 | /// The high price of ETF's Net Asset Value (NAV) 26 | High(f64), 27 | /// The low price of ETF's Net Asset Value (NAV) 28 | Low(f64), 29 | } 30 | 31 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 32 | #[serde(tag = "price")] 33 | /// The types of ticks related to price data. 34 | pub enum Price { 35 | /// Highest priced bid for the contract. 36 | Bid(f64), 37 | /// Lowest price offer on the contract. 38 | Ask(f64), 39 | /// Last price at which the contract traded (does not include some trades in `RTVolume`). 40 | Last(f64), 41 | /// High price for the day. 42 | High(f64), 43 | /// Low price for the day. 44 | Low(f64), 45 | /// The last available closing price for the previous day. For US Equities, we use corporate action processing to get the closing price, so the close price is adjusted to reflect forward and reverse splits and cash and stock dividends. 46 | Close(f64), 47 | /// Current session's opening price. Before open will refer to previous day. The official opening price requires a market data subscription to the native exchange of the instrument. 48 | Open(f64), 49 | /// Last Regular Trading Hours traded price. 50 | LastRthTrade(f64), 51 | } 52 | 53 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 54 | #[serde(tag = "size")] 55 | /// The types of ticks related to size data. 56 | pub enum Size { 57 | /// Number of contracts or lots offered at the bid price. 58 | Bid(f64), 59 | /// Number of contracts or lots offered at the ask price. 60 | Ask(f64), 61 | /// Number of contracts or lots traded at the last price. 62 | Last(f64), 63 | } 64 | 65 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 66 | #[serde(tag = "yield")] 67 | /// The types of ticks related to yield data. 68 | pub enum Yield { 69 | /// Implied yield of the bond if it is purchased at the current bid. 70 | Bid(f64), 71 | /// Implied yield of the bond if it is purchased at the current ask. 72 | Ask(f64), 73 | /// Implied yield of the bond if it is purchased at the last price. 74 | Last(f64), 75 | } 76 | 77 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 78 | #[serde(tag = "period")] 79 | /// Represents the various periods of trailing extreme value. 80 | pub enum Period { 81 | /// A value over a 13-week period. 82 | ThirteenWeek(f64), 83 | /// A value over a 26-week period. 84 | TwentySixWeek(f64), 85 | /// A value over a 52-week period. 86 | FiftyTwoWeek(f64), 87 | } 88 | 89 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 90 | #[serde(tag = "extreme")] 91 | /// Represents the two types of extreme values. 92 | pub enum ExtremeValue { 93 | /// The lowest value. 94 | Low(Period), 95 | /// The highest value. 96 | High(Period), 97 | } 98 | 99 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 100 | #[serde(tag = "source")] 101 | /// The various base prices that can be used to perform option computations. 102 | pub enum SecOptionCalculationSource { 103 | /// Use the bid price to perform the computations. 104 | Bid(SecOptionCalculations), 105 | /// Use the ask price to perform the computations. 106 | Ask(SecOptionCalculations), 107 | /// Use the last price to perform the computations. 108 | Last(SecOptionCalculations), 109 | /// Use the IBKR options model price to perform the computations. 110 | Model(SecOptionCalculations), 111 | /// Use a custom price to perform the computations. 112 | Custom(SecOptionCalculations), 113 | } 114 | 115 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 116 | #[serde(tag = "base")] 117 | /// A collection of option calculations. 118 | pub enum SecOptionCalculations { 119 | /// Return-based computations 120 | ReturnBased(SecOptionCalculationResults), 121 | /// Price-based computations 122 | PriceBased(SecOptionCalculationResults), 123 | } 124 | 125 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 126 | /// The core results of an option calculation. 127 | pub struct SecOptionCalculationResults { 128 | /// The implied volatility calculated by the TWS option modeler, using the specified tick type value. 129 | pub implied_volatility: CalculationResult, 130 | /// The option delta value. 131 | pub delta: CalculationResult, 132 | /// The option price. 133 | pub price: CalculationResult, 134 | /// The present value of dividends expected on the option's underlying. 135 | pub dividend_present_value: CalculationResult, 136 | /// The option gamma value. 137 | pub gamma: CalculationResult, 138 | /// The option vega value. 139 | pub vega: CalculationResult, 140 | /// The option theta value. 141 | pub theta: CalculationResult, 142 | /// The price of the underlying. 143 | pub underlying_price: CalculationResult, 144 | } 145 | 146 | #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] 147 | #[serde(tag = "type")] 148 | /// The exchanges posting the best bid / best offer / last traded prices. 149 | pub enum QuotingExchanges { 150 | /// For stock and options, identifies the exchange(s) posting the bid price. See Component Exchanges. 151 | Bid(Vec), 152 | /// For stock and options, identifies the exchange(s) posting the ask price. See Component Exchanges. 153 | Ask(Vec), 154 | /// Exchange of last traded price. 155 | Last(Vec), 156 | } 157 | 158 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 159 | #[serde(tag = "open_interest")] 160 | /// Represents the possible open interest callbacks. 161 | pub enum OpenInterest { 162 | /// Call option open interest. 163 | SecOptionCall(f64), 164 | /// Put option open interest. 165 | SecOptionPut(f64), 166 | /// Total number of outstanding futures contracts (TWS v965+). *HSI open interest requested with generic tick 101 167 | SecFuture(f64), 168 | } 169 | 170 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 171 | #[serde(tag = "volatility")] 172 | /// The types of volatility callbacks. 173 | pub enum Volatility { 174 | /// The 30-day historical volatility (currently for stocks). 175 | SecOptionHistorical(f64), 176 | /// A prediction of how volatile an underlying will be in the future. The IB 30-day volatility is the at-market volatility estimated for a maturity thirty calendar days forward of the current trading day, and is based on option prices from two consecutive expiration months. 177 | SecOptionImplied(f64), 178 | /// 30-day real time historical volatility. 179 | RealTimeHistorical(f64), 180 | } 181 | 182 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 183 | #[serde(tag = "timestamp")] 184 | /// Represents a timestamp callback. 185 | pub enum TimeStamp { 186 | /// Time of the last trade (in UNIX time). 187 | #[serde(with = "ts_seconds")] 188 | Last(DateTime), 189 | /// Timestamp (in Unix ms time) of last trade returned with regulatory snapshot. 190 | #[serde(with = "ts_milliseconds")] 191 | Regulatory(DateTime), 192 | } 193 | 194 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 195 | #[serde(tag = "auction_data")] 196 | /// Represents a callback that relates to auction data, auction prices, etc. 197 | pub enum AuctionData { 198 | /// The number of shares that would trade if no new orders were received and the auction were held now. 199 | Volume(f64), 200 | /// The price at which the auction would occur if no new orders were received and the auction were held now — the indicative price for the auction. Typically received after Auction imbalance (tick type 36) 201 | Price(f64), 202 | /// The number of unmatched shares for the next auction; returns how many more shares are on one side of the auction than the other. Typically received after Auction Volume (tick type 34) 203 | Imbalance(f64), 204 | /// The imbalance that is used to determine which at-the-open or at-the-close orders can be entered following the publishing of the regulatory imbalance. 205 | Regulatory(f64), 206 | } 207 | 208 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 209 | #[serde(tag = "mark_price")] 210 | /// Represents a callback containing to mark prices. 211 | pub enum MarkPrice { 212 | /// The mark price is the current theoretically-calculated value of an instrument. Since it is a calculated value, it will typically have many digits of precision. 213 | Standard(f64), 214 | /// Slower mark price update used in system calculations 215 | Slow(f64), 216 | } 217 | 218 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 219 | #[serde(tag = "real_time_volume")] 220 | /// A callback containing real-time volume information that is updated quickly. 221 | pub enum RealTimeVolume { 222 | /// Last trade details (Including both "Last" and "Unreportable Last" trades). 223 | All(RealTimeVolumeBase), 224 | /// Last trade details that excludes "Unreportable Trades". 225 | Trades(RealTimeVolumeBase), 226 | } 227 | 228 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 229 | /// A helper struct that represents all the information returned in [`RealTimeVolume`]. 230 | pub struct RealTimeVolumeBase { 231 | /// The last trade's price. 232 | pub(crate) last_price: f64, 233 | /// The last trade's size. 234 | pub(crate) last_size: f64, 235 | /// The last trade's time. 236 | #[serde(with = "ts_seconds")] 237 | pub(crate) last_time: DateTime, 238 | /// The current day's total traded volume. 239 | pub(crate) day_volume: f64, 240 | /// The current day's Volume Weighted Average Price (VWAP). 241 | pub(crate) vwap: f64, 242 | /// When true, the trade was filled by a single market maker. 243 | pub(crate) single_mm: bool, 244 | } 245 | 246 | /// A callback containing volume information that is not updated as quickly as [`RealTimeVolume`] 247 | pub type Volume = Class; 248 | 249 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 250 | #[serde(tag = "rate")] 251 | /// A callback containing information about trades and volume on a per-minute basis. 252 | pub enum Rate { 253 | /// Trade count per minute. 254 | Trade(f64), 255 | /// Volume per minute. 256 | Volume(f64), 257 | } 258 | 259 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 260 | #[serde(tag = "sec_option_volume")] 261 | /// A callback containing information about option volume. 262 | pub enum SecOptionVolume { 263 | /// Call option volume for the trading day. 264 | Call(f64), 265 | /// Put option volume for the trading day. 266 | Put(f64), 267 | /// Average volume of the corresponding option contracts. 268 | Average(f64), 269 | } 270 | 271 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 272 | #[serde(tag = "summary_volume")] 273 | /// A callback containing information about short-term volume 274 | pub enum SummaryVolume { 275 | /// The past three minutes volume. Interpolation may be applied. For stocks only. 276 | ThreeMinutes(f64), 277 | /// The past five minutes volume. Interpolation may be applied. For stocks only. 278 | FiveMinutes(f64), 279 | /// The past ten minutes volume. Interpolation may be applied. For stocks only. 280 | TenMinutes(f64), 281 | /// The average daily trading volume over 90 days. Multiplier of 100. For stocks only. 282 | NinetyDayAverage(f64), 283 | } 284 | 285 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 286 | #[serde(tag = "price_factor")] 287 | /// A callback containing information that relates the price of an instrument to some reference value. 288 | pub enum PriceFactor { 289 | /// The bond factor is a number that indicates the ratio of the current bond principal to the original principal. 290 | BondFactorMultiplier(f64), 291 | /// The number of points that the index is over the cash index. 292 | IndexFuturePremium(f64), 293 | } 294 | 295 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 296 | #[serde(tag = "accessibility")] 297 | /// A callback containing information about a security's accessibility for shorting and trading. 298 | pub enum Accessibility { 299 | /// Number of shares available to short (TWS Build 974+ is required) 300 | ShortableShares(f64), 301 | /// Describes the level of difficulty with which the contract can be sold short. 302 | Shortable(f64), 303 | /// Indicates if a contract is halted. 304 | Halted(f64), 305 | } 306 | 307 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 308 | #[serde(tag = "ipo")] 309 | /// A callback related to IPO information. 310 | pub enum Ipo { 311 | /// Midpoint is calculated based on IPO price range. 312 | Estimated(f64), 313 | /// Final price for IPO. 314 | Final(f64), 315 | } 316 | 317 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 318 | /// Information about dividends 319 | pub struct Dividends { 320 | /// The sum of dividends for the past 12 months. 321 | pub trailing_year: f64, 322 | /// The sum of dividends for the next 12 months. 323 | pub forward_year: f64, 324 | /// The next single dividend date and amount. 325 | #[serde(serialize_with = "serialize_dividend_tuple")] 326 | #[serde(deserialize_with = "deserialize_dividend_tuple")] 327 | pub next_dividend: (NaiveDate, f64), 328 | } 329 | 330 | fn serialize_dividend_tuple( 331 | div_tup: &(NaiveDate, f64), 332 | serializer: S, 333 | ) -> Result { 334 | let mut s = serializer.serialize_tuple(2)?; 335 | s.serialize_element(&div_tup.0.format("%Y-%m-%d").to_string())?; 336 | s.serialize_element(&div_tup.1)?; 337 | s.end() 338 | } 339 | 340 | fn deserialize_dividend_tuple<'de, D: Deserializer<'de>>( 341 | deserializer: D, 342 | ) -> Result<(NaiveDate, f64), D::Error> { 343 | deserializer.deserialize_tuple(2, TupVisitor) 344 | } 345 | 346 | struct TupVisitor; 347 | 348 | impl serde::de::Visitor<'_> for TupVisitor { 349 | type Value = (NaiveDate, f64); 350 | 351 | fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { 352 | write!( 353 | formatter, 354 | "either a YYYY-MM-DD date or a floating point number" 355 | ) 356 | } 357 | } 358 | 359 | /// A contract's news feed 360 | pub type News = String; 361 | 362 | /// Trade count for the day. 363 | pub type TradeCount = f64; 364 | 365 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] 366 | #[serde(tag = "data_class")] 367 | /// The two classes of data that can be returned for various market data requests. 368 | pub enum Class { 369 | /// Live data that requires a live data subscription. 370 | Live(P), 371 | /// Data that is delayed by at least 15-20 minutes. 372 | Delayed(P), 373 | } 374 | 375 | pub(crate) mod indicators { 376 | pub trait Valid {} 377 | 378 | impl Valid for super::Price {} 379 | impl Valid for super::Size {} 380 | impl Valid for super::SecOptionCalculationSource {} 381 | impl Valid for super::TimeStamp {} 382 | 383 | impl Valid for f64 {} 384 | } 385 | 386 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 387 | #[serde(untagged)] 388 | /// The result of an option calculation. 389 | pub enum CalculationResult { 390 | /// The computed value. 391 | Computed(f64), 392 | /// Indicates that the computation has not been computed yet but will be at some point. 393 | NotYetComputed, 394 | /// Indicates that the computation will not be computed. 395 | NotComputed, 396 | } 397 | 398 | impl FromStr for CalculationResult { 399 | type Err = ParseFloatError; 400 | 401 | #[inline] 402 | fn from_str(s: &str) -> Result { 403 | Ok(match s { 404 | "-1" => Self::NotComputed, 405 | "-2" => Self::NotYetComputed, 406 | s => Self::Computed(s.parse()?), 407 | }) 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/payload.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::fmt::Formatter; 3 | use std::str::FromStr; 4 | 5 | use chrono::{DateTime, Utc}; 6 | use chrono::serde::ts_seconds; 7 | use serde::{Deserialize, Serialize}; 8 | use thiserror::Error; 9 | 10 | use crate::contract::{Contract, ExchangeProxy}; 11 | 12 | #[derive(Debug, Clone, Error)] 13 | #[error("Invalid value encountered when attempting to parse a payload value.")] 14 | /// An error returned when parsing any value in the [`crate::payload`] module fails. 15 | pub enum ParsePayloadError { 16 | /// Invalid locate 17 | #[error("Invalid value encountered when attempting to parse locate. Expected \"locate\", found: {0}")] 18 | Locate(String), 19 | /// Invalid order status 20 | #[error("Invalid value encountered when attempting to parse order status. No such order status: {0}")] 21 | OrderStatus(String), 22 | /// Invalid entry side 23 | #[error("Invalid int encountered while parsing entry side")] 24 | Entry, 25 | /// Invalid MPID 26 | #[error("Invalid value encountered when attempting to parse MPID.")] 27 | Mpid, 28 | /// Invalid operation integer code 29 | #[error("Invalid int encountered while parsing operation")] 30 | Operation, 31 | } 32 | 33 | #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 34 | /// The result of a [`crate::client::Client::req_market_data`] request, which contains an identifier that can be passed to 35 | /// [`crate::client::Client::req_smart_components`] request to find which exchanges are included in the SMART aggregate exchange. 36 | pub struct ExchangeId(String); 37 | 38 | impl std::fmt::Display for ExchangeId { 39 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 40 | write!(f, "{}", self.0) 41 | } 42 | } 43 | 44 | impl FromStr for ExchangeId { 45 | type Err = Infallible; 46 | 47 | fn from_str(s: &str) -> Result { 48 | Ok(Self(s.to_owned())) 49 | } 50 | } 51 | 52 | /// Re-export of [`crate::market_data::live_data::Class`]. 53 | pub type MarketDataClass = crate::market_data::live_data::Class; 54 | 55 | /// Contains types related to market depth updates from [`crate::client::Client::req_market_depth`] 56 | pub mod market_depth { 57 | use serde::{de::Error, Deserialize, Serialize}; 58 | 59 | use crate::exchange::Primary; 60 | use crate::payload::ParsePayloadError; 61 | 62 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 63 | #[serde(tag = "operation")] 64 | /// Represents a single change to an existing order book 65 | pub enum Operation { 66 | /// Insert a given row 67 | Insert(CompleteEntry), 68 | /// Update a given row 69 | Update(CompleteEntry), 70 | /// Delete a given row 71 | Delete(CompleteEntry), 72 | } 73 | 74 | impl TryFrom<(i64, CompleteEntry)> for Operation { 75 | type Error = ParsePayloadError; 76 | 77 | fn try_from(value: (i64, CompleteEntry)) -> Result { 78 | Ok(match value.0 { 79 | 0 => Self::Insert(value.1), 80 | 1 => Self::Update(value.1), 81 | 2 => Self::Delete(value.1), 82 | _ => return Err(ParsePayloadError::Operation), 83 | }) 84 | } 85 | } 86 | 87 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 88 | #[serde(tag = "type")] 89 | /// A single entry in a limit order book 90 | pub enum Entry { 91 | /// A resting buy order 92 | Bid(Row), 93 | /// A resting sell order 94 | Ask(Row), 95 | } 96 | 97 | #[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Serialize, Deserialize)] 98 | /// A single row in a limit order book 99 | pub struct Row { 100 | /// The position of the row in the order book. 101 | pub position: u64, 102 | /// The order's price. 103 | pub price: f64, 104 | /// The order's size. 105 | pub size: f64, 106 | } 107 | 108 | impl TryFrom<(u32, u64, f64, f64)> for Entry { 109 | type Error = ParsePayloadError; 110 | 111 | fn try_from(value: (u32, u64, f64, f64)) -> Result { 112 | Ok(match value.0 { 113 | 0 => Self::Ask(Row { 114 | position: value.1, 115 | price: value.2, 116 | size: value.3, 117 | }), 118 | 1 => Self::Bid(Row { 119 | position: value.1, 120 | price: value.2, 121 | size: value.3, 122 | }), 123 | _ => Err(ParsePayloadError::Entry)?, 124 | }) 125 | } 126 | } 127 | 128 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 129 | #[serde(tag = "origin")] 130 | /// A complete entry in a limit order book that potentially containing additional information about the market-maker / exchange from where 131 | /// the quote was sourced. 132 | pub enum CompleteEntry { 133 | /// An entry that indicates additional information about the exchange from which the information has been aggregated 134 | SmartDepth { 135 | /// The exchange from which the entry is sourced. 136 | exchange: Primary, 137 | /// The entry itself. 138 | entry: Entry, 139 | }, 140 | /// An entry that indicates additional information about the market maker that has posted a given entry. 141 | MarketMaker { 142 | /// A unique identifier which conveys information about the market maker posting the entry. 143 | #[serde( 144 | serialize_with = "serialize_mpid", 145 | deserialize_with = "deserialize_mpid" 146 | )] 147 | market_maker: Mpid, 148 | /// The entry itself. 149 | entry: Entry, 150 | }, 151 | /// An entry that contains no additional information about the participant or exchange. 152 | Ordinary(Entry), 153 | } 154 | 155 | /// A unique four-character ID that identifies an individual market maker 156 | pub type Mpid = [char; 4]; 157 | 158 | fn serialize_mpid(mpid: &Mpid, serializer: S) -> Result { 159 | serializer.serialize_str(mpid.iter().collect::().as_str()) 160 | } 161 | 162 | fn deserialize_mpid<'de, D: serde::Deserializer<'de>>( 163 | deserializer: D, 164 | ) -> Result { 165 | let s = String::deserialize(deserializer)?; 166 | s.chars() 167 | .take(4) 168 | .collect::>() 169 | .try_into() 170 | .map_err(|_| Error::invalid_value(serde::de::Unexpected::Str(&s), &"Valid UTF-8 Mpid")) 171 | } 172 | } 173 | 174 | #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] 175 | /// A single entry in a histogram. 176 | pub struct HistogramEntry { 177 | /// The price (x-value). 178 | pub price: f64, 179 | /// The frequency of the price (size / y-value). 180 | pub size: f64, 181 | } 182 | 183 | #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] 184 | /// A single historical bar 185 | pub struct BarCore { 186 | /// The ending datetime for the bar. 187 | #[serde(with = "ts_seconds")] 188 | pub datetime: DateTime, 189 | /// The bar's open price. 190 | pub open: f64, 191 | /// The bar's high price. 192 | pub high: f64, 193 | /// The bar's low price. 194 | pub low: f64, 195 | ///The bar's close price. 196 | pub close: f64, 197 | } 198 | 199 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] 200 | #[serde(tag = "bar_type")] 201 | /// A single bar. 202 | pub enum Bar { 203 | /// The ordinary bar data returned from non [`crate::market_data::historical_bar::Trades`] requests. 204 | Ordinary(BarCore), 205 | /// The bar data returned from a [`crate::market_data::historical_bar::Trades`] request. 206 | Trades(Trade), 207 | } 208 | 209 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] 210 | #[serde(from = "TradeSerDeHelper", into = "TradeSerDeHelper")] 211 | /// A trade bar with volume, WAP, and count data. 212 | pub struct Trade { 213 | /// The core bar with open, high, low, close, etc. 214 | pub bar: BarCore, 215 | /// The bar's traded volume. 216 | pub volume: f64, 217 | /// The bar's Weighted Average Price. 218 | pub wap: f64, 219 | /// The number of trades during the bar's timespan. 220 | pub trade_count: u64, 221 | } 222 | 223 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)] 224 | /// Helper struct for serializing / deserializing [`Trade`] 225 | struct TradeSerDeHelper { 226 | /// The ending datetime for the bar. 227 | #[serde(with = "ts_seconds")] 228 | datetime: DateTime, 229 | /// The bar's open price. 230 | open: f64, 231 | /// The bar's high price. 232 | high: f64, 233 | /// The bar's low price. 234 | low: f64, 235 | ///The bar's close price. 236 | close: f64, 237 | /// The bar's traded volume. 238 | volume: f64, 239 | /// The bar's Weighted Average Price. 240 | wap: f64, 241 | /// The number of trades during the bar's timespan. 242 | trade_count: u64, 243 | } 244 | 245 | impl From for Trade { 246 | fn from(value: TradeSerDeHelper) -> Self { 247 | Trade { 248 | bar: BarCore { 249 | datetime: value.datetime, 250 | open: value.open, 251 | high: value.high, 252 | low: value.low, 253 | close: value.close, 254 | }, 255 | volume: value.volume, 256 | wap: value.wap, 257 | trade_count: value.trade_count, 258 | } 259 | } 260 | } 261 | 262 | impl From for TradeSerDeHelper { 263 | fn from(value: Trade) -> Self { 264 | Self { 265 | datetime: value.bar.datetime, 266 | open: value.bar.open, 267 | high: value.bar.high, 268 | low: value.bar.low, 269 | close: value.bar.close, 270 | volume: value.volume, 271 | wap: value.wap, 272 | trade_count: value.trade_count, 273 | } 274 | } 275 | } 276 | 277 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 278 | #[serde(tag = "tick")] 279 | /// A historical or live tick. 280 | pub enum TickData { 281 | /// A tick representing a midpoint price. 282 | Midpoint(Midpoint), 283 | /// A tick representing the current best bid / ask prices. 284 | BidAsk(BidAsk), 285 | /// A tick representing the last trade. 286 | Last(Last), 287 | } 288 | 289 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 290 | /// A tick representing the midpoint. 291 | pub struct Midpoint { 292 | /// The timestamp of the tick. 293 | #[serde(with = "ts_seconds")] 294 | pub datetime: DateTime, 295 | /// The midpoint price. 296 | pub price: f64, 297 | } 298 | 299 | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Serialize, Deserialize)] 300 | /// A tick representing a bid/ask. 301 | pub struct BidAsk { 302 | /// The timestamp of the tick. 303 | #[serde(with = "ts_seconds")] 304 | pub datetime: DateTime, 305 | /// The bid price. 306 | pub bid_price: f64, 307 | /// The ask price. 308 | pub ask_price: f64, 309 | /// The bid size. 310 | pub bid_size: f64, 311 | /// The ask size. 312 | pub ask_size: f64, 313 | } 314 | 315 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] 316 | /// A tick representing the last traded price. 317 | pub struct Last { 318 | /// The timestamp of the tick. 319 | #[serde(with = "ts_seconds")] 320 | pub datetime: DateTime, 321 | /// The last traded price. 322 | pub price: f64, 323 | /// The last traded size. 324 | pub size: f64, 325 | /// The last traded exchange. 326 | pub exchange: crate::exchange::Primary, 327 | } 328 | 329 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 330 | /// A single position, comprising a single security and details about its current value, P&L, etc. 331 | pub struct Position { 332 | /// The ID of the underlying contract. 333 | pub contract: ExchangeProxy, 334 | /// The number of contracts owned. 335 | pub position: f64, 336 | /// The current market price of each contract. 337 | pub market_price: f64, 338 | /// The current market value of the entire position. 339 | pub market_value: f64, 340 | /// The average cost per contract for the entire position. 341 | pub average_cost: f64, 342 | /// The unrealized P&L of the position. 343 | pub unrealized_pnl: f64, 344 | /// The realized P&L of the position. 345 | pub realized_pnl: f64, 346 | /// The account number holding the position. 347 | pub account_number: String, 348 | } 349 | 350 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 351 | /// A single position, comprising a single security and a few details about its cost, account, etc. 352 | pub struct PositionSummary { 353 | /// The underlying contract 354 | pub contract: ExchangeProxy, 355 | /// The number of contracts owned. 356 | pub position: f64, 357 | /// The average cost per contract for the entire position. 358 | pub average_cost: f64, 359 | /// The account number holding the position. 360 | pub account_number: String, 361 | } 362 | 363 | #[derive(Debug, Default, Clone, Copy, PartialOrd, PartialEq, Serialize, Deserialize)] 364 | /// A simple struct representing a few types of P&L. 365 | pub struct Pnl { 366 | /// The daily P&L for the account in real-time. 367 | pub daily: f64, 368 | /// Total unrealized P&L for the account. 369 | pub unrealized: f64, 370 | /// Total realized P&L for the account. 371 | pub realized: f64, 372 | } 373 | 374 | #[derive(Debug, Default, Clone, Copy, PartialOrd, PartialEq, Serialize, Deserialize)] 375 | /// A simple struct representing single position P&L 376 | pub struct PnlSingle { 377 | /// The daily P&L for the position in real-time. 378 | pub daily: f64, 379 | /// Unrealized P&L for the position. 380 | pub unrealized: f64, 381 | /// Realized P&L for the position. 382 | pub realized: f64, 383 | /// Current size of the position 384 | pub position_size: f64, 385 | /// The current market value of the position 386 | pub market_value: f64, 387 | } 388 | 389 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 390 | #[serde(tag = "order_status")] 391 | /// The possible statuses for a given order. 392 | pub enum OrderStatus { 393 | /// Indicates order has not yet been sent to IB server, for instance if there is a delay in receiving the security definition. Uncommonly received. 394 | ApiPending(OrderStatusCore), 395 | /// Indicates that you have transmitted the order, but have not yet received confirmation that it has been accepted by the order destination. Most commonly because exchange is closed. 396 | PendingSubmit(OrderStatusCore), 397 | /// Indicates that you have sent a request to cancel the order but have not yet received cancel confirmation from the order destination. At this point, your order is not confirmed canceled. It is not guaranteed that the cancellation will be successful. 398 | PendingCancel(OrderStatusCore), 399 | /// Indicates that a simulated order type has been accepted by the IB system and that this order has yet to be elected. The order is held in the IB system until the election criteria are met. At that time the order is transmitted to the order destination as specified. 400 | PreSubmitted(OrderStatusCore), 401 | /// Indicates that your order has been accepted at the order destination and is working. 402 | Submitted(OrderStatusCore), 403 | /// After an order has been submitted and before it has been acknowledged, an API client can request its cancellation, producing this state. 404 | ApiCancelled(OrderStatusCore), 405 | /// Indicates that the balance of your order has been confirmed canceled by the IB system. This could occur unexpectedly when IB or the destination has rejected your order. 406 | Cancelled(OrderStatusCore), 407 | /// Indicates that the order has been completely filled. Market orders executions will not always trigger a Filled status. 408 | Filled(OrderStatusCore), 409 | /// Indicates that the order was received by the system but is no longer active because it was rejected or canceled. 410 | Inactive(OrderStatusCore), 411 | } 412 | 413 | impl TryFrom<(&str, OrderStatusCore)> for OrderStatus { 414 | type Error = ParsePayloadError; 415 | 416 | fn try_from(value: (&str, OrderStatusCore)) -> Result { 417 | Ok(match value.0 { 418 | "ApiPending" => OrderStatus::ApiPending(value.1), 419 | "PendingSubmit" => OrderStatus::PendingSubmit(value.1), 420 | "PendingCancel" => OrderStatus::PendingCancel(value.1), 421 | "PreSubmitted" => OrderStatus::PreSubmitted(value.1), 422 | "Submitted" => OrderStatus::Submitted(value.1), 423 | "ApiCancelled" => OrderStatus::ApiCancelled(value.1), 424 | "Cancelled" => OrderStatus::Cancelled(value.1), 425 | "Filled" => OrderStatus::Filled(value.1), 426 | "Inactive" => OrderStatus::Inactive(value.1), 427 | s => return Err(ParsePayloadError::OrderStatus(s.to_owned())), 428 | }) 429 | } 430 | } 431 | 432 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 433 | /// The core fields of an Order's Status 434 | pub struct OrderStatusCore { 435 | /// The order's ID. 436 | pub order_id: i64, 437 | /// The details of how many contracts have been filled. 438 | pub fill: Option, 439 | /// The remnant positions. 440 | pub remaining: f64, 441 | /// The order’s permId used by the TWS to identify orders. 442 | pub permanent_id: i64, 443 | /// Parent’s id. Used for bracket and auto trailing stop orders. 444 | pub parent_id: Option, 445 | /// API client which submitted the order. 446 | pub client_id: i64, 447 | /// This field is used to identify an order held when TWS is trying to locate shares for a short sell. 448 | pub why_held: Option, 449 | /// If an order has been capped, this indicates the current capped price. 450 | pub market_cap_price: Option, 451 | } 452 | 453 | #[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Serialize, Deserialize)] 454 | /// Contains the details of an order's filled positions. 455 | pub struct Fill { 456 | /// Number of filled positions. 457 | pub filled: f64, 458 | /// Average filling price. 459 | pub average_price: f64, 460 | /// Price at which the last positions were filled. 461 | pub last_price: f64, 462 | } 463 | 464 | #[derive(Debug, Default, Clone, Copy, PartialOrd, Eq, Ord, PartialEq, Serialize, Deserialize)] 465 | /// Indicates whether an order is being held because IBKR is trying to locate shares for a short sale. 466 | pub struct Locate; 467 | 468 | impl FromStr for Locate { 469 | type Err = ParsePayloadError; 470 | 471 | fn from_str(s: &str) -> Result { 472 | match s.to_lowercase().as_str() { 473 | "locate" => Ok(Locate), 474 | s => Err(ParsePayloadError::Locate(s.to_owned())), 475 | } 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /src/account.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Formatter; 2 | use std::num::{ParseFloatError, ParseIntError}; 3 | use std::str::{FromStr, ParseBoolError}; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | use thiserror::Error; 7 | 8 | use crate::currency::{Currency, ParseCurrencyError}; 9 | 10 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 11 | #[serde(tag = "attribute", content = "data")] 12 | /// Represents a specific account value 13 | pub enum Attribute { 14 | /// The account ID number. 15 | AccountCode(String), 16 | /// "All" to return account summary data for all accounts, or set to a specific Advisor Account Group name that has already been created in TWS Global Configuration. 17 | AccountOrGroup(Group, Denomination), 18 | /// For internal use only. 19 | AccountReady(bool), 20 | /// Identifies the IB account structure. 21 | AccountType(String), 22 | /// Accrued cash value of stock, commodities and securities. 23 | AccruedCash(Segment, Denomination), 24 | /// Value of dividends accrued. 25 | AccruedDividend(Segment, Denomination), 26 | /// This value tells what you have available for trading. 27 | AvailableFunds(Segment, Denomination), 28 | /// Value of treasury bills. 29 | Billable(Segment, Denomination), 30 | /// Cash Account: Minimum (Equity with Loan Value, Previous Day Equity with Loan Value)-Initial Margin, Standard Margin Account: Minimum (Equity with Loan Value, Previous Day Equity with Loan Value) - Initial Margin *4. 31 | BuyingPower(f64, Denomination), 32 | /// Cash recognized at the time of trade + futures PNL. 33 | CashBalance(f64, Denomination), 34 | /// Unknown. 35 | ColumnPrio(Segment), 36 | /// Value of non-Government bonds such as corporate bonds and municipal bonds. 37 | CorporateBondValue(f64, Denomination), 38 | /// Value of cryptocurrency positions at PAXOS. 39 | Cryptocurrency(f64, Denomination), 40 | /// Open positions are grouped by currency. 41 | Currency(Denomination), 42 | /// Excess liquidity as a percentage of net liquidation value. 43 | Cushion(f64), 44 | /// Number of Open/Close trades one could do before Pattern Day Trading is detected. 45 | DayTradesRemaining(RemainingDayTrades), 46 | /// Number of Open/Close trades one could do tomorrow before Pattern Day Trading is detected. 47 | DayTradesRemainingTPlus1(RemainingDayTrades), 48 | /// Number of Open/Close trades one could do two days from today before Pattern Day Trading is detected. 49 | DayTradesRemainingTPlus2(RemainingDayTrades), 50 | /// Number of Open/Close trades one could do three days from today before Pattern Day Trading is detected. 51 | DayTradesRemainingTPlus3(RemainingDayTrades), 52 | /// Number of Open/Close trades one could do four days from today before Pattern Day Trading is detected. 53 | DayTradesRemainingTPlus4(RemainingDayTrades), 54 | /// Day trading status: For internal use only. 55 | DayTradingStatus(String), 56 | /// Forms the basis for determining whether a client has the necessary assets to either initiate or maintain security positions. 57 | EquityWithLoanValue(Segment, Denomination), 58 | /// This value shows your margin cushion, before liquidation. 59 | ExcessLiquidity(Segment, Denomination), 60 | /// The exchange rate of the currency to your base currency. 61 | ExchangeRate(f64, Denomination), 62 | /// Available funds of whole portfolio with no discounts or intraday credits. 63 | FullAvailableFunds(Segment, Denomination), 64 | /// Excess liquidity of whole portfolio with no discounts or intraday credits. 65 | FullExcessLiquidity(Segment, Denomination), 66 | /// Initial Margin of whole portfolio with no discounts or intraday credits. 67 | FullInitMarginReq(Segment, Denomination), 68 | /// Maintenance Margin of whole portfolio with no discounts or intraday credits. 69 | FullMaintenanceMarginReq(Segment, Denomination), 70 | /// Value of funds value (money market funds + mutual funds). 71 | FundValue(f64, Denomination), 72 | /// Real-time market-to-market value of futures options. 73 | FutureOptionValue(f64, Denomination), 74 | /// Real-time changes in futures value since last settlement. 75 | FuturesPnl(f64, Denomination), 76 | /// Cash balance in related IB-UKL account. 77 | FxCashBalance(f64, Denomination), 78 | /// Gross Position Value in securities segment. 79 | GrossPositionValue(f64, Denomination), 80 | /// Long Stock Value + Short Stock Value + Long Option Value + Short Option Value. 81 | GrossPositionValueSecurity(f64, Denomination), 82 | /// Guarantee: For internal use only. 83 | Guarantee(Segment, Denomination), 84 | /// Incentive coupon. 85 | IncentiveCoupons(Segment, Denomination), 86 | /// Margin rule for IB-IN accounts. 87 | IndianStockHaircut(Segment, Denomination), 88 | /// Initial Margin requirement of whole portfolio. 89 | InitMarginReq(Segment, Denomination), 90 | /// Real-time mark-to-market value of Issued Option. 91 | IssuerOptionValue(f64, Denomination), 92 | /// Quotient of `GrossPositionValue` and `NetLiquidation` in security segment. 93 | LeverageSecurity(f64), 94 | /// Time when look-ahead values take effect. 95 | LookAheadNextChange(i32), 96 | /// This value reflects your available funds at the next margin change. 97 | LookAheadAvailableFunds(Segment, Denomination), 98 | /// This value reflects your excess liquidity at the next margin change. 99 | LookAheadExcessLiquidity(Segment, Denomination), 100 | /// Initial margin requirement of whole portfolio as of next period's margin change. 101 | LookAheadInitMarginReq(Segment, Denomination), 102 | /// Maintenance margin requirement of whole portfolio as of next period's margin change. 103 | LookAheadMaintenanceMarginReq(Segment, Denomination), 104 | /// Maintenance Margin requirement of whole portfolio. 105 | MaintenanceMarginReq(Segment, Denomination), 106 | /// Market value of money market funds excluding mutual funds. 107 | MoneyMarketFundValue(f64, Denomination), 108 | /// Market value of mutual funds excluding money market funds. 109 | MutualFundValue(f64, Denomination), 110 | /// In review margin: Internal use only 111 | NlvAndMarginInReview(bool), 112 | /// The sum of the Dividend Payable/Receivable Values for the securities and commodities segments of the account. 113 | NetDividend(f64, Denomination), 114 | /// The basis for determining the price of the assets in your account. 115 | NetLiquidation(Segment, Denomination), 116 | /// Net liquidation for individual currencies. 117 | NetLiquidationByCurrency(f64, Denomination), 118 | /// Net liquidation uncertainty. 119 | NetLiquidationUncertainty(f64, Currency), 120 | /// Real-time mark-to-market value of options. 121 | OptionMarketValue(f64, Denomination), 122 | /// Personal Account shares value of whole portfolio. 123 | PaSharesValue(Segment, Denomination), 124 | /// Physical certificate value: Internal use only 125 | PhysicalCertificateValue(Segment, Denomination), 126 | /// Total projected "at expiration" excess liquidity. 127 | PostExpirationExcess(Segment, Denomination), 128 | /// Total projected "at expiration" margin. 129 | PostExpirationMargin(Segment, Denomination), 130 | /// Marginable Equity with Loan value as of 16:00 ET the previous day in securities segment. 131 | PreviousDayEquityWithLoanValue(f64, Denomination), 132 | /// `IMarginable` Equity with Loan value as of 16:00 ET the previous day. 133 | PreviousDayEquityWithLoanValueSecurity(f64, Denomination), 134 | /// Open positions are grouped by currency. 135 | RealCurrency(Denomination), 136 | /// Shows your profit on closed positions, which is the difference between your entry execution cost and exit execution costs, or (execution price + commissions to open the positions) - (execution price + commissions to close the position). 137 | RealizedPnL(f64, Denomination), 138 | /// Regulation T equity for universal account. 139 | RegTEquity(f64, Denomination), 140 | /// Regulation T equity for security segment. 141 | RegTEquitySecurity(f64, Denomination), 142 | /// Regulation T margin for universal account. 143 | RegTMargin(f64, Denomination), 144 | /// Regulation T margin for security segment. 145 | RegTMarginSecurity(f64, Denomination), 146 | /// Line of credit created when the market value of securities in a Regulation T account increase in value. 147 | Sma(f64, Denomination), 148 | /// Regulation T Special Memorandum Account balance for security segment. 149 | SmaSecurity(f64, Denomination), 150 | /// Account segment name. 151 | SegmentTitle(Segment, Denomination), 152 | /// Real-time mark-to-market value of stock. 153 | StockMarketValue(f64, Denomination), 154 | /// Value of treasury bonds. 155 | TBondValue(f64, Denomination), 156 | /// Value of treasury bills. 157 | TBillValue(f64, Denomination), 158 | /// Total Cash Balance including Future PNL. 159 | TotalCashBalance(f64, Denomination), 160 | /// Total cash value of stock, commodities and securities. 161 | TotalCashValue(Segment, Denomination), 162 | /// Total debit card pending charges. 163 | TotalDebitCardPendingCharges(Segment, Denomination), 164 | /// Account Type. 165 | TradingTypeSecurity(String), 166 | /// The difference between the current market value of your open positions and the average cost, or Value - Average Cost. 167 | UnrealizedPnL(f64, Denomination), 168 | /// Value of warrants. 169 | WarrantValue(f64, Denomination), 170 | /// To check projected margin requirements under Portfolio Margin model. 171 | WhatIfPMEnabled(bool), 172 | } 173 | 174 | #[derive(Debug, Clone, Error)] 175 | #[error("Invalid value encountered when attempting to parse attribute. Cause: {0}")] 176 | /// An error returned when parsing an [`Attribute`] fails. 177 | pub enum ParseAttributeError { 178 | #[error("Failed to parse floating point attribute {attribute_name}. Cause: {float_error}")] 179 | /// Failed to parse float attribute 180 | Float { 181 | /// The name of the attribute 182 | attribute_name: &'static str, 183 | /// The underlying error 184 | float_error: ParseFloatError, 185 | }, 186 | #[error("Failed to parse integer attribute {attribute_name}. Cause: {int_error}")] 187 | /// Failed to parse int attribute 188 | Int { 189 | /// The name of the attribute 190 | attribute_name: &'static str, 191 | /// The underlying error 192 | int_error: ParseIntError, 193 | }, 194 | #[error("Failed to parse day trades attribute {attribute_name}. Cause: {day_trades_error}")] 195 | /// Failed to parse [`RemainingDayTrades`] attribute 196 | DayTrades { 197 | /// The name of the attribute 198 | attribute_name: &'static str, 199 | /// The underlying error 200 | day_trades_error: ParseDayTradesError, 201 | }, 202 | #[error("Failed to parse boolean attribute {attribute_name}. Cause: {bool_error}")] 203 | /// Failed to parse [`bool`] attribute 204 | Bool { 205 | /// The name of the attribute 206 | attribute_name: &'static str, 207 | /// The underlying error 208 | bool_error: ParseBoolError, 209 | }, 210 | #[error( 211 | "Failed to parse denomination attribute {attribute_name}. Cause: {denomination_error}" 212 | )] 213 | /// Failed to parse [`Denomination`] attribute 214 | Denomination { 215 | /// The name of the attribute 216 | attribute_name: &'static str, 217 | /// The underlying error 218 | denomination_error: ParseCurrencyError, 219 | }, 220 | #[error("No such attribute {0}")] 221 | /// No such attribute exists 222 | NoSuchAttribute(String), 223 | } 224 | 225 | impl From<(&'static str, ParseFloatError)> for ParseAttributeError { 226 | fn from(value: (&'static str, ParseFloatError)) -> Self { 227 | Self::Float { 228 | attribute_name: value.0, 229 | float_error: value.1, 230 | } 231 | } 232 | } 233 | 234 | impl From<(&'static str, ParseIntError)> for ParseAttributeError { 235 | fn from(value: (&'static str, ParseIntError)) -> Self { 236 | Self::Int { 237 | attribute_name: value.0, 238 | int_error: value.1, 239 | } 240 | } 241 | } 242 | 243 | impl From<(&'static str, ParseDayTradesError)> for ParseAttributeError { 244 | fn from(value: (&'static str, ParseDayTradesError)) -> Self { 245 | Self::DayTrades { 246 | attribute_name: value.0, 247 | day_trades_error: value.1, 248 | } 249 | } 250 | } 251 | 252 | impl From<(&'static str, ParseBoolError)> for ParseAttributeError { 253 | fn from(value: (&'static str, ParseBoolError)) -> Self { 254 | Self::Bool { 255 | attribute_name: value.0, 256 | bool_error: value.1, 257 | } 258 | } 259 | } 260 | 261 | impl From<(&'static str, ParseCurrencyError)> for ParseAttributeError { 262 | fn from(value: (&'static str, ParseCurrencyError)) -> Self { 263 | Self::Denomination { 264 | attribute_name: value.0, 265 | denomination_error: value.1, 266 | } 267 | } 268 | } 269 | 270 | impl From<(&'static str, std::convert::Infallible)> for ParseAttributeError { 271 | fn from(_value: (&'static str, std::convert::Infallible)) -> Self { 272 | unreachable!() 273 | } 274 | } 275 | 276 | #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] 277 | #[serde(untagged)] 278 | /// The particular account groups managed by a given client. 279 | pub enum Group { 280 | /// All accounts to which a given user has access. 281 | All, 282 | /// A specific account. 283 | Name(String), 284 | } 285 | 286 | impl FromStr for Group { 287 | type Err = std::convert::Infallible; 288 | 289 | fn from_str(s: &str) -> Result { 290 | Ok(match s { 291 | "All" => Self::All, 292 | _ => Self::Name(s.to_owned()), 293 | }) 294 | } 295 | } 296 | 297 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] 298 | #[serde(tag = "segment")] 299 | /// The intra-account segments of various values. 300 | pub enum Segment { 301 | /// The total value across an entire account. 302 | Total(T), 303 | /// The value for US Commodities. 304 | Commodity(T), 305 | /// The value for Crypto at Paxos. 306 | Paxos(T), 307 | /// The value for US Securities. 308 | Security(T), 309 | } 310 | 311 | #[derive(Debug, Clone, Copy, PartialEq, Hash, Serialize, Deserialize)] 312 | #[serde(untagged)] 313 | /// The denomination of a given value. 314 | pub enum Denomination { 315 | /// The base currency for the corresponding account. 316 | Base, 317 | /// A specific [`Currency`] 318 | Specific(Currency), 319 | } 320 | 321 | impl FromStr for Denomination { 322 | type Err = ParseCurrencyError; 323 | 324 | fn from_str(s: &str) -> Result { 325 | match s.to_uppercase().as_str() { 326 | "BASE" => Ok(Self::Base), 327 | c => Ok(Self::Specific(c.parse()?)), 328 | } 329 | } 330 | } 331 | 332 | #[derive(Debug, Clone, Copy, PartialEq, Hash, Serialize, Deserialize)] 333 | #[serde(untagged)] 334 | /// Represents the possible numbers of day trades before a regulatory breach of pattern day-trading 335 | /// rules is committed. 336 | pub enum RemainingDayTrades { 337 | /// No limits on the number of day trades. 338 | Unlimited, 339 | /// A specified number of day trades are remaining. 340 | Count(u32), 341 | } 342 | 343 | #[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 344 | /// An error type that represents an invalid value encountered while parsing the numer of remaining 345 | /// day trades. 346 | pub struct ParseDayTradesError(String); 347 | 348 | impl std::fmt::Display for ParseDayTradesError { 349 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 350 | write!( 351 | f, 352 | "Unable to parse day trades information. Unexpected count {}", 353 | self.0 354 | ) 355 | } 356 | } 357 | 358 | impl std::error::Error for ParseDayTradesError {} 359 | 360 | impl FromStr for RemainingDayTrades { 361 | type Err = ParseDayTradesError; 362 | 363 | fn from_str(s: &str) -> Result { 364 | match s { 365 | "-1" => Ok(Self::Unlimited), 366 | u => Ok(Self::Count( 367 | u.parse().map_err(|_| ParseDayTradesError(u.to_owned()))?, 368 | )), 369 | } 370 | } 371 | } 372 | 373 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 374 | #[serde(untagged)] 375 | /// Represents the different tag and value pairs in an account summary callback. 376 | pub enum TagValue { 377 | /// A tag whose value is a String 378 | String(Tag, String), 379 | /// A tag whose value is an integer (i64) 380 | Int(Tag, i64), 381 | /// A tag whose valued is a float (f64) 382 | Float(Tag, f64), 383 | /// A tag whose value is a float (f64), Currency pair 384 | Currency(Tag, f64, Currency), 385 | } 386 | 387 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 388 | /// Represents the different types of account information available for a 389 | /// [`crate::client::Client::req_account_summary`] request. 390 | pub enum Tag { 391 | /// Identifies the IB account structure 392 | AccountType, 393 | /// The basis for determining the price of the assets in your account. Total cash value + stock value + options value + bond value. 394 | NetLiquidation, 395 | /// Total cash balance recognized at the time of trade + futures PNL. 396 | TotalCashValue, 397 | /// Cash recognized at the time of settlement - purchases at the time of trade - commissions - taxes - fees. 398 | SettledCash, 399 | /// Total accrued cash value of stock, commodities and securities. 400 | AccruedCash, 401 | /// Buying power serves as a measurement of the dollar value of securities that one may purchase in a securities account without depositing additional funds. 402 | BuyingPower, 403 | /// Forms the basis for determining whether a client has the necessary assets to either initiate or maintain security positions. Cash + stocks + bonds + mutual funds. 404 | EquityWithLoanValue, 405 | /// Marginable Equity with Loan value as of 16:00 ET the previous day. 406 | PreviousEquityWithLoanValue, 407 | /// The sum of the absolute value of all stock and equity option positions. 408 | GrossPositionValue, 409 | /// Regulation T equity for universal account. 410 | RegTEquity, 411 | /// Regulation T margin for universal account. 412 | RegTMargin, 413 | #[serde(rename(serialize = "SMA"))] 414 | /// Special Memorandum Account: Line of credit created when the market value of securities in a Regulation T account increase in value. 415 | Sma, 416 | /// Initial Margin requirement of whole portfolio. 417 | InitMarginReq, 418 | #[serde(rename(serialize = "MaintMarginReq"))] 419 | /// Maintenance Margin requirement of whole portfolio. 420 | MaintenanceMarginReq, 421 | /// This value tells what you have available for trading. 422 | AvailableFunds, 423 | /// This value shows your margin cushion, before liquidation. 424 | ExcessLiquidity, 425 | /// Excess liquidity as a percentage of net liquidation value. 426 | Cushion, 427 | /// Initial Margin of whole portfolio with no discounts or intraday credits. 428 | FullInitMarginReq, 429 | #[serde(rename(serialize = "FullMaintMarginReq"))] 430 | /// Maintenance Margin of whole portfolio with no discounts or intraday credits. 431 | FullMaintenanceMarginReq, 432 | /// Available funds of whole portfolio with no discounts or intraday credits. 433 | FullAvailableFunds, 434 | /// Excess liquidity of whole portfolio with no discounts or intraday credits. 435 | FullExcessLiquidity, 436 | /// Time when look-ahead values take effect. 437 | LookAheadNextChange, 438 | /// Initial Margin requirement of whole portfolio as of next period's margin change. 439 | LookAheadInitMarginReq, 440 | #[serde(rename(serialize = "LookAheadMaintMarginReq"))] 441 | /// Maintenance Margin requirement of whole portfolio as of next period's margin change. 442 | LookAheadMaintenanceMarginReq, 443 | /// This value reflects your available funds at the next margin change. 444 | LookAheadAvailableFunds, 445 | /// This value reflects your excess liquidity at the next margin change. 446 | LookAheadExcessLiquidity, 447 | /// A measure of how close the account is to liquidation. 448 | HighestSeverity, 449 | /// The Number of Open/Close trades a user could put on before Pattern Day Trading is detected. A value of "-1" means that the user can put on unlimited day trades. 450 | DayTradesRemaining, 451 | /// Quotient of `GrossPositionValue` and `NetLiquidation`. 452 | Leverage, 453 | } 454 | 455 | #[derive(Debug, Clone, Copy, Error)] 456 | #[error("Invalid value encountered when parsing tag.")] 457 | /// An error returned when attempting to parse a [`Tag`] 458 | pub struct ParseTagError; 459 | 460 | impl FromStr for Tag { 461 | type Err = ParseTagError; 462 | 463 | fn from_str(s: &str) -> Result { 464 | Ok(match s { 465 | "AccountType" => Self::AccountType, 466 | "NetLiquidation" => Self::NetLiquidation, 467 | "TotalCashValue" => Self::TotalCashValue, 468 | "SettledCash" => Self::SettledCash, 469 | "AccruedCash" => Self::AccruedCash, 470 | "BuyingPower" => Self::BuyingPower, 471 | "EquityWithLoanValue" => Self::EquityWithLoanValue, 472 | "PreviousEquityWithLoanValue" => Self::PreviousEquityWithLoanValue, 473 | "GrossPositionValue" => Self::GrossPositionValue, 474 | "RegTEquity" => Self::RegTEquity, 475 | "RegTMargin" => Self::RegTMargin, 476 | "SMA" => Self::Sma, 477 | "InitMarginReq" => Self::InitMarginReq, 478 | "MaintMarginReq" => Self::MaintenanceMarginReq, 479 | "AvailableFunds" => Self::AvailableFunds, 480 | "ExcessLiquidity" => Self::ExcessLiquidity, 481 | "Cushion" => Self::Cushion, 482 | "FullInitMarginReq" => Self::FullInitMarginReq, 483 | "FullMaintMarginReq" => Self::FullMaintenanceMarginReq, 484 | "FullAvailableFunds" => Self::FullAvailableFunds, 485 | "FullExcessLiquidity" => Self::FullExcessLiquidity, 486 | "LookAheadNextChange" => Self::LookAheadNextChange, 487 | "LookAheadInitMarginReq" => Self::LookAheadInitMarginReq, 488 | "LookAheadMaintMarginReq" => Self::LookAheadMaintenanceMarginReq, 489 | "LookAheadAvailableFunds" => Self::LookAheadAvailableFunds, 490 | "LookAheadExcessLiquidity" => Self::LookAheadExcessLiquidity, 491 | "HighestSeverity" => Self::HighestSeverity, 492 | "DayTradesRemaining" => Self::DayTradesRemaining, 493 | "Leverage" => Self::Leverage, 494 | _ => return Err(ParseTagError), 495 | }) 496 | } 497 | } 498 | --------------------------------------------------------------------------------