feat: add Wine/MT5 debugging tools and update release workflow
- Add 9 debugging/diagnostics tools for Wine/MT5 crash investigation - Update server.json with v1.30.0 and MCP package config - Update README.md with 85 tools count and debugging section - Update docs/MCP_TOOLS.md documentation - Enhance release workflow with MCP packaging job - Clean up mcp-package directory (now built in CI)
This commit is contained in:
Generated
+1
-1
@@ -481,7 +481,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mt5-quant"
|
name = "mt5-quant"
|
||||||
version = "1.30.0"
|
version = "1.31.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mt5-quant"
|
name = "mt5-quant"
|
||||||
version = "1.30.0"
|
version = "1.31.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "MT5-Quant MCP Server - Exposes MT5 backtest and optimization tools via MCP"
|
description = "MT5-Quant MCP Server - Exposes MT5 backtest and optimization tools via MCP"
|
||||||
authors = ["masdevid <masdevid@example.com>"]
|
authors = ["masdevid <masdevid@example.com>"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# MT5-Quant
|
# MT5-Quant
|
||||||
|
|
||||||
**MCP server for MT5 strategy development on macOS/Linux.** 85 tools to compile, backtest, analyze, optimize, debug crashes, and manage MQL5 Expert Advisors — no Windows required.
|
**MCP server for MT5 strategy development on macOS/Linux.** 87 tools to compile, backtest, analyze, optimize, debug crashes, and manage MQL5 Expert Advisors — no Windows required.
|
||||||
|
|
||||||
```
|
```
|
||||||
You: "Backtest MyEA Jan-Mar, what caused the February drawdown?"
|
You: "Backtest MyEA Jan-Mar, what caused the February drawdown?"
|
||||||
@@ -62,18 +62,22 @@ The AI runs the full pipeline: compile → clean cache → backtest → extract
|
|||||||
| [VSCODE.md](docs/VSCODE.md) | VS Code setup |
|
| [VSCODE.md](docs/VSCODE.md) | VS Code setup |
|
||||||
| [ANTIGRAVITY.md](docs/ANTIGRAVITY.md) | Antigravity IDE setup |
|
| [ANTIGRAVITY.md](docs/ANTIGRAVITY.md) | Antigravity IDE setup |
|
||||||
| [CONFIG.md](docs/CONFIG.md) | Configuration reference |
|
| [CONFIG.md](docs/CONFIG.md) | Configuration reference |
|
||||||
| [TOOLS.md](docs/MCP_TOOLS.md) | All 75 tools documented |
|
| [TOOLS.md](docs/MCP_TOOLS.md) | All 87 tools documented |
|
||||||
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | Design and internals |
|
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | Design and internals |
|
||||||
| [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common issues |
|
| [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common issues |
|
||||||
| [REMOTE_AGENTS.md](docs/REMOTE_AGENTS.md) | Linux optimization agents |
|
| [REMOTE_AGENTS.md](docs/REMOTE_AGENTS.md) | Linux optimization agents |
|
||||||
|
|
||||||
## MCP Tools (75)
|
## MCP Tools (87)
|
||||||
|
|
||||||
### Core workflow
|
### Core workflow
|
||||||
|
|
||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `run_backtest` | Full pipeline: compile → clean → backtest → extract → analyze |
|
| `run_backtest` | Full pipeline: compile → clean → backtest → extract → analyze |
|
||||||
|
| `run_backtest_quick` | Quick backtest using pre-compiled EA (skip compile) |
|
||||||
|
| `run_backtest_only` | Backtest only - just extract raw trades, no analysis |
|
||||||
|
| `launch_backtest` | Fire-and-forget: launch MT5 backtest, poll for completion |
|
||||||
|
| `get_backtest_status` | Poll running backtest status (MT5 running, report found, elapsed time) |
|
||||||
| `run_optimization` | Genetic optimization (background, returns immediately) |
|
| `run_optimization` | Genetic optimization (background, returns immediately) |
|
||||||
| `get_optimization_results` | Parse optimization results after MT5 finishes |
|
| `get_optimization_results` | Parse optimization results after MT5 finishes |
|
||||||
| `analyze_report` | Read `analysis.json` from any report directory |
|
| `analyze_report` | Read `analysis.json` from any report directory |
|
||||||
@@ -119,7 +123,6 @@ Use these for targeted analysis, or `analyze_report` to run all at once.
|
|||||||
| Tool | Description |
|
| Tool | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `verify_setup` | Check Wine/MT5 paths, Wine version, and EA/set file counts |
|
| `verify_setup` | Check Wine/MT5 paths, Wine version, and EA/set file counts |
|
||||||
| `get_backtest_status` | Check live progress of a running backtest pipeline |
|
|
||||||
| `get_optimization_status` | Check live state of a background optimization job |
|
| `get_optimization_status` | Check live state of a background optimization job |
|
||||||
| `list_jobs` | All optimization jobs with compact status in one call |
|
| `list_jobs` | All optimization jobs with compact status in one call |
|
||||||
|
|
||||||
|
|||||||
+119
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Full input/output schemas for MT5-Quant tools.
|
Full input/output schemas for MT5-Quant tools.
|
||||||
|
|
||||||
> **Documentation Status:** This file documents 49 of 85 total tools. Missing:
|
> **Documentation Status:** This file documents 56 of 87 total tools. Missing:
|
||||||
> - `list_experts`, `list_indicators`, `list_scripts`
|
> - `list_experts`, `list_indicators`, `list_scripts`
|
||||||
> - `healthcheck`, `list_symbols`
|
> - `healthcheck`, `list_symbols`
|
||||||
> - Reports query: `search_reports`, `get_latest_report`, `list_reports`, `prune_reports`, `tail_log`, `get_report_by_id`, `get_reports_summary`, `get_best_reports`, `search_reports_by_tags`, `search_reports_by_date_range`, `search_reports_by_notes`, `get_reports_by_set_file`, `get_comparable_reports`
|
> - Reports query: `search_reports`, `get_latest_report`, `list_reports`, `prune_reports`, `tail_log`, `get_report_by_id`, `get_reports_summary`, `get_best_reports`, `search_reports_by_tags`, `search_reports_by_date_range`, `search_reports_by_notes`, `get_reports_by_set_file`, `get_comparable_reports`
|
||||||
@@ -145,6 +145,124 @@ Run a complete backtest pipeline: compile → clean cache → backtest → extra
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## `run_backtest_quick`
|
||||||
|
|
||||||
|
Quick backtest using pre-compiled EA: clean cache → backtest → extract → analyze.
|
||||||
|
|
||||||
|
**When to call:** When EA code hasn't changed and you just want to test different parameters or date ranges. Faster than `run_backtest` because it skips compilation.
|
||||||
|
|
||||||
|
### Input schema
|
||||||
|
|
||||||
|
Same as `run_backtest`, but `skip_compile` is automatically set to `true`.
|
||||||
|
|
||||||
|
### Output schema
|
||||||
|
|
||||||
|
Same as `run_backtest`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `run_backtest_only`
|
||||||
|
|
||||||
|
Backtest only: clean cache → backtest → extract. No analysis phase.
|
||||||
|
|
||||||
|
**When to call:** When you just need raw trade data (deals.csv) and don't need analytics. Fastest option for batch processing.
|
||||||
|
|
||||||
|
### Input schema
|
||||||
|
|
||||||
|
Same as `run_backtest`, but `skip_compile` and `skip_analyze` are automatically set to `true`.
|
||||||
|
|
||||||
|
### Output schema
|
||||||
|
|
||||||
|
Same as `run_backtest` but without `analysis_summary`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `launch_backtest`
|
||||||
|
|
||||||
|
Fire-and-forget mode: compile → clean → launch MT5 backtest, return immediately with job info.
|
||||||
|
|
||||||
|
**When to call:** When you want to launch a backtest without waiting for completion. Use `get_backtest_status` to poll for completion.
|
||||||
|
|
||||||
|
### Input schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
expert: string; // Required. EA name without path or extension
|
||||||
|
symbol?: string; // Trading symbol (default: from config or first available)
|
||||||
|
from_date?: string; // Start date YYYY.MM.DD (default: past complete month)
|
||||||
|
to_date?: string; // End date YYYY.MM.DD (default: past complete month)
|
||||||
|
timeframe?: string; // M1, M5, M15, M30, H1, H4, D1 (default: M5)
|
||||||
|
deposit?: number; // Initial deposit (default: 10000)
|
||||||
|
model?: 0 | 1 | 2; // Tick model (default: 0)
|
||||||
|
set_file?: string; // Path to .set parameter file
|
||||||
|
skip_compile?: boolean; // Skip compilation
|
||||||
|
skip_clean?: boolean; // Skip cache cleaning
|
||||||
|
timeout?: number; // Max time in seconds (default: 900)
|
||||||
|
gui?: boolean; // Enable MT5 visualization
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
success: true;
|
||||||
|
message: string; // "Backtest launched successfully..."
|
||||||
|
report_id: string; // e.g., "20250122_034455_MyEA_XAUUSD_M5"
|
||||||
|
report_dir: string; // Full path to report directory
|
||||||
|
expert: string;
|
||||||
|
symbol: string;
|
||||||
|
timeframe: string;
|
||||||
|
launched_at: string; // ISO8601 timestamp
|
||||||
|
timeout_seconds: number;
|
||||||
|
poll_hint: string; // "Call get_backtest_status with report_dir to check progress"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `get_backtest_status`
|
||||||
|
|
||||||
|
Check progress of a running backtest pipeline launched via `launch_backtest`.
|
||||||
|
|
||||||
|
**When to call:** Poll periodically after calling `launch_backtest` to check completion status.
|
||||||
|
|
||||||
|
### Input schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
report_dir: string; // Report directory path from launch_backtest output
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output schema
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
success: true;
|
||||||
|
report_dir: string;
|
||||||
|
status: "completed" | "running" | "failed" | "in_progress" | "not_started";
|
||||||
|
stage: string; // Current pipeline stage: COMPILE, CLEAN, BACKTEST, EXTRACT, ANALYZE, DONE
|
||||||
|
is_complete: boolean; // True if backtest finished successfully
|
||||||
|
mt5_running: boolean; // Whether MT5 process is active
|
||||||
|
report_found: boolean; // Whether report file exists
|
||||||
|
metrics_extracted: boolean;
|
||||||
|
deals_extracted: boolean;
|
||||||
|
elapsed_seconds: number; // Time since launch
|
||||||
|
message: string; // Human-readable status message
|
||||||
|
job?: {
|
||||||
|
report_id: string;
|
||||||
|
expert: string;
|
||||||
|
symbol: string;
|
||||||
|
timeframe: string;
|
||||||
|
launched_at: string;
|
||||||
|
timeout_seconds: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## `run_optimization`
|
## `run_optimization`
|
||||||
|
|
||||||
Launch genetic parameter optimization as a detached background process.
|
Launch genetic parameter optimization as a detached background process.
|
||||||
|
|||||||
Binary file not shown.
@@ -1,247 +0,0 @@
|
|||||||
# MT5-Quant
|
|
||||||
|
|
||||||
**MCP server for MT5 strategy development on macOS/Linux.** 85 tools to compile, backtest, analyze, optimize, debug crashes, and manage MQL5 Expert Advisors — no Windows required.
|
|
||||||
|
|
||||||
```
|
|
||||||
You: "Backtest MyEA Jan-Mar, what caused the February drawdown?"
|
|
||||||
|
|
||||||
Claude: [compile → clean → backtest → analyze 1,847 deals]
|
|
||||||
→ Feb 14: BUY grid at L6, locking lot 1.75× base
|
|
||||||
→ Cutloss fired 17 points later
|
|
||||||
→ Recommendation: cap locking multiplier to ≤1.2×
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why MT5-Quant
|
|
||||||
|
|
||||||
| | MT5-Quant | Other MT5 MCPs | QuantConnect |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Backtest pipeline | ✅ Full | ❌ | Cloud only |
|
|
||||||
| Deal-level analytics | ✅ 15+ dims | ❌ | ❌ |
|
|
||||||
| MQL5 compilation | ✅ | ❌ | ❌ |
|
|
||||||
| macOS/Linux native | ✅ | Windows only | Cloud |
|
|
||||||
| Optimization | ✅ Background | ❌ | ✅ Paid |
|
|
||||||
| Crash debugging | ✅ Wine/MT5 diagnostics | ❌ | ❌ |
|
|
||||||
|
|
||||||
## Quick Install
|
|
||||||
|
|
||||||
### 1. Download & Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -L -o mt5.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-macos-arm64.tar.gz
|
|
||||||
tar -xzf mt5.tar.gz
|
|
||||||
bash scripts/setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Register MCP Server
|
|
||||||
|
|
||||||
| Platform | Command / Config | Docs |
|
|
||||||
|----------|------------------|------|
|
|
||||||
| **Claude Code** | `claude mcp add mt5-quant -- $(pwd)/mt5-quant` | [Setup →](docs/QUICKSTART.md) |
|
|
||||||
| **Windsurf** | Edit `~/.codeium/windsurf/mcp_config.json` | [WINDSURF.md →](docs/WINDSURF.md) |
|
|
||||||
| **Cursor** | Edit `~/.cursor/mcp.json` or use Settings → MCP | [CURSOR.md →](docs/CURSOR.md) |
|
|
||||||
| **VS Code** | Edit `.vscode/mcp.json` or run `MCP: Add Server` | [VSCODE.md →](docs/VSCODE.md) |
|
|
||||||
| **Antigravity** | Agent Panel → ... → MCP Servers → Edit configuration | [ANTIGRAVITY.md →](docs/ANTIGRAVITY.md) |
|
|
||||||
|
|
||||||
> **Note:** Use absolute paths like `/Users/name/mt5-quant/mt5-quant` or `$(pwd)/mt5-quant`, not relative paths like `./mt5-quant`.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```
|
|
||||||
Run a backtest on MyEA from 2025.01.01 to 2025.03.31
|
|
||||||
```
|
|
||||||
|
|
||||||
The AI runs the full pipeline: compile → clean cache → backtest → extract → analyze.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
| Doc | Purpose |
|
|
||||||
|-----|---------|
|
|
||||||
| [QUICKSTART.md](docs/QUICKSTART.md) | Complete setup for macOS/Linux |
|
|
||||||
| [WINDSURF.md](docs/WINDSURF.md) | Windsurf IDE setup |
|
|
||||||
| [CURSOR.md](docs/CURSOR.md) | Cursor IDE setup |
|
|
||||||
| [VSCODE.md](docs/VSCODE.md) | VS Code setup |
|
|
||||||
| [ANTIGRAVITY.md](docs/ANTIGRAVITY.md) | Antigravity IDE setup |
|
|
||||||
| [CONFIG.md](docs/CONFIG.md) | Configuration reference |
|
|
||||||
| [TOOLS.md](docs/MCP_TOOLS.md) | All 75 tools documented |
|
|
||||||
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | Design and internals |
|
|
||||||
| [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Common issues |
|
|
||||||
| [REMOTE_AGENTS.md](docs/REMOTE_AGENTS.md) | Linux optimization agents |
|
|
||||||
|
|
||||||
## MCP Tools (75)
|
|
||||||
|
|
||||||
### Core workflow
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `run_backtest` | Full pipeline: compile → clean → backtest → extract → analyze |
|
|
||||||
| `run_optimization` | Genetic optimization (background, returns immediately) |
|
|
||||||
| `get_optimization_results` | Parse optimization results after MT5 finishes |
|
|
||||||
| `analyze_report` | Read `analysis.json` from any report directory |
|
|
||||||
| `compare_baseline` | Compare report vs baseline, return winner/loser verdict |
|
|
||||||
| `compile_ea` | Compile MQL5 EA via MetaEditor |
|
|
||||||
| `list_experts` | List all EAs in MQL5/Experts directory |
|
|
||||||
| `list_indicators` | List all indicators in MQL5/Indicators directory |
|
|
||||||
| `list_scripts` | List all scripts in MQL5/Scripts directory |
|
|
||||||
| `healthcheck` | Quick server health check |
|
|
||||||
|
|
||||||
### Granular Analytics (individual analysis)
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `analyze_monthly_pnl` | Monthly P/L breakdown only |
|
|
||||||
| `analyze_drawdown_events` | Drawdown events and causes only |
|
|
||||||
| `analyze_top_losses` | Worst losing deals only |
|
|
||||||
| `analyze_loss_sequences` | Consecutive loss patterns only |
|
|
||||||
| `analyze_position_pairs` | Position hold time and P/L pairs |
|
|
||||||
| `analyze_direction_bias` | Buy vs Sell performance |
|
|
||||||
| `analyze_streaks` | Win/loss streak analysis |
|
|
||||||
| `analyze_concurrent_peak` | Peak simultaneous positions |
|
|
||||||
|
|
||||||
Use these for targeted analysis, or `analyze_report` to run all at once.
|
|
||||||
|
|
||||||
### Deal-Level Analytics (New)
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `list_deals` | List individual deals with filters (type, profit range, volume, dates) |
|
|
||||||
| `search_deals_by_comment` | Full-text search in deal comments (e.g., "Layer #3") |
|
|
||||||
| `search_deals_by_magic` | Filter deals by EA magic number |
|
|
||||||
| `analyze_profit_distribution` | Profit histogram: small/medium/large wins and losses |
|
|
||||||
| `analyze_time_performance` | Performance by hour of day and day of week |
|
|
||||||
| `analyze_hold_time_distribution` | Hold time buckets + correlation with profit |
|
|
||||||
| `analyze_layer_performance` | Grid/martingale layer analysis from comments |
|
|
||||||
| `analyze_volume_vs_profit` | Volume correlation + performance by lot size |
|
|
||||||
| `analyze_costs` | Commission and swap impact on profitability |
|
|
||||||
| `analyze_efficiency` | Profit per hour/day, annualized return, trade frequency |
|
|
||||||
|
|
||||||
### Monitoring
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `verify_setup` | Check Wine/MT5 paths, Wine version, and EA/set file counts |
|
|
||||||
| `get_backtest_status` | Check live progress of a running backtest pipeline |
|
|
||||||
| `get_optimization_status` | Check live state of a background optimization job |
|
|
||||||
| `list_jobs` | All optimization jobs with compact status in one call |
|
|
||||||
|
|
||||||
### Reports & logs
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `list_reports` | Compact table of all runs with key metrics — no full analysis needed |
|
|
||||||
| `get_latest_report` | Get most recent report with optional equity chart |
|
|
||||||
| `search_reports` | Find reports by EA, symbol, date range, or profit criteria |
|
|
||||||
| `get_report_by_id` | Get specific report by ID with equity chart |
|
|
||||||
| `get_reports_summary` | Aggregate stats: counts, averages, pass rates |
|
|
||||||
| `get_best_reports` | Top N reports sorted by any metric (profit factor, drawdown, etc.) |
|
|
||||||
| `search_reports_by_tags` | Find reports by tags |
|
|
||||||
| `search_reports_by_date_range` | Query by backtest date range |
|
|
||||||
| `search_reports_by_notes` | Full-text search in report notes |
|
|
||||||
| `get_reports_by_set_file` | Find all reports using a specific .set file |
|
|
||||||
| `get_comparable_reports` | Find comparable reports (same EA/symbol/timeframe) |
|
|
||||||
| `tail_log` | Read last N lines of any log; `filter=errors` to see only failures |
|
|
||||||
| `prune_reports` | Delete old report directories, keep last N (skips `_opt` dirs) |
|
|
||||||
|
|
||||||
### History & baseline
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `archive_report` | Convert one report dir → compact JSON entry in `backtest_history.json`, optionally delete source |
|
|
||||||
| `archive_all_reports` | Bulk-archive all report dirs then optionally delete them; keeps N newest safe |
|
|
||||||
| `get_history` | Query history with filters (EA, symbol, verdict, profit, DD) and sort options |
|
|
||||||
| `annotate_history` | Attach verdict / notes / tags to any history entry |
|
|
||||||
| `promote_to_baseline` | Write a history entry or report to `baseline.json` for compare_baseline |
|
|
||||||
|
|
||||||
### Cache management
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `cache_status` | MT5 tester cache size breakdown by symbol — check before cleaning |
|
|
||||||
| `clean_cache` | Delete tester cache files; supports per-symbol and `dry_run` |
|
|
||||||
|
|
||||||
### Pre-flight & Validation
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `get_active_account` | Get current MT5 account session (login, server, available symbols) |
|
|
||||||
| `check_symbol_data_status` | Validate symbol has sufficient history data for date range |
|
|
||||||
| `check_mt5_status` | Check if MT5 terminal is installed and ready |
|
|
||||||
| `validate_ea_syntax` | Pre-compile syntax check without running full compilation |
|
|
||||||
|
|
||||||
### Debugging & Diagnostics (New)
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `diagnose_wine` | Check Wine installation, version, and prefix health |
|
|
||||||
| `get_mt5_logs` | Get MT5 terminal, tester, or MetaEditor logs with filtering |
|
|
||||||
| `search_mt5_errors` | Search logs for error patterns (crash, exception, access violation) |
|
|
||||||
| `check_mt5_process` | Check if MT5 processes are running, get PID, CPU, memory usage |
|
|
||||||
| `kill_mt5_process` | Kill stuck MT5 processes (force=true for wineserver) |
|
|
||||||
| `check_system_resources` | Check disk space, memory, CPU availability |
|
|
||||||
| `validate_mt5_config` | Validate terminal.ini and tester configuration files |
|
|
||||||
| `get_wine_prefix_info` | Get Wine prefix details: Windows version, installed programs, registry |
|
|
||||||
| `get_backtest_crash_info` | Investigate backtest failures: incomplete markers, missing deals.csv, errors |
|
|
||||||
|
|
||||||
### Project Management
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `init_project` | Scaffold new MQL5 project with templates (scalper/swing/grid/basic) |
|
|
||||||
| `create_set_template` | Generate .set parameter file from EA input variables |
|
|
||||||
| `export_report` | Export backtest report to CSV, JSON, or Markdown |
|
|
||||||
|
|
||||||
### History & Comparison
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `get_backtest_history` | List all backtests for EA/symbol with summary metrics |
|
|
||||||
| `compare_backtests` | Compare 2+ backtest results side-by-side with analysis |
|
|
||||||
|
|
||||||
### .set file — read / write
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `list_set_files` | All .set files in tester profiles dir with sweep stats and combination counts |
|
|
||||||
| `read_set_file` | Parse UTF-16LE `.set` file → structured JSON params |
|
|
||||||
| `write_set_file` | Write full params dict → UTF-16LE `.set` with `chmod 444` |
|
|
||||||
| `patch_set_file` | Update specific params in-place, return diff — replaces read→edit→write |
|
|
||||||
| `clone_set_file` | Copy `.set` to new path with optional overrides in one call |
|
|
||||||
|
|
||||||
### .set file — analysis & generation
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `describe_sweep` | Swept params, value counts, and total optimization combinations |
|
|
||||||
| `diff_set_files` | Side-by-side diff of two `.set` files — only changed params returned |
|
|
||||||
| `set_from_optimization` | Generate a clean backtest `.set` from `get_optimization_results` params; optionally narrow sweep |
|
|
||||||
|
|
||||||
### Search & Discovery
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `search_experts` | Search EAs by name pattern across all directories |
|
|
||||||
| `search_indicators` | Search indicators by name pattern |
|
|
||||||
| `search_scripts` | Search scripts by name pattern |
|
|
||||||
| `copy_indicator_to_project` | Copy indicator to project directory |
|
|
||||||
| `copy_script_to_project` | Copy script to project directory |
|
|
||||||
|
|
||||||
Full schema: [docs/MCP_TOOLS.md](docs/MCP_TOOLS.md)
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
Run `verify_setup` from Claude first — it checks all paths and returns actionable hints.
|
|
||||||
|
|
||||||
For crashes or unexplained failures during backtest/compile/optimization:
|
|
||||||
- `diagnose_wine` — Check Wine installation and prefix health
|
|
||||||
- `search_mt5_errors` — Find crash causes in logs
|
|
||||||
- `check_mt5_process` + `kill_mt5_process` — Detect and kill stuck processes
|
|
||||||
- `get_backtest_crash_info` — Investigate failed backtest reports
|
|
||||||
|
|
||||||
**[Full Troubleshooting Guide →](docs/TROUBLESHOOTING.md)**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
---
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
; MT5 optimization .set file — example
|
|
||||||
; Format: param=current_value||start||step||stop||Y (Y=sweep this param)
|
|
||||||
; param=current_value||N (N=fixed, no sweep)
|
|
||||||
;
|
|
||||||
; MT5-Quant handles encoding automatically:
|
|
||||||
; - Converts to UTF-16LE (MT5 strips ||Y flags from UTF-8 files)
|
|
||||||
; - Sets read-only flag after writing
|
|
||||||
; - Resets OptMode in terminal.ini before launch
|
|
||||||
;
|
|
||||||
; Copy to your project dir and point --set to it.
|
|
||||||
|
|
||||||
; ── Entry parameters (sweep) ─────────────────────────────────────────────────
|
|
||||||
Min_Entry_Confidence=0.610||0.580||0.010||0.650||Y
|
|
||||||
Max_Entry_Spread=30||10||5||50||Y
|
|
||||||
|
|
||||||
; ── Take-profit and stop-loss (sweep) ────────────────────────────────────────
|
|
||||||
TP_Pips=400||300||50||600||Y
|
|
||||||
SL_Pips=800||600||100||1200||Y
|
|
||||||
|
|
||||||
; ── Grid / martingale settings (fixed) ───────────────────────────────────────
|
|
||||||
MaxLayers=6||N
|
|
||||||
LotMultiplier=1.5||N
|
|
||||||
GridStep_Pips=150||N
|
|
||||||
|
|
||||||
; ── Risk management (fixed) ──────────────────────────────────────────────────
|
|
||||||
Max_DD_Percent=15.0||N
|
|
||||||
CutLoss_Percent=25.0||N
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# mt5-quant configuration (flat key format — no YAML nesting)
|
|
||||||
# Copy this file to config/mt5-quant.yaml and fill in your paths.
|
|
||||||
|
|
||||||
# ── MT5 / Wine paths ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
# macOS (CrossOver / MetaTrader 5.app)
|
|
||||||
wine_executable: "/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64"
|
|
||||||
terminal_dir: "$HOME/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5"
|
|
||||||
experts_dir: "$HOME/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5/MQL5/Experts"
|
|
||||||
tester_profiles_dir: "$HOME/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5/MQL5/Profiles/Tester"
|
|
||||||
tester_cache_dir: "$HOME/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5/Tester"
|
|
||||||
|
|
||||||
# Linux (Wine)
|
|
||||||
# wine_executable: "/usr/bin/wine64"
|
|
||||||
# terminal_dir: "$HOME/.wine/drive_c/Program Files/MetaTrader 5"
|
|
||||||
# experts_dir: "$HOME/.wine/drive_c/Program Files/MetaTrader 5/MQL5/Experts"
|
|
||||||
# tester_profiles_dir: "$HOME/.wine/drive_c/Program Files/MetaTrader 5/MQL5/Profiles/Tester"
|
|
||||||
# tester_cache_dir: "$HOME/.wine/drive_c/Program Files/MetaTrader 5/Tester"
|
|
||||||
|
|
||||||
# ── Display ───────────────────────────────────────────────────────────────────
|
|
||||||
# auto: GUI on macOS CrossOver, Xvfb on Linux without $DISPLAY
|
|
||||||
# gui: always use display (requires active session)
|
|
||||||
# headless: always use Xvfb (requires xvfb-run)
|
|
||||||
display_mode: auto
|
|
||||||
|
|
||||||
# ── Project ───────────────────────────────────────────────────────────────────
|
|
||||||
# Root directory of your EA project (where .mq5 and .set files live)
|
|
||||||
project_dir: "/path/to/your/ea/project"
|
|
||||||
|
|
||||||
# ── Backtest defaults ─────────────────────────────────────────────────────────
|
|
||||||
# These are fallbacks when not passed as MCP tool arguments
|
|
||||||
backtest_symbol: "EURUSD"
|
|
||||||
backtest_deposit: 10000
|
|
||||||
backtest_currency: USD
|
|
||||||
backtest_leverage: 100
|
|
||||||
backtest_model: 0
|
|
||||||
backtest_timeframe: H1
|
|
||||||
backtest_timeout: 900
|
|
||||||
|
|
||||||
# ── Optimization ──────────────────────────────────────────────────────────────
|
|
||||||
opt_log_dir: /tmp
|
|
||||||
opt_min_agents: 1
|
|
||||||
|
|
||||||
# ── Reports output ────────────────────────────────────────────────────────────
|
|
||||||
reports_dir: "/path/to/your/ea/project/reports"
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# mt5-quant configuration — generated by scripts/setup.sh
|
|
||||||
# Re-run scripts/setup.sh to regenerate, or edit manually.
|
|
||||||
|
|
||||||
mt5:
|
|
||||||
# Path to Wine / CrossOver wine64 binary
|
|
||||||
wine_executable: "/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64"
|
|
||||||
|
|
||||||
# MT5 installation directory (inside the Wine prefix)
|
|
||||||
terminal_dir: "/Users/masdevid/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5"
|
|
||||||
|
|
||||||
# Where MT5 looks for Expert Advisor binaries (.ex5)
|
|
||||||
experts_dir: "/Users/masdevid/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5/MQL5/Experts"
|
|
||||||
|
|
||||||
# Where MT5 reads/writes .set files during backtests
|
|
||||||
tester_profiles_dir: "/Users/masdevid/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5/MQL5/Profiles/Tester"
|
|
||||||
|
|
||||||
# MT5 tester cache directory (cleared before each run)
|
|
||||||
tester_cache_dir: "/Users/masdevid/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5/Tester"
|
|
||||||
|
|
||||||
display:
|
|
||||||
# headless: true → Xvfb (Linux only)
|
|
||||||
# headless: false → GUI visible
|
|
||||||
# headless: auto → macOS=GUI, Linux with $DISPLAY=GUI, Linux without=Xvfb
|
|
||||||
mode: auto
|
|
||||||
|
|
||||||
# Virtual display for Xvfb (Linux headless only)
|
|
||||||
xvfb_display: ":99"
|
|
||||||
xvfb_screen: "1024x768x16"
|
|
||||||
|
|
||||||
backtest:
|
|
||||||
symbol: "XAUUSD"
|
|
||||||
deposit: 10000
|
|
||||||
currency: "USD"
|
|
||||||
leverage: 500
|
|
||||||
model: 0 # 0=every tick (required for martingale/grid EAs)
|
|
||||||
timeframe: "M5"
|
|
||||||
timeout: 900 # seconds per backtest run
|
|
||||||
|
|
||||||
optimization:
|
|
||||||
log_dir: "/tmp"
|
|
||||||
min_agents: 1
|
|
||||||
|
|
||||||
reports:
|
|
||||||
output_dir: "./reports"
|
|
||||||
keep_last: 20
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
# Antigravity MCP Integration Setup
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### Option 1: Download Prebuilt Binary (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (Apple Silicon)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-macos-arm64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
|
|
||||||
# Linux (x64)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-linux-x64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Build from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure Antigravity
|
|
||||||
|
|
||||||
### Step 1: Open MCP Manager
|
|
||||||
|
|
||||||
1. Launch Antigravity
|
|
||||||
2. Look at the **right-side Agent Panel**
|
|
||||||
3. Click the **"..."** (More Options) menu at the top
|
|
||||||
4. Select **MCP Servers**
|
|
||||||
|
|
||||||
### Step 2: Access Configuration
|
|
||||||
|
|
||||||
1. Click **Manage MCP Servers**
|
|
||||||
2. Click **View raw config** or **Edit configuration**
|
|
||||||
3. This opens `mcp_config.json`
|
|
||||||
|
|
||||||
### Step 3: Add mt5-quant Configuration
|
|
||||||
|
|
||||||
Add to `mcp_config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/absolute/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Reload and Verify
|
|
||||||
|
|
||||||
1. Save the file
|
|
||||||
2. **Restart Antigravity** (or reload the window)
|
|
||||||
3. Open the Agent chat and type: `What tools do you have access to?`
|
|
||||||
4. The agent should list MT5 tools like `verify_setup`, `run_backtest`, etc.
|
|
||||||
|
|
||||||
## Full Example Configuration
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/Users/name/mt5-quant/target/release/mt5-quant"
|
|
||||||
},
|
|
||||||
"github": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
||||||
"env": {
|
|
||||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
Antigravity supports `${VAR_NAME}` syntax for environment variable substitution:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/Users/name/mt5-quant/target/release/mt5-quant",
|
|
||||||
"env": {
|
|
||||||
"CUSTOM_VAR": "${env:MY_VAR}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This keeps secrets out of the config file.
|
|
||||||
|
|
||||||
## Verify Setup
|
|
||||||
|
|
||||||
In Antigravity chat, type:
|
|
||||||
|
|
||||||
```
|
|
||||||
Run verify_setup
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
Wine: /Applications/MetaTrader 5.app/.../wine64
|
|
||||||
MT5 dir: ~/Library/Application Support/.../MetaTrader 5
|
|
||||||
Display: gui
|
|
||||||
Arch: arch -x86_64
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### "Connection Refused" or "Tool not found"
|
|
||||||
|
|
||||||
1. Double-check the server path in `mcp_config.json`
|
|
||||||
2. Ensure the binary has execute permissions: `chmod +x /path/to/mt5-quant`
|
|
||||||
3. Try completely restarting Antigravity
|
|
||||||
4. Check the server is listed in MCP manager
|
|
||||||
|
|
||||||
### "Stdio Error" or JSON Parsing Error
|
|
||||||
|
|
||||||
1. Verify the JSON syntax in `mcp_config.json`
|
|
||||||
2. Use a JSON validator if needed
|
|
||||||
3. Ensure no trailing commas
|
|
||||||
|
|
||||||
### Agent Hallucinating Tool Parameters
|
|
||||||
|
|
||||||
If the agent makes up incorrect parameters:
|
|
||||||
1. Check the tool is actually available: "List your available tools"
|
|
||||||
2. Restart the agent session
|
|
||||||
3. Be explicit in your requests
|
|
||||||
|
|
||||||
## Configuration Location
|
|
||||||
|
|
||||||
| Platform | Path |
|
|
||||||
|----------|------|
|
|
||||||
| All | Via UI: Agent Panel → ... → MCP Servers → Manage → Edit configuration |
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Antigravity Documentation](https://docs.antigravity.dev/)
|
|
||||||
- [MCP Server Reference](https://github.com/modelcontextprotocol/servers)
|
|
||||||
- [MT5-Quant Tools Reference](./MCP_TOOLS.md)
|
|
||||||
@@ -1,458 +0,0 @@
|
|||||||
# Architecture Deep Dive
|
|
||||||
|
|
||||||
## Design Philosophy
|
|
||||||
|
|
||||||
**Deal-level over aggregate.** MT5's built-in HTML report gives you: profit, profit factor, max DD%, trade count. That's it. You cannot tell from those numbers whether a drawdown was caused by overleveraged locking, a bad entry during a news spike, or a grid that reached L8 in a trending market.
|
|
||||||
|
|
||||||
MT5-Quant extracts every individual deal — entry price, exit price, P/L, comment string — and reconstructs what was happening when each loss event occurred. The `analysis.json` artifact is the result: AI-readable, stable schema, diffable between runs.
|
|
||||||
|
|
||||||
**Pipeline idempotency.** MT5 caches aggressively. Cached `.ex5` binaries, cached `.set` files, stale `terminal.ini` flags. The pipeline exists specifically to invalidate all of these before every run. A backtest result that came from a cached EA binary is wrong in a way that's impossible to detect without the cache clear step.
|
|
||||||
|
|
||||||
**Background isolation for optimization.** Genetic optimizations run 2-6 hours. Running them inside a parent process that can be killed (Claude task runner, SSH session, terminal) corrupts the MT5 optimization state. The only correct pattern is `nohup + disown` — fully detached from all parent process trees.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Component Map
|
|
||||||
|
|
||||||
```
|
|
||||||
MT5-Quant/
|
|
||||||
├── src/
|
|
||||||
│ ├── main.rs # MCP server entry (stdio transport)
|
|
||||||
│ ├── mcp_server.rs # MCP protocol handling
|
|
||||||
│ ├── models/ # Data structures
|
|
||||||
│ │ ├── config.rs # Configuration
|
|
||||||
│ │ ├── deals.rs # Deal, PositionPair, DrawdownEvent, etc.
|
|
||||||
│ │ ├── metrics.rs # Metrics parsing from HTML/XML
|
|
||||||
│ │ └── report.rs # Report, PipelineMetadata, etc.
|
|
||||||
│ ├── analytics/ # Report extraction & analysis (migrated from Python)
|
|
||||||
│ │ ├── extract.rs # HTML/XML report parser → metrics.json + deals.csv
|
|
||||||
│ │ └── analyze.rs # Deal-level analysis engine → analysis.json
|
|
||||||
│ ├── compile/ # MQL5 compilation
|
|
||||||
│ │ └── mql_compiler.rs # MetaEditor wrapper (Wine/CrossOver)
|
|
||||||
│ ├── pipeline/ # Backtest orchestration
|
|
||||||
│ │ ├── backtest.rs # 5-stage pipeline (COMPILE→CLEAN→BACKTEST→EXTRACT→ANALYZE)
|
|
||||||
│ │ └── stages.rs # Pipeline stage definitions
|
|
||||||
│ └── tools/ # MCP tool definitions
|
|
||||||
│ ├── definitions/ # Tool schemas (9 domain modules, 43 tools)
|
|
||||||
│ │ ├── mod.rs
|
|
||||||
│ │ ├── analytics.rs # 9 analysis tools
|
|
||||||
│ │ ├── backtest.rs # 4 backtest tools
|
|
||||||
│ │ ├── baseline.rs # 1 baseline tool
|
|
||||||
│ │ ├── experts.rs # 4 EA/indicator/script tools
|
|
||||||
│ │ ├── optimization.rs # 4 optimization tools
|
|
||||||
│ │ ├── reports.rs # 11 report management tools
|
|
||||||
│ │ ├── setfiles.rs # 8 .set file tools
|
|
||||||
│ │ └── system.rs # 3 system tools
|
|
||||||
│ └── handlers/ # Tool dispatch (9 domain modules)
|
|
||||||
│ ├── mod.rs
|
|
||||||
│ ├── analysis.rs
|
|
||||||
│ ├── backtest.rs
|
|
||||||
│ ├── experts.rs
|
|
||||||
│ ├── optimization.rs
|
|
||||||
│ ├── reports.rs
|
|
||||||
│ ├── setfiles.rs
|
|
||||||
│ └── system.rs
|
|
||||||
│
|
|
||||||
├── scripts/
|
|
||||||
│ ├── setup.sh # Auto-detect Wine/MT5, write config, register MCP
|
|
||||||
│ ├── platform_detect.sh # Wine path + headless detection
|
|
||||||
│ ├── build-rust.sh # Rust build script
|
|
||||||
│ └── optimize.sh # Genetic optimization launcher (nohup + disown)
|
|
||||||
│
|
|
||||||
├── analytics/ # Legacy Python (reference only)
|
|
||||||
│ ├── extract.py
|
|
||||||
│ ├── analyze.py
|
|
||||||
│ └── optimize_parser.py
|
|
||||||
│
|
|
||||||
├── config/
|
|
||||||
│ ├── mt5-quant.example.yaml # Template config
|
|
||||||
│ └── mt5-quant.yaml # Live config (gitignored)
|
|
||||||
│
|
|
||||||
└── docs/
|
|
||||||
├── ARCHITECTURE.md # This file
|
|
||||||
├── MCP_TOOLS.md # Full tool spec
|
|
||||||
└── REMOTE_AGENTS.md # Linux agent farm setup
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pipeline Stages
|
|
||||||
|
|
||||||
### Stage 1: COMPILE
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/compile/mql_compiler.rs
|
|
||||||
let compiler = MqlCompiler::new(config);
|
|
||||||
let result = compiler.compile("src/experts/MyEA.mq5")?;
|
|
||||||
```
|
|
||||||
|
|
||||||
Invokes MetaEditor via Wine with the MQL5 source file. Copies resulting `.ex5` to the MT5 Experts directory. Fails the pipeline on compile errors.
|
|
||||||
|
|
||||||
**Why not skip this?** MT5 caches the `.ex5` binary by filename. If you edit your EA and re-run without recompiling, MT5 runs the old binary silently. Always compile.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Stage 2: CLEAN
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm -f "${MT5_TESTER_DIR}/cache/*.tst"
|
|
||||||
rm -f "${MT5_PROFILES_DIR}/Tester/${EXPERT}.set"
|
|
||||||
```
|
|
||||||
|
|
||||||
Clears:
|
|
||||||
- Tester cache (`.tst` files): compiled test results MT5 reuses to skip re-running ticks
|
|
||||||
- Cached `.set` file: MT5 writes the current parameter values here after each run; if stale, next run picks up wrong params
|
|
||||||
|
|
||||||
**The `.set` encoding trap:** MT5 reads parameter files as UTF-16LE with BOM. It writes them back as UTF-16LE. If you provide a UTF-8 `.set` file for optimization, MT5 reads the parameters correctly (it tries multiple encodings) but when it writes the optimization variants, it uses UTF-16LE and **strips the `||Y` optimization flags**. Every subsequent pass uses the base value. Your 500-combination optimization runs 500 identical backtests.
|
|
||||||
|
|
||||||
Solution: always write `.set` files as UTF-16LE with BOM, mark them read-only before MT5 starts.
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Python: write UTF-16LE with BOM
|
|
||||||
content = "\n".join(lines)
|
|
||||||
with open(set_path, 'w', encoding='utf-16-le') as f:
|
|
||||||
f.write('\ufeff') # BOM
|
|
||||||
f.write(content)
|
|
||||||
os.chmod(set_path, 0o444) # read-only
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Stage 3: BACKTEST
|
|
||||||
|
|
||||||
```bash
|
|
||||||
arch -x86_64 "${WINE}" cmd.exe /c 'C:\_backtest.bat'
|
|
||||||
```
|
|
||||||
|
|
||||||
The batch file sets MT5 CLI flags and launches `terminal64.exe`:
|
|
||||||
|
|
||||||
```bat
|
|
||||||
"C:\Program Files\MetaTrader 5\terminal64.exe" /config:C:\backtest.ini
|
|
||||||
```
|
|
||||||
|
|
||||||
`backtest.ini` contains:
|
|
||||||
```ini
|
|
||||||
[Tester]
|
|
||||||
Expert=MyEA
|
|
||||||
Symbol=XAUUSD
|
|
||||||
Period=M5
|
|
||||||
Deposit=10000
|
|
||||||
Currency=USD
|
|
||||||
Leverage=500
|
|
||||||
Model=0
|
|
||||||
FromDate=2025.01.01
|
|
||||||
ToDate=2025.06.30
|
|
||||||
Report=C:\report
|
|
||||||
Optimization=0
|
|
||||||
```
|
|
||||||
|
|
||||||
MT5 runs in headless mode, writes the report, and exits.
|
|
||||||
|
|
||||||
**Process isolation note:** On macOS with CrossOver, `arch -x86_64` is required — CrossOver ships arm64 Wine wrappers that don't support the x86_64 MT5 binary correctly. Without it, MT5 appears to start but produces no output.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Stage 4: EXTRACT
|
|
||||||
|
|
||||||
Single HTML/XML parse pass that produces three artifacts:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/analytics/extract.rs
|
|
||||||
let extractor = ReportExtractor::new();
|
|
||||||
let result = extractor.extract(&report_path, &output_dir)?;
|
|
||||||
// → metrics.json (aggregate summary)
|
|
||||||
// → deals.csv (all deals, 13 columns)
|
|
||||||
// → deals.json (same data, JSON)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why single-pass?** MT5 HTML reports are large (1-5MB for 14-month tests). Each regex pass over the file takes ~200ms. The old pipeline ran 5 separate grep/regex passes. The Rust implementation uses a single-pass parser: 5× faster and no partial-read inconsistencies.
|
|
||||||
|
|
||||||
**Format detection:**
|
|
||||||
```rust
|
|
||||||
// MT5 Build 48+ saves SpreadsheetML XML, not HTML
|
|
||||||
let ext = Path::new(&path).extension()
|
|
||||||
.and_then(|e| e.to_str())
|
|
||||||
.unwrap_or("");
|
|
||||||
|
|
||||||
if ext == "xml" || path.ends_with(".htm.xml") {
|
|
||||||
// Parse as SpreadsheetML XML
|
|
||||||
let doc = roxmltree::Document::parse(&text)?;
|
|
||||||
} else {
|
|
||||||
// Parse as HTML with regex
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Deal columns (13):**
|
|
||||||
```
|
|
||||||
Time | Type | Direction | Volume | Price | S/L | T/P | Profit | Balance | Comment | Order | Magic | Entry
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Comment` column is the key to grid analytics. The EA writes `"Layer #3"`, `"Locking Total"`, `"Zombie Exit"` etc. Pattern matching on comments reconstructs which position was at which layer.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Stage 5: ANALYZE
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// src/analytics/analyze.rs
|
|
||||||
let analyzer = DealAnalyzer::new();
|
|
||||||
let result = analyzer.analyze(&deals, &metrics, strategy, deep)?;
|
|
||||||
// → analysis.json
|
|
||||||
```
|
|
||||||
|
|
||||||
All functions operate on the parsed deal data — no MT5 or Wine required.
|
|
||||||
|
|
||||||
**Strategy profiles** (defined in `analyze.rs`):
|
|
||||||
- `grid` — Layer depth tracking, locking/cutloss/zombie keywords
|
|
||||||
- `scalper` — TP/SL/manual/trailing exit classification
|
|
||||||
- `trend` — TP/SL/trailing/breakeven/partial exits
|
|
||||||
- `hedge` — TP/SL/net_close/partial, magic+direction grouping
|
|
||||||
- `generic` — Simple profit-based TP/SL classification
|
|
||||||
|
|
||||||
#### Strategy profiles
|
|
||||||
|
|
||||||
The analysis engine is driven by a `PROFILES` dict. Each profile controls:
|
|
||||||
|
|
||||||
| Field | Type | Controls |
|
|
||||||
|-------|------|----------|
|
|
||||||
| `depth_re` | regex or `None` | Whether/how to extract depth from comments |
|
|
||||||
| `exit_keywords` | `{reason: [kw]}` | Comment patterns for exit classification |
|
|
||||||
| `dd_cause_keywords` | `{cause: [kw]}` | Comment patterns for DD cause classification |
|
|
||||||
| `cycle_group_by` | `'magic'` or `'magic+direction'` | How deals are grouped into cycles |
|
|
||||||
| `cycle_gap_min` | int | Minutes between opens that mark a new cycle |
|
|
||||||
|
|
||||||
Built-in profiles:
|
|
||||||
|
|
||||||
| Profile | `depth_re` | `cycle_group_by` | `cycle_gap_min` | Exit keywords |
|
|
||||||
|---------|-----------|-----------------|----------------|---------------|
|
|
||||||
| `generic` | — | `magic` | 60 | profit-sign only (tp/sl) |
|
|
||||||
| `grid` | `Layer #N` | `magic+direction` | 60 | locking, cutloss, zombie, timeout |
|
|
||||||
| `scalper` | — | `magic` | 10 | tp, sl, manual, trailing |
|
|
||||||
| `trend` | — | `magic` | 240 | breakeven, trailing, partial, tp, sl |
|
|
||||||
| `hedge` | — | `magic+direction` | 120 | tp, sl, net_close, partial |
|
|
||||||
|
|
||||||
#### Entry points (after `pip install -e .`)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mt5-analyze deals.csv # generic
|
|
||||||
mt5-analyze-grid deals.csv # grid / martingale (default in pipeline)
|
|
||||||
mt5-analyze-scalper deals.csv # scalper
|
|
||||||
mt5-analyze-trend deals.csv # trend following
|
|
||||||
mt5-analyze-hedge deals.csv # hedging
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Analytics functions
|
|
||||||
|
|
||||||
**Core (always run, strategy-agnostic):**
|
|
||||||
|
|
||||||
| Function | What it computes |
|
|
||||||
|----------|-----------------|
|
|
||||||
| `monthly_pnl` | P/L, trade count, green flag per calendar month |
|
|
||||||
| `reconstruct_dd_events` | Balance curve → local minima; cause from profile keywords |
|
|
||||||
| `top_losses` | Worst individual closing deals by P/L |
|
|
||||||
| `loss_sequences` | Consecutive losing closed deals (runs of length ≥ 2) |
|
|
||||||
| `position_pairs` | Match in/out by order ticket → hold time, depth at close |
|
|
||||||
| `direction_bias` | Buy vs sell win rate, total P/L, average trade |
|
|
||||||
| `streak_analysis` | Max consecutive win/loss streaks; current streak |
|
|
||||||
| `session_breakdown` | Asian (00–08h) / London (08–13h) / London-NY (13–17h) / New York (17–22h) |
|
|
||||||
| `weekday_pnl` | Mon–Sun P/L and win rate |
|
|
||||||
| `concurrent_peak` | Peak simultaneous open positions |
|
|
||||||
|
|
||||||
**Strategy-driven (output varies by profile):**
|
|
||||||
|
|
||||||
| Function | Generic | Grid | Scalper/Trend/Hedge |
|
|
||||||
|----------|---------|------|---------------------|
|
|
||||||
| `depth_histogram` | `{}` (empty) | L1–L8+ counts | `{}` (no `depth_re`) |
|
|
||||||
| `cycle_stats` | magic, 60-min gap | magic+direction, 60-min gap | per-profile config |
|
|
||||||
| `exit_reason_breakdown` | tp / sl | locking / cutloss / zombie / timeout | profile-specific |
|
|
||||||
|
|
||||||
**Deep analytics (`--deep` flag):**
|
|
||||||
|
|
||||||
| Function | What it computes |
|
|
||||||
|----------|-----------------|
|
|
||||||
| `hourly_pnl` | Hour-by-hour (0–23) P/L and win rate |
|
|
||||||
| `volume_profile` | P/L breakdown by lot size tier |
|
|
||||||
|
|
||||||
**DD event reconstruction:**
|
|
||||||
1. Walk deals chronologically, track running balance
|
|
||||||
2. At each local minimum (DD > 1%), record timestamp, depth (%), recovery date
|
|
||||||
3. Classify `cause` using `profile['dd_cause_keywords']`; returns `"unknown"` for generic/unmatched
|
|
||||||
|
|
||||||
**Cycle statistics:**
|
|
||||||
Deals are grouped by `cycle_group_by` key. A gap greater than `cycle_gap_min` between consecutive opens marks a new cycle boundary. Win rate is computed per cycle (not per deal), then broken down by max depth reached.
|
|
||||||
|
|
||||||
**Exit reason classification:**
|
|
||||||
Iterates `exit_keywords` in definition order — more specific patterns must appear before general ones to avoid substring false-positives (e.g. `"stop"` inside `"breakeven stop"`). Falls back to profit-sign if no keyword matches.
|
|
||||||
|
|
||||||
**Loss sequence detection:**
|
|
||||||
Consecutive closed deals where P/L < 0 (minimum length 2). Captures clusters of losses better than any single worst-trade metric.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Optimization Pipeline
|
|
||||||
|
|
||||||
### Why `nohup + disown` is mandatory
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nohup ./scripts/optimize.sh ... > /tmp/opt.log 2>&1 & disown
|
|
||||||
```
|
|
||||||
|
|
||||||
MT5 optimization uses Unix signals to coordinate between `terminal64.exe` (master) and `metatester64.exe` instances (workers). When the parent process tree is killed:
|
|
||||||
|
|
||||||
1. `SIGHUP` propagates to child processes
|
|
||||||
2. `metatester64.exe` workers receive the signal and terminate
|
|
||||||
3. The master `terminal64.exe` detects worker failure and aborts the optimization
|
|
||||||
4. `terminal.ini` is left with `OptMode=-1`, requiring manual reset before next run
|
|
||||||
|
|
||||||
`nohup` prevents `SIGHUP` propagation. `disown` removes the process from the shell's job table so it's not killed when the shell exits. Both are required.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### `OptMode` state machine
|
|
||||||
|
|
||||||
`terminal.ini` contains an `OptMode` key that MT5 uses to track optimization state:
|
|
||||||
|
|
||||||
| `OptMode` value | Meaning |
|
|
||||||
|----------------|---------|
|
|
||||||
| `0` | Normal backtest mode (ready) |
|
|
||||||
| `1` | Optimization in progress |
|
|
||||||
| `2` | Optimization complete — show results |
|
|
||||||
| `-1` | Optimization aborted / crashed |
|
|
||||||
|
|
||||||
After any optimization run (complete or aborted), MT5 writes `-1` or `2`. On next launch with `Optimization=2` in `backtest.ini`, MT5 reads `OptMode=-1` and exits immediately without running.
|
|
||||||
|
|
||||||
**Fix:** Before every optimization launch, force `OptMode=0` in `terminal.ini`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sed -i 's/OptMode=.*/OptMode=0/' "${MT5_DIR}/terminal.ini"
|
|
||||||
# Also remove LastOptimization line if present
|
|
||||||
sed -i '/LastOptimization=/d' "${MT5_DIR}/terminal.ini"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Remote Agent Architecture
|
|
||||||
|
|
||||||
MT5's distributed testing works via a custom TCP protocol. The master `terminal64.exe` listens on a port. Remote agents (`metatester64.exe`) connect and receive test configurations.
|
|
||||||
|
|
||||||
```
|
|
||||||
Mac (master) Linux server (agents)
|
|
||||||
terminal64.exe metatester64.exe × N
|
|
||||||
│ │
|
|
||||||
└──── TCP:3000 ─────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**Linux setup:**
|
|
||||||
```bash
|
|
||||||
# On Linux server (Wine required)
|
|
||||||
wine metatester64.exe /server:MAC_IP:3000 /agents:8
|
|
||||||
```
|
|
||||||
|
|
||||||
MT5 shows remote agents in the agent manager as `Agent-0.0.0.0-PORT` entries when listening, and activates them when the remote `metatester64.exe` connects.
|
|
||||||
|
|
||||||
**Throughput:** Linear scaling with agent count. 10 local + 16 remote = 26 agents. A 17,000-combination optimization that takes 3 hours locally completes in ~70 minutes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Headless Operation
|
|
||||||
|
|
||||||
MT5-Quant uses MT5's CLI mode (`terminal64.exe /config:backtest.ini`) — no user interaction, no clicking in the Strategy Tester GUI. Whether this is truly "headless" depends on platform:
|
|
||||||
|
|
||||||
| Platform | Status | Notes |
|
|
||||||
|----------|--------|-------|
|
|
||||||
| macOS + CrossOver | Near-headless | CrossOver manages the display internally. MT5 window may flash briefly or be suppressed entirely depending on bottle settings. No monitor required in practice. |
|
|
||||||
| Linux + Wine | Requires Xvfb | Wine needs an X11 display connection. Without one, `wine64 terminal64.exe` fails with `cannot open display`. |
|
|
||||||
| Linux + Wine + Xvfb | Full headless | Virtual framebuffer satisfies Wine's X11 requirement. Use on servers with no monitor. |
|
|
||||||
|
|
||||||
**Linux headless setup (Xvfb):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install Xvfb
|
|
||||||
sudo apt install xvfb
|
|
||||||
|
|
||||||
# Start virtual display on :99
|
|
||||||
Xvfb :99 -screen 0 1024x768x16 &
|
|
||||||
export DISPLAY=:99
|
|
||||||
|
|
||||||
# Now Wine can launch MT5 without a physical display
|
|
||||||
wine64 terminal64.exe /config:backtest.ini
|
|
||||||
```
|
|
||||||
|
|
||||||
**Persistent virtual display (systemd):**
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# /etc/systemd/system/xvfb.service
|
|
||||||
[Unit]
|
|
||||||
Description=Virtual Display for MT5
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/usr/bin/Xvfb :99 -screen 0 1024x768x16
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl enable xvfb
|
|
||||||
sudo systemctl start xvfb
|
|
||||||
```
|
|
||||||
|
|
||||||
Then set `DISPLAY=:99` in MT5-Quant's environment config.
|
|
||||||
|
|
||||||
**Note:** `metatester64.exe` (the agent worker process) is fully headless — it runs tick simulation with no display requirement. Only the master `terminal64.exe` needs a display to orchestrate the session. On Mac with CrossOver this is handled transparently.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Limitations
|
|
||||||
|
|
||||||
**macOS-specific:**
|
|
||||||
- Requires Wine. The native MT5.app from the Mac App Store (or MetaQuotes CDN) ships bundled Wine at `MetaTrader 5.app/Contents/SharedSupport/wine/`. CrossOver is an alternative.
|
|
||||||
- `arch -x86_64` required on Apple Silicon.
|
|
||||||
- File paths must go through Wine's virtual filesystem (`C:\` = inside the Wine prefix `drive_c/`).
|
|
||||||
|
|
||||||
**Report format dependency:**
|
|
||||||
- SpreadsheetML XML format (`.htm.xml`) has no documented schema from MetaQuotes. The parser is reverse-engineered from observed output. May break on future MT5 builds.
|
|
||||||
|
|
||||||
**Comment-based analytics:**
|
|
||||||
- Strategy-specific analytics (depth histogram, exit reason, DD cause) depend on EA comment strings. EAs that don't write structured comments will get `generic` profile results — summary metrics, session breakdown, streaks, and direction bias all still work; only keyword-classified fields fall back to `"unknown"` or profit-sign.
|
|
||||||
- Custom comment patterns can be supported by adding a new entry to `PROFILES` in `analytics/analyze.py` — no other code changes needed.
|
|
||||||
|
|
||||||
**Single MT5 instance:**
|
|
||||||
- MT5 is single-instance per Windows drive. Two backtests cannot run simultaneously on the same Wine prefix. Parallelism requires multiple Wine prefixes (separate installations).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Claude Code Integration
|
|
||||||
|
|
||||||
`setup.sh --claude-code` generates two files that give Claude persistent context about the user's trading setup:
|
|
||||||
|
|
||||||
### `config/CLAUDE.template.md`
|
|
||||||
|
|
||||||
A project-level CLAUDE.md template the user copies to their EA project root. Encodes:
|
|
||||||
- MT5-Quant tool names and when to use them
|
|
||||||
- Baseline tracking policy (never call something an improvement without comparing to `baseline.json`)
|
|
||||||
- Symbol name reminder (broker-specific suffix matters — `XAUUSD.cent` ≠ `XAUUSD`)
|
|
||||||
- Backtest and optimization constraints (model 0, single instance, UTF-16LE .set files)
|
|
||||||
|
|
||||||
### `.claude/hooks/user-prompt-submit.sh`
|
|
||||||
|
|
||||||
A Claude Code hook that runs before every prompt submission. Reads `config/baseline.json` and outputs a JSON context block:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"context": "## Production Baseline (config/baseline.json)\n..."}
|
|
||||||
```
|
|
||||||
|
|
||||||
Claude Code injects this into the system context for every conversation turn. The result: Claude always knows the current production metrics without the user having to paste them.
|
|
||||||
|
|
||||||
**Hook execution path:**
|
|
||||||
```
|
|
||||||
User types prompt
|
|
||||||
→ user-prompt-submit.sh executes
|
|
||||||
→ reads config/baseline.json
|
|
||||||
→ outputs {"context": "..."} to stdout
|
|
||||||
→ Claude Code prepends to system context
|
|
||||||
→ Claude sees baseline in every prompt
|
|
||||||
```
|
|
||||||
|
|
||||||
**Graceful degradation:** If `baseline.json` doesn't exist or is malformed, the hook exits 0 silently — no prompt is blocked. The baseline section simply doesn't appear until the user creates the file.
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
# Configuration Reference
|
|
||||||
|
|
||||||
## Config File Location
|
|
||||||
|
|
||||||
```
|
|
||||||
config/mt5-quant.yaml # Project directory (development)
|
|
||||||
~/.config/mt5-quant/config/mt5-quant.yaml # System-wide (production)
|
|
||||||
```
|
|
||||||
|
|
||||||
Environment variable to override:
|
|
||||||
```bash
|
|
||||||
export MT5_MCP_HOME=/path/to/config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full Config Example
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Required: Wine executable path
|
|
||||||
wine_executable: "/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64"
|
|
||||||
|
|
||||||
# Required: MT5 installation directory
|
|
||||||
terminal_dir: "~/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5"
|
|
||||||
|
|
||||||
# Optional: Default backtest parameters
|
|
||||||
defaults:
|
|
||||||
symbol: "XAUUSD.cent"
|
|
||||||
timeframe: "M5"
|
|
||||||
deposit: 10000
|
|
||||||
currency: "USD"
|
|
||||||
model: 0 # 0=every tick, 1=1min OHLC, 2=open price
|
|
||||||
leverage: 500
|
|
||||||
|
|
||||||
# Optional: Display settings
|
|
||||||
display:
|
|
||||||
mode: auto # auto, gui, headless
|
|
||||||
xvfb_display: ":99" # Linux headless only
|
|
||||||
xvfb_screen: "1024x768x16" # Linux headless only
|
|
||||||
|
|
||||||
# Optional: Directories (auto-detected from terminal_dir if not set)
|
|
||||||
experts_dir: "~/.../MetaTrader 5/MQL5/Experts"
|
|
||||||
indicators_dir: "~/.../MetaTrader 5/MQL5/Indicators"
|
|
||||||
scripts_dir: "~/.../MetaTrader 5/MQL5/Scripts"
|
|
||||||
|
|
||||||
# Optional: Reports directory
|
|
||||||
reports_dir: "./reports"
|
|
||||||
|
|
||||||
# Optional: Optimization settings
|
|
||||||
optimization:
|
|
||||||
remote_agents:
|
|
||||||
enabled: false
|
|
||||||
check_agent_count: true
|
|
||||||
min_agents: 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Platform-Specific Examples
|
|
||||||
|
|
||||||
### macOS with MetaTrader 5.app
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
wine_executable: "/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64"
|
|
||||||
terminal_dir: "~/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5"
|
|
||||||
defaults:
|
|
||||||
symbol: "XAUUSDc"
|
|
||||||
timeframe: "M5"
|
|
||||||
deposit: 10000
|
|
||||||
```
|
|
||||||
|
|
||||||
### macOS with CrossOver
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
wine_executable: "/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wine64"
|
|
||||||
terminal_dir: "~/Library/Application Support/MetaQuotes/Terminal/<hash>/drive_c/Program Files/MetaTrader 5"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
wine_executable: "/usr/bin/wine64"
|
|
||||||
terminal_dir: "~/.wine/drive_c/Program Files/MetaTrader 5"
|
|
||||||
display:
|
|
||||||
mode: headless
|
|
||||||
xvfb_display: ":99"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Headless Mode (Linux VPS)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
display:
|
|
||||||
mode: headless
|
|
||||||
xvfb_display: ":99"
|
|
||||||
xvfb_screen: "1024x768x16"
|
|
||||||
```
|
|
||||||
|
|
||||||
Requires:
|
|
||||||
```bash
|
|
||||||
sudo apt install xvfb
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auto-Detection
|
|
||||||
|
|
||||||
`setup.sh` automatically detects:
|
|
||||||
- Wine executable (MetaTrader 5.app, CrossOver, or system Wine)
|
|
||||||
- MT5 terminal directory
|
|
||||||
- Architecture (Apple Silicon adds `arch -x86_64`)
|
|
||||||
- Display mode (GUI vs headless)
|
|
||||||
|
|
||||||
Run `setup.sh` whenever you move the installation.
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
# Cursor MCP Integration Setup
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### Option 1: Download Prebuilt Binary (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (Apple Silicon)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-macos-arm64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
|
|
||||||
# Linux (x64)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-linux-x64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Build from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure Cursor
|
|
||||||
|
|
||||||
### Method 1: Settings UI (Recommended)
|
|
||||||
|
|
||||||
1. Open Cursor Settings (`Cmd/Ctrl + ,`)
|
|
||||||
2. Navigate to **Features** → **MCP**
|
|
||||||
3. Click **Add Custom MCP**
|
|
||||||
4. Enter:
|
|
||||||
- **Name**: `mt5-quant`
|
|
||||||
- **Command**: Full path to binary (e.g., `/Users/name/mt5-quant/target/release/mt5-quant`)
|
|
||||||
- **Type**: `stdio`
|
|
||||||
|
|
||||||
### Method 2: Edit mcp.json Directly
|
|
||||||
|
|
||||||
Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"type": "stdio",
|
|
||||||
"command": "/absolute/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Create the file if it doesn't exist:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.cursor
|
|
||||||
cat > ~/.cursor/mcp.json << 'EOF'
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"type": "stdio",
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verify Setup
|
|
||||||
|
|
||||||
In Cursor chat, type:
|
|
||||||
|
|
||||||
```
|
|
||||||
Run verify_setup
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
Wine: /Applications/MetaTrader 5.app/.../wine64
|
|
||||||
MT5 dir: ~/Library/Application Support/.../MetaTrader 5
|
|
||||||
Display: gui
|
|
||||||
Arch: arch -x86_64
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Locations
|
|
||||||
|
|
||||||
| Scope | Path | Use Case |
|
|
||||||
|-------|------|----------|
|
|
||||||
| Global | `~/.cursor/mcp.json` | Available in all projects |
|
|
||||||
| Project | `.cursor/mcp.json` | Project-specific tools |
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### MCP server not appearing
|
|
||||||
|
|
||||||
1. Check MCP panel in Cursor Settings
|
|
||||||
2. Verify the path is absolute (not relative)
|
|
||||||
3. Test binary: `/path/to/mt5-quant --help`
|
|
||||||
4. View MCP logs: Output panel → select "MCP" from dropdown
|
|
||||||
|
|
||||||
### Config interpolation
|
|
||||||
|
|
||||||
Cursor supports variable substitution in `mcp.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "${userHome}/bin/mt5-quant",
|
|
||||||
"args": ["--config", "${workspaceFolder}/config.yaml"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Available variables:
|
|
||||||
- `${userHome}` - Home directory
|
|
||||||
- `${workspaceFolder}` - Project root
|
|
||||||
- `${workspaceFolderBasename}` - Project folder name
|
|
||||||
- `${env:VAR_NAME}` - Environment variable
|
|
||||||
|
|
||||||
### Tool not found errors
|
|
||||||
|
|
||||||
If the agent says "Tool not found":
|
|
||||||
1. Check the server is enabled in MCP settings
|
|
||||||
2. Try disabling and re-enabling the server
|
|
||||||
3. Restart Cursor
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Cursor MCP Documentation](https://cursor.com/docs/context/mcp)
|
|
||||||
- [MT5-Quant Tools Reference](./MCP_TOOLS.md)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,191 +0,0 @@
|
|||||||
# Quickstart Guide
|
|
||||||
|
|
||||||
## 1. Download or Build
|
|
||||||
|
|
||||||
### Option A: Prebuilt Binary (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (Apple Silicon)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-macos-arm64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
|
|
||||||
# Linux (x64)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-linux-x64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option B: Build from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/masdevid/mt5-mcp
|
|
||||||
cd mt5-mcp
|
|
||||||
bash scripts/build-rust.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Install MetaTrader 5
|
|
||||||
|
|
||||||
### macOS - MetaTrader 5.app (Free)
|
|
||||||
|
|
||||||
1. Download from [metatrader5.com](https://www.metatrader5.com/en/download)
|
|
||||||
2. Install to `/Applications`
|
|
||||||
3. **Launch once** to initialize Wine prefix (~30s), then quit
|
|
||||||
|
|
||||||
Auto-detected paths:
|
|
||||||
- Wine: `/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64`
|
|
||||||
- MT5: `~/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5`
|
|
||||||
|
|
||||||
### macOS - CrossOver (Paid, Better Compatibility)
|
|
||||||
|
|
||||||
1. Install [CrossOver](https://www.codeweavers.com/)
|
|
||||||
2. Create bottle `MetaTrader5`
|
|
||||||
3. Install MT5 inside bottle
|
|
||||||
|
|
||||||
Auto-detected paths:
|
|
||||||
- Wine: `/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wine64`
|
|
||||||
- MT5: `~/Library/Application Support/MetaQuotes/<hash>/drive_c/Program Files/MetaTrader 5`
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Debian/Ubuntu
|
|
||||||
sudo apt install wine64 xvfb
|
|
||||||
|
|
||||||
# Fedora/RHEL
|
|
||||||
sudo dnf install wine xorg-x11-server-Xvfb
|
|
||||||
|
|
||||||
# Install MT5
|
|
||||||
wine64 MetaTrader5Setup.exe
|
|
||||||
```
|
|
||||||
|
|
||||||
MT5 location: `~/.wine/drive_c/Program Files/MetaTrader 5`
|
|
||||||
|
|
||||||
## 3. Configure
|
|
||||||
|
|
||||||
Run the setup script to auto-detect paths:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/setup.sh # interactive
|
|
||||||
bash scripts/setup.sh --yes # non-interactive (CI)
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates `config/mt5-quant.yaml` (gitignored).
|
|
||||||
|
|
||||||
Minimum config:
|
|
||||||
```yaml
|
|
||||||
wine_executable: "/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64"
|
|
||||||
terminal_dir: "~/Library/Application Support/net.metaquotes.wine.metatrader5/drive_c/Program Files/MetaTrader 5"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Register MCP Server
|
|
||||||
|
|
||||||
### Claude Code
|
|
||||||
|
|
||||||
```bash
|
|
||||||
claude mcp add MT5-Quant -- /path/to/mt5-quant/target/release/mt5-quant
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify:
|
|
||||||
```bash
|
|
||||||
claude mcp list
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windsurf
|
|
||||||
|
|
||||||
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cursor
|
|
||||||
|
|
||||||
Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use Settings → MCP → Add Custom MCP.
|
|
||||||
|
|
||||||
### VS Code
|
|
||||||
|
|
||||||
Add to `.vscode/mcp.json` in your workspace:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"servers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run `MCP: Add Server` from Command Palette.
|
|
||||||
|
|
||||||
### Antigravity
|
|
||||||
|
|
||||||
1. Open Agent panel → Click "..." menu → MCP Servers
|
|
||||||
2. Click "Manage MCP Servers"
|
|
||||||
3. Click "View raw config" or "Edit configuration"
|
|
||||||
4. Add to `mcp_config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
5. Reload Antigravity to apply changes
|
|
||||||
|
|
||||||
## 5. Verify Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/platform_detect.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Or in Claude/Windsurf:
|
|
||||||
```
|
|
||||||
Run verify_setup
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
Wine: /Applications/MetaTrader 5.app/.../wine64
|
|
||||||
MT5 dir: ~/Library/Application Support/.../MetaTrader 5
|
|
||||||
Display: gui
|
|
||||||
Arch: arch -x86_64
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Run First Backtest
|
|
||||||
|
|
||||||
```
|
|
||||||
Run a backtest on MyEA from 2025.01.01 to 2025.03.31
|
|
||||||
```
|
|
||||||
|
|
||||||
The AI will:
|
|
||||||
1. Verify setup
|
|
||||||
2. Compile your EA
|
|
||||||
3. Clean MT5 cache
|
|
||||||
4. Run backtest
|
|
||||||
5. Extract and analyze results
|
|
||||||
6. Report key findings
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Next:** See [TOOLS.md](TOOLS.md) for all 43 available tools.
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
# Remote Agent Setup (Linux Server)
|
|
||||||
|
|
||||||
MT5's distributed testing lets you farm optimization work across multiple machines. A Linux server running `metatester64.exe` via Wine connects to a Mac master over a local network.
|
|
||||||
|
|
||||||
**Throughput:** Each agent handles one pass at a time. 10 local + 16 remote = 26 agents. A 17,000-combination genetic optimization that takes 3 hours locally finishes in ~70 minutes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
**Mac (master)**
|
|
||||||
- MetaTrader 5 installed via CrossOver or Wine
|
|
||||||
- MT5-Quant configured and working for local backtests
|
|
||||||
- Port 3000 open in macOS firewall (or whichever port MT5 uses — check below)
|
|
||||||
|
|
||||||
**Linux server (agents)**
|
|
||||||
- Wine 7.0+ (or Wine Staging for better compatibility)
|
|
||||||
- `metatester64.exe` from an MT5 installation
|
|
||||||
- Access to the same MT5 data files (tick history) as the master — or let agents download on first run
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 1: Find the MT5 agent port on Mac
|
|
||||||
|
|
||||||
After running a local optimization, check the MT5 agent directories:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls ~/Library/Application\ Support/MetaQuotes/*/drive_c/Program\ Files/MetaTrader\ 5/
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll see directories like:
|
|
||||||
```
|
|
||||||
Agent-127.0.0.1-3000/ ← local agents (loopback)
|
|
||||||
Agent-127.0.0.1-3001/
|
|
||||||
Agent-0.0.0.0-3000/ ← remote listener (if enabled)
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't see `Agent-0.0.0.0-*` directories, enable remote agents in MT5:
|
|
||||||
**Tools → Options → Expert Advisors → Allow remote agents**
|
|
||||||
|
|
||||||
Note the port number (default: 3000).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 2: Open firewall on Mac
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check current firewall rules
|
|
||||||
sudo pfctl -s rules
|
|
||||||
|
|
||||||
# Allow incoming on agent port (example: 3000)
|
|
||||||
# Add to /etc/pf.conf or use macOS Firewall in System Settings
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use macOS System Settings → Privacy & Security → Firewall → Firewall Options → Add MetaTrader 5.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Install Wine on Linux server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ubuntu/Debian
|
|
||||||
sudo dpkg --add-architecture i386
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install wine64 wine32
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
wine64 --version
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Copy `metatester64.exe` to Linux
|
|
||||||
|
|
||||||
From your Mac MT5 installation:
|
|
||||||
```bash
|
|
||||||
MAC_MT5="$HOME/Library/Application Support/MetaQuotes/Terminal/XXXXX/drive_c/Program Files/MetaTrader 5"
|
|
||||||
|
|
||||||
scp "${MAC_MT5}/metatester64.exe" user@linux-server:~/mt5agents/
|
|
||||||
scp "${MAC_MT5}/metaeditor64.exe" user@linux-server:~/mt5agents/ # not required for agents
|
|
||||||
```
|
|
||||||
|
|
||||||
Also copy the required DLLs (they're in the same directory):
|
|
||||||
```bash
|
|
||||||
scp "${MAC_MT5}"/*.dll user@linux-server:~/mt5agents/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Launch agents on Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# On the Linux server
|
|
||||||
cd ~/mt5agents/
|
|
||||||
|
|
||||||
# Replace MAC_IP with your Mac's local IP address
|
|
||||||
# Replace 8 with number of CPU cores - 1
|
|
||||||
wine64 metatester64.exe /server:192.168.1.100:3000 /agents:8
|
|
||||||
```
|
|
||||||
|
|
||||||
**What this does:**
|
|
||||||
- Connects to your Mac's MT5 master at `192.168.1.100:3000`
|
|
||||||
- Registers 8 worker agents
|
|
||||||
- MT5 on Mac will show 8 new agents in the agent manager
|
|
||||||
|
|
||||||
**To run as a background service:**
|
|
||||||
```bash
|
|
||||||
nohup wine64 metatester64.exe /server:192.168.1.100:3000 /agents:8 \
|
|
||||||
> ~/mt5agents/agents.log 2>&1 &
|
|
||||||
disown
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 6: Verify agents appear in MT5
|
|
||||||
|
|
||||||
On your Mac, open MT5:
|
|
||||||
**View → Strategy Tester → Agents tab**
|
|
||||||
|
|
||||||
You should see entries like:
|
|
||||||
```
|
|
||||||
Agent-192.168.1.200-3000 [Active]
|
|
||||||
Agent-192.168.1.200-3001 [Active]
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
If agents appear as `[Inactive]`, check:
|
|
||||||
1. Mac firewall is allowing incoming connections on the agent port
|
|
||||||
2. Linux server can reach Mac IP: `ping 192.168.1.100`
|
|
||||||
3. Port is open: `nc -zv 192.168.1.100 3000`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 7: Configure MT5-Quant for remote agents
|
|
||||||
|
|
||||||
In `config/MT5-Quant.yaml`:
|
|
||||||
```yaml
|
|
||||||
optimization:
|
|
||||||
remote_agents:
|
|
||||||
enabled: true
|
|
||||||
check_agent_count: true # Verify remote agents are connected before launching
|
|
||||||
min_agents: 4 # Require at least N agents before optimizing
|
|
||||||
```
|
|
||||||
|
|
||||||
MT5-Quant will log the agent count before launching optimization:
|
|
||||||
```
|
|
||||||
[optimize] Local agents: 9, Remote agents: 8, Total: 17
|
|
||||||
[optimize] Estimated completion: ~105 minutes (17,640 passes at 17 agents)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tick Data Sync
|
|
||||||
|
|
||||||
Remote agents need tick history to replay trades. On first run, MT5 automatically downloads ticks from the broker for each symbol+period combination tested. This can take 10-30 minutes per symbol.
|
|
||||||
|
|
||||||
**Speed up first run:** Pre-populate the tick cache on the Linux server by copying from Mac:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# On Mac — find tick data location
|
|
||||||
find ~/Library/Application\ Support/MetaQuotes -name "*.bin" | grep "XAUUSD"
|
|
||||||
|
|
||||||
# Typical path:
|
|
||||||
# Terminal/XXXXX/drive_c/users/user/AppData/Roaming/MetaQuotes/Terminal/Common/Files/
|
|
||||||
|
|
||||||
scp -r "${TICK_DIR}" user@linux-server:~/mt5agents/ticks/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**Agents connect then immediately disconnect**
|
|
||||||
- MT5 version mismatch between `metatester64.exe` (from Mac) and the master. Use the exact same build number.
|
|
||||||
- Check `~/mt5agents/agents.log` for Wine errors.
|
|
||||||
|
|
||||||
**Agents show as connected but don't receive work**
|
|
||||||
- MT5 sometimes requires optimization to be started before assigning work to new agents.
|
|
||||||
- Start an optimization from the MT5 GUI first to "activate" remote agents, then cancel it and use MT5-Quant.
|
|
||||||
|
|
||||||
**Performance is slower with remote agents**
|
|
||||||
- Network latency between Mac and Linux server. Results are sent over TCP after each pass.
|
|
||||||
- Local gigabit network: negligible. WiFi or WAN: significant overhead. Use wired connection.
|
|
||||||
|
|
||||||
**Linux server runs out of memory**
|
|
||||||
- Each `metatester64.exe` instance uses ~200-400MB.
|
|
||||||
- 8 agents = ~2-3GB RAM. Size agent count accordingly.
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
# Troubleshooting
|
|
||||||
|
|
||||||
Run `verify_setup` first — it checks all paths and returns actionable hints.
|
|
||||||
|
|
||||||
## Wine Not Found
|
|
||||||
|
|
||||||
### macOS
|
|
||||||
|
|
||||||
Confirm `/Applications/MetaTrader 5.app` exists and has been launched at least once.
|
|
||||||
|
|
||||||
Check detection:
|
|
||||||
```bash
|
|
||||||
bash scripts/platform_detect.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If using CrossOver, confirm bottle is named `MetaTrader5`.
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install wine64 # Debian/Ubuntu
|
|
||||||
sudo dnf install wine # Fedora/RHEL
|
|
||||||
which wine64 # confirm on PATH
|
|
||||||
```
|
|
||||||
|
|
||||||
## terminal64.exe Missing
|
|
||||||
|
|
||||||
MT5 unpacks `terminal64.exe` only after first launch.
|
|
||||||
|
|
||||||
1. Open MetaTrader 5.app
|
|
||||||
2. Wait for initialization (~30s)
|
|
||||||
3. Quit
|
|
||||||
4. Re-run setup:
|
|
||||||
```bash
|
|
||||||
bash scripts/setup.sh --yes
|
|
||||||
```
|
|
||||||
|
|
||||||
## MCP Server Not Appearing
|
|
||||||
|
|
||||||
### Claude Code
|
|
||||||
|
|
||||||
```bash
|
|
||||||
claude mcp list # should show MT5-Quant
|
|
||||||
claude mcp remove MT5-Quant # remove stale entry
|
|
||||||
claude mcp add MT5-Quant -- /absolute/path/to/mt5-quant
|
|
||||||
```
|
|
||||||
|
|
||||||
**Must use absolute path** — relative paths break when Claude starts from different directories.
|
|
||||||
|
|
||||||
### Windsurf
|
|
||||||
|
|
||||||
1. Check logs: `~/.windsurf/logs/`
|
|
||||||
2. Verify executable path is absolute
|
|
||||||
3. Test manually: `./mt5-quant --help`
|
|
||||||
|
|
||||||
## Config Not Found
|
|
||||||
|
|
||||||
Set `MT5_MCP_HOME` or ensure config exists at:
|
|
||||||
- macOS: `~/.config/mt5-quant/config/mt5-quant.yaml`
|
|
||||||
- Project: `config/mt5-quant.yaml`
|
|
||||||
|
|
||||||
## Report Not Found After Backtest
|
|
||||||
|
|
||||||
1. **Wrong symbol name** — brokers use custom names (`XAUUSDm`, `XAUUSD.cent`). Check `verify_setup` or look in `<terminal_dir>/history/`.
|
|
||||||
|
|
||||||
2. **No history data** — open MT5, open symbol chart, wait for history download.
|
|
||||||
|
|
||||||
3. **EA crash at startup** — check `<terminal_dir>/MQL5/Logs/` for `OnInit` errors.
|
|
||||||
|
|
||||||
4. **Date range has no trades** — try wider range or confirm symbol was active.
|
|
||||||
|
|
||||||
## MetaEditor Compile Errors
|
|
||||||
|
|
||||||
Check `<terminal_dir>/MQL5/Logs/`:
|
|
||||||
|
|
||||||
- **Missing `#include`** — copy dependencies into `Experts/` alongside `.mq5`
|
|
||||||
- **Stale `.ex5`** — delete old binary and recompile
|
|
||||||
|
|
||||||
## No Deals in Backtest Report
|
|
||||||
|
|
||||||
- Use `model=0` (every tick) — models 1/2 skip intra-bar movement, producing zero deals for grid/martingale EAs
|
|
||||||
- Check `.set` file values appropriate for symbol/broker
|
|
||||||
- Confirm `OnInit()` returns `INIT_SUCCEEDED` (MT5 Journal tab)
|
|
||||||
|
|
||||||
## Optimization Never Finishes
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# From Claude:
|
|
||||||
tail_log(job_id=X, filter=errors)
|
|
||||||
get_optimization_status(job_id=X)
|
|
||||||
```
|
|
||||||
|
|
||||||
If MT5 crashed, edit `<terminal_dir>/terminal.ini` and remove line containing `OptMode=-1`, then retry.
|
|
||||||
|
|
||||||
## Permission Denied
|
|
||||||
|
|
||||||
```bash
|
|
||||||
chmod +x /path/to/mt5-quant
|
|
||||||
```
|
|
||||||
|
|
||||||
## Still Stuck?
|
|
||||||
|
|
||||||
1. Run `verify_setup` and share output
|
|
||||||
2. Check `tail_log` for errors
|
|
||||||
3. Review `<terminal_dir>/MQL5/Logs/` for EA errors
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
# VS Code MCP Integration Setup
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### Option 1: Download Prebuilt Binary (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (Apple Silicon)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-macos-arm64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
|
|
||||||
# Linux (x64)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-linux-x64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Build from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure VS Code
|
|
||||||
|
|
||||||
### Method 1: Command Palette (Recommended)
|
|
||||||
|
|
||||||
1. Press `Cmd/Ctrl + Shift + P`
|
|
||||||
2. Run `MCP: Add Server`
|
|
||||||
3. Choose **Workspace** or **User** scope
|
|
||||||
4. Enter server name: `mt5-quant`
|
|
||||||
5. Enter command: Full path to binary (e.g., `/Users/name/mt5-quant/target/release/mt5-quant`)
|
|
||||||
|
|
||||||
### Method 2: Edit mcp.json Directly
|
|
||||||
|
|
||||||
Add to `.vscode/mcp.json` in your workspace:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"servers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/absolute/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Create the file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p .vscode
|
|
||||||
cat > .vscode/mcp.json << 'EOF'
|
|
||||||
{
|
|
||||||
"servers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 3: VS Code CLI
|
|
||||||
|
|
||||||
```bash
|
|
||||||
code --add-mcp '{"name":"mt5-quant","command":"/path/to/mt5-quant"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verify Setup
|
|
||||||
|
|
||||||
In Copilot chat, type:
|
|
||||||
|
|
||||||
```
|
|
||||||
Run verify_setup
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
Wine: /Applications/MetaTrader 5.app/.../wine64
|
|
||||||
MT5 dir: ~/Library/Application Support/.../MetaTrader 5
|
|
||||||
Display: gui
|
|
||||||
Arch: arch -x86_64
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration Locations
|
|
||||||
|
|
||||||
| Scope | Path | Use Case |
|
|
||||||
|-------|------|----------|
|
|
||||||
| Workspace | `.vscode/mcp.json` | Share with team via source control |
|
|
||||||
| User | `~/.vscode/mcp.json` | Personal tools across all projects |
|
|
||||||
| Dev Container | `devcontainer.json` → `customizations.vscode.mcp` | Containerized environments |
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### MCP server not appearing
|
|
||||||
|
|
||||||
1. Open **Output** panel (`Cmd/Ctrl + Shift + U`)
|
|
||||||
2. Select **MCP** from dropdown
|
|
||||||
3. Check for connection errors
|
|
||||||
4. Verify the path is absolute
|
|
||||||
|
|
||||||
### Config not found
|
|
||||||
|
|
||||||
The binary auto-detects its config, but you can also:
|
|
||||||
1. Run `setup.sh` to create `config/mt5-quant.yaml`
|
|
||||||
2. Or let the binary auto-discover on first run
|
|
||||||
|
|
||||||
### Dev Container Setup
|
|
||||||
|
|
||||||
Add to `.devcontainer/devcontainer.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"mcp": {
|
|
||||||
"servers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/path/to/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Differences from Other IDEs
|
|
||||||
|
|
||||||
VS Code uses `servers` (not `mcpServers`) in the JSON structure:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"servers": { // ← VS Code uses "servers"
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Other platforms use `mcpServers`.
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [VS Code MCP Documentation](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)
|
|
||||||
- [MCP Configuration Reference](https://code.visualstudio.com/docs/copilot/reference/mcp-configuration)
|
|
||||||
- [MT5-Quant Tools Reference](./MCP_TOOLS.md)
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
# Windsurf MCP Integration Setup
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### Option 1: Download Prebuilt Binary (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS (Apple Silicon)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-macos-arm64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
|
|
||||||
# Linux (x64)
|
|
||||||
curl -L -o mt5-quant.tar.gz https://github.com/masdevid/mt5-mcp/releases/latest/download/mt5-quant-linux-x64.tar.gz
|
|
||||||
tar -xzf mt5-quant.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Build from Source
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/build-rust.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Configure Windsurf
|
|
||||||
|
|
||||||
Edit `~/.codeium/windsurf/mcp_config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/Users/masdevid/jobs/mt5-quant/target/release/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or use the automated setup:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Restart Windsurf
|
|
||||||
Close and reopen Windsurf to load the MCP server.
|
|
||||||
|
|
||||||
### 4. Verify
|
|
||||||
In Windsurf chat, test with:
|
|
||||||
```
|
|
||||||
Run verify_setup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment to Multiple Machines
|
|
||||||
|
|
||||||
### Build for Distribution
|
|
||||||
```bash
|
|
||||||
# Build release binary
|
|
||||||
cargo build --release
|
|
||||||
|
|
||||||
# Create tarball
|
|
||||||
tar -czf mt5-quant-macos-arm64.tar.gz -C target/release mt5-quant
|
|
||||||
|
|
||||||
# Deploy to remote server
|
|
||||||
scp mt5-quant-macos-arm64.tar.gz user@server:~/
|
|
||||||
ssh user@server "tar -xzf mt5-quant-macos-arm64.tar.gz -C /opt/"
|
|
||||||
ssh user@server "ln -s /opt/mt5-quant /usr/local/bin/"
|
|
||||||
|
|
||||||
# Copy config
|
|
||||||
scp -r config/mt5-quant.yaml user@server:~/.config/mt5-quant/config/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Target Machine Requirements
|
|
||||||
- MetaTrader 5 installed (via Wine/CrossOver)
|
|
||||||
- Config file at `~/.config/mt5-quant/config/mt5-quant.yaml`
|
|
||||||
- **NO Python required!**
|
|
||||||
|
|
||||||
### Windsurf Config on Target Machine
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"mt5-quant": {
|
|
||||||
"command": "/usr/local/bin/mt5-quant"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The binary auto-detects its config location. No environment variables needed.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### MCP server not appearing
|
|
||||||
1. Check Windsurf logs: `~/.windsurf/logs/`
|
|
||||||
2. Verify executable path is absolute
|
|
||||||
3. Test executable manually: `./target/release/mt5-quant --help`
|
|
||||||
|
|
||||||
### Config not found
|
|
||||||
Set `MT5_MCP_HOME` environment variable or ensure config is at default location:
|
|
||||||
- macOS: `~/.config/mt5-quant/config/mt5-quant.yaml`
|
|
||||||
|
|
||||||
### Permission denied
|
|
||||||
```bash
|
|
||||||
chmod +x /path/to/mt5-quant
|
|
||||||
```
|
|
||||||
Binary file not shown.
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
||||||
"name": "io.github.masdevid/mt5-quant",
|
|
||||||
"title": "MT5-Quant",
|
|
||||||
"description": "MCP server for MT5 strategy development. 85 tools for backtesting, optimizing, and debugging MQL5 EAs on macOS/Linux.",
|
|
||||||
"repository": {
|
|
||||||
"url": "https://github.com/masdevid/mt5-quant",
|
|
||||||
"source": "github"
|
|
||||||
},
|
|
||||||
"version": "1.30.0",
|
|
||||||
"packages": [
|
|
||||||
{
|
|
||||||
"registryType": "mcpb",
|
|
||||||
"version": "1.30.0",
|
|
||||||
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.30.0/mcp-mt5-quant-macos-arm64.tar.gz",
|
|
||||||
"fileSha256": "PLACEHOLDER_SHA256",
|
|
||||||
"transport": {
|
|
||||||
"type": "stdio"
|
|
||||||
},
|
|
||||||
"environmentVariables": [
|
|
||||||
{
|
|
||||||
"description": "Path to MT5-Quant config directory (default: ~/.config/mt5-quant)",
|
|
||||||
"isRequired": false,
|
|
||||||
"format": "string",
|
|
||||||
"name": "MT5_MCP_HOME"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
+5
-5
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
||||||
"name": "io.github.masdevid/mt5-quant",
|
"name": "io.github.masdevid/mt5-quant",
|
||||||
"title": "MT5-Quant",
|
"title": "MT5-Quant",
|
||||||
"description": "MT5 strategy development with 85 tools. Backtest, optimize, debug MQL5 EAs on macOS/Linux.",
|
"description": "MT5 strategy development with 87 tools. Backtest, optimize, debug MQL5 EAs on macOS/Linux.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/masdevid/mt5-quant",
|
"url": "https://github.com/masdevid/mt5-quant",
|
||||||
"source": "github"
|
"source": "github"
|
||||||
},
|
},
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"registryType": "mcpb",
|
"registryType": "mcpb",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.30.0/mcp-mt5-quant-macos-arm64.tar.gz",
|
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.31.0/mcp-mt5-quant-macos-arm64.tar.gz",
|
||||||
"fileSha256": "6028b87c5be71db157c83e9804936190d9c3c546089e728b87496cbfc4168c0d",
|
"fileSha256": "b23ab29823c14bc2c968869ec9ce0787e60fe531f40f19e6492d0b3314f8223d",
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "stdio"
|
"type": "stdio"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::time::Duration;
|
||||||
|
use tokio::time::timeout as tokio_timeout;
|
||||||
|
|
||||||
use crate::models::Config;
|
use crate::models::Config;
|
||||||
|
|
||||||
@@ -28,7 +29,11 @@ impl MqlCompiler {
|
|||||||
Self { config }
|
Self { config }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(&self, source_path: &str) -> Result<CompileResult> {
|
pub async fn compile(&self, source_path: &str) -> Result<CompileResult> {
|
||||||
|
self.compile_with_timeout(source_path, Duration::from_secs(120)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn compile_with_timeout(&self, source_path: &str, timeout: Duration) -> Result<CompileResult> {
|
||||||
let source_path = Path::new(source_path);
|
let source_path = Path::new(source_path);
|
||||||
if !source_path.exists() {
|
if !source_path.exists() {
|
||||||
return Err(anyhow!("Source file not found: {}", source_path.display()));
|
return Err(anyhow!("Source file not found: {}", source_path.display()));
|
||||||
@@ -61,7 +66,7 @@ impl MqlCompiler {
|
|||||||
let staged_mq5 = &sync.dest_mq5;
|
let staged_mq5 = &sync.dest_mq5;
|
||||||
tracing::info!("Staged {} file(s) to: {}", sync.files_copied, staged_mq5.display());
|
tracing::info!("Staged {} file(s) to: {}", sync.files_copied, staged_mq5.display());
|
||||||
|
|
||||||
self.run_metaeditor(wine_exe, &wine_prefix, &metaeditor, staged_mq5)?;
|
self.run_metaeditor_with_timeout(wine_exe, &wine_prefix, &metaeditor, staged_mq5, timeout).await?;
|
||||||
|
|
||||||
// /log flag (no path) writes log adjacent to source: {ea_name}.log
|
// /log flag (no path) writes log adjacent to source: {ea_name}.log
|
||||||
let log_path = staged_mq5.with_extension("log");
|
let log_path = staged_mq5.with_extension("log");
|
||||||
@@ -197,15 +202,16 @@ impl MqlCompiler {
|
|||||||
Ok(SyncStats { dest_mq5, files_copied })
|
Ok(SyncStats { dest_mq5, files_copied })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run MetaEditor to compile `source_mq5`.
|
/// Run MetaEditor to compile `source_mq5` with timeout.
|
||||||
/// Uses Unix host path for /compile: and bare /log flag (writes log adjacent to source).
|
/// Uses Unix host path for /compile: and bare /log flag (writes log adjacent to source).
|
||||||
/// Shell script intermediary required on macOS to preserve DYLD_* vars past SIP.
|
/// Shell script intermediary required on macOS to preserve DYLD_* vars past SIP.
|
||||||
fn run_metaeditor(
|
async fn run_metaeditor_with_timeout(
|
||||||
&self,
|
&self,
|
||||||
wine_exe: &str,
|
wine_exe: &str,
|
||||||
wine_prefix: &Path,
|
wine_prefix: &Path,
|
||||||
metaeditor: &Path,
|
metaeditor: &Path,
|
||||||
source_mq5: &Path,
|
source_mq5: &Path,
|
||||||
|
timeout: Duration,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mt5_dir = metaeditor.parent().unwrap_or(metaeditor);
|
let mt5_dir = metaeditor.parent().unwrap_or(metaeditor);
|
||||||
|
|
||||||
@@ -242,16 +248,24 @@ impl MqlCompiler {
|
|||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?;
|
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?;
|
||||||
}
|
}
|
||||||
Command::new("/bin/sh").arg(&script_path).output()?;
|
let compile_future = tokio::process::Command::new("/bin/sh")
|
||||||
|
.arg(&script_path)
|
||||||
|
.output();
|
||||||
|
let result = tokio_timeout(timeout, compile_future).await
|
||||||
|
.map_err(|_| anyhow!("Compilation timed out after {} seconds", timeout.as_secs()))?;
|
||||||
|
result?;
|
||||||
} else {
|
} else {
|
||||||
Command::new(wine_exe)
|
let compile_future = tokio::process::Command::new(wine_exe)
|
||||||
.arg(metaeditor)
|
.arg(metaeditor)
|
||||||
.arg(format!("/compile:{}", source_mq5.display()))
|
.arg(format!("/compile:{}", source_mq5.display()))
|
||||||
.arg("/log")
|
.arg("/log")
|
||||||
.env("WINEPREFIX", wine_prefix)
|
.env("WINEPREFIX", wine_prefix)
|
||||||
.env("WINEDEBUG", "-all")
|
.env("WINEDEBUG", "-all")
|
||||||
.current_dir(mt5_dir)
|
.current_dir(mt5_dir)
|
||||||
.output()?;
|
.output();
|
||||||
|
let result = tokio_timeout(timeout, compile_future).await
|
||||||
|
.map_err(|_| anyhow!("Compilation timed out after {} seconds", timeout.as_secs()))?;
|
||||||
|
result?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
// Re-export chrono for BacktestJob
|
||||||
|
use chrono;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Report {
|
pub struct Report {
|
||||||
@@ -31,6 +34,8 @@ pub struct PipelineMetadata {
|
|||||||
pub report_dir: String,
|
pub report_dir: String,
|
||||||
pub duration_seconds: i64,
|
pub duration_seconds: i64,
|
||||||
pub files: FilePaths,
|
pub files: FilePaths,
|
||||||
|
#[serde(default)]
|
||||||
|
pub no_trades: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -48,6 +53,9 @@ pub struct BacktestStatus {
|
|||||||
pub elapsed_seconds: i64,
|
pub elapsed_seconds: i64,
|
||||||
pub is_complete: bool,
|
pub is_complete: bool,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
pub report_dir: Option<String>,
|
||||||
|
pub mt5_running: Option<bool>,
|
||||||
|
pub report_found: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -60,4 +68,43 @@ pub enum PipelineStage {
|
|||||||
Extract,
|
Extract,
|
||||||
Analyze,
|
Analyze,
|
||||||
Done,
|
Done,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track a running backtest job for fire-and-poll pattern
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BacktestJob {
|
||||||
|
pub report_id: String,
|
||||||
|
pub report_dir: String,
|
||||||
|
pub expert: String,
|
||||||
|
pub symbol: String,
|
||||||
|
pub timeframe: String,
|
||||||
|
pub launched_at: String,
|
||||||
|
pub mt5_pid: Option<u32>,
|
||||||
|
pub expected_report_path: String,
|
||||||
|
pub timeout_seconds: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BacktestJob {
|
||||||
|
pub fn new(
|
||||||
|
report_id: String,
|
||||||
|
report_dir: String,
|
||||||
|
expert: String,
|
||||||
|
symbol: String,
|
||||||
|
timeframe: String,
|
||||||
|
expected_report_path: String,
|
||||||
|
timeout_seconds: u64,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
report_id,
|
||||||
|
report_dir,
|
||||||
|
expert,
|
||||||
|
symbol,
|
||||||
|
timeframe,
|
||||||
|
launched_at: chrono::Utc::now().to_rfc3339(),
|
||||||
|
mt5_pid: None,
|
||||||
|
expected_report_path,
|
||||||
|
timeout_seconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+107
-7
@@ -8,7 +8,7 @@ use tokio::time::{sleep, Duration};
|
|||||||
use crate::analytics::{DealAnalyzer, ReportExtractor};
|
use crate::analytics::{DealAnalyzer, ReportExtractor};
|
||||||
use crate::compile::MqlCompiler;
|
use crate::compile::MqlCompiler;
|
||||||
use crate::models::config::Config;
|
use crate::models::config::Config;
|
||||||
use crate::models::report::{PipelineMetadata, FilePaths};
|
use crate::models::report::{PipelineMetadata, FilePaths, BacktestJob};
|
||||||
use crate::storage::{ReportDb, ReportEntry};
|
use crate::storage::{ReportDb, ReportEntry};
|
||||||
|
|
||||||
pub struct BacktestPipeline {
|
pub struct BacktestPipeline {
|
||||||
@@ -73,7 +73,7 @@ impl BacktestPipeline {
|
|||||||
|
|
||||||
if !params.skip_compile {
|
if !params.skip_compile {
|
||||||
self.log_progress(&progress_log, "COMPILE").await;
|
self.log_progress(&progress_log, "COMPILE").await;
|
||||||
self.compile_ea(¶ms.expert).await?;
|
self.compile_ea(¶ms.expert, params.timeout).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !params.skip_clean {
|
if !params.skip_clean {
|
||||||
@@ -90,6 +90,13 @@ impl BacktestPipeline {
|
|||||||
&report_dir.to_string_lossy(),
|
&report_dir.to_string_lossy(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Handle case where EA didn't trade - no deals generated
|
||||||
|
if extraction.deals.is_empty() {
|
||||||
|
tracing::warn!("Backtest completed but no deals were generated - EA did not trade during this period");
|
||||||
|
let warning_path = report_dir.join("NO_TRADES_WARNING.txt");
|
||||||
|
let _ = fs::write(&warning_path, "Warning: No deals were generated during this backtest.\nThe EA did not execute any trades during the specified date range.\n");
|
||||||
|
}
|
||||||
|
|
||||||
// Move equity chart images to OS temp dir, then delete the HTML report.
|
// Move equity chart images to OS temp dir, then delete the HTML report.
|
||||||
let charts_dir = self.relocate_charts(&report_path, &report_id).await;
|
let charts_dir = self.relocate_charts(&report_path, &report_id).await;
|
||||||
let _ = fs::remove_file(&report_path);
|
let _ = fs::remove_file(&report_path);
|
||||||
@@ -108,7 +115,7 @@ impl BacktestPipeline {
|
|||||||
self.log_progress(&progress_log, "DONE").await;
|
self.log_progress(&progress_log, "DONE").await;
|
||||||
|
|
||||||
let duration = (chrono::Utc::now() - start_time).num_seconds();
|
let duration = (chrono::Utc::now() - start_time).num_seconds();
|
||||||
self.save_metadata(¶ms, &report_dir, duration).await?;
|
self.save_metadata(¶ms, &report_dir, duration, extraction.deals.is_empty()).await?;
|
||||||
|
|
||||||
// Register in the SQLite report registry.
|
// Register in the SQLite report registry.
|
||||||
self.register_in_db(
|
self.register_in_db(
|
||||||
@@ -122,14 +129,105 @@ impl BacktestPipeline {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let message = if extraction.deals.is_empty() {
|
||||||
|
"Backtest completed successfully, but EA did not execute any trades during this period".to_string()
|
||||||
|
} else {
|
||||||
|
"Backtest completed successfully".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
Ok(PipelineResult {
|
Ok(PipelineResult {
|
||||||
success: true,
|
success: true,
|
||||||
report_dir,
|
report_dir,
|
||||||
duration_seconds: duration,
|
duration_seconds: duration,
|
||||||
message: "Backtest completed successfully".to_string(),
|
message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Launch backtest in fire-and-forget mode: compile, clean, launch MT5, return immediately.
|
||||||
|
/// Returns a BacktestJob that can be used with get_backtest_status to poll for completion.
|
||||||
|
pub async fn launch_backtest(&self, params: BacktestParams) -> Result<BacktestJob> {
|
||||||
|
let _start_time = chrono::Utc::now();
|
||||||
|
let report_id = self.generate_report_id(¶ms);
|
||||||
|
let report_dir = self.config.reports_dir().join(&report_id);
|
||||||
|
|
||||||
|
fs::create_dir_all(&report_dir)?;
|
||||||
|
|
||||||
|
let progress_log = report_dir.join("progress.log");
|
||||||
|
self.log_progress(&progress_log, "START").await;
|
||||||
|
|
||||||
|
if !params.skip_compile {
|
||||||
|
self.log_progress(&progress_log, "COMPILE").await;
|
||||||
|
self.compile_ea(¶ms.expert, params.timeout).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !params.skip_clean {
|
||||||
|
self.log_progress(&progress_log, "CLEAN").await;
|
||||||
|
self.clean_cache(¶ms.expert).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.log_progress(&progress_log, "BACKTEST").await;
|
||||||
|
|
||||||
|
// Get MT5 paths
|
||||||
|
let mt5_dir = self.config.mt5_dir()
|
||||||
|
.ok_or_else(|| anyhow!("MT5 directory not configured"))?;
|
||||||
|
let wine_exe = self.config.wine_executable.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("wine_executable not configured"))?;
|
||||||
|
let wine_prefix = mt5_dir
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.parent())
|
||||||
|
.and_then(|p| p.parent())
|
||||||
|
.map(|p| p.to_path_buf())
|
||||||
|
.ok_or_else(|| anyhow!("Could not determine Wine prefix from terminal_dir"))?;
|
||||||
|
let reports_dir = mt5_dir.join("reports");
|
||||||
|
fs::create_dir_all(&reports_dir)?;
|
||||||
|
|
||||||
|
// Write config files
|
||||||
|
let ini_content = self.build_backtest_ini(¶ms, &report_id)?;
|
||||||
|
let config_host = wine_prefix.join("drive_c").join("backtest_config.ini");
|
||||||
|
fs::write(&config_host, ini_content.as_bytes())?;
|
||||||
|
self.update_terminal_ini(¶ms, &report_id)?;
|
||||||
|
|
||||||
|
// Kill any running MT5
|
||||||
|
self.kill_mt5().await?;
|
||||||
|
|
||||||
|
// Launch MT5 (fire and forget)
|
||||||
|
let mut cmd = self.build_wine_launch(wine_exe, &wine_prefix)?;
|
||||||
|
let child = cmd.stdin(std::process::Stdio::null())
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.stderr(std::process::Stdio::null())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let pid = child.id();
|
||||||
|
tracing::info!("MT5 launched with PID {:?} for backtest {}", pid, report_id);
|
||||||
|
|
||||||
|
// Create and save the job tracking file
|
||||||
|
let expected_report = reports_dir.join(format!("{}.htm", report_id));
|
||||||
|
let job = BacktestJob::new(
|
||||||
|
report_id.clone(),
|
||||||
|
report_dir.to_string_lossy().to_string(),
|
||||||
|
params.expert.clone(),
|
||||||
|
params.symbol.clone(),
|
||||||
|
params.timeframe.clone(),
|
||||||
|
expected_report.to_string_lossy().to_string(),
|
||||||
|
params.timeout,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save job info for polling
|
||||||
|
let job_path = report_dir.join("job.json");
|
||||||
|
fs::write(&job_path, serde_json::to_string_pretty(&job)?)?;
|
||||||
|
|
||||||
|
// Save initial metadata
|
||||||
|
self.save_metadata(¶ms, &report_dir, 0, false).await?;
|
||||||
|
|
||||||
|
// Register in DB as "running"
|
||||||
|
let db = ReportDb::new(&Config::db_path());
|
||||||
|
if let Err(e) = db.init() {
|
||||||
|
tracing::warn!("Failed to init report DB: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(job)
|
||||||
|
}
|
||||||
|
|
||||||
/// Move equity chart images (*.png, *.gif) from MT5's reports dir to OS temp,
|
/// Move equity chart images (*.png, *.gif) from MT5's reports dir to OS temp,
|
||||||
/// returning the temp path if any images were found.
|
/// returning the temp path if any images were found.
|
||||||
async fn relocate_charts(&self, html_path: &Path, report_id: &str) -> Option<PathBuf> {
|
async fn relocate_charts(&self, html_path: &Path, report_id: &str) -> Option<PathBuf> {
|
||||||
@@ -232,7 +330,7 @@ impl BacktestPipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn compile_ea(&self, expert: &str) -> Result<()> {
|
async fn compile_ea(&self, expert: &str, timeout_secs: u64) -> Result<()> {
|
||||||
let mut search_paths = vec![
|
let mut search_paths = vec![
|
||||||
PathBuf::from(&self.config.get("project_dir")).join("src/experts").join(format!("{}.mq5", expert)),
|
PathBuf::from(&self.config.get("project_dir")).join("src/experts").join(format!("{}.mq5", expert)),
|
||||||
PathBuf::from(&self.config.get("project_dir")).join("src").join(format!("{}.mq5", expert)),
|
PathBuf::from(&self.config.get("project_dir")).join("src").join(format!("{}.mq5", expert)),
|
||||||
@@ -252,7 +350,8 @@ impl BacktestPipeline {
|
|||||||
.find(|p| p.exists())
|
.find(|p| p.exists())
|
||||||
.ok_or_else(|| anyhow!("Cannot find {}.mq5 — searched project_dir and MT5 Experts dir", expert))?;
|
.ok_or_else(|| anyhow!("Cannot find {}.mq5 — searched project_dir and MT5 Experts dir", expert))?;
|
||||||
|
|
||||||
let result = self.compiler.compile(&source_path.to_string_lossy())?;
|
let timeout = std::time::Duration::from_secs(timeout_secs.min(300)); // Max 5 min for compile
|
||||||
|
let result = self.compiler.compile_with_timeout(&source_path.to_string_lossy(), timeout).await?;
|
||||||
|
|
||||||
if !result.success {
|
if !result.success {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
@@ -761,7 +860,7 @@ impl BacktestPipeline {
|
|||||||
let _ = fs::write(log_path, line);
|
let _ = fs::write(log_path, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_metadata(&self, params: &BacktestParams, report_dir: &Path, duration: i64) -> Result<()> {
|
async fn save_metadata(&self, params: &BacktestParams, report_dir: &Path, duration: i64, no_trades: bool) -> Result<()> {
|
||||||
let metadata = PipelineMetadata {
|
let metadata = PipelineMetadata {
|
||||||
expert: params.expert.clone(),
|
expert: params.expert.clone(),
|
||||||
symbol: params.symbol.clone(),
|
symbol: params.symbol.clone(),
|
||||||
@@ -781,6 +880,7 @@ impl BacktestPipeline {
|
|||||||
deals_csv: report_dir.join("deals.csv").to_string_lossy().to_string(),
|
deals_csv: report_dir.join("deals.csv").to_string_lossy().to_string(),
|
||||||
deals_json: report_dir.join("deals.json").to_string_lossy().to_string(),
|
deals_json: report_dir.join("deals.json").to_string_lossy().to_string(),
|
||||||
},
|
},
|
||||||
|
no_trades,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string_pretty(&metadata)?;
|
let json = serde_json::to_string_pretty(&metadata)?;
|
||||||
|
|||||||
@@ -3,7 +3,85 @@ use serde_json::{json, Value};
|
|||||||
pub fn tool_run_backtest() -> Value {
|
pub fn tool_run_backtest() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"name": "run_backtest",
|
"name": "run_backtest",
|
||||||
"description": "Run a complete MT5 backtest pipeline: compile → clean cache → backtest → extract → analyze",
|
"description": "Full backtest pipeline: compile EA → clean cache → run backtest → extract results → analyze. Use this when you have modified the EA source code.",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["expert"],
|
||||||
|
"properties": {
|
||||||
|
"expert": { "type": "string", "description": "EA name without path or extension" },
|
||||||
|
"symbol": { "type": "string", "description": "Trading symbol (default: from config or first available)" },
|
||||||
|
"from_date": { "type": "string", "description": "Start date YYYY.MM.DD (default: past complete month)" },
|
||||||
|
"to_date": { "type": "string", "description": "End date YYYY.MM.DD (default: past complete month)" },
|
||||||
|
"timeframe": { "type": "string", "enum": ["M1", "M5", "M15", "M30", "H1", "H4", "D1"], "description": "Chart timeframe (default: M5)" },
|
||||||
|
"deposit": { "type": "integer", "description": "Initial deposit (default: 10000)" },
|
||||||
|
"model": { "type": "integer", "enum": [0, 1, 2], "description": "Tick model: 0=Every tick, 1=OHLC, 2=Open prices" },
|
||||||
|
"set_file": { "type": "string", "description": "Path to .set parameter file for EA inputs" },
|
||||||
|
"skip_compile": { "type": "boolean", "description": "Skip compilation (use existing .ex5)" },
|
||||||
|
"skip_clean": { "type": "boolean", "description": "Skip cache cleaning" },
|
||||||
|
"skip_analyze": { "type": "boolean", "description": "Skip analysis phase" },
|
||||||
|
"deep": { "type": "boolean", "description": "Run deep analysis with extra metrics" },
|
||||||
|
"shutdown": { "type": "boolean", "description": "Close MT5 after backtest completes" },
|
||||||
|
"kill_existing": { "type": "boolean", "description": "Kill any running MT5 instance first" },
|
||||||
|
"timeout": { "type": "integer", "description": "Max wait time in seconds (default: 900)" },
|
||||||
|
"gui": { "type": "boolean", "description": "Enable MT5 visualization window" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool_run_backtest_quick() -> Value {
|
||||||
|
json!({
|
||||||
|
"name": "run_backtest_quick",
|
||||||
|
"description": "Quick backtest using pre-compiled EA: clean cache → run backtest → extract → analyze. Skips compilation. Use when EA code hasn't changed.",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["expert"],
|
||||||
|
"properties": {
|
||||||
|
"expert": { "type": "string", "description": "EA name without path or extension (must have .ex5 compiled)" },
|
||||||
|
"symbol": { "type": "string", "description": "Trading symbol (default: from config or first available)" },
|
||||||
|
"from_date": { "type": "string", "description": "Start date YYYY.MM.DD (default: past complete month)" },
|
||||||
|
"to_date": { "type": "string", "description": "End date YYYY.MM.DD (default: past complete month)" },
|
||||||
|
"timeframe": { "type": "string", "enum": ["M1", "M5", "M15", "M30", "H1", "H4", "D1"], "description": "Chart timeframe (default: M5)" },
|
||||||
|
"deposit": { "type": "integer", "description": "Initial deposit (default: 10000)" },
|
||||||
|
"model": { "type": "integer", "enum": [0, 1, 2], "description": "Tick model" },
|
||||||
|
"set_file": { "type": "string", "description": "Path to .set parameter file for EA inputs" },
|
||||||
|
"deep": { "type": "boolean", "description": "Run deep analysis" },
|
||||||
|
"shutdown": { "type": "boolean", "description": "Close MT5 after backtest" },
|
||||||
|
"timeout": { "type": "integer", "description": "Max wait time in seconds (default: 900)" },
|
||||||
|
"gui": { "type": "boolean", "description": "Enable MT5 visualization" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool_run_backtest_only() -> Value {
|
||||||
|
json!({
|
||||||
|
"name": "run_backtest_only",
|
||||||
|
"description": "Backtest only: just run backtest and extract results. No compile, no analysis. Fastest option when you just need raw trade data.",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["expert"],
|
||||||
|
"properties": {
|
||||||
|
"expert": { "type": "string", "description": "EA name without path or extension (must have .ex5 compiled)" },
|
||||||
|
"symbol": { "type": "string", "description": "Trading symbol (default: from config)" },
|
||||||
|
"from_date": { "type": "string", "description": "Start date YYYY.MM.DD" },
|
||||||
|
"to_date": { "type": "string", "description": "End date YYYY.MM.DD" },
|
||||||
|
"timeframe": { "type": "string", "enum": ["M1", "M5", "M15", "M30", "H1", "H4", "D1"], "description": "Chart timeframe (default: M5)" },
|
||||||
|
"deposit": { "type": "integer", "description": "Initial deposit (default: 10000)" },
|
||||||
|
"model": { "type": "integer", "enum": [0, 1, 2], "description": "Tick model" },
|
||||||
|
"set_file": { "type": "string", "description": "Path to .set parameter file" },
|
||||||
|
"shutdown": { "type": "boolean", "description": "Close MT5 after backtest" },
|
||||||
|
"timeout": { "type": "integer", "description": "Max wait time (default: 900)" },
|
||||||
|
"gui": { "type": "boolean", "description": "Enable MT5 visualization" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool_launch_backtest() -> Value {
|
||||||
|
json!({
|
||||||
|
"name": "launch_backtest",
|
||||||
|
"description": "Launch MT5 backtest in fire-and-forget mode: compile → clean cache → launch MT5 backtest, then return immediately. Use get_backtest_status to poll for completion.",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["expert"],
|
"required": ["expert"],
|
||||||
@@ -18,12 +96,8 @@ pub fn tool_run_backtest() -> Value {
|
|||||||
"set_file": { "type": "string", "description": "Path to .set parameter file" },
|
"set_file": { "type": "string", "description": "Path to .set parameter file" },
|
||||||
"skip_compile": { "type": "boolean" },
|
"skip_compile": { "type": "boolean" },
|
||||||
"skip_clean": { "type": "boolean" },
|
"skip_clean": { "type": "boolean" },
|
||||||
"skip_analyze": { "type": "boolean" },
|
"timeout": { "type": "integer", "description": "Max time in seconds to wait for backtest (default: 900)" },
|
||||||
"deep": { "type": "boolean", "description": "Run deep analysis" },
|
"gui": { "type": "boolean", "description": "Enable visualization during backtest" }
|
||||||
"shutdown": { "type": "boolean", "description": "Close MT5 after backtest" },
|
|
||||||
"kill_existing": { "type": "boolean" },
|
|
||||||
"timeout": { "type": "integer" },
|
|
||||||
"gui": { "type": "boolean" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ pub mod utility;
|
|||||||
|
|
||||||
pub fn get_tools_list() -> Value {
|
pub fn get_tools_list() -> Value {
|
||||||
let tools = vec![
|
let tools = vec![
|
||||||
// Backtest
|
// Backtest - Granular options
|
||||||
backtest::tool_run_backtest(),
|
backtest::tool_run_backtest(), // Full pipeline: compile + clean + backtest + extract + analyze
|
||||||
backtest::tool_get_backtest_status(),
|
backtest::tool_run_backtest_quick(), // Quick: skip compile, do clean + backtest + extract + analyze
|
||||||
|
backtest::tool_run_backtest_only(), // Minimal: skip compile, do clean + backtest + extract only
|
||||||
|
backtest::tool_launch_backtest(), // Fire-and-forget: compile + clean + launch MT5
|
||||||
|
backtest::tool_get_backtest_status(), // Poll for completion
|
||||||
backtest::tool_cache_status(),
|
backtest::tool_cache_status(),
|
||||||
backtest::tool_clean_cache(),
|
backtest::tool_clean_cache(),
|
||||||
// Optimization
|
// Optimization
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ use serde_json::{json, Value};
|
|||||||
pub fn tool_run_optimization() -> Value {
|
pub fn tool_run_optimization() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"name": "run_optimization",
|
"name": "run_optimization",
|
||||||
"description": "Launch MT5 genetic parameter optimization",
|
"description": "Launch MT5 genetic parameter optimization in fire-and-forget mode. Returns immediately with job_id. Use get_optimization_status to poll for completion. Optimization typically runs for 2-6 hours.",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["expert", "set_file", "from_date", "to_date"],
|
"required": ["expert", "set_file", "from_date", "to_date"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"expert": { "type": "string" },
|
"expert": { "type": "string", "description": "EA name without path or extension" },
|
||||||
"set_file": { "type": "string" },
|
"set_file": { "type": "string", "description": "Path to .set file with parameter ranges for optimization" },
|
||||||
"symbol": { "type": "string" },
|
"symbol": { "type": "string", "description": "Trading symbol (default: XAUUSD)" },
|
||||||
"from_date": { "type": "string" },
|
"from_date": { "type": "string", "description": "Start date YYYY.MM.DD" },
|
||||||
"to_date": { "type": "string" },
|
"to_date": { "type": "string", "description": "End date YYYY.MM.DD" },
|
||||||
"deposit": { "type": "integer" }
|
"deposit": { "type": "integer", "description": "Initial deposit (default: 10000)" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -22,12 +22,12 @@ pub fn tool_run_optimization() -> Value {
|
|||||||
pub fn tool_get_optimization_status() -> Value {
|
pub fn tool_get_optimization_status() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"name": "get_optimization_status",
|
"name": "get_optimization_status",
|
||||||
"description": "Check progress of a running optimization job",
|
"description": "Check progress of a running optimization job. Poll periodically until status shows 'completed'.",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["job_id"],
|
"required": ["job_id"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"job_id": { "type": "string" }
|
"job_id": { "type": "string", "description": "Job ID returned by run_optimization" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -36,14 +36,14 @@ pub fn tool_get_optimization_status() -> Value {
|
|||||||
pub fn tool_get_optimization_results() -> Value {
|
pub fn tool_get_optimization_results() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"name": "get_optimization_results",
|
"name": "get_optimization_results",
|
||||||
"description": "Parse completed MT5 optimization results",
|
"description": "Parse completed MT5 optimization results and find best parameter combinations",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"job_id": { "type": "string" },
|
"job_id": { "type": "string", "description": "Job ID to parse results for" },
|
||||||
"report_file": { "type": "string" },
|
"report_file": { "type": "string", "description": "Direct path to optimization report XML file" },
|
||||||
"dd_threshold": { "type": "number" },
|
"dd_threshold": { "type": "number", "description": "Max drawdown percentage filter" },
|
||||||
"top_n": { "type": "integer" }
|
"top_n": { "type": "integer", "description": "Number of top passes to return (default: 30)" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -52,7 +52,7 @@ pub fn tool_get_optimization_results() -> Value {
|
|||||||
pub fn tool_list_jobs() -> Value {
|
pub fn tool_list_jobs() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"name": "list_jobs",
|
"name": "list_jobs",
|
||||||
"description": "List running and completed optimization jobs",
|
"description": "List all running and completed optimization jobs with their status",
|
||||||
"inputSchema": {
|
"inputSchema": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
|
|||||||
+217
-10
@@ -2,7 +2,9 @@ use anyhow::Result;
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
use crate::models::Config;
|
use crate::models::Config;
|
||||||
|
use crate::models::report::BacktestJob;
|
||||||
use crate::pipeline::backtest::{BacktestParams, BacktestPipeline};
|
use crate::pipeline::backtest::{BacktestParams, BacktestPipeline};
|
||||||
|
|
||||||
/// Pre-flight check result for backtest readiness
|
/// Pre-flight check result for backtest readiness
|
||||||
@@ -190,38 +192,243 @@ pub async fn handle_run_backtest(config: &Config, args: &Value) -> Result<Value>
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_run_backtest_quick(config: &Config, args: &Value) -> Result<Value> {
|
||||||
|
// Quick backtest: skip compile, do clean → backtest → extract → analyze
|
||||||
|
let mut args = args.clone();
|
||||||
|
if let Some(obj) = args.as_object_mut() {
|
||||||
|
obj.insert("skip_compile".to_string(), json!(true));
|
||||||
|
// keep skip_analyze as false (default) to run analysis
|
||||||
|
}
|
||||||
|
handle_run_backtest(config, &args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_run_backtest_only(config: &Config, args: &Value) -> Result<Value> {
|
||||||
|
// Backtest only: skip compile, skip analyze - just backtest and extract
|
||||||
|
let mut args = args.clone();
|
||||||
|
if let Some(obj) = args.as_object_mut() {
|
||||||
|
obj.insert("skip_compile".to_string(), json!(true));
|
||||||
|
obj.insert("skip_analyze".to_string(), json!(true));
|
||||||
|
}
|
||||||
|
handle_run_backtest(config, &args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_launch_backtest(config: &Config, args: &Value) -> Result<Value> {
|
||||||
|
let expert = args.get("expert")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("expert is required"))?;
|
||||||
|
|
||||||
|
// Run pre-flight check
|
||||||
|
let preflight = BacktestPreflight::check(config, expert);
|
||||||
|
|
||||||
|
// Check account session
|
||||||
|
if preflight.account.is_none() {
|
||||||
|
return Ok(json!({
|
||||||
|
"content": [{ "type": "text", "text": json!({
|
||||||
|
"error": "No active MT5 account session detected.",
|
||||||
|
"hint": "Open MT5 and login to your trading account before running backtests."
|
||||||
|
}).to_string() }],
|
||||||
|
"isError": true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get symbol
|
||||||
|
let requested_symbol = args.get("symbol")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
|
let symbol = if requested_symbol.is_empty() {
|
||||||
|
config.backtest_symbol.clone()
|
||||||
|
.or_else(|| preflight.available_symbols.first().cloned())
|
||||||
|
.unwrap_or_else(|| "EURUSD".to_string())
|
||||||
|
} else {
|
||||||
|
requested_symbol.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// EA existence check
|
||||||
|
if !preflight.ea_exists {
|
||||||
|
return Ok(json!({
|
||||||
|
"content": [{ "type": "text", "text": json!({
|
||||||
|
"error": format!("EA '{}' not found in Experts directory.", expert),
|
||||||
|
"hint": "Use search_experts or list_experts to find available EAs."
|
||||||
|
}).to_string() }],
|
||||||
|
"isError": true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date defaulting
|
||||||
|
let (from_date, to_date) = {
|
||||||
|
let f = args.get("from_date").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
let t = args.get("to_date").and_then(|v| v.as_str()).unwrap_or("");
|
||||||
|
if f.is_empty() || t.is_empty() {
|
||||||
|
super::past_complete_month()
|
||||||
|
} else {
|
||||||
|
(f.to_string(), t.to_string())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let params = BacktestParams {
|
||||||
|
expert: expert.to_string(),
|
||||||
|
symbol: symbol.to_string(),
|
||||||
|
from_date: from_date.to_string(),
|
||||||
|
to_date: to_date.to_string(),
|
||||||
|
timeframe: args.get("timeframe").and_then(|v| v.as_str()).unwrap_or("M5").to_string(),
|
||||||
|
deposit: args.get("deposit").and_then(|v| v.as_u64()).unwrap_or(10000) as u32,
|
||||||
|
model: args.get("model").and_then(|v| v.as_u64()).unwrap_or(0) as u8,
|
||||||
|
leverage: args.get("leverage").and_then(|v| v.as_u64()).unwrap_or(500) as u32,
|
||||||
|
set_file: args.get("set_file").and_then(|v| v.as_str()).map(|s| s.to_string()),
|
||||||
|
skip_compile: args.get("skip_compile").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||||
|
skip_clean: args.get("skip_clean").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||||
|
skip_analyze: true, // Not needed for launch mode
|
||||||
|
deep_analyze: false,
|
||||||
|
shutdown: false, // Don't shutdown so we can poll
|
||||||
|
kill_existing: false,
|
||||||
|
timeout: args.get("timeout").and_then(|v| v.as_u64()).unwrap_or(900),
|
||||||
|
gui: args.get("gui").and_then(|v| v.as_bool()).unwrap_or(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pipeline = BacktestPipeline::new(config.clone());
|
||||||
|
let job = pipeline.launch_backtest(params).await?;
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"content": [{ "type": "text", "text": json!({
|
||||||
|
"success": true,
|
||||||
|
"message": "Backtest launched successfully. Use get_backtest_status to poll for completion.",
|
||||||
|
"report_id": job.report_id,
|
||||||
|
"report_dir": job.report_dir,
|
||||||
|
"expert": job.expert,
|
||||||
|
"symbol": job.symbol,
|
||||||
|
"timeframe": job.timeframe,
|
||||||
|
"launched_at": job.launched_at,
|
||||||
|
"timeout_seconds": job.timeout_seconds,
|
||||||
|
"poll_hint": "Call get_backtest_status with report_dir to check progress"
|
||||||
|
}).to_string() }],
|
||||||
|
"isError": false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_get_backtest_status(_config: &Config, args: &Value) -> Result<Value> {
|
pub async fn handle_get_backtest_status(_config: &Config, args: &Value) -> Result<Value> {
|
||||||
let report_dir = args.get("report_dir")
|
let report_dir = args.get("report_dir")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("latest");
|
.unwrap_or("latest");
|
||||||
|
|
||||||
let progress_file = Path::new(report_dir).join("progress.log");
|
let report_path = Path::new(report_dir);
|
||||||
|
let progress_file = report_path.join("progress.log");
|
||||||
|
let job_file = report_path.join("job.json");
|
||||||
|
|
||||||
let status = if progress_file.exists() {
|
// Load job info if available
|
||||||
|
let job: Option<BacktestJob> = if job_file.exists() {
|
||||||
|
fs::read_to_string(&job_file)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| serde_json::from_str(&s).ok())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check progress log for stage
|
||||||
|
let (stage, progress_lines) = if progress_file.exists() {
|
||||||
if let Ok(content) = fs::read_to_string(&progress_file) {
|
if let Ok(content) = fs::read_to_string(&progress_file) {
|
||||||
let last_line = content.lines().last().unwrap_or("");
|
let lines: Vec<&str> = content.lines().collect();
|
||||||
if last_line.contains("DONE") {
|
let last_stage = lines.last()
|
||||||
"completed"
|
.and_then(|l| l.split_whitespace().next())
|
||||||
} else {
|
.unwrap_or("UNKNOWN");
|
||||||
"running"
|
(last_stage.to_string(), lines.len())
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
"unknown"
|
("UNKNOWN".to_string(), 0)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
("NOT_STARTED".to_string(), 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if MT5 is running
|
||||||
|
let mt5_running = is_mt5_running();
|
||||||
|
|
||||||
|
// Check if report file exists
|
||||||
|
let report_found = job.as_ref()
|
||||||
|
.map(|j| Path::new(&j.expected_report_path).exists())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
// Check for completed artifacts
|
||||||
|
let metrics_exists = report_path.join("metrics.json").exists();
|
||||||
|
let deals_exists = report_path.join("deals.csv").exists();
|
||||||
|
let is_complete = stage == "DONE" || (report_found && metrics_exists);
|
||||||
|
|
||||||
|
// Calculate elapsed time if job exists
|
||||||
|
let elapsed_seconds = job.as_ref()
|
||||||
|
.and_then(|j| {
|
||||||
|
chrono::DateTime::parse_from_rfc3339(&j.launched_at)
|
||||||
|
.ok()
|
||||||
|
.map(|t| (chrono::Utc::now() - t.with_timezone(&chrono::Utc)).num_seconds())
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
// Determine status message
|
||||||
|
let status_msg = if is_complete {
|
||||||
|
"completed"
|
||||||
|
} else if stage == "BACKTEST" && mt5_running {
|
||||||
|
"running"
|
||||||
|
} else if stage == "BACKTEST" && !mt5_running && !report_found {
|
||||||
|
"failed"
|
||||||
|
} else if progress_lines > 0 {
|
||||||
|
"in_progress"
|
||||||
} else {
|
} else {
|
||||||
"not_started"
|
"not_started"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let message = if is_complete {
|
||||||
|
"Backtest completed successfully"
|
||||||
|
} else if stage == "BACKTEST" && mt5_running {
|
||||||
|
"MT5 is running the backtest"
|
||||||
|
} else if stage == "BACKTEST" && !mt5_running {
|
||||||
|
"MT5 process exited but report not found - backtest may have failed"
|
||||||
|
} else {
|
||||||
|
&format!("Backtest is at stage: {}", stage)
|
||||||
|
};
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"content": [{ "type": "text", "text": json!({
|
"content": [{ "type": "text", "text": json!({
|
||||||
"success": true,
|
"success": true,
|
||||||
"report_dir": report_dir,
|
"report_dir": report_dir,
|
||||||
"status": status
|
"status": status_msg,
|
||||||
|
"stage": stage,
|
||||||
|
"is_complete": is_complete,
|
||||||
|
"mt5_running": mt5_running,
|
||||||
|
"report_found": report_found,
|
||||||
|
"metrics_extracted": metrics_exists,
|
||||||
|
"deals_extracted": deals_exists,
|
||||||
|
"elapsed_seconds": elapsed_seconds,
|
||||||
|
"message": message,
|
||||||
|
"job": job.map(|j| {
|
||||||
|
json!({
|
||||||
|
"report_id": j.report_id,
|
||||||
|
"expert": j.expert,
|
||||||
|
"symbol": j.symbol,
|
||||||
|
"timeframe": j.timeframe,
|
||||||
|
"launched_at": j.launched_at,
|
||||||
|
"timeout_seconds": j.timeout_seconds
|
||||||
|
})
|
||||||
|
})
|
||||||
}).to_string() }],
|
}).to_string() }],
|
||||||
"isError": false
|
"isError": false
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if MT5 is currently running
|
||||||
|
fn is_mt5_running() -> bool {
|
||||||
|
let patterns = if cfg!(target_os = "macos") {
|
||||||
|
vec!["MetaTrader 5\\.app", "terminal64\\.exe"]
|
||||||
|
} else {
|
||||||
|
vec!["terminal64\\.exe", "metatrader"]
|
||||||
|
};
|
||||||
|
|
||||||
|
patterns.iter().any(|pat| {
|
||||||
|
Command::new("pgrep")
|
||||||
|
.args(["-f", pat])
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_cache_status(config: &Config) -> Result<Value> {
|
pub async fn handle_cache_status(config: &Config) -> Result<Value> {
|
||||||
let cache_dir = config.tester_cache_dir.as_ref()
|
let cache_dir = config.tester_cache_dir.as_ref()
|
||||||
.map(|s| Path::new(s))
|
.map(|s| Path::new(s))
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ pub async fn handle_compile_ea(config: &Config, args: &Value) -> Result<Value> {
|
|||||||
let compiler = MqlCompiler::new(config.clone());
|
let compiler = MqlCompiler::new(config.clone());
|
||||||
let expert_path = resolved_path.as_str();
|
let expert_path = resolved_path.as_str();
|
||||||
|
|
||||||
match compiler.compile(&expert_path) {
|
match compiler.compile(&expert_path).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"content": [{ "type": "text", "text": json!({
|
"content": [{ "type": "text", "text": json!({
|
||||||
|
|||||||
@@ -42,8 +42,11 @@ impl ToolHandler {
|
|||||||
"copy_indicator_to_project" => experts::handle_copy_indicator_to_project(&self.config, args).await,
|
"copy_indicator_to_project" => experts::handle_copy_indicator_to_project(&self.config, args).await,
|
||||||
"copy_script_to_project" => experts::handle_copy_script_to_project(&self.config, args).await,
|
"copy_script_to_project" => experts::handle_copy_script_to_project(&self.config, args).await,
|
||||||
|
|
||||||
// Backtest handlers
|
// Backtest handlers - Granular pipeline options
|
||||||
"run_backtest" => backtest::handle_run_backtest(&self.config, args).await,
|
"run_backtest" => backtest::handle_run_backtest(&self.config, args).await, // Full: compile + clean + backtest + extract + analyze
|
||||||
|
"run_backtest_quick" => backtest::handle_run_backtest_quick(&self.config, args).await, // Quick: skip compile, do backtest + extract + analyze
|
||||||
|
"run_backtest_only" => backtest::handle_run_backtest_only(&self.config, args).await, // Minimal: skip compile, do backtest + extract only
|
||||||
|
"launch_backtest" => backtest::handle_launch_backtest(&self.config, args).await, // Fire-and-forget mode
|
||||||
"get_backtest_status" => backtest::handle_get_backtest_status(&self.config, args).await,
|
"get_backtest_status" => backtest::handle_get_backtest_status(&self.config, args).await,
|
||||||
"cache_status" => backtest::handle_cache_status(&self.config).await,
|
"cache_status" => backtest::handle_cache_status(&self.config).await,
|
||||||
"clean_cache" => backtest::handle_clean_cache(&self.config, args).await,
|
"clean_cache" => backtest::handle_clean_cache(&self.config, args).await,
|
||||||
|
|||||||
Reference in New Issue
Block a user