Toxic taker¶
Toxic taker is the simplest live strategy in Dyson and the clearest illustration of one idea: form a view on one venue, act on another. A Binance trade-price impulse moves a held fair value; when Kuru's executable book is far enough away from that fair value to satisfy the configured edge, the strategy crosses the spread with an IOC.
It is a thin executable over the shared runtime — its native config, including the feeds that derive its NATS subjects, comes from MongoDB.
Flow¶
flowchart LR
DB["MongoDB<br/>version + deployment"] --> TT["toxic-taker bootstrap"]
B["Binance publisher"] -->|"md.binance.ethusdt.default"| N["NATS"]
K["Kuru publisher"] -->|"md.kuru.wethusdc.default"| N
N --> TT
TT --> R["Dyson ProcessSupervisor"]
R --> A["algo-mm Hold + Aggressor"]
A --> X["Guarded Kuru execution"]
R -->|"status + events"| N
A --> P["InfluxDB post-trade projection"]
classDef store fill:#2e7d32,stroke:#1b5e20,color:#fff
class DB,N,P store
The two feeds play different roles:
| Feed | Role |
|---|---|
| Binance trades | Impulse that updates the held fair value |
| Kuru book packets | The executable book inside the embedded runtime |
The upstream Aggressor submits an IOC only when executable depth satisfies its
configured edge and position limits. An inside-spread reference produces no
order at all — that behavior is pinned by tests.
Mandatory execution caps¶
Both max_order_quantity and max_order_quote_notional are required. The
bootstrap rejects a deployment missing either cap before it builds the
execution client, so a misconfigured deployment cannot reach a state where it
could submit an unbounded order.
Running it¶
Start local NATS:
docker compose up -d nats
Then the two publishers. The JSON files at the package root configure the imported publisher processes — they are not read by toxic taker and contain no strategy or execution behavior:
MARKET_DATA_NATS_URL=nats://127.0.0.1:4222 \
BINANCE_MARKET_DATA_CONFIG=strategies/toxic-taker/binance-market-data.json \
external/algo-trading/target/debug/binance_market_data_writer
MARKET_DATA_NATS_URL=nats://127.0.0.1:4222 \
KURU_MARKET_DATA_CONFIG=strategies/toxic-taker/kuru-market-data.json \
RPC_URL="$KURU_MAINNET_RPC_URL" \
RPC_WEBSOCKET_URL="$KURU_MAINNET_WS_URL" \
external/algo-trading/target/debug/kuru_historical_market_data_writer
Then the strategy, with its deployment ID:
cargo run -p dyson-toxic-taker -- deploy-toxic-taker-testnet
It publishes Loaded and waits for an explicit start:
nats pub strategy.commands.deploy-toxic-taker-testnet \
'{"type":"start","command_id":"start-001"}'
Status and events land on strategy.status.Strategy:toxic-taker.{run_id} and
strategy.events.Strategy:toxic-taker.{run_id}. Pause, resume, drain, stop, and
shutdown use the shared lifecycle.
Environment¶
MONGODB_URI=mongodb://localhost:27017
MONGODB_DATABASE=dyson
NATS_URL=nats://127.0.0.1:4222
KURU_TESTNET_RPC_URL=https://...
KURU_TESTNET_PRIVATE_KEY=...
INFLUX_HOST=http://127.0.0.1:8181
INFLUX_DATABASE=dyson
Logs go to stderr. The default filter includes debug from Dyson's runtime —
NATS messages, market-data packets, venue events, lifecycle activity.
RUST_LOG=info narrows it to startup, subscriptions, lifecycle changes, and
errors; RUST_LOG=debug widens it to every dependency.
Operating caveats¶
Mainnet data, testnet execution
The current stress-test arrangement deliberately combines mainnet market data with testnet execution. Verify the MongoDB version's execution orderbook address and hard limits before every run.
This deployment stays restricted to controlled testnet use until shutdown verifies authoritative venue reconciliation.
Configuration fixtures live under strategies/toxic-taker/tests/fixtures/mongodb
— strategy_version.json and strategy_deployment.json. They are fixtures, not
a production source: replace the placeholder orderbook address, source commit,
and config hash before creating real records.