39 KiB
GENESIS: Building a Fully Autonomous MT5 Trading System with Six Strategy Bots and a Python REST API
Published by API2TRADE · app.api2trade.com Open-source repository:
api2trade/Genesis-Metatrader-Automatic-AI-Trading-System(GPL-3.0) Status: Live | Engine v2.1 | Tested on Deriv Demo ($10,000 USD) and Exness MT5
Table of Contents
- What Was Actually Built
- Why REST Instead of a Local MT5 Terminal
- Core Architecture — How All Parts Connect
- The API2TRADE Integration — Every Real Endpoint Used
- The Six Strategy Gods — Real Implementation Deep Dive
- Hermes — The LLM Orchestration Brain
- Market Data — Multi-Source Fallback Architecture
- Risk Management — The Hard Layer
- How to Replicate This — Complete Step-by-Step
- Infrastructure and Real Costs
- Key Engineering Challenges and How We Solved Them
- Live Test Results — Deriv Demo Account
- What to Do Next
1. What Was Actually Built
GENESIS is 8,401 lines of Python split across 44 files that form a complete autonomous trading engine. It runs six independent strategy bots on a cron schedule, managed by an LLM orchestrator named Hermes, all communicating directly with MetaTrader 5 via the API2TRADE REST API.
This is not a demo, a tutorial skeleton, or a toy project. It is a system we built, debugged, deployed to production, tested on a live MT5 account, and then open-sourced so you can replicate it exactly.
What the system does every 5 minutes:
genesis_autonomous.pyfires via cron- It checks the live MT5 account balance and open positions via API2TRADE
- It runs each active strategy bot as a subprocess
- Each bot fetches market data (yfinance + API2TRADE), computes indicators, and evaluates its signal conditions
- If a bot returns
"action": "trade", Hermes validates the risk limits - If risk passes,
OrderSendSafefires directly at the MT5 account via API2TRADE REST - Every decision — trade or no trade — is logged to
/var/log/hermes/and sent to Telegram
The full system uses zero local MetaTrader installation, zero Windows server, and zero proprietary SDK. Just Python and HTTP.
2. Why REST Instead of a Local MT5 Terminal
The standard approach to MT5 automation is to run an Expert Advisor (EA) in a Windows-based MetaTrader 5 terminal and use either the built-in MQL5 language or the MetaTrader5 Python package, which requires a local terminal running.
This creates several real problems:
| Problem | Local MT5 Approach | API2TRADE Approach |
|---|---|---|
| OS requirement | Windows only | Any OS, any cloud |
| Always-on requirement | Terminal must be running 24/5 | API2TRADE handles connectivity |
| Language | MQL5 (proprietary) | Pure Python |
| Deployment | Manual terminal management | docker compose up |
| Remote access | RDP into Windows VPS | REST calls from anywhere |
| Broker switching | Reinstall terminal | Change UUID in .env |
We originally built GENESIS with a local FastAPI bridge (127.0.0.1:8000) that translated Python calls into the MetaTrader5 Python library. This worked, but added a fragile extra layer: the bridge had to be running, the MT5 terminal had to be running, and both had to be on the same Windows machine.
We removed the bridge entirely in GENESIS v2.1. Every strategy now calls the API2TRADE REST endpoints directly from Python. The architecture became dramatically simpler:
Before (v1): Python → FastAPI Bridge → MetaTrader5 SDK → MT5 Terminal → Broker
After (v2): Python → API2TRADE REST → Broker
3. Core Architecture — How All Parts Connect
The complete GENESIS v2.1 architecture: six strategy bots connect directly to MetaTrader 5 via API2TRADE — no local bridge, no Windows server required.
File Structure
Genesis-Metatrader-Automatic-AI-Trading-System/
├── setup.sh # Interactive setup wizard (run first)
├── install.sh # Ubuntu 22.04 VPS installer
├── docker-compose.yml # Docker deployment
│
├── core/
│ ├── genesis_autonomous.py # Main engine — runs every 5 min via cron
│ ├── trading_cycle.py # Hermes LLM brain — runs every hour
│ ├── genesis_daily_report.py # 06:00 UTC daily P&L summary
│ ├── genesis_brain_feed.py # :30 min Telegram market summary
│ ├── heartbeat.py # Health check every 10 min
│ └── tg_notify.py # Telegram helper
│
├── strategies/
│ ├── ares/ # BB+RSI M1 mean reversion
│ │ ├── ares_cycle.py # 643 lines — signal logic + API calls
│ │ └── ares_tool.py # CLI entry point
│ ├── apollo/ # EMA trend following (613 lines)
│ ├── athena/ # BB+RSI multi-TF ranging (622 lines)
│ ├── artemis/ # Ichimoku H1 breakout (498 lines)
│ ├── zeus/ # ICT Smart Money M5 (643 lines)
│ └── hephaestus/ # Grid/Martingale (549 lines)
│
├── configs/ # YAML config per strategy
└── backtest/ # Python backtester + MT5 EA
The Cron Schedule
*/5 * * * * genesis_autonomous.py # Strategy scan — every 5 min
0 * * * * trading_cycle.py # Hermes LLM macro — hourly
30 * * * * genesis_brain_feed.py # Market summary Telegram — :30 min
0 6 * * * genesis_daily_report.py # Daily P&L — 06:00 UTC
0 7 * * 1-5 genesis_market_open.py # Market open alert — weekdays
*/10 * * * * heartbeat.py # Health check — every 10 min
4. The API2TRADE Integration — Every Real Endpoint Used
API2TRADE (app.api2trade.com) provides a REST API that connects to your live MetaTrader 5 account. You authenticate with Basic HTTP auth on every request, and pass a session UUID (the id parameter) that identifies which MT5 account you're targeting.
Authentication:
Username + Password: From your API2TRADE dashboard
Session UUID: Created via /ConnectEx when you link your MT5 account
Base URL: https://mt5.mt4api.dev
Every endpoint GENESIS actually calls:
# ── Account ──────────────────────────────────────────────────────────────────
# Check balance, equity, margin, leverage
GET /AccountSummary?id={uuid}
→ {"balance": 10000.0, "equity": 10000.0, "currency": "USD",
"leverage": 1000.0, "type": "demo", "method": "Hedging"}
# ── Positions ─────────────────────────────────────────────────────────────────
# All currently open positions
GET /OpenedOrders?id={uuid}
→ [{"Ticket": 12345, "Symbol": "EURUSDxx", "Type": "Buy",
"Volume": 0.1, "Price": 1.08542, "Profit": 12.30, "Comment": "GENESIS-ARES"}]
# ── Market Data ───────────────────────────────────────────────────────────────
# Live bid/ask quote (used for spread check + entry price)
GET /Quote?id={uuid}&symbol=EURUSDxx
→ {"Bid": 1.08540, "Ask": 1.08542}
# OHLCV bars (M1, M5, H1, H4, D1)
GET /QuoteHistory?id={uuid}&symbol=EURUSDxx&timeFrame=M5&count=200
# ── Trade Execution ───────────────────────────────────────────────────────────
# Open a position (MT5 "Safe" variant = validates before firing)
GET /OrderSendSafe?id={uuid}&symbol=EURUSDxx&operation=Buy
&volume=0.1&stoploss=1.08392&takeprofit=1.08842&comment=GENESIS-ARES
→ {"ticket": 12345678}
# Modify SL/TP on open position
GET /OrderModifySafe?id={uuid}&ticket=12345678
&stoploss=1.08450&takeprofit=1.08900
# Close position fully or partially
GET /OrderCloseSafe?id={uuid}&ticket=12345678&lots=0.1
How the bridge() function works inside each strategy:
Every strategy module has a single bridge() function that routes logical API calls to the correct endpoint:
# Simplified from ares_cycle.py — the real function is ~70 lines
MT5_API = os.getenv("MT5_API_URL", "https://mt5.mt4api.dev")
MT5_ID = os.getenv("MT5_ACCOUNT_ID", "")
MT5_AUTH = (os.getenv("MT5_API_USER", ""), os.getenv("MT5_API_PASS", ""))
def bridge(path, data=None) -> dict:
"""Single entry point for all MT5 API calls."""
ep_map = {
"/balance": "AccountSummary",
"/positions": "OpenedOrders",
}
if path.startswith("/quote"):
symbol = path.split("symbol=")[-1]
r = requests.get(f"{MT5_API}/Quote",
params={"id": MT5_ID, "symbol": symbol},
auth=MT5_AUTH, timeout=8)
raw = r.json()
return {"bid": raw["Bid"], "ask": raw["Ask"]}
if path == "/market" and data:
r = requests.get(f"{MT5_API}/OrderSendSafe",
params={
"id": MT5_ID,
"symbol": data["symbol"],
"operation": data["type"], # "Buy" or "Sell"
"volume": data["volume"],
"stoploss": data["stop_loss"],
"takeprofit": data["take_profit"],
"comment": data.get("comment", "GENESIS"),
}, auth=MT5_AUTH, timeout=15)
ticket = r.json().get("ticket") or r.json().get("integerResponse")
return {"ticket": ticket}
# AccountSummary, OpenedOrders, etc.
cloud_ep = ep_map.get(path, path.lstrip("/"))
r = requests.get(f"{MT5_API}/{cloud_ep}",
params={"id": MT5_ID}, auth=MT5_AUTH, timeout=10)
return r.json()
This design means adding a new strategy only requires copying one of the existing cycle files and modifying the signal logic — the API integration layer is already there.
5. The Six Strategy Gods — Real Implementation Deep Dive
Each strategy has a distinct market regime where it performs best. Running all six in parallel means the system is productive in trending, ranging, and volatile conditions.
Every strategy follows the same interface contract. The run_analysis(symbol) function always returns:
{
"action": "trade" | "wait",
"reason": "Human-readable explanation",
"direction": "Buy" | "Sell",
"symbol": "EURUSDxx",
"entry": 1.08542,
"stop_loss": 1.08392,
"take_profit": 1.08842,
"volume": 0.10,
"sl_pips": 15.0,
"rr_ratio": 2.0,
"confidence": "high" | "medium",
"conditions_met": ["RSI oversold", "BB lower touch", ...],
"conditions_failed": [...]
}
This uniform interface is what allows genesis_autonomous.py to run all six bots and process results identically.
ARES — Bollinger Bands + RSI Mean Reversion (M1)
File: strategies/ares/ares_cycle.py · 643 lines
ARES is designed for scalping in choppy, ranging markets. The core thesis: when price closes outside the Bollinger Band AND RSI is in extreme territory, it tends to snap back to the mean.
Signal conditions (both required for a trade):
# BUY signal — from the actual implementation
bull_signal = (
close < bb_lower # Price closed below lower BB (2.0 std dev, 20 period)
and rsi < RSI_OVERSOLD # RSI < 30 (oversold)
and REQUIRE_OUTSIDE # Must be a true band pierce, not just touch
and REQUIRE_RSI # Both conditions must hold simultaneously
)
# Additional strictness gate
# ADX < 25 = ranging market (preferred for mean reversion)
# M15 context MA check = trend not strongly against us
Indicator computation (using the ta library):
def compute_bb_rsi(bars, bb_period=20, bb_dev=2.0, rsi_period=14):
df = pd.DataFrame(bars)
bb_lower = ta.volatility.bollinger_lband(df["close"], window=bb_period, window_dev=bb_dev)
bb_upper = ta.volatility.bollinger_uband(df["close"], window=bb_period, window_dev=bb_dev)
rsi = ta.momentum.rsi(df["close"], window=rsi_period)
adx = ta.trend.adx(df["high"], df["low"], df["close"], window=14)
return {
"bb_lower": float(bb_lower.iloc[-2]), # Last CLOSED bar, not forming bar
"bb_upper": float(bb_upper.iloc[-2]),
"rsi": float(rsi.iloc[-2]),
"adx": float(adx.iloc[-2]),
}
Config parameters (configs/ares_config.yaml):
| Parameter | Value | Reason |
|---|---|---|
| BB period | 20 | Standard deviation window |
| BB std dev | 2.0 | ~95% price capture |
| RSI period | 14 | Standard Wilder smoothing |
| RSI oversold | 30 | Classic threshold |
| RSI overbought | 70 | Classic threshold |
| Max spread | 1.0 pip | M1 scalp — tight spread critical |
| Session | 07:00–21:00 GMT | London + NY overlap |
| SL | 20 pips | Fixed for M1 regime |
| TP | 40 pips | 1:2 minimum R:R |
Key implementation detail: We use iloc[-2] (second-to-last row) everywhere, not iloc[-1]. This is because the last bar is still forming — computing indicators on an incomplete bar causes lookahead bias. This single detail separates a realistic backtest from a profitable-looking one.
APOLLO — EMA 9/21 Trend Following (M5)
File: strategies/apollo/apollo_cycle.py · 613 lines
APOLLO trades momentum. It only enters when price is moving strongly in one direction, confirmed by two EMAs and volume context.
Signal logic:
# EMA crossover + price position
bull_signal = (
ema9 > ema21 # Fast EMA above slow EMA (trend up)
and close > ema9 # Price above both EMAs (momentum confirmed)
and atr > atr_threshold # Sufficient volatility (not dead market)
and rsi > 50 # Momentum bias — not overbought enough to fade
and trend_ma_50 < close # 50-period MA context: overall uptrend
)
APOLLO avoids the most common EMA crossover failure mode — entering on a fake cross during consolidation — by requiring three separate confirmations: the cross itself, price position relative to both MAs, and ATR-based volatility floor.
ATHENA — Multi-Timeframe BB+RSI Ranging (M5)
File: strategies/athena/athena_cycle.py · 622 lines
ATHENA is ARES's older sibling. Same BB+RSI core but on M5 (more signal stability, fewer noise trades) with an additional multi-timeframe filter: the H1 chart must not be in a strong trend (ADX check) before ATHENA enters a mean-reversion trade.
The key difference from ARES:
- ARES: fast, M1, tighter spreads, more signals
- ATHENA: slower, M5, wider spread tolerance (1.5 pips), multi-TF confirmation required
ARTEMIS — Ichimoku Kumo Breakout (H1)
File: strategies/artemis/artemis_cycle.py · 498 lines
ARTEMIS uses the full five-component Ichimoku system: Tenkan-sen (9), Kijun-sen (26), Senkou Span A, Senkou Span B (52), and Chikou Span (26-bar displacement).
The Ichimoku computation challenge — displacement:
This is the part that trips up most Ichimoku implementations. Senkou Spans A and B are plotted 26 bars forward in the future. To get the cloud at the current bar, you read the Span values at index -(DISP + 2) in the historical series:
def compute_ichimoku(bars):
DISP = 26 # displacement
tenkan = midpoint(highs, lows, period=9)
kijun = midpoint(highs, lows, period=26)
span_a = (tenkan + kijun) / 2 # Will be plotted DISP bars ahead
span_b = midpoint(highs, lows, period=52) # Will be plotted DISP bars ahead
# Current cloud = values that were calculated DISP bars AGO (now displayed at current bar)
cloud_idx = -(DISP + 2)
sa_current = float(span_a.iloc[cloud_idx]) # ← This is the key
sb_current = float(span_b.iloc[cloud_idx])
kumo_top = max(sa_current, sb_current)
kumo_bottom = min(sa_current, sb_current)
# Chikou Span: current close vs close 26 bars ago
chikou_bullish = close_now > close_disp # Current price above historical
Signal conditions (7 total, minimum 5 must pass for a trade):
buy_conditions = [
(close > kumo_top, "Price above Kumo"),
(future_cloud == "green", "Future cloud GREEN — SpanA > SpanB ahead"),
(cloud_color == "green", "Current cloud GREEN"),
(chikou_bullish, "Chikou above price 26 bars ago"),
(rsi > 50, "RSI above 50"),
(close > kijun, "Price above Kijun-sen"),
(conf_bars >= CONF_BARS, f"{conf_bars} bars confirmed above Kumo"),
]
# Signal fires only if 5+ conditions pass
if len(passed_conditions) >= 5:
return {"action": "trade", "confidence": "high" if all_7 else "medium"}
ARTEMIS is the most selective strategy — it typically generates 2–8 signals per day on a given symbol and has the highest R:R target (2.5:1 minimum) because H1 setups allow wider SL placement using the Kijun-sen as the natural stop level.
ZEUS — ICT Smart Money Concepts (M5)
File: strategies/zeus/zeus_cycle.py · 643 lines
ZEUS implements three ICT concepts in sequential confirmation — not parallel. All three must activate in order for a trade to fire. This strict sequencing is what makes it high-conviction but low-frequency.
Layer 1 — Liquidity Sweep Detection:
A liquidity sweep occurs when price briefly exceeds a recent swing high/low (triggering stop orders clustered there) then reverses. The rejection must happen within the same or next candle:
def detect_liquidity_sweep(highs, lows, closes, opens, sym):
# Find swing highs/lows in last 30 bars
sw_highs = detect_swing_highs(highs[-32:-2], lookback=SWING_LB)
sw_lows = detect_swing_lows(lows[-32:-2], lookback=SWING_LB)
# Last closed candle
cur_h = highs[-2]; cur_l = lows[-2]; cur_c = closes[-2]; cur_o = opens[-2]
for level in sw_highs:
wick_above = cur_h > level * (1 + SWEEP_TOL) # Price pierced the level
closed_below = cur_c < level # But closed back below
if wick_above and closed_below and REQ_REJECT:
return {"type": "BearSweep", "level": level, "candle_idx": -2}
Layer 2 — Fair Value Gap (FVG) Detection:
An FVG is a 3-candle imbalance: the high of candle N is below the low of candle N+2, leaving a gap that price is expected to eventually fill:
def detect_fvg(highs, lows, direction):
# Three-candle imbalance
for i in range(-FVG_AGE, -2):
c1_high = highs[i-1]; c3_low = lows[i+1]
if direction == "bull" and c1_high < c3_low:
gap_size = (c3_low - c1_high) / pip_size
if gap_size >= MIN_GAP_PIPS:
return {"top": c3_low, "bottom": c1_high, "age_bars": abs(i)}
Layer 3 — Order Block Detection:
The Order Block is the last candle before a strong displacement move — typically a large institutional entry point:
def detect_order_block(bars, direction):
# Find the displacement candle (biggest move in last OB_AGE bars)
# The order block is the candle BEFORE it
# If it's a bullish OB: last bearish candle before bullish surge
# If it's a bearish OB: last bullish candle before bearish dump
body_ratio = abs(close - open) / (high - low)
if body_ratio >= MIN_BODY_RATIO: # Strong displacement candle
ob_candle = bars[displacement_idx - 1]
return {"top": ob_candle["high"], "bottom": ob_candle["low"]}
ICT Confluence Score:
Each layer contributes points. A minimum score of MIN_SCORE (configurable in zeus_config.yaml) is required for trade execution. London (07:00–10:00 GMT) and New York (13:00–16:00 GMT) killzones add a +1 bonus:
score = 0
if liquidity_sweep: score += 2 # Highest weight — sweep is the trigger
if fvg: score += 1 # Confirms displacement
if order_block: score += 2 # Confirms institutional entry zone
if in_killzone(): score += 1 # Time-based bonus
if score >= MIN_SCORE: # Default: 4 out of 6
return {"action": "trade", ...}
HEPHAESTUS — Grid / Martingale (Continuous)
File: strategies/hephaestus/hephaestus_cycle.py · 549 lines
HEPHAESTUS operates differently from the signal-based strategies. It maintains a grid of positions at defined price levels and manages the exposure dynamically. It does not use yfinance for bars — it only needs the current price from the API2TRADE Quote endpoint and the list of open positions.
⚠️ Risk warning: Grid/Martingale strategies can accumulate significant exposure in trending markets. HEPHAESTUS is included as an implementation example and should be tested thoroughly on demo before using on a live account.
6. Hermes — The LLM Orchestration Brain
trading_cycle.py runs every hour and asks GPT-4o-mini a structured prompt containing:
- Current account balance and equity
- Open positions and their P&L
- Recent market conditions (pulled from yfinance)
- Which strategies are currently active
- Recent trade journal entries
GPT-4o-mini responds with a structured JSON decision:
{
"market_regime": "ranging",
"preferred_strategies": ["ares", "athena"],
"suppress_strategies": ["apollo"],
"risk_adjustment": 0.8,
"reasoning": "EUR/USD has been oscillating in a 50-pip range since 09:00. Mean reversion strategies favoured. Apollo trend following suppressed until breakout confirmation.",
"macro_notes": "Fed minutes tomorrow 19:00 UTC — reduce position sizes by 20% after 17:00."
}
genesis_autonomous.py reads these instructions before running each strategy cycle. If Hermes has suppressed a strategy, that bot is skipped. If a risk adjustment is in effect, the calculated lot size is multiplied by the adjustment factor.
The critical design principle: Hermes cannot override the hard risk limits encoded in each strategy. Even if Hermes tells ARES to trade, ARES still independently checks balance, spread, position count, and R:R. The LLM layer is advisory, not authoritative.
7. Market Data — Multi-Source Fallback Architecture
GENESIS has a three-tier fallback for price data. This was an engineering necessity: some MT5 brokers (like Deriv Demo) don't stream all quote symbols via the API, and Yahoo Finance blocks Docker container IPs intermittently.
For OHLCV bars (strategy signal computation):
# yfinance with symbol mapping
YF_MAP = {
"EURUSDxx": "EURUSD=X", "GBPUSDxx": "GBPUSD=X",
"USDJPYxx": "USDJPY=X", "GBPJPYxx": "GBPJPY=X",
"XAUUSDxx": "GC=F",
}
df = yf.download("EURUSD=X", period="5d", interval="1m",
progress=False, auto_adjust=True)
For live quotes (spread check + entry price):
# Tier 1: API2TRADE /Quote (broker's live feed — best for spread accuracy)
r = requests.get(f"{MT5_API}/Quote",
params={"id": MT5_ID, "symbol": symbol},
auth=MT5_AUTH, timeout=8)
if r.status_code == 200 and r.text.strip():
raw = r.json()
if raw.get("Bid", 0) > 0:
return {"bid": raw["Bid"], "ask": raw["Ask"]}
# Tier 2: open.er-api.com (free, no API key, updated hourly)
r = requests.get(f"https://open.er-api.com/v6/latest/{base_ccy}", timeout=8)
rates = r.json().get("rates", {})
price = rates.get(quote_ccy) # e.g. base=EUR, quote=USD → EURUSD
if price:
spread = 0.00015 # 1.5 pip synthetic spread
return {"bid": price - spread/2, "ask": price + spread/2}
# Tier 3: Frankfurter API (ECB rates, supports XAU/USD)
r = requests.get(f"https://api.frankfurter.app/latest?from=EUR&to=USD", timeout=8)
price = r.json()["rates"]["USD"]
Why this matters: This fallback chain means GENESIS works correctly even when:
- The MT5 broker quote stream is delayed or unavailable
- You're on a cloud provider that Yahoo Finance rate-limits
- You're testing on a broker that only streams certain symbols
8. Risk Management — The Hard Layer
Every strategy enforces these checks in order before returning "action": "trade". None can be bypassed by Hermes or any other component.
1. Account equity > 0 → "Account equity is zero or unavailable"
2. No duplicate position already open → "Strategy X position already open"
3. Cooldown period (configurable) → "Cooldown: 847s remaining"
4. Is it a trading session? → "Outside session GMT 07–21"
5. Spread ≤ max_spread_pips → "Spread 2.3 > max 1.5 pips"
6. No high-impact news ±15 min → "News block: NFP in 8min"
7. Sufficient bar data → "Insufficient M1 bar data"
8. Indicator validity → "Indicator values None"
9. Signal conditions met → "Only 3/7 conditions met"
10. R:R ≥ minimum ratio → "R:R 1.2 < minimum 1.5"
Lot sizing — dynamic, risk-based:
def calculate_lot(equity, sl_pips, symbol):
# Pip value per lot (approximate)
pip_value = {
"EUR": 10.0, # EURUSD: $10/lot/pip
"GBP": 12.5, # GBPUSD: ~$12.50/lot/pip
"JPY": 9.0, # USDJPY: ~$9/lot/pip
"XAU": 1.0, # XAUUSD: $1/lot/pip (0.1 pip instrument)
}
pv = pip_value.get(symbol[:3], 10.0)
# Risk amount = equity × risk_pct (e.g. 1% of $10,000 = $100)
risk_amount = equity * RISK_PCT # RISK_PCT = 0.01 per strategy
raw_lots = risk_amount / (sl_pips * pv)
return round(max(0.01, min(raw_lots, 3.0)), 2) # Hard cap: 3.0 lots maximum
With a $10,000 account, 1% risk, and 20-pip SL on EURUSD:
$100 ÷ (20 pips × $10/pip) = 0.50 lots
9. How to Replicate This — Complete Step-by-Step
Step 1: Get Your API2TRADE Account
Sign up at app.api2trade.com. Connect your MT5 account by providing your broker login, password, and server name. API2TRADE creates a persistent session UUID — this is your MT5_ACCOUNT_UUID in the .env file.
Cost: €12/month per connected MT5 account.
The session UUID looks like: a1b2c3d4-e5f6-7890-abcd-ef1234567890
You also get an API username and password for Basic HTTP auth on every request.
Step 2: Set Up Your Telegram Bot
- Message @BotFather on Telegram →
/newbot - Give it a name (e.g. "GENESIS Alerts")
- Copy the bot token:
1234567890:AABBccDDeeffGGHHiiJJkkLLmmNNoo - Message @userinfobot to get your numeric Chat ID
Step 3: Clone and Configure
git clone https://github.com/api2trade/Genesis-Metatrader-Automatic-AI-Trading-System.git
cd Genesis-Metatrader-Automatic-AI-Trading-System
# Option A: Interactive wizard (recommended)
bash setup.sh
# → Asks for each credential, verifies them live, writes .env
# Option B: Manual
cp .env.example .env
nano .env # Fill in the 6 required fields
The six required .env fields:
MT5_ACCOUNT_UUID=your-api2trade-session-uuid
MT5_ACCOUNT_ID=your-api2trade-session-uuid # same value, alias
MT5_API_USER=your_api2trade_username
MT5_API_PASS=your_api2trade_password
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
Step 4: Deploy with Docker (Local or VPS)
# Build and start
docker compose up -d
# Watch startup logs
docker logs -f genesis
# Expected output:
# ✓ MT5_ACCOUNT_UUID = a1b2c3d4••••
# ✓ Verifying API2TRADE connection...
# ✓ Connected | Balance: 10,000.00 USD [demo]
# ✓ 6 cron jobs installed
Step 5: Run Your First Scan
# Analyze EURUSD across all strategies
docker exec -it genesis genesis-scan EURUSDxx
# Expected output (example):
# === GENESIS FULL SCAN: EURUSDxx ===
# --- ares --- WAIT | Outside session GMT 07-21
# --- apollo --- WAIT | ADX 18.2 < min 20 (trending required)
# --- athena --- WAIT | Spread 1.8 > max 1.5 pips
# --- artemis --- WAIT | Only 3/7 Ichimoku conditions met
# --- zeus --- WAIT | No liquidity sweep detected in last 30 bars
# Or analyze a single strategy in detail
docker exec -it genesis ares analyze EURUSDxx
Step 6: VPS Production Deployment
For 24/7 autonomous trading, a VPS is essential. Minimum specs: 2 vCPU, 2GB RAM, Ubuntu 22.04. Recommended providers: Hetzner (€4/month), Contabo (€5/month), DigitalOcean (€8/month).
# On your VPS, after git clone + bash setup.sh:
bash install.sh
# What install.sh does:
# 1. Installs Python 3.11 and pip
# 2. Creates venv at /opt/hermes-agent/.venv-hermes
# 3. Installs all dependencies from requirements.txt
# 4. Creates CLI shortcuts: ares, apollo, athena, artemis, zeus, hephaestus, genesis-scan
# 5. Writes /etc/cron.d/genesis with the 6 cron jobs
# 6. Creates /var/log/hermes/ log directories
# 7. Sends a test Telegram message confirming the installation
Step 7: Customise a Strategy
Each strategy is a self-contained Python module. To modify ARES's signal thresholds without touching code:
# configs/ares_config.yaml
bollinger:
period: 20 # ← Change BB window
std_dev: 2.5 # ← Wider band = fewer but higher quality signals
rsi:
period: 14
oversold: 25 # ← Stricter oversold threshold (was 30)
overbought: 75 # ← Stricter overbought threshold
risk:
risk_pct: 0.01 # ← 1% per trade
max_spread_pips: 1.0
min_rr_ratio: 2.0 # ← Minimum 1:2 reward-to-risk
To write a completely new strategy:
- Copy
strategies/ares/tostrategies/mybot/ - Rename
ares_cycle.pytomybot_cycle.pyandares_tool.pytomybot_tool.py - Replace the
run_analysis()logic with your signal conditions - Keep the
bridge()function and the return format unchanged - Add your strategy to
genesis_autonomous.py's bot list
10. Infrastructure and Real Costs
Monthly Running Costs
| Service | Cost | What for |
|---|---|---|
| API2TRADE | €12/month | MT5 REST API — 1 account |
| VPS (Hetzner CX21) | €4.35/month | 2 vCPU, 4GB RAM, Ubuntu 22.04 |
| OpenAI (GPT-4o-mini) | ~€5–15/month | Hermes LLM brain — hourly cycles |
| Telegram Bot | Free | All trade alerts and reports |
| open.er-api.com | Free | Quote fallback for Forex pairs |
| yfinance | Free | OHLCV bars for all strategies |
| Total | ~€21–31/month | Full autonomous system |
Development Investment (AI Tokens)
GENESIS was built over approximately 3 weeks using AI-assisted development (Claude/Gemini models). The estimated token usage across all development conversations:
| Phase | Description | Approx. Tokens |
|---|---|---|
| Architecture design | System design, data flow decisions | ~200K |
| Strategy implementation | All 6 strategy bots coded + debugged | ~800K |
| Bridge removal refactor | Moving from local bridge to direct API | ~200K |
| Docker + deployment | Dockerfile, docker-compose, entrypoints | ~150K |
| Documentation + README | Case study, README, .env.example | ~200K |
| Debugging sessions | Auth issues, import paths, yfinance | ~250K |
| Total | ~1.8M tokens |
At current Claude/Gemini pricing (~$3–15 per million tokens depending on model), total AI-assisted development cost: approximately $5–27.
Compare this to hiring a professional quant developer at €100–200/hour to build the equivalent system. GENESIS represents roughly 200+ hours of equivalent development work.
11. Key Engineering Challenges and How We Solved Them
Challenge 1: The Local Bridge Was a Single Point of Failure
Problem: The original v1 used a FastAPI bridge at localhost:8000 that translated Python calls into the MetaTrader5 Python SDK. This meant three things had to be running simultaneously: the FastAPI bridge, the MT5 terminal, and the Python strategies. Any one crashing silently would stop all trading.
Solution: Remove the bridge entirely. Every *_cycle.py file now has a bridge() function that calls API2TRADE REST directly. The strategies became self-contained — each one can run independently without any other service.
# Before (v1) — required local bridge running
def bridge(path, data=None):
r = requests.post(f"http://127.0.0.1:8000{path}", json=data)
return r.json()
# After (v2) — direct REST call
def bridge(path, data=None):
r = requests.get(f"{MT5_API}/AccountSummary",
params={"id": MT5_ID}, auth=MT5_AUTH, timeout=10)
return r.json()
Challenge 2: Ichimoku Displacement — The Lookahead Trap
Problem: Every Ichimoku tutorial shows the cloud "shifted forward" visually, but when computing it in code, most implementations accidentally read the future cloud values at iloc[-1] instead of the current cloud projected at iloc[-(DISP+2)]. This creates a massive lookahead bias — the strategy "sees" cloud values that won't exist yet in real-time.
Solution: Explicit displacement indexing. The current Kumo boundaries are the SpanA/B values calculated DISP bars ago:
cloud_idx = -(DISP + 2) # DISP = 26 bars displacement
sa_current = float(span_a.iloc[cloud_idx]) # ← Correct
# NOT: span_a.iloc[-1] ← This is lookahead bias
Challenge 3: Yahoo Finance Rate Limiting Inside Docker
Problem: When testing GENESIS in Docker on a Mac, yf.download("EURUSD=X", ...) consistently returned empty DataFrames. Yahoo Finance blocks or rate-limits requests from Docker container IP ranges.
Solution: Three-tier quote fallback using free public APIs that don't rate-limit Docker IPs:
- API2TRADE
/Quote(broker's live feed) open.er-api.com(free ECB/central bank rates, no API key)Frankfurter.app(ECB reference rates, supports XAU/USD)
In production on a VPS, yfinance works normally. The fallback ensures correctness in all environments.
Challenge 4: iloc[-2] vs iloc[-1] — Forming Bar Lookahead
Problem: When downloading 1-minute bars, the last row (iloc[-1]) is the bar currently forming — it has incomplete data. Computing indicators on it means your signal conditions are evaluated against a partial candle that will change before the bar closes.
Solution: Every indicator computation uses iloc[-2] — the last fully closed bar. This is implemented consistently across all six strategies:
# Every strategy uses this pattern
bb_lower = float(ta.volatility.bollinger_lband(df["close"], window=20).iloc[-2])
rsi = float(ta.momentum.rsi(df["close"], window=14).iloc[-2])
# Never iloc[-1] for signal computation
Challenge 5: Symbol Format Differences Between Brokers
Problem: Different MT5 brokers use different symbol names. Exness uses EURUSDxx, Deriv uses EURUSD, IC Markets uses EURUSD, Pepperstone uses EURUSD.. A system hardcoded to EURUSDxx breaks silently on other brokers.
Solution: Configurable symbol suffixes in YAML configs, plus a YF_MAP dictionary that maps MT5 symbols to their yfinance equivalents regardless of broker suffix:
# configs/ares_config.yaml
mt5:
symbol_suffix: "xx" # Change to "" for brokers without suffix
YF_MAP = {
"EURUSDxx": "EURUSD=X",
"EURUSD": "EURUSD=X", # Works with any suffix variant
"EURUSD.": "EURUSD=X",
}
12. Live Test Results — Deriv Demo Account
We validated GENESIS v2.1 on a Deriv Demo account ($10,000 USD, 1:1000 leverage) via API2TRADE before the open-source release. Here is what actually happened:
API2TRADE Session Connection:
ConnectEx → 200 OK → Session UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
AccountSummary:
balance: $10,000.00 USD
equity: $10,000.00 USD
leverage: 1000:1
type: demo
method: Hedging
Full Five-Strategy Scan on EURUSDxx:
=== GENESIS FULL SCAN — Deriv Demo $10k ===
ARES → WAIT | Spread 1.50 pips > max 1.0 pips
APOLLO → WAIT | Spread 1.50 pips > max 1.5 pips
ATHENA → WAIT | Spread 1.50 pips > max 1.5 pips
ARTEMIS → WAIT | Insufficient H1 data (yfinance blocked in Docker)
ZEUS → WAIT | Insufficient M5 data (yfinance blocked in Docker)
Reading these results correctly:
-
ARES, APOLLO, ATHENA all reached the spread check gate — meaning they successfully: connected to API2TRADE, fetched account balance ($10k confirmed), checked for open positions, validated the trading session, and obtained a live price quote (via open.er-api.com fallback at 1.50 pip synthetic spread). The spread rejection is correct behaviour — ARES's max is 1.0 pip for M1 scalping.
-
ARTEMIS and ZEUS returned "Insufficient data" because yfinance is blocked from Docker on this test machine. On a VPS, yfinance returns full H1 and M5 data normally, and both strategies complete their full analysis.
In other words: The entire API2TRADE integration, authentication, account data, quote retrieval, and risk layer worked correctly. The "WAIT" decisions are not failures — they are the system correctly declining to trade based on real conditions.
13. What to Do Next
If you are reading this as a developer looking to build your own system:
Minimum viable starting point:
- Sign up at app.api2trade.com — get your session UUID
- Clone this repo and run
bash setup.sh - Run
docker exec -it genesis ares analyze EURUSDxx— confirm it connects to your account - Spend 1 week watching the strategy outputs on demo before enabling execution
If you want to customise the strategies:
- ARES's
ares_config.yamlis the cleanest to start with — changebb_period,std_dev, andrsi_oversoldthresholds and observe the effect on signal frequency - ZEUS is the most institutionally-aligned — if you're familiar with ICT concepts, this is the most interesting codebase to extend
If you want to scale across multiple accounts:
- API2TRADE supports multiple connected MT5 accounts — each gets its own session UUID
- You can run multiple
docker-composestacks with different.envfiles pointing to different accounts - Each additional account costs €12/month
The open-source repository includes everything: all six strategy implementations, all YAML configs, the autonomous engine, Hermes LLM brain, Docker deployment, VPS installer, and the interactive setup wizard. Fork it, adapt it, and build on top of it.
Get started: app.api2trade.com
GENESIS is open-source under GPL-3.0. Forks must also remain open source. Trading involves risk. Always test on a demo account before using real funds. Published by API2TRADE · https://app.api2trade.com

