Build an AI Forex Trading Bot with Claude Code and OANDA (Python)

intermediate 45 min · · By Alpha Guy · claude-code

What You Are Building

A Python bot that connects to OANDA’s REST API, fetches live forex price data, runs a moving average crossover strategy on EUR/USD, and executes paper trades automatically. Claude Code writes the code while you focus on the strategy logic and risk rules. By the end you will have a working bot running against OANDA’s free practice account.

Why Forex with Claude Code

Most AI trading bot tutorials focus on crypto or US stocks. Forex gets less attention even though it is the largest financial market by volume — over $7.5 trillion per day in 2026. OANDA is one of the few forex brokers with a clean REST API and a free practice environment, which makes it a good fit for bot development.

If you have already built a crypto bot with our Bybit MCP tutorial or the Alpaca stock bot, the workflow here follows the same pattern: Claude Code generates the code, you test on paper, and you iterate on the strategy before considering real money.

The key differences with forex:

AspectCrypto/StocksForex
Market hoursCrypto: 24/7. Stocks: market hours24 hours Sun-Fri, closed weekends
Lot sizingShares or coin unitsStandard (100K), mini (10K), micro (1K) units
LeverageVaries, often 2-10xTypically 30:1 to 50:1 (practice accounts)
SpreadsFixed or percentage feesVariable pip spreads, no commission on most pairs
PairsTicker symbolsCurrency pairs like EUR/USD, GBP/JPY

Prerequisites

  • Python 3.10+
  • An OANDA account (free practice account works)
  • Claude Code installed and working
  • Basic Python familiarity
  • A text editor or IDE

Step 1: Get OANDA API Credentials

OANDA gives you a practice account with $100,000 in virtual funds:

  1. Sign up at oanda.com and select Practice Account
  2. Log into the OANDA portal
  3. Go to Manage API Access under your account settings
  4. Generate a personal access token
  5. Note your account ID (visible on the main dashboard)

The practice environment uses real market data with simulated execution. Spreads and pricing mirror the live environment closely.

Step 2: Install Dependencies

Create a project folder and install the OANDA Python library:

mkdir forex-bot && cd forex-bot
python -m venv venv
source venv/bin/activate
pip install oandapyV20 python-dotenv pandas

Create a .env file for your credentials:

OANDA_ACCESS_TOKEN=your-token-here
OANDA_ACCOUNT_ID=your-account-id-here
OANDA_ENVIRONMENT=practice

Step 3: Prompt Claude Code for the Bot Skeleton

Open Claude Code in your project directory and use this prompt:

Write a Python forex trading bot that:

  1. Connects to OANDA’s practice API using oandapyV20
  2. Fetches the latest 100 candles of EUR/USD on the H1 (1-hour) timeframe
  3. Calculates a 20-period and 50-period simple moving average
  4. Generates a BUY signal when the 20 SMA crosses above the 50 SMA
  5. Generates a SELL signal when the 20 SMA crosses below the 50 SMA
  6. Places market orders with a fixed lot size of 1000 units (micro lot)
  7. Sets a stop loss 50 pips from entry and take profit 100 pips from entry
  8. Tracks whether a position is already open to avoid duplicate orders
  9. Runs in a loop checking every 60 seconds
  10. Logs every action with timestamp, price, signal, and order details
  11. Loads credentials from environment variables using python-dotenv

Claude Code will generate a complete script. Here is what the core logic typically looks like:

import time
import pandas as pd
from oandapyV20 import API
from oandapyV20.endpoints.instruments import InstrumentsCandles
from oandapyV20.endpoints.orders import OrderCreate
from oandapyV20.endpoints.positions import OpenPositions
from dotenv import load_dotenv
import os
import logging

load_dotenv()

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s"
)
logger = logging.getLogger(__name__)

ACCESS_TOKEN = os.getenv("OANDA_ACCESS_TOKEN")
ACCOUNT_ID = os.getenv("OANDA_ACCOUNT_ID")
INSTRUMENT = "EUR_USD"
SHORT_WINDOW = 20
LONG_WINDOW = 50
UNITS = 1000  # micro lot
STOP_LOSS_PIPS = 50
TAKE_PROFIT_PIPS = 100
PIP_VALUE = 0.0001  # for EUR/USD

client = API(access_token=ACCESS_TOKEN, environment="practice")


def get_candles():
    params = {"granularity": "H1", "count": 100, "price": "M"}
    r = InstrumentsCandles(instrument=INSTRUMENT, params=params)
    client.request(r)
    candles = r.response["candles"]
    data = []
    for c in candles:
        if c["complete"]:
            data.append({
                "time": c["time"],
                "close": float(c["mid"]["c"]),
            })
    return pd.DataFrame(data)


def calculate_signal(df):
    df["sma_short"] = df["close"].rolling(window=SHORT_WINDOW).mean()
    df["sma_long"] = df["close"].rolling(window=LONG_WINDOW).mean()
    if len(df) < LONG_WINDOW + 1:
        return "HOLD"
    prev = df.iloc[-2]
    curr = df.iloc[-1]
    if prev["sma_short"] <= prev["sma_long"] and curr["sma_short"] > curr["sma_long"]:
        return "BUY"
    if prev["sma_short"] >= prev["sma_long"] and curr["sma_short"] < curr["sma_long"]:
        return "SELL"
    return "HOLD"


def has_open_position():
    r = OpenPositions(accountID=ACCOUNT_ID)
    client.request(r)
    positions = r.response.get("positions", [])
    for p in positions:
        if p["instrument"] == INSTRUMENT:
            return True
    return False


def place_order(signal, price):
    units = UNITS if signal == "BUY" else -UNITS
    sl_distance = STOP_LOSS_PIPS * PIP_VALUE
    tp_distance = TAKE_PROFIT_PIPS * PIP_VALUE

    order_data = {
        "order": {
            "type": "MARKET",
            "instrument": INSTRUMENT,
            "units": str(units),
            "stopLossOnFill": {"distance": str(sl_distance)},
            "takeProfitOnFill": {"distance": str(tp_distance)},
        }
    }
    r = OrderCreate(accountID=ACCOUNT_ID, data=order_data)
    client.request(r)
    logger.info(f"Order placed: {signal} {UNITS} units at {price}")
    return r.response

Step 4: Refine the Risk Controls

The first version works but needs tighter risk management. Send this follow-up prompt:

Improve the forex bot with these risk controls:

  1. Never risk more than 2% of account balance per trade
  2. Calculate position size based on account balance and stop loss distance
  3. Do not open a new trade if there is already an open position on EUR/USD
  4. Add a daily loss limit of 5% — stop trading for the day if hit
  5. Log the account balance, margin used, and unrealized PnL on each check
  6. Add a function to close all open positions when the bot shuts down (KeyboardInterrupt handler)

Claude Code will update the script with dynamic position sizing. The key change is calculating units from your account balance instead of using a fixed lot size:

def calculate_position_size(account_balance, stop_pips):
    risk_amount = account_balance * 0.02  # 2% risk
    pip_value_per_unit = PIP_VALUE  # for EUR/USD
    units = int(risk_amount / (stop_pips * pip_value_per_unit))
    # cap at 100K units (1 standard lot) for practice
    return min(units, 100000)

Step 5: Add Multiple Currency Pairs

Once EUR/USD works, extend the bot to watch more pairs:

Update the bot to monitor these pairs: EUR/USD, GBP/USD, USD/JPY, AUD/USD. For each pair:

  1. Run the same SMA crossover strategy independently
  2. Use the correct pip value (0.0001 for EUR, GBP, AUD pairs; 0.01 for JPY pairs)
  3. Track positions per pair separately
  4. Apply the same risk rules per trade and the same daily loss limit across all pairs
  5. Stagger the API calls so you do not hit OANDA’s rate limit (20 requests per second)

This is where forex bots differ from crypto bots. Each currency pair has its own pip value, spread characteristics, and volatility profile. JPY pairs use 0.01 per pip instead of 0.0001, so your position sizing math needs to account for that.

Step 6: Run and Monitor

Start the bot against your practice account:

python forex_bot.py

You should see output like:

2026-05-08 10:00:01 INFO Checking EUR_USD — Close: 1.0842, SMA20: 1.0835, SMA50: 1.0828
2026-05-08 10:00:01 INFO Signal: BUY (SMA20 crossed above SMA50)
2026-05-08 10:00:02 INFO Order placed: BUY 4200 units at 1.0842
2026-05-08 10:00:02 INFO Account balance: $100,000.00 | Margin used: $8.40 | Open PnL: $0.00

Watch it run for a few days before making changes. Common issues you will hit:

  • Weekend gaps: Forex markets close Friday evening and reopen Sunday. Your bot will see a price gap on the first candle after the weekend. The SMA crossover might trigger a false signal. Add a check to skip the first candle after a market open.
  • Spread widening: During low-liquidity hours (Asian session for EUR/USD), spreads widen. Your 50-pip stop loss might get hit by spread alone if the spread jumps to 5+ pips on a volatile news release.
  • API rate limits: OANDA allows 20 requests per second. If you monitor 4 pairs with 60-second intervals, you are well within limits. Going below 10-second intervals with multiple pairs will get you throttled.

What This Strategy Does Not Do

This is a basic trend-following bot. It does not:

  • Account for economic calendar events (NFP, FOMC, ECB rate decisions)
  • Use more sophisticated entry timing (RSI filters, volume confirmation)
  • Adapt to ranging vs. trending market conditions
  • Handle correlation between pairs (GBP/USD and EUR/USD often move together)

These are all things you can add with follow-up Claude Code prompts. But start simple, run it on paper for at least two weeks, and look at the actual trade log before adding complexity.

Forex vs. Crypto/Stock Bots: What to Know

If you are coming from our crypto bot tutorials, here are the practical differences:

FactorCrypto BotsForex Bots
Execution speedFast, exchange-native APIFast, but spread-dependent
SlippageCommon on volatile altsMinimal on major pairs
Data costUsually freeFree on OANDA, paid on some providers
RegulationVaries by jurisdictionHeavily regulated, broker matters
TestingTestnet with fake balancesPractice account with real market data
Best pairs for beginnersBTC/USDT, ETH/USDTEUR/USD, GBP/USD

Next Steps

  • Run the bot on OANDA’s practice account for 2-4 weeks
  • Review the trade log and calculate win rate, average profit/loss per trade, and max drawdown
  • Try different SMA periods (10/30, 15/45) and compare results
  • Add an RSI filter to avoid entries in overbought/oversold conditions
  • Read our AI trading 101 guide if you are new to algorithmic trading concepts
  • Check our MCP servers guide for connecting Claude Code to additional data sources
Disclaimer: This article is for educational purposes only and is not financial advice. Trading cryptocurrencies involves substantial risk of loss. Past performance does not guarantee future results. Always do your own research before making any trading decisions. Read full disclaimer →
Alpha Guy
Alpha Guy

Founder of VibeTradingLab. Ex-Goldman Sachs engineer, 2025 Binance Top 1% Trader. Writes about using AI tools to build trading systems that actually work. Currently nomading between Bali, Dubai, and the Mediterranean.

Got stuck? Have questions?

Join our Telegram group to ask questions, share your bots, and connect with other AI traders.

Join Telegram