View as Markdown

Build an AI trading agent

Give your own AI agent a scoped agent key, connect it to the Tickwire MCP server or the agent REST routes, and handle held, rejected and unknown outcomes safely.

An AI agent is a program where a language model decides which tools to call. This guide shows how to give one access to a MetaTrader 4 or 5 account through Tickwire, so that it can look, and trade only if you allow it, with every order checked and recorded.

There are two ways in. Both use the same guarded path and the same agent key.

RouteBest forHow the key is sent
The Tickwire MCP serverAgent frameworks that speak MCP (Claude Agent SDK, OpenAI Agents SDK, Claude Code, Cursor)Authorization: Bearer twag_…
The agent REST routes, /v1/agent/…Your own tool-calling loop, or any languageX-Tickwire-Agent-Key: twag_…

AI apps such as Claude and ChatGPT don’t need a key at all: they add the MCP address and sign in with Tickwire. See AI agents for those steps.

1. Create an agent key

Create the key in the dashboard under AI agents > Keys, or with the API using your own API key:

curl -s -X POST https://api.tickwire.io/v1/agent-keys \
  -H "Authorization: Bearer $TICKWIRE_API_KEY" \
  -H "content-type: application/json" -H "X-Reason: research agent" \
  -d '{"name": "research-agent", "account_ids": ["<ACCOUNT_ID>"], "read_only": true}'
  • The answer’s key (twag_…) is shown once. Keep it in a secret store or an environment variable, never in source control.
  • account_ids limits the key to those accounts. The agent can’t see any others.
  • read_only: true is the safe default. A read-only key that tries to trade gets 403 read_only_key and nothing is sent. Change it later with PATCH /v1/agent-keys/{id} or in the dashboard.
  • Agent keys can’t call the direct terminal routes (they get 401 invalid_token), so an agent can’t go around the checks below. See Authentication.

Check what the key may do:

curl -s -H "X-Tickwire-Agent-Key: $AGENT_KEY" https://api.tickwire.io/v1/agent/me

2. What happens to every agent order

Every trade from an agent passes the same checks, in this order:

  1. Scope. The account must be one the key may use.
  2. Read-only. A read-only key stops here with read_only_key.
  3. Kill switches. One per agent key and one per account. Engaged means kill_switch_engaged, and nothing is sent.
  4. Your pre-trade hook, if you set one. Tickwire posts the order, who asked, why, and a fresh read of the account to your HTTPS endpoint, signed with HMAC-SHA256. Your hook answers allow, deny or hold. If it can’t be reached or its answer can’t be verified, the order is not sent. See the hook.
  5. Sent once. The order reaches the terminal once per idempotency key, and the answer says what actually happened.

Tickwire has no built-in size or loss limits and doesn’t judge whether a trade is a good idea. Rules like a maximum volume, a daily loss limit or trading hours belong in your hook.

3. Read the outcome correctly

Every trading answer has an outcome. Teach your agent what each one means, in its instructions and in your code:

outcomeMeaningWhat the agent should do
appliedThe terminal confirmed it (evidence says how)Carry on
no_changeNothing needed doing, for example it was already closedCarry on
rejectedNot executed. sent: false means it never left TickwireRead error; don’t loop on it
heldYour hook is holding it for approval. Nothing is sent yetWait. Check later; never resend
unknownIt may have executedCheck before doing anything else

After unknown, never retry with a new idempotency key. Ask what happened first: GET /v1/agent/requests/{idempotency_key} (the MCP tool get_trade_status), then the positions and orders. Repeating a call with the same key is always safe: you get the first answer back with replayed: true, never a second order. More in Idempotency.

hook_denied, kill_switch_engaged and read_only_key are the account owner’s decisions. The agent should report them, not try to work around them.

4. Connect an agent through MCP

The MCP server address is https://api.tickwire.io/mcp. Its tools include list_accounts, get_agent_permissions, get_account_health, get_account_info, get_positions, get_orders, get_quote, get_symbol_spec, get_candles, get_history, place_order, modify_position, close_position, modify_order, cancel_order, get_trade_status and get_audit_log. Every trading tool needs a reason, which goes into the audit log. The server adds an idempotency key if the agent doesn’t give one (client_request_id).

Both examples below give the model only the tools it needs. That is a good habit: an agent that only reads should not even see place_order.

Claude Agent SDK (Python)

pip install claude-agent-sdk
import asyncio, os
from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    mcp_servers={
        "tickwire": {
            "type": "http",
            "url": "https://api.tickwire.io/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['AGENT_KEY']}"},
        }
    },
    allowed_tools=[
        "mcp__tickwire__list_accounts",
        "mcp__tickwire__get_account_health",
        "mcp__tickwire__get_positions",
        "mcp__tickwire__get_quote",
    ],
    system_prompt=(
        "You read MetaTrader accounts through Tickwire. Check account health first. "
        "Times are UTC unless a field says broker time."
    ),
)

async def main():
    async for message in query(prompt="Show the health and open positions of my first account.",
                               options=options):
        print(message)

asyncio.run(main())

OpenAI Agents SDK (Python)

pip install openai-agents
import asyncio, os
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp, create_static_tool_filter

async def main():
    async with MCPServerStreamableHttp(
        name="tickwire",
        params={
            "url": "https://api.tickwire.io/mcp",
            "headers": {"Authorization": f"Bearer {os.environ['AGENT_KEY']}"},
        },
        tool_filter=create_static_tool_filter(
            allowed_tool_names=["list_accounts", "get_account_health", "get_positions", "get_quote"]
        ),
    ) as tickwire:
        agent = Agent(
            name="Account reader",
            instructions="You read MetaTrader accounts through Tickwire. Check account health first.",
            mcp_servers=[tickwire],
        )
        result = await Runner.run(agent, "Show the health and open positions of my first account.")
        print(result.final_output)

asyncio.run(main())

Option names in agent frameworks change between versions. If one of these fails, check the framework’s own documentation for remote MCP servers with custom headers.

5. Or call the agent REST routes yourself

If you run your own tool-calling loop, map each tool to a plain HTTP call. The reads:

RouteWhat it returns
GET /v1/agent/accountsAccounts in the key’s scope
GET /v1/agent/accounts/{id}/healthconnected, disconnected or unknown, with evidence
GET /v1/agent/accounts/{id}/account-informationBalance, equity, margin, currency
GET /v1/agent/accounts/{id}/positions and …/ordersOpen positions and pending orders
GET /v1/agent/accounts/{id}/symbols/{symbol}/current-priceBid and ask
GET /v1/agent/accounts/{id}/history-dealsDeals, 24 hours by default, at most 31 days

Trading is one route, POST /v1/agent/accounts/{id}/trade, and it needs an Idempotency-Key and an X-Reason header. Here is a trade function that handles every outcome:

import os, uuid, requests

API = "https://api.tickwire.io"
HEADERS = {"X-Tickwire-Agent-Key": os.environ["AGENT_KEY"]}

def trade(account_id: str, order: dict, reason: str, key: str | None = None) -> dict:
    """Send one order. Reuse `key` to repeat the same order safely."""
    key = key or str(uuid.uuid4())
    try:
        r = requests.post(f"{API}/v1/agent/accounts/{account_id}/trade", json=order, timeout=30,
                          headers={**HEADERS, "Idempotency-Key": key, "X-Reason": reason})
    except requests.RequestException:
        # No answer: the order may have gone. Ask, with the same key.
        return status(key)
    body = r.json()
    body["idempotency_key"] = key
    return body

def status(key: str) -> dict:
    r = requests.get(f"{API}/v1/agent/requests/{key}", headers=HEADERS, timeout=30)
    return r.json()

result = trade("<ACCOUNT_ID>",
               {"actionType": "ORDER_TYPE_BUY", "symbol": "EURUSD", "volume": 0.01},
               reason="example order from the guide")

match result.get("outcome"):
    case "applied" | "no_change":
        print("done", result.get("evidence"))
    case "held":
        print("waiting for approval, hold", result.get("hold_id"))
    case "unknown":
        print("may have executed: check", status(result["idempotency_key"]))
    case _:
        print("not executed:", result.get("error"), result.get("message"))

The order uses absolute prices only, in lots. actionType covers market, limit, stop and stop-limit orders, POSITION_MODIFY, POSITION_PARTIAL, POSITION_CLOSE_ID, ORDER_MODIFY and ORDER_CANCEL. MT4 has no stop-limit orders, and those answer not_supported_on_mt4. See Platforms.

A 409 idempotency_in_flight answer means an earlier request with the same key is still running. Wait for Retry-After and repeat with the same key.

6. Stop an agent, and see what it did

Engage a kill switch to stop an agent at once. Nothing else it sends will reach the broker:

curl -s -X PUT https://api.tickwire.io/v1/agent-keys/<KEY_ID>/kill-switch \
  -H "Authorization: Bearer $TICKWIRE_API_KEY" \
  -H "content-type: application/json" -d '{"engaged": true, "reason": "stop the agent"}'

The per-account switch is PUT /v1/agent-kill-switches/accounts/{account_id}, and both are in the dashboard under AI agents. Everything an agent did, with its reasons and outcomes, is in GET /v1/agent-audit (yours, as the owner) and GET /v1/agent/audit (the agent’s own log).

Checklist

  • One agent key per agent, scoped to the accounts it needs, read-only until you’re sure.
  • Only the tools it needs.
  • Size, loss and hours rules in your pre-trade hook, not in the prompt.
  • Every trade with a reason and an idempotency key.
  • unknown means check, never resend with a new key.
  • A kill switch you know how to reach.
  • Practise against the sandbox first.

Further reading: Which AI apps can place trades on MT4 and MT5?, Risk controls for AI trading agents and Safe prompts for an AI trading assistant.