Skip to content
Open
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
60 changes: 38 additions & 22 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,46 @@ on:
- main

permissions:
contents: write
contents: read
pages: write
id-token: write

jobs:
build-docs:
runs-on: ubuntu-latest
environment:
name: github-pages
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: 3.12
- uses: abatilo/actions-poetry@v2
- name: install
run: poetry install --with=docs
- name: Build documentation
run: |
mkdir html
touch html/.nojekyll
poetry run sphinx-build -b html docs html
- name: Deploy documentation
if: ${{ github.event_name == 'push' }}
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: html
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: 3.12

- name: Install Poetry
run: |
pip install pip poetry setuptools wheel -U

- name: Install dependencies
run: |
poetry install --with=docs

- name: Build documentation
run: |
mkdir html
touch html/.nojekyll
poetry run sphinx-build -b html docs html

- name: Upload documentation artifact
if: ${{ github.event_name == 'push' }}
uses: actions/upload-pages-artifact@v4
with:
path: html

- name: Deploy documentation
if: ${{ github.event_name == 'push' }}
uses: actions/deploy-pages@v4
52 changes: 43 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
name: ib_async

on: [ push, pull_request ]
on: [push, pull_request]

jobs:
build:
# https://github.com/actions/runner-images
build-poetry:
name: poetry (${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.10", "pypy3.11" ]
python-version: ["3.11", "3.12", "3.13", "3.14", "pypy3.11"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies of dependencies
- name: Install Poetry toolchain
run: |
pip install pip poetry uv setuptools wheel -U
pip install pip poetry setuptools wheel -U

- name: Install dependencies
- name: Install dependencies with Poetry
run: |
poetry install --with=dev

Expand All @@ -34,3 +35,36 @@ jobs:
- name: Ruff check
run: |
poetry run ruff check

build-uv:
name: uv (${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14", "pypy3.11"]

steps:
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install uv toolchain
run: |
pip install pip uv setuptools wheel -U

- name: Install dependencies with uv
run: |
uv sync --group dev

- name: MyPy static code analysis
if: ${{ !startsWith(matrix.python-version, 'pypy') }}
run: |
uv run mypy --pretty ib_async

- name: Ruff check
run: |
uv run ruff check
58 changes: 50 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build](https://github.com/ib-api-reloaded/ib_async/actions/workflows/test.yml/badge.svg?branch=next)](https://github.com/ib-api-reloaded/ib_async/actions) [![PyVersion](https://img.shields.io/badge/python-3.10+-blue.svg)](#) <!-- [![Status](https://img.shields.io/badge/status-beta-green.svg)](#) --> [![PyPiVersion](https://img.shields.io/pypi/v/ib_async.svg)](https://pypi.python.org/pypi/ib_async) [![License](https://img.shields.io/badge/license-BSD-blue.svg)](#) <!-- [![Downloads](https://static.pepy.tech/badge/ib-insync)](https://pepy.tech/project/ib-insync) --> [![Docs](https://img.shields.io/badge/Documentation-green.svg)](https://ib-api-reloaded.github.io/ib_async/)
[![Build](https://github.com/ib-api-reloaded/ib_async/actions/workflows/test.yml/badge.svg?branch=next)](https://github.com/ib-api-reloaded/ib_async/actions) [![PyVersion](https://img.shields.io/badge/python-3.11+-blue.svg)](#) <!-- [![Status](https://img.shields.io/badge/status-beta-green.svg)](#) --> [![PyPiVersion](https://img.shields.io/pypi/v/ib_async.svg)](https://pypi.python.org/pypi/ib_async) [![License](https://img.shields.io/badge/license-BSD-blue.svg)](#) <!-- [![Downloads](https://static.pepy.tech/badge/ib-insync)](https://pepy.tech/project/ib-insync) --> [![Docs](https://img.shields.io/badge/Documentation-green.svg)](https://ib-api-reloaded.github.io/ib_async/)

# ib_async

Expand Down Expand Up @@ -32,13 +32,29 @@ and the [API docs](https://ib-api-reloaded.github.io/ib_async/api.html).

## Installation

`ib_async` now targets Python 3.11+ and publishes standard PEP 621 metadata, so the project installs cleanly with `pip`, `uv`, and `poetry`.

### Install with pip

```
pip install ib_async
```

### Install with uv

```
uv add ib_async
```

### Install with poetry

```
poetry add ib_async
```

Requirements:

- Python 3.10 or higher
- Python 3.11 or higher
- We plan to support Python releases [2 years back](https://devguide.python.org/versions/) which allows us to continue adding newer features and performance improvements over time.
- A running IB Gateway application (or TWS with API mode enabled)
- [stable gateway](https://www.interactivebrokers.com/en/trading/ibgateway-stable.php) — updated every few months
Expand All @@ -50,41 +66,59 @@ The ibapi package from IB is not needed. `ib_async` implements the full IBKR API

## Build Manually

First, install poetry:
First, install your preferred environment manager:

```
pip install poetry -U
```

### Installing Only Library
or:

```
pip install uv -U
```

### Install the project with poetry

```
poetry install
```

### Install Everything (enable docs + dev testing)
### Install the project with uv

```
uv sync
```

### Install everything for development and docs

```bash
poetry install --with=docs,dev
uv sync --group dev --group docs
```

## Generate Docs

```
```bash
poetry install --with=docs
poetry run sphinx-build -b html docs html

uv sync --group docs
uv run sphinx-build -b html docs html
```

## Check Types

```
```bash
poetry run mypy ib_async
uv run mypy ib_async
```

## Build Package

```
```bash
poetry build
python -m build
```

## Upload Package (if maintaining)
Expand Down Expand Up @@ -481,19 +515,26 @@ The complete [API documentation](https://ib-api-reloaded.github.io/ib_async/api.
```bash
poetry install --with=dev
poetry run pytest

uv sync --group dev
uv run pytest
```

### Type Checking

```bash
poetry run mypy ib_async
uv run mypy ib_async
```

### Code Formatting

```bash
poetry run ruff format
poetry run ruff check --fix

uv run ruff format
uv run ruff check --fix
```

### Local Development
Expand All @@ -507,6 +548,7 @@ cd ib_async
2. Install dependencies:
```bash
poetry install --with=dev,docs
uv sync --group dev --group docs
```

3. Make your changes and run tests:
Expand Down
21 changes: 11 additions & 10 deletions ib_async/ib.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ class IB:
A profit- and loss entry for a single position is updated.

* ``tickNewsEvent`` (news: :class:`.NewsTick`):
Emit a new news headline.
Emit a new news headline with the associated contract when
available.

* ``newsBulletinEvent`` (bulletin: :class:`.NewsBulletin`):
Emit a new news bulletin.
Expand Down Expand Up @@ -457,7 +458,7 @@ def waitOnUpdate(self, timeout: float = 0) -> bool:
if timeout:
try:
util.run(asyncio.wait_for(self.updateEvent, timeout))
except asyncio.TimeoutError:
except TimeoutError:
return False
else:
util.run(self.updateEvent)
Expand Down Expand Up @@ -2096,7 +2097,7 @@ async def connectAsync(
if fetchFields & StartupFetch.EXECUTIONS:
try:
await asyncio.wait_for(self.reqExecutionsAsync(), timeout)
except asyncio.TimeoutError:
except TimeoutError:
msg = "executions request timed out"
errors.append(msg)
self._logger.error(msg)
Expand Down Expand Up @@ -2315,7 +2316,7 @@ async def reqMatchingSymbolsAsync(
try:
await asyncio.wait_for(future, 4)
return future.result()
except asyncio.TimeoutError:
except TimeoutError:
self._logger.error("reqMatchingSymbolsAsync: Timeout")
return None

Expand All @@ -2327,7 +2328,7 @@ async def reqMarketRuleAsync(
self.client.reqMarketRule(marketRuleId)
await asyncio.wait_for(future, 1)
return future.result()
except asyncio.TimeoutError:
except TimeoutError:
self._logger.error("reqMarketRuleAsync: Timeout")
return None

Expand Down Expand Up @@ -2375,7 +2376,7 @@ async def reqHistoricalDataAsync(
task = asyncio.wait_for(future, timeout) if timeout else future
try:
await task
except asyncio.TimeoutError:
except TimeoutError:
self.client.cancelHistoricalData(reqId)
self._logger.warning(f"reqHistoricalData: Timeout for {contract}")
bars.clear()
Expand Down Expand Up @@ -2520,7 +2521,7 @@ async def calculateImpliedVolatilityAsync(
try:
await asyncio.wait_for(future, 4)
return future.result()
except asyncio.TimeoutError:
except TimeoutError:
self._logger.error("calculateImpliedVolatilityAsync: Timeout")
return None
finally:
Expand All @@ -2541,7 +2542,7 @@ async def calculateOptionPriceAsync(
try:
await asyncio.wait_for(future, 4)
return future.result()
except asyncio.TimeoutError:
except TimeoutError:
self._logger.error("calculateOptionPriceAsync: Timeout")
return None
finally:
Expand Down Expand Up @@ -2596,7 +2597,7 @@ async def reqHistoricalNewsAsync(
try:
await asyncio.wait_for(future, 4)
return future.result()
except asyncio.TimeoutError:
except TimeoutError:
self._logger.error("reqHistoricalNewsAsync: Timeout")
return None

Expand All @@ -2606,7 +2607,7 @@ async def requestFAAsync(self, faDataType: int):
try:
await asyncio.wait_for(future, 4)
return future.result()
except asyncio.TimeoutError:
except TimeoutError:
self._logger.error("requestFAAsync: Timeout")

async def getWshMetaDataAsync(self) -> str:
Expand Down
5 changes: 3 additions & 2 deletions ib_async/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from __future__ import annotations

from dataclasses import dataclass, field
from datetime import UTC, datetime, tzinfo
from datetime import date as date_
from datetime import datetime, timezone, tzinfo
from typing import Any, NamedTuple

from eventkit import Event
Expand Down Expand Up @@ -452,6 +452,7 @@ class NewsTick:
articleId: str
headline: str
extraData: str
contract: Contract | None = None


@dataclass(slots=True, frozen=True)
Expand Down Expand Up @@ -591,4 +592,4 @@ class IBDefaults:
unset: Any = nan

# optionally change the timezone used for log history events in objects (no impact on orders or data processing)
timezone: tzinfo = timezone.utc
timezone: tzinfo = UTC
Loading