Skip to content
Trading Systems

Agents Now Trade Brokerage Accounts And Prediction-Market Signals: Building An Agent Order Gateway With Enforced Limits, And Plotting Every Agent Decision On The Chart

September made AI trading agents a retail product. tastytrade shipped an official open-source MCP server that places multi-leg orders from Claude, Cursor and VS Code. StockBrokers.com tested seven brokers with first-party MCP servers and found controls ranging from Interactive Brokers, where the agent never reaches the order book, to brokers with no enforced caps at all - and a tastytrade confirmation token that an unattended agent can clear itself. On 24 September Public launched AI agents that trade stocks and options when Kalshi prediction-market probabilities cross a threshold. This is the trading-systems build: an agent order gateway that enforces limits the model cannot change, binds approval to the exact order with a human step the agent cannot perform, and draws every proposal, rejection and fill onto a Lightweight Charts price chart so traders can see what the agent did and why.

AlchmAI Engineering16 min read

7

Brokers with first-party MCP servers tested by StockBrokers.com in September, from Interactive Brokers to Public

60 sec

Life of tastytrade's dry-run confirmation token - which reviewers found an unattended agent can mint and clear itself

24 Sept

Public launched AI agents that trade when Kalshi prediction-market probabilities cross user-set thresholds

0

Times the agent should be able to change its own limits, approve its own order, or hide a decision from the chart

The pieces of an AI trading stack arrived together this month. tastytrade's official MCP server gives Claude Desktop, Claude Code, Cursor, VS Code, Antigravity CLI and Perplexity real-time quotes, option chains with Greeks, positions and order entry across equities, options, futures, crypto and forex, running locally with credentials on the user's machine. StockBrokers.com's review of seven first-party broker MCP servers put Interactive Brokers top because the agent never reaches the order book, praised Webull's hard caps, whitelists and read-only modes, and flagged that tastytrade's otherwise thorough dry-run token can be generated and cleared by an unattended agent. And on 24 September Public launched agents that watch Kalshi probabilities - buy when an FDA approval probability reaches 75%, buy puts if an earnings-miss probability passes 60% - and execute on the member's behalf.

For a firm building trading products or internal desk tooling, the lesson from the review is the design principle: protections depend on what the platform enforces, not on how the AI behaves. So we put a gateway between the agent and the market, and we make every decision visible on the chart the trader is already looking at.

The Architecture

  1. 01The agent holds a 'propose' credential only. Its MCP tool is propose_order, which returns a proposal ID - never an execution.
  2. 02The gateway runs deterministic pre-trade checks against limits stored outside the agent's reach: instrument allow-list, max order notional, max daily notional, max position, price collar against the live mid, and trading-hours rules.
  3. 03Orders inside a small auto-approve band go straight to the broker; everything else waits for a human approval bound to a hash of the exact order.
  4. 04Every proposal, rejection, approval and fill is written to an append-only log and streamed to the chart as a marker.
typescriptgateway/pretrade.ts
import { createHash } from "node:crypto";

export interface Proposal {
  id: string; agentId: string; symbol: string;
  side: "buy" | "sell"; qty: number; limitPrice: number;
  rationale: string; signal?: { source: string; value: number };  // e.g. kalshi prob
}

export interface Limits {
  allow: Set<string>; maxOrderNotional: number; maxDailyNotional: number;
  maxPosition: number; collarPct: number; autoApproveNotional: number;
}

export type Verdict =
  | { status: "rejected"; reason: string }
  | { status: "auto_approved" }
  | { status: "needs_human"; orderHash: string };

export function orderHash(p: Proposal): string {
  // Bind approval to the exact economic content of the order.
  const canonical = [p.symbol, p.side, p.qty, p.limitPrice.toFixed(4)].join("|");
  return createHash("sha256").update(canonical).digest("hex");
}

export function check(p: Proposal, l: Limits, mid: number, usedToday: number, pos: number): Verdict {
  const notional = p.qty * p.limitPrice;
  if (!l.allow.has(p.symbol)) return { status: "rejected", reason: "symbol not allowed" };
  if (notional > l.maxOrderNotional) return { status: "rejected", reason: "order notional cap" };
  if (usedToday + notional > l.maxDailyNotional) return { status: "rejected", reason: "daily cap" };
  const newPos = pos + (p.side === "buy" ? p.qty : -p.qty);
  if (Math.abs(newPos) > l.maxPosition) return { status: "rejected", reason: "position limit" };
  if (Math.abs(p.limitPrice - mid) / mid > l.collarPct) return { status: "rejected", reason: "price collar" };
  if (notional <= l.autoApproveNotional) return { status: "auto_approved" };
  return { status: "needs_human", orderHash: orderHash(p) };
}
typescriptgateway/approve.ts
// Approval arrives on a separate authenticated channel (desk UI with SSO +
// step-up MFA). The agent's token has no 'orders:approve' scope, so it
// cannot call this endpoint even if it learns the URL.
export async function approve(req: { proposalId: string; orderHash: string; approver: User }) {
  requireScope(req.approver, "orders:approve");
  const p = await proposals.get(req.proposalId);
  if (!p || p.status !== "needs_human") throw new Error("not awaiting approval");
  if (orderHash(p) !== req.orderHash) throw new Error("order changed since review");
  if (Date.now() - p.createdAt > 60_000) throw new Error("approval window expired");
  if (req.approver.id === p.agentOwnerId && p.notional > FOUR_EYES_THRESHOLD) {
    throw new Error("four-eyes: a second approver is required");
  }
  await audit.append({ event: "approved", proposalId: p.id, by: req.approver.id });
  return broker.submit(p);   // the ONLY path to the broker
}

Prediction-Market Signals Need The Same Discipline

Public's launch makes a new signal type mainstream: an event probability driving a securities trade. Engineering-wise, treat it like any external signal. Record the probability, its timestamp and its source with the proposal, so the audit trail shows why the agent acted. Debounce it - a probability crossing 75% for one print on a thin market is not the same as holding there for ten minutes. And collar it: the gateway's price collar protects the securities leg even when the signal is right.

pythonsignals/threshold.py
from collections import deque
from time import time

class DebouncedThreshold:
    """Fire only when the probability holds above the level for hold_s seconds
    with enough traded volume to mean something."""
    def __init__(self, level: float, hold_s: int = 600, min_volume: int = 5_000):
        self.level, self.hold_s, self.min_volume = level, hold_s, min_volume
        self.window = deque()

    def update(self, prob: float, volume: int, ts: float = None) -> bool:
        ts = ts or time()
        self.window.append((ts, prob, volume))
        while self.window and ts - self.window[0][0] > self.hold_s:
            self.window.popleft()
        span = ts - self.window[0][0]
        held = all(p >= self.level for _, p, _ in self.window)
        vol = sum(v for _, _, v in self.window)
        return span >= self.hold_s * 0.95 and held and vol >= self.min_volume

Putting Agent Decisions On The Chart

Traders trust what they can see. Every agent decision should appear on the price chart at the bar where it happened: proposals as neutral arrows, rejections in grey with the reason on hover, approvals and fills in colour, and the signal threshold as a price line. Lightweight Charts v5 does this with series markers and price lines, and it keeps the audit trail and the trader's view in one place.

typescriptchart/agentMarkers.ts
import {
  createChart, CandlestickSeries, createSeriesMarkers,
  LineStyle, SeriesMarker, UTCTimestamp,
} from "lightweight-charts";

type AgentEvent = {
  time: UTCTimestamp; kind: "proposed" | "rejected" | "approved" | "filled";
  side: "buy" | "sell"; label: string;
};

const STYLE = {
  proposed: { color: "#94a3b8", shape: "circle" },
  rejected: { color: "#64748b", shape: "square" },
  approved: { color: "#a855f7", shape: "arrowUp" },
  filled:   { color: "#22d3ee", shape: "arrowUp" },
} as const;

export function mountAgentChart(el: HTMLElement, bars: any[], events: AgentEvent[]) {
  const chart = createChart(el, { layout: { background: { color: "#0b0b12" }, textColor: "#cbd5e1" } });
  const candles = chart.addSeries(CandlestickSeries);
  candles.setData(bars);

  const toMarker = (e: AgentEvent): SeriesMarker<UTCTimestamp> => ({
    time: e.time,
    position: e.side === "buy" ? "belowBar" : "aboveBar",
    color: STYLE[e.kind].color,
    shape: e.kind === "filled" || e.kind === "approved"
      ? (e.side === "buy" ? "arrowUp" : "arrowDown")
      : STYLE[e.kind].shape,
    text: e.label,          // e.g. "AGENT BUY 200 @ 41.20 (prob 0.76)"
  });

  // Markers must be sorted by time.
  const markers = createSeriesMarkers(candles, events.map(toMarker).sort((a, b) => a.time - b.time));

  return {
    push(e: AgentEvent) { markers.setMarkers([...markers.markers(), toMarker(e)]); },
    collar(mid: number, pct: number) {
      for (const p of [mid * (1 + pct), mid * (1 - pct)]) {
        candles.createPriceLine({ price: p, color: "#f59e0b", lineWidth: 1,
          lineStyle: LineStyle.Dashed, axisLabelVisible: true, title: "agent collar" });
      }
    },
  };
}

“If a trader cannot see on the chart what the agent proposed, what the gateway refused and what actually filled, the agent is not a tool on the desk. It is a stranger with a login.”


Checklist Before An Agent Touches A Live Account

  • Paper first, with the same gateway and limits as live, for long enough to see the agent's behaviour across quiet and volatile sessions.
  • Limits stored and enforced in the gateway or OMS, versioned, and changeable only by a human with the right role.
  • Approval bound to the order hash, time-boxed, and impossible for the agent's identity to perform; four-eyes above a notional threshold.
  • Research and trading sessions separated: an agent that reads news or social posts should not hold the credential that proposes orders in the same context.
  • Kill switch that halts proposals, cancels resting agent orders and revokes agent tokens, tested monthly.
  • Every decision on the chart and in the append-only log, so a trader and a compliance reviewer see the same story.

The Bottom Line

With tastytrade's MCP server, six more brokers offering first-party equivalents, and Public's prediction-market agents live since 24 September, AI agents placing trades is a product category - and the StockBrokers.com review shows the controls behind it are uneven, down to confirmation steps an unattended agent can clear itself. The architecture that holds up is a gateway: propose-only agent credentials, deterministic pre-trade checks against limits the model cannot touch, approval bound to the exact order hash on a channel the agent cannot reach, debounced and recorded signals, and every decision drawn on the trader's chart with Lightweight Charts markers and price lines. That is the trading AI architecture and charting work we build in London, and it is what turns an agent that can trade into one a desk can trust.

References & Further Reading

AI Automation Trading codeTrading AI architectureTrading Workflow architecturetrading automationtrading charts AIMCPLightweight Charts
Share Email
AI

AlchmAI Engineering

Engineering, London

Written by the AlchmAI engineering team in Mayfair, London. We build trading platforms, real-time charts, market data pipelines and AI features for brokers, prop firms and fintech teams. The Playbook is where we explain how we approach these systems, with code you can run and sources you can check.

Code in this guide is illustrative and supplied without warranty. Review and test it before production use. Nothing here is investment advice. Important information