View as markdown

AI trading agent

Tickerbot is the stock market, in SQL. An AI trading agent is a model with the table as its tools: computed answers that fit in context.

An agent is only as good as what its tools return. Feed it computed state instead of raw data and the context goes to the decision: every ticker × every signal, queryable by the model itself.

1. Give the model the market

One config block installs the MCP server in Claude, ChatGPT, Cursor, or any MCP runtime. Scans, tickers, series, news, and webhooks become tools the model calls itself.

{
  "mcpServers": {
    "tickerbot": {
      "command": "npx",
      "args": ["-y", "@tickerbot/mcp-server"],
      "env": { "TICKERBOT_API_KEY": "tb_live_…" }
    }
  }
}

2. Ask in English

The agent compiles the question into tool calls on its own — one prompt, one scan, computed answers back.

You: which large caps went oversold this week while holding their 200-day?

Agent → tickerbot_scan({ q: "rsi_14 < 30 AND above_sma_200
                              AND market_cap > 1e10" })
      ← { count: 7, results: [ { ticker, price, rsi_14, … } × 7 ] }

Agent: Seven names match — here's the list, and what stands out…

3. Write the agent loop

For a headless agent: market state from Tickerbot, decisions from Claude, execution through your broker as a separate tool you control. The SDK's tool runner drives the loop — each cycle is one call.

import Anthropic from '@anthropic-ai/sdk'
import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod'
import { z } from 'zod'

const anthropic = new Anthropic()               // ANTHROPIC_API_KEY from env
const KEY = process.env.TICKERBOT_API_KEY

// Market state: the same scan as every other guide, as a tool
const scanMarket = betaZodTool({
  name: 'scan_market',
  description: 'Run a SQL WHERE clause over every ticker; returns matching rows.',
  inputSchema: z.object({
    q: z.string().describe('e.g. "rsi_14 < 30 AND market_cap > 1e10"'),
  }),
  run: async ({ q }) =>
    fetch('https://api.tickerbot.io/v2/scan', {
      method: 'POST',
      headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ q }),
    }).then((r) => r.text()),
})

// Execution stays behind YOUR broker tool — market state is read-only
const placeOrder = betaZodTool({
  name: 'place_order',
  description: 'Buy or sell through the broker.',
  inputSchema: z.object({
    side: z.enum(['buy', 'sell']), ticker: z.string(), qty: z.number(),
  }),
  run: async ({ side, ticker, qty }) => {
    console.log('ORDER', side, ticker, qty)     // …or your broker's API — every action logged
    return 'accepted'
  },
})

async function cycle() {
  const final = await anthropic.beta.messages.toolRunner({
    model: 'claude-opus-5',
    max_tokens: 16000,
    system: 'You manage a small momentum book. Max 5 positions. Explain before ordering.',
    messages: [{ role: 'user', content: 'Review the market and act if warranted.' }],
    tools: [scanMarket, placeOrder],
  })
  for (const block of final.content) if (block.type === 'text') console.log(block.text)
}
setInterval(cycle, 5 * 60 * 1000)

That separation — execution behind your own broker tool, every action logged — is what makes an agent auditable.

4. Let the market interrupt

Subscribe the conditions the agent cares about and wake it on the webhook instead of polling — app.post('/wake', () => cycle()) — and the market becomes the scheduler.

curl -X POST "https://api.tickerbot.io/v2/scan/subscribe" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e10",
        "target_url": "https://your-agent.example.com/wake" }'
Response
{
  "id": "wh_Clk9b8KHRLk",
  "q": "rsi_14 < 30 AND above_sma_200 AND market_cap > 1e10",
  "channel": "webhook",
  "target_url": "https://your-agent.example.com/wake",
  "signing_secret": "whsec_…",
  "status": "active",
  …
}

The calls behind it

All build guides