Files
2026-07-11 02:42:55 +08:00

39 KiB
Raw Permalink Blame History

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

  1. What Was Actually Built
  2. Why REST Instead of a Local MT5 Terminal
  3. Core Architecture — How All Parts Connect
  4. The API2TRADE Integration — Every Real Endpoint Used
  5. The Six Strategy Gods — Real Implementation Deep Dive
  6. Hermes — The LLM Orchestration Brain
  7. Market Data — Multi-Source Fallback Architecture
  8. Risk Management — The Hard Layer
  9. How to Replicate This — Complete Step-by-Step
  10. Infrastructure and Real Costs
  11. Key Engineering Challenges and How We Solved Them
  12. Live Test Results — Deriv Demo Account
  13. 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:

  1. genesis_autonomous.py fires via cron
  2. It checks the live MT5 account balance and open positions via API2TRADE
  3. It runs each active strategy bot as a subprocess
  4. Each bot fetches market data (yfinance + API2TRADE), computes indicators, and evaluates its signal conditions
  5. If a bot returns "action": "trade", Hermes validates the risk limits
  6. If risk passes, OrderSendSafe fires directly at the MT5 account via API2TRADE REST
  7. 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

GENESIS Architecture 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

Strategy Overview 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:0021: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 28 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.

ICT Detection Pipeline

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:0010:00 GMT) and New York (13:0016: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

MT5 API Flow

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.

VPS Deployment

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 0721"
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

  1. Message @BotFather on Telegram → /newbot
  2. Give it a name (e.g. "GENESIS Alerts")
  3. Copy the bot token: 1234567890:AABBccDDeeffGGHHiiJJkkLLmmNNoo
  4. 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:

  1. Copy strategies/ares/ to strategies/mybot/
  2. Rename ares_cycle.py to mybot_cycle.py and ares_tool.py to mybot_tool.py
  3. Replace the run_analysis() logic with your signal conditions
  4. Keep the bridge() function and the return format unchanged
  5. 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) ~€515/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 ~€2131/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 (~$315 per million tokens depending on model), total AI-assisted development cost: approximately $527.

Compare this to hiring a professional quant developer at €100200/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:

  1. API2TRADE /Quote (broker's live feed)
  2. open.er-api.com (free ECB/central bank rates, no API key)
  3. 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:

  1. Sign up at app.api2trade.com — get your session UUID
  2. Clone this repo and run bash setup.sh
  3. Run docker exec -it genesis ares analyze EURUSDxx — confirm it connects to your account
  4. Spend 1 week watching the strategy outputs on demo before enabling execution

If you want to customise the strategies:

  • ARES's ares_config.yaml is the cleanest to start with — change bb_period, std_dev, and rsi_oversold thresholds 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-compose stacks with different .env files 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