Skip to content

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.