3f763827f4
MCP server exposing MetaTrader 5 strategy development tools to AI assistants (Claude, Cursor, etc.) on macOS (CrossOver) and Linux (Wine). Tools: - run_backtest: full pipeline — compile EA, clean cache, backtest, parse HTML/XML report, analyze deals → metrics.json + analysis.json - run_optimization: background genetic optimization with nohup/disown, UTF-16LE .set file handling, OptMode reset - compile_ea: MQL5 compilation via MetaEditor with auto-detected include/ directory sync - get_backtest_status / get_optimization_status: job polling - verify_environment: Wine/MT5 path validation Analytics: - extract.py: MT5 HTML and SpreadsheetML XML report parser - analyze.py: deal-level analysis (drawdown events, grid depth, loss sequences, monthly P&L) → analysis.json - optimize_parser.py: optimization result parser with convergence analysis Platform support: - macOS CrossOver (GUI mode, no Xvfb needed) - Linux Wine + Xvfb (headless, CI/CD compatible) - Auto-detection of Wine executable and MT5 terminal paths
144 lines
5.6 KiB
Bash
Executable File
144 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# mqlcompile.sh — Compile an MQL5 Expert Advisor via MetaEditor (Wine/CrossOver)
|
|
#
|
|
# Usage:
|
|
# ./scripts/mqlcompile.sh <path/to/Expert.mq5>
|
|
#
|
|
# Output:
|
|
# Compiled .ex5 written to MT5_EXPERTS_DIR
|
|
# Exit 0 on success, 1 on compile errors
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/platform_detect.sh"
|
|
|
|
# ── Args ──────────────────────────────────────────────────────────────────────
|
|
SOURCE_FILE="${1:-}"
|
|
if [[ -z "$SOURCE_FILE" ]]; then
|
|
echo "Usage: $0 <path/to/Expert.mq5>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$SOURCE_FILE" ]]; then
|
|
echo "ERROR: Source file not found: $SOURCE_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SOURCE_FILE="$(realpath "$SOURCE_FILE")"
|
|
EXPERT_NAME="$(basename "$SOURCE_FILE" .mq5)"
|
|
|
|
# ── Resolve platform ──────────────────────────────────────────────────────────
|
|
resolve_platform
|
|
|
|
METAEDITOR="${MT5_DIR}/metaeditor64.exe"
|
|
if [[ ! -f "$METAEDITOR" ]]; then
|
|
echo "ERROR: metaeditor64.exe not found at: $METAEDITOR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ── Copy source to MT5 Experts source dir ────────────────────────────────────
|
|
# MetaEditor requires the source file to be inside the MT5 directory tree
|
|
MT5_SRC_DIR="${MT5_DIR}/MQL5/Experts"
|
|
mkdir -p "$MT5_SRC_DIR"
|
|
cp "$SOURCE_FILE" "${MT5_SRC_DIR}/${EXPERT_NAME}.mq5"
|
|
|
|
WINE_SRC_PATH="$(host_to_wine_path "${MT5_SRC_DIR}/${EXPERT_NAME}.mq5")"
|
|
|
|
# ── Sync .mqh include files to MT5 Include dir ───────────────────────────────
|
|
# Auto-detect include/ directory relative to source file. Searches up to 2
|
|
# levels above the source file for an include/ sibling directory.
|
|
# Layout supported:
|
|
# <project>/experts/EA.mq5 + <project>/include/<subdir>/*.mqh
|
|
# <project>/src/experts/EA.mq5 + <project>/src/include/<subdir>/*.mqh
|
|
_find_include_dir() {
|
|
local source_dir="$1"
|
|
local candidate
|
|
for candidate in "$source_dir" "$(dirname "$source_dir")" "$(dirname "$(dirname "$source_dir")")"; do
|
|
if [[ -d "${candidate}/include" ]]; then
|
|
echo "${candidate}/include"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
INCLUDE_BASE=""
|
|
INCLUDE_BASE=$(_find_include_dir "$(dirname "$SOURCE_FILE")") || true
|
|
|
|
if [[ -n "$INCLUDE_BASE" ]]; then
|
|
synced_total=0
|
|
# Sync each subdirectory under include/ → MQL5/Include/<subdir>/
|
|
while IFS= read -r -d '' subdir; do
|
|
dir_name="$(basename "$subdir")"
|
|
mt5_dest="${MT5_DIR}/MQL5/Include/${dir_name}"
|
|
rm -rf "$mt5_dest"
|
|
cp -r "$subdir" "$mt5_dest"
|
|
count=$(find "$mt5_dest" -name "*.mqh" | wc -l | tr -d ' ')
|
|
echo "[compile] Synced ${count} .mqh → Include/${dir_name}/"
|
|
synced_total=$((synced_total + count))
|
|
done < <(find "$INCLUDE_BASE" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
|
|
|
|
# Also sync any .mqh files directly in include/ (flat layout)
|
|
while IFS= read -r -d '' mqh; do
|
|
cp "$mqh" "${MT5_DIR}/MQL5/Include/"
|
|
synced_total=$((synced_total + 1))
|
|
done < <(find "$INCLUDE_BASE" -maxdepth 1 -name "*.mqh" -print0 2>/dev/null)
|
|
|
|
if [[ $synced_total -eq 0 ]]; then
|
|
echo "[compile] INFO: include/ found but contains no .mqh files — skipping sync"
|
|
else
|
|
echo "[compile] Synced ${synced_total} .mqh file(s) total"
|
|
fi
|
|
else
|
|
echo "[compile] INFO: No include/ directory found — skipping .mqh sync"
|
|
fi
|
|
|
|
# ── Set Wine prefix ───────────────────────────────────────────────────────────
|
|
WINE_PREFIX_DIR=$(dirname "$(dirname "$(dirname "$MT5_DIR")")")
|
|
export WINEPREFIX="$WINE_PREFIX_DIR"
|
|
export WINEDEBUG="-all"
|
|
|
|
# ── Run MetaEditor ────────────────────────────────────────────────────────────
|
|
echo "[compile] Compiling ${EXPERT_NAME}.mq5 ..."
|
|
LOG_FILE="$(mktemp /tmp/mqlcompile_XXXXXX.log)"
|
|
|
|
set +e
|
|
${MT5_ARCH} "${MT5_WINE}" "${METAEDITOR}" \
|
|
/compile:"${WINE_SRC_PATH}" \
|
|
/log:"${LOG_FILE}" \
|
|
2>/dev/null
|
|
WINE_EXIT=$?
|
|
set -e
|
|
|
|
# MetaEditor always exits 0 on macOS/Wine; check log for errors
|
|
ERRORS=0
|
|
WARNINGS=0
|
|
if [[ -f "$LOG_FILE" ]]; then
|
|
# Log may be UTF-16LE
|
|
LOG_TEXT=$(iconv -f UTF-16LE -t UTF-8 "$LOG_FILE" 2>/dev/null || cat "$LOG_FILE")
|
|
ERRORS=$(echo "$LOG_TEXT" | grep -cE "^.*error" || true)
|
|
WARNINGS=$(echo "$LOG_TEXT" | grep -cE "^.*warning" || true)
|
|
|
|
if [[ $ERRORS -gt 0 ]]; then
|
|
echo "[compile] FAILED: $ERRORS error(s), $WARNINGS warning(s)"
|
|
echo "$LOG_TEXT" | grep -E "error|warning" | head -20
|
|
rm -f "$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ── Verify .ex5 was produced ──────────────────────────────────────────────────
|
|
EX5_PATH="${MT5_SRC_DIR}/${EXPERT_NAME}.ex5"
|
|
if [[ ! -f "$EX5_PATH" ]]; then
|
|
echo "[compile] ERROR: .ex5 not produced. MetaEditor may have failed silently." >&2
|
|
[[ -f "$LOG_FILE" ]] && cat "$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
BINARY_SIZE=$(stat -f%z "$EX5_PATH" 2>/dev/null || stat -c%s "$EX5_PATH")
|
|
echo "[compile] OK: ${EXPERT_NAME}.ex5 (${BINARY_SIZE} bytes, ${WARNINGS} warning(s))"
|
|
|
|
rm -f "$LOG_FILE"
|
|
exit 0
|