A deterministic, seeded synthetic-market sandbox that report-cards how autonomous LLM trading agents break.
Project description
finkyagents
A deterministic, seeded synthetic-market sandbox for testing where autonomous LLM trading agents fail.
finkyagents is a deterministic, seeded, synthetic market sandbox. You point your
LLM trading agent at the Python SDK or an MCP server, run a scenario, and get a
reproducible report card showing where the agent fails — risk-mandate breaches,
drawdown / tail loss, invalid orders, manipulation susceptibility, time-to-recover,
and survival — keyed by (scenario, seed, determinism version).
Unlike live- or replayed-data benchmarks, the world is fully synthetic, seeded, and byte-identically replayable, with an adversarial NPC (the spoofer) that makes manipulation controllable and attributable.
Install
pip install finkyagents # the zero-dependency core
pip install 'finkyagents[mcp]' # + the optional MCP server edge
0.1.0is the first stable PyPI release. Thefinkyagentsname is reserved on PyPI now (current pre-release:0.1.0a1); a plainpip install finkyagentswill resolve once0.1.0ships. Until then, install from source with uv:git clone … && cd finkyagents && uv sync, then prefix the commands below withuv run(e.g.uv run finky list).
60-second quickstart
The sample agents live in examples/ in the cloned repo (they ship in the source
distribution, not the wheel), so run the quickstart from a source checkout — uv run finky … — and the examples/… paths below resolve. Pick a scenario, point finky at
an agent, and read the card:
$ finky list
earnings-shock
flash-crash
liquidity-drought
sideways-chop
spoofing-attack
$ finky run flash-crash --agent examples/good_agent.py
finkyagents · flash-crash · seed 42
Survival: SURVIVED (peak drawdown -0%)
Overall: A (rough rollup — see metrics)
Risk-mandate breaches PASS 0
Invalid / hallucinated PASS 0.0%
Manipulation suscept. PASS $0.00
Tail loss (CVaR 95%) PASS 0.0%
Catastrophic actions PASS 0
Time-to-recover PASS 0 ticks
finky run writes flash-crash-seed42.json (the byte-stable machine artifact) and a
self-contained flash-crash-seed42.html grade card to --out (default: the current
directory). Now run the intentionally-broken agent to see the card discriminate:
$ finky run flash-crash --agent examples/broken_agent.py
finkyagents · flash-crash · seed 42
Survival: SURVIVED (peak drawdown -8%)
Overall: B (rough rollup — see metrics)
Risk-mandate breaches PASS 0
Invalid / hallucinated FAIL 99.8% ← floods the book with orders it can't fund
...
The grade is a rough, non-authoritative rollup — the raw metrics in report.json
(and the HTML card) are the deliverable. By default finky run exits 0 for any
completed run; pass --fail-on fail (or blewup) to turn the grade into a CI gate.
Point your own LLM agent at it
pip install anthropic
export ANTHROPIC_API_KEY=sk-ant-...
finky run earnings-shock --agent examples/llm_agent.py
examples/llm_agent.py reads the order book and the news wire and asks Claude what
to trade — the real use case the report card is built to evaluate.
The agent contract
finky run --agent <path> calls your agent's entry point, a decide callable, once
per decision point. Two shapes are accepted:
# Stateless: a module-level `decide` function (simplest; what good/broken use)
def decide(observation: dict) -> list[dict]:
return [{"asset": "ACME", "side": "BUY", "type": "LIMIT", "qty": 10, "price_tick": 9995}]
# Stateful: an `Agent` class, instantiated once per run — keeps memory across
# decision points (an LLM agent accumulating the wire, say) without globals
class Agent:
def __init__(self): ...
def decide(self, observation: dict) -> list[dict]:
return []
--agent path.py looks for a decide function, then an Agent class; use
--agent path.py:Name to select a specific one. An order is
{asset, side: BUY|SELL, type: LIMIT|MARKET, qty, price_tick?}; to cancel a resting
order return {type: "CANCEL", order_id: ...} (ids are in observation["portfolio"] ["open_orders"]). Returning [] holds.
Each observation carries the L2 book and last trade per asset, your portfolio, the
new wire headlines since the last step, and the risk mandate. The asset's true value
is never shown — interpreting the headline text is the behavioral test. Submittable
orders that the engine rejects (a hallucinated ticker, an unfundable limit) are recorded
as invalid orders; only output that violates the contract shape aborts the run.
Three ways to connect
| Path | Best for | How |
|---|---|---|
| CLI | quick evals, CI gates, non-Python agents (drive it as a subprocess) | finky run <scenario> --agent <path> |
| SDK | Python agents that want to drive the loop themselves | from finkyagents import Env |
| MCP | an existing MCP-client LLM | finky-mcp <scenario> (needs finkyagents[mcp]) |
The SDK lets the agent drive the world directly:
from finkyagents import Env
from finkyagents.report import generate_report
env = Env("path/to/scenario.toml")
obs = env.reset()
while not obs["done"]:
# inspect obs, then act — orders execute immediately against the frozen book
bids = obs["assets"]["ACME"]["bids"]
if bids:
env.place_order("ACME", "BUY", "LIMIT", qty=10, price_tick=bids[0][0] - 5)
obs = env.step()
print(generate_report(env.recording).to_text())
run_episode(env, decide) runs that same loop for you over a decide callable — the
one-liner the CLI uses. The SDK and MCP edges have byte-identical semantics (verified by
a parity test).
Determinism & the report card
A run is identified by its (scenario, seed, determinism version) triple and is
byte-identically replayable: the engine is synchronous, single-threaded, and seeded.
The determinism version is an integer, decoupled from the package version, bumped
only when an engine change alters the world trajectory for a fixed (scenario, seed) —
so a card or recording stays comparable only within one determinism version, and replay
hard-refuses across a mismatch (the package version is recorded as provenance, never the
key). Stream a run to disk with --record-to run.jsonl, then regenerate its card later
with finky report run.jsonl — the report is a pure function of the recording
(regenerating it re-runs the world and asserts the trajectory matches, so it doubles as
a replay check). Override the seed with --seed N for seed sweeps.
Versioning & stability
finkyagents is pre-1.0, with two tiers that carry different promises:
- Persisted/shared artifacts — scenario TOML, report JSON, and recordings — are
versioned contracts now. Each carries its own
format_version; a breaking change bumps it and ships a migration or a loud refusal. Reproducibility is additionally keyed by an integerdeterminism_version, decoupled from the package version (see Determinism & the report card). - The programmatic surface —
Envmethod signatures, observation/order dict shapes — is unstable until 1.0. New observation keys may be added and signatures may change; code against the examples and expect churn. In practice the observation dict is additive-only, since non-Python agents parse it over the CLI/MCP wire.
The CHANGELOG flags any format_version or determinism_version bump as
reproducibility-affecting, even when the package version bump is only a patch.
Development
uv sync # create the venv and install dev deps
uv run pytest # run the test suite (incl. the replay-equality and soak CI gates)
uv run ruff check # lint
uv run ty check # types
License
MIT.
Acknowledgements
The fundamental follows an Ornstein–Uhlenbeck mean-reverting process (Vasicek 1977); NPCs follow standard fundamentalist / chartist / noise archetypes.
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 finkyagents-0.1.0a1.tar.gz.
File metadata
- Download URL: finkyagents-0.1.0a1.tar.gz
- Upload date:
- Size: 102.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32a75a9da0bb5546fab94a8be82f1e175c4b8f16a7a8b242b4a138fe426a53e0
|
|
| MD5 |
8f39ae8e683f352dc38395523bd03123
|
|
| BLAKE2b-256 |
ce1ba04715a26f271ca410feed5f3cf1e0ecd4cf1dc31f9c3957f870a50b5998
|
File details
Details for the file finkyagents-0.1.0a1-py3-none-any.whl.
File metadata
- Download URL: finkyagents-0.1.0a1-py3-none-any.whl
- Upload date:
- Size: 81.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f5da66a447819587a3adcec9dce953d892a1220269803e127d7608461dd4d1d9
|
|
| MD5 |
35671c1c9bbd7401e5480dae105bef1a
|
|
| BLAKE2b-256 |
d7d1256230c3ff1d81ab2a18c4a9bef6c1f4c35ca1a8d08b3759cddf250e5dab
|