If your trading idea fits in a few sentences, the idea is rarely the expensive part. The expensive part is everything around it: an API client, order signing, reconnects, keeping track of what is resting in the book. That is a week of work before the first order, and most ideas don't survive it.
So I tried the other route. Write the rules in plain English, give Claude Code a set of exchange tools over MCP, and let it run the rules every round.
Disclosure: I work on Lucyd, the exchange used here. It runs on the Sepolia testnet with test USDC, so the agent trades against a live order book without anyone's money at risk.
The market in one paragraph
Each round asks whether BTC/USD will be above the strike when the round ends. The strike is the Pyth price at the start of the round. There are two tokens, Up and Down, and the winning one pays 1 USDC. If Up trades at 0.62, the book puts 62% on Up. Rounds run for 5 minutes, 15 minutes, 30 minutes, 1 hour, 4 hours and a day.
https://www.youtube.com/watch?v=8J9DvOhKxuc
How Lucyd works: one round from open to payout.
That shape is why these markets suit an agent. One question, two outcomes, and a maximum loss you know before you click.
The pieces
Claude Code is the agent. It reads the strategy, decides, and calls tools.
The Lucyd MCP server provides those tools: markets, order books, trades, positions, orders and payouts. It runs on your machine and signs orders with a wallet key that never leaves it.
The strategy is a file called strategy.md. A scheduler starts the agent once per round.
Connect read-only first
The hosted server needs no install and no key. It only has the market data tools, which is all you need to check that the wiring works:
claude mcp add --transport http lucyd https://mcp.lucyd.trade/mcp
Ask "What are the open 15-minute Bitcoin markets and their prices?" and you should get the current round with its strike and book.
To trade, switch to the local server. It needs Node.js 18 or newer and the key of a wallet created only for the agent:
claude mcp add lucyd -e LUCYD_PRIVATE_KEY=0xYOUR_AGENT_KEY -e LUCYD_MAX_ORDER_USD=20 -- npx -y @lucyd/mcp
Run this inside the folder the agent will work from. claude mcp add saves the server for that project by default, and the scheduled runs later start in the same folder.
Then ask once: "approve trading and turn on auto redeem". That sends the one-time approvals for the Exchange contract and switches on automatic payout of winnings. Those transactions need a little Sepolia ETH for gas. Orders themselves don't: the exchange settles fills on chain.
What the agent can call
| Tools | What they do |
|---|---|
list_markets, get_market |
Open rounds, strike, end time, rules |
get_order_book |
Bids, asks, best bid and ask of one outcome |
list_trades, top_traders |
Recent trades and the leaderboard |
get_positions, get_trader_stats, get_pnl_history |
What the wallet holds and how it is doing |
place_order, cancel_orders, close_position |
Trading |
get_open_orders, get_order_history |
Your resting and past orders |
get_wallet, approve_trading, set_auto_redeem, redeem |
Balances, setup and payouts |
get_status |
Whether the exchange and settlement are healthy |
Two details matter for the strategy. get_order_book returns one outcome per call, so the agent asks for Up and Down separately. And place_order takes a number of whole tokens and a limit price, not a dollar amount, so the rules should say how to turn dollars into tokens.
The strategy file
Numbers, not adjectives. This one is for 15-minute rounds:
# Strategy: 15-minute Bitcoin Up or Down
Every run:
1. Find the open 15m round. Skip it if less than 3 minutes are left.
2. Read the strike and the Up and Down order books.
3. Get the current BTC price from https://api.coinbase.com/v2/prices/BTC-USD/spot and compare it with the strike.
4. If BTC is more than 0.15% above the strike and Up's best ask is below 0.70, buy Up for 10 USDC.
If BTC is more than 0.15% below the strike and Down's best ask is below 0.70, buy Down for 10 USDC.
Buying means a fak order at the best ask for 10 / ask tokens, rounded down.
Otherwise do nothing.
5. Never hold more than 20 USDC in one round. Never place more than 6 orders a day.
6. Append one line to trades.log: time, round, action, price, reason.
Why 0.15%: in our data the median 15-minute move is about 0.07% (statistics on 8,000 settled rounds). A price that far from the strike with minutes left is a real lead, and the rule only buys it while the book still sells it cheap.
You could point the same file at 5-minute rounds by changing the period and the cutoff. I'd start on 15 minutes anyway, for the reasons in "Where the agent loses to plain code" below.
Running it every round
Inside a Claude Code session, /loop repeats a prompt on an interval:
/loop 15m Follow strategy.md for the current 15-minute Bitcoin round.
Timing matters here. At the open BTC sits on the strike, so a run at that moment almost never sees a 0.15% gap. Start the loop about 9 minutes into a round, so every run lands with around 6 minutes left.
To run without an open terminal, call Claude Code headless from cron, once per round, and allow only the tools the run needs. The 9-59/15 schedule fires at :09, :24, :39 and :54, the same 6 minutes before each close:
9-59/15 * * * * cd ~/agent && flock -n /tmp/lucyd-agent.lock claude -p "Follow strategy.md for the current 15-minute Bitcoin round." --allowedTools "mcp__lucyd__*" "WebFetch(domain:api.coinbase.com)" "Read" "Edit" "Write" >> run.log 2>&1
flock skips a run if the previous one is still going. The per-round and daily limits in strategy.md are only enforced by the agent itself, so two runs at once could both spend the same allowance. The server enforces only LUCYD_MAX_ORDER_USD per order, so don't start a second agent on the same key.
The file tools are there for strategy.md and trades.log. Nothing else on the machine is in reach of the run.
Where the agent loses to plain code
Speed. A run is a chain of steps: the model reads the file, calls a tool, reads the result, calls the next one. On top of that, the server opens a fresh signed session to the order book for each order and closes it afterwards. That keeps the agent free of the session and reconnect bookkeeping that a long-running bot has to handle, but it is slow. On a 5-minute round a run eats a real share of the time left, and the edge right before the close goes to whoever sees the last print first. That isn't the agent.
Tokens. Every run reads the strategy, the market and two books again, and the model has no memory between runs except files. A 15-minute schedule is 96 runs a day, a 5-minute one is 288. A run that ends in "do nothing" costs about as much as one that trades, so the bill follows the number of rounds, not the number of trades.
Determinism. The same inputs don't guarantee the same action. The model can skip a step, misread the strike (the API gives it as an integer, the price times 1e8), or talk itself into a trade the rules don't allow. Strict numbers in the file help, and so does the reason column in trades.log, which lets you check each decision against the rule that should have produced it.
If any of those three is the bottleneck, write a bot. I wrote one up separately: a 5-minute trading bot in Python, with signing over the WebSocket API and a backtest on the free order book history. Both trade the same book, and neither pays trading fees.
Safety limits
The server tells the agent to confirm with you before trading. In a headless run nobody answers, so that line protects nothing. The limits that hold are the ones outside the model.
A separate wallet is the hard one. The agent can't lose more than that wallet holds, so keep in it only what you're ready to lose.
LUCYD_MAX_ORDER_USD is enforced in the server: a buy above it is refused whatever the model decided. It only applies to buys, and if you don't set it the default is 100 USDC, so set it.
LUCYD_READ_ONLY=true makes every tool that trades or sends a transaction refuse. That's the mode for a dry run: the agent goes through the whole strategy and writes to trades.log what it would have done. Compare a few days of that log with the actual results before you give it a working key. The hosted server is read-only too, so you can debug the reading half there without a key at all.
Keep the key out of the chat. It lives in the server's environment, and the model never needs to see it.
Judging it
Ask the agent to grade itself: "Show my positions and profit for today, and compare each trade in trades.log with the result of its round." Change one rule at a time and give it a few hundred rounds before deciding anything. Short rounds are close to a coin flip, and a few dozen results prove nothing.
Mainnet with real USDC is next for us, and the MCP server stays the same.
If you try this and something in the setup or the tools trips you up, ask in the comments.
Comments
Join the discussionThe per-order server limit is useful. Are the 20 USDC per-round limit and six-orders-per-day limit also enforced outside strategy.md? Two overlapping runs could each read the same remaining allowance before either order arrives. Reserving exposure for pending orders, then releasing only the unfilled part, would make a useful concurrency test on the testnet.