wboayue / rust-ibapi

An implementation of the Interactive Brokers API for Rust
MIT License
45 stars 21 forks source link

Contract::futures does not return a valid contract for historical data requests. #102

Closed BrettForr closed 2 weeks ago

BrettForr commented 1 year ago

Summary

To retrieve historical data for futures contracts, the Contract fields for exchange and last_trade_date_or_contract_month need to be filled in, but Contract::futures(symbol) only accepts one argument for the symbol, so the exchange and last_trade_date_or_contract_month fields use the defaults empty strings.

A futures contract without an exchange and a contract month is a valid contract for a contract_details request. Based on my limited testing, running a contract_details request without an exchange or contract month returns a Iterator of all active contracts, whereas running the same request with a futures contract with the exchange and contract month specified returns and Iterator with just the details for that single contract. Each version has it's use case, so would be good to support both.

I didn't do thorough testing of what other requests work for a futures Contract without an exchange or contract month. At a glance, it looks like requesting the head_timestamp returns a value but it appears to be nonsense. I'm seeing 1970-01-01 for a contract that first existed just a couple years ago.

Steps to reproduce

Running the following program

fn main() {
    let client = Client::connect("127.0.0.1:4001", 100).unwrap();

    let symbol = "ES";
    let contract = Contract::futures(symbol);

    let start_date = Some(datetime!(2023-07-26 16:00 UTC));

    let bid_ask_ticks = client
        .historical_ticks_bid_ask(&contract, start_date, None, 1000, true, false)
        .expect("Historical ticks request failed");

    println!("Here are the bids and asks");

    for tick in bid_ask_ticks {
        let time = tick.timestamp;
        let bid = tick.price_bid;
        let ask = tick.price_ask;
        println!("{time} | ${bid} | ${ask}");
    }
}

Returns the following (i.e. the ticks returned is an empty Iterator):

Finished dev [unoptimized + debuginfo] target(s) in 0.24s
     Running `target/debug/rust_ib_data`
Here are the bids and asks
[2104] Market data farm connection is OK:usfarm
[2106] HMDS data farm connection is OK:ushmds
[2158] Sec-def data farm connection is OK:secdefnj

The log from IB Gateway says:

22:36:57:756 -> ---54-2--1-2104-Market data farm connection is OK:usfarm-
22:36:57:756 -> ---34-2--1-2106-HMDS data farm connection is OK:ushmds-
22:36:57:756 -> ---84-2--1-2158-Sec-def data farm connection is OK:secdefnj-
22:36:57:799 <- 96-9000-0-ES-FUT--0-----USD---0-20230726 16:00:00 UTC--1000-BID_ASK-1-0--
22:36:57:799 -> ---Q4-2-9000-321-Error validating request.-'bV' : cause - Exchange must not be empty-

Environment

I'm using IB Gateway 10.19. These are my dependencies:

[dependencies]
time = "0.3.23"
ibapi = "0.3.0"

Working version

Switching the futures function to this results in a valid contract for historical data requests:

    /// Creates futures contract from specified symbol
    /// The contract's last trading day or contract month (for Options and Futures).
    /// Strings with format YYYYMM will be interpreted as the Contract Month whereas YYYYMMDD will be interpreted as Last Trading Day.
    pub fn futures(symbol: &str, exchange: &str, last_trade_date_or_contract_month: &str) -> Contract {
        Contract {
            symbol: symbol.to_string(),
            security_type: SecurityType::Future,
            currency: "USD".to_string(),
            exchange: exchange.to_string(),
            last_trade_date_or_contract_month: last_trade_date_or_contract_month.to_string(),
            include_expired: true,
            ..Default::default()
        }
    }

Running this program

fn main() {
    let client = Client::connect("127.0.0.1:4001", 100).unwrap();

    let symbol = "ES";
    let exchange = "CME";
    let expiry = "202309";
    let contract = Contract::futures(symbol, exchange, expiry);

    let start_date = Some(datetime!(2023-07-26 16:00 UTC));

    let bid_ask_ticks = client
        .historical_ticks_bid_ask(&contract, start_date, None, 1000, true, false)
        .expect("Historical ticks request failed");

    println!("Here are the bids and asks");

    for tick in bid_ask_ticks {
        let time = tick.timestamp;
        let bid = tick.price_bid;
        let ask = tick.price_ask;
        println!("{time} | ${bid} | ${ask}");
    }
}

results in the expected output:

Here are the bids and asks
[2104] Market data farm connection is OK:usfarm
[2106] HMDS data farm connection is OK:ushmds
[2158] Sec-def data farm connection is OK:secdefnj
2023-07-26 15:59:59.0 +00:00:00 | $4589.75 | $4590
2023-07-26 16:00:00.0 +00:00:00 | $4589.75 | $4590
2023-07-26 16:00:00.0 +00:00:00 | $4589.75 | $4590
...

Proposal

Since there's a valid use for both a futures product (retrieving contract details for all active contracts) as well as an individual futures contract (retrieving historical data or contract details for a single contract), I think it makes sense to keep the current contstructor and add another Contract constructor function for an individual futures contract with an exchange and contract month. Also, I think include_expired be set to true for the individual contract constructor and perhaps be an argument for the current constructor.

I can put together a pull request for this.

wboayue commented 1 year ago

There are several options here:

  1. Use the constructor as a starting point. (original design choice)
    let symbol = "ES";
    let exchange = "CME";
    let expiry = "202309";

    let mut contract = Contract::futures(symbol);
    contract.expiry = expiry
    contract.exchange = exchange
  1. Provide multiple constructors. Since Rust doesn't support method overloading or default arguments, I think this gets clunky.
    let symbol = "ES";
    let exchange = "CME";
    let expiry = "202309";

    let contract1 = Contract::futures(symbol);
    let contract2 = Contract::futures_with_exchange(symbol, exchange);
  1. Provide a builder for contracts. This is probably the most flexible but will take the most time to implement.
    let symbol = "ES";
    let exchange = "CME";
    let expiry = "202309";

    let contract = ContractBuilder::futures(symbol)
                                .with_exchange(exchange)
                                .with_expiry(expiry)
                                .build()