Production-grade MT4 trade signal broadcaster. When you enter a trade on MT4, it broadcasts to Telegram, WhatsApp, webhooks, and copies to other MT4 accounts in under 500ms.
MT4 Monitor EA (HMAC signed)
↓
Go HTTP Server (VPS)
├── HMAC + timestamp verification (only your MT4 can send signals)
├── JWT admin auth
├── POST /signal → dedup → Postgres → Redis cache → job queue
├── GET /pending/{symbol} → Redis (receiver EAs poll this)
└── Admin API + Web Dashboard
↓
Go Worker (separate process)
├── Per-symbol sequential queues (ordered delivery)
├── Exponential backoff + jitter on failure
├── Dead letter queue after max retries
└── Stale job cleanup (crash recovery)
↓
Postgres (source of truth) + Redis (speed layer)
- Signal ingestion: Every POST to
/signalmust carry a valid HMAC-SHA256 signature. The EA signsticket_id:signal_type:symbol:timestampwith a shared secret. Requests older than 30 seconds are rejected (replay attack prevention). No valid signature = 401. - Admin API: JWT access + refresh tokens. 15-minute access token TTL. Refresh tokens are blocklisted on logout.
- API keys: SHA-256 hashed in DB, never stored plain. Supports active/suspended/revoked states. Rotation with overlap window.
- Rate limiting: Per-IP auth failure counter in Redis.
- Docker + Docker Compose
- Go 1.22+ (for local development)
cp .env.example .envEdit .env:
Generate SIGNAL_HMAC_SECRET:
openssl rand -hex 32Generate JWT_SECRET:
openssl rand -hex 32Generate ADMIN_PASSWORD_HASH (bcrypt of your chosen password):
# Install htpasswd (apache2-utils) or use this Go one-liner:
go run -mod=mod -ldflags="" <(cat <<'EOF'
package main
import ("fmt"; "golang.org/x/crypto/bcrypt"; "os")
func main() {
h, _ := bcrypt.GenerateFromPassword([]byte(os.Args[1]), 12)
fmt.Println(string(h))
}
EOF
) yourpasswordcd docker
docker compose up -dServer starts on port 8080. Admin dashboard at http://your-vps-ip:8080/dashboard
Monitor EA (your trading account):
- Open MT4 → Tools → Options → Expert Advisors
- Tick "Allow WebRequest for listed URL"
- Add your server URL:
http://your-vps-ip:8080/signal - Compile
mql4/monitor_ea.mq4in MetaEditor - Attach to any chart (it runs in background)
- Set
HMACSecretinput to matchSIGNAL_HMAC_SECRETin your.env
Receiver EA (copy account):
- Whitelist
http://your-vps-ip:8080in MT4 options - Compile
mql4/receiver_ea.mq4 - Issue an API key from the admin dashboard (scope:
copy:receive) - Attach EA to a chart for the symbol you want to copy
- Set
APIKeyandSymbolToWatchinputs
Open http://your-vps-ip:8080/dashboard, log in, then:
- API Keys → Issue Key → set owner + scopes
- Subscribers → Add Subscriber → pick channel + paste config
Telegram config: {"chat_id": "-1001234567890"}
Get chat_id by adding your bot to a group and calling getUpdates.
WhatsApp (Twilio) config: {"phone": "+237612345678"}
Webhook config: {"url": "https://your-server.com/hook", "secret": "optional-hmac-secret"}
MT4 copier config: {"symbols": ["EURUSD", "XAUUSD"]} (empty = all symbols)
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/login |
— | Returns access + refresh tokens |
| POST | /auth/refresh |
— | Refresh access token |
| POST | /auth/logout |
JWT | Revoke refresh token |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /signal |
HMAC headers | Ingest signal from monitor EA |
| GET | /pending/{symbol} |
X-API-Key | Poll latest signal (receiver EA) |
| Method | Path | Description |
|---|---|---|
| POST | /admin/keys |
Issue new API key |
| GET | /admin/keys |
List all keys |
| PATCH | /admin/keys/{id}/status |
Set active/revoked/suspended |
| POST | /admin/keys/{id}/rotate |
Rotate key with overlap window |
| POST | /admin/subscribers |
Add subscriber |
| GET | /admin/subscribers |
List subscribers |
| PATCH | /admin/subscribers/{id}/active |
Enable/disable subscriber |
| GET | /admin/metrics |
Dashboard metrics |
| GET | /admin/signals |
Signal history |
| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check (Postgres + Redis) |
POST /signal
X-Signal-Signature: <hmac-sha256-hex>
X-Signal-Timestamp: 2024-01-15T10:30:00Z
{
"ticket_id": 12345678,
"signal_type": "OPEN",
"symbol": "EURUSD",
"direction": "BUY",
"price": 1.08542,
"sl": 1.08200,
"tp": 1.09000,
"lot": 0.10,
"timestamp": "2024-01-15T10:30:00Z"
}Signal types: OPEN, MODIFY, CLOSE, PARTIAL
HMAC message format: ticket_id:signal_type:SYMBOL:timestamp
- Run multiple
serverinstances behind a load balancer (stateless, share Postgres + Redis) - Run multiple
workerinstances (each picks jobs from Redis queues) - Add new notification channels: implement
func(ctx, *signal.Job) errorininternal/notify/, register incmd/worker/main.go - Add new symbols: add to
watchedSymbolsincmd/worker/main.go
cmd/
server/main.go HTTP server entrypoint
worker/main.go Worker process entrypoint
internal/
auth/ HMAC verification, API key validation, JWT admin
signal/ Signal receive, dedup, persist, enqueue + pending poll
queue/ BRPOPLPUSH consumer, retry, dead letter, stale cleanup
store/ Postgres layer
cache/ Redis layer (two clients: critical + lru)
admin/ Admin HTTP handlers
config/ Env-based config
health/ Health check
notify/
telegram/ Telegram Bot API
whatsapp/ Twilio / CallMeBot
webhook/ HTTP webhook with HMAC signing
mt4copier/ MT4 receiver delivery log
migrations/
001_initial_schema.sql
mql4/
monitor_ea.mq4 Runs on your trading MT4
receiver_ea.mq4 Runs on copy MT4 accounts
web/
dashboard.html Admin web UI (single file)
docker/
docker-compose.yml
Dockerfile.server
Dockerfile.worker