Architecture

An agent is a strategy plugged into a fixed pipeline. The strategy only ever proposes — every risk cap is enforced by the agent, after simulation, before anything executes. A strategy cannot skip the gate, in paper mode or live.

The pipeline

1

Observe — Market (src/framework/market.ts)

Every number a strategy sees comes from a live RPC call through the hoodchain SDK: Chainlink latestRoundData() for Stock Token quotes, Uniswap v3 QuoterV2 simulated calls for spot prices, and the live sequencer/log watchers for new launches. Nothing here is cached beyond a few seconds (ETH/USD, 30s) and nothing is fabricated — nulls propagate instead of invented numbers.

2

Decide — Strategy.tick()

Pure function of the current market view and open positions to a Decision: a list of Intents (buy/sell proposals) and Alerts. Strategies never touch the wallet, the risk engine, or the journal directly — they cannot.

3

Simulate — a real eth_call

Every intent is re-quoted against live liquidity via QuoterV2 before anything else happens — this is a real simulation against the current pool state, the same call type Uniswap's own frontend uses, not a cached or stale price.

4

Risk-check — RiskEngine.check() (fails CLOSED)

Kill switch → zero/negative amount → cooldown → slippage bound → position cap → agent daily cap → fleet daily cap, in that order, so the journaled refusal reason is always the most specific true one. Any check the engine cannot satisfy refuses the trade — there is no default-allow path. See the full table below.

5

Execute

Paper: the simulated fill is recorded as the trade — same code path, same accounting, no signature. Live: buildSwapTxensureApprovalsendTransaction → wait for receipt, using hoodchain's router-flavor-aware swap builder (mainnet SwapRouter02 vs. testnet's classic SwapRouter).

6

Journal — SQLite (src/framework/journal.ts)

Every trade AND every refusal is written, with the strategy's reason string, the risk verdict, and an equity mark. bigints are stored as decimal TEXT (SQLite's native integer is 64-bit signed; token amounts routinely exceed that). The dashboard reads straight from this table — nothing is held only in memory.

Risk rails

Every rail applies identically in paper and live mode. Sells are exempt from the position/spend caps — they reduce risk, and refusing a de-risking sell because of a spend cap would trap an agent in a losing position.

RailEnv varEnforced where
Per-position capAGENT_MAX_POSITION_USDGBefore any buy — projected position value must stay under the cap
Per-agent daily spend capAGENT_MAX_DAILY_SPEND_USDGBefore any buy — UTC-day rolling spend
Fleet-wide daily spend capFLEET_MAX_DAILY_SPEND_USDGBefore any buy, across every agent
Slippage boundAGENT_MAX_SLIPPAGE_BPSEvery order, buy or sell
CooldownAGENT_COOLDOWN_SECONDSEvery order, buy or sell
Kill switchKILL_FILE / SIGINT / POST /api/killEvery order — halts new risk immediately, never force-sells

Paper vs. live

Paper mode runs the identical pipeline against identical live data — the only difference is step 5: paper records the QuoterV2-simulated fill as the trade, live signs and broadcasts it. This means a strategy validated in paper mode behaves the same way in live mode, modulo real-world slippage/MEV the simulation can't see. Live mode requires both HOOD_TRADERS_LIVE=1 and a valid ROBINHOOD_CHAIN_PRIVATE_KEY — missing either falls back to paper, so there is no accidental-live path.

Why a fresh framework and not hoodkit

This package's prompt called for building on hoodkit (the wave-2 advanced SDK wrapper) if it existed. At build time it had not shipped, so hood-traders talks to hoodchain (the core SDK) directly through src/framework/market.ts — a thin adapter that is the only place a future hoodkit swap-in would touch.