Metadata-Version: 2.4
Name: johnnybt-trading
Version: 0.1.1
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Dist: sqlalchemy[asyncio]>=2.0.36 ; extra == 'sqlalchemy'
Provides-Extra: sqlalchemy
License-File: LICENSE
Summary: Runtime foundation for the johnnybt trading systems: one Tokio runtime behind a timer, an embedded RESP server, an HTTP server and async SQLite.
Keywords: trading,tokio,asyncio,resp,sqlite,pyo3
Author-email: MJ <tywf91@gmail.com>
License-Expression: MIT
Requires-Python: >=3.14
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/quant-on-quest/johnnybt-trading
Project-URL: Issues, https://github.com/quant-on-quest/johnnybt-trading/issues
Project-URL: Repository, https://github.com/quant-on-quest/johnnybt-trading

# johnnybt-trading

[![CI](https://github.com/quant-on-quest/johnnybt-trading/actions/workflows/ci.yml/badge.svg)](https://github.com/quant-on-quest/johnnybt-trading/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/johnnybt-trading.svg)](https://pypi.org/project/johnnybt-trading/)
[![Python](https://img.shields.io/badge/python-3.14-blue.svg)](https://pypi.org/project/johnnybt-trading/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

Runtime foundation for the johnnybt trading systems.

Four capabilities over **one Tokio runtime**, so a Python trading process gets
concurrency without Python threads and without Python subprocesses: a timer, an
embedded RESP server, an HTTP server, and async SQLite with a SQLAlchemy
dialect. Everything asynchronous returns an ordinary awaitable, so it composes
with `asyncio` like any other library.

There are **no Python dependencies**. Every capability is compiled into the
extension module — a foundation that dragged a web framework and a database
driver in behind it would not be a foundation.

```sh
pip install johnnybt-trading
# the SQLAlchemy dialect is optional
pip install "johnnybt-trading[sqlalchemy]"
```

**Requires Python 3.14.** That is the only version this is built against and
tested on; the extension is compiled `abi3`, but nothing below 3.14 is claimed
or supported.

Prebuilt wheels: Linux (x86_64, aarch64, musl x86_64), macOS (x86_64, arm64),
Windows (x64) — one wheel per platform.

## Timer

`every` ticks in Rust. A callback that runs long is a **measured skip**, not a
silent drift, and never an overlapping run — the counters say which happened.

```python
import johnnybt_trading as jbt

handle = jbt.every(0.2, tick, on_error=report, fire_immediately=True)
...
print(handle.fired, handle.skipped, handle.failed)
handle.cancel()
```

## RESP server

`RedisServer` speaks enough Redis to be a real one to a client that cannot
install anything to speak something else — which is the situation when the
process on the far side is a broker terminal.

```python
server = jbt.RedisServer(bind="127.0.0.1:6381", password=None, databases=16)
address = await server.start()
...
await server.stop()
```

Pub/sub is on the same object: `server.publish(channel, payload)` and
`server.subscribe(channel)`. A slow subscriber loses its oldest message rather
than stalling the publisher.

## HTTP server

Axum routing into Python coroutine handlers, plus a static directory served
straight off disk.

```python
server = jbt.HttpServer(bind="127.0.0.1:8080", on_error=report)
server.route("GET", "/healthz", lambda request: jbt.Response(b"ok"))
# Big files never enter Python: Range, conditional GET and compression for free.
server.static_dir("/assets", "dist/assets", cache_seconds=31536000, immutable=True)
address = await server.start()
```

## SQLite

One connection per actor thread, `?`-style parameters, and an event log that
lives in the same database — which is what lets an event and the fact it
describes commit together.

```python
db = await jbt.connect("trades.db")
await db.execute("CREATE TABLE fills (id INTEGER PRIMARY KEY, code TEXT)")
await db.execute("INSERT INTO fills (code) VALUES (?)", ["002105.SZ"])

await db.create_event_schema()
await db.append_event("orders", "filled", b'{"code": "002105.SZ"}')
for event in await db.read_events("orders", after=0):
    ...
```

The SQLAlchemy async dialect registers itself, so `sqlite+jbt://` resolves
without an explicit call:

```python
from sqlalchemy.ext.asyncio import create_async_engine

engine = create_async_engine("sqlite+jbt:///trades.db")
```

## Pub/sub or event log?

Both ship, and they answer different questions. **Pub/sub** tells whoever is
listening *now* — nothing is kept, a subscriber that was not there missed it.
**The event log** tells whoever reads *later* — appended in order, read from a
cursor, and durable in the same transaction as the change it describes.

## Development

```sh
uv sync --all-extras --dev
uv run maturin develop --uv
uv run pytest
cargo test --lib
```

## License

MIT

