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:
| Aspect | Crypto/Stocks | Forex |
|---|---|---|
| Market hours | Crypto: 24/7. Stocks: market hours | 24 hours Sun-Fri, closed weekends |
| Lot sizing | Shares or coin units | Standard (100K), mini (10K), micro (1K) units |
| Leverage | Varies, often 2-10x | Typically 30:1 to 50:1 (practice accounts) |
| Spreads | Fixed or percentage fees | Variable pip spreads, no commission on most pairs |
| Pairs | Ticker symbols | Currency 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:
- Sign up at oanda.com and select Practice Account
- Log into the OANDA portal
- Go to Manage API Access under your account settings
- Generate a personal access token
- 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:
- Connects to OANDA’s practice API using oandapyV20
- Fetches the latest 100 candles of EUR/USD on the H1 (1-hour) timeframe
- Calculates a 20-period and 50-period simple moving average
- Generates a BUY signal when the 20 SMA crosses above the 50 SMA
- Generates a SELL signal when the 20 SMA crosses below the 50 SMA
- Places market orders with a fixed lot size of 1000 units (micro lot)
- Sets a stop loss 50 pips from entry and take profit 100 pips from entry
- Tracks whether a position is already open to avoid duplicate orders
- Runs in a loop checking every 60 seconds
- Logs every action with timestamp, price, signal, and order details
- 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:
- Never risk more than 2% of account balance per trade
- Calculate position size based on account balance and stop loss distance
- Do not open a new trade if there is already an open position on EUR/USD
- Add a daily loss limit of 5% — stop trading for the day if hit
- Log the account balance, margin used, and unrealized PnL on each check
- 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:
- Run the same SMA crossover strategy independently
- Use the correct pip value (0.0001 for EUR, GBP, AUD pairs; 0.01 for JPY pairs)
- Track positions per pair separately
- Apply the same risk rules per trade and the same daily loss limit across all pairs
- 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:
| Factor | Crypto Bots | Forex Bots |
|---|---|---|
| Execution speed | Fast, exchange-native API | Fast, but spread-dependent |
| Slippage | Common on volatile alts | Minimal on major pairs |
| Data cost | Usually free | Free on OANDA, paid on some providers |
| Regulation | Varies by jurisdiction | Heavily regulated, broker matters |
| Testing | Testnet with fake balances | Practice account with real market data |
| Best pairs for beginners | BTC/USDT, ETH/USDT | EUR/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