Skip to content

Commit

Permalink
Switch from ib_insync to ib_async
Browse files Browse the repository at this point in the history
This resolves #382 and #403.
  • Loading branch information
brndnmtthws committed Aug 16, 2024
1 parent 4185c28 commit 86723a4
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 249 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ redistribution.

## Requirements

The bot is based on the [ib_insync](https://github.com/erdewit/ib_insync)
The bot is based on the [ib_async](https://github.com/ib-api-reloaded/ib_async)
library, and uses [IBC](https://github.com/IbcAlpha/IBC) for managing the API
gateway.

Expand Down Expand Up @@ -305,7 +305,7 @@ You are now ready to make a splash! 🐳
|---|---|---|
| Requested market data is not subscribed. | Requisite market data subscriptions have not been set up on IBKR. | [Configure](https://www.interactivebrokers.com/en/software/am3/am/settings/marketdatasubscriptions.htm) your market data subscriptions. The default config that ships with this script uses the `Cboe One Add-On Bundle` and the `US Equity and Options Add-On Streaming Bundle`. **Note**: You _must_ fund your account before IBKR will send data for subscriptions. Without funding you can still subscribe but you will get an error from ibc. |
| No market data during competing live session | Your account is logged in somewhere else, such as the IBKR web portal, the desktop app, or even another instance of this script. | Log out of all sessions and then re-run the script. |
| `ib_insync.wrapper ERROR Error 200, reqId 10: The contract description specified for SYMBOL is ambiguous.` | IBKR needs to know which exchange is the primary exchange for a given symbol. | You need to specify the primary exchange for the stock. This is normal for companies, typically. For ETFs it usually isn't required. Specify the `primary_exchange` parameter for the symbol, i.e., `primary_exchange = "NYSE"`. |
| `ib_async.wrapper ERROR Error 200, reqId 10: The contract description specified for SYMBOL is ambiguous.` | IBKR needs to know which exchange is the primary exchange for a given symbol. | You need to specify the primary exchange for the stock. This is normal for companies, typically. For ETFs it usually isn't required. Specify the `primary_exchange` parameter for the symbol, i.e., `primary_exchange = "NYSE"`. |
| IBKey and MFA-related authentication issues | IBKR requires MFA for the primary account user. | Create a second account with limited permissions using the web portal (remove withdrawal/transfer, client management, IP restriction, etc permissions) and set an IP restriction if possible. When logging into the second account, ignore the MFA nags and do not enable MFA. |

## Support and sponsorship
Expand Down
434 changes: 212 additions & 222 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ version = "1.12.5"
[tool.poetry.dependencies]
click = "^8.1.3"
click-log = "^0.4.0"
ib_insync = "^0.9.86"
more-itertools = ">=9.1,<11.0"
numpy = ">=1.26,<3.0"
python = ">=3.10,<3.13"
Expand All @@ -21,6 +20,7 @@ version = "1.12.5"
rich = "^13.7.0"
schema = "^0.7.5"
toml = "^0.10.2"
ib-async = "^1.0.3"

[tool.poetry.group.dev.dependencies]
autohooks = ">=23.4,<25.0"
Expand Down
4 changes: 2 additions & 2 deletions thetagang.toml
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,8 @@ minimum_open_interest = 10
weight = 0.05

# parts = 5
[ib_insync]
logfile = '/etc/thetagang/ib_insync.log'
[ib_async]
logfile = '/etc/thetagang/ib_async.log'

# Typically the amount of time needed when waiting on data from the IBKR API.
# Sometimes it can take a while to retrieve data, and it's lazy-loaded by the
Expand Down
12 changes: 11 additions & 1 deletion thetagang/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ def normalize_config(config: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, A
# Do any pre-processing necessary to the config here, such as handling
# defaults, deprecated values, config changes, etc.

if "ib_insync" in config:
error_console.print(
"WARNING: config param `ib_insync` is deprecated, please rename it to the equivalent `ib_async`.",
)

if "ib_async" not in config:
# swap the old ib_insync key to the new ib_async key
config["ib_async"] = config["ib_insync"]
del config["ib_insync"]

if "twsVersion" in config["ibc"]:
error_console.print(
"WARNING: config param ibc.twsVersion is deprecated, please remove it from your config.",
Expand Down Expand Up @@ -183,7 +193,7 @@ def validate_config(config: Dict[str, Dict[str, Any]]) -> None:
Optional("no_trading"): bool,
}
},
Optional("ib_insync"): {
Optional("ib_async"): {
Optional("logfile"): And(str, len),
Optional("api_response_wait_time"): int,
},
Expand Down
2 changes: 1 addition & 1 deletion thetagang/config_defaults.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any, Dict

DEFAULT_CONFIG: Dict[str, Dict[str, Any]] = {
"ib_insync": {
"ib_async": {
"api_response_wait_time": 60,
},
"orders": {
Expand Down
16 changes: 8 additions & 8 deletions thetagang/portfolio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Any, Callable, Dict, FrozenSet, List, Optional, Tuple

import numpy as np
from ib_insync import (
from ib_async import (
AccountValue,
OptionChain,
Order,
Expand All @@ -16,9 +16,9 @@
Trade,
util,
)
from ib_insync.contract import ComboLeg, Contract, Index, Option, Stock
from ib_insync.ib import IB
from ib_insync.order import LimitOrder
from ib_async.contract import ComboLeg, Contract, Index, Option, Stock
from ib_async.ib import IB
from ib_async.order import LimitOrder
from more_itertools import partition
from rich import box
from rich.console import Console, Group
Expand Down Expand Up @@ -64,9 +64,9 @@
console = Console()


# Turn off some of the more annoying logging output from ib_insync
logging.getLogger("ib_insync.ib").setLevel(logging.ERROR)
logging.getLogger("ib_insync.wrapper").setLevel(logging.CRITICAL)
# Turn off some of the more annoying logging output from ib_async
logging.getLogger("ib_async.ib").setLevel(logging.ERROR)
logging.getLogger("ib_async.wrapper").setLevel(logging.CRITICAL)


class NoValidContractsError(Exception):
Expand All @@ -92,7 +92,7 @@ def __init__(
self.qualified_contracts: Dict[int, Contract] = {}

def api_response_wait_time(self) -> int:
return self.config["ib_insync"]["api_response_wait_time"]
return self.config["ib_async"]["api_response_wait_time"]

def orderStatusEvent(self, trade: Trade) -> None:
if "Filled" in trade.orderStatus.status:
Expand Down
4 changes: 2 additions & 2 deletions thetagang/test_util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import math
from datetime import date, timedelta

from ib_insync import Option, Order, PortfolioItem
from ib_insync.contract import Stock
from ib_async import Option, Order, PortfolioItem
from ib_async.contract import Stock

from thetagang.util import (
calculate_net_short_positions,
Expand Down
8 changes: 4 additions & 4 deletions thetagang/thetagang.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from asyncio import Future

from ib_insync import IB, IBC, Watchdog, util
from ib_insync.contract import Contract
from ib_async import IB, IBC, Watchdog, util
from ib_async.contract import Contract
from rich import box
from rich.console import Console, Group
from rich.panel import Panel
Expand Down Expand Up @@ -402,8 +402,8 @@ def start(config_path: str, without_ibc: bool = False) -> None:
tree.add(Group(":yin_yang: Symbology", symbols_table))
console.print(Panel(tree, title="Config"))

if config.get("ib_insync", {}).get("logfile"):
util.logToFile(config["ib_insync"]["logfile"]) # type: ignore
if config.get("ib_async", {}).get("logfile"):
util.logToFile(config["ib_async"]["logfile"]) # type: ignore

def onConnected() -> None:
portfolio_manager.manage()
Expand Down
12 changes: 6 additions & 6 deletions thetagang/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from operator import itemgetter
from typing import Any, Callable, Dict, List, Optional, Tuple

import ib_insync.objects
import ib_insync.ticker
from ib_insync import AccountValue, Order, PortfolioItem, TagValue, Ticker, util
from ib_insync.contract import Option
import ib_async.objects
import ib_async.ticker
from ib_async import AccountValue, Order, PortfolioItem, TagValue, Ticker, util
from ib_async.contract import Option

from thetagang.options import option_dte

Expand All @@ -32,7 +32,7 @@ def portfolio_positions_to_dict(
return d


def position_pnl(position: ib_insync.objects.PortfolioItem) -> float:
def position_pnl(position: ib_async.objects.PortfolioItem) -> float:
return position.unrealizedPNL / abs(position.averageCost * position.position)


Expand Down Expand Up @@ -207,7 +207,7 @@ def get_lower_price(ticker: Ticker) -> float:


def midpoint_or_market_price(ticker: Ticker) -> float:
# As per the ib_insync docs, marketPrice returns the last price first, but
# As per the ib_async docs, marketPrice returns the last price first, but
# we often prefer the midpoint over the last price. This function pulls the
# midpoint first, then falls back to marketPrice() if midpoint is nan.
if util.isNan(ticker.midpoint()):
Expand Down

0 comments on commit 86723a4

Please sign in to comment.