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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
IBKR_HOST=127.0.0.1
IBKR_PORT=7497
IBKR_CLIENT_ID=7
TRADOVATE_BASE_URL=https://demo.tradovateapi.com/v1
TRADOVATE_NAME=your_login_name
TRADOVATE_PASSWORD=your_password
TRADOVATE_APP_ID=YourApp
TRADOVATE_APP_VERSION=1.0
TRADOVATE_DEVICE_ID=YourDevice
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__/
*.pyc
.env
.venv/
43 changes: 43 additions & 0 deletions IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Suggested Improvements

The current paper-trading clients and CLI provide a functional baseline, but a few
targeted enhancements could make the project more resilient and extensible:

## Cross-cutting enhancements

- **Centralized HTTP session management.** Both broker clients instantiate fresh
network sessions for every call. Establishing persistent sessions (and closing
them explicitly) would reduce overhead and simplify testing by enabling
session-level instrumentation.
- **Credential validation and helpful errors.** Configuration objects should
validate required fields up front and raise descriptive errors when mandatory
credentials or endpoints are missing. This avoids late failures deep inside API
calls.
- **Consistent response typing.** Return types currently vary between raw
dictionaries and lists. Introducing dataclasses or TypedDicts for normalized
payloads would provide clearer contracts for downstream consumers.

## IBKR client

- **Support additional order types.** `IBKRClient.place_order` accepts `order_type`
but only handles market orders. Implementing limit/stop orders would align the
method signature with behavior and unlock more realistic workflows.
- **Expose order lifecycle hooks.** The wrapper returns the initial trade status
but does not monitor fills or errors. Surfacing trade callbacks or a polling
utility would help strategies react to execution updates.
- **Connection lifecycle management.** Consider retry/backoff when the TWS
gateway is unavailable instead of raising immediately, and allow users to pass
a preconfigured `ib_insync.IB` instance for advanced scenarios.

## Tradovate client

- **Token refresh robustness.** The access token handling assumes the API always
returns `accessToken` and `expiresIn`. Add defensive checks and retries to
handle transient failures or malformed responses.
- **Order validation.** Sanitize `side`, `order_type`, and `quantity` before
submission to catch mistakes locally and surface clearer error messages.
- **WebSocket streaming.** Implement the Tradovate WebSocket feed so positions
and fills update in near real-time, as hinted in the original roadmap.

These improvements build on the existing structure while keeping tests isolated
from live network calls.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# OpenBB Lab Broker Clients

This package exposes lightweight paper-trading clients for Interactive Brokers
(IBKR) via [`ib_insync`](https://github.com/erdewit/ib_insync) and Tradovate's
demo REST API. A Typer-based CLI (`openbb-lab`) provides quick access to common
operations such as placing orders and fetching positions.

## Installation

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
pip install '.[brokers]'
cp .env.example .env # configure your credentials
```

## Usage

```bash
# IBKR (requires TWS/Gateway in paper mode)
openbb-lab ibkr_order --symbol AAPL --side BUY --qty 1

# Tradovate demo REST
openbb-lab tradovate_order --symbol MNQZ5 --side BUY --qty 1 --account_id 123456
```

Both clients expose additional commands for retrieving positions and account
summaries. Use `--help` on each command for the available options.
3 changes: 3 additions & 0 deletions openbb_lab/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""OpenBB Lab execution toolkit."""

from .version import __version__ # noqa: F401
109 changes: 109 additions & 0 deletions openbb_lab/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Command line interface for broker paper trading."""

from __future__ import annotations

import json
import logging
import typer
from dotenv import load_dotenv

from .execution import IBKRClient, TradovateClient

app = typer.Typer(add_completion=False, help="Utilities for paper trading workflows.")


def _setup_logging(verbose: bool) -> None:
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(level=level, format="%(levelname)s %(name)s - %(message)s")


@app.callback()
def main(ctx: typer.Context, verbose: bool = typer.Option(False, "--verbose", help="Enable debug logging.")) -> None:
"""Entrypoint for the ``openbb-lab`` CLI."""

load_dotenv() # load .env if present
_setup_logging(verbose)


@app.command("ibkr_order")
def ibkr_order(
symbol: str = typer.Option(..., help="Ticker symbol to trade."),
side: str = typer.Option(..., help="BUY or SELL."),
qty: float = typer.Option(..., help="Order quantity."),
order_type: str = typer.Option("MKT", help="Order type (currently only MKT)."),
exchange: str = typer.Option("SMART", help="Routing exchange."),
currency: str = typer.Option("USD", help="Trading currency."),
) -> None:
"""Submit an order to IBKR paper trading."""

client = IBKRClient()
result = client.place_order(
symbol=symbol,
side=side,
quantity=qty,
order_type=order_type,
exchange=exchange,
currency=currency,
)
typer.echo(json.dumps(result, indent=2))


@app.command("ibkr_positions")
def ibkr_positions() -> None:
"""Display IBKR open positions."""

client = IBKRClient()
typer.echo(json.dumps(client.positions(), indent=2))


@app.command("ibkr_account")
def ibkr_account() -> None:
"""Display IBKR account summary."""

client = IBKRClient()
typer.echo(json.dumps(client.account_info(), indent=2))


@app.command("tradovate_order")
def tradovate_order(
symbol: str = typer.Option(..., help="Tradovate product symbol."),
side: str = typer.Option(..., help="BUY or SELL."),
qty: float = typer.Option(..., help="Order quantity."),
account_id: int = typer.Option(..., help="Tradovate account identifier."),
order_type: str = typer.Option("Market", help="Order type."),
) -> None:
"""Submit an order to the Tradovate demo environment."""

client = TradovateClient()
result = client.place_order(
symbol=symbol,
side=side,
quantity=qty,
account_id=account_id,
order_type=order_type,
)
typer.echo(json.dumps(result, indent=2))


@app.command("tradovate_positions")
def tradovate_positions() -> None:
"""Display Tradovate positions."""

client = TradovateClient()
typer.echo(json.dumps(client.positions(), indent=2))


@app.command("tradovate_account")
def tradovate_account() -> None:
"""Display Tradovate account information."""

client = TradovateClient()
typer.echo(json.dumps(client.account_info(), indent=2))


def run() -> None:
app()


if __name__ == "__main__": # pragma: no cover
run()
11 changes: 11 additions & 0 deletions openbb_lab/execution/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Execution clients for broker integrations."""

from .ibkr_client import IBKRClient, IBKRConfig # noqa: F401
from .tradovate_client import TradovateClient, TradovateConfig # noqa: F401

__all__ = [
"IBKRClient",
"IBKRConfig",
"TradovateClient",
"TradovateConfig",
]
Loading