Arena for agents
Prompt your agent
Once arena login has run, paste this into Claude Code, Cursor or any other coding agent to teach it the CLI in one shot.
You have the Arena CLI installed as `arena`. It is Arena Predictions:
paper trading for prediction markets at live prices, in a sandbox. Every order
you place is a paper order, in paper dollars, unless I ask for a live order in
those words.
Start by running `arena` on its own. It prints my balance and how many
positions I have open, so you do not need a discovery round trip.
The commands you will want:
arena markets --league nba open markets
arena search "kansas city" find a market by text (titles name cities)
arena resolve "chiefs ml" a bet in words to an instrument id, ticker and side
arena game "chiefs dolphins" every instrument on one game, with its ids
arena instrument <ins_id> one instrument: what YES means, which ticker holds it
arena quotes <ins_id> one bet's price on Kalshi, Novig and Polymarket
arena gaps price gaps for the same bet across venues, after fees
arena perps Kalshi perp tickers and their live marks (needs login)
arena market <ticker|ins_id> one market in detail
arena orderbook <ticker|ins_id> resting bids and asks
arena chart <ref> --range 24h the recent price path
arena positions open positions with P&L
arena balance balance, net worth, P&L since my last reset
arena buy <ticker|ins_id> --side yes --contracts 10 --limit 45 --yes
buy at 45c or better; without --limit it buys at the ask
arena orders my resting limit orders
arena cancel <order-id> --yes withdraw a resting order and free its hold
arena sell <trade-id> --yes
arena history settled trades
arena stats my trading graded: win rate, calibration, drawdown
arena privacy whether my account is private (the default)
Store instrument ids (ins_...), not tickers: an id is permanent. With an
instrument id, --side means the instrument's side, and the CLI maps it to the
ticker's side and shows both. From arena-prediction-cli 0.6.0,
words with a day that match nothing ("jets ml tonight" on a night off) exit 6
not_found and name the next game, and help[0] is the same command on its id.
From arena-prediction-cli 0.5.0, add --prob <pct> to a buy to record your
probability that the side wins, a whole percent from 1 to 99. It never
changes the fill; it is saved with the trade and scored once the pick settles.
From arena-prediction-cli 0.5.0 (arena --version shows yours), buy and sell use
--venue best by default: a paper fill can take Novig's or Polymarket's price
for the same bet when it passes Arena's checks, and the receipt names the
venue. Pass --venue kalshi to fill at Kalshi's price. An older CLI has no
--venue, so leave the flag off there: its trades always fill at Kalshi's price.
arena quotes and arena gaps name each price's venue and its time, and leave a
price out with a note when it cannot be compared: during a game Novig shows
only when Novig updated it in the last minute and ProphetX not at all, and a
Novig price left out as out of date says so. Tell me which venue each price is
from, and pass the notes on.
Your output is piped, so the CLI is already in agent mode: you get TOON instead
of tables and you are never prompted. buy, sell and cancel need --yes; without
it they exit 2 and send nothing. arena settle needs no --yes, because it only
settles trades whose markets have already resolved. Add --json to any command
if you would rather parse JSON.
Exit codes: 0 ok, 1 internal error (a bug, report it), 2 bad usage, 4 not
signed in, 5 needs an Arena Basic membership, 6 refused by the backend
(final), 7 network or server error.
From arena-prediction-cli 0.6.0 a rate limit is 7 too,
rate_limited, with retryAfterSeconds (in 0.5.0 a 429 was 6, worth retrying
after Retry-After), and 4 also covers key_required (arena eval with neither
a login nor an API key) and invalid_token (an API key that was refused). Every error also has a machine code (not_found, no_liquidity,
price_moved, ...). After a 7 on a
buy, run arena positions before anything else; a buy you ran with
--idempotency-key <uuid> can be retried with the command its receipt prints
(the same order, the same key), which answers the trade already placed and
never places a second one.
Before you buy anything, run arena buy ... --dry-run and tell me the venue, the
price and the stake. It places nothing. arena market <ref> --contracts N shows
the estimated venue fee, which is not charged on paper. A dry run names the venue
from 0.5.0; on an older CLI the venue is always Kalshi. Before you sell,
from 0.5.0 run arena sell <trade-id> --dry-run, which shows the exit price
with --venue best. Otherwise run arena market <ticker> and read the side's bid
(yes_bid or no_bid), and tell me contracts x bid / 100 dollars. Never place or
close a trade I did not ask for.
Never send a live order unless I ask for a live order in those words. A live
order goes to my own Kalshi or Polymarket International account with my own
venue credentials, saved in Settings: real money at the venue, under the
venue's rules and terms. Arena is not a broker and never holds my funds. So
never add --live or --poly-intl to a command, and never set ARENA_KALSHI_LIVE
or ARENA_POLY_INTL_LIVE, on your own. When I do ask for a live buy, it carries
a price I set, --max-price or --limit: never send one without it, and ask me
for the price if I gave none. ProphetX orders are switched off.
From arena-prediction-cli 0.6.0, run arena open to see it on the
web: arena open gives my dashboard link, and arena open trade <id>, market
<ins_id> or game <game_id> the page for one thing. It opens nothing for you;
give me the link. A link with audience owner shows only my own data, so never
send it to anyone else.
From arena-prediction-cli 0.6.0, a buy dry run says what the bet
pays if it wins and the most it can lose (payoutDollars, maxLossDollars and a
pays sentence): tell me both in dollars. For an ins_ id or a bet in words
its help[0] is exactly the order it priced, so run that and nothing else when
I say yes; after a Kalshi-ticker dry run it names the Arena instrument the
ticker lists (an unlisted ticker's line is placeTemplate). If it says the order would be refused
as priced (overCap), price it again before anything else. arena buy takes a bet in
words too (arena buy steelers ml --stake 100 --dry-run). A dry run over my cash
exits 6 insufficient_balance, and a side nobody sells exits 6 no_liquidity:
tell me why, and never place it anyway.
From arena-prediction-cli 0.6.0, rows say the bet in words
(bet, such as "PHI ML (PHI at CHI)"): tell me the bet, not the ticker. Bare
arena lists recentlySettled, the bets that settled in the last 24 hours: tell
me how each one ended.
From arena-prediction-cli 0.6.0, arena stats grades the
probability I state with --prob next to the price I paid, and arena eval scores
my own settled picks the way the eval board scores them. arena eval reads with
my login, or an API key I made; if it exits 4 with key_required, tell me, and
never make a key on your own.The AXI contract
Arena implements AXI (axi.md). If you are wiring this into an agent, this page is the contract.
Agent mode triggers itself
The CLI is in agent mode when !process.stdout.isTTY (that is, whenever output is piped or captured) or when ARENA_AGENT=1 is set. There is nothing to configure. In agent mode:
- output is TOON, not tables, and never has spinners or colour;
- there are no prompts, ever.
buy,sellorcancelwithout--yesis a hard error (exit 2, nothing sent), and the CLI will not wait on stdin.arena settleneeds no--yes, because it only settles trades whose markets have already resolved; - stdout carries data only. stderr carries errors, logs and diagnostics, so
2>/dev/nulldrops the error text but never the exit code. The one exception is--json: from CLI 0.5.0 an error under--jsonis JSON on stdout, so a script that reads stdout always gets JSON; - every list reports
totalCount, and an empty result prints an explicitcount: 0oremptyReasonrather than nothing at all; - every output ends with a
help[]array of next-step commands, each anarena ...command that is not the one just run. A value the CLI knows (an id, a name, the first row) is filled in; a<placeholder>is left only where nothing is known, or where the value would be a venue's ticker, which Arena never repeats.
The home view
Bare arena is live state, not help text. An agent can orient itself in one call.
bin: arena
version: 0.6.0
description: Arena paper trading for markets, positions and trades from the terminal.
signedIn: true
openPositions: 3
balance: 98430.5
pnlSinceReset: -1569.5
allTimeRank: null
allTimeRankReason: private
help[3]:
- arena games
- arena positions
- arena statsTOON shape
This is the whole grammar the CLI writes.
- A value is
key: value. An object iskey:with its fields indented two spaces per level. Every line ends in a line feed, the last one too. - An array of objects with the same keys in the same order is a table:
key[N]{f1,f2}:and then N rows, indented one level. Each row gives the values in field order, split by commas. - An array of primitives is inline,
key[N]: a,b, when no value needs quotes and the values, commas included, fit in 80 characters. - Any other array is a list. It is
key[N]:and then one- itemper line. - An empty array is
key[0]:. - Numbers,
true,falseandnullare bare. - A string is quoted when it is empty, has a leading or trailing space, holds a double quote, backslash, line break or
:(a colon and a space), could be read as a number,true,falseornull, or starts with-or[. Inside quotes,\",\\and\nare the escapes. - A comma forces quotes only in a table row or an inline array. On a
key: valueline or a- itemline, the value runs to the end of the line.
leaderboard[3]{rank,name,pnl,tradesClosed,resets,busts}:
1,sharp_joe,2683.84,66,0,0
2,kcnerd,1089.3,41,1,0
3,longshot_lu,612.5,23,2,1
totalCount: 3
window: all
kind: people
rule: Total profit since Sep 1, 2026, across every reset. Accounts with at least 20 closed trades and a first trade 30 or more days ago.
top: 3
refreshedAt: 2026-10-05T05:10:00Z
help[3]: arena trader <ref>,arena leaderboard --window 30d,arena leaderboard --aiAn empty result is an answer, not a failure. It exits 0 and says why:
trader:
traderId: 4d2f8c1e-9b0a-4c6e-8f21-3a7b5d9e0c12
displayName: sharp_joe
rank: 1
lifetimePnl: 2683.84
run: 0
resets: 0
busts: 0
record: 41-22-3
resets[0]:
emptyReason: no resets
help[2]: arena trader <ref> picks,arena leaderboard --window 30dErrors are structured too. In agent mode an error is TOON on stderr, never on stdout, unless `--json` asked for JSON. The exit code is the signal: branch on it, then read `code` (from CLI 0.5.0, the same words the MCP server uses, such as `not_found`, `no_liquidity`, `price_moved` or `confirmation_required`) and `retryable`, then the message. From CLI 0.6.0 a mistyped flag or a missing argument is the same envelope, `invalid_request` with exit 2, and `help[]` holds the corrected command (when there is one) and the command's `--help`; 0.5.0 prints a one-line parser message on stderr, with exit 2. A failed lookup looks like this:
error:
code: not_found
message: "no trader matches \"nobody_here\". Handles are case-insensitive; arena leaderboard lists names you can try."
exitCode: 6
retryable: false
help[2]: arena leaderboard,arena leaderboard --ai--json prints JSON instead, in either mode. It is the stable, non-proprietary escape hatch and it is not going away. From CLI 0.5.0 it is shaped: a receipt is { receipt, trade }, rows leave out internal columns, --fields applies, and an error is {"error": {code, message, exitCode, retryable, help}} on stdout. --raw adds the server's answer exactly as it came.
Exit codes
- 0
- Success, including a definitive empty result.
- 1
- Internal error: a bug in the CLI itself. Report it rather than retrying in a loop.
- 2
- Usage error: unknown flag or bad argument. Reserved for this, so the CLI never silently ignores a flag it does not understand.
- 4
- Not signed in, or the session expired after one refresh attempt: run arena login. From CLI 0.6.0 also
key_required(arena evalwith neither a login nor an API key: runarena login) andinvalid_token(an API key that was refused: invalid, revoked or expired; make a new one). - 5
- An Arena Basic membership is required for this action. From CLI 0.6.0 also
quota_exceeded: the month's API requests are used. - 6
- Refused by the backend, final: market closed or resolved, no liquidity, insufficient balance, trade not yours, record or instrument not found. Do not retry. In CLI 0.5.0 a 429 (rate limited) was exit 6 too, the one worth retrying after the Retry-After, and a rate-limited dry run read as
no_liquidity. - 7
- Network failure or a 5xx, and from CLI 0.6.0 any 429 rate limit,
rate_limited,arena quotesandarena gapsincluded, withretryAfterSeconds(the server's count or its Retry-After, else 60) and the wait in the message. Retry a read after that wait. After a buy, sell or cancel, runarena positionsandarena ordersbefore anything else, and never resend: the request may have gone through, and the CLI already retried a lost market buy with the same idempotency key.
| Code | Meaning |
|---|---|
| 0 | Success, including a definitive empty result. |
| 1 | Internal error: a bug in the CLI itself. Report it rather than retrying in a loop. |
| 2 | Usage error: unknown flag or bad argument. Reserved for this, so the CLI never silently ignores a flag it does not understand. |
| 4 | Not signed in, or the session expired after one refresh attempt: run arena login. From CLI 0.6.0 also key_required (arena eval with neither a login nor an API key: run arena login) and invalid_token (an API key that was refused: invalid, revoked or expired; make a new one). |
| 5 | An Arena Basic membership is required for this action. From CLI 0.6.0 also quota_exceeded: the month's API requests are used. |
| 6 | Refused by the backend, final: market closed or resolved, no liquidity, insufficient balance, trade not yours, record or instrument not found. Do not retry. In CLI 0.5.0 a 429 (rate limited) was exit 6 too, the one worth retrying after the Retry-After, and a rate-limited dry run read as no_liquidity. |
| 7 | Network failure or a 5xx, and from CLI 0.6.0 any 429 rate limit, rate_limited, arena quotes and arena gaps included, with retryAfterSeconds (the server's count or its Retry-After, else 60) and the wait in the message. Retry a read after that wait. After a buy, sell or cancel, run arena positions and arena orders before anything else, and never resend: the request may have gone through, and the CLI already retried a lost market buy with the same idempotency key. |
Any other non-zero exit is an unexpected internal error, so treat it as a bug and report it rather than retrying in a loop.
# agent mode, no --yes: refused before any request is made, exits 2, never waits on stdin
$ ARENA_AGENT=1 arena buy ARENA-EXAMPLE-2 --side yes --contracts 1
error:
code: confirmation_required
message: refusing to mutate without --yes in agent mode (non-TTY or ARENA_AGENT=1); pass --yes to confirm, or --dry-run to price it without placing
exitCode: 2
retryable: false
$ echo $?
2Ambient context hook
Let the agent see your balance and how many positions you have open before it acts.
A session-start hook lets the agent see your balance and how many positions you have open before it acts, instead of spending a discovery round trip on it. Bare arena in agent mode prints both (the home view above), so the hook is one line of config: in Claude Code, ~/.claude/settings.json. (arena hook install, which writes this for you, is not in this release.)
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "arena" }] }
]
}
}bin: arena
version: 0.6.0
description: Arena paper trading for markets, positions and trades from the terminal.
signedIn: true
openPositions: 3
balance: 98430.5
pnlSinceReset: -1569.5
allTimeRank: null
allTimeRankReason: private
help[3]:
- arena games
- arena positions
- arena statsThe hook runs bare arena, which reads nothing from the hook's input (no session id, transcript or working directory).