tastytrade order-execution consumer / SDK: drains order intents off Redis Streams and places them
Project description
faro
A tastytrade order-execution consumer (and the seed of a small tastytrade SDK).
faro drains order intents off a Redis Stream and places them against
tastytrade, reporting results back on a results stream. It owns a minimal,
in-house tastytrade REST client (not a third-party broker library), so the
credential and money path is auditable end to end.
The wire schema it speaks is wire (dollarsmore-wire); a producer
emits OrderIntents, faro consumes them and emits ResultEvents.
Install
pip install dollarsmore-faro
# or
uv add dollarsmore-faro
The distribution is dollarsmore-faro; the import package is faro.
Run
python -m faro # consumes intents, places against the tastytrade production broker
python -m faro --account ... --redis-url redis://host:6379/0 --consumer faro-1
Credentials and wiring come from the environment (never baked into the image):
| Variable | Purpose |
|---|---|
TASTYTRADE_CLIENT_SECRET |
OAuth2 client secret |
TASTYTRADE_REFRESH_TOKEN |
OAuth2 refresh token |
TASTYTRADE_ACCOUNT |
account number (optional if exactly one account) |
REDIS_URL |
Redis connection (default redis://localhost:6379/0) |
FARO_CASEKEEPER_DIR |
audit-log directory (default /app/casekeeper) |
FARO_ARCHIVE_DIR |
stream-archive directory (default /app/archive) |
A second process, python -m faro.archive, tails every order/ops stream into a
durable per-day archive and trims the streams so Redis stays bounded.
Runtime mode
faro reads the Redis key faro:mode each loop. Only the exact value live arms real
placement; an absent or unrecognised key reads as dry (fail-safe), so a fresh deploy never
trades until an operator deliberately sets live. In dry mode every placement is sent to the
broker's dry-run endpoint, which validates the order (buying power, fees, structure) without
executing it, while cancels and the corrective flatten are suppressed. The switch is hot (no
restart), and flipping live→dry while positions are open cancels any working orders and
flattens any held position at market first.
faroassumes it owns the trading account exclusively. Reconciliation treats any position or order it did not place as a discrepancy, so in live mode it may auto-close such a position, and thelive→dryflatten cancels and closes everything in the account — including orders or trades placed manually in the broker UI. Do not hand-trade the same account.
Lifecycle
An order flows from a producer's intent on orders:intents to a terminal result on
orders:results. An OpenBracketIntent is placed as one OTO bracket at tastytrade (a limit
entry with a pre-staged take-profit); the entry then fills or expires, and a filled
position is closed by its take-profit or by a producer CLOSE. The whole map:
Each path, watched live:
Take-profit: the entry fills, the pre-staged TP fills, the OTO self-closes. |
Not filled: the entry limit never trades and expires at session end; nothing at risk. |
Close: a producer CLOSE cancels the resting TP and flattens the position. |
Rejected: the broker refuses the placement; no order works. |
A bracket can also be pulled before it fills (a producer CANCEL), and the live→dry
kill-switch cancels working orders and flattens any position at once. The animations are rendered
from tools/lifecycle.py (python -m tools.lifecycle), not shipped in the
package.
Layout
Two layers live side by side; the seam is where a future fuller SDK grows:
- SDK core —
session.py(OAuth2 refresh-token session, never logs tokens, refuses non-tastyworks hosts),client.py(TastytradeClientREST),symbols.py(OCC option symbols),executor.py(BrokerExecutorport +TastytradeExecutoradapter — the swappable broker seam). - Consumer app —
stream.py(Redis Streams transport: intents / results / dead-letter),service.py(FaroServicedispatch loop, crash-tolerant with PEL recovery),fill_tracker.py(polls the broker to confirm fills, records the actual price/time, emitsFILLED),book.py(faro's believed state per trade),reconciler.py(diffs the book against the broker's positions, alerts onops:alerts, mints a guarded corrective close),casekeeper.py(append-only per-day audit log of every intent received and broker result),archive.py(python -m faro.archive: tails the streams into a durable per-day archive and trims them),heartbeat.py(container healthcheck verdict),__main__.py(wires it together).
Develop
uv sync # wire resolves from PyPI (dollarsmore-wire)
uv run pytest -q
uv run ruff check .
Docker
docker build -t faro . # installs dollarsmore-wire from PyPI
The image ships only faro + wire + requests/redis — no strategy code — so
it is safe to publish.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dollarsmore_faro-0.9.3.tar.gz.
File metadata
- Download URL: dollarsmore_faro-0.9.3.tar.gz
- Upload date:
- Size: 724.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c1721b998e809bbf62a9c6868fd8708c134a059c8884a1a51e796018ca50eb9
|
|
| MD5 |
a7c7c270da4a5fa7b37a75c882c294b6
|
|
| BLAKE2b-256 |
2e280af60273f21fa4f65d8b71209bcb831adab0c55aeb8f239fce4b86768fcb
|
File details
Details for the file dollarsmore_faro-0.9.3-py3-none-any.whl.
File metadata
- Download URL: dollarsmore_faro-0.9.3-py3-none-any.whl
- Upload date:
- Size: 53.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae1261998cb70c7f68dbbe72c7c983edfeead364181f9dabe6c076a823f9970d
|
|
| MD5 |
fad6c5171f4ad641dd10421a4e590064
|
|
| BLAKE2b-256 |
e11f96324af28973254841704411fd3fd56b038bfaa4c987eef6c3ef053412fc
|