algorithmic trading — system design case study

OANDA Bot Suite

Seven currency-specialist trading bots share a single brokerage account, a single strategy, and a single risk budget. Each bot watches its own slice of the market; a coordination layer keeps their combined risk from ever exceeding what one bot alone would take.

7 Currency specialists
3 Entry gates
This page is a write-up of the system's design — strategy logic, multi-bot coordination, and risk management. It is not a dashboard. Nothing here starts, stops, or connects to the running system, and no account data, balances, or trade history are shown.

System architecture

Seven specialists, one account

Rather than one bot trading all 28 currency pairs OANDA offers, the suite splits the work by currency. Each specialist watches only the pairs built around its assigned currency, running the exact same strategy logic as its six siblings.

JPYJapanese Yen
EUREuro
GBPBritish Pound
AUDAustralian Dollar
NZDNew Zealand Dollar
CADCanadian Dollar
CHFSwiss Franc

Coordination across processes

Running seven independent processes against one account creates a coordination problem a backtest never sees: two specialists can decide to act on the account at the same instant. The suite runs on Linux and macOS specifically to use POSIX file locking, so specialists queue behind each other for the moment they touch shared account state instead of racing one another.

Operating the suite

A control dashboard, built with Streamlit and reachable only from the machine running it, is where specialists get started, stopped, and monitored. Because a specialist may be holding an open position at any moment, the shared configuration below is designed to hot-reload: a change takes effect on the process's next scan cycle rather than requiring it to be killed and relaunched. That dashboard is a private operating tool and isn't part of this page.

Trade logic

Entry logic: three narrowing gates

Every specialist runs the identical decision funnel; only the currency and pairs change. A trade idea has to clear all three timeframes, largest to smallest, before it's allowed anywhere near an entry.

Gate 1 — Daily

TEMA200 trend direction

Gate 2 — 8h / 4h

Moving-average stack alignment

Gate 3 — 1h

S/R entry + RSI confirmation

Gate 1 — daily trend

A 200-period triple exponential moving average sets the macro direction: its slope and the price's position relative to it have to agree before anything downstream is even evaluated.

Gate 2 — 8-hour / 4-hour structure

On both timeframes, price has to sit on the correct side of a full stack of moving averages, in order: a 50-period DEMA, then a 100-period DEMA, then the 200-period TEMA. A stack that's aligned but out of order doesn't count.

Gate 3 — hourly trigger

Entry only fires at a support or resistance level on the 1-hour chart, confirmed by the 50-period RSI crossing its own midline in the trade's direction. This is the timing gate; the first two only decide whether the setup is allowed to exist.

Stops and targets

The stop is anchored to market structure rather than a flat pip count chosen in advance: it sits a fixed 15 pips beyond the S/R level that triggered the entry. No trade is taken unless its target, the next S/R level out, offers at least a 2:1 reward-to-risk ratio.

That 2:1 floor isn't an arbitrary round number: it sets how often a trade actually has to win before the rule set is net-positive at all, which is worth showing directly rather than taking on faith.

Breakeven win rate by minimum reward:risk ratio

Pure algebra from this page's own MIN_RR rule: breakeven win rate = 1 ÷ (1 + R). Not a performance result, a backtest, or live trading data — no trading data appears anywhere on this page.

100 75 50 25 0 1:1 2:1 3:1 4:1 5:1 MIN_RR = 2.0 → 33.3% breakeven win rate MIN_RR = 2.0 33.3% breakeven win rate minimum reward:risk ratio (R)

Read as: at a 2:1 minimum reward:risk ratio, this rule set only needs to win more often than 33.3% of the time to be net-positive before costs; spread and slippage aren't modeled here, or anywhere else on this page.

Post-news mode

A separate mode watches for high-impact economic releases and, after the initial spike, looks for a Fibonacci retracement entry into the pullback rather than chasing the move itself.

Risk controls

Risk management: one account, seven processes

The strategy decides when to trade. A separate layer decides how much, and it's built around the fact that seven independent processes are all drawing on the same equity.

Position sizing splits, it doesn't stack

Each specialist risks a fixed percentage of equity per trade, but that equity figure is divided evenly across however many specialists are currently running. Starting a second bot halves each bot's position size rather than doubling the account's total exposure, and the relationship holds for any number running at once:

Equity share per bot, by number of active specialists

Arithmetic from the sizing rule (100% ÷ active bots), not a performance result — no trading data appears anywhere on this page.

100 75 50 25 0 1 bot running — 100% of equity 2 bots running — 50% of equity each 3 bots running — 33.3% of equity each 4 bots running — 25% of equity each 5 bots running — 20% of equity each 6 bots running — 16.7% of equity each 7 bots running — 14.3% of equity each 100% 50% 33.3% 25% 20% 16.7% 14.3% 1 2 3 4 5 6 7 active specialist bots running

Read as: with 3 specialists running, each one sizes trades off one third of account equity.

Account-wide caps

Sizing isn't the only backstop. A handful of limits are checked against the whole account, not each bot's slice of it, so they hold no matter how many specialists are active:

  • Open position cap

    A maximum number of simultaneous trades across all seven bots combined, read from OANDA's live position data.

  • JPY exposure cap

    A separate, lower ceiling on open JPY-pair trades specifically, enforced the same way.

  • Daily loss limit

    Always evaluated against full account equity, never a bot's divided share, so it protects the account as a whole. Crossing it pauses every bot for the day.

  • Startup guard

    Starting a bot while positions are already open requires explicit confirmation; the suite never silently begins trading on top of an existing book.

Parameters

Shared configuration

Every specialist reads the same parameter set from a shared config module, built to hot-reload rather than require a restart, since a restart mid-trade would mean momentarily losing track of an open position:

ParameterDefaultWhat it controls
RISK_PCT0.01Fraction of a bot's equity share risked per trade
MIN_RR2.0Minimum reward-to-risk ratio required to take a trade
MAX_OPEN_POSITIONS3Account-wide cap on simultaneous open trades
DAILY_LOSS_LIMIT0.03Account drawdown that pauses every bot for the day
JPY_MAX_OPEN2Account-wide cap on open JPY-pair trades
RSI_PERIOD50Lookback for the Gate 3 RSI confirmation
SR_LOOKBACK20Candles checked each side to confirm a swing high or low

Strategy and risk parameters are shared by all seven specialists; only the traded currency and pair list differ between them.

Self-hosting

Get the suite

There's no public repository to clone yet. What follows describes how the project is actually laid out and what setting it up from scratch looks like, not a download link.

What it needs

Python 3 and pip, on Linux or macOS — the suite coordinates its seven bot processes with POSIX file locking, which Windows doesn't have natively, so Windows users run it inside WSL2. A setup script installs the Python dependencies (the OANDA API client, pandas and numpy for indicator math, Streamlit for the dashboard, and a handful of others) with one command.

How it's laid out

One module per concern, all reading from the same shared configuration:

ModuleRole
config.pyShared parameters, the same ones in the table above, plus the currency-pair mapping that assigns every OANDA instrument to exactly one of the seven groups.
bot.pyThe specialist process itself; one instance per currency group, e.g. started as python3 bot.py --group JPY.
strategy.py, indicators.py, levels.pyThe three-gate entry logic and support/resistance detection described above.
risk.py, executor.pyPosition sizing, the account-wide caps, and order placement.
suite.pyThe cross-process coordination layer: POSIX file locking across specialists and the equity-share-per-bot sizing divisor described above. Imported by bot.py, risk.py, executor.py, and dashboard.py alike.
news.py, swing.py, learner.pynews.py watches the economic calendar for high-impact releases; swing.py evaluates the post-news Fibonacci-retracement entries described above once one fires; learner.py is a self-learning win-probability filter.
dashboard.pyThe Streamlit control panel that starts, stops, and configures bots; bound to localhost only, never the network.
backtest.pyReplays the strategy against historical candles before anything runs against a real account.

Config and credentials

Everything reads from that one shared config module at startup, which in turn loads a local .env file that never gets committed anywhere. Three values matter to start: an API token, an account ID, and which OANDA environment to point at.

# copy the template, then edit it
cp .env.example .env

# inside .env
OANDA_API_KEY=your token
OANDA_ACCOUNT_ID=your account id
OANDA_ENVIRONMENT=practice   # or live, once you trust it

OANDA issues practice credentials for free against a fake-money demo account, which is what a first run should point at. Every risk parameter in the table further up is read from that same config module and stays hot-reloadable from the dashboard, so tuning them doesn't need a restart either.

Running it

A setup script installs dependencies; a backtest run against historical candles comes before anything else; then a launch script starts the dashboard on 127.0.0.1:8501 only, and every specialist bot gets started and stopped from its sidebar from there. One more script stops everything, bots and dashboard together, in a single call.

None of that is reachable from this page. As the callout up top says, this write-up doesn't start, stop, or connect to anything — it's a description of what running it yourself would involve.