Metadata-Version: 2.4
Name: dollarsmore-wire
Version: 0.6.0
Summary: Broker-neutral order-intent wire schema for a producer/consumer Redis-Streams transport
Project-URL: Homepage, https://github.com/dollarsmore/wire
Project-URL: Repository, https://github.com/dollarsmore/wire
Project-URL: Issues, https://github.com/dollarsmore/wire/issues
Author: Robert Krzysztoforski
License-Expression: MIT
License-File: LICENSE
Keywords: orders,redis,schema,trading,wire-protocol
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.11
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/wire-readme-header.png" alt="wire — a broker-neutral order-intent wire schema" width="100%">
</p>

# wire

A tiny, broker-neutral **order-intent wire schema** for a producer/consumer
transport (Redis Streams or anything else). A strategy process produces
`OrderIntent`s; an executor process consumes them and reports back with
`ResultEvent`s. `wire` is the vendor-free contract both sides agree on: pure
stdlib dataclasses with explicit JSON (de)serialisation, no broker, transport,
or networking imports.

## Install

```bash
pip install dollarsmore-wire
# or
uv add dollarsmore-wire
```

```python
from wire import OrderIntent, ResultEvent, IntentAction, OrderType
```

The distribution is `dollarsmore-wire`; the import package is `wire`.

## What's in it

- `OrderIntent` — an order command: `OPEN` / `CLOSE` / `CANCEL`, one or more `IntentLeg`s,
  order type, price (positive-magnitude `Decimal`) with an explicit
  `PriceEffect` (`DEBIT` / `CREDIT`), time-in-force, correlation id, strategy tag.
- `OpenBracketIntent` — an entry plus a pre-staged take-profit as one OTO bracket.
- `ResultEvent` — the executor's reply: `ACCEPTED` / `REJECTED` / `FILLED` /
  `CANCELLED` / `ERROR`, broker order id, fill price, detail.
- Enums: `IntentAction`, `LegAction`, `OptionType`, `OrderType`, `PriceEffect`,
  `ResultStatus`.

Every type round-trips through `.to_wire()` / `.from_wire()` plain dicts, so the
transport only ever moves JSON.

## Redis-Streams transport (`wire.transport`)

`wire.transport` is the shared Redis-Streams envelope both producer and consumer
agree on — stream names (`orders:intents` / `:results` / `:deadletter`), the
consumer group, and pure `encode_*` / `decode_*` helpers that wrap a wire object
in a `{kind, payload}` dict. It has **no redis import** (stdlib + wire only); the
caller drives its own redis client (`client.xadd(INTENTS_STREAM,
encode_bracket(intent))`). Keeping it here stops producer and consumer drifting
on the wire format.

## Versioning

`WIRE_VERSION` is stamped into every serialised payload (the `v` field). Bump it
on any breaking change to the wire shape so producer and consumer can detect a
mismatch.

## Design rules

- Stdlib only — no third-party dependencies, ever.
- Money is a positive-magnitude `Decimal` plus an explicit `PriceEffect`; never a
  signed float.
- Times are ISO 8601 strings on the wire; enums travel by value.

`from_wire` is the contract boundary and refuses anything it cannot honour, always
with a `ValueError` so a consumer can dead-letter on one exception type:

- **Money is a JSON string holding a plain decimal literal**, never a JSON number.
  `{"price": 1.60}` would round through binary float and decode as
  `1.6000000000000000888...`, which is what the broker would then be quoted. Digits with
  at most one point: no sign, no exponent, no underscores, no surrounding whitespace, so
  `NaN`, `Infinity`, `-0.00`, `1e3` and `1_0` are all refused.
- **Money sits at or below `MONEY_CEILING`** (1,000,000, exported). A premium past it is a
  corrupt print rather than a price, and `to_wire` refuses it BEFORE expanding to fixed
  point: `1E+9999999` is twelve bytes in and ten megabytes out.
- **`strike` and `quantity` are JSON integers**, not floats, strings or booleans:
  `2.9` must not silently become 2 contracts.
- **`quantity` sits in `QUANTITY_FLOOR..QUANTITY_CEILING`** (1 to 99), both exported.
- **A `LIMIT` order carries both `price` and `price_effect`**; a limit with no limit
  price is refused rather than passed on as the string `"None"`.
- **`v` is an integer** no greater than `WIRE_VERSION`.

`to_wire` enforces the money rule too, so a producer fails on its own bad value
instead of learning about it as a dead-letter in another process.

## Develop

```bash
uv sync
uv run pytest
uv run ruff check .
uv run mypy wire tests
```
