yatws 0.1.7

Yet Another TWS (Interactive Brokers TWS API) Implementation
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# YATWS (Yet Another TWS API)

A comprehensive, thread-safe, type-safe, and ergonomic Rust interface to the Interactive Brokers TWS API.

## Overview

YATWS provides a modern Rust implementation for interacting with Interactive Brokers' Trader Workstation (TWS) and Gateway. The library's manager-based architecture and support for both synchronous and asynchronous patterns make it suitable for various trading applications, from simple data retrieval to complex automated trading systems.

This library was born out of the need to place orders in rapid succession in response to market events, which is not easily accomplished with existing Rust crates. It takes about 3ms to place an order with this library. While order management was the primary focus, other interfaces (market data, account information, etc.) have been implemented for completeness.

**Current Status**: Early stage but US equity trading is production-ready. The API has been used to trade 9 figures of dollars in volume.

## Features

- **Comprehensive API Coverage**: Access to orders, accounts, market data, fundamentals, news, and reference data
- **Book-keeping**: Keeps the portfolio with PNL and order book up-to-date
- **Multiple Programming Patterns**:
  - Synchronous blocking calls with timeouts
  - Asynchronous observer pattern
  - Subscription model
- **Options Strategy Builder**: Simplified creation of common options strategies
- **Strong Type Safety**: Leverages Rust's type system for safer API interactions
- **Domain-Specific Managers**: Organized access to different API functionalities
- **Rate limits**: pace the requests in accordance with IBKR rules
- **Session Recording/Replay**: Record TWS interactions for testing and debugging

## Architecture

YATWS follows a client-manager architecture:

1. **Core Client (`IBKRClient`)**: Central connection point to TWS/Gateway
2. **Specialized Managers**: Domain-specific functionality organized into managers:
   - `OrderManager`: Order placement, modification, and tracking
   - `AccountManager`: Account and portfolio data (summary, positions, P&L, executions, liquidation warnings)
   - `DataMarketManager`: Real-time and historical market data
   - `DataRefManager`: Reference data (contract details, etc.)
   - `DataNewsManager`: News headlines and articles
   - `DataFundamentalsManager`: Financial data
   - `FinancialAdvisorManager`: Financial Advisor configurations (groups, profiles, aliases)

## Usage Examples

### Establishing a Connection

```rust
// Connect to TWS/Gateway
let client = IBKRClient::new("127.0.0.1", 7497, 0, None)?;

// Alternative: Record session to SQLite database
let client = IBKRClient::new(
    "127.0.0.1",
    7497,
    0,
    Some(("sessions.db", "my_trading_session"))
)?;

// Replay a recorded session
let replay_client = IBKRClient::from_db("sessions.db", "my_trading_session")?;
```

### Account Information

```rust
// Subscribe to account updates
client.account().subscribe_account_updates()?;

// Get account summary
let info = client.account().get_account_info()?;
println!("Account Net Liq: {}", info.net_liquidation);

// Get a single specific value.
use yatws::account::AccountValueKey;
let buying_power_value = client.account().get_account_value(AccountValueKey::BuyingPower)?;
if let Some(bp) = buying_power_value {
    println!("Buying Power: {} {}", bp.value, bp.currency.unwrap_or_default());
}

// List positions
let positions = client.account().list_open_positions()?;

// Get today's executions
let executions = client.account().get_day_executions()?;

// Check for pre-liquidation warning
if client.account().has_received_pre_liquidation_warning() {
    println!("Warning: Pre-liquidation warning received this session.");
}
```

### Order Management

```rust
// Create and place an order using OrderBuilder
let (contract, order_request) = OrderBuilder::new(OrderSide::Buy, 100.0)
    .for_stock("AAPL")
    .with_exchange("SMART")
    .with_currency("USD")
    .limit(150.0)
    .with_tif(TimeInForce::Day)
    .build()?;

let order_id = client.orders().place_order(contract, order_request)?;

// Wait for order to be filled (with timeout)
let status = client.orders().try_wait_order_executed(
    &order_id,
    Duration::from_secs(30)
)?;
```

### Order Subscription Pattern

For tracking order lifecycle with real-time updates:

```rust
use yatws::{OrderBuilder, OrderSide, TimeInForce};
use yatws::order_manager::OrderEvent;
use std::time::Duration;

// Create order specification
let (contract, order_request) = OrderBuilder::new(OrderSide::Buy, 100.0)
    .for_stock("AAPL")
    .market()
    .with_tif(TimeInForce::Day)
    .build()?;

// Subscribe to order lifecycle events
let order_subscription = client.orders().subscribe_new_order(contract, order_request)?;
let mut order_events = order_subscription.events();

// Process order events until completion or error
while let Some(event) = order_events.try_next(Duration::from_secs(1)) {
    match event {
        OrderEvent::Update(order) => {
            println!("Order {} status: {:?}, filled: {}",
                     order.id, order.state.status, order.state.filled_quantity);

            if order.state.status.is_terminal() {
                println!("Order completed with status: {:?}", order.state.status);
                break;
            }
        }
        OrderEvent::Error(error) => {
            eprintln!("Order error: {:?}", error);
            break;
        }
    }
}
```

### Market Data

```rust
// Get a simple quote
let (bid, ask, last) = client.data_market().get_quote(
    &contract,
    None,
    Duration::from_secs(5)
)?;

// Get historical data
use yatws::data::DurationUnit;
let bars = client.data_market().get_historical_data(
    &contract,
    None,                 // End time (None = now)
    DurationUnit::Day(1),
    yatws::contract::BarSize::Hour1,
    yatws::contract::WhatToShow::Trades,
    true,                 // Use RTH
    1,                    // Date format
    false,                // Keep up to date?
    None,                 // Market data type
    &[]                   // Chart options
)?;
```

### Options Strategies

```rust
// Create a bull call spread
let builder = OptionsStrategyBuilder::new(
    client.data_ref(),
    "AAPL",
    150.0,     // Current price
    10.0,      // Quantity (10 spreads)
    SecType::Stock
)?;

let (contract, order) = builder
    .bull_call_spread(
        NaiveDate::from_ymd_opt(2025, 12, 19).unwrap(),
        150.0,  // Lower strike
        160.0   // Higher strike
    )?
    .with_limit_price(3.50)  // Debit of $3.50 per spread
    .build()?;

let order_id = client.orders().place_order(contract, order)?;
```

### Financial Advisor Configuration

```rust
// Access the FinancialAdvisorManager
let fa_manager = client.financial_advisor();

// Request FA Groups data
fa_manager.request_fa_data(yatws::FADataType::Groups)?;

// Get the current FA configuration
let fa_config = fa_manager.get_config();
println!("FA Groups: {:?}", fa_config.groups);
```

## Asynchronous Event Handling

YATWS supports an observer pattern for handling asynchronous events. This is a push programming model where events are pushed to you. You initiate the request and implement `on_event` methods. This is the closest to the official IBKR TWS API, except you have observers split by functionality rather than a single `EWrapper`.

This is best suited for event-driven trading, such as reacting to news, order fills and buying power depletion.

### Market Data Observer Example

```rust
use yatws::{IBKRClient, IBKRError, contract::Contract, data::{MarketDataType, TickType, TickAttrib}, data_observer::MarketDataObserver};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

#[derive(Debug)]
struct MyMarketObserver {
    name: String,
    tick_count: Arc<Mutex<usize>>,
}

impl MarketDataObserver for MyMarketObserver {
    fn on_tick_price(&self, req_id: i32, tick_type: TickType, price: f64, attrib: TickAttrib) {
        println!("[{}] TickPrice: ReqID={}, Type={:?}, Price={}, Attrib={:?}", self.name, req_id, tick_type, price, attrib);
        *self.tick_count.lock().unwrap() += 1;
    }
    // Implement other MarketDataObserver methods as needed (on_tick_size, on_error, etc.)
    fn on_error(&self, req_id: i32, error_code: i32, error_message: &str) {
        eprintln!("[{}] Error: ReqID={}, Code={}, Msg='{}'", self.name, req_id, error_code, error_message);
    }
}

fn main() -> Result<(), IBKRError> {
    let client = IBKRClient::new("127.0.0.1", 7497, 2, None)?;
    let market_mgr = client.data_market();
    let contract = Contract::stock("AAPL");

    let observer = MyMarketObserver {
        name: "AAPL-Observer".to_string(),
        tick_count: Arc::new(Mutex::new(0)),
    };

    // Request streaming data and associate it with the observer
    let (req_id, _observer_id) = market_mgr.request_observe_market_data(
        &contract,
        &[GenericTickType::MiscellaneousStats], // Empty generic_tick_list for default ticks
        false, // snapshot = false for streaming
        false, // regulatory_snapshot
        &[],   // mkt_data_options
        Some(MarketDataType::Delayed), // Or RealTime if subscribed
        observer,
    )?;
    println!("Observing market data for AAPL with ReqID: {}", req_id);

    thread::sleep(Duration::from_secs(15)); // Observe for 15 seconds

    market_mgr.cancel_market_data(req_id)?; // Cancel the stream
    println!("Cancelled market data for AAPL.");

    Ok(())
}
```

## Subscriptions
Simimlar to the `ibapi` crate, you can use a subscription programming model. This is in-between the sync and async models desribed above. This is best suited for receiving a stream of data. This is a pull model where you receive an iterator of events.

### Account Subscription Example

```rust
use yatws::{IBKRClient, IBKRError};
use yatws::account_subscription::{AccountSubscription, AccountEvent};
use std::time::Duration;
use std::thread;

fn main() -> Result<(), IBKRError> {
    let client = IBKRClient::new("127.0.0.1", 7497, 1, None)?; // Use a unique client_id
    let account_manager = client.account();

    let mut account_sub = AccountSubscription::new(account_manager)?;
    println!("AccountSubscription created for account: {}", account_sub.account_id());

    // Example: Print initial summary
    if let Some(summary) = account_sub.last_known_summary() {
        println!("Initial Net Liquidation: {}", summary.net_liquidation);
    }

    // Spawn a thread to listen for events
    let event_receiver = account_sub.events(); // Get a receiver clone
    let event_thread = thread::spawn(move || {
        for event in event_receiver { // Iterates until channel is disconnected
            match event {
                AccountEvent::SummaryUpdate { info, .. } => {
                    println!("[Thread] Account Summary Update: NetLiq = {}", info.net_liquidation);
                }
                AccountEvent::PositionUpdate { position, .. } => {
                    println!("[Thread] Position Update: {} {} @ {}",
                             position.symbol, position.quantity, position.market_price);
                }
                AccountEvent::ExecutionUpdate { execution, .. } => {
                    println!("[Thread] Execution: {} {} {} @ {}",
                             execution.side, execution.quantity, execution.symbol, execution.price);
                }
                AccountEvent::Error { error, .. } => {
                    eprintln!("[Thread] AccountSubscription Error: {:?}", error);
                }
                AccountEvent::Closed { account_id, .. } => {
                    println!("[Thread] AccountSubscription for {} closed.", account_id);
                    break; // Exit loop
                }
            }
        }
    });

    // Let the subscription run for a bit
    thread::sleep(Duration::from_secs(60));

    // Explicitly close the subscription
    println!("Main thread: Closing AccountSubscription...");
    account_sub.close()?; // This will send AccountEvent::Closed and disconnect the channel

    // Wait for the event thread to finish
    event_thread.join().expect("Event thread panicked");
    println!("Main thread: Done.");

    Ok(())
}
```

### News Subscription Example

```rust
use yatws::{IBKRClient, IBKRError, news_subscription::NewsEvent};
use std::time::Duration;
use std::thread;

fn main() -> Result<(), IBKRError> {
    let client = IBKRClient::new("127.0.0.1", 7497, 3, None)?;
    let news_manager = client.data_news();

    // Subscribe to new news bulletins
    let mut news_sub = news_manager.subscribe_news_bulletins_stream(false).submit()?;
    println!("Subscribed to news bulletins (ReqID: {}).", news_sub.request_id());

    let event_receiver = news_sub.events();
    let news_thread = thread::spawn(move || {
        for event in event_receiver {
            match event {
                NewsEvent::Bulletin { article, .. } => {
                    println!("[Thread] News Bulletin: Provider={}, ID={}, Headline='{}'",
                             article.provider_code, article.article_id, article.headline);
                }
                NewsEvent::Error(e) => {
                    eprintln!("[Thread] NewsSubscription Error: {:?}", e);
                }
                NewsEvent::Closed { .. } => {
                    println!("[Thread] NewsSubscription closed.");
                    break;
                }
            }
        }
    });

    thread::sleep(Duration::from_secs(60)); // Listen for news for 60 seconds

    println!("Main thread: Closing NewsSubscription...");
    news_sub.cancel()?; // Explicitly cancel the subscription

    news_thread.join().expect("News thread panicked");
    println!("Main thread: Done with news.");

    Ok(())
}
```

## Advanced Features

### Session Recording and Replay

```rust
// Enable session recording
let client = IBKRClient::new(
    "127.0.0.1",
    7497,
    0,
    Some(("sessions.db", "my_trading_session"))
)?;

// Later, replay the session
let replay_client = IBKRClient::from_db("sessions.db", "my_trading_session")?;
```

### Order Conditions

```rust
// Create a price-conditional order
let (contract, order) = OrderBuilder::new(OrderSide::Buy, 100.0)
    .for_stock("AAPL")
    .limit(150.0)
    .add_price_condition(
        265598,             // SPY con_id
        "ISLAND",           // Exchange
        400.0,              // Price
        TriggerMethod::Last, // Trigger method
        false               // Is less than 400
    )
    .build()?;
```

### Rate Limiting

YATWS provides built-in rate limiting to ensure compliance with Interactive Brokers' API limits:

```rust
// Enable rate limiting with default settings (50 msgs/sec, 50 historical requests, 100 market data lines)
client.enable_rate_limiting()?;

// Configure custom rate limiting settings
let mut config = RateLimiterConfig::default();
config.enabled = true;
config.max_messages_per_second = 40;  // Be more conservative
config.max_historical_requests = 30;  // Lower than default 50
config.rate_limit_wait_timeout = Duration::from_secs(10);  // Longer timeout
client.configure_rate_limiter(config)?;

// Check current rate limiter status
if let Some(status) = client.get_rate_limiter_status() {
    println!("Rate limiting enabled: {}", status.enabled);
    println!("Current message rate: {:.2} msgs/sec", status.current_message_rate);
    println!("Active historical requests: {}/{}",
             status.active_historical_requests,
             config.max_historical_requests);
}

// Disable rate limiting when needed
client.disable_rate_limiting()?;

// Clean up stale requests (useful for long-running applications)
let (hist_cleaned, mkt_cleaned) = client.cleanup_stale_rate_limiter_requests(
    Duration::from_secs(300)  // Clean up requests older than 5 minutes
)?;
println!("Cleaned up {} historical and {} market data requests", hist_cleaned, mkt_cleaned);
```

## Error Handling

YATWS uses Rust's Result pattern consistently, with a custom `IBKRError` type:

```rust
match client.account().get_net_liquidation() {
    Ok(net_liq_value) => println!("Account Net Liquidation Value: ${}", net_liq_value),
    Err(IBKRError::Timeout) => println!("Operation timed out"),
    Err(IBKRError::ApiError(code, msg)) => println!("API error {}: {}", code, msg),
    Err(e) => println!("Other error: {:?}", e),
}
```

## API Reference

For detailed information about the available functions, structs, and traits, please refer to the [API documentation on docs.rs](https://docs.rs/yatws) or the [API reference on GitHub](https://github.com/drpngx/yatws/blob/main/yatws/doc/api.md).

Key components include:

- `IBKRClient`: Primary client for interacting with the TWS API
- `OrderManager`: Order-related operations
- `AccountManager`: Account and portfolio data (including liquidation warning status)
- `DataMarketManager`: Market data operations
- `DataRefManager`: Reference data
- `DataNewsManager`: News data
- `DataFundamentalsManager`: Financial data
- `FinancialAdvisorManager`: Financial Advisor configurations
- `OrderBuilder`: Fluent API for creating orders
- `OptionsStrategyBuilder`: Factory for common options strategies