Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from ib_insync to ib_async #478

Merged
merged 1 commit into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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