Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/compile-ea.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Compile EA

# Compiles the MQL5 Expert Advisor with the real MetaEditor compiler so a PR
# that doesn't build can't be merged. Runs on a Windows runner (MetaEditor is
# a native Windows app) and fails the job on any compile error.

on:
push:
paths:
- "bulk-add-signals.mq5"
- ".github/workflows/compile-ea.yml"
pull_request:
paths:
- "bulk-add-signals.mq5"
- ".github/workflows/compile-ea.yml"
workflow_dispatch:

jobs:
compile:
runs-on: windows-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- name: Download MetaTrader 5 installer
shell: pwsh
run: |
$url = "https://download.mql5.com/cdn/web/metaquotes.software.corp/mt5/mt5setup.exe"
Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\mt5setup.exe"
Write-Host "Downloaded installer ($((Get-Item "$env:RUNNER_TEMP\mt5setup.exe").Length) bytes)"

- name: Install MetaTrader 5 + standard library
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$mt5 = "C:\Program Files\MetaTrader 5"
$me = "$mt5\MetaEditor64.exe"
$inc = "$mt5\MQL5\Include\Trade\Trade.mqh"

function Wait-ForPath($p, $minutes) {
$deadline = (Get-Date).AddMinutes($minutes)
while (-not (Test-Path -LiteralPath $p) -and (Get-Date) -lt $deadline) { Start-Sleep -Seconds 5 }
return (Test-Path -LiteralPath $p)
}

Start-Process -FilePath "$env:RUNNER_TEMP\mt5setup.exe" -ArgumentList "/auto" | Out-Null

# Wait for the compiler itself.
if (-not (Wait-ForPath $me 8)) { throw "MetaEditor not found - install did not complete" }
Write-Host "MetaEditor installed: $me"

# The installer normally lays down the MQL5 standard library too, but it
# writes files in stages - wait for the header the compile actually needs.
if (-not (Wait-ForPath $inc 4)) {
# Fallback: unpack it by launching the terminal once (guard on the exe
# existing first, so we never race on a not-yet-written terminal64.exe).
$term = "$mt5\terminal64.exe"
if (Wait-ForPath $term 4) {
Write-Host "Standard library missing; launching terminal to unpack it..."
$p = Start-Process -FilePath $term -ArgumentList "/portable" -WorkingDirectory $mt5 -PassThru
Wait-ForPath $inc 3 | Out-Null
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
}
}

if (-not (Test-Path -LiteralPath $inc)) { throw "MQL5 standard library not found at $inc" }
Write-Host "Standard library ready: $inc"

# Ensure no terminal process is holding files open before we compile.
Get-Process terminal64 -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue

- name: Compile
shell: pwsh
run: |
$mt5 = "C:\Program Files\MetaTrader 5"
$experts = "$mt5\MQL5\Experts"
$src = "$experts\bulk-add-signals.mq5"
$ex5 = "$experts\bulk-add-signals.ex5"
$log = "$env:RUNNER_TEMP\compile.log"

Copy-Item "bulk-add-signals.mq5" $src -Force
if (Test-Path $ex5) { Remove-Item $ex5 -Force }

# /portable makes MetaEditor resolve includes from the install-dir MQL5
# tree (where Trade.mqh is) instead of the empty roaming data directory.
Start-Process -FilePath "$mt5\MetaEditor64.exe" `
-ArgumentList "/portable", "/compile:`"$src`"", "/log:`"$log`"" -Wait

if (-not (Test-Path $log)) { throw "No compile log was produced" }
# MetaEditor writes the log as UTF-16.
$text = Get-Content -Path $log -Encoding Unicode -Raw
Write-Host "----------------- MetaEditor log -----------------"
Write-Host $text
Write-Host "--------------------------------------------------"

$m = [regex]::Match($text, "Result:\s*(\d+)\s*error")
$errors = if ($m.Success) { [int]$m.Groups[1].Value } else { 1 }

if ($errors -gt 0 -or -not (Test-Path $ex5)) {
throw "Compilation FAILED ($errors error(s)); see log above."
}
Write-Host "Compilation succeeded (0 errors)."

- name: Upload compiled .ex5
if: success()
uses: actions/upload-artifact@v4
with:
name: bulk-add-signals-ex5
path: C:\Program Files\MetaTrader 5\MQL5\Experts\bulk-add-signals.ex5
if-no-files-found: error
9 changes: 7 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@ COPY config.json .
# Expose API port
EXPOSE 8080

# Run the server
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
# Inside a container we must bind all interfaces. Set API_KEY (e.g. via
# docker-compose environment or --env) to require auth on the trading endpoints.
ENV HOST=0.0.0.0
ENV PORT=8080

# Run the server (no --reload in production)
CMD ["python", "server.py"]
34 changes: 26 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# MT5 Trade Split Manager 🤖

[![Compile EA](https://github.com/codedpro/mt5-trade-split-manager/actions/workflows/compile-ea.yml/badge.svg)](https://github.com/codedpro/mt5-trade-split-manager/actions/workflows/compile-ea.yml)
[![MetaTrader 5](https://img.shields.io/badge/MetaTrader-5-blue.svg)](https://www.metatrader5.com/)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![FastAPI](https://img.shields.io/badge/FastAPI-0.109.0-green.svg)](https://fastapi.tiangolo.com/)
Expand Down Expand Up @@ -34,7 +35,7 @@ System: Splits order → Manages positions → Returns status
- ✅ **Safe Shutdown Mode** - Protects positions when EA is offline
- ✅ **REST API Integration** - FastAPI server for external signal processing
- ✅ **TCP Socket Communication** - High-performance bidirectional communication
- ✅ **Smart Order Type Detection** - Automatically converts STOP/LIMIT based on market price
- ✅ **Order Type Safety** - Validates STOP/LIMIT (and SL/TP sides) against the live market and **rejects** wrong-side orders instead of silently reinterpreting your intent
- ✅ **Position Recovery** - Rebuilds tracking from existing orders on restart
- ✅ **Multi-Symbol Support** - Optimized for Gold (XAUUSD) and Silver (XAGUSD)
- ✅ **Risk Management** - Daily loss limits, max positions, spread checks
Expand Down Expand Up @@ -174,7 +175,7 @@ The EA automatically adjusts pip values based on symbol:

**When TP2 closes (45 pips profit):**
1. EA detects TP2 position is closed
2. Automatically moves SL to TP1 price (15 pips from entry)
2. Automatically moves SL to **your actual TP1 price** (the price you sent, not a fixed pip offset)
3. Remaining 30% of position (TP3, TP4, TP5) is now **risk-free**
4. Even if market reverses, you keep 15 pips profit on remaining positions

Expand Down Expand Up @@ -317,7 +318,7 @@ Place a new order with automatic splitting.

**Parameters:**
- `symbol` (string): Trading symbol ("XAUUSD" or "XAGUSD")
- `order_type` (string): "BUY_STOP" or "SELL_STOP"
- `order_type` (string): "BUY_STOP", "SELL_STOP", "BUY_LIMIT", or "SELL_LIMIT". The price must be on the correct side of the market for the type (e.g. BUY_STOP above the ask); wrong-side requests are rejected with an explanatory message rather than converted.
- `price` (float): Entry price
- `sl` (float): Stop loss price
- `tp_levels` (array): 5 take profit levels
Expand Down Expand Up @@ -489,6 +490,23 @@ Configure in MT5 when attaching EA to chart:
| `MaxSpreadPips` | 10 | Maximum allowed spread in pips |
| `MaxDailyLossPercent` | 5.0 | Stop trading if daily loss exceeds % |

### 🔒 Security

This API can place and close real trades, so it is locked down by default:

- **Localhost only** — the HTTP server binds to `127.0.0.1` unless you set `HOST=0.0.0.0`.
- **Optional API key** — set the `API_KEY` environment variable and every trading endpoint (everything except `/` and `/health`) requires a matching `X-API-Key` header:

```bash
API_KEY=your-secret HOST=0.0.0.0 python server.py

curl -X POST http://localhost:8080/order \
-H "X-API-Key: your-secret" -H "Content-Type: application/json" \
-d '{ ... }'
```

If you expose the server beyond localhost (e.g. Docker with `HOST=0.0.0.0`) **always** set `API_KEY`. The server prints a warning if you don't.

## 📦 Installation

### Option 1: Manual Installation
Expand Down Expand Up @@ -590,13 +608,13 @@ curl -X POST http://localhost:8080/safe-shutdown

**Symptoms:** All split orders fail immediately

**Cause:** Order type doesn't match price position relative to market
**Cause:** The order type doesn't match the price's position relative to the market.

**Solution:** The EA now auto-detects and converts:
- SELL STOP above market → SELL LIMIT
- BUY STOP below market → BUY LIMIT
**Solution:** The EA validates this and returns a clear message instead of silently changing your order. Pick the type that matches your intent:
- Entry **above** market → `BUY_STOP` (breakout) or `SELL_LIMIT` (fade)
- Entry **below** market → `SELL_STOP` (breakout) or `BUY_LIMIT` (dip-buy)

Recompile the EA with the latest code.
SL and TP prices must also be on the correct side of the entry, or the order is rejected. Recompile the EA with the latest code.

### Port Already in Use

Expand Down
Loading
Loading