Field manual · rev. 2026-07

Every seed, documented.

This is the complete operator's manual for QUANTHEHOOD: how agents are funded and run, what each strategy actually does, how backtests mirror live fills, and what sits on-chain. Everything here is drawn from the running platform: the screenshots are captured live, the numbers come from the code.

Robinhood Chain testnet · 46630 testnet funds, nothing real at risk

Chapter 00

What this is

QUANTHEHOOD is a desk for autonomous trading agents. You define a strategy and its limits; the platform funds a dedicated wallet with testnet capital and executes on-chain, around the clock, whether your terminal is open or not.

The metaphor is the product: the dandelion head is your strategy, and every deployed agent is a seed released to work the market on its own. Each seed carries exactly 1,000 USDC of testnet capital, trades five tokenized stocks (TSLA, AMZN, PLTR, NFLX, AMD) against USDC, and reports its live value and PnL back to your desk.

Fig. 01The trading desk: chart, live book, order ticket· captured live on testnet 46630

Under the desk sit two price layers and three workers. Real-market candles from Yahoo Finance drive what you see; a Chainlink-mirror oracle on testnet drives what agents fill at. A runner ticks every active agent every 30 seconds, applies your guardrails, and settles every trade as a real on-chain swap you can open in the block explorer.

CHART LAYER: WHAT YOU SEEEXECUTION LAYER: WHAT FILLSYOUR DESKROBINHOOD CHAIN TESTNET · 46630Yahoo Finance v8real-market OHLC candles5m pulled every 60scandle-refresherworker · 5m/60s · 1h+1d/300supsert (ticker, res, ts)market_candlesPostgres · 5m | 1h | 1d to IPOChart + Market backtestscandles back to each IPOChainlink · RH mainnetchain 4663 · real feedsread · NFLX falls back to Yahooprice-pusherworker · every 30s · treasury-signedupdateAnswer · logged to price_historyMockV3Aggregator × 5on-chain feeds · 8 decimalsprices every swapOraclePricedSwapfills at oracle minus 0.30% spreadStrategy + guardrailsthreshold · DCA · trailing · rebalanceevaluated every tickagent-runnerworker · ticks every 30ssigns with custodial keyAgent walletcustodial · 1,000 USDC + gasapprove + swap · 1% slippageTreasuryfunds wallets · signs feed pushes
Fig. 02Two price layers, one desk: every box is real code in this repo

Chapter 01

Quickstart

From zero to a trading agent in about two minutes. The steps are ordered because each one depends on the last.

  1. Bring a browser wallet

    Install MetaMask, Rabby, or Coinbase Wallet. Logging in only ever signs a message. It's gasless and never sends a transaction.
  2. Claim testnet ETH (once)

    Claim ETH from the Robinhood testnet faucet (0.01 ETH per address per 24h). ETH is the only thing you claim. The desk mints your USDC and holds its own tradable stock tokens, so ignore the faucet's stock tokens. Deploying is gated on your wallet holding at least 0.005 ETH, proof of a claim; the desk never spends it, so one claim passes the gate forever.
  3. Connect at /login

    Press Connect wallet. The platform issues a 5-minute nonce, your wallet signs the challenge, and the signature is your login. First connect is account creation. There is no signup form.
  4. Deploy an agent

    On the desk's order ticket, pick a template (or tune a strategy by hand), name your agent, and press Deploy agent. The platform creates a dedicated wallet and funds it with 1,000 testnet USDC plus a sliver of gas. The agent arrives inactive.
  5. Press Start

    Start moves the agent into the live book's rotation: it checks the market every 30 seconds, trades inside your guardrails, and records every fill with its transaction hash.
  6. Watch the desk

    The live book refreshes every 20 seconds with value, PnL, and holdings. Expand Trades for the blotter, or follow any tx link into the public explorer.
Fig. 03Login: one signature, no gas· captured live on testnet 46630
Fig. 04Order ticket: Index 60/40 template applied· captured live on testnet 46630

Chapter 02

The desk

One screen, three instruments: the market chart, the live book, and the order ticket. Everything self-refreshes on its own clock.

The chartshows real Yahoo candles for the selected ticker with your own agents' buys and sells overlaid as markers, plus a dashed oracle line, the price agents actually execute at. The live book lists your agents with status, uptime, value, PnL, and a holdings composition bar; the page header sums it into desk totals (net PnL and active count). The order ticket is a two-tab panel (Deploy and Backtest) whose drafts survive tab switches.

Fig. 05Live book: running agent with blotter expanded· captured live on testnet 46630

The clockwork

Six independent cadences keep the desk honest. If the book misses two polls in a row, its indicator flips to a red Stale, retrying so you never mistake a frozen number for a live one.

Refresh cadences
EveryWhat happens
30sThe oracle price-pusher writes fresh on-chain prices (execution layer)
30sThe agent runner ticks every active agent's strategy
60sThe candle refresher pulls new 5m market candles (chart layer)
20sThe live book re-polls your agents' value, PnL, and holdings
60sThe market chart refreshes candles without touching your zoom
30sThe market-session badge re-checks US trading hours

Chapter 03

Agents

An agent is a strategy, a set of guardrails, and its own on-chain wallet. Its whole life runs through four states.

Agent lifecycle
StateMeaning
inactiveCreated and funded, not trading. This is where every agent starts, and where Stop returns it.
activeIn the runner's rotation, evaluated every 30 seconds.
killedPermanently retired behind a confirm dialog. Final value is frozen for the record; no restart.
deletedSoft-removed from your book (only when not active). On-chain history and the wallet survive.

Capital, exactly once

Deploying funds the agent's wallet from the platform treasury with 1,000 USDC and 0.0001 ETHfor gas, and that is the agent's entire lifetime budget. There is no deposit, no withdrawal, and no key export: the wallet is custodial, its private key AES-256-GCM-encrypted server-side. Want more capital? Deploy another seed. The same 1,000 USDC doubles as the PnL baseline every card measures against.

Fig. 06Agent card: lifecycle controls, blotter, explorer links· captured live on testnet 46630

The card

Each card carries the agent's name, strategy chip, wallet address, status and uptime, live value and signed PnL, and a composition bar of its holdings. Config expands the exact deployed parameters; Trades opens the blotter: pair, amount, price, time, and a tx link straight into the explorer. Sharehands anyone the agent's public explorer page: on this chain, every portfolio is auditable.

Chapter 04

Strategies

Four strategy engines, shared verbatim between live agents and the backtester. Defaults are the deploy form's.

Strategy types
StrategyBehaviorParameters (defaults)
Threshold / limitA range trader: buys whenever price is under your buy level, sells the entire holding when it climbs over your sell level. Keeps buying every tick while the price stays low. The position-cap guardrail is the intended brake.ticker TSLA · buy below $300 · sell above $400 · trade size $100
DCABuys a fixed amount on a fixed schedule, regardless of price. The clock only advances when a buy actually executes, so blocked buys retry next tick.ticker TSLA · amount $50 · interval 3600s (real seconds, in backtests too)
Trailing stop / take-profitOne-shot position rider: buys in at start, ratchets a peak, sells everything on a trail-percent drop from peak or a take-profit gain from entry, then permanently stops. If a position cap blocks the entry, it auto-halves the attempt (down to 1/64) until it fits.ticker TSLA · initial buy $500 · trail 10% · take profit 50%
Rebalance to weightsHolds the portfolio at target weights and trades only when an asset drifts outside the band. USDC's weight is implicit. It is never traded directly. Weights must total exactly 100%.weights TSLA 50 / USDC 50 · band 5%

Templates

Four one-click presets sit above both the Deploy and Backtest forms. Their taglines quote measured results from a strategy test matrix run on 26 days of real recorded prices, not projections. Applying a template stomps your draft (with an Undo on the toast) and, on Deploy, fills the guardrails too.

Strategy templates
TemplatePresetMeasured note
Range Riderthreshold · TSLA · b<375 s>400 · $100/order · cap $300+6.6% on the last 26d of real prices, 2.4% max drawdown
Dip Hunterthreshold · TSLA · b<385 s>395 · $50/order · cap $200 · DD 15%+2.8% on the same 26d window: tighter range, more trades
Steady StackerDCA · AMZN · $50 hourly · cap $600Long-horizon accumulation: judge it over weeks, not days
Index 60/40rebalance · TSLA 60% / USDC 40% · band 5% · DD 20%Shines when the market actually moves

Chapter 05

Guardrails

Hard limits applied after the strategy speaks and before anything executes. A blocked intent simply never trades.

Guardrails
GuardrailExact semantics
Max position (USD)Blocks any buy where the current position value plus the spend would exceed the cap. Sells are never blocked.
Max drawdown (%)While portfolio value sits below peak × (1 − dd%), all buys are blocked. Sells stay allowed. The agent can still de-risk. The peak is tracked automatically, every tick.
Kill switchBlocks everything, buys and sells. The only guardrail that stops selling.

Strategies never see your guardrails: they propose, guardrails dispose. That separation has one designed interaction: a trailing agent whose initial buy exceeds a lower position cap would deadlock, so its entry auto-halves (investUsd/2ᵏ down to 1/64) until an attempt fits under the cap's headroom.

Chapter 06

Backtesting

Dry-run any config against three kinds of price history (including every daily candle back to each stock's IPO) through the same code path the live runner uses.

Price sources
SourceWhat it replaysBounds
SyntheticA deterministic simulated walk: same seed, same series, every time. Tests strategy logic, not market truth.steps ≤ 10,000 · seed-reproducible
HistoricalReal recorded oracle ticks: the exact prices live agents executed at.26 days available
MarketReal Yahoo market closes. Daily resolution reaches back to each stock's IPO. AMD's series starts in 1980.5m ≤ 60d · 1h ≤ 730d · 1d ≤ 20,000d

Live parity, by construction

The simulator builds the same context the live runner builds, calls the same evaluate, applies the same guardrails, and fills trades with the swap contract's exact quote formula including the 0.30%spread. Timestamps are real epoch seconds, so time-based strategies (DCA's interval) behave identically in replay and live. What it deliberately doesn't model: gas, slippage curves, partial fills.

Fig. 07Dip Hunter replayed over TSLA's full daily history· captured live on testnet 46630

Results come back as an equity curve plus four metrics: Return, Max drawdown, Trades, and Final value. One click on Deploy this config → hands the exact tested draft to the deploy form. Guardrails are the one thing the bridge does not carry. Set them again before deploying.

Chapter 07

Market data

Two deliberate price layers: real-market candles for your eyes, a Chainlink-mirror oracle for agent fills. The chart shows both, labeled.

Fig. 08Market chart: Yahoo candles, dashed oracle line· captured live on testnet 46630

The chart layer is genuine Yahoo Finance OHLC, refreshed continuously: about 60 days of 5-minute bars, two years of hourlies, and daily candles to each IPO. The execution layer is the price a worker pushes on-chain every 30 seconds, mirrored from Robinhood Chain mainnet Chainlink feeds. That dashed oracle line is where your agents actually fill. Candles are lime up, red down; your buys print a lime B under the bar, sells a white S above it.

Chart intervals
IntervalWindow shownDepth
5m≈300 bars, the latest sessions~60 days stored
1h≈500 bars (2w change label)~730 days stored
4haggregated from 1h on the fly3-month view
1dup to 5,000 bars (10y label)back to IPO, TSLA 2010
1wMonday-anchored weekly bucketsmax, AMD reaches 1980

Chapter 08

The chain

Robinhood Chain testnet has no native oracle or DEX, so the platform deploys its own market substrate: mintable tokens, mock Chainlink feeds, and an oracle-priced swap.

Chain facts
FactValue
Chain id46630. Any other chain is structurally refused
Default RPCrobinhood-testnet.drpc.org
Explorerexplorer.testnet.chain.robinhood.com
Faucetfaucet.testnet.chain.robinhood.com. Users claim ETH here for the deploy gate (also grants 5 canonical stock tokens, which the desk does not use)
Token decimals18 everywhere, including USDC (unlike the real 6). Oracle prices are 8-decimal integers.

How a trade settles

Every fill is two real transactions from the agent's own wallet: an ERC-20 approve, then swap on the OraclePricedSwap contract. The pool fills any stock↔USDC pair from treasury-seeded inventory at the oracle price minus a 0.30% spread, the only trading cost on the platform. Default slippage tolerance is 1% with a 120s deadline; stock-to-stock swaps are unsupported by design. Trading reads demand a price fresher than 120s and a live sequencer. A stalled oracle halts trading rather than filling on stale marks.

Contract registry

Each stock carries its own Chainlink-compatible feed, seeded with 500,000 tokens of swap liquidity. The price-pusher signs a fresh updateAnswer for all five feeds every 30 seconds from mainnet Chainlink reads. NFLX, which has no mainnet feed, mirrors its latest 5-minute Yahoo close instead. A registry check cross-verifies every address against on-chain symbol and decimals at load.

Chapter 09

Access & accounts

Wallet-signature login only. No passwords, no email. Your address is your account.

Login is a nonce-challenge flow: the server issues a single-use nonce (valid for 5 minutes, mirrored into an httpOnly cookie), your wallet signs the exact challenge below via personal_sign, and the server verifies the signature and that the message carries that nonce. Sessions are stateless JWTs carrying your user id and address.

Sign-in challenge · personal_sign
QUANTHEHOOD wants you to sign in with your wallet:
{address}

Sign in to access your agents and portfolio. This request
will not trigger a transaction or cost gas.

Nonce: {nonce}

Chapter 10

API reference

Every route the platform exposes. Authenticated routes read your NextAuth session cookie; USD values travel as 1e18 fixed-point strings, oracle prices as 8-decimal integers.

Auth

Wallet-signature login. Fetch a nonce, sign the challenge, exchange it for a JWT session. First sign-in creates the account.

GET/api/auth/wallet/nonce#

Issues a single-use login nonce (32 hex chars), returned in the body and mirrored into the httpOnly qh_wallet_nonce cookie for server-side verification.

Returns
{ nonce } + Set-Cookie: qh_wallet_nonce (HttpOnly, SameSite=Lax, Max-Age 300s)

POST/api/auth/callback/wallet#

NextAuth credentials callback for the wallet provider. Verifies the personal_sign signature over the exact challenge message and that it contains the literal line “Nonce: <nonce>” matching the cookie, then upserts the user by lowercased address.

Params
address, message, signature (via next-auth signIn("wallet", …))
Returns
JWT session cookie carrying user.id + user.address
Errors
signature/nonce mismatch → login rejected

Agents

Deploy, run, and retire agents. All routes require a session and operate only on agents you own.

GET/api/onboardingsession#

Pre-deploy status for the order ticket: whether your login wallet has passed the faucet gate, and how many live agents the account already runs. The deploy route re-checks both.

Returns
{ address, ethWei, faucet { url, minWei, minEth, ok: boolean | null }, agents { alive, max } } - ok:null means the balance read failed (RPC blip)

POST/api/agentssession#

Deploys an agent: creates a custodial wallet, funds it from the treasury with 1,000 testnet USDC + 0.0001 ETH gas, stores the 1,000 USDC PnL baseline, and inserts the agent as inactive. Gated on a faucet claim and a per-account live-agent cap.

Params
name (required) · strategyType: threshold | dca | trailing | rebalance (required) · strategyConfig (zod-validated per strategy) · guardrails { maxPositionUsd?, maxDrawdownPct?, killSwitch? }
Returns
201 { id, address }
Errors
400 invalid config · 403 FAUCET_GATE (wallet must hold ≥0.005 testnet ETH) · 409 agent limit (5 live max) · 503 “chain RPC unavailable. Try again shortly” (retryable)

GET/api/agentssession#

Lists your agents (soft-deleted excluded) with live on-chain value, PnL vs the 1,000 USDC baseline, and a holdings breakdown. Killed agents return their frozen final value without touching the RPC.

Returns
[{ id, name, status, strategyType, address, pnl { pnlUsd, pnlPct } | null, valueUsd, holdings [{ ticker, usd }], strategyConfig, guardrails, createdAt }]

POST/api/agents/{id}/startsession#

Sets the agent active. The runner picks it up on its next 30-second tick.

Returns
{ id, status: "active" }
Errors
404 not found / not owner

POST/api/agents/{id}/stopsession#

Pauses the agent (status inactive). It can be started again.

Returns
{ id, status: "inactive" }
Errors
404 not found / not owner

POST/api/agents/{id}/killsession#

Permanently retires the agent and freezes its final portfolio value for the record. Killed agents cannot be restarted.

Returns
{ id, status: "killed" }
Errors
404 not found / not owner

DELETE/api/agents/{id}session#

Soft-deletes a stopped or killed agent from your book. On-chain history and the wallet key are never destroyed.

Returns
{ id, status: "deleted" }
Errors
409 “stop or kill the agent before deleting” · 404 not found

GET/api/agents/{id}/tradessession#

The agent's full blotter: every executed fill with amounts, tx hash, and effective fill price.

Returns
[{ sellTicker, buyTicker, sellAmount, buyAmount (1e18 wei strings), txHash, priceUsd (8-dp string | null), createdAt }]
Errors
404 not found / not owner

Backtest

One endpoint, three price sources. Fills mirror the on-chain swap quote exactly, including the 0.30% spread.

POST/api/backtestsession#

Replays a strategy config over a price series through the same evaluate → guardrails → execute path the live runner uses, against a virtual portfolio.

Params
strategyType, ticker, startingUsd, series (all required) · series.kind: "synthetic" { startUsd, volPct=20, steps=100 (≤10,000), seed=1 } | "recorded" { fromTs, toTs (≤366d) } | "market" { res: 5m|1h|1d, lookbackDays (5m ≤60 · 1h ≤730 · 1d ≤20,000) }
Returns
{ pnlCurve [{ t, valueUsd }], trades [{ t, sell, buy, sellAmount, buyAmount }], metrics { totalReturnPct, maxDrawdownPct, numTrades, finalValueUsd } }
Errors
400 “no market data for that ticker” / “no price data for that range” / “invalid config: …”

Markets

Public price data. The same feeds the landing page and dashboard chart read.

GET/api/markets#

Ticker board plus platform counters. Oracle prices older than 600 seconds are silently omitted.

Returns
{ tickers [{ ticker, priceUsd (2-dp string) }], activeAgents, totalTrades }

GET/api/markets/{ticker}/history#

OHLC candles from the Yahoo layer for one of the five tickers, falling back to oracle-tick aggregation when no candles exist. 4h and 1w are SQL-aggregated on the fly; weekly buckets anchor on Monday 00:00 UTC.

Params
interval: 5m | 1h | 4h | 1d | 1w (bar windows 300/500/500/5000/3000) · legacy mode without interval: hours 1–168
Returns
{ ticker, interval, candles [{ time, open, high, low, close }] } ascending
Errors
400 “unknown ticker” / “unknown interval”

GET/api/tradessession#

Your own buy/sell markers for one ticker, windowed to the charted range. This is what overlays B/S arrows on the candles.

Params
ticker (required) · hours 1–8760 (default 24)
Returns
{ markers [{ time, side: buy|sell, agentName, txHash }] } - 500 newest, ascending
Errors
401 · 400 “unknown ticker”

Chain

Raw reads against testnet 46630. Public by design. Every portfolio on this chain is auditable.

GET/api/chain/health#

Liveness probe: asserts the RPC reports chain id 46630 and returns the current block.

Returns
{ chainId: 46630, blockNumber, ok: true }

GET/api/chain/prices#

All five oracle feeds with freshness (≤120s) and sequencer-up enforcement. All-or-nothing: one stale feed fails the call.

Returns
[{ ticker, priceUsd (8-dp integer string), decimals: 8, updatedAt }]
Errors
500 on stale price or sequencer down

GET/api/chain/portfolio#

USD-valued holdings of any address across the asset registry. USDC is pegged at $1; stock prices tolerated up to 3600s stale.

Params
address (0x…, required)
Returns
{ totalUsd (1e18 string), holdings [{ ticker, balance, usd }] } - zero balances omitted
Errors
400 “address required”

GET/api/chain/swap/quote#

Read-only quote from the swap contract: how much buy-token a sell amount fetches, net of the 0.30% spread.

Params
sell, buy (tickers) · sellAmount (wei string)
Returns
{ buyAmount (wei string) }
Errors
400 missing params · 500 on quote revert

POST/api/chain/walletsession#

Creates a standalone custodial wallet for your account (not attached to an agent). The key is AES-256-GCM encrypted server-side; the address is returned, the key never is.

Returns
201 { id, address }
Errors
500 “wallet creation failed” (orphan row rolled back)

Chapter 11

Run your own

One Next.js app, three tsx workers, one Postgres. Fill an env file, push the schema, build, and run under pm2.

Environment variables
VariablePurpose
DATABASE_URLPostgres connection string: app, workers, and drizzle-kit all read it
AUTH_SECRETSigns the NextAuth session JWTs
AUTH_URL · AUTH_TRUST_HOSTCookie host for NextAuth: baked at build time; a public hostname needs a rebuild
WALLET_ENCRYPTION_KEY64 hex chars (32 bytes): AES-256-GCM key for custodial wallet keys
TREASURY_PRIVATE_KEYFunds agent wallets and signs every oracle push. Top up via the faucet
RH_CHAIN_RPC_URLTestnet RPC override (default: dRPC endpoint)
RH_MAINNET_RPC_URLRead-only mainnet RPC for the price-pusher's Chainlink reads
Operator console
pnpm install
pnpm drizzle-kit push   # sync schema (the checked-in SQL migrations lag it)
pnpm build && pnpm web  # serves on :2555

npx tsx services/price-pusher/index.ts      # oracle, every 30s
npx tsx services/agent-runner/index.ts      # agent ticks, every 30s
npx tsx services/candle-refresher/index.ts  # Yahoo candles, every 60s

Production runs the same four processes under pm2 (rh-web, rh-pusher, rh-agent-runner, plus the candle refresher) behind a Caddy reverse proxy with automatic TLS. The test suite is 46 vitest files: pnpm test for units, pnpm itest to unlock the live-testnet integration set (it spends real testnet tokens), plus 11 Foundry tests under contracts/.

Chapter 12

Field notes

The honest edges: behaviors that surprise first-time operators, written down so they don't have to.

  • Deploying is gated twice: your login wallet must hold a faucet claim (≥ 0.005 ETH), and each account caps at 5 live agents. Both exist to protect the shared treasury that funds every seed.
  • There are two sets of TSLA/AMZN/PLTR/NFLX/AMD tokens on this testnet: the faucet's canonical ones (unused) and the desk's own mintable MockStock contracts (what agents actually trade, seeded 500k each into the swap pool). You never need the faucet's stock tokens. Agents buy stock by swapping USDC in the pool. Claim ETH from the faucet; everything else the desk provides.
  • A threshold agent re-buys every 30-second tick while price sits under its buy level. During a closed-market lull it will happily convert all 1,000 USDC into stock. The position cap is the intended brake.
  • A trailing agent is one-shot: after its sell fires, it marks itself done and never re-enters. Restarting doesn't reset it.
  • DCA intervals are real wall-clock seconds, in live trading and in backtests alike. 3600 means hourly, everywhere.
  • The 0.0001 ETH gas grant is a lifetime budget. A gas-dry agent still shows active but stops filling. The only evidence is in server logs.
  • Profits stay in the agent's custodial wallet forever. There is no withdrawal on a testnet where tokens have no real value. Kill freezes the books; it doesn't liquidate.
  • NFLX has no mainnet Chainlink feed. Its oracle mirrors the latest 5-minute Yahoo close, so it can lag the tape by a few minutes.
  • If the oracle stalls past 120 seconds, trading halts with a stale-price error rather than filling on old marks. Portfolio valuation tolerates up to an hour.
  • Backtest results are history, not forecasts, and Market-source replays fill at Yahoo closes while live agents fill at oracle prices.