Built a Trading Bot With Claude Code or ChatGPT? A Pre-Live Checklist
Yes: Claude Code, ChatGPT and Codex can write a working trading bot, from strategy logic and backtests to the Interactive Brokers or MetaTrader 5 connection. What none of them can tell you is whether that code is safe to run with real money. Anthropic’s own guide notes that Claude “stops when the work looks done,” and in trading code the expensive mistakes, such as look-ahead bias, a duplicate order or a position the bot does not know about, look done too. This checklist turns the step from “it runs” to “it trades live” into 24 verifiable checks, with two tested Python examples, as of November 2026.
Can Claude Code or ChatGPT build a trading bot?
Technically, yes. Anthropic describes Claude Code as an agentic coding tool that reads your codebase, edits files and runs commands. OpenAI’s Codex works similarly in the Codex CLI, the IDE extension or Codex Cloud, and ChatGPT in the browser writes code that you paste and run yourself.
The weak point is verification. Claude Code’s best-practices guide separates guidelines the model may decide not to apply, such as prompts and CLAUDE.md, from hooks, deny rules, managed settings and tests, which are enforced every time. Every rule that must hold with real money therefore needs a check that returns pass or fail, plus a person who reads the order and risk code. To have that review done for you, see trading bot development and code review for Interactive Brokers and MetaTrader 5.
Why “it runs” is not the bar for AI-written trading code
Generated trading code is usually syntactically clean. The typical errors sit in the domain logic, and many make a backtest look better, so they go unnoticed without a dedicated check:
- Data: a signal computed on the close and filled at that same close, centered rolling windows, scaling over the whole data set, bars labeled with their open time but used as if closed.
- Costs: returns from price differences without commissions, spread and slippage.
- Execution: an order sent twice after a timeout, partial fills treated as complete, prices off the minimum tick (IBKR error 110).
- State: after a restart the bot assumes it is flat while the broker still holds a position.
- Overfitting: an assistant can test hundreds of parameter sets in minutes. Bailey, Borwein, López de Prado and Zhu (2014) showed that the more configurations are tried, the higher the probability that the best backtest is overfit.
IBKR’s documentation adds a point for generated code: the TWS API contains no logic beyond the integrity of the exchanged messages; validation happens in TWS and on IBKR’s servers, so your code must bring its own limits.
The pre-live checklist: 24 checks
Each pass criterion can be verified without the assistant’s summary. Check the code and setup that will actually go live.
| # | Check (as of November 2026) | Pass criterion |
|---|---|---|
| 1 | Secrets: no live credentials near the assistant | No live key, password or token where the assistant works; a secret scan (OWASP names a pre-commit hook as one option) finds none in the repository or its git history. |
| 2 | No order tools for the assistant | No MCP server or tool with order functions connected while coding (MetaTrader 5: terminal MCP server off, AI Assistant set to not trade); IBKR sessions the tools can reach use Read-Only API. |
| 3 | Rules enforced in configuration | Deny rules or hooks protect secrets and test files; sandbox on; full-access modes only in an isolated container without credentials. |
| 4 | Human review of order, sizing and risk code | A person can explain every order path (entry, exit, stop, cancel, flatten) without the assistant. |
| 5 | Dependencies pinned | Pinned versions; every package the assistant added is maintained and used. |
| 6 | Look-ahead bias | Prefix test passes for every signal and filter on a long history; fills no earlier than the next bar or tick. |
| 7 | Bar timestamps | A test with constructed bars shows whether timestamps mark the open or the close. |
| 8 | Costs in every simulated trade | A one-trade test returns the exact expected net result; a backtest without a cost model fails. |
| 9 | Overfitting | The number of parameter sets tried is logged; final parameters are checked on data not used to choose them. |
| 10 | Time zones and daylight saving time | Times stored in UTC; session filters pass tests around clock changes in the exchange and server time zones. |
| 11 | Units: size, multiplier, tick size | Risk converts to quantity correctly per instrument type; prices round to the tick (IBKR minTick and market rules, MQL5 SYMBOL_TRADE_TICK_SIZE), volumes to SYMBOL_VOLUME_STEP. |
| 12 | One signal, one order | IBKR order IDs strictly increase; replaying a signal after a timeout sends no second order. |
| 13 | Restart | After a hard stop with an open position and a working order, the restarted bot loads positions and open orders before sending anything. |
| 14 | Position reconciliation | Bot and broker positions match at startup and on a schedule; a mismatch blocks new orders and raises an alert. |
| 15 | Partial fills, rejections, cancellations | Tests feed each case to the order handler; internal state matches the simulated broker afterward. |
| 16 | Disconnects | Simulated IBKR code 1100 pauses new orders; after 1101 or 1102, orders resume only once positions and open orders are rechecked; after 1101, market data is requested again. |
| 17 | Request rate | A throttle stays below the documented limit (IBKR: market data lines divided by two per second, so 50 with the default 100 lines). |
| 18 | Pre-trade limits | Orders above a maximum quantity or value, or outside a price band, are blocked in code, especially if TWS order precautions are bypassed. |
| 19 | Daily loss limit | Reaching the limit stops new orders; the stop survives a restart. |
| 20 | Kill switch | One action blocks new orders and cancels working ones, works without the strategy process, was triggered once in paper, and only a person clears it. |
| 21 | Alerts and heartbeat | A test alert reaches your phone; a missing heartbeat raises an alert. |
| 22 | Logging | Each decision is logged in UTC with inputs, order ID, broker response and fills, without secrets, and stored off the bot’s machine. |
| 23 | Paper phase | Exit criteria written down before the start and met; sizing based on live capital, not the paper balance. |
| 24 | Account guard | The bot checks account ID and type at startup and stops on a mismatch; going live is a deliberate change by a person. |
How to test for look-ahead bias with a prefix check
Look-ahead bias means a signal uses information that did not exist at the time. A prefix test finds it without reading the code: compute signals on the full history and on truncated copies. A causal signal for bar 300 cannot change because later bars are missing; if it does, the code peeks ahead. The worked example compares a correct moving-average signal with a centered window, a typical leak:
"""Prefix test for look-ahead bias. Python 3 standard library only."""
import unittest
CLOSES = [100, 101, 99, 102, 106, 103, 105, 101, 100, 101, 108, 107]
def causal_signal(closes, n=3):
"""Signal at bar i uses closes[0..i] only: 1 = long, 0 = flat."""
out = []
for i in range(len(closes)):
if i + 1 < n:
out.append(None)
else:
out.append(1 if closes[i] > sum(closes[i + 1 - n:i + 1]) / n else 0)
return out
def centered_signal(closes, n=3):
"""Typical bug: a centered window reads one bar into the future."""
half, out = n // 2, []
for i in range(len(closes)):
window = closes[max(0, i - half):i + half + 1]
out.append(1 if closes[i] > sum(window) / len(window) else 0)
return out
def first_difference(signal_fn, closes, min_bars=4):
"""Recompute on every truncated history; return (cut, bar) of the first change."""
full = signal_fn(closes)
for cut in range(min_bars, len(closes)):
for bar, (a, b) in enumerate(zip(full[:cut], signal_fn(closes[:cut]))):
if a != b:
return cut, bar
return None
class LookAheadTest(unittest.TestCase):
def test_causal_signal_passes(self):
self.assertIsNone(first_difference(causal_signal, CLOSES))
def test_centered_window_is_caught(self):
self.assertIsNotNone(first_difference(centered_signal, CLOSES))
if __name__ == "__main__":
print("centered window, first difference (cut, bar):", first_difference(centered_signal, CLOSES))
unittest.main()The example runs with Python 3.9 and 3.12, standard library only; both tests pass, and first_difference returns (4, 3) for the centered window: bar 3 gets a different signal with four bars than with the full history. The test only catches leaks that change a signal in the data you feed it, so run it on a long real history. Have the assistant write it before the strategy code, and protect test files with a deny rule such as Edit(./tests/**) so a red test cannot be “fixed” by editing it; with the sandbox on, the rule also binds scripts the assistant runs.
How to reconcile positions and stop on a mismatch
Reconciliation compares what the bot believes it holds with what the broker reports. With the TWS API, reqPositions() returns the positions of all accessible accounts, ends with positionEnd() and then streams changes; in MQL5, PositionsTotal() and the position functions serve the same purpose. The worked example wraps every order in a persistent kill switch, a size limit and daily order and loss limits; a position mismatch trips the switch, which blocks all orders until a person clears it:
"""Reconciliation, pre-trade limits and a persistent kill switch.
Python 3 standard library only. StubBroker replaces the broker API:
no connection, no orders leave this process."""
import json
import os
import tempfile
# Production: a fixed path that survives restarts; only a person deletes it.
KILL_FILE = os.path.join(tempfile.mkdtemp(), "kill_switch.json")
class StubBroker:
def __init__(self, positions):
self._positions = dict(positions) # e.g. collected via reqPositions() up to positionEnd()
self.sent = []
def positions(self):
return dict(self._positions)
def place_order(self, symbol, qty):
self.sent.append((symbol, qty)) # recorded only
def kill(reason):
with open(KILL_FILE, "w") as f:
json.dump({"reason": reason}, f)
def kill_reason():
if not os.path.exists(KILL_FILE):
return None
with open(KILL_FILE) as f:
return json.load(f)["reason"]
def reconcile(expected, broker):
actual = broker.positions()
diff = {s: (expected.get(s, 0), actual.get(s, 0))
for s in sorted(set(expected) | set(actual))
if expected.get(s, 0) != actual.get(s, 0)}
if diff:
kill(f"position mismatch (bot, broker): {diff}")
return diff
def guarded_order(broker, symbol, qty, limits, today):
if kill_reason():
return "blocked: " + kill_reason()
if abs(qty) > limits["max_qty"]:
return "blocked: size above max_qty"
if today["orders"] >= limits["max_orders_per_day"]:
kill("daily order count reached")
return "blocked: order count"
if today["pnl"] <= -limits["max_daily_loss"]:
kill("daily loss limit reached")
return "blocked: daily loss"
broker.place_order(symbol, qty)
today["orders"] += 1
return "sent to stub"
if __name__ == "__main__":
limits = {"max_qty": 200, "max_orders_per_day": 20, "max_daily_loss": 500.0}
today = {"orders": 0, "pnl": 0.0}
broker = StubBroker({"AAPL": 100})
print(guarded_order(broker, "AAPL", 10, limits, today)) # sent to stub
print(guarded_order(broker, "AAPL", 900, limits, today)) # blocked: size
broker = StubBroker({"AAPL": 110, "MSFT": 50}) # restart: unknown MSFT position
print(reconcile({"AAPL": 110}, broker)) # {'MSFT': (0, 50)}
print(guarded_order(broker, "AAPL", 10, limits, today)) # blocked: position mismatch
print("orders after the restart:", broker.sent) # []It runs with Python 3.9 and 3.12 against a stubbed broker: no broker connection, no orders. In a real bot, the kill-switch file sits at a fixed path outside the code directory, daily P&L comes from the broker, and the checks run in a process separate from the strategy, so a strategy bug cannot skip them.
How to keep the AI assistant away from the live account
The OWASP Top 10 for LLM applications lists excessive agency as a risk and recommends human approval for high-impact actions; a live order is one. The rule behind this checklist: the assistant writes and tests code against stubs and recorded data, gets no broker credentials and places no orders, not even in paper; you start the bot, first in paper, later live. Current tool defaults make this a deliberate setup step (as of November 3, 2026):
- Claude Code: from v2.1.283, auto mode is the starting mode in the terminal and VS Code, and its classifier by default allows “Reading
.envand sending credentials to their matching API.” Read deny rules such asRead(./.env)do not cover a Python script that opens the file itself unless the sandbox is on; it is off by default and, once on, by default still lets commands read most of the machine and inherit secrets from environment variables. - Codex: in version-controlled folders the recommended Auto preset is
workspace-writewithon-requestapprovals and no network access;danger-full-accessremoves these limits. - MetaTrader 5: the built-in AI Assistant has a Trading option (execute trades, request explicit confirmation, or not trade at all), and the terminal MCP server, which clients such as Claude Code or Codex can use, has tools for placing and managing orders. “Enable internal server” switches only that terminal server, not the separate metaeditor server for MQL5 coding. For development: a demo login, the terminal server off and “not trade at all.”
- Interactive Brokers: IBKR’s TWS API setup page lists disabling “Read-Only API” among the key connection settings; a third-party guide republished on IBKR Campus suggests enabling it so orders are not executed accidentally while testing. Keep it on for every TWS or IB Gateway session your development tools can reach.
More on assistants and broker connections: IBKR MCP with Claude or ChatGPT: what works and what does not.
What the paper phase proves, and what it does not
Paper trading tests the plumbing under real market data: connection, order handling, reconciliation, logging and alerts. It does not test real fills. IBKR’s paper trading guide lists differences, among them:
- fills are simulated from the top of the book, with no deep book access;
- stops and other complex order types are always simulated;
- the remainder of an exchange-directed market order that partially executes is rejected;
- every paper account starts with USD 1,000,000 in paper equity, so a percent-of-equity sizer can trade far larger than it will live.
For MetaTrader 5, run the Strategy Tester with “Every tick based on real ticks,” which uses tick data accumulated by brokers, then a demo account. Before the paper phase starts, write down exit criteria: a number of sessions fixed in advance, zero unexplained position differences, one deliberate kill-switch trigger, one restart with an open position, one disconnect, and paper fills compared with the backtest for the same days. An Expert Advisor can also refuse an unexpected account; this OnInit() guard is illustrative and was not compiled, so check it against the MQL5 reference before use:
// Illustrative, not compiled: check against the MQL5 reference before use.
input bool AllowNonDemoAccount = false; // set to true only after the demo phase
input long ExpectedLogin = 0; // 0 = no check; otherwise the only allowed account
int OnInit()
{
if(MQLInfoInteger(MQL_TESTER))
return(INIT_SUCCEEDED);
ENUM_ACCOUNT_TRADE_MODE mode = (ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE);
if(mode != ACCOUNT_TRADE_MODE_DEMO && !AllowNonDemoAccount)
{
Print("Account is not a demo account and AllowNonDemoAccount is false. EA stops.");
return(INIT_FAILED);
}
if(ExpectedLogin != 0 && AccountInfoInteger(ACCOUNT_LOGIN) != ExpectedLogin)
{
Print("Unexpected account ", AccountInfoInteger(ACCOUNT_LOGIN), ". EA stops.");
return(INIT_FAILED);
}
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)
|| !AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
Print("Automated trading is disabled in the terminal, the EA settings or on the server.");
return(INIT_SUCCEEDED);
}MetaTrader 5 can also disable automated trading when switching accounts, an option its help describes as useful when moving from a demo to a real account.
Limits of the checklist, and what to do next
Passing all 24 checks lowers the risk of technical failure. It says nothing about whether the strategy works, and automated trading can lose money; with leverage, depending on the product, more than the deposit. For investment firms engaged in algorithmic trading, RTS 6 requires a kill functionality (Article 12) and pre-trade controls such as price collars, maximum order values and volumes and message limits (Article 15); for a private bot they are a useful benchmark.
A practical order of work:
- Write the prefix, cost and reconciliation tests first, and protect them from edits by the assistant.
- Lock the setup: no live credentials, deny rules, sandbox, no order tools.
- Read the order and risk code yourself or have it reviewed.
- Run the paper phase against written exit criteria, then go live with reduced size.
Whether to build it yourself with AI or hand it over is weighed in what a trading bot costs: hire a developer or build it with AI; an independent review of an existing bot or Expert Advisor also covers code written with ChatGPT or Claude.
Frequently asked questions
Can Claude Code write a trading bot?
Yes. Claude Code can write strategy logic, backtests and broker connections for Interactive Brokers or MetaTrader 5, and run the tests itself. Whether the bot is correct is decided by tests that return pass or fail and by a person who reads the order and risk code. The assistant should not place orders or hold live credentials; you start the bot, first in paper, later live.
Can I build a trading bot with ChatGPT?
ChatGPT can write the code, and OpenAI’s Codex can also edit files and run commands in your project. The result needs the same checks as any other bot: look-ahead and cost tests, position reconciliation, a kill switch and a paper phase with written exit criteria. Whether a strategy makes money is a separate question that no code generator answers.
Should I give Claude or ChatGPT access to my brokerage account?
No. OWASP recommends human approval for high-impact actions, and a live order is one. Keep broker credentials off the machine the assistant works on, connect no MCP server with order tools while coding, set the MetaTrader 5 AI Assistant to not trade, and keep IBKR sessions that development tools can reach in Read-Only API mode. The bot, started by you, talks to the paper account first.
How do I test a trading bot for look-ahead bias?
Use a prefix test. Compute all signals on the full history, then on truncated copies of the same history, and compare the values before each cut. A causal signal does not change when later bars are missing; any difference means the code uses future data. Run it on a long real history, because a leak only shows up where it changes a signal.
How long should a trading bot run in paper trading before going live?
No fixed duration fits every strategy. Set exit criteria before you start: a fixed number of sessions, zero unexplained position differences, one deliberate kill-switch trigger, one restart with an open position and one disconnect. Keep in mind that paper fills are simulated; IBKR states that they come from the top of the book, so live execution can differ, especially for larger orders.
Is Claude Code’s auto mode safe for a trading bot repository?
Auto mode lets a classifier review actions instead of you. According to Anthropic it blocks risky steps such as sending sensitive data to external endpoints, but by default it allows reading .env files and sending credentials to their matching API. Keep live credentials out of the repository and machine, add deny rules for secrets and test files, enable the sandbox and review order and risk code yourself.
Sources and status
- Claude Code overview — Anthropic (retrieved November 3, 2026)
- Best practices for Claude Code — Anthropic (retrieved November 3, 2026)
- Choose a permission mode — Anthropic (retrieved November 3, 2026)
- Configure permissions — Anthropic (retrieved November 3, 2026)
- Configure the sandboxed Bash tool — Anthropic (retrieved November 3, 2026)
- Agent approvals & security (Codex) — OpenAI (retrieved November 3, 2026)
- Overview: ChatGPT and Codex surfaces — OpenAI (retrieved November 3, 2026)
- Configuration: MCP and AI (AI Assistant Trading option, Enable internal server) — MetaQuotes (MetaTrader 5 Help) (retrieved November 3, 2026)
- Tools and operations supported by MCP servers — MetaQuotes (MetaTrader 5 Help) (retrieved November 3, 2026)
- Platform settings: Expert Advisors — MetaQuotes (MetaTrader 5 Help) (retrieved November 3, 2026)
- Strategy testing: tick generation modes — MetaQuotes (MetaTrader 5 Help) (retrieved November 3, 2026)
- Trade permission — MetaQuotes (MQL5 Reference) (retrieved November 3, 2026)
- Account properties (ENUM_ACCOUNT_TRADE_MODE) — MetaQuotes (MQL5 Reference) (retrieved November 3, 2026)
- OnInit — MetaQuotes (MQL5 Reference) (retrieved November 3, 2026)
- Symbol properties — MetaQuotes (MQL5 Reference) (retrieved November 3, 2026)
- PositionsTotal — MetaQuotes (MQL5 Reference) (retrieved November 3, 2026)
- TWS configuration for API use — Interactive Brokers (retrieved November 3, 2026)
- Order precautions — Interactive Brokers (retrieved November 3, 2026)
- TWS API limitations (incl. paper trading) — Interactive Brokers (retrieved November 3, 2026)
- OrderId — Interactive Brokers (retrieved November 3, 2026)
- Positions (reqPositions) — Interactive Brokers (retrieved November 3, 2026)
- Market price increment (minTick, market rules) — Interactive Brokers (retrieved November 3, 2026)
- Error codes (110, 1100, 1101, 1102) — Interactive Brokers (retrieved November 3, 2026)
- Pacing limitations — Interactive Brokers (retrieved November 3, 2026)
- About paper trading accounts (paper trading limitations) — Interactive Brokers (Client Portal guide) (retrieved November 3, 2026)
- Interactive Brokers Python API (Native): a step-by-step guide (third-party article, Read-Only API tip) — IBKR Campus (republished from AlgoTrading101) (retrieved November 3, 2026)
- LLM06:2025 Excessive Agency — OWASP Gen AI Security Project (retrieved November 3, 2026)
- Secrets Management Cheat Sheet — OWASP Cheat Sheet Series (retrieved November 3, 2026)
- Pseudo-Mathematics and Financial Charlatanism: The Effects of Backtest Overfitting on Out-of-Sample Performance (Notices of the AMS, May 2014) — Bailey, Borwein, López de Prado, Zhu (Western Michigan University ScholarWorks) (retrieved November 3, 2026)
- Commission Delegated Regulation (EU) 2017/589 (RTS 6), Article 12: kill functionality (text as adopted) — legislation.gov.uk (The National Archives) (retrieved November 3, 2026)
- Commission Delegated Regulation (EU) 2017/589 (RTS 6), Article 15: pre-trade controls (text as adopted) — legislation.gov.uk (The National Archives) (retrieved November 3, 2026)
As of November 2026. Third-party rules, prices and APIs may have changed since; the providers' current documentation and the official texts are authoritative.
Built a bot with Claude Code or ChatGPT and want it checked before it goes live? Get in touch — a free intro call clarifies the scope of a review: order and risk code, tests, reconciliation and the paper phase. More: Trading bot development for Interactive Brokers and MetaTrader 5.