├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── log_config.yml └── src ├── bin └── manual_tests.rs ├── core ├── account_summary_tags.rs ├── algo_params.rs ├── client.rs ├── common.rs ├── contract.rs ├── decoder.rs ├── errors.rs ├── execution.rs ├── messages.rs ├── mod.rs ├── order.rs ├── order_condition.rs ├── order_decoder.rs ├── reader.rs ├── scanner.rs ├── server_versions.rs ├── streamer.rs └── wrapper.rs ├── examples ├── contract_samples.rs ├── defaults.rs ├── fa_allocation_samples.rs ├── mod.rs ├── order_samples.rs ├── scanner_subscription_samples.rs └── test_helpers.rs ├── lib.rs └── tests ├── mod.rs ├── test_eclient.rs └── test_messages.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | .idea 12 | /.idea/ 13 | /.vscode/ 14 | 15 | *.log 16 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "IBKR-API-Rust" 3 | version = "0.1.0" 4 | authors = ["brett.miller@sparkstart.com"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | name = "twsapi" 11 | path = "src/lib.rs" 12 | 13 | [[bin]] 14 | name = "twsapi_client" 15 | path = "src/bin/manual_tests.rs" 16 | 17 | [dependencies] 18 | bzip2 = "0.3.3" 19 | log = "0.4.8" 20 | log4rs = "0.12.0" 21 | bytebuffer = "0.2.1" 22 | encoding = "0.2" 23 | num = "0.3.0" 24 | num-derive = "0.3" 25 | num-traits = "0.2.12" 26 | byteorder = "1.3.4" 27 | ascii = "1.0.0" 28 | from-ascii = "0.0.1" 29 | serde = { version = "1.0", features = ["derive"] } 30 | bigdecimal = "0.1.2" 31 | float-cmp = "0.8.0" 32 | chrono = "0.4.11" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SparkStart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IBKR-API-Rust 2 | 3 | Port of Interactive Broker's trading API written in Rust (API_Version=9.76.01) 4 | 5 | Please see the latest IB Tws Api documentation here: . 6 | 7 | The documentation has information regarding configuring Trader WorkStation and IB Gateway to enable API access. 8 | 9 | For usage of this library, please see the example implementation in [src/examples/test_helpers/manual_tests.rs](src/bin/manual_tests.rs) 10 | 11 | The main structs and traits that clients will use are [**EClient**](src/core/client.rs) , a struct that is responsible for 12 | connecting to TWS or IB Gateway and sending requests, and [**Wrapper**](src/core/wrapper.rs), a trait that clients will implement that declares callback functions 13 | that get called when the application receives messages from TWS/IB Gateway. 14 | 15 | ## Example 16 | 17 | In the example below, TWS will send the next valid order ID when the sample application connects. This will cause the ***Wrapper*** callback method 18 | ***next_valid_id*** to be called, which will start sending test requests to TWS (see the 19 | ***start_requests*** method in ***TestWrapper*** which is called by ***next_valid_id***). 20 | 21 | ```rust, no_run 22 | use twsapi::core::errors::IBKRApiLibError; 23 | use twsapi::core::client::EClient; 24 | use std::time::Duration; 25 | use twsapi::examples::test_helpers::TestWrapper; 26 | use std::sync::{Arc, Mutex}; 27 | use std::thread; 28 | 29 | fn main() -> Result<(), IBKRApiLibError> { 30 | let wrapper = Arc::new(Mutex::new(TestWrapper::new())); 31 | let app = Arc::new(Mutex::new(EClient::new(wrapper.clone()))); 32 | 33 | println!("getting connection..."); 34 | 35 | wrapper.lock().expect("Wrapper mutex was poisoned").client = Option::from(app.clone()); 36 | 37 | app.lock() 38 | .expect("EClient mutex was poisoned") 39 | .connect("127.0.0.1", 4002, 0)?; 40 | 41 | thread::sleep(Duration::new(18600, 0)); 42 | 43 | Ok(()) 44 | } 45 | ``` 46 | 47 | ## TODO 48 | 49 | - [X] Expand documentation - Done 50 | - [ ] Write automated tests - in progress 51 | - [ ] Write an async function in TestWrapper that checks when next_valid_id has been populated by the callback 52 | - [ ] Publish to crates.io 53 | 54 | If you find a bug or would like to suggest changes, please contact me at brett.miller@sparkstart.com or submit a pull 55 | request. 56 | 57 | ## DISCLAIMER 58 | 59 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 60 | -------------------------------------------------------------------------------- /log_config.yml: -------------------------------------------------------------------------------- 1 | appenders: 2 | console: 3 | kind: console 4 | encoder: 5 | pattern: "{d(%Y-%m-%d %H:%M:%S)} {M} {f} line: {L} {l} - {m}\n" 6 | filters: 7 | - kind: threshold 8 | level: debug 9 | file: 10 | kind: file 11 | path: log/output.log 12 | encoder: 13 | pattern: "{d(%Y-%m-%d %H:%M:%S)} {M} {f} line: {L} {l} - {m}\n" 14 | filters: 15 | - kind: threshold 16 | level: debug 17 | 18 | root: 19 | level: debug 20 | appenders: 21 | - console 22 | - file 23 | 24 | loggers: 25 | test::a: 26 | level: info 27 | appenders: 28 | - console 29 | additive: true 30 | test::b: 31 | level: info 32 | appenders: 33 | - file 34 | additive: true -------------------------------------------------------------------------------- /src/bin/manual_tests.rs: -------------------------------------------------------------------------------- 1 | //! Binary for manually testing crate 2 | 3 | use log::*; 4 | use std::sync::{Arc, Mutex}; 5 | use std::thread; 6 | use std::time::Duration; 7 | use twsapi::core::client::EClient; 8 | use twsapi::core::{errors::*, streamer::TcpStreamer}; 9 | use twsapi::examples::test_helpers::TestWrapper; 10 | 11 | /// Example of using client and wrapper. 12 | /// Requires a running instance of TWS or IB Gateway connected to the port in main. 13 | /// Upon connecting, TWS will send the next valid order ID which will cause the wrapper callback method 14 | /// next_valid_id to be called, which will start sending tests requests to TWS (see the 15 | /// start_requests function inn TestWrapper which is called by next_valid_id 16 | //================================================================================================== 17 | pub fn main() -> Result<(), IBKRApiLibError> { 18 | match log4rs::init_file("./log_config.yml", Default::default()) { 19 | Ok(_) => (), 20 | Err(_) => { 21 | return Err(IBKRApiLibError::ApiError(TwsApiReportableError::new( 22 | -1, 23 | "-1".to_string(), 24 | "Failed to create logger!!".to_string(), 25 | ))) 26 | } 27 | }; 28 | 29 | let wrapper = Arc::new(Mutex::new(TestWrapper::::new())); 30 | let app = Arc::new(Mutex::new(EClient::new(wrapper.clone()))); 31 | 32 | info!("getting connection..."); 33 | 34 | wrapper.lock().expect("Wrapper mutex was poisoned").client = Option::from(app.clone()); 35 | 36 | //use port 7497 for TWS or 4002 for IB Gateway, depending on the port you have set 37 | app.lock() 38 | .expect("EClient mutex was poisoned") 39 | .connect("127.0.0.1", 4002, 0)?; 40 | 41 | thread::sleep(Duration::new(2, 0)); 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/core/account_summary_tags.rs: -------------------------------------------------------------------------------- 1 | //! Account summary tags 2 | use std::fmt::{Display, Error, Formatter}; 3 | 4 | use crate::core::account_summary_tags::AccountSummaryTags::*; 5 | 6 | //================================================================================================== 7 | /// AccountType — Identifies the IB account structure 8 | /// NetLiquidation — The basis for determining the price of the assets in your account. Total cash value + stock value + options value + bond value 9 | /// TotalCashValue — Total cash balance recognized at the time of trade + futures PNL 10 | /// SettledCash — Cash recognized at the time of settlement - purchases at the time of trade - commissions - taxes - fees 11 | /// AccruedCash — Total accrued cash value of stock, commodities and securities 12 | /// BuyingPower — Buying power serves as a measurement of the dollar value of securities that one may purchase in a securities account without depositing additional funds 13 | /// EquityWithLoanValue — Forms the basis for determining whether a client has the necessary assets to either initiate or maintain security positions. Cash + stocks + bonds + mutual funds 14 | /// PreviousEquityWithLoanValue — Marginable Equity with Loan value as of 16:00 ET the previous day 15 | /// GrossPositionValue — The sum of the absolute value of all stock and equity option positions 16 | /// RegTEquity — Regulation T equity for universal account 17 | /// RegTMargin — Regulation T margin for universal account 18 | /// SMA — Special Memorandum Account: Line of credit created when the market value of securities in a Regulation T account increase in value 19 | /// InitMarginReq — Initial Margin requirement of whole portfolio 20 | /// MaintMarginReq — Maintenance Margin requirement of whole portfolio 21 | /// AvailableFunds — This value tells what you have available for trading 22 | /// ExcessLiquidity — This value shows your margin cushion, before liquidation 23 | /// Cushion — Excess liquidity as a percentage of net liquidation value 24 | /// FullInitMarginReq — Initial Margin of whole portfolio with no discounts or intraday credits 25 | /// FullMaintMarginReq — Maintenance Margin of whole portfolio with no discounts or intraday credits 26 | /// FullAvailableFunds — Available funds of whole portfolio with no discounts or intraday credits 27 | /// FullExcessLiquidity — Excess liquidity of whole portfolio with no discounts or intraday credits 28 | /// LookAheadNextChange — Time when look-ahead values take effect 29 | /// LookAheadInitMarginReq — Initial Margin requirement of whole portfolio as of next period's margin change 30 | /// LookAheadMaintMarginReq — Maintenance Margin requirement of whole portfolio as of next period's margin change 31 | /// LookAheadAvailableFunds — This value reflects your available funds at the next margin change 32 | /// LookAheadExcessLiquidity — This value reflects your excess liquidity at the next margin change 33 | /// HighestSeverity — A measure of how close the account is to liquidation 34 | /// DayTradesRemaining — 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. 35 | /// Leverage — GrossPositionValue / NetLiquidation 36 | /// $LEDGER — Single flag to relay all cash balance tags*, only in base currency. 37 | /// $LEDGER:CURRENCY — Single flag to relay all cash balance tags*, only in the specified currency. 38 | /// $LEDGER:ALL — Single flag to relay all cash balance tags* in all currencies. 39 | 40 | pub enum AccountSummaryTags { 41 | AccountType, 42 | NetLiquidation, 43 | TotalCashValue, 44 | SettledCash, 45 | AccruedCash, 46 | BuyingPower, 47 | EquityWithLoanValue, 48 | PreviousEquityWithLoanValue, 49 | GrossPositionValue, 50 | ReqTEquity, 51 | ReqTMargin, 52 | SMA, 53 | InitMarginReq, 54 | MaintMarginReq, 55 | AvailableFunds, 56 | ExcessLiquidity, 57 | Cushion, 58 | FullInitMarginReq, 59 | FullMaintMarginReq, 60 | FullAvailableFunds, 61 | FullExcessLiquidity, 62 | LookAheadNextChange, 63 | LookAheadInitMarginReq, 64 | LookAheadMaintMarginReq, 65 | LookAheadAvailableFunds, 66 | LookAheadExcessLiquidity, 67 | HighestSeverity, 68 | DayTradesRemaining, 69 | Leverage, 70 | Ledger, 71 | LedgerCurrency, 72 | LedgerAll, 73 | AllTags, 74 | } 75 | 76 | impl AccountSummaryTags { 77 | fn display(&self) -> &str { 78 | match self { 79 | AccountType => "AccountType", 80 | NetLiquidation => "NetLiquidation", 81 | TotalCashValue => "TotalCashValue", 82 | SettledCash => "SettledCash", 83 | AccruedCash => "AccruedCash", 84 | BuyingPower => "BuyingPower", 85 | EquityWithLoanValue => "EquityWithLoanValue", 86 | PreviousEquityWithLoanValue => "PreviousEquityWithLoanValue", 87 | GrossPositionValue => "GrossPositionValue", 88 | ReqTEquity => "ReqTEquity", 89 | ReqTMargin => "ReqTMargin", 90 | SMA => "SMA", 91 | InitMarginReq => "InitMarginReq", 92 | MaintMarginReq => "MaintMarginReq", 93 | AvailableFunds => "AvailableFunds", 94 | ExcessLiquidity => "ExcessLiquidity", 95 | Cushion => "Cushion", 96 | FullInitMarginReq => "FullInitMarginReq", 97 | FullMaintMarginReq => "FullMaintMarginReq", 98 | FullAvailableFunds => "FullAvailableFunds", 99 | FullExcessLiquidity => "FullExcessLiquidity", 100 | LookAheadNextChange => "LookAheadNextChange", 101 | LookAheadInitMarginReq => "LookAheadInitMarginReq", 102 | LookAheadMaintMarginReq => "LookAheadMaintMarginReq", 103 | LookAheadAvailableFunds => "LookAheadAvailableFunds", 104 | LookAheadExcessLiquidity => "LookAheadExcessLiquidity", 105 | HighestSeverity => "HighestSeverity", 106 | DayTradesRemaining => "DayTradesRemaining", 107 | Leverage => "Leverage", 108 | Ledger => "$LEDGER", 109 | LedgerCurrency => "$LEDGER:CURRENCY", 110 | LedgerAll => "$LEDGER:ALL", 111 | AllTags => { 112 | "AccountType, 113 | NetLiquidation, 114 | TotalCashValue, 115 | SettledCash, 116 | AccruedCash, 117 | BuyingPower, 118 | EquityWithLoanValue, 119 | PreviousEquityWithLoanValue, 120 | GrossPositionValue, 121 | ReqTEquity, 122 | ReqTMargin, 123 | SMA, 124 | InitMarginReq, 125 | MaintMarginReq, 126 | AvailableFunds, 127 | ExcessLiquidity, 128 | Cushion,Cushion, 129 | FullInitMarginReq, 130 | FullMaintMarginReq, 131 | FullAvailableFunds, 132 | FullExcessLiquidity, 133 | LookAheadNextChange, 134 | LookAheadInitMarginReq, 135 | LookAheadMaintMarginReq, 136 | LookAheadAvailableFunds, 137 | LookAheadExcessLiquidity, 138 | HighestSeverity, 139 | DayTradesRemaining, 140 | Leverage, 141 | $LEDGER, 142 | $LEDGER:CURRENCY, 143 | $LEDGER:ALL" 144 | } 145 | } 146 | } 147 | } 148 | 149 | impl Display for AccountSummaryTags { 150 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 151 | write!(f, "{}", self.display()) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/core/algo_params.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions that illustrate setting fields related to algo parameters 2 | use crate::core::common::TagValue; 3 | use crate::core::order::Order; 4 | 5 | //================================================================================================== 6 | /// Scale parameters 7 | pub fn fill_scale_params( 8 | base_order: &mut Order, 9 | scale_init_level_size: i32, 10 | scale_subs_level_size: i32, 11 | scale_random_percent: bool, 12 | scale_price_increment: f64, 13 | scale_price_adjust_value: f64, 14 | scale_price_adjust_interval: i32, 15 | scale_profit_offset: f64, 16 | scale_auto_reset: bool, 17 | scale_init_position: i32, 18 | scale_init_fill_qty: i32, 19 | ) { 20 | base_order.scale_init_level_size = scale_init_level_size; // Initial Component Size 21 | base_order.scale_subs_level_size = scale_subs_level_size; // Subsequent Comp. Size 22 | base_order.scale_random_percent = scale_random_percent; // Randomize size by +/-55% 23 | base_order.scale_price_increment = scale_price_increment; // Price Increment 24 | 25 | // Auto Price adjustment 26 | base_order.scale_price_adjust_value = scale_price_adjust_value; // starting price by 27 | base_order.scale_price_adjust_interval = scale_price_adjust_interval; // in seconds 28 | 29 | // Profit Orders 30 | base_order.scale_profit_offset = scale_profit_offset; // Create profit taking order Profit Offset 31 | base_order.scale_auto_reset = scale_auto_reset; // Restore size after taking profit 32 | base_order.scale_init_position = scale_init_position; // Initial Position 33 | base_order.scale_init_fill_qty = scale_init_fill_qty; // Filled initial Component Size 34 | } 35 | 36 | //================================================================================================== 37 | /// Arrival price parameters 38 | pub fn fill_arrival_price_params( 39 | base_order: &mut Order, 40 | max_pct_vol: f64, 41 | risk_aversion: &str, 42 | start_time: &str, 43 | end_time: &str, 44 | force_completion: bool, 45 | allow_past_time: bool, 46 | monetary_value: i32, 47 | ) { 48 | base_order.algo_strategy = "ArrivalPx".to_string(); 49 | 50 | base_order.algo_params.push(TagValue::new( 51 | "maxPctVol".to_string(), 52 | max_pct_vol.to_string(), 53 | )); 54 | base_order.algo_params.push(TagValue::new( 55 | "riskAversion".to_string(), 56 | risk_aversion.to_string(), 57 | )); 58 | base_order.algo_params.push(TagValue::new( 59 | "startTime".to_string(), 60 | start_time.to_string(), 61 | )); 62 | base_order 63 | .algo_params 64 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 65 | base_order.algo_params.push(TagValue::new( 66 | "forceCompletion".to_string(), 67 | force_completion.to_string(), 68 | )); 69 | base_order.algo_params.push(TagValue::new( 70 | "allowPastEndTime".to_string(), 71 | allow_past_time.to_string(), 72 | )); 73 | base_order.algo_params.push(TagValue::new( 74 | "monetaryValue".to_string(), 75 | monetary_value.to_string(), 76 | )); 77 | } 78 | 79 | //================================================================================================== 80 | /// Dark ice parameters 81 | pub fn fill_dark_ice_params( 82 | base_order: &mut Order, 83 | display_size: i32, 84 | start_time: &str, 85 | end_time: &str, 86 | allow_past_end_time: bool, 87 | monetary_value: f64, 88 | ) { 89 | base_order.algo_strategy = "DarkIce".to_string(); 90 | base_order.algo_params.push(TagValue::new( 91 | "displaySize".to_string(), 92 | display_size.to_string(), 93 | )); 94 | base_order.algo_params.push(TagValue::new( 95 | "startTime".to_string(), 96 | start_time.to_string(), 97 | )); 98 | base_order 99 | .algo_params 100 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 101 | base_order.algo_params.push(TagValue::new( 102 | "allowPastEndTime".to_string(), 103 | (allow_past_end_time as i32).to_string(), 104 | )); 105 | base_order.algo_params.push(TagValue::new( 106 | "monetaryValue".to_string(), 107 | monetary_value.to_string(), 108 | )); 109 | } 110 | 111 | //================================================================================================== 112 | /// Percent volume parameters 113 | pub fn fill_pct_vol_params( 114 | base_order: &mut Order, 115 | pct_vol: f64, 116 | start_time: &str, 117 | end_time: &str, 118 | no_take_liq: bool, 119 | monetary_value: f64, 120 | ) { 121 | base_order.algo_strategy = "PctVol".to_string(); 122 | 123 | base_order 124 | .algo_params 125 | .push(TagValue::new("pctVol".to_string(), pct_vol.to_string())); 126 | base_order.algo_params.push(TagValue::new( 127 | "startTime".to_string(), 128 | start_time.to_string(), 129 | )); 130 | base_order 131 | .algo_params 132 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 133 | base_order.algo_params.push(TagValue::new( 134 | "noTakeLiq".to_string(), 135 | (no_take_liq as i32).to_string(), 136 | )); 137 | base_order.algo_params.push(TagValue::new( 138 | "monetaryValue".to_string(), 139 | monetary_value.to_string(), 140 | )); 141 | } 142 | 143 | // ! [twap_params] 144 | //================================================================================================== 145 | /// Fill Twap parameters 146 | pub fn fill_twap_params( 147 | base_order: &mut Order, 148 | strategy_type: &str, 149 | start_time: &str, 150 | end_time: &str, 151 | allow_past_end_time: bool, 152 | monetary_value: f64, 153 | ) { 154 | base_order.algo_strategy = "Twap".to_string(); 155 | 156 | base_order.algo_params.push(TagValue::new( 157 | "strategyType".to_string(), 158 | strategy_type.to_string(), 159 | )); 160 | base_order.algo_params.push(TagValue::new( 161 | "startTime".to_string(), 162 | start_time.to_string(), 163 | )); 164 | base_order 165 | .algo_params 166 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 167 | base_order.algo_params.push(TagValue::new( 168 | "allowPastEndTime".to_string(), 169 | (allow_past_end_time as i32).to_string(), 170 | )); 171 | base_order.algo_params.push(TagValue::new( 172 | "monetaryValue".to_string(), 173 | monetary_value.to_string(), 174 | )); 175 | } 176 | 177 | // ! [twap_params] 178 | 179 | // ! [vwap_params] 180 | //================================================================================================== 181 | /// Fill Vwap parameters 182 | pub fn fill_vwap_params( 183 | base_order: &mut Order, 184 | max_pct_vol: f64, 185 | start_time: &str, 186 | end_time: &str, 187 | allow_past_end_time: bool, 188 | no_take_liq: bool, 189 | monetary_value: f64, 190 | ) { 191 | base_order.algo_strategy = "Vwap".to_string(); 192 | 193 | base_order.algo_params.push(TagValue::new( 194 | "maxPctVol".to_string(), 195 | max_pct_vol.to_string(), 196 | )); 197 | base_order.algo_params.push(TagValue::new( 198 | "startTime".to_string(), 199 | start_time.to_string(), 200 | )); 201 | base_order 202 | .algo_params 203 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 204 | base_order.algo_params.push(TagValue::new( 205 | "allowPastEndTime".to_string(), 206 | (allow_past_end_time as i32).to_string(), 207 | )); 208 | base_order.algo_params.push(TagValue::new( 209 | "noTakeLiq".to_string(), 210 | (no_take_liq as i32).to_string(), 211 | )); 212 | base_order.algo_params.push(TagValue::new( 213 | "monetaryValue".to_string(), 214 | monetary_value.to_string(), 215 | )); 216 | } 217 | 218 | // ! [vwap_params] 219 | 220 | // ! [ad_params] 221 | //================================================================================================== 222 | /// Accumulate/Distrubute parameters 223 | pub fn fill_accumulate_distribute_params( 224 | base_order: &mut Order, 225 | component_size: i32, 226 | time_between_orders: i32, 227 | randomize_time_20: bool, 228 | randomize_size_55: bool, 229 | give_up: i32, 230 | catch_up: bool, 231 | wait_for_fill: bool, 232 | start_time: &str, 233 | end_time: &str, 234 | ) { 235 | base_order.algo_strategy = "AD".to_string(); 236 | 237 | base_order.algo_params.push(TagValue::new( 238 | "ComponentSize".to_string(), 239 | component_size.to_string(), 240 | )); 241 | base_order.algo_params.push(TagValue::new( 242 | "TimeBetweenOrders".to_string(), 243 | time_between_orders.to_string(), 244 | )); 245 | base_order.algo_params.push(TagValue::new( 246 | "RandomizeTime20".to_string(), 247 | (randomize_time_20 as i32).to_string(), 248 | )); 249 | base_order.algo_params.push(TagValue::new( 250 | "RandomizeSize55".to_string(), 251 | (randomize_size_55 as i32).to_string(), 252 | )); 253 | base_order 254 | .algo_params 255 | .push(TagValue::new("GiveUp".to_string(), give_up.to_string())); 256 | base_order.algo_params.push(TagValue::new( 257 | "CatchUp".to_string(), 258 | (catch_up as i32).to_string(), 259 | )); 260 | base_order.algo_params.push(TagValue::new( 261 | "WaitForFill".to_string(), 262 | (wait_for_fill as i32).to_string(), 263 | )); 264 | base_order.algo_params.push(TagValue::new( 265 | "activeTimeStart".to_string(), 266 | start_time.to_string(), 267 | )); 268 | base_order.algo_params.push(TagValue::new( 269 | "activeTimeEnd".to_string(), 270 | end_time.to_string(), 271 | )); 272 | } 273 | 274 | //================================================================================================== 275 | /// Balance impact risk parameters 276 | pub fn fill_balance_impact_risk_params( 277 | base_order: &mut Order, 278 | max_pct_vol: f64, 279 | risk_aversion: &str, 280 | force_completion: bool, 281 | ) { 282 | base_order.algo_strategy = "BalanceImpactRisk".to_string(); 283 | 284 | base_order.algo_params.push(TagValue::new( 285 | "maxPctVol".to_string(), 286 | max_pct_vol.to_string(), 287 | )); 288 | base_order.algo_params.push(TagValue::new( 289 | "riskAversion".to_string(), 290 | risk_aversion.to_string(), 291 | )); 292 | base_order.algo_params.push(TagValue::new( 293 | "forceCompletion".to_string(), 294 | force_completion.to_string(), 295 | )); 296 | } 297 | 298 | // ! [balanceimpactrisk_params] 299 | 300 | // ! [minimpact_params] 301 | //================================================================================================== 302 | /// Minimal impact parameters 303 | pub fn fill_min_impact_params(base_order: &mut Order, max_pct_vol: f64) { 304 | base_order.algo_strategy = "MinImpact".to_string(); 305 | 306 | base_order.algo_params.push(TagValue::new( 307 | "maxPctVol".to_string(), 308 | max_pct_vol.to_string(), 309 | )); 310 | } 311 | 312 | //================================================================================================== 313 | /// Adaptive priority parameters 314 | pub fn fill_adaptive_params(base_order: &mut Order, priority: &str) { 315 | base_order.algo_strategy = "Adaptive".to_string(); 316 | 317 | base_order.algo_params.push(TagValue::new( 318 | "adaptivePriority".to_string(), 319 | priority.to_string(), 320 | )); 321 | } 322 | 323 | //================================================================================================== 324 | /// Close price parameters 325 | pub fn fill_close_price_params( 326 | base_order: &mut Order, 327 | max_pct_vol: f64, 328 | risk_aversion: &str, 329 | start_time: &str, 330 | force_completion: bool, 331 | monetary_value: f64, 332 | ) { 333 | base_order.algo_strategy = "ClosePx".to_string(); 334 | 335 | base_order.algo_params.push(TagValue::new( 336 | "maxPctVol".to_string(), 337 | max_pct_vol.to_string(), 338 | )); 339 | base_order.algo_params.push(TagValue::new( 340 | "riskAversion".to_string(), 341 | risk_aversion.to_string(), 342 | )); 343 | base_order.algo_params.push(TagValue::new( 344 | "startTime".to_string(), 345 | start_time.to_string(), 346 | )); 347 | base_order.algo_params.push(TagValue::new( 348 | "forceCompletion".to_string(), 349 | (force_completion as i32).to_string(), 350 | )); 351 | base_order.algo_params.push(TagValue::new( 352 | "monetaryValue".to_string(), 353 | monetary_value.to_string(), 354 | )); 355 | } 356 | 357 | //================================================================================================== 358 | /// Price variant percent volume parameters 359 | pub fn fill_price_variant_pct_vol_params( 360 | base_order: &mut Order, 361 | pct_vol: f64, 362 | delta_pct_vol: f64, 363 | min_pct_vol_4px: f64, 364 | max_pct_vol_4px: f64, 365 | start_time: &str, 366 | end_time: &str, 367 | no_take_liq: bool, 368 | monetary_value: f64, 369 | ) { 370 | base_order.algo_strategy = "PctVolPx".to_string(); 371 | 372 | base_order 373 | .algo_params 374 | .push(TagValue::new("pctVol".to_string(), pct_vol.to_string())); 375 | base_order.algo_params.push(TagValue::new( 376 | "deltaPctVol".to_string(), 377 | delta_pct_vol.to_string(), 378 | )); 379 | base_order.algo_params.push(TagValue::new( 380 | "minPctVol4Px".to_string(), 381 | min_pct_vol_4px.to_string(), 382 | )); 383 | base_order.algo_params.push(TagValue::new( 384 | "maxPctVol4Px".to_string(), 385 | max_pct_vol_4px.to_string(), 386 | )); 387 | base_order.algo_params.push(TagValue::new( 388 | "startTime".to_string(), 389 | start_time.to_string(), 390 | )); 391 | base_order 392 | .algo_params 393 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 394 | base_order.algo_params.push(TagValue::new( 395 | "noTakeLiq".to_string(), 396 | (no_take_liq as i32).to_string(), 397 | )); 398 | base_order.algo_params.push(TagValue::new( 399 | "monetaryValue".to_string(), 400 | monetary_value.to_string(), 401 | )); 402 | } 403 | 404 | //================================================================================================== 405 | /// Size variant percent volume parameters 406 | pub fn fill_size_variant_pct_vol_params( 407 | base_order: &mut Order, 408 | start_pct_vol: f64, 409 | end_pct_vol: f64, 410 | start_time: &str, 411 | end_time: &str, 412 | no_take_liq: bool, 413 | monetary_value: f64, 414 | ) { 415 | base_order.algo_strategy = "PctVolSz".to_string(); 416 | 417 | base_order.algo_params.push(TagValue::new( 418 | "startPctVol".to_string(), 419 | start_pct_vol.to_string(), 420 | )); 421 | base_order.algo_params.push(TagValue::new( 422 | "endPctVol".to_string(), 423 | end_pct_vol.to_string(), 424 | )); 425 | base_order.algo_params.push(TagValue::new( 426 | "startTime".to_string(), 427 | start_time.to_string(), 428 | )); 429 | base_order 430 | .algo_params 431 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 432 | base_order.algo_params.push(TagValue::new( 433 | "noTakeLiq".to_string(), 434 | (no_take_liq as i32).to_string(), 435 | )); 436 | base_order.algo_params.push(TagValue::new( 437 | "monetaryValue".to_string(), 438 | monetary_value.to_string(), 439 | )); 440 | } 441 | 442 | //================================================================================================== 443 | /// Time variant percent volume parameters 444 | pub fn fill_time_variant_pct_vol_params( 445 | base_order: &mut Order, 446 | start_pct_vol: f64, 447 | end_pct_vol: f64, 448 | start_time: &str, 449 | end_time: &str, 450 | no_take_liq: bool, 451 | monetary_value: f64, 452 | ) { 453 | base_order.algo_strategy = "PctVolTm".to_string(); 454 | 455 | base_order.algo_params.push(TagValue::new( 456 | "startPctVol".to_string(), 457 | start_pct_vol.to_string(), 458 | )); 459 | base_order.algo_params.push(TagValue::new( 460 | "endPctVol".to_string(), 461 | end_pct_vol.to_string(), 462 | )); 463 | base_order.algo_params.push(TagValue::new( 464 | "startTime".to_string(), 465 | start_time.to_string(), 466 | )); 467 | base_order 468 | .algo_params 469 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 470 | base_order.algo_params.push(TagValue::new( 471 | "noTakeLiq".to_string(), 472 | (no_take_liq as i32).to_string(), 473 | )); 474 | base_order.algo_params.push(TagValue::new( 475 | "monetaryValue".to_string(), 476 | monetary_value.to_string(), 477 | )); 478 | } 479 | 480 | //================================================================================================== 481 | /// Jefferies Vwap parameters 482 | pub fn fill_jefferies_vwapparams( 483 | base_order: &mut Order, 484 | start_time: &str, 485 | end_time: &str, 486 | relative_limit: f64, 487 | max_volume_rate: f64, 488 | exclude_auctions: &str, 489 | trigger_price: f64, 490 | wow_price: f64, 491 | min_fill_size: i32, 492 | wow_order_pct: f64, 493 | wow_mode: &str, 494 | is_buy_back: bool, 495 | wow_reference: &str, 496 | ) { 497 | // must be direct-routed to "JEFFALGO" 498 | base_order.algo_strategy = "VWAP".to_string(); 499 | 500 | base_order.algo_params.push(TagValue::new( 501 | "startTime".to_string(), 502 | start_time.to_string(), 503 | )); 504 | base_order 505 | .algo_params 506 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 507 | base_order.algo_params.push(TagValue::new( 508 | "relativeLimit".to_string(), 509 | relative_limit.to_string(), 510 | )); 511 | base_order.algo_params.push(TagValue::new( 512 | "maxVolumeRate".to_string(), 513 | max_volume_rate.to_string(), 514 | )); 515 | base_order.algo_params.push(TagValue::new( 516 | "excludeAuctions".to_string(), 517 | exclude_auctions.to_string(), 518 | )); 519 | base_order.algo_params.push(TagValue::new( 520 | "triggerPrice".to_string(), 521 | trigger_price.to_string(), 522 | )); 523 | base_order 524 | .algo_params 525 | .push(TagValue::new("wowPrice".to_string(), wow_price.to_string())); 526 | base_order.algo_params.push(TagValue::new( 527 | "minFillSize".to_string(), 528 | min_fill_size.to_string(), 529 | )); 530 | base_order.algo_params.push(TagValue::new( 531 | "wowOrderPct".to_string(), 532 | wow_order_pct.to_string(), 533 | )); 534 | base_order 535 | .algo_params 536 | .push(TagValue::new("wowMode".to_string(), wow_mode.to_string())); 537 | base_order.algo_params.push(TagValue::new( 538 | "isBuyBack".to_string(), 539 | (is_buy_back as i32).to_string(), 540 | )); 541 | base_order.algo_params.push(TagValue::new( 542 | "wowReference".to_string(), 543 | wow_reference.to_string(), 544 | )); 545 | } 546 | 547 | //================================================================================================== 548 | /// CSFB inline parameters 549 | pub fn fill_csfbinline_params( 550 | base_order: &mut Order, 551 | start_time: &str, 552 | end_time: &str, 553 | exec_style: &str, 554 | min_percent: i32, 555 | max_percent: i32, 556 | display_size: i32, 557 | auction: &str, 558 | block_finder: bool, 559 | block_price: f64, 560 | min_block_size: i32, 561 | max_block_size: i32, 562 | i_would_price: f64, 563 | ) { 564 | // must be direct-routed to "CSFBALGO" 565 | base_order.algo_strategy = "INLINE".to_string(); 566 | 567 | base_order.algo_params.push(TagValue::new( 568 | "StartTime".to_string(), 569 | start_time.to_string(), 570 | )); 571 | base_order 572 | .algo_params 573 | .push(TagValue::new("EndTime".to_string(), end_time.to_string())); 574 | base_order.algo_params.push(TagValue::new( 575 | "ExecStyle".to_string(), 576 | exec_style.to_string(), 577 | )); 578 | base_order.algo_params.push(TagValue::new( 579 | "MinPercent".to_string(), 580 | min_percent.to_string(), 581 | )); 582 | base_order.algo_params.push(TagValue::new( 583 | "MaxPercent".to_string(), 584 | max_percent.to_string(), 585 | )); 586 | base_order.algo_params.push(TagValue::new( 587 | "DisplaySize".to_string(), 588 | display_size.to_string(), 589 | )); 590 | base_order 591 | .algo_params 592 | .push(TagValue::new("Auction".to_string(), auction.to_string())); 593 | base_order.algo_params.push(TagValue::new( 594 | "BlockFinder".to_string(), 595 | (block_finder as i32).to_string(), 596 | )); 597 | base_order.algo_params.push(TagValue::new( 598 | "BlockPrice".to_string(), 599 | block_price.to_string(), 600 | )); 601 | base_order.algo_params.push(TagValue::new( 602 | "MinBlockSize".to_string(), 603 | min_block_size.to_string(), 604 | )); 605 | base_order.algo_params.push(TagValue::new( 606 | "MaxBlockSize".to_string(), 607 | max_block_size.to_string(), 608 | )); 609 | base_order.algo_params.push(TagValue::new( 610 | "IWouldPrice".to_string(), 611 | i_would_price.to_string(), 612 | )); 613 | } 614 | 615 | //================================================================================================== 616 | /// QB algo parameters 617 | pub fn fill_qbalgo_in_line_params( 618 | base_order: &mut Order, 619 | start_time: &str, 620 | end_time: &str, 621 | _duration: f64, 622 | benchmark: &str, 623 | percent_volume: f64, 624 | no_clean_up: bool, 625 | ) { 626 | // must be direct-routed to "QBALGO" 627 | base_order.algo_strategy = "STROBE".to_string(); 628 | 629 | base_order.algo_params.push(TagValue::new( 630 | "startTime".to_string(), 631 | start_time.to_string(), 632 | )); 633 | base_order 634 | .algo_params 635 | .push(TagValue::new("endTime".to_string(), end_time.to_string())); 636 | //This example uses end_time instead of duration 637 | //base_order.algo_params.push(TagValue::new("Duration".to_string(), str(duration.to_string()) 638 | base_order.algo_params.push(TagValue::new( 639 | "benchmark".to_string(), 640 | benchmark.to_string(), 641 | )); 642 | base_order.algo_params.push(TagValue::new( 643 | "percentVolume".to_string(), 644 | percent_volume.to_string(), 645 | )); 646 | base_order.algo_params.push(TagValue::new( 647 | "noCleanUp".to_string(), 648 | (no_clean_up as i32).to_string(), 649 | )); 650 | } 651 | -------------------------------------------------------------------------------- /src/core/contract.rs: -------------------------------------------------------------------------------- 1 | //! Types related to Contracts 2 | use std::fmt::{Display, Error, Formatter}; 3 | 4 | use num_derive::FromPrimitive; 5 | use serde::{Deserialize, Serialize}; 6 | // 0.2.6 (the trait) 7 | 8 | use crate::core::common::TagValue; 9 | 10 | //================================================================================================== 11 | #[repr(i32)] 12 | #[derive(Serialize, Deserialize, Clone, Copy, FromPrimitive, Debug)] 13 | pub enum PositionType { 14 | SamePos = 0, 15 | //open/close leg value is same as combo 16 | OpenPos = 1, 17 | //open 18 | ClosePos = 2, 19 | //close 20 | UnknownPos = 3, //unknown 21 | } 22 | 23 | impl Display for PositionType { 24 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 25 | match *self { 26 | PositionType::SamePos => write!(f, "SamePos"), 27 | PositionType::OpenPos => write!(f, "OpenPos"), 28 | PositionType::ClosePos => write!(f, "ClosePos"), 29 | PositionType::UnknownPos => write!(f, "UnknownPos"), 30 | } 31 | } 32 | } 33 | 34 | impl Default for PositionType { 35 | fn default() -> Self { 36 | PositionType::SamePos 37 | } 38 | } 39 | 40 | //================================================================================================== 41 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 42 | pub struct ComboLeg { 43 | pub con_id: i32, 44 | pub ratio: f64, 45 | pub action: String, 46 | // BUY /SELL / SSHORT 47 | pub exchange: String, 48 | pub open_close: PositionType, 49 | // for stock legs when doing short sale 50 | pub short_sale_slot: i32, 51 | pub designated_location: String, 52 | pub exempt_code: i32, 53 | } 54 | 55 | impl ComboLeg { 56 | pub fn new( 57 | con_id: i32, 58 | ratio: f64, 59 | action: String, 60 | exchange: String, 61 | open_close: PositionType, 62 | short_sale_slot: i32, 63 | designated_location: String, 64 | exempt_code: i32, 65 | ) -> Self { 66 | ComboLeg { 67 | con_id, 68 | ratio, 69 | action, 70 | exchange, 71 | open_close, 72 | short_sale_slot, 73 | designated_location, 74 | exempt_code, 75 | } 76 | } 77 | } 78 | 79 | impl Display for ComboLeg { 80 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 81 | write!( 82 | f, 83 | "con_id: {}, 84 | ratio: {}, 85 | action: {}, 86 | exchange: {}, 87 | open_close: {}, 88 | short_sale_slot: {}, 89 | designated_location: {}, 90 | exempt_code: {}", 91 | self.con_id, 92 | self.ratio, 93 | self.action, 94 | self.exchange, 95 | self.open_close, 96 | self.short_sale_slot, 97 | self.designated_location, 98 | self.exempt_code 99 | ) 100 | } 101 | } 102 | 103 | //================================================================================================== 104 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 105 | pub struct DeltaNeutralContract { 106 | pub con_id: i32, 107 | pub delta: f64, 108 | pub price: f64, 109 | } 110 | 111 | impl DeltaNeutralContract { 112 | pub fn new(con_id: i32, delta: f64, price: f64) -> Self { 113 | DeltaNeutralContract { 114 | con_id, 115 | delta, 116 | price, 117 | } 118 | } 119 | } 120 | 121 | impl Display for DeltaNeutralContract { 122 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 123 | write!( 124 | f, 125 | "con_id: {}, delta: {}, price: {},", 126 | self.con_id, self.delta, self.price, 127 | ) 128 | } 129 | } 130 | 131 | //================================================================================================== 132 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 133 | pub struct Contract { 134 | pub con_id: i32, 135 | pub symbol: String, 136 | pub sec_type: String, 137 | pub last_trade_date_or_contract_month: String, 138 | pub strike: f64, 139 | pub right: String, 140 | pub multiplier: String, 141 | pub exchange: String, 142 | pub primary_exchange: String, 143 | // pick an actual (ie non - aggregate) exchange that the contract trades on.DO NOT SET TO SMART. 144 | pub currency: String, 145 | pub local_symbol: String, 146 | pub trading_class: String, 147 | pub include_expired: bool, 148 | pub sec_id_type: String, 149 | // CUSIP; SEDOL; ISIN;RIC 150 | pub sec_id: String, 151 | 152 | //combos 153 | pub combo_legs_descrip: String, 154 | // received in open order 14 and up for all combos 155 | pub combo_legs: Vec, 156 | pub delta_neutral_contract: Option, 157 | } 158 | 159 | impl Contract { 160 | pub fn new( 161 | con_id: i32, 162 | symbol: String, 163 | sec_type: String, 164 | last_trade_date_or_contract_month: String, 165 | strike: f64, 166 | right: String, 167 | multiplier: String, 168 | exchange: String, 169 | primary_exchange: String, 170 | currency: String, 171 | local_symbol: String, 172 | trading_class: String, 173 | include_expired: bool, 174 | sec_id_type: String, 175 | sec_id: String, 176 | combo_legs_descrip: String, 177 | combo_legs: Vec, 178 | delta_neutral_contract: Option, 179 | ) -> Self { 180 | Contract { 181 | con_id, 182 | symbol, 183 | sec_type, 184 | last_trade_date_or_contract_month, 185 | strike, 186 | right, 187 | multiplier, 188 | exchange, 189 | primary_exchange, 190 | currency, 191 | local_symbol, 192 | trading_class, 193 | include_expired, 194 | sec_id_type, 195 | sec_id, 196 | combo_legs_descrip, 197 | combo_legs, 198 | delta_neutral_contract, 199 | } 200 | } 201 | } 202 | 203 | impl Display for Contract { 204 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 205 | write!( 206 | f, 207 | "con_id: {}, 208 | symbol: {}, 209 | sec_type: {}, 210 | last_trade_date_or_contract_month: {}, 211 | strike: {}, 212 | right: {}, 213 | multiplier: {}, 214 | exchange: {}, 215 | primary_exchange: {}, 216 | currency: {}, 217 | local_symbol: {}, 218 | trading_class: {}, 219 | include_expired: {}, 220 | sec_id_type: {}, 221 | sec_id: {}, 222 | combo_legs_descrip: {}, 223 | combo_legs: [{}], 224 | delta_neutral_contract: [{:?}], 225 | ", 226 | self.con_id, 227 | self.symbol, 228 | self.sec_type, 229 | self.last_trade_date_or_contract_month, 230 | self.strike, 231 | self.right, 232 | self.multiplier, 233 | self.exchange, 234 | self.primary_exchange, 235 | self.currency, 236 | self.local_symbol, 237 | self.trading_class, 238 | self.include_expired, 239 | self.sec_id_type, 240 | self.sec_id, 241 | self.combo_legs_descrip, 242 | self.combo_legs 243 | .iter() 244 | .map(|x| { format!("{}", x.to_string()) }) 245 | .collect::>() 246 | .join(","), 247 | self.delta_neutral_contract 248 | ) 249 | } 250 | } 251 | 252 | //================================================================================================== 253 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 254 | pub struct ContractDetails { 255 | pub contract: Contract, 256 | pub market_name: String, 257 | pub min_tick: f64, 258 | pub order_types: String, 259 | pub valid_exchanges: String, 260 | pub price_magnifier: i32, 261 | pub under_con_id: i32, 262 | pub long_name: String, 263 | pub contract_month: String, 264 | pub industry: String, 265 | pub category: String, 266 | pub subcategory: String, 267 | pub time_zone_id: String, 268 | pub trading_hours: String, 269 | pub liquid_hours: String, 270 | pub ev_rule: String, 271 | pub ev_multiplier: f64, 272 | pub md_size_multiplier: i32, 273 | pub agg_group: i32, 274 | pub under_symbol: String, 275 | pub under_sec_type: String, 276 | pub market_rule_ids: String, 277 | pub sec_id_list: Vec, 278 | pub real_expiration_date: String, 279 | pub last_trade_time: String, 280 | 281 | // BOND values 282 | pub cusip: String, 283 | pub ratings: String, 284 | pub desc_append: String, 285 | pub bond_type: String, 286 | pub coupon_type: String, 287 | pub callable: bool, 288 | pub putable: bool, 289 | pub coupon: f64, 290 | pub convertible: bool, 291 | pub maturity: String, 292 | pub issue_date: String, 293 | pub next_option_date: String, 294 | pub next_option_type: String, 295 | pub next_option_partial: bool, 296 | pub notes: String, 297 | } 298 | 299 | impl ContractDetails { 300 | pub fn new( 301 | contract: Contract, 302 | market_name: String, 303 | min_tick: f64, 304 | order_types: String, 305 | valid_exchanges: String, 306 | price_magnifier: i32, 307 | under_con_id: i32, 308 | long_name: String, 309 | contract_month: String, 310 | industry: String, 311 | category: String, 312 | subcategory: String, 313 | time_zone_id: String, 314 | trading_hours: String, 315 | liquid_hours: String, 316 | ev_rule: String, 317 | ev_multiplier: f64, 318 | md_size_multiplier: i32, 319 | agg_group: i32, 320 | under_symbol: String, 321 | under_sec_type: String, 322 | market_rule_ids: String, 323 | sec_id_list: Vec, 324 | real_expiration_date: String, 325 | last_trade_time: String, 326 | cusip: String, 327 | ratings: String, 328 | desc_append: String, 329 | bond_type: String, 330 | coupon_type: String, 331 | callable: bool, 332 | putable: bool, 333 | coupon: f64, 334 | convertible: bool, 335 | maturity: String, 336 | issue_date: String, 337 | next_option_date: String, 338 | next_option_type: String, 339 | next_option_partial: bool, 340 | notes: String, 341 | ) -> Self { 342 | ContractDetails { 343 | contract, 344 | market_name, 345 | min_tick, 346 | order_types, 347 | valid_exchanges, 348 | price_magnifier, 349 | under_con_id, 350 | long_name, 351 | contract_month, 352 | industry, 353 | category, 354 | subcategory, 355 | time_zone_id, 356 | trading_hours, 357 | liquid_hours, 358 | ev_rule, 359 | ev_multiplier, 360 | md_size_multiplier, 361 | agg_group, 362 | under_symbol, 363 | under_sec_type, 364 | market_rule_ids, 365 | sec_id_list, 366 | real_expiration_date, 367 | last_trade_time, 368 | cusip, 369 | ratings, 370 | desc_append, 371 | bond_type, 372 | coupon_type, 373 | callable, 374 | putable, 375 | coupon, 376 | convertible, 377 | maturity, 378 | issue_date, 379 | next_option_date, 380 | next_option_type, 381 | next_option_partial, 382 | notes, 383 | } 384 | } 385 | } 386 | 387 | impl Display for ContractDetails { 388 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 389 | write!( 390 | f, 391 | "contract: {}, 392 | market_name: {}, 393 | min_tick: {}, 394 | order_types: {}, 395 | valid_exchanges: {}, 396 | price_magnifier: {}, 397 | under_con_id: {}, 398 | long_name: {}, 399 | contract_month: {}, 400 | industry: {}, 401 | category: {}, 402 | subcategory: {}, 403 | time_zone_id: {}, 404 | trading_hours: {}, 405 | liquid_hours: {}, 406 | ev_rule: {}, 407 | ev_multiplier: {}, 408 | md_size_multiplier: {}, 409 | agg_group: {}, 410 | under_symbol: {}, 411 | under_sec_type: {}, 412 | market_rule_ids: {}, 413 | sec_id_list: {}, 414 | real_expiration_date: {}, 415 | last_trade_time: {}, 416 | cusip: {}, 417 | ratings: {}, 418 | desc_append: {}, 419 | bond_type: {}, 420 | coupon_type: {}, 421 | callable: {}, 422 | putable: {}, 423 | coupon: {}, 424 | convertible: {}, 425 | maturity: {}, 426 | issue_date: {}, 427 | next_option_date: {}, 428 | next_option_type: {}, 429 | next_option_partial: {}, 430 | notes: {},", 431 | self.contract, 432 | self.market_name, 433 | self.min_tick, 434 | self.order_types, 435 | self.valid_exchanges, 436 | self.price_magnifier, 437 | self.under_con_id, 438 | self.long_name, 439 | self.contract_month, 440 | self.industry, 441 | self.category, 442 | self.subcategory, 443 | self.time_zone_id, 444 | self.trading_hours, 445 | self.liquid_hours, 446 | self.ev_rule, 447 | self.ev_multiplier, 448 | self.md_size_multiplier, 449 | self.agg_group, 450 | self.under_symbol, 451 | self.under_sec_type, 452 | self.market_rule_ids, 453 | self.sec_id_list 454 | .iter() 455 | .map(|x| { format!("{}", x.to_string()) }) 456 | .collect::>() 457 | .join(","), 458 | self.real_expiration_date, 459 | self.last_trade_time, 460 | self.cusip, 461 | self.ratings, 462 | self.desc_append, 463 | self.bond_type, 464 | self.coupon_type, 465 | self.callable, 466 | self.putable, 467 | self.coupon, 468 | self.convertible, 469 | self.maturity, 470 | self.issue_date, 471 | self.next_option_date, 472 | self.next_option_type, 473 | self.next_option_partial, 474 | self.notes 475 | ) 476 | } 477 | } 478 | 479 | //================================================================================================== 480 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 481 | pub struct ContractDescription { 482 | pub contract: Contract, 483 | pub derivative_sec_types: Vec, // type: list of strings 484 | } 485 | 486 | impl ContractDescription { 487 | pub fn new(contract: Contract, derivative_sec_types: Vec) -> Self { 488 | ContractDescription { 489 | contract, 490 | derivative_sec_types, 491 | } 492 | } 493 | } 494 | 495 | impl Display for ContractDescription { 496 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 497 | write!( 498 | f, 499 | "contract: {}, derivative_sec_types: ({})", 500 | self.contract, 501 | self.derivative_sec_types 502 | .iter() 503 | .map(|x| { x.to_owned() }) 504 | .collect::>() 505 | .join(",") 506 | ) 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /src/core/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types 2 | use std::num::{ParseFloatError, ParseIntError}; 3 | use std::sync::mpsc::{RecvError, RecvTimeoutError}; 4 | use std::{error, fmt, io}; 5 | 6 | const ALREADY_CONNECTED: (i32, &str) = (501, "Already connected."); 7 | const CONNECT_FAIL: (i32, &str) = (502, "Couldn't connect to TWS. Confirm that \"Enable ActiveX and Socket EClients\" 8 | is enabled and connection port is the same as \"Socket Port\" on the 9 | TWS \"Edit->Global Configuration...->API->Settings\" menu. Live Trading ports: 10 | TWS: 7496; IB Gateway: 4001. Simulated Trading ports for new installations 11 | of version 954.1 or newer: TWS: 7497; IB Gateway: 4002"); 12 | const UPDATE_TWS: (i32, &str) = (503, "The TWS is out of date and must be upgraded."); 13 | const NOT_CONNECTED: (i32, &str) = (504, "Not connected."); 14 | const UNKNOWN_ID: (i32, &str) = (505, "Fatal TwsError: Unknown message id."); 15 | const UNSUPPORTED: (i32, &str) = (506, "UNSUPPORTED version"); 16 | const BAD_LENGTH: (i32, &str) = (507, "Bad message length."); 17 | const BAD_MESSAGE: (i32, &str) = (508, "Bad message."); 18 | const SOCKET_EXCEPTION: (i32, &str) = (509, "Exception caught while reading socket."); 19 | const FAIL_CREATE_SOCK: (i32, &str) = (520, "Failed to create socket."); 20 | const SSL_FAIL: (i32, &str) = (530, "SSL specific TwsError."); 21 | 22 | #[derive(Clone, Debug)] 23 | pub enum TwsError { 24 | AlreadyConnected, 25 | ConnectFail, 26 | UpdateTws, 27 | NotConnected, 28 | UnknownId, 29 | Unsupported, 30 | BadLength, 31 | BadMessage, 32 | SocketException, 33 | FailCreateSock, 34 | SslFail, 35 | } 36 | 37 | impl TwsError { 38 | pub fn code(&self) -> i32 { 39 | match *self { 40 | TwsError::AlreadyConnected => ALREADY_CONNECTED.0, 41 | TwsError::ConnectFail => CONNECT_FAIL.0, 42 | TwsError::UpdateTws => UPDATE_TWS.0, 43 | TwsError::NotConnected => NOT_CONNECTED.0, 44 | TwsError::UnknownId => UNKNOWN_ID.0, 45 | TwsError::Unsupported => UNSUPPORTED.0, 46 | TwsError::BadLength => BAD_LENGTH.0, 47 | TwsError::BadMessage => BAD_MESSAGE.0, 48 | TwsError::SocketException => SOCKET_EXCEPTION.0, 49 | TwsError::FailCreateSock => FAIL_CREATE_SOCK.0, 50 | TwsError::SslFail => SSL_FAIL.0, 51 | } 52 | } 53 | pub fn message(&self) -> &'static str { 54 | match *self { 55 | TwsError::AlreadyConnected => ALREADY_CONNECTED.1, 56 | TwsError::ConnectFail => CONNECT_FAIL.1, 57 | TwsError::UpdateTws => UPDATE_TWS.1, 58 | TwsError::NotConnected => NOT_CONNECTED.1, 59 | TwsError::UnknownId => UNKNOWN_ID.1, 60 | TwsError::Unsupported => UNSUPPORTED.1, 61 | TwsError::BadLength => BAD_LENGTH.1, 62 | TwsError::BadMessage => BAD_MESSAGE.1, 63 | TwsError::SocketException => SOCKET_EXCEPTION.1, 64 | TwsError::FailCreateSock => FAIL_CREATE_SOCK.1, 65 | TwsError::SslFail => SSL_FAIL.1, 66 | } 67 | } 68 | } 69 | 70 | impl error::Error for TwsError {} 71 | 72 | impl fmt::Display for TwsError { 73 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 74 | write!(fmt, "Code: {}, Message: {}", self.code(), self.message()) 75 | } 76 | } 77 | 78 | // #[derive(Debug)] 79 | pub enum IBKRApiLibError { 80 | Io(io::Error), 81 | ParseFloat(ParseFloatError), 82 | ParseInt(ParseIntError), 83 | RecvError(RecvError), 84 | RecvTimeoutError(RecvTimeoutError), 85 | ApiError(TwsApiReportableError), 86 | } 87 | 88 | impl fmt::Display for IBKRApiLibError { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | match self { 91 | // Both underlying errors already impl `Display`, so we defer to 92 | // their implementations. 93 | IBKRApiLibError::Io(ref err) => write!(f, "IO error: {}", err), 94 | IBKRApiLibError::ParseFloat(ref err) => write!(f, "Parse error: {}", err), 95 | IBKRApiLibError::ParseInt(ref err) => write!(f, "Parse error: {}", err), 96 | IBKRApiLibError::RecvError(ref err) => write!(f, "Recieve error: {}", err), 97 | IBKRApiLibError::RecvTimeoutError(ref err) => write!(f, "Reader Send error {}", err), 98 | IBKRApiLibError::ApiError(ref err) => write!(f, "TWS Error: {}", err), 99 | } 100 | } 101 | } 102 | 103 | impl fmt::Debug for IBKRApiLibError { 104 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 105 | match self { 106 | // Both underlying errors already impl `Display`, so we defer to 107 | // their implementations. 108 | IBKRApiLibError::Io(ref err) => write!(f, "IO error: {}", err), 109 | IBKRApiLibError::ParseFloat(ref err) => write!(f, "Parse error: {}", err), 110 | IBKRApiLibError::ParseInt(ref err) => write!(f, "Parse error: {}", err), 111 | IBKRApiLibError::RecvError(ref err) => write!(f, "Recieve error: {}", err), 112 | IBKRApiLibError::RecvTimeoutError(ref err) => write!(f, "Reader Send error {}", err), 113 | IBKRApiLibError::ApiError(ref err) => write!(f, "TWS Error: {}", err), 114 | } 115 | } 116 | } 117 | 118 | impl error::Error for IBKRApiLibError { 119 | fn cause(&self) -> Option<&dyn error::Error> { 120 | match self { 121 | // N.B. Both of these implicitly cast `err` from their concrete 122 | // types (either `&io::Error` or `&num::ParseIntError`) 123 | // to a trait object `&Error`. This works because both error types 124 | // implement `Error`. 125 | IBKRApiLibError::Io(ref err) => Some(err), 126 | IBKRApiLibError::ParseFloat(ref err) => Some(err), 127 | IBKRApiLibError::ParseInt(ref err) => Some(err), 128 | IBKRApiLibError::RecvError(ref err) => Some(err), 129 | IBKRApiLibError::RecvTimeoutError(ref err) => Some(err), 130 | IBKRApiLibError::ApiError(ref err) => Some(err), 131 | } 132 | } 133 | } 134 | 135 | impl From for IBKRApiLibError { 136 | fn from(err: io::Error) -> IBKRApiLibError { 137 | IBKRApiLibError::Io(err) 138 | } 139 | } 140 | 141 | impl From for IBKRApiLibError { 142 | fn from(err: ParseIntError) -> IBKRApiLibError { 143 | IBKRApiLibError::ParseInt(err) 144 | } 145 | } 146 | 147 | impl From for IBKRApiLibError { 148 | fn from(err: ParseFloatError) -> IBKRApiLibError { 149 | IBKRApiLibError::ParseFloat(err) 150 | } 151 | } 152 | 153 | impl From for IBKRApiLibError { 154 | fn from(err: RecvError) -> IBKRApiLibError { 155 | IBKRApiLibError::RecvError(err) 156 | } 157 | } 158 | 159 | impl From for IBKRApiLibError { 160 | fn from(err: RecvTimeoutError) -> IBKRApiLibError { 161 | IBKRApiLibError::RecvTimeoutError(err) 162 | } 163 | } 164 | 165 | impl From for IBKRApiLibError { 166 | fn from(err: TwsApiReportableError) -> IBKRApiLibError { 167 | IBKRApiLibError::ApiError(err) 168 | } 169 | } 170 | 171 | #[derive(Clone, Debug)] 172 | pub struct TwsApiReportableError { 173 | pub req_id: i32, 174 | pub code: String, 175 | pub description: String, 176 | } 177 | 178 | impl TwsApiReportableError { 179 | pub fn new(req_id: i32, code: String, description: String) -> Self { 180 | Self { 181 | req_id: req_id, 182 | code: code, 183 | description: description, 184 | } 185 | } 186 | } 187 | 188 | impl fmt::Display for TwsApiReportableError { 189 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 190 | write!( 191 | f, 192 | "TWS Error: req_id = {}. code = {}. description = {}", 193 | self.req_id, self.code, self.description 194 | ) 195 | } 196 | } 197 | 198 | impl error::Error for TwsApiReportableError {} 199 | -------------------------------------------------------------------------------- /src/core/execution.rs: -------------------------------------------------------------------------------- 1 | //! Types related to executions 2 | use std::fmt::{Display, Error, Formatter}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | //================================================================================================== 7 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 8 | pub struct Execution { 9 | pub exec_id: String, 10 | pub time: String, 11 | pub acct_number: String, 12 | pub exchange: String, 13 | pub side: String, 14 | pub shares: f64, 15 | pub price: f64, 16 | pub perm_id: i32, 17 | pub client_id: i32, 18 | pub order_id: i32, 19 | pub liquidation: i32, 20 | pub cum_qty: f64, 21 | pub avg_price: f64, 22 | pub order_ref: String, 23 | pub ev_rule: String, 24 | pub ev_multiplier: f64, 25 | pub model_code: String, 26 | pub last_liquidity: i32, 27 | } 28 | 29 | impl Execution { 30 | pub fn new( 31 | exec_id: String, 32 | time: String, 33 | acct_number: String, 34 | exchange: String, 35 | side: String, 36 | shares: f64, 37 | price: f64, 38 | perm_id: i32, 39 | client_id: i32, 40 | order_id: i32, 41 | liquidation: i32, 42 | cum_qty: f64, 43 | avg_price: f64, 44 | order_ref: String, 45 | ev_rule: String, 46 | ev_multiplier: f64, 47 | model_code: String, 48 | last_liquidity: i32, 49 | ) -> Self { 50 | Execution { 51 | exec_id, 52 | time, 53 | acct_number, 54 | exchange, 55 | side, 56 | shares, 57 | price, 58 | perm_id, 59 | client_id, 60 | order_id, 61 | liquidation, 62 | cum_qty, 63 | avg_price, 64 | order_ref, 65 | ev_rule, 66 | ev_multiplier, 67 | model_code, 68 | last_liquidity, 69 | } 70 | } 71 | } 72 | 73 | impl Display for Execution { 74 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 75 | write!( 76 | f, 77 | "exec_id: : {}, 78 | time: : {}, 79 | acct_number: : {}, 80 | exchange: : {}, 81 | side: : {}, 82 | shares: : {}, 83 | price: : {}, 84 | perm_id: : {}, 85 | client_id: : {}, 86 | order_id: : {}, 87 | liquidation: : {}, 88 | cum_qty: : {}, 89 | avg_price: : {}, 90 | order_ref: : {}, 91 | ev_rule: : {}, 92 | ev_multiplier: : {}, 93 | model_code: : {}, 94 | last_liquidity: : {} ", 95 | self.exec_id, 96 | self.time, 97 | self.acct_number, 98 | self.exchange, 99 | self.side, 100 | self.shares, 101 | self.price, 102 | self.perm_id, 103 | self.client_id, 104 | self.order_id, 105 | self.liquidation, 106 | self.cum_qty, 107 | self.avg_price, 108 | self.order_ref, 109 | self.ev_rule, 110 | self.ev_multiplier, 111 | self.model_code, 112 | self.last_liquidity, 113 | ) 114 | } 115 | } 116 | 117 | //================================================================================================== 118 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 119 | pub struct ExecutionFilter { 120 | pub client_id: i32, 121 | pub acct_code: String, 122 | pub time: String, 123 | pub symbol: String, 124 | pub sec_type: String, 125 | pub exchange: String, 126 | pub side: String, 127 | } 128 | 129 | impl ExecutionFilter { 130 | pub fn new( 131 | client_id: i32, 132 | acct_code: String, 133 | time: String, 134 | symbol: String, 135 | sec_type: String, 136 | exchange: String, 137 | side: String, 138 | ) -> Self { 139 | ExecutionFilter { 140 | client_id, 141 | acct_code, 142 | time, 143 | symbol, 144 | sec_type, 145 | exchange, 146 | side, 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/core/messages.rs: -------------------------------------------------------------------------------- 1 | //! Functions for processing messages 2 | use std::any::Any; 3 | use std::convert::TryInto; 4 | use std::io::Write; 5 | use std::string::String; 6 | 7 | use std::vec::Vec; 8 | 9 | use ascii; 10 | use ascii::AsAsciiStr; 11 | 12 | use log::*; 13 | use num_derive::FromPrimitive; 14 | 15 | use crate::core::common::{UNSET_DOUBLE, UNSET_INTEGER}; 16 | use crate::core::errors::IBKRApiLibError; 17 | 18 | //================================================================================================== 19 | trait EClientMsgSink { 20 | fn server_version(version: i32, time: &str); 21 | fn redirect(host: &str); 22 | } 23 | 24 | //================================================================================================== 25 | /// FA msg data types 26 | pub enum FAMessageDataTypes { 27 | Groups = 1, 28 | Profiles = 2, 29 | Aliases = 3, 30 | } 31 | 32 | //================================================================================================== 33 | /// incoming msg id's 34 | #[derive(FromPrimitive)] 35 | #[repr(i32)] 36 | pub enum IncomingMessageIds { 37 | TickPrice = 1, 38 | TickSize = 2, 39 | OrderStatus = 3, 40 | ErrMsg = 4, 41 | OpenOrder = 5, 42 | AcctValue = 6, 43 | PortfolioValue = 7, 44 | AcctUpdateTime = 8, 45 | NextValidId = 9, 46 | ContractData = 10, 47 | ExecutionData = 11, 48 | MarketDepth = 12, 49 | MarketDepthL2 = 13, 50 | NewsBulletins = 14, 51 | ManagedAccts = 15, 52 | ReceiveFa = 16, 53 | HistoricalData = 17, 54 | BondContractData = 18, 55 | ScannerParameters = 19, 56 | ScannerData = 20, 57 | TickOptionComputation = 21, 58 | TickGeneric = 45, 59 | TickString = 46, 60 | TickEfp = 47, 61 | CurrentTime = 49, 62 | RealTimeBars = 50, 63 | FundamentalData = 51, 64 | ContractDataEnd = 52, 65 | OpenOrderEnd = 53, 66 | AcctDownloadEnd = 54, 67 | ExecutionDataEnd = 55, 68 | DeltaNeutralValidation = 56, 69 | TickSnapshotEnd = 57, 70 | MarketDataType = 58, 71 | CommissionReport = 59, 72 | PositionData = 61, 73 | PositionEnd = 62, 74 | AccountSummary = 63, 75 | AccountSummaryEnd = 64, 76 | VerifyMessageApi = 65, 77 | VerifyCompleted = 66, 78 | DisplayGroupList = 67, 79 | DisplayGroupUpdated = 68, 80 | VerifyAndAuthMessageApi = 69, 81 | VerifyAndAuthCompleted = 70, 82 | PositionMulti = 71, 83 | PositionMultiEnd = 72, 84 | AccountUpdateMulti = 73, 85 | AccountUpdateMultiEnd = 74, 86 | SecurityDefinitionOptionParameter = 75, 87 | SecurityDefinitionOptionParameterEnd = 76, 88 | SoftDollarTiers = 77, 89 | FamilyCodes = 78, 90 | SymbolSamples = 79, 91 | MktDepthExchanges = 80, 92 | TickReqParams = 81, 93 | SmartComponents = 82, 94 | NewsArticle = 83, 95 | TickNews = 84, 96 | NewsProviders = 85, 97 | HistoricalNews = 86, 98 | HistoricalNewsEnd = 87, 99 | HeadTimestamp = 88, 100 | HistogramData = 89, 101 | HistoricalDataUpdate = 90, 102 | RerouteMktDataReq = 91, 103 | RerouteMktDepthReq = 92, 104 | MarketRule = 93, 105 | Pnl = 94, 106 | PnlSingle = 95, 107 | HistoricalTicks = 96, 108 | HistoricalTicksBidAsk = 97, 109 | HistoricalTicksLast = 98, 110 | TickByTick = 99, 111 | OrderBound = 100, 112 | CompletedOrder = 101, 113 | CompletedOrdersEnd = 102, 114 | } 115 | 116 | //================================================================================================== 117 | /// Outgoing msg id's 118 | #[derive(FromPrimitive)] 119 | #[repr(i32)] 120 | pub enum OutgoingMessageIds { 121 | ReqMktData = 1, 122 | CancelMktData = 2, 123 | PlaceOrder = 3, 124 | CancelOrder = 4, 125 | ReqOpenOrders = 5, 126 | ReqAcctData = 6, 127 | ReqExecutions = 7, 128 | ReqIds = 8, 129 | ReqContractData = 9, 130 | ReqMktDepth = 10, 131 | CancelMktDepth = 11, 132 | ReqNewsBulletins = 12, 133 | CancelNewsBulletins = 13, 134 | SetServerLoglevel = 14, 135 | ReqAutoOpenOrders = 15, 136 | ReqAllOpenOrders = 16, 137 | ReqManagedAccts = 17, 138 | ReqFa = 18, 139 | ReplaceFa = 19, 140 | ReqHistoricalData = 20, 141 | ExerciseOptions = 21, 142 | ReqScannerSubscription = 22, 143 | CancelScannerSubscription = 23, 144 | ReqScannerParameters = 24, 145 | CancelHistoricalData = 25, 146 | ReqCurrentTime = 49, 147 | ReqRealTimeBars = 50, 148 | CancelRealTimeBars = 51, 149 | ReqFundamentalData = 52, 150 | CancelFundamentalData = 53, 151 | ReqCalcImpliedVolat = 54, 152 | ReqCalcOptionPrice = 55, 153 | CancelCalcImpliedVolat = 56, 154 | CancelCalcOptionPrice = 57, 155 | ReqGlobalCancel = 58, 156 | ReqMarketDataType = 59, 157 | ReqPositions = 61, 158 | ReqAccountSummary = 62, 159 | CancelAccountSummary = 63, 160 | CancelPositions = 64, 161 | VerifyRequest = 65, 162 | VerifyMessage = 66, 163 | QueryDisplayGroups = 67, 164 | SubscribeToGroupEvents = 68, 165 | UpdateDisplayGroup = 69, 166 | UnsubscribeFromGroupEvents = 70, 167 | StartApi = 71, 168 | VerifyAndAuthRequest = 72, 169 | VerifyAndAuthMessage = 73, 170 | ReqPositionsMulti = 74, 171 | CancelPositionsMulti = 75, 172 | ReqAccountUpdatesMulti = 76, 173 | CancelAccountUpdatesMulti = 77, 174 | ReqSecDefOptParams = 78, 175 | ReqSoftDollarTiers = 79, 176 | ReqFamilyCodes = 80, 177 | ReqMatchingSymbols = 81, 178 | ReqMktDepthExchanges = 82, 179 | ReqSmartComponents = 83, 180 | ReqNewsArticle = 84, 181 | ReqNewsProviders = 85, 182 | ReqHistoricalNews = 86, 183 | ReqHeadTimestamp = 87, 184 | ReqHistogramData = 88, 185 | CancelHistogramData = 89, 186 | CancelHeadTimestamp = 90, 187 | ReqMarketRule = 91, 188 | ReqPnl = 92, 189 | CancelPnl = 93, 190 | ReqPnlSingle = 94, 191 | CancelPnlSingle = 95, 192 | ReqHistoricalTicks = 96, 193 | ReqTickByTickData = 97, 194 | CancelTickByTickData = 98, 195 | ReqCompletedOrders = 99, 196 | } 197 | 198 | //================================================================================================== 199 | pub fn make_message(msg: &str) -> Result, IBKRApiLibError> { 200 | //let mut buffer = ByteBuffer::new(); 201 | let mut buffer: Vec = Vec::new(); 202 | 203 | buffer.extend_from_slice(&i32::to_be_bytes(msg.len() as i32)); 204 | 205 | buffer.write(msg.as_ascii_str().unwrap().as_bytes())?; 206 | let tmp = buffer.clone(); 207 | //debug!("Message after create: {:?}", buffer); 208 | 209 | let (_size, _msg, _buf) = read_msg(tmp.as_slice())?; 210 | //debug!("Message read: size:{}, msg:{}, bytes: {:?}", size, msg, buf); 211 | 212 | Ok(tmp) 213 | } 214 | 215 | //================================================================================================== 216 | pub fn read_msg<'a>(buf: &[u8]) -> Result<(usize, String, Vec), IBKRApiLibError> { 217 | // first the size prefix and then the corresponding msg payload "" 218 | 219 | if buf.len() < 4 { 220 | debug!("read_msg: buffer too small!! {:?}", buf.len()); 221 | return Ok((0, String::new(), buf.to_vec())); 222 | } 223 | 224 | let size = i32::from_be_bytes(buf[0..4].try_into().unwrap()) as usize; 225 | //debug!("read_msg: Message size: {:?}", size); 226 | 227 | if buf.len() - 4 >= size { 228 | let text = String::from_utf8(buf[4..4 + size].to_vec()).unwrap(); 229 | //debug!("read_msg: text in read message: {:?}", text); 230 | Ok((size, text, buf[4 + size..].to_vec())) 231 | } else { 232 | Ok((size, String::new(), buf.to_vec())) 233 | } 234 | } 235 | 236 | //================================================================================================== 237 | pub fn read_fields(buf: &str) -> Vec { 238 | //msg payload is made of fields terminated/separated by NULL chars 239 | let a = '\u{0}'; 240 | let mut fields: Vec<&str> = buf.split(a).collect::>(); 241 | //debug!("fields.len() in read_fields: {}", fields.len()); 242 | //last one is empty 243 | fields.remove(fields.len() - 1); 244 | 245 | fields 246 | .iter() 247 | .map(|x| String::from(*x)) 248 | .collect::>() 249 | } 250 | 251 | //================================================================================================== 252 | pub fn make_field(val: &dyn Any) -> Result { 253 | // debug!("CALLING make_field!!"); 254 | // adds the NULL string terminator 255 | let mut field = "\0".to_string(); 256 | // bool type is encoded as int 257 | if let Some(boolval) = val.downcast_ref::() { 258 | field = format!("{}\0", *boolval as i32); 259 | } else if let Some(stringval) = val.downcast_ref::() { 260 | field = format!("{}\0", *stringval as i32); 261 | } else if let Some(stringval) = val.downcast_ref::() { 262 | if UNSET_DOUBLE == *stringval { 263 | field = format!("{}\0", ""); 264 | } else { 265 | field = format!("{}\0", *stringval as f64); 266 | } 267 | } else if let Some(stringval) = val.downcast_ref::() { 268 | if UNSET_INTEGER == *stringval { 269 | field = format!("{}\0", ""); 270 | } else { 271 | field = format!("{}\0", *stringval as i32); 272 | } 273 | } else if let Some(stringval) = val.downcast_ref::() { 274 | field = format!("{}\0", stringval); 275 | } else if let Some(stringval) = val.downcast_ref::<&str>() { 276 | field = format!("{}\0", stringval); 277 | } 278 | 279 | Ok(field) 280 | } 281 | 282 | //================================================================================================== 283 | pub fn make_field_handle_empty(val: &dyn Any) -> Result { 284 | if let Some(stringval) = val.downcast_ref::() { 285 | if UNSET_DOUBLE == *stringval { 286 | return make_field(&""); 287 | } 288 | } else if let Some(stringval) = val.downcast_ref::() { 289 | if UNSET_INTEGER == *stringval { 290 | return make_field(&""); 291 | } 292 | } 293 | 294 | make_field(val) 295 | } 296 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | //! Core structs, enums, and functions 2 | pub mod account_summary_tags; 3 | pub mod algo_params; 4 | pub mod client; 5 | pub mod common; 6 | pub mod contract; 7 | pub mod decoder; 8 | pub mod errors; 9 | pub mod execution; 10 | pub mod messages; 11 | pub mod order; 12 | pub mod order_condition; 13 | pub mod order_decoder; 14 | pub mod reader; 15 | pub mod scanner; 16 | pub mod server_versions; 17 | pub mod streamer; 18 | pub mod wrapper; 19 | -------------------------------------------------------------------------------- /src/core/order.rs: -------------------------------------------------------------------------------- 1 | //! Types related to orders 2 | use std::fmt::{Display, Error, Formatter}; 3 | 4 | use num_derive::FromPrimitive; 5 | 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::core::common::{TagValue, UNSET_DOUBLE, UNSET_INTEGER}; 9 | use crate::core::order::AuctionStrategy::AuctionUnset; 10 | use crate::core::order::Origin::Customer; 11 | use crate::core::order_condition::{Condition, OrderConditionEnum}; 12 | 13 | //================================================================================================== 14 | #[repr(i32)] 15 | #[derive(Serialize, Deserialize, Clone, Debug, FromPrimitive, Copy)] 16 | pub enum Origin { 17 | Customer = 0, 18 | Firm = 1, 19 | Unknown = 2, 20 | } 21 | 22 | impl Default for Origin { 23 | fn default() -> Self { 24 | Origin::Unknown 25 | } 26 | } 27 | 28 | // enum AuctionStrategy 29 | //================================================================================================== 30 | #[repr(i32)] 31 | #[derive(Serialize, Deserialize, Clone, Debug, FromPrimitive, Copy)] 32 | pub enum AuctionStrategy { 33 | AuctionUnset = 0, 34 | AuctionMatch = 1, 35 | AuctionImprovement = 2, 36 | AuctionTransparent = 3, 37 | } 38 | 39 | impl Default for AuctionStrategy { 40 | fn default() -> Self { 41 | AuctionStrategy::AuctionUnset 42 | } 43 | } 44 | 45 | //================================================================================================== 46 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 47 | pub struct SoftDollarTier { 48 | pub name: String, 49 | pub val: String, 50 | pub display_name: String, 51 | } 52 | 53 | impl SoftDollarTier { 54 | pub fn new(name: String, val: String, display_name: String) -> Self { 55 | SoftDollarTier { 56 | name, 57 | val, 58 | display_name, 59 | } 60 | } 61 | } 62 | 63 | impl Display for SoftDollarTier { 64 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 65 | write!( 66 | f, 67 | "name: {}, value: {}, display_name: {}", 68 | self.name, self.val, self.display_name 69 | ) 70 | } 71 | } 72 | 73 | //================================================================================================== 74 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 75 | pub struct OrderState { 76 | pub status: String, 77 | pub init_margin_before: String, 78 | pub maint_margin_before: String, 79 | pub equity_with_loan_before: String, 80 | pub init_margin_change: String, 81 | pub maint_margin_change: String, 82 | pub equity_with_loan_change: String, 83 | pub init_margin_after: String, 84 | pub maint_margin_after: String, 85 | pub equity_with_loan_after: String, 86 | pub commission: f64, 87 | pub min_commission: f64, 88 | pub max_commission: f64, 89 | pub commission_currency: String, 90 | pub warning_text: String, 91 | pub completed_time: String, 92 | pub completed_status: String, 93 | } 94 | 95 | impl OrderState { 96 | pub fn new( 97 | status: String, 98 | init_margin_before: String, 99 | maint_margin_before: String, 100 | equity_with_loan_before: String, 101 | init_margin_change: String, 102 | maint_margin_change: String, 103 | equity_with_loan_change: String, 104 | init_margin_after: String, 105 | maint_margin_after: String, 106 | equity_with_loan_after: String, 107 | commission: f64, 108 | min_commission: f64, 109 | max_commission: f64, 110 | commission_currency: String, 111 | warning_text: String, 112 | completed_time: String, 113 | completed_status: String, 114 | ) -> Self { 115 | OrderState { 116 | status, 117 | init_margin_before, 118 | maint_margin_before, 119 | equity_with_loan_before, 120 | init_margin_change, 121 | maint_margin_change, 122 | equity_with_loan_change, 123 | init_margin_after, 124 | maint_margin_after, 125 | equity_with_loan_after, 126 | commission, 127 | min_commission, 128 | max_commission, 129 | commission_currency, 130 | warning_text, 131 | completed_time, 132 | completed_status, 133 | } 134 | } 135 | } 136 | 137 | impl Display for OrderState { 138 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 139 | write!( 140 | f, 141 | "status: {}, 142 | init_margin_before: {}, 143 | maint_margin_before: {}, 144 | equity_with_loan_before: {}, 145 | init_margin_change: {}, 146 | maint_margin_change: {}, 147 | equity_with_loan_change: {}, 148 | init_margin_after: {}, 149 | maint_margin_after: {}, 150 | equity_with_loan_after: {}, 151 | commission: {}, 152 | min_commission: {}, 153 | max_commission: {}, 154 | commission_currency: {}, 155 | warning_text: {}, 156 | completed_time: {}, 157 | completed_status: {},\n", 158 | self.status, 159 | self.init_margin_before, 160 | self.maint_margin_before, 161 | self.equity_with_loan_before, 162 | self.init_margin_change, 163 | self.maint_margin_change, 164 | self.equity_with_loan_change, 165 | self.init_margin_after, 166 | self.maint_margin_after, 167 | self.equity_with_loan_after, 168 | if self.commission == UNSET_DOUBLE { 169 | format!("{:E}", self.commission) 170 | } else { 171 | format!("{:?}", self.commission) 172 | }, 173 | if self.min_commission == UNSET_DOUBLE { 174 | format!("{:E}", self.min_commission) 175 | } else { 176 | format!("{:?}", self.min_commission) 177 | }, 178 | if self.max_commission == UNSET_DOUBLE { 179 | format!("{:E}", self.max_commission) 180 | } else { 181 | format!("{:?}", self.max_commission) 182 | }, 183 | self.commission_currency, 184 | self.warning_text, 185 | self.completed_time, 186 | self.completed_status, 187 | ) 188 | } 189 | } 190 | 191 | //================================================================================================== 192 | #[derive(Serialize, Deserialize, Clone, Debug, Default)] 193 | pub struct OrderComboLeg { 194 | pub(crate) price: f64, // type: float 195 | } 196 | 197 | impl OrderComboLeg { 198 | pub fn new(price: f64) -> Self { 199 | OrderComboLeg { price } 200 | } 201 | } 202 | 203 | impl Display for OrderComboLeg { 204 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 205 | write!(f, "{}", self.price) 206 | } 207 | } 208 | 209 | //================================================================================================== 210 | #[derive(Serialize, Deserialize, Clone, Debug)] 211 | pub struct Order { 212 | pub soft_dollar_tier: SoftDollarTier, 213 | // order identifier 214 | pub order_id: i32, 215 | pub client_id: i32, 216 | pub perm_id: i32, 217 | 218 | // main order fields 219 | pub action: String, 220 | pub total_quantity: f64, 221 | pub order_type: String, 222 | pub lmt_price: f64, 223 | pub aux_price: f64, 224 | 225 | // extended order fields 226 | pub tif: String, 227 | // "Time in Force" - DAY, GTC, etc. 228 | pub active_start_time: String, 229 | // for GTC orders 230 | pub active_stop_time: String, 231 | // for GTC orders 232 | pub oca_group: String, 233 | // one cancels all group name 234 | pub oca_type: i32, 235 | // 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK 236 | pub order_ref: String, 237 | pub transmit: bool, 238 | // if false, order will be created but not transmited 239 | pub parent_id: i32, 240 | // Parent order Id, to associate Auto STP or TRAIL orders with the original order. 241 | pub block_order: bool, 242 | pub sweep_to_fill: bool, 243 | pub display_size: i32, 244 | pub trigger_method: i32, 245 | // 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last, 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point 246 | pub outside_rth: bool, 247 | pub hidden: bool, 248 | pub good_after_time: String, 249 | // Format: 20060505 08:00:00 {time zone} 250 | pub good_till_date: String, 251 | // Format: 20060505 08:00:00 {time zone} 252 | pub rule80a: String, 253 | // Individual = 'I', Agency = 'A', AgentOtherMember = 'W', IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M', IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N' 254 | pub all_or_none: bool, 255 | pub min_qty: i32, 256 | //type: int 257 | pub percent_offset: f64, 258 | // type: float; REL orders only 259 | pub override_percentage_constraints: bool, 260 | pub trail_stop_price: f64, 261 | // type: float 262 | pub trailing_percent: f64, // type: float; TRAILLIMIT orders only 263 | 264 | // financial advisors only 265 | pub fa_group: String, 266 | pub fa_profile: String, 267 | pub fa_method: String, 268 | pub fa_percentage: String, 269 | 270 | // institutional (ie non-cleared) only 271 | pub designated_location: String, 272 | //used only when short_sale_slot=2 273 | pub open_close: String, 274 | // O=Open, C=Close 275 | pub origin: Origin, 276 | // 0=Customer, 1=Firm 277 | pub short_sale_slot: i32, 278 | // type: int; 1 if you hold the shares, 2 if they will be delivered from elsewhere. Only for Action=SSHORT 279 | pub exempt_code: i32, 280 | 281 | // SMART routing only 282 | pub discretionary_amt: f64, 283 | pub e_trade_only: bool, 284 | pub firm_quote_only: bool, 285 | pub nbbo_price_cap: f64, 286 | // type: float 287 | pub opt_out_smart_routing: bool, 288 | 289 | // BOX exchange orders only 290 | pub auction_strategy: AuctionStrategy, 291 | // type: int; AuctionMatch, AuctionImprovement, AuctionTransparent 292 | pub starting_price: f64, 293 | // type: float 294 | pub stock_ref_price: f64, 295 | // type: float 296 | pub delta: f64, // type: float 297 | 298 | // pegged to stock and VOL orders only 299 | pub stock_range_lower: f64, 300 | // type: float 301 | pub stock_range_upper: f64, // type: float 302 | 303 | pub randomize_price: bool, 304 | pub randomize_size: bool, 305 | 306 | // VOLATILITY ORDERS ONLY 307 | pub volatility: f64, 308 | // type: float 309 | pub volatility_type: i32, 310 | // type: int // 1=daily, 2=annual 311 | pub delta_neutral_order_type: String, 312 | pub delta_neutral_aux_price: f64, 313 | // type: float 314 | pub delta_neutral_con_id: i32, 315 | pub delta_neutral_settling_firm: String, 316 | pub delta_neutral_clearing_account: String, 317 | pub delta_neutral_clearing_intent: String, 318 | pub delta_neutral_open_close: String, 319 | pub delta_neutral_short_sale: bool, 320 | pub delta_neutral_short_sale_slot: i32, 321 | pub delta_neutral_designated_location: String, 322 | pub continuous_update: bool, 323 | pub reference_price_type: i32, // type: int; 1=Average, 2 = BidOrAsk 324 | 325 | // COMBO ORDERS ONLY 326 | pub basis_points: f64, 327 | // type: float; EFP orders only 328 | pub basis_points_type: i32, // type: int; EFP orders only 329 | 330 | // SCALE ORDERS ONLY 331 | pub scale_init_level_size: i32, 332 | // type: int 333 | pub scale_subs_level_size: i32, 334 | // type: int 335 | pub scale_price_increment: f64, 336 | // type: float 337 | pub scale_price_adjust_value: f64, 338 | // type: float 339 | pub scale_price_adjust_interval: i32, 340 | // type: int 341 | pub scale_profit_offset: f64, 342 | // type: float 343 | pub scale_auto_reset: bool, 344 | pub scale_init_position: i32, 345 | // type: int 346 | pub scale_init_fill_qty: i32, 347 | // type: int 348 | pub scale_random_percent: bool, 349 | pub scale_table: String, 350 | 351 | // HEDGE ORDERS 352 | pub hedge_type: String, 353 | // 'D' - delta, 'B' - beta, 'F' - FX, 'P' - pair 354 | pub hedge_param: String, // 'beta=X' value for beta hedge, 'ratio=Y' for pair hedge 355 | 356 | // Clearing info 357 | pub account: String, 358 | // IB account 359 | pub settling_firm: String, 360 | pub clearing_account: String, 361 | //True beneficiary of the order 362 | pub clearing_intent: String, // "" (Default), "IB", "Away", "PTA" (PostTrade) 363 | 364 | // ALGO ORDERS ONLY 365 | pub algo_strategy: String, 366 | 367 | pub algo_params: Vec, 368 | //TagValueList 369 | pub smart_combo_routing_params: Vec, //TagValueList 370 | 371 | pub algo_id: String, 372 | 373 | // What-if 374 | pub what_if: bool, 375 | 376 | // Not Held 377 | pub not_held: bool, 378 | pub solicited: bool, 379 | 380 | // models 381 | pub model_code: String, 382 | 383 | // order combo legs 384 | pub order_combo_legs: Vec, // OrderComboLegListSPtr 385 | 386 | pub order_misc_options: Vec, // TagValueList 387 | 388 | // VER PEG2BENCH fields: 389 | pub reference_contract_id: i32, 390 | pub pegged_change_amount: f64, 391 | pub is_pegged_change_amount_decrease: bool, 392 | pub reference_change_amount: f64, 393 | pub reference_exchange_id: String, 394 | pub adjusted_order_type: String, 395 | 396 | pub trigger_price: f64, 397 | pub adjusted_stop_price: f64, 398 | pub adjusted_stop_limit_price: f64, 399 | pub adjusted_trailing_amount: f64, 400 | pub adjustable_trailing_unit: i32, 401 | pub lmt_price_offset: f64, 402 | 403 | pub conditions: Vec, 404 | // std::vector> 405 | pub conditions_cancel_order: bool, 406 | pub conditions_ignore_rth: bool, 407 | 408 | // ext operator 409 | pub ext_operator: String, 410 | 411 | // native cash quantity 412 | pub cash_qty: f64, 413 | 414 | pub mifid2decision_maker: String, 415 | pub mifid2decision_algo: String, 416 | pub mifid2execution_trader: String, 417 | pub mifid2execution_algo: String, 418 | 419 | pub dont_use_auto_price_for_hedge: bool, 420 | 421 | pub is_oms_container: bool, 422 | 423 | pub discretionary_up_to_limit_price: bool, 424 | 425 | pub auto_cancel_date: String, 426 | pub filled_quantity: f64, 427 | pub ref_futures_con_id: i32, 428 | pub auto_cancel_parent: bool, 429 | pub shareholder: String, 430 | pub imbalance_only: bool, 431 | pub route_marketable_to_bbo: bool, 432 | pub parent_perm_id: i32, 433 | 434 | pub use_price_mgmt_algo: bool, 435 | } 436 | 437 | impl Order { 438 | pub fn new( 439 | soft_dollar_tier: SoftDollarTier, 440 | order_id: i32, 441 | client_id: i32, 442 | perm_id: i32, 443 | action: String, 444 | total_quantity: f64, 445 | order_type: String, 446 | lmt_price: f64, 447 | aux_price: f64, 448 | tif: String, 449 | active_start_time: String, 450 | active_stop_time: String, 451 | oca_group: String, 452 | oca_type: i32, 453 | order_ref: String, 454 | transmit: bool, 455 | parent_id: i32, 456 | block_order: bool, 457 | sweep_to_fill: bool, 458 | display_size: i32, 459 | trigger_method: i32, 460 | outside_rth: bool, 461 | hidden: bool, 462 | good_after_time: String, 463 | good_till_date: String, 464 | rule80a: String, 465 | all_or_none: bool, 466 | min_qty: i32, 467 | percent_offset: f64, 468 | override_percentage_constraints: bool, 469 | trail_stop_price: f64, 470 | trailing_percent: f64, 471 | fa_group: String, 472 | fa_profile: String, 473 | fa_method: String, 474 | fa_percentage: String, 475 | designated_location: String, 476 | open_close: String, 477 | origin: Origin, 478 | short_sale_slot: i32, 479 | exempt_code: i32, 480 | discretionary_amt: f64, 481 | e_trade_only: bool, 482 | firm_quote_only: bool, 483 | nbbo_price_cap: f64, 484 | opt_out_smart_routing: bool, 485 | auction_strategy: AuctionStrategy, 486 | starting_price: f64, 487 | stock_ref_price: f64, 488 | delta: f64, 489 | stock_range_lower: f64, 490 | stock_range_upper: f64, 491 | randomize_price: bool, 492 | randomize_size: bool, 493 | volatility: f64, 494 | volatility_type: i32, 495 | delta_neutral_order_type: String, 496 | delta_neutral_aux_price: f64, 497 | delta_neutral_con_id: i32, 498 | delta_neutral_settling_firm: String, 499 | delta_neutral_clearing_account: String, 500 | delta_neutral_clearing_intent: String, 501 | delta_neutral_open_close: String, 502 | delta_neutral_short_sale: bool, 503 | delta_neutral_short_sale_slot: i32, 504 | delta_neutral_designated_location: String, 505 | continuous_update: bool, 506 | reference_price_type: i32, 507 | basis_points: f64, 508 | basis_points_type: i32, 509 | scale_init_level_size: i32, 510 | scale_subs_level_size: i32, 511 | scale_price_increment: f64, 512 | scale_price_adjust_value: f64, 513 | scale_price_adjust_interval: i32, 514 | scale_profit_offset: f64, 515 | scale_auto_reset: bool, 516 | scale_init_position: i32, 517 | scale_init_fill_qty: i32, 518 | scale_random_percent: bool, 519 | scale_table: String, 520 | hedge_type: String, 521 | hedge_param: String, 522 | account: String, 523 | settling_firm: String, 524 | clearing_account: String, 525 | clearing_intent: String, 526 | algo_strategy: String, 527 | algo_params: Vec, 528 | smart_combo_routing_params: Vec, 529 | algo_id: String, 530 | what_if: bool, 531 | not_held: bool, 532 | solicited: bool, 533 | model_code: String, 534 | order_combo_legs: Vec, 535 | order_misc_options: Vec, 536 | reference_contract_id: i32, 537 | pegged_change_amount: f64, 538 | is_pegged_change_amount_decrease: bool, 539 | reference_change_amount: f64, 540 | reference_exchange_id: String, 541 | adjusted_order_type: String, 542 | trigger_price: f64, 543 | adjusted_stop_price: f64, 544 | adjusted_stop_limit_price: f64, 545 | adjusted_trailing_amount: f64, 546 | adjustable_trailing_unit: i32, 547 | lmt_price_offset: f64, 548 | conditions: Vec, 549 | conditions_cancel_order: bool, 550 | conditions_ignore_rth: bool, 551 | ext_operator: String, 552 | cash_qty: f64, 553 | mifid2decision_maker: String, 554 | mifid2decision_algo: String, 555 | mifid2execution_trader: String, 556 | mifid2execution_algo: String, 557 | dont_use_auto_price_for_hedge: bool, 558 | is_oms_container: bool, 559 | discretionary_up_to_limit_price: bool, 560 | auto_cancel_date: String, 561 | filled_quantity: f64, 562 | ref_futures_con_id: i32, 563 | auto_cancel_parent: bool, 564 | shareholder: String, 565 | imbalance_only: bool, 566 | route_marketable_to_bbo: bool, 567 | parent_perm_id: i32, 568 | use_price_mgmt_algo: bool, 569 | ) -> Self { 570 | Order { 571 | soft_dollar_tier, 572 | order_id, 573 | client_id, 574 | perm_id, 575 | action, 576 | total_quantity, 577 | order_type, 578 | lmt_price, 579 | aux_price, 580 | tif, 581 | active_start_time, 582 | active_stop_time, 583 | oca_group, 584 | oca_type, 585 | order_ref, 586 | transmit, 587 | parent_id, 588 | block_order, 589 | sweep_to_fill, 590 | display_size, 591 | trigger_method, 592 | outside_rth, 593 | hidden, 594 | good_after_time, 595 | good_till_date, 596 | rule80a, 597 | all_or_none, 598 | min_qty, 599 | percent_offset, 600 | override_percentage_constraints, 601 | trail_stop_price, 602 | trailing_percent, 603 | fa_group, 604 | fa_profile, 605 | fa_method, 606 | fa_percentage, 607 | designated_location, 608 | open_close, 609 | origin, 610 | short_sale_slot, 611 | exempt_code, 612 | discretionary_amt, 613 | e_trade_only, 614 | firm_quote_only, 615 | nbbo_price_cap, 616 | opt_out_smart_routing, 617 | auction_strategy, 618 | starting_price, 619 | stock_ref_price, 620 | delta, 621 | stock_range_lower, 622 | stock_range_upper, 623 | randomize_price, 624 | randomize_size, 625 | volatility, 626 | volatility_type, 627 | delta_neutral_order_type, 628 | delta_neutral_aux_price, 629 | delta_neutral_con_id, 630 | delta_neutral_settling_firm, 631 | delta_neutral_clearing_account, 632 | delta_neutral_clearing_intent, 633 | delta_neutral_open_close, 634 | delta_neutral_short_sale, 635 | delta_neutral_short_sale_slot, 636 | delta_neutral_designated_location, 637 | continuous_update, 638 | reference_price_type, 639 | basis_points, 640 | basis_points_type, 641 | scale_init_level_size, 642 | scale_subs_level_size, 643 | scale_price_increment, 644 | scale_price_adjust_value, 645 | scale_price_adjust_interval, 646 | scale_profit_offset, 647 | scale_auto_reset, 648 | scale_init_position, 649 | scale_init_fill_qty, 650 | scale_random_percent, 651 | scale_table, 652 | hedge_type, 653 | hedge_param, 654 | account, 655 | settling_firm, 656 | clearing_account, 657 | clearing_intent, 658 | algo_strategy, 659 | algo_params, 660 | smart_combo_routing_params, 661 | algo_id, 662 | what_if, 663 | not_held, 664 | solicited, 665 | model_code, 666 | order_combo_legs, 667 | order_misc_options, 668 | reference_contract_id, 669 | pegged_change_amount, 670 | is_pegged_change_amount_decrease, 671 | reference_change_amount, 672 | reference_exchange_id, 673 | adjusted_order_type, 674 | trigger_price, 675 | adjusted_stop_price, 676 | adjusted_stop_limit_price, 677 | adjusted_trailing_amount, 678 | adjustable_trailing_unit, 679 | lmt_price_offset, 680 | conditions, 681 | conditions_cancel_order, 682 | conditions_ignore_rth, 683 | ext_operator, 684 | cash_qty, 685 | mifid2decision_maker, 686 | mifid2decision_algo, 687 | mifid2execution_trader, 688 | mifid2execution_algo, 689 | dont_use_auto_price_for_hedge, 690 | is_oms_container, 691 | discretionary_up_to_limit_price, 692 | auto_cancel_date, 693 | filled_quantity, 694 | ref_futures_con_id, 695 | auto_cancel_parent, 696 | shareholder, 697 | imbalance_only, 698 | route_marketable_to_bbo, 699 | parent_perm_id, 700 | use_price_mgmt_algo, 701 | } 702 | } 703 | } 704 | 705 | impl Display for Order { 706 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 707 | write!( 708 | f, 709 | "order_id = {}, 710 | client_id = {}, 711 | perm_id = {}, 712 | order_type = {}, 713 | action = {}, 714 | total_quantity = {}, 715 | lmt_price = {}, 716 | tif = {}, 717 | what_if = {}, 718 | algo_strategy = {}, 719 | algo_params = ({}), 720 | CMB = ({}), 721 | COND = ({}),\n", 722 | self.order_id, 723 | self.client_id, 724 | self.perm_id, 725 | self.order_type, 726 | self.action, 727 | self.total_quantity, 728 | if self.lmt_price == UNSET_DOUBLE { 729 | format!("{:E}", self.lmt_price) 730 | } else { 731 | format!("{:?}", self.lmt_price) 732 | }, 733 | self.tif, 734 | self.what_if, 735 | self.algo_strategy, 736 | if !self.algo_params.is_empty() { 737 | self.algo_params 738 | .iter() 739 | .map(|t| format!("{} = {}", t.tag, t.value)) 740 | .collect::>() 741 | .join(",") 742 | } else { 743 | "".to_string() 744 | }, 745 | if !self.order_combo_legs.is_empty() { 746 | self.order_combo_legs 747 | .iter() 748 | .map(|x| format!("{}", x)) 749 | .collect::>() 750 | .join(",") 751 | } else { 752 | "".to_string() 753 | }, 754 | if !self.conditions.is_empty() { 755 | self.conditions 756 | .iter() 757 | .map(|x| format!("{}|", x.make_fields().unwrap().as_slice().join(","))) 758 | .collect::() 759 | } else { 760 | "".to_string() 761 | }, 762 | ) 763 | } 764 | } 765 | 766 | impl Default for Order { 767 | fn default() -> Self { 768 | Order { 769 | soft_dollar_tier: SoftDollarTier::new("".to_string(), "".to_string(), "".to_string()), 770 | // order identifier 771 | order_id: 0, 772 | client_id: 0, 773 | perm_id: 0, 774 | 775 | // main order fields 776 | action: "".to_string(), 777 | total_quantity: 0.0, 778 | order_type: "".to_string(), 779 | lmt_price: UNSET_DOUBLE, 780 | aux_price: UNSET_DOUBLE, 781 | 782 | // extended order fields 783 | tif: "".to_string(), // "Time in Force" - DAY, GTC, etc. 784 | active_start_time: "".to_string(), // for GTC orders 785 | active_stop_time: "".to_string(), // for GTC orders 786 | oca_group: "".to_string(), // one cancels all group name 787 | oca_type: 0, // 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK 788 | order_ref: "".to_string(), 789 | transmit: true, // if false, order will be created but not transmited 790 | parent_id: 0, // Parent order Id, to associate Auto STP or TRAIL orders with the original order. 791 | block_order: false, 792 | sweep_to_fill: false, 793 | display_size: 0, 794 | trigger_method: 0, // 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last, 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point 795 | outside_rth: false, 796 | hidden: false, 797 | good_after_time: "".to_string(), // Format: 20060505 08:00:00 {time zone} 798 | good_till_date: "".to_string(), // Format: 20060505 08:00:00 {time zone} 799 | rule80a: "".to_string(), // Individual = 'I', Agency = 'A', AgentOtherMember = 'W', IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M', IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N' 800 | all_or_none: false, 801 | min_qty: UNSET_INTEGER, //type: int 802 | percent_offset: UNSET_DOUBLE, // type: float; REL orders only 803 | override_percentage_constraints: false, 804 | trail_stop_price: UNSET_DOUBLE, // type: float 805 | trailing_percent: UNSET_DOUBLE, // type: float; TRAILLIMIT orders only 806 | 807 | // financial advisors only 808 | fa_group: "".to_string(), 809 | fa_profile: "".to_string(), 810 | fa_method: "".to_string(), 811 | fa_percentage: "".to_string(), 812 | 813 | // institutional (ie non-cleared) only 814 | designated_location: "".to_string(), //used only when shortSaleSlot=2 815 | open_close: "O".to_string(), // O=Open, C=Close 816 | origin: Customer, // 0=Customer, 1=Firm 817 | short_sale_slot: 0, // type: int; 1 if you hold the shares, 2 if they will be delivered from elsewhere. Only for Action=SSHORT 818 | exempt_code: -1, 819 | 820 | // SMART routing only 821 | discretionary_amt: 0.0, 822 | e_trade_only: true, 823 | firm_quote_only: true, 824 | nbbo_price_cap: UNSET_DOUBLE, // type: float 825 | opt_out_smart_routing: false, 826 | 827 | // BOX exchange orders only 828 | auction_strategy: AuctionUnset, // type: int; AUCTION_MATCH, AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT 829 | starting_price: UNSET_DOUBLE, // type: float 830 | stock_ref_price: UNSET_DOUBLE, // type: float 831 | delta: UNSET_DOUBLE, // type: float 832 | 833 | // pegged to stock and VOL orders only 834 | stock_range_lower: UNSET_DOUBLE, // type: float 835 | stock_range_upper: UNSET_DOUBLE, // type: float 836 | 837 | randomize_price: false, 838 | randomize_size: false, 839 | 840 | // VOLATILITY ORDERS ONLY 841 | volatility: UNSET_DOUBLE, // type: float 842 | volatility_type: UNSET_INTEGER, // type: int // 1=daily, 2=annual 843 | delta_neutral_order_type: "".to_string(), 844 | delta_neutral_aux_price: UNSET_DOUBLE, // type: float 845 | delta_neutral_con_id: 0, 846 | delta_neutral_settling_firm: "".to_string(), 847 | delta_neutral_clearing_account: "".to_string(), 848 | delta_neutral_clearing_intent: "".to_string(), 849 | delta_neutral_open_close: "".to_string(), 850 | delta_neutral_short_sale: false, 851 | delta_neutral_short_sale_slot: 0, 852 | delta_neutral_designated_location: "".to_string(), 853 | continuous_update: false, 854 | reference_price_type: UNSET_INTEGER, // type: int; 1=Average, 2 = BidOrAsk 855 | 856 | // COMBO ORDERS ONLY 857 | basis_points: UNSET_DOUBLE, // type: float; EFP orders only 858 | basis_points_type: UNSET_INTEGER, // type: int; EFP orders only 859 | 860 | // SCALE ORDERS ONLY 861 | scale_init_level_size: UNSET_INTEGER, // type: int 862 | scale_subs_level_size: UNSET_INTEGER, // type: int 863 | scale_price_increment: UNSET_DOUBLE, // type: float 864 | scale_price_adjust_value: UNSET_DOUBLE, // type: float 865 | scale_price_adjust_interval: UNSET_INTEGER, // type: int 866 | scale_profit_offset: UNSET_DOUBLE, // type: float 867 | scale_auto_reset: false, 868 | scale_init_position: UNSET_INTEGER, // type: int 869 | scale_init_fill_qty: UNSET_INTEGER, // type: int 870 | scale_random_percent: false, 871 | scale_table: "".to_string(), 872 | 873 | // HEDGE ORDERS 874 | hedge_type: "".to_string(), // 'D' - delta, 'B' - beta, 'F' - FX, 'P' - pair 875 | hedge_param: "".to_string(), // 'beta=X' value for beta hedge, 'ratio=Y' for pair hedge 876 | 877 | // Clearing info 878 | account: "".to_string(), // IB account 879 | settling_firm: "".to_string(), 880 | clearing_account: "".to_string(), //True beneficiary of the order 881 | clearing_intent: "".to_string(), // "" (Default), "IB", "Away", "PTA" (PostTrade) 882 | 883 | // ALGO ORDERS ONLY 884 | algo_strategy: "".to_string(), 885 | 886 | algo_params: vec![], //TagValueList 887 | smart_combo_routing_params: vec![], //TagValueList 888 | 889 | algo_id: "".to_string(), 890 | 891 | // What-if 892 | what_if: false, 893 | 894 | // Not Held 895 | not_held: false, 896 | solicited: false, 897 | 898 | // models 899 | model_code: "".to_string(), 900 | 901 | // order combo legs 902 | order_combo_legs: vec![], // OrderComboLegListSPtr 903 | 904 | order_misc_options: vec![], // TagValueList 905 | 906 | // VER PEG2BENCH fields: 907 | reference_contract_id: 0, 908 | pegged_change_amount: 0.0, 909 | is_pegged_change_amount_decrease: false, 910 | reference_change_amount: 0.0, 911 | reference_exchange_id: "".to_string(), 912 | adjusted_order_type: "".to_string(), 913 | 914 | trigger_price: UNSET_DOUBLE, 915 | adjusted_stop_price: UNSET_DOUBLE, 916 | adjusted_stop_limit_price: UNSET_DOUBLE, 917 | adjusted_trailing_amount: UNSET_DOUBLE, 918 | adjustable_trailing_unit: 0, 919 | lmt_price_offset: UNSET_DOUBLE, 920 | 921 | conditions: vec![], // std::vector> 922 | conditions_cancel_order: false, 923 | conditions_ignore_rth: false, 924 | 925 | // ext operator 926 | ext_operator: "".to_string(), 927 | 928 | // native cash quantity 929 | cash_qty: UNSET_DOUBLE, 930 | 931 | mifid2decision_maker: "".to_string(), 932 | mifid2decision_algo: "".to_string(), 933 | mifid2execution_trader: "".to_string(), 934 | mifid2execution_algo: "".to_string(), 935 | 936 | dont_use_auto_price_for_hedge: false, 937 | 938 | is_oms_container: false, 939 | 940 | discretionary_up_to_limit_price: false, 941 | 942 | auto_cancel_date: "".to_string(), 943 | filled_quantity: UNSET_DOUBLE, 944 | ref_futures_con_id: 0, 945 | auto_cancel_parent: false, 946 | shareholder: "".to_string(), 947 | imbalance_only: false, 948 | route_marketable_to_bbo: false, 949 | parent_perm_id: 0, 950 | 951 | use_price_mgmt_algo: false, 952 | } 953 | } 954 | } 955 | -------------------------------------------------------------------------------- /src/core/reader.rs: -------------------------------------------------------------------------------- 1 | //! Reads and processes messages from the TCP socket 2 | use std::io::Read; 3 | use std::net::Shutdown; 4 | use std::sync::atomic::{AtomicBool, Ordering}; 5 | use std::sync::mpsc::Sender; 6 | use std::sync::Arc; 7 | 8 | use log::*; 9 | 10 | use super::streamer::Streamer; 11 | use crate::core::errors::IBKRApiLibError; 12 | use crate::core::messages::read_msg; 13 | 14 | //================================================================================================== 15 | pub struct Reader { 16 | stream: Box, 17 | messages: Sender, 18 | disconnect_requested: Arc, 19 | is_connected: bool, 20 | } 21 | 22 | impl Reader { 23 | pub fn new( 24 | stream: Box, 25 | messages: Sender, 26 | disconnect_requested: Arc, 27 | ) -> Self { 28 | Reader { 29 | stream, 30 | messages, 31 | disconnect_requested, 32 | is_connected: true, 33 | } 34 | } 35 | 36 | //---------------------------------------------------------------------------------------------- 37 | pub fn recv_packet(&mut self) -> Result, IBKRApiLibError> { 38 | //debug!("_recv_all_msg"); 39 | let buf = self._recv_all_msg()?; 40 | // receiving 0 bytes outside a timeout means the connection is either 41 | // closed or broken 42 | if buf.len() == 0 { 43 | if !self.disconnect_requested.load(Ordering::Acquire) { 44 | info!("socket either closed or broken, disconnecting"); 45 | self.stream.shutdown(Shutdown::Both)?; 46 | self.is_connected = false; 47 | } 48 | } 49 | Ok(buf) 50 | } 51 | 52 | //---------------------------------------------------------------------------------------------- 53 | fn _recv_all_msg(&mut self) -> Result, IBKRApiLibError> { 54 | let mut cont = true; 55 | let mut allbuf: Vec = Vec::new(); 56 | const NUM_BYTES: usize = 4096; 57 | 58 | while cont { 59 | let mut buf: [u8; NUM_BYTES] = [0; NUM_BYTES]; 60 | 61 | let bytes_read = self 62 | .stream 63 | .read(&mut buf) 64 | .expect("Couldnt read from reader..."); 65 | allbuf.extend_from_slice(&buf[0..bytes_read]); 66 | //logger.debug("len %d raw:%s|", len(buf), buf) 67 | 68 | if bytes_read < NUM_BYTES { 69 | cont = false; 70 | } 71 | } 72 | Ok(allbuf) 73 | } 74 | 75 | //---------------------------------------------------------------------------------------------- 76 | fn process_reader_msgs(&mut self) -> Result<(), IBKRApiLibError> { 77 | // grab a packet of messages from the socket 78 | let mut message_packet = self.recv_packet()?; 79 | //debug!(" recvd size {}", message_packet.len()); 80 | 81 | // Read messages from the packet until there are no more. 82 | // When this loop ends, break into the outer loop and grab another packet. 83 | // Repeat until the connection is closed 84 | // 85 | let _msg = String::new(); 86 | while message_packet.len() > 0 { 87 | // Read a message from the packet then add it to the message queue below. 88 | let (_size, msg, remaining_messages) = read_msg(message_packet.as_slice())?; 89 | 90 | // clear the Vec that holds the bytes from the packet 91 | // and reload with the bytes that haven't been read. 92 | // The variable remaining_messages only holds the unread messagesleft in the packet 93 | message_packet.clear(); 94 | message_packet.extend_from_slice(remaining_messages.as_slice()); 95 | 96 | if msg.as_str() != "" { 97 | self.messages.send(msg).expect("READER CANNOT SEND MESSAGE"); 98 | } else { 99 | //Break to the outer loop in run and get another packet of messages. 100 | 101 | debug!("more incoming packet(s) are needed "); 102 | break; 103 | } 104 | } 105 | Ok(()) 106 | } 107 | //---------------------------------------------------------------------------------------------- 108 | pub fn run(&mut self) { 109 | debug!("starting reader loop"); 110 | loop { 111 | if self.disconnect_requested.load(Ordering::Acquire) || !self.is_connected { 112 | return; 113 | } 114 | let result = self.process_reader_msgs(); 115 | if !result.is_err() { 116 | continue; 117 | } 118 | error!("{:?}", result); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/core/scanner.rs: -------------------------------------------------------------------------------- 1 | //! Types for dealing with scanner data and scanner subscriptions 2 | use std::fmt::{Display, Error, Formatter}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::core::contract::ContractDetails; 7 | 8 | //================================================================================================== 9 | 10 | #[derive(Serialize, Deserialize, Clone, Default, Debug)] 11 | pub struct ScanData { 12 | pub contract: ContractDetails, 13 | pub rank: i32, 14 | pub distance: String, 15 | pub benchmark: String, 16 | pub projection: String, 17 | pub legs: String, 18 | } 19 | 20 | impl ScanData { 21 | pub fn new( 22 | contract: ContractDetails, 23 | rank: i32, 24 | distance: String, 25 | benchmark: String, 26 | projection: String, 27 | legs: String, 28 | ) -> Self { 29 | ScanData { 30 | contract, 31 | rank, 32 | distance, 33 | benchmark, 34 | projection, 35 | legs, 36 | } 37 | } 38 | } 39 | 40 | impl Display for ScanData { 41 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 42 | write!(f, "Rank: {}, Symbol: {}, SecType: {}, Currency: {}, Distance: {}, Benchmark: {}, Projection: {}, Legs: {}", 43 | self.rank, self.contract.contract.symbol, self.contract.contract.sec_type, self.contract.contract.currency, 44 | self.distance, self.benchmark, self.projection, self.legs) 45 | } 46 | } 47 | 48 | //================================================================================================== 49 | #[derive(Serialize, Deserialize, Debug, Clone, Default)] 50 | pub struct ScannerSubscription { 51 | pub number_of_rows: i32, 52 | pub instrument: String, 53 | pub location_code: String, 54 | pub scan_code: String, 55 | pub above_price: f64, 56 | pub below_price: f64, 57 | pub above_volume: i32, 58 | pub market_cap_above: f64, 59 | pub market_cap_below: f64, 60 | pub moody_rating_above: String, 61 | pub moody_rating_below: String, 62 | pub sp_rating_above: String, 63 | pub sp_rating_below: String, 64 | pub maturity_date_above: String, 65 | pub maturity_date_below: String, 66 | pub coupon_rate_above: f64, 67 | pub coupon_rate_below: f64, 68 | pub exclude_convertible: bool, 69 | pub average_option_volume_above: i32, 70 | pub scanner_setting_pairs: String, 71 | pub stock_type_filter: String, 72 | } 73 | 74 | impl ScannerSubscription { 75 | pub fn new( 76 | number_of_rows: i32, 77 | instrument: String, 78 | location_code: String, 79 | scan_code: String, 80 | above_price: f64, 81 | below_price: f64, 82 | above_volume: i32, 83 | market_cap_above: f64, 84 | market_cap_below: f64, 85 | moody_rating_above: String, 86 | moody_rating_below: String, 87 | sp_rating_above: String, 88 | sp_rating_below: String, 89 | maturity_date_above: String, 90 | maturity_date_below: String, 91 | coupon_rate_above: f64, 92 | coupon_rate_below: f64, 93 | exclude_convertible: bool, 94 | average_option_volume_above: i32, 95 | scanner_setting_pairs: String, 96 | stock_type_filter: String, 97 | ) -> Self { 98 | ScannerSubscription { 99 | number_of_rows, 100 | instrument, 101 | location_code, 102 | scan_code, 103 | above_price, 104 | below_price, 105 | above_volume, 106 | market_cap_above, 107 | market_cap_below, 108 | moody_rating_above, 109 | moody_rating_below, 110 | sp_rating_above, 111 | sp_rating_below, 112 | maturity_date_above, 113 | maturity_date_below, 114 | coupon_rate_above, 115 | coupon_rate_below, 116 | exclude_convertible, 117 | average_option_volume_above, 118 | scanner_setting_pairs, 119 | stock_type_filter, 120 | } 121 | } 122 | } 123 | 124 | impl Display for ScannerSubscription { 125 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { 126 | write!( 127 | f, 128 | "Instrument: {}, LocationCode: {}, ScanCode: {}", 129 | self.instrument, self.location_code, self.scan_code 130 | ) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/core/server_versions.rs: -------------------------------------------------------------------------------- 1 | //! The known server versions. 2 | 3 | //MIN_SERVER_VER_REAL_TIME_BARS = 34 4 | //MIN_SERVER_VER_SCALE_ORDERS = 35 5 | //MIN_SERVER_VER_SNAPSHOT_MKT_DATA = 35 6 | //MIN_SERVER_VER_SSHORT_COMBO_LEGS = 35 7 | //MIN_SERVER_VER_WHAT_IF_ORDERS = 36 8 | //MIN_SERVER_VER_CONTRACT_CONID = 37 9 | pub const MIN_SERVER_VER_PTA_ORDERS: i32 = 39; 10 | pub const MIN_SERVER_VER_FUNDAMENTAL_DATA: i32 = 40; 11 | pub const MIN_SERVER_VER_DELTA_NEUTRAL: i32 = 40; 12 | pub const MIN_SERVER_VER_CONTRACT_DATA_CHAIN: i32 = 40; 13 | pub const MIN_SERVER_VER_SCALE_ORDERS2: i32 = 40; 14 | pub const MIN_SERVER_VER_ALGO_ORDERS: i32 = 41; 15 | pub const MIN_SERVER_VER_EXECUTION_DATA_CHAIN: i32 = 42; 16 | pub const MIN_SERVER_VER_NOT_HELD: i32 = 44; 17 | pub const MIN_SERVER_VER_SEC_ID_TYPE: i32 = 45; 18 | pub const MIN_SERVER_VER_PLACE_ORDER_CONID: i32 = 46; 19 | pub const MIN_SERVER_VER_REQ_MKT_DATA_CONID: i32 = 47; 20 | pub const MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT: i32 = 49; 21 | pub const MIN_SERVER_VER_REQ_CALC_OPTION_PRICE: i32 = 50; 22 | pub const MIN_SERVER_VER_SSHORTX_OLD: i32 = 51; 23 | pub const MIN_SERVER_VER_SSHORTX: i32 = 52; 24 | pub const MIN_SERVER_VER_REQ_GLOBAL_CANCEL: i32 = 53; 25 | pub const MIN_SERVER_VER_HEDGE_ORDERS: i32 = 54; 26 | pub const MIN_SERVER_VER_REQ_MARKET_DATA_TYPE: i32 = 55; 27 | pub const MIN_SERVER_VER_OPT_OUT_SMART_ROUTING: i32 = 56; 28 | pub const MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS: i32 = 57; 29 | pub const MIN_SERVER_VER_DELTA_NEUTRAL_CONID: i32 = 58; 30 | pub const MIN_SERVER_VER_SCALE_ORDERS3: i32 = 60; 31 | pub const MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE: i32 = 61; 32 | pub const MIN_SERVER_VER_TRAILING_PERCENT: i32 = 62; 33 | pub const MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE: i32 = 66; 34 | pub const MIN_SERVER_VER_POSITIONS: i32 = 67; 35 | pub const MIN_SERVER_VER_ACCOUNT_SUMMARY: i32 = 67; 36 | pub const MIN_SERVER_VER_TRADING_CLASS: i32 = 68; 37 | pub const MIN_SERVER_VER_SCALE_TABLE: i32 = 69; 38 | pub const MIN_SERVER_VER_LINKING: i32 = 70; 39 | pub const MIN_SERVER_VER_ALGO_ID: i32 = 71; 40 | pub const MIN_SERVER_VER_OPTIONAL_CAPABILITIES: i32 = 72; 41 | pub const MIN_SERVER_VER_ORDER_SOLICITED: i32 = 73; 42 | pub const MIN_SERVER_VER_LINKING_AUTH: i32 = 74; 43 | pub const MIN_SERVER_VER_PRIMARYEXCH: i32 = 75; 44 | pub const MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE: i32 = 76; 45 | pub const MIN_SERVER_VER_FRACTIONAL_POSITIONS: i32 = 101; 46 | pub const MIN_SERVER_VER_PEGGED_TO_BENCHMARK: i32 = 102; 47 | pub const MIN_SERVER_VER_MODELS_SUPPORT: i32 = 103; 48 | pub const MIN_SERVER_VER_SEC_DEF_OPT_PARAMS_REQ: i32 = 104; 49 | pub const MIN_SERVER_VER_EXT_OPERATOR: i32 = 105; 50 | pub const MIN_SERVER_VER_SOFT_DOLLAR_TIER: i32 = 106; 51 | pub const MIN_SERVER_VER_REQ_FAMILY_CODES: i32 = 107; 52 | pub const MIN_SERVER_VER_REQ_MATCHING_SYMBOLS: i32 = 108; 53 | pub const MIN_SERVER_VER_PAST_LIMIT: i32 = 109; 54 | pub const MIN_SERVER_VER_MD_SIZE_MULTIPLIER: i32 = 110; 55 | pub const MIN_SERVER_VER_CASH_QTY: i32 = 111; 56 | pub const MIN_SERVER_VER_REQ_MKT_DEPTH_EXCHANGES: i32 = 112; 57 | pub const MIN_SERVER_VER_TICK_NEWS: i32 = 113; 58 | pub const MIN_SERVER_VER_REQ_SMART_COMPONENTS: i32 = 114; 59 | pub const MIN_SERVER_VER_REQ_NEWS_PROVIDERS: i32 = 115; 60 | pub const MIN_SERVER_VER_REQ_NEWS_ARTICLE: i32 = 116; 61 | pub const MIN_SERVER_VER_REQ_HISTORICAL_NEWS: i32 = 117; 62 | pub const MIN_SERVER_VER_REQ_HEAD_TIMESTAMP: i32 = 118; 63 | pub const MIN_SERVER_VER_REQ_HISTOGRAM: i32 = 119; 64 | pub const MIN_SERVER_VER_SERVICE_DATA_TYPE: i32 = 120; 65 | pub const MIN_SERVER_VER_AGG_GROUP: i32 = 121; 66 | pub const MIN_SERVER_VER_UNDERLYING_INFO: i32 = 122; 67 | pub const MIN_SERVER_VER_CANCEL_HEADTIMESTAMP: i32 = 123; 68 | pub const MIN_SERVER_VER_SYNT_REALTIME_BARS: i32 = 124; 69 | pub const MIN_SERVER_VER_CFD_REROUTE: i32 = 125; 70 | pub const MIN_SERVER_VER_MARKET_RULES: i32 = 126; 71 | pub const MIN_SERVER_VER_PNL: i32 = 127; 72 | pub const MIN_SERVER_VER_NEWS_QUERY_ORIGINS: i32 = 128; 73 | pub const MIN_SERVER_VER_UNREALIZED_PNL: i32 = 129; 74 | pub const MIN_SERVER_VER_HISTORICAL_TICKS: i32 = 130; 75 | pub const MIN_SERVER_VER_MARKET_CAP_PRICE: i32 = 131; 76 | pub const MIN_SERVER_VER_PRE_OPEN_BID_ASK: i32 = 132; 77 | pub const MIN_SERVER_VER_REAL_EXPIRATION_DATE: i32 = 134; 78 | pub const MIN_SERVER_VER_REALIZED_PNL: i32 = 135; 79 | pub const MIN_SERVER_VER_LAST_LIQUIDITY: i32 = 136; 80 | pub const MIN_SERVER_VER_TICK_BY_TICK: i32 = 137; 81 | pub const MIN_SERVER_VER_DECISION_MAKER: i32 = 138; 82 | pub const MIN_SERVER_VER_MIFID_EXECUTION: i32 = 139; 83 | pub const MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE: i32 = 140; 84 | pub const MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE: i32 = 141; 85 | pub const MIN_SERVER_VER_WHAT_IF_EXT_FIELDS: i32 = 142; 86 | pub const MIN_SERVER_VER_SCANNER_GENERIC_OPTS: i32 = 143; 87 | pub const MIN_SERVER_VER_API_BIND_ORDER: i32 = 144; 88 | pub const MIN_SERVER_VER_ORDER_CONTAINER: i32 = 145; 89 | pub const MIN_SERVER_VER_SMART_DEPTH: i32 = 146; 90 | pub const MIN_SERVER_VER_REMOVE_NULL_ALL_CASTING: i32 = 147; 91 | pub const MIN_SERVER_VER_D_PEG_ORDERS: i32 = 148; 92 | pub const MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE: i32 = 149; 93 | pub const MIN_SERVER_VER_COMPLETED_ORDERS: i32 = 150; 94 | pub const MIN_SERVER_VER_PRICE_MGMT_ALGO: i32 = 151; 95 | 96 | // 100+ messaging */ 97 | // 100 = enhanced handshake, msg length prefixes 98 | 99 | pub const MIN_CLIENT_VER: i32 = 100; 100 | pub const MAX_CLIENT_VER: i32 = MIN_SERVER_VER_PRICE_MGMT_ALGO; 101 | -------------------------------------------------------------------------------- /src/core/streamer.rs: -------------------------------------------------------------------------------- 1 | use bytebuffer::ByteBuffer; 2 | use std::net::{SocketAddr, TcpStream}; 3 | use std::{ 4 | io::{self, Read, Write}, 5 | net::Shutdown, 6 | }; 7 | 8 | //---------------------------------------------------------------------------------------------- 9 | pub trait Streamer: Read + Write + Send + Sync { 10 | fn shutdown(&mut self, how: Shutdown) -> io::Result<()>; 11 | fn connect(&mut self, addr: &SocketAddr); 12 | } 13 | //---------------------------------------------------------------------------------------------- 14 | #[derive(Debug)] 15 | pub struct TcpStreamer { 16 | pub(crate) stream: TcpStream, 17 | } 18 | 19 | impl TcpStreamer { 20 | pub fn new(stream: TcpStream) -> Self { 21 | Self { stream: stream } 22 | } 23 | } 24 | 25 | impl Streamer for TcpStreamer { 26 | fn shutdown(&mut self, how: Shutdown) -> io::Result<()> { 27 | self.stream.shutdown(how) 28 | } 29 | 30 | fn connect(&mut self, addr: &SocketAddr) { 31 | self.stream = TcpStream::connect(addr).expect("Cannot connect!!"); 32 | } 33 | } 34 | 35 | impl Clone for TcpStreamer { 36 | fn clone(&self) -> Self { 37 | TcpStreamer::new(self.stream.try_clone().unwrap()) 38 | } 39 | } 40 | 41 | impl Read for TcpStreamer { 42 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 43 | self.stream.read(buf) 44 | } 45 | } 46 | 47 | impl Write for TcpStreamer { 48 | fn write(&mut self, buf: &[u8]) -> io::Result { 49 | self.stream.write(buf) 50 | } 51 | fn flush(&mut self) -> io::Result<()> { 52 | self.stream.flush() 53 | } 54 | } 55 | 56 | //---------------------------------------------------------------------------------------------- 57 | pub struct TestStreamer { 58 | stream: ByteBuffer, 59 | } 60 | 61 | impl TestStreamer { 62 | pub fn new() -> Self { 63 | TestStreamer { 64 | stream: ByteBuffer::new(), 65 | } 66 | } 67 | } 68 | 69 | impl Streamer for TestStreamer { 70 | fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> { 71 | Ok(()) 72 | } 73 | 74 | fn connect(&mut self, _addr: &SocketAddr) {} 75 | } 76 | 77 | impl Read for TestStreamer { 78 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 79 | self.stream.read(buf) 80 | } 81 | 82 | fn read_to_end(&mut self, allbuf: &mut Vec) -> io::Result { 83 | let mut cont = true; 84 | const NUM_BYTES: usize = 4096; 85 | 86 | while cont { 87 | let mut buf: [u8; NUM_BYTES] = [0; NUM_BYTES]; 88 | 89 | let bytes_read = self 90 | .stream 91 | .read(&mut buf) 92 | .expect("Couldnt read from reader..."); 93 | allbuf.extend_from_slice(&buf[0..bytes_read]); 94 | 95 | if bytes_read < NUM_BYTES { 96 | cont = false; 97 | } 98 | } 99 | Ok(allbuf.len()) 100 | } 101 | } 102 | 103 | impl Write for TestStreamer { 104 | fn write(&mut self, buf: &[u8]) -> io::Result { 105 | self.stream.write(buf) 106 | } 107 | fn flush(&mut self) -> io::Result<()> { 108 | self.stream.flush() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/examples/contract_samples.rs: -------------------------------------------------------------------------------- 1 | //! Examples of populating fields that define various types of contacts 2 | 3 | use crate::core::contract::{ComboLeg, Contract, PositionType}; 4 | 5 | //================================================================================================== 6 | pub fn eur_gbp_fx() -> Contract { 7 | let mut contract = Contract::default(); 8 | contract.symbol = "EUR".to_string(); 9 | contract.sec_type = "CASH".to_string(); 10 | contract.currency = "GBP".to_string(); 11 | contract.exchange = "IDEALPRO".to_string(); 12 | 13 | contract 14 | } 15 | 16 | //================================================================================================== 17 | pub fn index() -> Contract { 18 | let mut contract = Contract::default(); 19 | contract.symbol = "DAX".to_string(); 20 | contract.sec_type = "IND".to_string(); 21 | contract.currency = "EUR".to_string(); 22 | contract.exchange = "DTB".to_string(); 23 | 24 | contract 25 | } 26 | 27 | //================================================================================================== 28 | pub fn cfd() -> Contract { 29 | let mut contract = Contract::default(); 30 | contract.symbol = "IBDE30".to_string(); 31 | contract.sec_type = "cfd".to_string(); 32 | contract.currency = "EUR".to_string(); 33 | contract.exchange = "SMART".to_string(); 34 | 35 | contract 36 | } 37 | 38 | //================================================================================================== 39 | pub fn european_stock() -> Contract { 40 | let mut contract = Contract::default(); 41 | contract.symbol = "BMW".to_string(); 42 | contract.sec_type = "STK".to_string(); 43 | contract.currency = "EUR".to_string(); 44 | contract.exchange = "SMART".to_string(); 45 | contract.primary_exchange = "IBIS".to_string(); 46 | contract 47 | } 48 | 49 | //================================================================================================== 50 | pub fn european_stock2() -> Contract { 51 | let mut contract = Contract::default(); 52 | contract.symbol = "NOKIA".to_string(); 53 | contract.sec_type = "STK".to_string(); 54 | contract.currency = "EUR".to_string(); 55 | contract.exchange = "SMART".to_string(); 56 | contract.primary_exchange = "HEX".to_string(); 57 | contract 58 | } 59 | 60 | //================================================================================================== 61 | pub fn option_at_ise() -> Contract { 62 | let mut contract = Contract::default(); 63 | contract.symbol = "COF".to_string(); 64 | contract.sec_type = "OPT".to_string(); 65 | contract.currency = "USD".to_string(); 66 | contract.exchange = "ISE".to_string(); 67 | contract.last_trade_date_or_contract_month = "20190315".to_string(); 68 | contract.right = "P".to_string(); 69 | contract.strike = 105.0; 70 | contract.multiplier = "100".to_string(); 71 | contract 72 | } 73 | 74 | //================================================================================================== 75 | pub fn bond_with_cusip() -> Contract { 76 | let mut contract = Contract::default(); 77 | // enter CUSIP as symbol 78 | contract.symbol = "912828C57".to_string(); 79 | contract.sec_type = "BOND".to_string(); 80 | contract.exchange = "SMART".to_string(); 81 | contract.currency = "USD".to_string(); 82 | 83 | contract 84 | } 85 | 86 | //================================================================================================== 87 | pub fn bond() -> Contract { 88 | let mut contract = Contract::default(); 89 | contract.con_id = 15960357; 90 | contract.exchange = "SMART".to_string(); 91 | 92 | contract 93 | } 94 | 95 | //================================================================================================== 96 | pub fn mutual_fund() -> Contract { 97 | let mut contract = Contract::default(); 98 | contract.symbol = "VINIX".to_string(); 99 | contract.sec_type = "FUND".to_string(); 100 | contract.exchange = "FUNDSERV".to_string(); 101 | contract.currency = "USD".to_string(); 102 | 103 | contract 104 | } 105 | 106 | //================================================================================================== 107 | pub fn commodity() -> Contract { 108 | let mut contract = Contract::default(); 109 | contract.symbol = "XAUUSD".to_string(); 110 | contract.sec_type = "CMDTY".to_string(); 111 | contract.exchange = "SMART".to_string(); 112 | contract.currency = "USD".to_string(); 113 | 114 | contract 115 | } 116 | 117 | //================================================================================================== 118 | pub fn usstock() -> Contract { 119 | let mut contract = Contract::default(); 120 | contract.symbol = "AMZN".to_string(); 121 | contract.sec_type = "STK".to_string(); 122 | contract.currency = "USD".to_string(); 123 | //In the API side, NASDAQ is always defined as ISLAND in the exchange field 124 | contract.exchange = "ISLAND".to_string(); 125 | //stkcontract] 126 | contract 127 | } 128 | 129 | //================================================================================================== 130 | pub fn usstock_with_primary_exch() -> Contract { 131 | let mut contract = Contract::default(); 132 | contract.symbol = "MSFT".to_string(); 133 | contract.sec_type = "STK".to_string(); 134 | contract.currency = "USD".to_string(); 135 | contract.exchange = "SMART".to_string(); 136 | //Specify the Primary Exchange attribute to avoid contract ambiguity 137 | //(there is an ambiguity because there is also a MSFT contract with primary exchange = "AEB") 138 | contract.primary_exchange = "ISLAND".to_string(); 139 | //stkcontractwithprimary] 140 | contract 141 | } 142 | 143 | //================================================================================================== 144 | pub fn us_stock_at_smart() -> Contract { 145 | let mut contract = Contract::default(); 146 | contract.symbol = "MSFT".to_string(); 147 | contract.sec_type = "STK".to_string(); 148 | contract.currency = "USD".to_string(); 149 | contract.exchange = "SMART".to_string(); 150 | contract 151 | } 152 | 153 | //================================================================================================== 154 | pub fn us_option_contract() -> Contract { 155 | let mut contract = Contract::default(); 156 | contract.symbol = "GOOG".to_string(); 157 | contract.sec_type = "OPT".to_string(); 158 | contract.exchange = "SMART".to_string(); 159 | contract.currency = "USD".to_string(); 160 | contract.last_trade_date_or_contract_month = "20201218".to_string(); 161 | contract.strike = 1180.0; 162 | contract.right = "C".to_string(); 163 | contract.multiplier = "100".to_string(); 164 | 165 | contract 166 | } 167 | 168 | //================================================================================================== 169 | pub fn option_at_box() -> Contract { 170 | let mut contract = Contract::default(); 171 | contract.symbol = "GOOG".to_string(); 172 | contract.sec_type = "OPT".to_string(); 173 | contract.exchange = "BOX".to_string(); 174 | contract.currency = "USD".to_string(); 175 | contract.last_trade_date_or_contract_month = "20201218".to_string(); 176 | contract.strike = 1180.0; 177 | contract.right = "C".to_string(); 178 | contract.multiplier = "100".to_string(); 179 | 180 | contract 181 | } 182 | 183 | //================================================================================================== 184 | /// Option contracts require far more information since there are many 185 | /// contracts having the exact same attributes such as symbol, currency, 186 | /// strike, etc. This can be overcome by adding more details such as the 187 | //' trading class 188 | pub fn option_with_trading_class() -> Contract { 189 | let mut contract = Contract::default(); 190 | contract.symbol = "SANT".to_string(); 191 | contract.sec_type = "OPT".to_string(); 192 | contract.exchange = "MEFFRV".to_string(); 193 | contract.currency = "EUR".to_string(); 194 | contract.last_trade_date_or_contract_month = "20190621".to_string(); 195 | contract.strike = 7.5; 196 | contract.right = "C".to_string(); 197 | contract.multiplier = "100".to_string(); 198 | contract.trading_class = "SANEU".to_string(); 199 | 200 | contract 201 | } 202 | 203 | //================================================================================================== 204 | /// Using the contract's own symbol (local_symbol) can greatly simplify a 205 | /// contract description. Watch out for the spaces within the local symbol! 206 | pub fn option_with_local_symbol() -> Contract { 207 | let mut contract = Contract::default(); 208 | //Watch out for the spaces within the local symbol! 209 | contract.local_symbol = "C DBK DEC 20 1600".to_string(); 210 | contract.sec_type = "OPT".to_string(); 211 | contract.exchange = "DTB".to_string(); 212 | contract.currency = "EUR".to_string(); 213 | 214 | contract 215 | } 216 | 217 | //================================================================================================== 218 | /// Dutch Warrants (IOPTs) can be defined using the local symbol or conid 219 | pub fn dutch_warrant() -> Contract { 220 | let mut contract = Contract::default(); 221 | contract.local_symbol = "B881G".to_string(); 222 | contract.sec_type = "IOPT".to_string(); 223 | contract.exchange = "SBF".to_string(); 224 | contract.currency = "EUR".to_string(); 225 | 226 | contract 227 | } 228 | 229 | //================================================================================================== 230 | /// Future contracts also require an expiration date but are less 231 | /// complicated than options. 232 | pub fn simple_future() -> Contract { 233 | let mut contract = Contract::default(); 234 | contract.symbol = "ES".to_string(); 235 | contract.sec_type = "FUT".to_string(); 236 | contract.exchange = "GLOBEX".to_string(); 237 | contract.currency = "USD".to_string(); 238 | contract.last_trade_date_or_contract_month = "202009".to_string(); 239 | 240 | contract 241 | } 242 | 243 | //================================================================================================== 244 | /// Rather than giving expiration dates we can also provide the local symbol 245 | /// attributes such as symbol, currency, strike, etc. 246 | pub fn future_with_local_symbol() -> Contract { 247 | let mut contract = Contract::default(); 248 | contract.sec_type = "FUT".to_string(); 249 | contract.exchange = "GLOBEX".to_string(); 250 | contract.currency = "USD".to_string(); 251 | contract.local_symbol = "ESU0".to_string(); 252 | 253 | contract 254 | } 255 | 256 | //================================================================================================== 257 | pub fn future_with_multiplier() -> Contract { 258 | let mut contract = Contract::default(); 259 | contract.symbol = "DAX".to_string(); 260 | contract.sec_type = "FUT".to_string(); 261 | contract.exchange = "DTB".to_string(); 262 | contract.currency = "EUR".to_string(); 263 | contract.last_trade_date_or_contract_month = "201903".to_string(); 264 | contract.multiplier = "5".to_string(); 265 | 266 | contract 267 | } 268 | 269 | //================================================================================================== 270 | /// Note the space in the symbol! 271 | pub fn wrong_contract() -> Contract { 272 | let mut contract = Contract::default(); 273 | contract.symbol = " IJR ".to_string(); 274 | contract.con_id = 9579976; 275 | contract.sec_type = "STK".to_string(); 276 | contract.exchange = "SMART".to_string(); 277 | contract.currency = "USD".to_string(); 278 | contract 279 | } 280 | 281 | //================================================================================================== 282 | pub fn futures_on_options() -> Contract { 283 | let mut contract = Contract::default(); 284 | contract.symbol = "ES".to_string(); 285 | contract.sec_type = "FOP".to_string(); 286 | contract.exchange = "GLOBEX".to_string(); 287 | contract.currency = "USD".to_string(); 288 | contract.last_trade_date_or_contract_month = "20190315".to_string(); 289 | contract.strike = 2900.0; 290 | contract.right = "C".to_string(); 291 | contract.multiplier = "50".to_string(); 292 | 293 | contract 294 | } 295 | 296 | //================================================================================================== 297 | /// It is also possible to deine contracts based on their ISIN (IBKR STK 298 | /// sample). 299 | pub fn by_isin() -> Contract { 300 | let mut contract = Contract::default(); 301 | contract.sec_id_type = "ISIN".to_string(); 302 | contract.sec_id = "US45841N1072".to_string(); 303 | contract.exchange = "SMART".to_string(); 304 | contract.currency = "USD".to_string(); 305 | contract.sec_type = "STK".to_string(); 306 | contract 307 | } 308 | 309 | //================================================================================================== 310 | /// Or their con_id (EUR.uSD sample). 311 | /// Note: passing a contract containing the con_id can cause problems if one of 312 | /// the other provided attributes does not match 100% with what is in IB's 313 | /// database. This is particularly important for contracts such as Bonds which 314 | /// may change their description from one day to another. 315 | /// If the con_id is provided, it is best not to give too much information as 316 | /// in the example below. 317 | pub fn by_con_id() -> Contract { 318 | let mut contract = Contract::default(); 319 | contract.sec_type = "CASH".to_string(); 320 | contract.con_id = 12087792; 321 | contract.exchange = "IDEALPRO".to_string(); 322 | contract 323 | } 324 | 325 | //================================================================================================== 326 | /// Ambiguous contracts are great to use with 327 | /// Contract::req_contract_details. This way 328 | /// you can query the whole option chain for an underlying. Bear in mind that 329 | /// there are pacing mechanisms in place which will delay any further responses 330 | /// from the TWS to prevent abuse. 331 | pub fn option_for_query() -> Contract { 332 | let mut contract = Contract::default(); 333 | contract.symbol = "FISV".to_string(); 334 | contract.sec_type = "OPT".to_string(); 335 | contract.exchange = "SMART".to_string(); 336 | contract.currency = "USD".to_string(); 337 | 338 | contract 339 | } 340 | 341 | //================================================================================================== 342 | pub fn option_combo_contract() -> Contract { 343 | let mut contract = Contract::default(); 344 | contract.symbol = "DBK".to_string(); 345 | contract.sec_type = "BAG".to_string(); 346 | contract.currency = "EUR".to_string(); 347 | contract.exchange = "DTB".to_string(); 348 | 349 | let mut leg1 = ComboLeg::default(); 350 | leg1.con_id = 317960956; //DBK JUN 21 2019 C 351 | leg1.ratio = 1.0; 352 | leg1.action = "BUY".to_string(); 353 | leg1.exchange = "DTB".to_string(); 354 | 355 | let mut leg2 = ComboLeg::default(); 356 | leg2.con_id = 334216780; //DBK MAR 15 2019 C 357 | leg2.ratio = 1.0; 358 | leg2.action = "SELL".to_string(); 359 | leg2.exchange = "DTB".to_string(); 360 | 361 | contract.combo_legs = vec![]; 362 | contract.combo_legs.push(leg1); 363 | contract.combo_legs.push(leg2); 364 | //bagoptcontract] 365 | contract 366 | } 367 | 368 | //================================================================================================== 369 | /// STK Combo contract 370 | /// Leg 1: 43645865 - IBKR's STK 371 | /// Leg 2: 9408 - McDonald's STK 372 | pub fn stock_combo_contract() -> Contract { 373 | let mut contract = Contract::default(); 374 | contract.symbol = "IBKR,MCD".to_string(); 375 | contract.sec_type = "BAG".to_string(); 376 | contract.currency = "USD".to_string(); 377 | contract.exchange = "SMART".to_string(); 378 | 379 | let mut leg1 = ComboLeg::default(); 380 | leg1.con_id = 43645865; //IBKR STK 381 | leg1.ratio = 1.0; 382 | leg1.action = "BUY".to_string(); 383 | leg1.exchange = "SMART".to_string(); 384 | 385 | let mut leg2 = ComboLeg::default(); 386 | leg2.con_id = 9408; //MCD STK 387 | leg2.ratio = 1.0; 388 | leg2.action = "SELL".to_string(); 389 | leg2.exchange = "SMART".to_string(); 390 | 391 | contract.combo_legs = vec![]; 392 | contract.combo_legs.push(leg1); 393 | contract.combo_legs.push(leg2); 394 | 395 | contract 396 | } 397 | 398 | //================================================================================================== 399 | /// CBOE volatility Index Future combo contract 400 | pub fn future_combo_contract() -> Contract { 401 | let mut contract = Contract::default(); 402 | contract.symbol = "VIX".to_string(); 403 | contract.sec_type = "BAG".to_string(); 404 | contract.currency = "USD".to_string(); 405 | contract.exchange = "CFE".to_string(); 406 | 407 | let mut leg1 = ComboLeg::default(); 408 | leg1.con_id = 438391466; // VIX FUT 201903 409 | leg1.ratio = 1.0; 410 | leg1.action = "BUY".to_string(); 411 | leg1.exchange = "CFE".to_string(); 412 | leg1.exempt_code = -1; 413 | leg1.open_close = PositionType::SamePos; 414 | 415 | let mut leg2 = ComboLeg::default(); 416 | leg2.con_id = 394987014; // VIX FUT 201904 417 | leg2.ratio = 1.0; 418 | leg2.action = "SELL".to_string(); 419 | leg2.exchange = "CFE".to_string(); 420 | leg2.exempt_code = -1; 421 | leg2.open_close = PositionType::SamePos; 422 | 423 | contract.combo_legs = vec![]; 424 | contract.combo_legs.push(leg1); 425 | contract.combo_legs.push(leg2); 426 | 427 | contract 428 | } 429 | 430 | //================================================================================================== 431 | pub fn smart_future_combo_contract() -> Contract { 432 | let mut contract = Contract::default(); 433 | contract.symbol = "WTI".to_string(); // WTI,COIL spread. Symbol can be defined as first leg symbol ("WTI") or currency ("USD") 434 | contract.sec_type = "BAG".to_string(); 435 | contract.currency = "USD".to_string(); 436 | contract.exchange = "SMART".to_string(); 437 | 438 | let mut leg1 = ComboLeg::default(); 439 | leg1.con_id = 55928698; // WTI future June 2017 440 | leg1.ratio = 1.0; 441 | leg1.action = "BUY".to_string(); 442 | leg1.exchange = "IPE".to_string(); 443 | 444 | let mut leg2 = ComboLeg::default(); 445 | leg2.con_id = 55850663; // COIL future June 2017 446 | leg2.ratio = 1.0; 447 | leg2.action = "SELL".to_string(); 448 | leg2.exchange = "IPE".to_string(); 449 | 450 | contract.combo_legs = vec![]; 451 | contract.combo_legs.push(leg1); 452 | contract.combo_legs.push(leg2); 453 | 454 | contract 455 | } 456 | 457 | //================================================================================================== 458 | pub fn inter_cmdty_futures_contract() -> Contract { 459 | let mut contract = Contract::default(); 460 | contract.symbol = "CL.BZ".to_string(); //symbol is 'local symbol' of intercommodity spread. 461 | contract.sec_type = "BAG".to_string(); 462 | contract.currency = "USD".to_string(); 463 | contract.exchange = "NYMEX".to_string(); 464 | 465 | let mut leg1 = ComboLeg::default(); 466 | leg1.con_id = 47207310; //CL Dec'16 @NYMEX 467 | leg1.ratio = 1.0; 468 | leg1.action = "BUY".to_string(); 469 | leg1.exchange = "NYMEX".to_string(); 470 | 471 | let mut leg2 = ComboLeg::default(); 472 | leg2.con_id = 47195961; //BZ Dec'16 @NYMEX 473 | leg2.ratio = 1.0; 474 | leg2.action = "SELL".to_string(); 475 | leg2.exchange = "NYMEX".to_string(); 476 | 477 | contract.combo_legs = vec![]; 478 | contract.combo_legs.push(leg1); 479 | contract.combo_legs.push(leg2); 480 | 481 | contract 482 | } 483 | 484 | //================================================================================================== 485 | pub fn news_feed_for_query() -> Contract { 486 | let mut contract = Contract::default(); 487 | contract.sec_type = "NEWS".to_string(); 488 | contract.exchange = "BRFG".to_string(); //Briefing Trader 489 | 490 | contract 491 | } 492 | 493 | //================================================================================================== 494 | pub fn brfgbroadtape_news_feed() -> Contract { 495 | let mut contract = Contract::default(); 496 | contract.symbol = "BRFG:BRFG_ALL".to_string(); 497 | contract.sec_type = "NEWS".to_string(); 498 | contract.exchange = "BRFG".to_string(); 499 | 500 | contract 501 | } 502 | 503 | //================================================================================================== 504 | pub fn djnlbroadtape_news_feed() -> Contract { 505 | let mut contract = Contract::default(); 506 | contract.symbol = "DJNL:DJNL_ALL".to_string(); 507 | contract.sec_type = "NEWS".to_string(); 508 | contract.exchange = "DJNL".to_string(); 509 | 510 | contract 511 | } 512 | 513 | //================================================================================================== 514 | pub fn djtopbroadtape_news_feed() -> Contract { 515 | let mut contract = Contract::default(); 516 | contract.symbol = "DJTOP:ASIAPAC".to_string(); 517 | contract.sec_type = "NEWS".to_string(); 518 | contract.exchange = "DJTOP".to_string(); 519 | 520 | contract 521 | } 522 | 523 | //================================================================================================== 524 | pub fn brfupdnbroadtape_news_feed() -> Contract { 525 | let mut contract = Contract::default(); 526 | contract.symbol = "BRFUPDN:BRF_ALL".to_string(); 527 | contract.sec_type = "NEWS".to_string(); 528 | contract.exchange = "BRFUPDN".to_string(); 529 | 530 | contract 531 | } 532 | 533 | //================================================================================================== 534 | pub fn cont_fut() -> Contract { 535 | let mut contract = Contract::default(); 536 | contract.symbol = "ES".to_string(); 537 | contract.sec_type = "CONTFUT".to_string(); 538 | contract.exchange = "GLOBEX".to_string(); 539 | 540 | contract 541 | } 542 | 543 | //================================================================================================== 544 | pub fn cont_and_expiring_fut() -> Contract { 545 | let mut contract = Contract::default(); 546 | contract.symbol = "ES".to_string(); 547 | contract.sec_type = "FUT+CONTFUT".to_string(); 548 | contract.exchange = "GLOBEX".to_string(); 549 | 550 | contract 551 | } 552 | 553 | //================================================================================================== 554 | pub fn jefferies_contract() -> Contract { 555 | let mut contract = Contract::default(); 556 | contract.symbol = "AAPL".to_string(); 557 | contract.sec_type = "STK".to_string(); 558 | contract.exchange = "JEFFALGO".to_string(); 559 | contract.currency = "USD".to_string(); 560 | 561 | contract 562 | } 563 | 564 | //================================================================================================== 565 | pub fn csfbcontract() -> Contract { 566 | let mut contract = Contract::default(); 567 | contract.symbol = "IBKR".to_string(); 568 | contract.sec_type = "STK".to_string(); 569 | contract.exchange = "CSFBALGO".to_string(); 570 | contract.currency = "USD".to_string(); 571 | 572 | contract 573 | } 574 | 575 | //================================================================================================== 576 | pub fn usstock_cfd() -> Contract { 577 | let mut contract = Contract::default(); 578 | contract.symbol = "IBM".to_string(); 579 | contract.sec_type = "cfd".to_string(); 580 | contract.currency = "USD".to_string(); 581 | contract.exchange = "SMART".to_string(); 582 | 583 | contract 584 | } 585 | 586 | //================================================================================================== 587 | pub fn european_stock_cfd() -> Contract { 588 | let mut contract = Contract::default(); 589 | contract.symbol = "BMW".to_string(); 590 | contract.sec_type = "cfd".to_string(); 591 | contract.currency = "EUR".to_string(); 592 | contract.exchange = "SMART".to_string(); 593 | 594 | contract 595 | } 596 | 597 | //================================================================================================== 598 | pub fn cash_cfd() -> Contract { 599 | let mut contract = Contract::default(); 600 | contract.symbol = "EUR".to_string(); 601 | contract.sec_type = "cfd".to_string(); 602 | contract.currency = "USD".to_string(); 603 | contract.exchange = "SMART".to_string(); 604 | 605 | contract 606 | } 607 | 608 | //================================================================================================== 609 | pub fn qbalgo_contract() -> Contract { 610 | let mut contract = Contract::default(); 611 | contract.symbol = "ES".to_string(); 612 | contract.sec_type = "FUT".to_string(); 613 | contract.exchange = "QBALGO".to_string(); 614 | contract.currency = "USD".to_string(); 615 | contract.last_trade_date_or_contract_month = "202009".to_string(); 616 | 617 | contract 618 | } 619 | -------------------------------------------------------------------------------- /src/examples/fa_allocation_samples.rs: -------------------------------------------------------------------------------- 1 | //! Example FA allocation XML request data 2 | 3 | ///Replace all with your own accountIds 4 | 5 | //================================================================================================== 6 | pub const FA_ONE_GROUP: &str = " \ 7 | \ 8 | \ 9 | Equal_Quantity \ 10 | \ 11 | DU119915 \ 12 | DU119916 \ 13 | \ 14 | EqualQuantity \ 15 | \ 16 | "; 17 | 18 | //================================================================================================== 19 | pub const FA_TWO_GROUPS: &str = " \ 20 | \ 21 | \ 22 | Equal_Quantity \ 23 | \ 24 | DU119915 \ 25 | DU119916 \ 26 | \ 27 | EqualQuantity \ 28 | \ 29 | \ 30 | Pct_Change \ 31 | 32 | DU119915 \ 33 | DU119916 \ 34 | \ 35 | PctChange \ 36 | \ 37 | "; 38 | 39 | //================================================================================================== 40 | pub const FA_ONE_PROFILE: &str = " \ 41 | \ 42 | \ 43 | Percent_60_40 \ 44 | 1 \ 45 | \ 46 | 47 | DU119915 \ 48 | 60.0 \ 49 | \ 50 | \ 51 | DU119916 \ 52 | 40.0 \ 53 | \ 54 | \ 55 | \ 56 | "; 57 | 58 | //================================================================================================== 59 | pub const FA_TWO_PROFILES: &str = " \ 60 | \ 61 | \ 62 | Percent_60_40 \ 63 | 1 \ 64 | \ 65 | \ 66 | DU119915 \ 67 | 60.0 \ 68 | \ 69 | \ 70 | DU119916 \ 71 | 40.0 \ 72 | \ 73 | \ 74 | \ 75 | \ 76 | Ratios_2_1 \ 77 | 1 \ 78 | \ 79 | \ 80 | DU119915 \ 81 | 2.0 \ 82 | \ 83 | 84 | DU119916 \ 85 | 1.0 \ 86 | \ 87 | \ 88 | \ 89 | "; 90 | -------------------------------------------------------------------------------- /src/examples/mod.rs: -------------------------------------------------------------------------------- 1 | //! Examples of populating structs for contacts, orders, scanner subscriptions and other requests 2 | pub mod contract_samples; 3 | pub mod defaults; 4 | pub mod fa_allocation_samples; 5 | pub mod order_samples; 6 | pub mod scanner_subscription_samples; 7 | pub mod test_helpers; 8 | -------------------------------------------------------------------------------- /src/examples/scanner_subscription_samples.rs: -------------------------------------------------------------------------------- 1 | //! Scanner subscription examples 2 | use crate::core::scanner::ScannerSubscription; 3 | 4 | ///Hot US stocks by volume 5 | pub fn hot_usstk_by_volume() -> ScannerSubscription { 6 | let mut scan_sub = ScannerSubscription::default(); 7 | scan_sub.instrument = "STK".to_string(); 8 | scan_sub.location_code = "STK.US.MAJOR".to_string(); 9 | scan_sub.scan_code = "HOT_BY_VOLUME".to_string(); 10 | scan_sub 11 | } 12 | 13 | /// Top % gainers at IBIS 14 | pub fn top_percent_gainers_ibis() -> ScannerSubscription { 15 | let mut scan_sub = ScannerSubscription::default(); 16 | scan_sub.instrument = "STOCK.EU".to_string(); 17 | scan_sub.location_code = "STK.EU.IBIS".to_string(); 18 | scan_sub.scan_code = "TOP_PERC_GAIN".to_string(); 19 | scan_sub 20 | } 21 | 22 | /// Most active futures at SOFFEX 23 | pub fn most_active_fut_soffex() -> ScannerSubscription { 24 | let mut scan_sub = ScannerSubscription::default(); 25 | scan_sub.instrument = "FUT.EU".to_string(); 26 | scan_sub.location_code = "FUT.EU.SOFFEX".to_string(); 27 | scan_sub.scan_code = "MOST_ACTIVE".to_string(); 28 | scan_sub 29 | } 30 | 31 | /// High option volume P/C ratio US indexes 32 | pub fn high_opt_volume_pcratio_usindexes() -> ScannerSubscription { 33 | let mut scan_sub = ScannerSubscription::default(); 34 | scan_sub.instrument = "IND.US".to_string(); 35 | scan_sub.location_code = "IND.US".to_string(); 36 | scan_sub.scan_code = "HIGH_OPT_VOLUME_PUT_CALL_RATIO".to_string(); 37 | scan_sub 38 | } 39 | 40 | /// Combination order latest trade 41 | pub fn complex_orders_and_trades() -> ScannerSubscription { 42 | let mut scan_sub = ScannerSubscription::default(); 43 | scan_sub.instrument = "NATCOMB".to_string(); 44 | scan_sub.location_code = "NATCOMB.OPT.US".to_string(); 45 | scan_sub.scan_code = "COMBO_LATEST_TRADE".to_string(); 46 | scan_sub 47 | } 48 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Lib for sending requests to and processing responses from Interactive Broker's Trader Workstation or IB Gateway 2 | //! 3 | //! For usage of this library, please see the example implementation in [src/examples/test_helpers/manual_tests.rs](https://github.com/sparkstartconsulting/IBKR-API-Rust/blob/fix_docs_add_tests/src/examples/test_helpers/manual_tests.rs) 4 | //! 5 | //! The main structs and traits that clients will use are [**EClient**](https://github.com/sparkstartconsulting/IBKR-API-Rust/blob/fix_docs_add_tests/src/core/client.rs) , a struct that is responsible for 6 | //! connecting to TWS or IB Gateway and sending requests, and [**Wrapper**](https://github.com/sparkstartconsulting/IBKR-API-Rust/blob/fix_docs_add_tests/src/core/wrapper.rs), a trait that clients will implement that declares callback functions 7 | //! that get called when the application receives messages from the server. 8 | //! 9 | //! In the example below, TWS will send the next valid order ID when the sample application connects. This will cause the ***Wrapper*** callback method 10 | //! ***next_valid_id*** to be called, which will start sending test requests to TWS (see the 11 | //! ***start_requests*** method in ***TestWrapper*** which is called by ***next_valid_id***). 12 | //! 13 | //! ```no_run 14 | //! use twsapi::core::errors::IBKRApiLibError; 15 | //! use twsapi::core::client::EClient; 16 | //! use twsapi::core::streamer::{Streamer, TcpStreamer}; 17 | //! use std::time::Duration; 18 | //! use twsapi::examples::test_helpers::TestWrapper; 19 | //! use std::sync::{Arc, Mutex}; 20 | //! use std::thread; 21 | //! 22 | //! fn main() -> Result<(), IBKRApiLibError> { 23 | //! 24 | //! let wrapper = Arc::new(Mutex::new(TestWrapper::::new())); 25 | //! let app = Arc::new(Mutex::new(EClient::new(wrapper.clone()))); 26 | //! 27 | //! println!("getting connection..."); 28 | //! 29 | //! wrapper.lock().expect("Wrapper mutex was poisoned").client = Option::from(app.clone()); 30 | //! 31 | //! //use port 7497 for TWS or 4002 for IB Gateway, depending on the port you have set 32 | //! app.lock() 33 | //! .expect("EClient mutex was poisoned") 34 | //! .connect("127.0.0.1", 4002, 0)?; 35 | //! 36 | //! //4002 37 | //! thread::sleep(Duration::new(18600, 0)); 38 | //! 39 | //! Ok(()) 40 | //! } 41 | //! ``` 42 | pub mod core; 43 | pub mod examples; 44 | mod tests; 45 | 46 | pub use bigdecimal::BigDecimal; -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod test_eclient; 2 | pub(crate) mod test_messages; 3 | -------------------------------------------------------------------------------- /src/tests/test_eclient.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::core::client::{ConnStatus, EClient, POISONED_MUTEX}; 4 | 5 | use crate::core::{ 6 | common::{ 7 | BarData, CommissionReport, DepthMktDataDescription, FaDataType, FamilyCode, 8 | HistogramData, HistoricalTick, HistoricalTickBidAsk, HistoricalTickLast, NewsProvider, 9 | PriceIncrement, RealTimeBar, SmartComponent, TickAttrib, TickAttribBidAsk, 10 | TickAttribLast, TickByTickType, TickType, 11 | }, 12 | contract::{Contract, ContractDescription, ContractDetails, DeltaNeutralContract}, 13 | execution::{Execution, ExecutionFilter}, 14 | order::{Order, SoftDollarTier}, 15 | streamer::{Streamer, TestStreamer}, 16 | wrapper::Wrapper, 17 | }; 18 | use crate::{ 19 | core::{ 20 | errors::IBKRApiLibError, 21 | messages::{read_fields, read_msg, OutgoingMessageIds}, 22 | order::OrderState, 23 | }, 24 | examples::contract_samples::simple_future, 25 | }; 26 | use std::sync::{Arc, Mutex}; 27 | 28 | pub struct DummyTestWrapper {} 29 | 30 | impl DummyTestWrapper { 31 | fn new() -> Self { 32 | DummyTestWrapper {} 33 | } 34 | } 35 | 36 | impl Wrapper for DummyTestWrapper { 37 | fn error(&mut self, _req_id: i32, _error_code: i32, _error_string: &str) { 38 | todo!() 39 | } 40 | fn win_error(&mut self, _text: &str, _last_error: i32) { 41 | todo!() 42 | } 43 | fn connect_ack(&mut self) { 44 | todo!() 45 | } 46 | fn market_data_type(&mut self, _req_id: i32, _market_data_type: i32) { 47 | todo!() 48 | } 49 | fn tick_price( 50 | &mut self, 51 | _req_id: i32, 52 | _tick_type: TickType, 53 | _price: f64, 54 | _attrib: TickAttrib, 55 | ) { 56 | todo!() 57 | } 58 | fn tick_size(&mut self, _req_id: i32, _tick_type: TickType, _size: i32) { 59 | todo!() 60 | } 61 | fn tick_snapshot_end(&mut self, _req_id: i32) { 62 | todo!() 63 | } 64 | fn tick_generic(&mut self, _req_id: i32, _tick_type: TickType, _value: f64) { 65 | todo!() 66 | } 67 | fn tick_string(&mut self, _req_id: i32, _tick_type: TickType, _value: &str) { 68 | todo!() 69 | } 70 | fn tick_efp( 71 | &mut self, 72 | _req_id: i32, 73 | _tick_type: TickType, 74 | _basis_points: f64, 75 | _formatted_basis_points: &str, 76 | _implied_future: f64, 77 | _hold_days: i32, 78 | _future_last_trade_date: &str, 79 | _dividend_impact: f64, 80 | _dividends_to_last_trade_date: f64, 81 | ) { 82 | todo!() 83 | } 84 | fn order_status( 85 | &mut self, 86 | _order_id: i32, 87 | _status: &str, 88 | _filled: f64, 89 | _remaining: f64, 90 | _avg_fill_price: f64, 91 | _perm_id: i32, 92 | _parent_id: i32, 93 | _last_fill_price: f64, 94 | _client_id: i32, 95 | _why_held: &str, 96 | _mkt_cap_price: f64, 97 | ) { 98 | todo!() 99 | } 100 | fn open_order( 101 | &mut self, 102 | _order_id: i32, 103 | _contract: Contract, 104 | _order: Order, 105 | _order_state: OrderState, 106 | ) { 107 | todo!() 108 | } 109 | fn open_order_end(&mut self) { 110 | todo!() 111 | } 112 | fn connection_closed(&mut self) { 113 | todo!() 114 | } 115 | fn update_account_value( 116 | &mut self, 117 | _key: &str, 118 | _val: &str, 119 | _currency: &str, 120 | _account_name: &str, 121 | ) { 122 | todo!() 123 | } 124 | fn update_portfolio( 125 | &mut self, 126 | _contract: Contract, 127 | _position: f64, 128 | _market_price: f64, 129 | _market_value: f64, 130 | _average_cost: f64, 131 | _unrealized_pnl: f64, 132 | _realized_pnl: f64, 133 | _account_name: &str, 134 | ) { 135 | todo!() 136 | } 137 | fn update_account_time(&mut self, _time_stamp: &str) { 138 | todo!() 139 | } 140 | fn account_download_end(&mut self, _account_name: &str) { 141 | todo!() 142 | } 143 | fn next_valid_id(&mut self, _order_id: i32) { 144 | todo!() 145 | } 146 | fn contract_details(&mut self, _req_id: i32, _contract_details: ContractDetails) { 147 | todo!() 148 | } 149 | fn bond_contract_details(&mut self, _req_id: i32, _contract_details: ContractDetails) { 150 | todo!() 151 | } 152 | fn contract_details_end(&mut self, _req_id: i32) { 153 | todo!() 154 | } 155 | fn exec_details(&mut self, _req_id: i32, _contract: Contract, _execution: Execution) { 156 | todo!() 157 | } 158 | fn exec_details_end(&mut self, _req_id: i32) { 159 | todo!() 160 | } 161 | fn update_mkt_depth( 162 | &mut self, 163 | _req_id: i32, 164 | _position: i32, 165 | _operation: i32, 166 | _side: i32, 167 | _price: f64, 168 | _size: i32, 169 | ) { 170 | todo!() 171 | } 172 | fn update_mkt_depth_l2( 173 | &mut self, 174 | _req_id: i32, 175 | _position: i32, 176 | _market_maker: &str, 177 | _operation: i32, 178 | _side: i32, 179 | _price: f64, 180 | _size: i32, 181 | _is_smart_depth: bool, 182 | ) { 183 | todo!() 184 | } 185 | fn update_news_bulletin( 186 | &mut self, 187 | _msg_id: i32, 188 | _msg_type: i32, 189 | _news_message: &str, 190 | _origin_exch: &str, 191 | ) { 192 | todo!() 193 | } 194 | fn managed_accounts(&mut self, _accounts_list: &str) { 195 | todo!() 196 | } 197 | fn receive_fa(&mut self, _fa_data: FaDataType, _cxml: &str) { 198 | todo!() 199 | } 200 | fn historical_data(&mut self, _req_id: i32, _bar: BarData) { 201 | todo!() 202 | } 203 | fn historical_data_end(&mut self, _req_id: i32, _start: &str, _end: &str) { 204 | todo!() 205 | } 206 | fn scanner_parameters(&mut self, _xml: &str) { 207 | todo!() 208 | } 209 | fn scanner_data( 210 | &mut self, 211 | _req_id: i32, 212 | _rank: i32, 213 | _contract_details: ContractDetails, 214 | _distance: &str, 215 | _benchmark: &str, 216 | _projection: &str, 217 | _legs_str: &str, 218 | ) { 219 | todo!() 220 | } 221 | fn scanner_data_end(&mut self, _req_id: i32) { 222 | todo!() 223 | } 224 | fn realtime_bar(&mut self, _req_id: i32, _bar: RealTimeBar) { 225 | todo!() 226 | } 227 | fn current_time(&mut self, _time: i64) { 228 | todo!() 229 | } 230 | fn fundamental_data(&mut self, _req_id: i32, _data: &str) { 231 | todo!() 232 | } 233 | fn delta_neutral_validation( 234 | &mut self, 235 | _req_id: i32, 236 | _delta_neutral_contract: DeltaNeutralContract, 237 | ) { 238 | todo!() 239 | } 240 | fn commission_report(&mut self, _commission_report: CommissionReport) { 241 | todo!() 242 | } 243 | fn position( 244 | &mut self, 245 | _account: &str, 246 | _contract: Contract, 247 | _position: f64, 248 | _avg_cost: f64, 249 | ) { 250 | todo!() 251 | } 252 | fn position_end(&mut self) { 253 | todo!() 254 | } 255 | fn account_summary( 256 | &mut self, 257 | _req_id: i32, 258 | _account: &str, 259 | _tag: &str, 260 | _value: &str, 261 | _currency: &str, 262 | ) { 263 | todo!() 264 | } 265 | fn account_summary_end(&mut self, _req_id: i32) { 266 | todo!() 267 | } 268 | fn verify_message_api(&mut self, _api_data: &str) { 269 | todo!() 270 | } 271 | fn verify_completed(&mut self, _is_successful: bool, _error_text: &str) { 272 | todo!() 273 | } 274 | fn verify_and_auth_message_api(&mut self, _api_data: &str, _xyz_challange: &str) { 275 | todo!() 276 | } 277 | fn verify_and_auth_completed(&mut self, _is_successful: bool, _error_text: &str) { 278 | todo!() 279 | } 280 | fn display_group_list(&mut self, _req_id: i32, _groups: &str) { 281 | todo!() 282 | } 283 | fn display_group_updated(&mut self, _req_id: i32, _contract_info: &str) { 284 | todo!() 285 | } 286 | fn position_multi( 287 | &mut self, 288 | _req_id: i32, 289 | _account: &str, 290 | _model_code: &str, 291 | _contract: Contract, 292 | _pos: f64, 293 | _avg_cost: f64, 294 | ) { 295 | todo!() 296 | } 297 | fn position_multi_end(&mut self, _req_id: i32) { 298 | todo!() 299 | } 300 | fn account_update_multi( 301 | &mut self, 302 | _req_id: i32, 303 | _account: &str, 304 | _model_code: &str, 305 | _key: &str, 306 | _value: &str, 307 | _currency: &str, 308 | ) { 309 | todo!() 310 | } 311 | fn account_update_multi_end(&mut self, _req_id: i32) { 312 | todo!() 313 | } 314 | fn tick_option_computation( 315 | &mut self, 316 | _req_id: i32, 317 | _tick_type: TickType, 318 | _implied_vol: f64, 319 | _delta: f64, 320 | _opt_price: f64, 321 | _pv_dividend: f64, 322 | _gamma: f64, 323 | _vega: f64, 324 | _theta: f64, 325 | _und_price: f64, 326 | ) { 327 | todo!() 328 | } 329 | fn security_definition_option_parameter( 330 | &mut self, 331 | _req_id: i32, 332 | _exchange: &str, 333 | _underlying_con_id: i32, 334 | _trading_class: &str, 335 | _multiplier: &str, 336 | _expirations: std::collections::HashSet, 337 | _strikes: std::collections::HashSet, 338 | ) { 339 | todo!() 340 | } 341 | fn security_definition_option_parameter_end(&mut self, _req_id: i32) { 342 | todo!() 343 | } 344 | fn soft_dollar_tiers(&mut self, _req_id: i32, _tiers: Vec) { 345 | todo!() 346 | } 347 | fn family_codes(&mut self, _family_codes: Vec) { 348 | todo!() 349 | } 350 | fn symbol_samples( 351 | &mut self, 352 | _req_id: i32, 353 | _contract_descriptions: Vec, 354 | ) { 355 | todo!() 356 | } 357 | fn mkt_depth_exchanges( 358 | &mut self, 359 | _depth_mkt_data_descriptions: Vec, 360 | ) { 361 | todo!() 362 | } 363 | fn tick_news( 364 | &mut self, 365 | _ticker_id: i32, 366 | _time_stamp: i32, 367 | _provider_code: &str, 368 | _article_id: &str, 369 | _headline: &str, 370 | _extra_data: &str, 371 | ) { 372 | todo!() 373 | } 374 | fn smart_components(&mut self, _req_id: i32, _smart_components: Vec) { 375 | todo!() 376 | } 377 | fn tick_req_params( 378 | &mut self, 379 | _ticker_id: i32, 380 | _min_tick: f64, 381 | _bbo_exchange: &str, 382 | _snapshot_permissions: i32, 383 | ) { 384 | todo!() 385 | } 386 | fn news_providers(&mut self, _news_providers: Vec) { 387 | todo!() 388 | } 389 | fn news_article(&mut self, _request_id: i32, _article_type: i32, _article_text: &str) { 390 | todo!() 391 | } 392 | fn historical_news( 393 | &mut self, 394 | _request_id: i32, 395 | _time: &str, 396 | _provider_code: &str, 397 | _article_id: &str, 398 | _headline: &str, 399 | ) { 400 | todo!() 401 | } 402 | fn historical_news_end(&mut self, _request_id: i32, _has_more: bool) { 403 | todo!() 404 | } 405 | fn head_timestamp(&mut self, _req_id: i32, _head_timestamp: &str) { 406 | todo!() 407 | } 408 | fn histogram_data(&mut self, _req_id: i32, _items: Vec) { 409 | todo!() 410 | } 411 | fn historical_data_update(&mut self, _req_id: i32, _bar: BarData) { 412 | todo!() 413 | } 414 | fn reroute_mkt_data_req(&mut self, _req_id: i32, _con_id: i32, _exchange: &str) { 415 | todo!() 416 | } 417 | fn reroute_mkt_depth_req(&mut self, _req_id: i32, _con_id: i32, _exchange: &str) { 418 | todo!() 419 | } 420 | fn market_rule(&mut self, _market_rule_id: i32, _price_increments: Vec) { 421 | todo!() 422 | } 423 | fn pnl( 424 | &mut self, 425 | _req_id: i32, 426 | _daily_pn_l: f64, 427 | _unrealized_pn_l: f64, 428 | _realized_pn_l: f64, 429 | ) { 430 | todo!() 431 | } 432 | fn pnl_single( 433 | &mut self, 434 | _req_id: i32, 435 | _pos: i32, 436 | _daily_pn_l: f64, 437 | _unrealized_pn_l: f64, 438 | _realized_pn_l: f64, 439 | _value: f64, 440 | ) { 441 | todo!() 442 | } 443 | fn historical_ticks(&mut self, _req_id: i32, _ticks: Vec, _done: bool) { 444 | todo!() 445 | } 446 | fn historical_ticks_bid_ask( 447 | &mut self, 448 | _req_id: i32, 449 | _ticks: Vec, 450 | _done: bool, 451 | ) { 452 | todo!() 453 | } 454 | fn historical_ticks_last( 455 | &mut self, 456 | _req_id: i32, 457 | _ticks: Vec, 458 | _done: bool, 459 | ) { 460 | todo!() 461 | } 462 | fn tick_by_tick_all_last( 463 | &mut self, 464 | _req_id: i32, 465 | _tick_type: TickByTickType, 466 | _time: i64, 467 | _price: f64, 468 | _size: i32, 469 | _tick_attrib_last: TickAttribLast, 470 | _exchange: &str, 471 | _special_conditions: &str, 472 | ) { 473 | todo!() 474 | } 475 | fn tick_by_tick_bid_ask( 476 | &mut self, 477 | _req_id: i32, 478 | _time: i64, 479 | _bid_price: f64, 480 | _ask_price: f64, 481 | _bid_size: i32, 482 | _ask_size: i32, 483 | _tick_attrib_bid_ask: TickAttribBidAsk, 484 | ) { 485 | todo!() 486 | } 487 | fn tick_by_tick_mid_point(&mut self, _req_id: i32, _time: i64, _mid_point: f64) { 488 | todo!() 489 | } 490 | fn order_bound(&mut self, _req_id: i32, _api_client_id: i32, _api_order_id: i32) { 491 | todo!() 492 | } 493 | fn completed_order( 494 | &mut self, 495 | _contract: Contract, 496 | _order: Order, 497 | _order_state: OrderState, 498 | ) { 499 | todo!() 500 | } 501 | fn completed_orders_end(&mut self) { 502 | todo!() 503 | } 504 | } 505 | 506 | //------------------------------------------------------------------------------------------------ 507 | trait ClientConnectForTest { 508 | fn connect_test(&mut self); 509 | } 510 | 511 | impl ClientConnectForTest for EClient { 512 | fn connect_test(&mut self) { 513 | *self.conn_state.lock().expect(POISONED_MUTEX) = ConnStatus::CONNECTED; 514 | let streamer = TestStreamer::new(); 515 | self.set_streamer(Option::from(Box::new(streamer) as Box)); 516 | self.server_version = 151; 517 | } 518 | } 519 | 520 | //------------------------------------------------------------------------------------------------ 521 | #[test] 522 | fn test_req_account_summary() -> Result<(), IBKRApiLibError> { 523 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 524 | let app = Arc::new(Mutex::new(EClient::::new( 525 | wrapper.clone(), 526 | ))); 527 | 528 | let version = 2; 529 | let req_id = 100; 530 | let group_name = "MyGroup"; 531 | let tags = "tag1:tag_value1, tag2:tag_value2"; 532 | let mut buf = Vec::::new(); 533 | 534 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 535 | 536 | locked_app.connect_test(); 537 | locked_app.req_account_summary(req_id, group_name, tags)?; 538 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 539 | 540 | let expected: [u8; 54] = [ 541 | 0, 0, 0, 50, 54, 50, 0, 50, 0, 49, 48, 48, 0, 77, 121, 71, 114, 111, 117, 112, 0, 116, 542 | 97, 103, 49, 58, 116, 97, 103, 95, 118, 97, 108, 117, 101, 49, 44, 32, 116, 97, 103, 543 | 50, 58, 116, 97, 103, 95, 118, 97, 108, 117, 101, 50, 0, 544 | ]; 545 | 546 | let msg_data = read_msg(buf.as_slice())?; 547 | 548 | let fields = read_fields(&msg_data.1); 549 | assert_eq!(expected.as_ref(), buf.as_slice()); 550 | assert_eq!( 551 | OutgoingMessageIds::ReqAccountSummary as u8, 552 | fields[0].parse::().unwrap() 553 | ); 554 | assert_eq!(version, fields[1].parse::().unwrap()); 555 | assert_eq!(req_id, fields[2].parse::().unwrap()); 556 | assert_eq!(group_name, fields[3]); 557 | assert_eq!(tags, fields[4]); 558 | 559 | Ok(()) 560 | } 561 | 562 | //------------------------------------------------------------------------------------------------ 563 | #[test] 564 | fn test_req_account_updates() -> Result<(), IBKRApiLibError> { 565 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 566 | let app = Arc::new(Mutex::new(EClient::::new( 567 | wrapper.clone(), 568 | ))); 569 | 570 | let version = 2; 571 | let subscribe = true; 572 | let acct_code = "D12345"; 573 | let mut buf = Vec::::new(); 574 | 575 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 576 | 577 | locked_app.connect_test(); 578 | locked_app.req_account_updates(subscribe, acct_code)?; 579 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 580 | 581 | let expected: [u8; 17] = [0, 0, 0, 13, 54, 0, 50, 0, 49, 0, 68, 49, 50, 51, 52, 53, 0]; 582 | 583 | let msg_data = read_msg(buf.as_slice())?; 584 | let fields = read_fields(&msg_data.1); 585 | assert_eq!(expected.as_ref(), buf.as_slice()); 586 | assert_eq!( 587 | OutgoingMessageIds::ReqAcctData as u8, 588 | fields[0].parse::().unwrap() 589 | ); 590 | assert_eq!(version, fields[1].parse::().unwrap()); 591 | assert_eq!(subscribe as i32, fields[2].parse::().unwrap()); 592 | assert_eq!(acct_code, fields[3]); 593 | 594 | Ok(()) 595 | } 596 | 597 | //------------------------------------------------------------------------------------------------ 598 | #[test] 599 | fn test_req_account_updates_multi() -> Result<(), IBKRApiLibError> { 600 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 601 | let app = Arc::new(Mutex::new(EClient::::new( 602 | wrapper.clone(), 603 | ))); 604 | 605 | let version = 1; 606 | let req_id = 101; 607 | let acct_code = "D12345"; 608 | let model_code = "ABC"; 609 | let ledger_and_nvl = true; 610 | let mut buf = Vec::::new(); 611 | 612 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 613 | 614 | locked_app.connect_test(); 615 | locked_app.req_account_updates_multi(req_id, acct_code, model_code, ledger_and_nvl)?; 616 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 617 | 618 | let expected: [u8; 26] = [ 619 | 0, 0, 0, 22, 55, 54, 0, 49, 0, 49, 48, 49, 0, 68, 49, 50, 51, 52, 53, 0, 65, 66, 67, 0, 620 | 49, 0, 621 | ]; 622 | 623 | let msg_data = read_msg(buf.as_slice())?; 624 | 625 | let fields = read_fields(&msg_data.1); 626 | assert_eq!(expected.as_ref(), buf.as_slice()); 627 | assert_eq!( 628 | OutgoingMessageIds::ReqAccountUpdatesMulti as u8, 629 | fields[0].parse::().unwrap() 630 | ); 631 | assert_eq!(version, fields[1].parse::().unwrap()); 632 | assert_eq!(req_id, fields[2].parse::().unwrap()); 633 | assert_eq!(acct_code, fields[3]); 634 | assert_eq!(model_code, fields[4]); 635 | assert_eq!(ledger_and_nvl as i32, fields[5].parse::().unwrap()); 636 | 637 | Ok(()) 638 | } 639 | 640 | //------------------------------------------------------------------------------------------------ 641 | #[test] 642 | fn test_req_all_open_orders() -> Result<(), IBKRApiLibError> { 643 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 644 | let app = Arc::new(Mutex::new(EClient::::new( 645 | wrapper.clone(), 646 | ))); 647 | 648 | let version = 1; 649 | 650 | let mut buf = Vec::::new(); 651 | 652 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 653 | 654 | locked_app.connect_test(); 655 | locked_app.req_all_open_orders()?; 656 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 657 | 658 | let expected: [u8; 9] = [0, 0, 0, 5, 49, 54, 0, 49, 0]; 659 | 660 | let msg_data = read_msg(buf.as_slice())?; 661 | let fields = read_fields(&msg_data.1); 662 | 663 | assert_eq!(expected.as_ref(), buf.as_slice()); 664 | assert_eq!( 665 | OutgoingMessageIds::ReqAllOpenOrders as u8, 666 | fields[0].parse::().unwrap() 667 | ); 668 | assert_eq!(version, fields[1].parse::().unwrap()); 669 | 670 | Ok(()) 671 | } 672 | 673 | //------------------------------------------------------------------------------------------------ 674 | #[test] 675 | fn test_req_auto_open_orders() -> Result<(), IBKRApiLibError> { 676 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 677 | let app = Arc::new(Mutex::new(EClient::::new( 678 | wrapper.clone(), 679 | ))); 680 | 681 | let version = 1; 682 | let auto_bind = true; 683 | let mut buf = Vec::::new(); 684 | 685 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 686 | 687 | locked_app.connect_test(); 688 | locked_app.req_auto_open_orders(auto_bind)?; 689 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 690 | 691 | let expected: [u8; 11] = [0, 0, 0, 7, 49, 53, 0, 49, 0, 49, 0]; 692 | 693 | let msg_data = read_msg(buf.as_slice())?; 694 | 695 | let fields = read_fields(&msg_data.1); 696 | 697 | assert_eq!(expected.as_ref(), buf.as_slice()); 698 | assert_eq!( 699 | OutgoingMessageIds::ReqAutoOpenOrders as u8, 700 | fields[0].parse::().unwrap() 701 | ); 702 | assert_eq!(version, fields[1].parse::().unwrap()); 703 | 704 | Ok(()) 705 | } 706 | 707 | //------------------------------------------------------------------------------------------------ 708 | #[test] 709 | fn test_req_completed_orders() -> Result<(), IBKRApiLibError> { 710 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 711 | let app = Arc::new(Mutex::new(EClient::::new( 712 | wrapper.clone(), 713 | ))); 714 | 715 | let version = 1; 716 | let api_only = true; 717 | let mut buf = Vec::::new(); 718 | 719 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 720 | 721 | locked_app.connect_test(); 722 | locked_app.req_completed_orders(api_only)?; 723 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 724 | 725 | let expected: [u8; 9] = [0, 0, 0, 5, 57, 57, 0, 49, 0]; 726 | 727 | let msg_data = read_msg(buf.as_slice())?; 728 | 729 | let fields = read_fields(&msg_data.1); 730 | 731 | assert_eq!(expected.as_ref(), buf.as_slice()); 732 | assert_eq!( 733 | OutgoingMessageIds::ReqCompletedOrders as u8, 734 | fields[0].parse::().unwrap() 735 | ); 736 | assert_eq!(version, fields[1].parse::().unwrap()); 737 | 738 | Ok(()) 739 | } 740 | 741 | //------------------------------------------------------------------------------------------------ 742 | #[test] 743 | fn test_req_contract_details() -> Result<(), IBKRApiLibError> { 744 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 745 | let app = Arc::new(Mutex::new(EClient::::new( 746 | wrapper.clone(), 747 | ))); 748 | 749 | let version = 8; 750 | let req_id = 102; 751 | let mut buf = Vec::::new(); 752 | 753 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 754 | 755 | locked_app.connect_test(); 756 | let contract = simple_future(); 757 | locked_app.req_contract_details(req_id, &contract)?; 758 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 759 | 760 | let expected: [u8; 50] = [ 761 | 0, 0, 0, 46, 57, 0, 56, 0, 49, 48, 50, 0, 48, 0, 69, 83, 0, 70, 85, 84, 0, 50, 48, 50, 762 | 48, 48, 57, 0, 48, 0, 0, 0, 71, 76, 79, 66, 69, 88, 0, 0, 85, 83, 68, 0, 0, 0, 48, 0, 763 | 0, 0, 764 | ]; 765 | 766 | let msg_data = read_msg(buf.as_slice())?; 767 | let fields = read_fields(&msg_data.1); 768 | 769 | assert_eq!(expected.as_ref(), buf.as_slice()); 770 | assert_eq!( 771 | OutgoingMessageIds::ReqContractData as u8, 772 | fields[0].parse::().unwrap() 773 | ); 774 | assert_eq!(version, fields[1].parse::().unwrap()); 775 | assert_eq!(req_id, fields[2].parse::().unwrap()); 776 | assert_eq!(contract.con_id, fields[3].parse::().unwrap()); // srv v37 and above 777 | assert_eq!(contract.symbol, fields[4]); 778 | 779 | assert_eq!(contract.sec_type, fields[5]); 780 | assert_eq!(contract.last_trade_date_or_contract_month, fields[6]); 781 | assert_eq!(contract.strike, fields[7].parse::().unwrap()); 782 | assert_eq!(contract.right, fields[8]); 783 | assert_eq!(contract.multiplier, fields[9]); // srv v15 and above 784 | 785 | assert_eq!(contract.exchange, fields[10]); 786 | assert_eq!(contract.primary_exchange, fields[11]); 787 | 788 | assert_eq!(contract.currency, fields[12]); 789 | assert_eq!(contract.local_symbol, fields[13]); 790 | 791 | assert_eq!(contract.trading_class, fields[14]); 792 | assert_eq!( 793 | contract.include_expired as i32, 794 | fields[15].parse::().unwrap() 795 | ); // srv v31 and above 796 | 797 | assert_eq!(contract.sec_id_type, fields[16]); 798 | assert_eq!(contract.sec_id, fields[17]); 799 | 800 | Ok(()) 801 | } 802 | 803 | //------------------------------------------------------------------------------------------------ 804 | #[test] 805 | fn test_req_current_time() -> Result<(), IBKRApiLibError> { 806 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 807 | let app = Arc::new(Mutex::new(EClient::::new( 808 | wrapper.clone(), 809 | ))); 810 | 811 | let version = 2; 812 | 813 | let mut buf = Vec::::new(); 814 | 815 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 816 | 817 | locked_app.connect_test(); 818 | locked_app.req_current_time()?; 819 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 820 | 821 | let expected: [u8; 9] = [0, 0, 0, 5, 52, 57, 0, 50, 0]; 822 | 823 | let msg_data = read_msg(buf.as_slice())?; 824 | 825 | let fields = read_fields(&msg_data.1); 826 | 827 | assert_eq!(expected.as_ref(), buf.as_slice()); 828 | assert_eq!( 829 | OutgoingMessageIds::ReqCurrentTime as u8, 830 | fields[0].parse::().unwrap() 831 | ); 832 | assert_eq!(version, fields[1].parse::().unwrap()); 833 | 834 | Ok(()) 835 | } 836 | 837 | //------------------------------------------------------------------------------------------------ 838 | #[test] 839 | fn test_req_executions() -> Result<(), IBKRApiLibError> { 840 | let wrapper = Arc::new(Mutex::new(DummyTestWrapper::new())); 841 | let app = Arc::new(Mutex::new(EClient::::new( 842 | wrapper.clone(), 843 | ))); 844 | 845 | let version = 3; 846 | let req_id = 102; 847 | let client_id = 0; 848 | let acct_code = "D54321"; 849 | 850 | //Time from which the executions will be returned yyyymmdd hh:mm:ss Only those executions reported after the specified time will be returned. 851 | let time = ""; 852 | let symbol = "ES"; 853 | let sec_type = "FUT"; 854 | let exchange = "GLOBEX"; 855 | let side = "BUY"; 856 | let mut buf = Vec::::new(); 857 | 858 | let mut locked_app = app.lock().expect("EClient mutex was poisoned"); 859 | 860 | locked_app.connect_test(); 861 | 862 | let exec_filter = ExecutionFilter::new( 863 | client_id, 864 | acct_code.to_string(), 865 | time.to_string(), 866 | symbol.to_string().to_string(), 867 | sec_type.to_string(), 868 | exchange.to_string(), 869 | side.to_string(), 870 | ); 871 | locked_app.req_executions(req_id, &exec_filter)?; 872 | locked_app.stream.as_mut().unwrap().read_to_end(&mut buf)?; 873 | 874 | let expected: [u8; 40] = [ 875 | 0, 0, 0, 36, 55, 0, 51, 0, 49, 48, 50, 0, 48, 0, 68, 53, 52, 51, 50, 49, 0, 0, 69, 83, 876 | 0, 70, 85, 84, 0, 71, 76, 79, 66, 69, 88, 0, 66, 85, 89, 0, 877 | ]; 878 | 879 | let msg_data = read_msg(buf.as_slice())?; 880 | //println!("read message: {:?}", read_msg(buf.as_slice())?); 881 | let fields = read_fields(&msg_data.1); 882 | //println!("read fields: {:?}", read_fields(&msg_data.1)); 883 | assert_eq!(expected.as_ref(), buf.as_slice()); 884 | assert_eq!( 885 | OutgoingMessageIds::ReqExecutions as u8, 886 | fields[0].parse::().unwrap() 887 | ); 888 | assert_eq!(version, fields[1].parse::().unwrap()); 889 | assert_eq!(req_id, fields[2].parse::().unwrap()); 890 | assert_eq!(client_id, fields[3].parse::().unwrap()); 891 | assert_eq!(acct_code, fields[4]); 892 | assert_eq!(time, fields[5]); 893 | assert_eq!(symbol, fields[6]); 894 | assert_eq!(sec_type, fields[7]); 895 | assert_eq!(exchange, fields[8]); 896 | assert_eq!(side, fields[9]); 897 | 898 | Ok(()) 899 | } 900 | } 901 | -------------------------------------------------------------------------------- /src/tests/test_messages.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | 4 | use crate::core::common::{TickByTickType, UNSET_DOUBLE, UNSET_INTEGER}; 5 | use crate::core::errors::IBKRApiLibError; 6 | use crate::core::messages::{ 7 | make_field, make_field_handle_empty, make_message, read_fields, read_msg, 8 | OutgoingMessageIds, 9 | }; 10 | use crate::examples::contract_samples; 11 | #[test] 12 | fn test_make_field() -> Result<(), IBKRApiLibError> { 13 | assert_eq!("1\u{0}", make_field(&true)?); 14 | assert_eq!("\u{0}", make_field(&UNSET_DOUBLE)?); 15 | assert_eq!("\u{0}", make_field(&UNSET_INTEGER)?); 16 | assert_eq!("100\u{0}", make_field(&100)?); 17 | assert_eq!("2.5\u{0}", make_field(&2.5)?); 18 | assert_eq!("hello\u{0}", make_field(&"hello")?); 19 | assert_eq!("hello\u{0}", make_field(&"hello".to_string())?); 20 | assert_eq!("\u{0}", make_field(&"".to_string())?); 21 | assert_eq!("\u{0}", make_field(&Option::::None)?); 22 | Ok(()) 23 | } 24 | 25 | #[test] 26 | fn test_make_field_handle_empty() -> Result<(), IBKRApiLibError> { 27 | assert_eq!("1\u{0}", make_field_handle_empty(&true)?); 28 | assert_eq!("\u{0}", make_field_handle_empty(&UNSET_DOUBLE)?); 29 | assert_eq!("\u{0}", make_field_handle_empty(&UNSET_INTEGER)?); 30 | assert_eq!("100\u{0}", make_field_handle_empty(&100)?); 31 | assert_eq!("2.5\u{0}", make_field_handle_empty(&2.5)?); 32 | assert_eq!("hello\u{0}", make_field_handle_empty(&"hello")?); 33 | assert_eq!("hello\u{0}", make_field_handle_empty(&"hello".to_string())?); 34 | Ok(()) 35 | } 36 | 37 | #[test] 38 | fn test_read_fields() { 39 | let fields = "here\u{0}are\u{0}some\u{0}fields\u{0}1\u{0}2.5\u{0}1000\u{0}"; 40 | let result_fields = vec!["here", "are", "some", "fields", "1", "2.5", "1000"]; 41 | assert_eq!(result_fields, read_fields(fields)); 42 | } 43 | 44 | #[test] 45 | fn test_make_msg() -> Result<(), IBKRApiLibError> { 46 | let mut msg = "".to_string(); 47 | let contract = contract_samples::usstock(); 48 | let message_id = OutgoingMessageIds::ReqTickByTickData as i32; 49 | 50 | msg.push_str(&make_field(&message_id)?); 51 | msg.push_str(&make_field(&1009)?); 52 | msg.push_str(&make_field(&contract.con_id)?); 53 | msg.push_str(&make_field(&contract.symbol)?); 54 | msg.push_str(&make_field(&contract.sec_type)?); 55 | msg.push_str(&make_field(&contract.last_trade_date_or_contract_month)?); 56 | msg.push_str(&make_field(&contract.strike)?); 57 | msg.push_str(&make_field(&contract.right)?); 58 | msg.push_str(&make_field(&contract.multiplier)?); 59 | msg.push_str(&make_field(&contract.exchange)?); 60 | msg.push_str(&make_field(&contract.primary_exchange)?); 61 | msg.push_str(&make_field(&contract.currency)?); 62 | msg.push_str(&make_field(&contract.local_symbol)?); 63 | msg.push_str(&make_field(&contract.trading_class)?); 64 | msg.push_str(&make_field(&(TickByTickType::AllLast.to_string()))?); 65 | 66 | msg.push_str(&make_field(&0)?); 67 | msg.push_str(&make_field(&false)?); 68 | 69 | let actual = make_message(msg.as_str())?; 70 | 71 | let expected: Vec = vec![ 72 | 0, 0, 0, 50, 57, 55, 0, 49, 48, 48, 57, 0, 48, 0, 65, 77, 90, 78, 0, 83, 84, 75, 0, 0, 73 | 48, 0, 0, 0, 73, 83, 76, 65, 78, 68, 0, 0, 85, 83, 68, 0, 0, 0, 65, 108, 108, 76, 97, 74 | 115, 116, 0, 48, 0, 48, 0, 75 | ]; 76 | 77 | assert_eq!(expected, actual); 78 | 79 | Ok(()) 80 | } 81 | 82 | #[test] 83 | fn test_read_msg() -> Result<(), IBKRApiLibError> { 84 | let msg_bytes: Vec = vec![ 85 | 0, 0, 0, 50, 57, 55, 0, 49, 48, 48, 57, 0, 48, 0, 65, 77, 90, 78, 0, 83, 84, 75, 0, 0, 86 | 48, 0, 0, 0, 73, 83, 76, 65, 78, 68, 0, 0, 85, 83, 68, 0, 0, 0, 65, 108, 108, 76, 97, 87 | 115, 116, 0, 48, 0, 48, 0, 88 | ]; 89 | let expected = (50, "97\u{0}1009\u{0}0\u{0}AMZN\u{0}STK\u{0}\u{0}0\u{0}\u{0}\u{0}ISLAND\u{0}\u{0}USD\u{0}\u{0}\u{0}AllLast\u{0}0\u{0}0\u{0}".to_owned(), Vec::::new()); 90 | let actual = read_msg(&msg_bytes)?; 91 | assert_eq!(expected, actual); 92 | 93 | Ok(()) 94 | } 95 | } 96 | --------------------------------------------------------------------------------