from enum import Enum
from typing import Union, Dict, List, Callable, Optional
import json
import datetime
import numpy
import pandas
from deephaven.table import Table
from deephaven.constants import NULL_DOUBLE
from deephaven.dtypes import Instant
from ibapi.contract import Contract, ContractDetails
from ibapi.order import Order
from ._tws import IbTwsClient
from ._tws.order_id_queue import OrderIdStrategy
from .time import to_ib_datetime
__all__ = ["MarketDataType", "TickDataType", "BarDataType", "BarSize", "Duration", "OrderIdStrategy",
"Request", "RegisteredContract", "IbSessionTws"]
[docs]
class MarketDataType(Enum):
"""Type of market data to use."""
REAL_TIME = 1
"""Real-time market data."""
FROZEN = 2
"""Real-time market data during regular trading hours, and frozen prices after the close."""
DELAYED = 3
"""Delayed market data."""
[docs]
class TickDataType(Enum):
"""Tick data type."""
LAST = "Last"
"""Most recent trade."""
BID_ASK = "BidAsk"
""""Most recent bid and ask."""
MIDPOINT = "MidPoint"
"""Most recent midpoint."""
def historical_value(self) -> str:
if self.value == "Last":
return "Trades"
else:
return self.value
class GenericTickType(Enum):
"""Tick data types for 'Generic' data.
See: https://interactivebrokers.github.io/tws-api/tick_types.html
"""
NEWS = 292
"""News."""
DIVIDENDS = 456
"""Dividends."""
AUCTION = 225
"""Auction details."""
MARK_PRICE = 232
"""Mark price is the current theoretical calculated value of an instrument. Since it is a calculated value, it will typically have many digits of precision."""
MARK_PRICE_SLOW = 619
"""Slower mark price update used in system calculations."""
TRADING_RANGE = 165
"""Multi-week price and volume trading ranges."""
TRADE_LAST_RTH = 318
"""Last regular trading hours traded price."""
TRADE_COUNT = 293
"""Trade count for the day."""
TRADE_COUNT_RATE = 294
"""Trade count per minute."""
TRADE_VOLUME = 233
"""Trade volume for the day."""
TRADE_VOLUME_NO_UNREPORTABLE = 375
"""Trade volume for the day that excludes "Unreportable Trades"."""
TRADE_VOLUME_RATE = 295
"""Trade volume per minute."""
TRADE_VOLUME_SHORT_TERM = 595
"""Short-term trading volume."""
SHORTABLE = 236
"""Describes the level of difficulty with which the contract can be sold short."""
SHORTABLE_SHARES = 236
"""Number of shares available to short."""
FUTURE_OPEN_INTEREST = 588
"""Total number of outstanding futures contracts."""
FUTURE_INDEX_PREMIUM = 162
"""Number of points that the index is over the cash index."""
OPTION_VOLATILITY_HISTORICAL = 104
"""30-day historical volatility."""
OPTION_VOLATILITY_HISTORICAL_REAL_TIME = 411
"""Real-time historical volatility."""
OPTION_VOLATILITY_IMPLIED = 106
"""IB 30-day at-market volatility, estimated for a maturity thirty calendar days forward of the current trading day"""
OPTION_VOLUME = 100
"""Option volume for the trading day."""
OPTION_VOLUME_AVERAGE = 105
"""Average option volume for a trading day."""
OPTION_OPEN_INTEREST = 101
"""Option open interest."""
ETF_NAV_CLOSE = 578
"""ETF's Net Asset Value (NAV) closing price."""
ETF_NAV_PRICE = 576
"""ETF's Net Asset Value (NAV) bid / ask price."""
ETF_NAV_LAST = 577
"""ETF's Net Asset Value (NAV) last price."""
ETF_NAV_LAST_FROZEN = 623
"""ETF's Net Asset Value (NAV) for frozen data."""
ETF_NAV_RANGE = 614
"""ETF's Net Asset Value (NAV) price range."""
BOND_FACTOR_MULTIPLIER = 460
"""Bond factor multiplier is a number that indicates the ratio of the current bond principal to the original principal."""
[docs]
class BarDataType(Enum):
"""Bar data type."""
TRADES = 1
"""Trade prices."""
MIDPOINT = 2
"""Midpoint prices."""
BID = 3
"""Bid prices."""
ASK = 4
"""Ask prices."""
BID_ASK = 5
"""Bid/Ask prices."""
ADJUSTED_LAST = 6
"""Bid/Ask prices."""
HISTORICAL_VOLATILITY = 7
"""Historical volatility."""
OPTION_IMPLIED_VOLATILITY = 8
"""Option implied volatility."""
REBATE_RATE = 9
"""Rebate rate."""
FEE_RATE = 10
"""Fee rate."""
YIELD_BID = 11
"""Bid yield."""
YIELD_ASK = 12
"""Ask yield."""
YIELD_BID_ASK = 13
"""Bid/Ask yield."""
YIELD_LAST = 14
"""Last yield."""
AGGTRADES = 15
"""Aggregate trade prices."""
[docs]
class BarSize(Enum):
"""Bar data sizes."""
SEC_1 = "1 sec"
"""1 second bar."""
SEC_5 = "5 secs"
"""5 second bar."""
SEC_10 = "10 secs"
"""10 second bar."""
SEC_15 = "15 secs"
"""15 second bar."""
SEC_30 = "30 secs"
"""30 second bar."""
MIN_1 = "1 min"
"""1 minute bar."""
MIN_2 = "2 mins"
"""2 minute bar."""
MIN_3 = "3 mins"
"""3 minute bar."""
MIN_5 = "5 mins"
"5 minute bar."
MIN_10 = "10 mins"
"10 minute bar."
MIN_15 = "15 mins"
"""15 minute bar."""
MIN_20 = "20 mins"
"20 minute bar."
MIN_30 = "30 mins"
"""30 minute bar."""
HOUR_1 = "1 hour"
"""1 hour bar."""
HOUR_2 = "2 hour"
"""2 hour bar."""
HOUR_3 = "3 hour"
"""3 hour bar."""
HOUR_4 = "4 hour"
"""4 hour bar."""
HOUR_8 = "8 hour"
"""8 hour bar."""
DAY_1 = "1 day"
"""1 day bar."""
WEEK_1 = "1W"
"""1 week bar."""
MONTH_1 = "1M"
"""1 month bar."""
[docs]
class Duration:
"""Time period to request data for."""
value: str
def __init__(self, value: str):
self.value = value
[docs]
@staticmethod
def seconds(value: int) -> "Duration":
"""Create a duration of a specified number of seconds.
Args:
value (int): number of seconds
Returns:
A duration.
"""
return Duration(f"{value} S")
[docs]
@staticmethod
def days(value: int) -> "Duration":
"""Create a duration of a specified number of days.
Args:
value (int): number of days
Returns:
A duration.
"""
return Duration(f"{value} D")
[docs]
@staticmethod
def weeks(value: int) -> "Duration":
"""Create a duration of a specified number of weeks.
Args:
value (int): number of weeks
Returns:
A duration.
"""
return Duration(f"{value} W")
[docs]
@staticmethod
def months(value: int) -> "Duration":
"""Create a duration of a specified number of months.
Args:
value (int): number of months
Returns:
A duration.
"""
return Duration(f"{value} M")
[docs]
@staticmethod
def years(value: int) -> "Duration":
"""Create a duration of a specified number of years.
Args:
value (int): number of years
Returns:
A duration.
"""
return Duration(f"{value} Y")
def __repr__(self) -> str:
return f"Duration('{self.value}')"
[docs]
class Request:
""" IB session request. """
request_id: int
_cancel_func: Callable
def __init__(self, request_id: int, cancel_func: Callable = None):
self.request_id = request_id
self._cancel_func = cancel_func
[docs]
def is_cancellable(self) -> bool:
"""Is the request cancellable?
Returns:
An indication if the request is cancellable.
"""
return self._cancel_func is not None
[docs]
def cancel(self) -> None:
"""Cancel the request.
Returns:
None
Raises:
Exception: request is not cancellable.
"""
if not self.is_cancellable():
raise Exception("Request is not cancellable.")
self._cancel_func(self.request_id)
[docs]
class RegisteredContract:
""" Details describing a financial instrument that has been registered in the framework. This can be a stock, bond, option, etc.
When some contracts are registered, details on multiple contracts are returned.
"""
query_contract: Contract
contract_details: List[ContractDetails]
def __init__(self, query_contract: Contract, contract_details: List[ContractDetails]):
self.query_contract = query_contract
self.contract_details = contract_details
[docs]
def is_multi(self) -> bool:
"""Does the contract have multiple contract details?
Returns:
An indication if the requested contract is associated with multiple contract details.
"""
return len(self.contract_details) > 1
def __repr__(self) -> str:
return f"RegistredContract({self.query_contract},[{'|'.join([str(cd.contract) for cd in self.contract_details])}])"
[docs]
class IbSessionTws:
""" IB TWS session.
**NOTE: Some tables are data specific to the current client_id (e.g. orders_submitted). A client_id of 0 includes
data manually entered into the TWS session. For example, orders entered by hand.**
Args:
host (str): The host name or IP address of the machine where TWS is running. Leave blank to connect to the local host.
When run inside docker, you probably want ``host.docker.internal``.
port (int): TWS port, specified in TWS on the ``Configure->API->Socket Port`` field.
By default production trading uses port 7496 and paper trading uses port 7497.
client_id (int): A number used to identify this client connection.
All orders placed/modified from this client will be associated with this client identifier.
**NOTE: Each client MUST connect with a unique clientId.**
download_short_rates (bool): True to download a short rates table.
order_id_strategy (OrderIdStrategy): strategy for obtaining new order ids.
read_only (bool): True to create a read only client that can not trade; false to create a read-write client that can trade. Default is true.
is_fa (bool): True for financial advisor accounts; false otherwise. Default is false.
Tables:
####
# General
####
* **errors**: an error log.
* **requests**: requests to IB.
####
# Contracts
####
* **contract_details**: details describing contracts of interest. Automatically populated.
* **contracts_matching**: contracts matching query strings provided to ``request_contracts_matching``.
* **market_rules**: market rules indicating the price increment a contract can trade in. Automatically populated.
* **short_rates**: interest rates for shorting securities. Automatically populated if ``download_short_rates=True``.
####
# Accounts
####
* **accounts_managed**: accounts managed by the TWS session login. Automatically populated.
* **accounts_family_codes**: account family. Automatically populated.
* **accounts_groups**: account groups. Automatically populated.
* **accounts_allocation_profiles**: allocation profiles for accounts. Automatically populated.
* **accounts_value**: account values. Automatically populated.
* **accounts_overview**: overview of account details. Automatically populated.
* **accounts_summary**: account summary. Automatically populated.
* **accounts_positions**: account positions. Automatically populated.
* **accounts_pnl**: account PNL. Automatically populated.
* **accounts_pnl_single**: single PNL. populated by calling request_single_pnl() on a specific contract.
####
# News
####
* **news_providers**: currently subscribed news sources. Automatically populated.
* **news_bulletins**: news bulletins. Automatically populated.
* **news_articles**: the content of news articles requested via ``request_news_article``.
* **news_historical**: historical news headlines requested via ``request_news_historical``.
####
# Market Data
####
* **ticks_price**: real-time tick market data of price values requested via ``request_market_data``.
* **ticks_size**: real-time tick market data of size values requested via ``request_market_data``.
* **ticks_string**: real-time tick market data of string values requested via ``request_market_data``.
* **ticks_efp**: real-time tick market data of exchange for physical (EFP) values requested via ``request_market_data``.
* **ticks_generic**: real-time tick market data of generic floating point values requested via ``request_market_data``.
* **ticks_option_computation**: real-time tick market data of option computations requested via ``request_market_data``.
* **ticks_trade**: real-time tick market data of trade prices requested via ``request_tick_data_historical`` or ``request_tick_data_realtime``.
* **ticks_bid_ask**: real-time tick market data of bid and ask prices requested via ``request_tick_data_historical`` or ``request_tick_data_realtime``.
* **ticks_mid_point**: real-time tick market data of mid-point prices requested via ``request_tick_data_historical`` or ``request_tick_data_realtime``.
* **bars_historical**: historical price bars requested via ``request_bars_historical``. Real-time bars change as new data arrives.
* **bars_realtime**: real-time price bars requested via ``request_bars_realtime``.
####
# Order Management System (OMS)
####
* **orders_submitted**: submitted orders **FOR THE THE CLIENT ID**. A client ID of 0 contains manually entered orders. Automatically populated.
* **orders_status**: order statuses. Automatically populated.
* **orders_completed**: completed orders. Automatically populated.
* **orders_exec_details**: order execution details. Automatically populated.
* **orders_exec_commission_report**: order execution commission report. Automatically populated.
"""
_host: str
_port: int
_client_id: int
_read_only: bool
_client: IbTwsClient
_tables_raw: Dict[str, Table]
_tables: Dict[str, Table]
def __init__(self, host: str = "", port: int = 7497, client_id: int = 0, download_short_rates: bool = True, order_id_strategy: OrderIdStrategy = OrderIdStrategy.INCREMENT, read_only: bool = True, is_fa: bool = False):
self._host = host
self._port = port
self._client_id = client_id
self._read_only = read_only
self._client = IbTwsClient(download_short_rates=download_short_rates, order_id_strategy=order_id_strategy, read_only=read_only, is_fa=is_fa)
self._tables_raw = {f"raw_{k}": v for k, v in self._client.tables.items()}
self._tables = dict(sorted(IbSessionTws._make_tables(self._tables_raw).items()))
@property
def host(self) -> str:
"""Client host.
Returns:
Client host.
"""
return self._host
@property
def port(self) -> int:
"""Client port.
Returns:
Client port.
"""
return self._port
@property
def client_id(self) -> int:
"""Client ID.
Returns:
Client ID.
"""
return self._client_id
@property
def read_only(self) -> bool:
"""Is the client read only?
Returns:
a boolean indicating if the client is read only.
"""
def __repr__(self) -> str:
return f"IbSessionTws(host={self._host}, port={self._port}, client_id={self._client_id}, read_only={self._read_only})"
####################################################################################################################
####################################################################################################################
## Connect / Disconnect / Subscribe
####################################################################################################################
####################################################################################################################
[docs]
def connect(self) -> None:
"""Connect to an IB TWS session. Raises an exception if already connected.
Returns:
None
Raises:
Exception: problem executing action.
"""
self._client.connect(self._host, self._port, self._client_id)
[docs]
def disconnect(self) -> None:
"""Disconnect from an IB TWS session.
Returns:
None
"""
self._client.disconnect()
[docs]
def is_connected(self) -> bool:
"""Is there a connection with TWS?
Returns:
an indication if the client is connected to TWS.
"""
return self._client.isConnected()
def _assert_connected(self) -> None:
"""Assert that the IbSessionTws is connected."""
if not self.is_connected():
raise Exception("IbSessionTws is not connected.")
def _assert_read_write(self) -> None:
"""Assert that the IbSessionTws is read-write."""
if self._read_only:
raise Exception("IbSessionTws is read-only. Set 'read_only=False' to enable read-write operations, such as trading.")
####################################################################################################################
####################################################################################################################
## General
####################################################################################################################
####################################################################################################################
@staticmethod
def _make_tables(tables_raw: Dict[str, Table]) -> Dict[str, Table]:
def annotate_ticks(t):
requests = tables_raw["raw_requests"] \
.drop_columns(["ReceiveTime", "RequestType", "SecId", "SecIdType", "DeltaNeutralContract", "Note"])
requests_col_names = [ c.name for c in requests.columns ]
rst = t.natural_join(requests, on="RequestId").move_columns_up(requests_col_names)
if "Timestamp" in [ c.name for c in rst.columns ]:
if "TimestampEnd" in [ c.name for c in rst.columns ]:
rst = rst.move_columns_up(["RequestId", "ReceiveTime", "Timestamp", "TimestampEnd"])
else:
rst = rst.move_columns_up(["RequestId", "ReceiveTime", "Timestamp"])
else:
rst = rst.move_columns_up(["RequestId", "ReceiveTime"])
return rst
def deephaven_ib_float_value(s: Optional[str]) -> Optional[float]:
if not s:
return NULL_DOUBLE
try:
return float(s)
except ValueError:
return NULL_DOUBLE
def deephaven_ib_parse_note(note:str, key:str) -> Optional[str]:
dict = json.loads(note)
if key in dict:
return dict[key]
return None
return {
"requests": tables_raw["raw_requests"] \
.move_columns_up(["RequestId", "ReceiveTime"]),
"errors": tables_raw["raw_errors"] \
.natural_join(tables_raw["raw_requests"] \
.drop_columns("Note").rename_columns("RequestTime=ReceiveTime"), on="RequestId") \
.move_columns_up(["RequestId", "ReceiveTime"]),
"contracts_details": tables_raw["raw_contracts_details"] \
.move_columns_up(["RequestId", "ReceiveTime"]),
"accounts_family_codes": tables_raw["raw_accounts_family_codes"] \
.drop_columns("ReceiveTime"),
"accounts_groups": tables_raw["raw_accounts_groups"] \
.drop_columns("ReceiveTime"),
"accounts_allocation_profiles": tables_raw["raw_accounts_allocation_profiles"] \
.drop_columns("ReceiveTime"),
"accounts_aliases": tables_raw["raw_accounts_aliases"] \
.drop_columns("ReceiveTime"),
"accounts_managed": tables_raw["raw_accounts_managed"] \
.select_distinct("Account"),
"accounts_positions": tables_raw["raw_accounts_positions"] \
.last_by(["RequestId", "Account", "ModelCode", "ContractId"]) \
.move_columns_up(["RequestId", "ReceiveTime"]),
"accounts_overview": tables_raw["raw_accounts_overview"] \
.last_by(["RequestId", "Account", "Currency", "Key"]) \
.update("DoubleValue = (double)deephaven_ib_float_value(Value)") \
.move_columns_up(["RequestId", "ReceiveTime"]),
"accounts_summary": tables_raw["raw_accounts_summary"] \
.natural_join(tables_raw["raw_requests"], on="RequestId", joins="Note") \
.update("GroupName=(String)deephaven_ib_parse_note(Note,`groupName`)") \
.drop_columns("Note") \
.update("DoubleValue = (double)deephaven_ib_float_value(Value)") \
.last_by(["RequestId", "GroupName", "Account", "Tag"]) \
.move_columns_up(["RequestId", "ReceiveTime", "GroupName"]),
"accounts_pnl": tables_raw["raw_accounts_pnl"] \
.natural_join(tables_raw["raw_requests"], on="RequestId", joins="Note") \
.update([
"Account=(String)deephaven_ib_parse_note(Note,`account`)",
"ModelCode=(String)deephaven_ib_parse_note(Note,`model_code`)"]) \
.move_columns_up(["RequestId", "ReceiveTime", "Account", "ModelCode"]) \
.drop_columns("Note") \
.last_by("RequestId"),
"accounts_pnl_single": tables_raw["raw_accounts_pnl_single"] \
.natural_join(tables_raw["raw_requests"], on="RequestId", joins="Note") \
.update([
"Account=(String)deephaven_ib_parse_note(Note,`account`)",
"ModelCode=(String)deephaven_ib_parse_note(Note,`model_code`)",
"ConId=(String)deephaven_ib_parse_note(Note,`conid`)"]) \
.move_columns_up(["RequestId", "ReceiveTime", "Account", "ModelCode", "ConId"]) \
.drop_columns("Note") \
.last_by("RequestId"),
"contracts_matching": tables_raw["raw_contracts_matching"] \
.natural_join(tables_raw["raw_requests"], on="RequestId", joins="Pattern=Note") \
.move_columns_up(["RequestId", "ReceiveTime", "Pattern"]) \
.update("Pattern=(String)deephaven_ib_parse_note(Pattern,`pattern`)"),
"market_rules": tables_raw["raw_market_rules"].select_distinct(["MarketRuleId", "LowEdge", "Increment"]),
"news_bulletins": tables_raw["raw_news_bulletins"],
"news_providers": tables_raw["raw_news_providers"] \
.drop_columns("ReceiveTime"),
"news_articles": tables_raw["raw_news_articles"] \
.move_columns_up(["RequestId", "ReceiveTime"]),
"news_historical": tables_raw["raw_news_historical"] \
.natural_join(tables_raw["raw_requests"], on="RequestId", joins=["ContractId","SecType","Symbol","LocalSymbol"]) \
.move_columns_up(["RequestId", "ReceiveTime", "Timestamp", "ContractId", "SecType", "Symbol",
"LocalSymbol"]),
"orders_completed": tables_raw["raw_orders_completed"] \
.move_columns_up(["ReceiveTime", "OrderId", "ClientId", "PermId", "ParentId"]),
"orders_exec_commission_report": tables_raw["raw_orders_exec_commission_report"],
"orders_exec_details": tables_raw["raw_orders_exec_details"] \
.move_columns_up(["RequestId", "ReceiveTime", "Timestamp", "ExecId", "AcctNumber"]) \
.rename_columns("Account=AcctNumber"),
# The status on raw_orders_submitted is buggy, so using the status from raw_orders_status
"orders_submitted": tables_raw["raw_orders_submitted"] \
.last_by("PermId") \
.drop_columns("Status") \
.natural_join(tables_raw["raw_orders_status"].last_by("PermId"), on="PermId", joins="Status")
.move_columns_up(["ReceiveTime", "Account", "ModelCode", "PermId", "ClientId", "OrderId", "ParentId",
"Status"]),
"orders_status": tables_raw["raw_orders_status"] \
.last_by("PermId") \
.move_columns_up(["ReceiveTime", "PermId", "ClientId", "OrderId", "ParentId"]),
"bars_historical": annotate_ticks(tables_raw["raw_bars_historical"]).last_by(["RequestId", "Timestamp", "ContractId"]),
"bars_realtime": annotate_ticks(tables_raw["raw_bars_realtime"]),
"ticks_efp": annotate_ticks(tables_raw["raw_ticks_efp"]),
"ticks_generic": annotate_ticks(tables_raw["raw_ticks_generic"]),
"ticks_mid_point": annotate_ticks(tables_raw["raw_ticks_mid_point"]),
"ticks_option_computation": annotate_ticks(tables_raw["raw_ticks_option_computation"]),
"ticks_price": annotate_ticks(tables_raw["raw_ticks_price"]),
"ticks_size": annotate_ticks(tables_raw["raw_ticks_size"]),
"ticks_string": annotate_ticks(tables_raw["raw_ticks_string"]),
"ticks_trade": annotate_ticks(tables_raw["raw_ticks_trade"] \
.rename_columns("TradeExchange=Exchange")),
"ticks_bid_ask": annotate_ticks(tables_raw["raw_ticks_bid_ask"]),
}
@property
def tables(self) -> Dict[str, Table]:
"""Gets a dictionary of all data tables.
Returns:
Dictionary of all data tables.
"""
return self._tables
@property
def tables_raw(self) -> Dict[str, Table]:
"""Gets a dictionary of all raw data tables. Raw tables are just as the data comes from IB.
Returns:
Dictionary of all raw data tables.
"""
return self._tables_raw
####################################################################################################################
####################################################################################################################
## Contracts
####################################################################################################################
####################################################################################################################
[docs]
def get_registered_contract(self, contract: Contract) -> RegisteredContract:
"""Gets a contract that has been registered in the framework. The registered contract is confirmed to
exist in the IB system and contains a complete description of the contract.
Args:
contract (Contract): contract to search for
Returns:
A contract that has been registered with deephaven-ib.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
try:
cd = self._client.contract_registry.request_contract_details_blocking(contract)
return RegisteredContract(query_contract=contract, contract_details=cd)
except Exception as e:
raise Exception(f"Error getting registered contract: contract={contract} {e}")
[docs]
def request_contracts_matching(self, pattern: str) -> Request:
"""Request contracts matching a pattern. Results are returned in the ``contracts_matching`` table.
Args:
pattern (str): pattern to search for. Can include part of a ticker or part of the company name.
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "MatchingSymbols", None, {"pattern": pattern})
self._client.reqMatchingSymbols(reqId=req_id, pattern=pattern)
return Request(request_id=req_id)
####################################################################################################################
####################################################################################################################
## Accounts
####################################################################################################################
####################################################################################################################
[docs]
def request_account_pnl(self, account: str = "All", model_code: str = "") -> Request:
"""Request PNL updates. Results are returned in the ``accounts_pnl`` table.
Args:
account (str): Account to request PNL for. "All" requests for all accounts.
model_code (str): Model portfolio code to request PNL for.
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
req_id = self._client.request_account_pnl(account, model_code)
return Request(request_id=req_id)
[docs]
def request_account_overview(self, account: str, model_code: str = "") -> Request:
"""Request portfolio overview updates. Results are returned in the ``accounts_overview`` table.
Args:
account (str): Account to request an overview for. "All" requests for all accounts.
model_code (str): Model portfolio code to request an overview for.
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
req_id = self._client.request_account_overview(account, model_code)
return Request(request_id=req_id)
[docs]
def request_account_positions(self, account: str, model_code: str = "") -> Request:
"""Request portfolio position updates. Results are returned in the ``accounts_positions`` table.
Args:
account (str): Account to request positions for. "All" requests for all accounts.
model_code (str): Model portfolio code to request positions for.
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
req_id = self._client.request_account_positions(account, model_code)
return Request(request_id=req_id)
[docs]
def request_single_pnl(self, contract: RegisteredContract, account: str, model_code: str = "") -> Request:
"""Request PNL updates for a single position. Results are returned in the ``accounts_pnl_single`` table.
Args:
contract (RegisteredContract): contract data is requested for.
account (str): Account to request PNL for.
model_code (str): Model portfolio code to request PNL for.
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
req_id = self._client.request_single_pnl(account, model_code, contract.contract_details[0].contract.conId)
return Request(request_id=req_id)
####################################################################################################################
####################################################################################################################
## News
####################################################################################################################
####################################################################################################################
[docs]
def request_news_historical(self, contract: RegisteredContract,
start: Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp],
end: Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp],
provider_codes: List[str] = None, total_results: int = 100) -> List[Request]:
""" Request historical news for a contract. Results are returned in the ``news_historical`` table.
Registered contracts that are associated with multiple contract details produce multiple requests.
Args:
contract (RegisteredContract): contract data is requested for
provider_codes (List[str]): a list of provider codes. By default, all subscribed codes are used.
start (Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp]): marks the (exclusive) start of the date range. See https://deephaven.io/core/pydoc/code/deephaven.time.html#deephaven.time.to_j_instant for supported inputs.
end (Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp]): marks the (inclusive) end of the date range. See https://deephaven.io/core/pydoc/code/deephaven.time.html#deephaven.time.to_j_instant for supported inputs.
total_results (int): the maximum number of headlines to fetch (1 - 300)
Returns:
All of the requests created by the action.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
if not provider_codes:
provider_codes = self._client.news_providers
pc = "+".join(provider_codes)
requests = []
for cd in contract.contract_details:
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "HistoricalNews", cd.contract,
{"provider_codes": provider_codes, "start": start, "end": end,
"total_results": total_results})
self._client.reqHistoricalNews(reqId=req_id, conId=cd.contract.conId, providerCodes=pc,
startDateTime=to_ib_datetime(start, sub_sec=False),
endDateTime=to_ib_datetime(end, sub_sec=False),
totalResults=total_results, historicalNewsOptions=[])
requests.append(Request(request_id=req_id))
return requests
[docs]
def request_news_article(self, provider_code: str, article_id: str) -> Request:
""" Request the text of a news article. Results are returned in the ``news_articles`` table.
Args:
provider_code (str): short code indicating news provider, e.g. FLY
article_id (str): id of the specific article
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "NewsArticle", None,
{f"provider_code": provider_code, "article_id": article_id})
self._client.reqNewsArticle(reqId=req_id, providerCode=provider_code, articleId=article_id,
newsArticleOptions=[])
return Request(request_id=req_id)
####################################################################################################################
####################################################################################################################
## Market Data
####################################################################################################################
####################################################################################################################
[docs]
def set_market_data_type(self, market_data_type: MarketDataType) -> None:
"""Sets the default type of market data.
Args:
market_data_type (MarketDataType): market data type
Returns:
None
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._client.reqMarketDataType(marketDataType=market_data_type.value)
# noinspection PyDefaultArgument
[docs]
def request_market_data(self, contract: RegisteredContract, generic_tick_types: List[GenericTickType] = [],
snapshot: bool = False, regulatory_snapshot: bool = False) -> List[Request]:
""" Request market data for a contract. Results are returned in the ``ticks_price``, ``ticks_size``,
``ticks_string``, ``ticks_efp``, ``ticks_generic``, and ``ticks_option_computation`` tables.
Registered contracts that are associated with multiple contract details produce multiple requests.
Args:
contract (RegisteredContract): contract data is requested for
generic_tick_types (List[GenericTickType]): generic tick types being requested
snapshot (bool): True to return a single snapshot of Market data and have the market data subscription cancel.
Do not enter any genericTicklist values if you use snapshots.
regulatory_snapshot (bool): True to get a regulatory snapshot. Requires the US Value Snapshot Bundle for stocks.
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
generic_tick_list = ",".join([str(x.value) for x in generic_tick_types])
requests = []
for cd in contract.contract_details:
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "MarketData", cd.contract,
{"generic_tick_types": generic_tick_types, "snapshot": snapshot,
"regulatory_snapshot": regulatory_snapshot})
self._client.reqMktData(reqId=req_id, contract=cd.contract,
genericTickList=generic_tick_list, snapshot=snapshot,
regulatorySnapshot=regulatory_snapshot, mktDataOptions=[])
requests.append(Request(request_id=req_id, cancel_func=self._cancel_market_data))
return requests
def _cancel_market_data(self, req_id: int) -> None:
"""Cancel a market data request.
Args:
req_id (int): request id
Returns:
None
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._client.cancelMktData(reqId=req_id)
[docs]
def request_bars_historical(self, contract: RegisteredContract,
duration: Duration, bar_size: BarSize, bar_type: BarDataType,
end: Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp] = None,
market_data_type: MarketDataType = MarketDataType.FROZEN,
keep_up_to_date: bool = True) -> List[Request]:
"""Requests historical bars for a contract. Results are returned in the ``bars_historical`` table.
Registered contracts that are associated with multiple contract details produce multiple requests.
Args:
contract (RegisteredContract): contract data is requested for
end (Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp]): Ending timestamp of the requested data. See https://deephaven.io/core/pydoc/code/deephaven.time.html#deephaven.time.to_j_instant for supported inputs.
duration (Duration): Duration of data being requested by the query.
bar_size (BarSize): Size of the bars that will be returned.
bar_type (BarDataType): Type of bars that will be returned.
market_data_type (MarketDataType): Type of market data to return after the close.
keep_up_to_date (bool): True to continuously update bars
Returns:
All of the requests created by this action.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
requests = []
for cd in contract.contract_details:
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "HistoricalData", cd.contract,
{
"end": end,
"duration": duration,
"bar_size": bar_size,
"bar_type": bar_type,
"market_data_type": market_data_type,
"keep_up_to_date": keep_up_to_date,
})
self._client.reqHistoricalData(reqId=req_id, contract=cd.contract,
endDateTime=to_ib_datetime(end, sub_sec=False),
durationStr=duration.value, barSizeSetting=bar_size.value,
whatToShow=bar_type.name, useRTH=(market_data_type == MarketDataType.FROZEN),
formatDate=2, keepUpToDate=keep_up_to_date, chartOptions=[])
requests.append(Request(request_id=req_id))
return requests
[docs]
def request_bars_realtime(self, contract: RegisteredContract, bar_type: BarDataType, bar_size: int = 5,
market_data_type: MarketDataType = MarketDataType.FROZEN) -> List[Request]:
"""Requests real time bars for a contract. Results are returned in the ``bars_realtime`` table.
Registered contracts that are associated with multiple contract details produce multiple requests.
Args:
contract (RegisteredContract): contract data is requested for
bar_type (BarDataType): Type of bars that will be returned.
bar_size (int): Bar size in seconds.
market_data_type (MarketDataType): Type of market data to return after the close.
Returns:
All of the requests created by this action.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
requests = []
if bar_type not in [BarDataType.TRADES, BarDataType.AGGTRADES, BarDataType.MIDPOINT, BarDataType.BID, BarDataType.ASK]:
raise Exception(f"Unsupported bar type: {bar_type}")
for cd in contract.contract_details:
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "RealTimeBars", cd.contract,
{"bar_type": bar_type, "bar_size": bar_size, "market_data_type": market_data_type})
self._client.reqRealTimeBars(reqId=req_id, contract=cd.contract, barSize=bar_size,
whatToShow=bar_type.name, useRTH=(market_data_type == MarketDataType.FROZEN),
realTimeBarsOptions=[])
requests.append(Request(request_id=req_id, cancel_func=self._cancel_bars_realtime))
return requests
def _cancel_bars_realtime(self, req_id: int) -> None:
"""Cancel a real-time bar request.
Args:
req_id (int): request id
Returns:
None
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._client.cancelRealTimeBars(reqId=req_id)
[docs]
def request_tick_data_realtime(self, contract: RegisteredContract, tick_type: TickDataType,
number_of_ticks: int = 0, ignore_size: bool = False) -> List[Request]:
"""Requests real-time tick-by-tick data. Results are returned in the ``ticks_trade``, ``ticks_bid_ask``,
and ``ticks_mid_point`` tables.
Registered contracts that are associated with multiple contract details produce multiple requests.
Args:
contract (RegisteredContract): contract data is requested for
tick_type (TickDataType): Type of market data to return.
number_of_ticks (int): Number of historical ticks to request.
ignore_size (bool): should size values be ignored.
Returns:
All of the requests created by this action.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
requests = []
for cd in contract.contract_details:
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "TickByTickData", cd.contract,
{"tick_type": tick_type, "number_of_ticks": number_of_ticks,
"ignore_size": ignore_size})
self._client.reqTickByTickData(reqId=req_id, contract=cd.contract,
tickType=tick_type.value,
numberOfTicks=number_of_ticks, ignoreSize=ignore_size)
requests.append(Request(request_id=req_id, cancel_func=self._cancel_tick_data_realtime))
return requests
def _cancel_tick_data_realtime(self, req_id: int) -> None:
"""Cancel a real-time tick-by-tick data request.
Args:
req_id (int): request id
Returns:
None
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._client.cancelTickByTickData(reqId=req_id)
[docs]
def request_tick_data_historical(self, contract: RegisteredContract,
tick_type: TickDataType, number_of_ticks: int,
start: Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp] = None,
end: Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp] = None,
market_data_type: MarketDataType = MarketDataType.FROZEN,
ignore_size: bool = False) -> List[Request]:
"""Requests historical tick-by-tick data. Results are returned in the ``ticks_trade``, ``ticks_bid_ask``,
and ``ticks_mid_point`` tables.
Registered contracts that are associated with multiple contract details produce multiple requests.
Args:
contract (RegisteredContract): contract data is requested for
start (Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp]): marks the (exclusive) start of the date range. See https://deephaven.io/core/pydoc/code/deephaven.time.html#deephaven.time.to_j_instant for supported inputs.
end (Union[None, Instant, int, str, datetime.datetime, numpy.datetime64, pandas.Timestamp]): marks the (inclusive) end of the date range. See https://deephaven.io/core/pydoc/code/deephaven.time.html#deephaven.time.to_j_instant for supported inputs.
tick_type (TickDataType): Type of market data to return.
number_of_ticks (int): Number of historical ticks to request.
market_data_type (MarketDataType): Type of market data to return after the close.
ignore_size (bool): should size values be ignored.
Returns:
All of the requests created by this action.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
what_to_show = tick_type.historical_value()
requests = []
if tick_type not in [TickDataType.MIDPOINT, TickDataType.LAST]:
raise Exception(f"Unsupported tick data type: {tick_type}")
for cd in contract.contract_details:
req_id = self._client.request_id_manager.next_id()
self._client.log_request(req_id, "HistoricalTicks", cd.contract,
{"start": start,
"end": end,
"tick_type": tick_type,
"number_of_ticks": number_of_ticks,
"market_data_type": market_data_type,
"ignore_size": ignore_size,
})
self._client.reqHistoricalTicks(reqId=req_id, contract=cd.contract,
startDateTime=to_ib_datetime(start, sub_sec=False),
endDateTime=to_ib_datetime(end, sub_sec=False),
numberOfTicks=number_of_ticks, whatToShow=what_to_show,
useRth=market_data_type.value,
ignoreSize=ignore_size, miscOptions=[])
requests.append(Request(request_id=req_id))
return requests
####################################################################################################################
####################################################################################################################
## Order Management System (OMS)
####################################################################################################################
####################################################################################################################
[docs]
def order_place(self, contract: RegisteredContract, order: Order) -> Request:
"""Places an order.
Args:
contract (RegisteredContract): contract to place an order on
order (Order): order to place
Returns:
A Request.
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._assert_read_write()
if contract.is_multi():
raise Exception(
f"RegisteredContracts with multiple contract details are not supported for orders: {contract}")
if order.orderId == 0 or order.orderId is None:
req_id = self._client.next_order_id()
order.orderId = req_id
else:
req_id = order.orderId
cd = contract.contract_details[0]
self._client.log_request(req_id, "PlaceOrder", cd.contract, {"order": f"Order({order})"})
self._client.placeOrder(req_id, cd.contract, order)
return Request(request_id=req_id, cancel_func=self.order_cancel)
[docs]
def order_cancel(self, order_id: int) -> None:
"""Cancels an order.
Args:
order_id (int): order ID
Returns:
None
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._assert_read_write()
self._client.cancelOrder(orderId=order_id, manualCancelOrderTime="")
[docs]
def order_cancel_all(self) -> None:
"""Cancel all open orders.
Returns:
None
Raises:
Exception: problem executing action.
"""
self._assert_connected()
self._assert_read_write()
self._client.reqGlobalCancel()