Compare commits

..
Author SHA1 Message Date
github-actions[bot]andDevid HW 6d900f5766 ci: update server.json SHA256 for v1.34.2 2026-08-25 06:21:42 +07:00
Devid HW e03acb108b docs(readme): fix Scorecard badge, add CI, crates.io, and license badges 2026-08-25 06:06:06 +07:00
Devid HW 785c99c80d ci(scorecard): rename invalid 'publish' input to 'publish_results' so results are published 2026-08-25 06:06:05 +07:00
Devid HW 0135ffa3b4 release: v1.34.2
- Version bump 1.34.1 → 1.34.2
- server.json identifier URL updated (SHA256 set by CI after build)
- CHANGELOG.md updated
2026-08-25 05:48:16 +07:00
Devid HW 4dded9b083 ci(release): publish to crates.io via trusted publishing (OIDC) 2026-08-25 05:46:34 +07:00
Devid HW 46e6de2474 fix: resolve clippy lints surfaced by newer toolchain in CI gate 2026-08-25 02:25:45 +07:00
Devid HW a23dbee52f fix(release): update server.json via auto-merged PR to satisfy branch ruleset 2026-08-25 02:07:38 +07:00
Devid HW ffe55863ba chore(templates): link Discussions from issue chooser 2026-08-25 01:57:22 +07:00
Devid HW ed82452200 chore: add CODEOWNERS 2026-08-25 01:52:45 +07:00
Devid HW 941be795c1 chore(templates): add issue forms and PR template 2026-08-25 01:52:17 +07:00
Devid HW 6a54b2f54a ci: gate PRs on fmt and clippy, add run concurrency groups 2026-08-25 01:50:26 +07:00
Devid HW e17e6b82fa ci(security): add OpenSSF Scorecard analysis and badge 2026-08-25 01:45:40 +07:00
Devid HW a6618e6811 ci(hardening): least-privilege GITHUB_TOKEN permissions across workflows 2026-08-25 01:45:15 +07:00
Devid HW e6111c421f ci(security): add cargo-deny checks for advisories, licenses, bans, sources 2026-08-25 01:44:40 +07:00
Devid HW 5056be3ee9 ci(security): add dependency review on PRs 2026-08-25 01:43:30 +07:00
Devid HW 059141a3d5 docs(security): add security policy 2026-08-25 01:43:15 +07:00
Devid HW 53e9cd6ee6 ci: upgrade actions to latest majors and pin by commit SHA 2026-08-25 01:37:05 +07:00
Devid HW 11e7b27fe2 chore: add MIT LICENSE file matching Cargo.toml declaration 2026-08-25 01:35:05 +07:00
Devid HW f1e4538b6a fix(ci): activate Dependabot for cargo and github-actions ecosystems 2026-08-25 01:34:25 +07:00
github-actions[bot] 415fb90f72 ci: update server.json SHA256 for v1.34.1 [skip ci] 2026-08-24 18:19:29 +00:00
Devid HW ce172a077f chore: broaden gitignore coverage for macOS metadata 2026-08-25 01:11:24 +07:00
Devid HW 9aa9bb83b7 chore: untrack AGENTS.md from version control
Keep agent instructions local-only; file remains on disk.
2026-08-25 01:02:59 +07:00
28 changed files with 487 additions and 301 deletions
+2
View File
@@ -0,0 +1,2 @@
# Default owners for everything
* @masdevid
+71
View File
@@ -0,0 +1,71 @@
name: Bug Report
description: Report a problem with mt5-quant (backtest, compile, analytics, MCP tools)
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug. Please keep answers concise — link gists for very long logs.
- type: textarea
id: description
attributes:
label: Description
description: A clear, one-paragraph summary of the bug.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: Include the MT5 command or MCP tool called, with its arguments (and EA/.set inputs if relevant).
placeholder: |
1. Call tool `run_backtest` with expert=..., symbol=XAUUSDc, from_date=...
2. Observe ...
validations:
required: true
- type: textarea
id: expected_vs_actual
attributes:
label: Expected vs Actual
description: What did you expect to happen, and what actually happened?
validations:
required: true
- type: dropdown
id: os
attributes:
label: OS
options:
- macOS (Apple Silicon)
- macOS (Intel)
- Linux
validations:
required: true
- type: dropdown
id: wine_setup
attributes:
label: Wine / CrossOver setup
options:
- CrossOver (macOS)
- MetaTrader 5.app bundled Wine (macOS)
- System Wine on Linux (Xvfb headless)
- Other / not sure
validations:
required: true
- type: input
id: mt5_build
attributes:
label: MT5 terminal build
description: e.g. 4885 (Help → About in MetaTrader 5)
- type: textarea
id: logs
attributes:
label: Relevant logs
description: Tester journal, MT5 logs, or MCP server output (wrap in code fences).
render: text
+6
View File
@@ -0,0 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Discussion
url: https://github.com/masdevid/mt5-quant/discussions
about: Ask questions and discuss ideas here instead of opening an issue
@@ -0,0 +1,30 @@
name: Feature Request
description: Suggest an idea or improvement for mt5-quant
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for suggesting a feature. Start with the problem — the solution can be discussed from there.
- type: textarea
id: problem
attributes:
label: Problem Statement
description: What are you trying to do, and what is missing or painful today? Name the MCP tool(s) involved, if any.
validations:
required: true
- type: textarea
id: proposed_solution
attributes:
label: Proposed Solution
description: What would you like to happen? Include rough tool names/arguments if it affects the MCP surface.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Other approaches, workarounds you've tried, or existing tools that almost fit.
+18
View File
@@ -0,0 +1,18 @@
## What & Why
<!-- Summarize the change and why it is needed. Link related issues. -->
## How Tested
- [ ] `cargo test` passes
- [ ] `cargo clippy --all-targets -- -D warnings` passes clean
- [ ] `cargo fmt --all -- --check` passes
- [ ] Verified against MT5/Wine locally (only if compile/backtest behavior changed)
## MCP Tool Impact
- [ ] No tool schema changes (if schemas changed, list affected tools below)
- [ ] Existing tool outputs unchanged (no breaking changes)
- [ ] Any new tool wired into definitions, handler, and dispatch table
<!-- Affected MCP tools: -->
+17 -6
View File
@@ -1,11 +1,22 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# Dependabot version updates: weekly grouped updates for Cargo dependencies
# and GitHub Actions.
# Configuration reference:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"
interval: "weekly"
groups:
minor-and-patch:
update-types: ["minor", "patch"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
github-actions:
patterns: ["*"]
+15
View File
@@ -0,0 +1,15 @@
name: Dependency Review
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Dependency Review
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
+95 -29
View File
@@ -11,19 +11,28 @@ on:
required: true
type: string
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
jobs:
# ── Build binaries ───────────────────────────────────────────────────────────
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with:
toolchain: stable
- name: Cache Cargo
uses: actions/cache@v4
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
@@ -46,7 +55,7 @@ jobs:
tar -czf mcp-mt5-quant-macos-arm64.tar.gz mcp-mt5-quant-macos
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: macos-binary
path: dist/mcp-mt5-quant-macos-arm64.tar.gz
@@ -54,13 +63,15 @@ jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with:
toolchain: stable
- name: Cache Cargo
uses: actions/cache@v4
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
@@ -83,7 +94,7 @@ jobs:
tar -czf mcp-mt5-quant-linux-x64.tar.gz mcp-mt5-quant-linux
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-binary
path: dist/mcp-mt5-quant-linux-x64.tar.gz
@@ -92,14 +103,19 @@ jobs:
cargo-publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for crates.io Trusted Publishing (OIDC)
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with:
toolchain: stable
- name: Cache Cargo
uses: actions/cache@v4
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
@@ -108,10 +124,14 @@ jobs:
key: cargo-publish-${{ hashFiles('Cargo.lock') }}
restore-keys: cargo-publish-
- name: Get crates.io authentication token
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
id: auth
- name: Publish to crates.io
run: cargo publish --no-verify --allow-dirty
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
# ── GitHub Release ───────────────────────────────────────────────────────────
@@ -124,22 +144,22 @@ jobs:
env:
RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download macOS artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: macos-binary
path: dist
- name: Download Linux artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: linux-binary
path: dist
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_TAG }}
@@ -166,10 +186,10 @@ jobs:
env:
RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download macOS binary
uses: actions/download-artifact@v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: macos-binary
path: dist
@@ -193,7 +213,7 @@ jobs:
echo "MCP Package SHA256: $SHA256"
- name: Upload MCP package to release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ env.RELEASE_TAG }}
files: mcp-mt5-quant-macos-arm64.tar.gz
@@ -201,22 +221,26 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload MCP package artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mcp-package
path: mcp-mt5-quant-macos-arm64.tar.gz
# ── Update server.json SHA256 ────────────────────────────────────────────────
# Commits the computed SHA256 back to master so server.json always
# reflects the latest published package hash.
# Updates server.json on master via an auto-merged PR instead of a direct
# push: the protect-master ruleset requires build/lint/cargo-deny status
# checks, which github-actions[bot] cannot bypass on a direct push. The PR
# runs those checks legitimately, then auto-merges once green. This job
# blocks until the merge lands so mcp-publish reads the patched server.json.
update-server-json:
needs: build-mcp-package
runs-on: ubuntu-latest
permissions:
contents: write
contents: write # push the ci/* release branch
pull-requests: write # create the PR and enable auto-merge
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: master
fetch-depth: 1
@@ -256,19 +280,61 @@ jobs:
print(f"Patched: version={version}, sha256={sha256[:16]}...")
PYEOF
- name: Commit server.json
- name: Commit server.json to release branch
id: commit
run: |
BRANCH="ci/server-json-sha-${VERSION}"
# Drop any stale branch/PR left over from a previous attempt at
# this release so every run starts from a clean slate
git push origin --delete "$BRANCH" 2>/dev/null || true
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add server.json
if git diff --cached --quiet; then
echo "server.json unchanged — no commit needed"
echo "changed=false" >> "$GITHUB_OUTPUT"
else
git commit -m "ci: update server.json SHA256 for v${VERSION} [skip ci]"
git push origin master
echo "Committed server.json update"
git push origin "$BRANCH"
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Committed server.json update to ${BRANCH}"
fi
- name: Create PR and enable auto-merge
if: steps.commit.outputs.changed == 'true'
run: |
BRANCH="ci/server-json-sha-${VERSION}"
GH_TOKEN="${{ github.token }}" gh pr create \
--title "ci: update server.json SHA256 for v${VERSION} [skip ci]" \
--body "Automated MCP package hash update." \
--base master \
--head "$BRANCH"
GH_TOKEN="${{ github.token }}" gh pr merge "$BRANCH" --auto --squash
# Block until required checks (build, lint, cargo-deny x4) finish
# and auto-merge completes — mcp-publish depends on this job and
# checks out master expecting the patched server.json
DEADLINE=$((SECONDS + 1800))
while (( SECONDS < DEADLINE )); do
STATE=$(GH_TOKEN="${{ github.token }}" gh pr view "$BRANCH" --json state --jq .state)
case "$STATE" in
MERGED)
echo "server.json PR merged to master"
exit 0
;;
CLOSED)
echo "::error::server.json PR was closed without merging" >&2
exit 1
;;
esac
sleep 20
done
echo "::error::Timed out after 30m waiting for server.json PR auto-merge (required checks: build, lint, cargo-deny x4)" >&2
exit 1
# ── MCP Registry Publish ─────────────────────────────────────────────────────
mcp-publish:
@@ -278,12 +344,12 @@ jobs:
contents: read
id-token: write # Required for GitHub OIDC
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: master # Pull master so server.json has the correct SHA256
- name: Download MCP package
uses: actions/download-artifact@v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: mcp-package
path: .
+43 -1
View File
@@ -6,6 +6,13 @@ on:
pull_request:
branches: [ "master" ]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
env:
CARGO_TERM_COLOR: always
@@ -15,8 +22,43 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with:
toolchain: stable
components: clippy, rustfmt
- name: Cache Cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: linux-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: linux-cargo-
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
cargo-deny:
runs-on: ubuntu-latest
strategy:
matrix:
checks: [advisories, bans, licenses, sources]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: embarkstudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1
with:
command: check ${{ matrix.checks }}
log-level: warn
+38
View File
@@ -0,0 +1,38 @@
name: Scorecard supply-chain security
on:
branch_protection_rule:
schedule:
- cron: '30 6 * * 3'
push:
branches: [master]
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
- uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
with:
sarif_file: results.sarif
+11 -1
View File
@@ -35,8 +35,18 @@ AGENTS.md
# Codegraph
.codegraph/
# macOS
# macOS metadata
.DS_Store
.AppleDouble
.LSOverride
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Logs
*.log
-245
View File
@@ -1,245 +0,0 @@
# AGENTS.md — Developer instructions for MT5-Quant
## Project Overview
Open-source MCP server for MetaTrader 5 backtesting, optimization, and analytics — written entirely in Rust. Exposes 89 stdio MCP tools for compiling MQL5 EAs, running backtests via Wine, extracting deal-level data, organizing reports in SQLite, and performing 19 dimensions of analysis.
Target users: MQL developers on macOS (CrossOver or native MetaTrader5.app) and Linux (Wine/Xvfb).
## Repository Layout
```
MT5-Quant/
├── src/
│ ├── main.rs # CLI entry + stdio MCP transport
│ ├── mcp_server.rs # McpServer struct, tool dispatch, init/notification
│ ├── mt5.rs # Wine/MT5 executable launchers
│ ├── models/
│ │ ├── mod.rs
│ │ ├── config.rs # Config, CurrentAccount, wine detection, .set helpers
│ │ ├── deals.rs # Deal, PositionPair, DrawdownEvent
│ │ ├── metrics.rs # BacktestMetrics parsing (HTML + XML formats)
│ │ └── report.rs # ReportEntry, PipelineMetadata, BacktestJob, status
│ ├── analytics/
│ │ ├── mod.rs
│ │ ├── extract.rs # HTML/XML report → Deal[] + metrics
│ │ └── analyze.rs # DealAnalyzer: 19+ analysis methods
│ ├── compile/
│ │ ├── mod.rs
│ │ └── mql_compiler.rs # MetaEditor compiler via Wine
│ ├── pipeline/
│ │ ├── mod.rs
│ │ ├── backtest.rs # 5-stage pipeline: COMPILE→CLEAN→BACKTEST→EXTRACT→ANALYZE
│ │ └── stages.rs # PipelineStage enum
│ ├── optimization/
│ │ ├── mod.rs
│ │ ├── optimizer.rs # Background optimization launcher
│ │ └── parser.rs # Optimization result XML → best params
│ ├── storage/
│ │ ├── mod.rs
│ │ └── database.rs # ReportDb: reports + deals SQLite tables, queries
│ ├── utils/ # (new) shared utilities — growing directory
│ └── tools/
│ ├── mod.rs # MCP definitions aggregation
│ ├── definitions/ # Tool schemas (JSON Schema) — 11 modules, 90 tools
│ │ ├── mod.rs # exports: all_tools(), system_tools, etc.
│ │ ├── analytics.rs # 19 analysis tools + compare_baseline
│ │ ├── backtest.rs # 7 backtest tools
│ │ ├── baseline.rs # 1 baseline tool
│ │ ├── experts.rs # 9 EA/indicator/script tools
│ │ ├── optimization.rs # 4 optimization tools
│ │ ├── reports.rs # 20 report management tools
│ │ ├── setfiles.rs # 8 .set file tools
│ │ ├── system.rs # 6 system/health tools + version update
│ │ ├── update.rs # update tool definition
│ │ └── utility.rs # 10 pre-flight/compat tools
│ └── handlers/ # Tool dispatch functions — 11 modules
│ ├── mod.rs # ToolHandler struct, dispatch table (89+ cases)
│ ├── analysis.rs # analytics/analyze dispatch
│ ├── backtest.rs # pipeline/backtest dispatch
│ ├── experts.rs # wine + WalkDir helpers: scan_mql_dir, copy_mql_to_project
│ ├── optimization.rs # optimizer dispatch
│ ├── reports.rs # ReportDb + ReportEntry dispatch
│ ├── setfiles.rs # UTF-16LE .set read/write/patch helpers
│ ├── system.rs # healthcheck, version update
│ ├── utility.rs # pre-flight, diagnostics, init_project helpers
│ └── update.rs # update tool handler
├── scripts/
│ ├── setup.sh # Auto-detect Wine/MT5, write config (1271 lines)
│ ├── platform_detect.sh # Wine path + headless detection
│ ├── build-rust.sh # cargo build --release (25 lines)
│ ├── release.sh # Version bump + changelog + tag + push (238 lines)
│ └── optimize.sh # Legacy optimization driver (pending full Rust migration)
├── analytics/ # Legacy Python (reference only — do not modify)
├── config/
│ ├── mt5-quant.example.yaml # Template user copies to mt5-quant.yaml
│ └── mt5-quant.yaml # Generated config (gitignored)
├── tests/
│ ├── test_mcp.py # End-to-end MCP tests (stdio-driven)
│ ├── integration_test.sh # Shell-based integration tests
│ └── fixtures/ # Sample reports + CSVs for testing
├── server.json # MCP registry manifest (tracked)
├── Cargo.toml # version = 1.34.0
└── CHANGELOG.md
```
## Key Design Constraints
- **Headless/GUI**: `platform_detect.sh` auto-selects. macOS CrossOver = GUI (CrossOver abstracts display). Linux without `$DISPLAY` = Xvfb. Controlled by `display.mode` in config (`auto` | `gui` | `headless`).
- **Single MT5 instance**: Never run two backtests in parallel on the same Wine prefix.
- **Model 0 only for optimization**: Model 1 overfits martingale/grid EAs — intra-bar movement not simulated. Use `model=0` (every tick).
- **UTF-16LE .set files**: MT5 strips `||Y` flags from UTF-8. All `.set` writes use UTF-16LE + `chmod 444`. See `setfiles.rs` helpers.
- **OptMode reset**: After any optimization, `terminal.ini` gets `OptMode=-1`. Must reset to `0` before next backtest (handled in `backtest.rs` cleanup stage).
- **Report format detection**: MT5 Build 48+ writes SpreadsheetML XML (`.htm.xml`), not HTML. Both formats handled in `extract.rs` — always check for the XML variant first.
- **EA-agnostic**: Never hardcode magic numbers, strategy names, or assume specific EA behavior. All code must work with any MQL5 EA.
## Architecture
1. **CLI (`main.rs`)** — parses CLi args (`--stdio`, `--port`, `--test-launch`), initializes `McpServer`, runs stdio or TCP transport loop, handles JSON-RPC requests.
2. **McpServer (`mcp_server.rs`)** — wraps `ToolHandler` with `Arc<Mutex>`, handles `initialize`/`notifications`/`tool/error` method routing. Auto-verifies Wine/MT5 on first call.
3. **ToolHandler (`tools/handlers/mod.rs`)** — dispatch table mapping 89 tool names to handler functions. Each handler module is self-contained.
4. **Definitions (`tools/definitions/`)** — JSON Schema objects for tool input/output. Never executes logic.
5. **Analytics (`analytics/`)** — two core structs:
- `ReportExtractor` — reads HTML/XML report + tester log → `Vec<Deal>` + `BacktestMetrics`
- `DealAnalyzer` — takes `Vec<Deal>` → 19 analysis methods (monthly PnL, drawdown events, streaks, etc.)
6. **Pipeline (`pipeline/backtest.rs`)** — `BacktestPipeline` struct with 5 stages:
- `COMPILE` — `MqlCompiler::compile()` via MetaEditor
- `CLEAN` — clean cache, reset OptMode
- `BACKTEST` — launch MT5 tester via Wine, poll status
- `EXTRACT` — parse HTML/XML → deals + metrics
- `ANALYZE` — run all analytics, write to SQLite
7. **Storage (`storage/database.rs`)** — `ReportDb` with two tables:
- `reports` — metrics, set file paths, verdict, tags, notes
- `deals` — individual deal rows, keyed by `report_id`
- `CREATE TABLE IF NOT EXISTS` in `init()` — idempotent on startup
## Building
```bash
bash scripts/build-rust.sh
# or
cargo build --release
```
Binary: `target/release/mt5-quant`
## Testing
- **Unit/integration:** `cargo test`
- **End-to-end MCP:** `python3 tests/test_mcp.py` — drives the MCP server over stdio (full backtest + all analytics)
- **Analytics only:** No MT5/Wine needed; loads deals from SQLite DB via `db.get_deals(report_id)`
- **Symbol note:** Testing account uses `XAUUSDc` (broker suffix required), not `XAUUSD`
## Configuration
Users copy `config/mt5-quant.example.yaml` → `config/mt5-quant.yaml` (gitignored).
Run `bash scripts/setup.sh` to auto-detect Wine, MT5 paths, architecture, and write config.
Minimum config:
```yaml
wine_executable: "/path/to/wine64"
terminal_dir: "/path/to/MetaTrader 5"
```
Optional:
```yaml
defaults:
symbol: "XAUUSD.cent"
timeframe: "M5"
deposit: 10000
display:
mode: auto # auto | gui | headless
reports_dir: "./reports"
experts_dir: "~/.../MQL5/Experts"
```
## Developing New Tools
Each new tool follows a 4-file pattern:
1. **Schema** — Add tool input/output to `tools/definitions/{domain}.rs` via `Tool::new(name) {...}` with `Input::schema` and `Output::schema`.
2. **Export** — Add to `tools/definitions/mod.rs`:
- Include in `all_tools()` array
- Add to domain exports if creating new domain
3. **Handler** — Add function in `tools/handlers/{domain}.rs`:
```rust
pub async fn handle_tool_name(config: &Config, args: &Value) -> Result<Value> {
let required = required_str(args, "param_name")?;
ok_response(json! { "result": "..." })
}
```
4. **Dispatch** — Add case in `handlers/mod.rs` match arm under `ToolHandler::handle()`.
### Handler Helper Functions (use instead of inline logic)
| Helper | Purpose |
|--------|---------|
| `required_str(args, key)` | Extract required `&str` or return error |
| `ok_response(data)` / `err_response(msg)` | Wrap in MCP content envelope |
| `resolve_report(args)` | → `(deals, metrics, report_dir)` — `report_id > report_dir > latest` |
| `prepare_analysis(args)` | → `(deals, metrics, analyzer, report_dir)` — wraps resolve_report |
| `scan_mql_dir(dir, filter, type_label)` | WalkDir with optional filter for list/search |
| `copy_mql_to_project(config, args, fallback)` | Shared copy logic for indicator/script |
Extend helpers when adding new handlers.
## Commit Style
Conventional commits: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `release:`
Keep commits focused — one logical change per commit.
## Release Process
```bash
bash scripts/release.sh <patch|minor|major|X.Y.Z> [--yes]
```
Script handles:
1. Bump `Cargo.toml` + `Cargo.lock` version
2. Update `server.json` (version + SHA256 placeholder)
3. Generate CHANGELOG entry from git log
4. `cargo check --quiet`
5. Commit `release: vX.Y.Z`
6. Tag `vX.Y.Z`
7. Push — CI (`release.yml`) triggered:
- Build macOS arm64 + Linux x64 binaries
- Publish to crates.io
- Create GitHub Release with artifacts
- Build MCP package, compute SHA256
- Update `server.json` SHA256, push
- Publish to MCP Registry
## Deal Storage
- Deals stored in `deals` SQLite table, keyed by `report_id`. **No `deals.csv` written automatically.**
- `db.insert_deals(report_id, &[Deal])` called in pipeline after extraction.
- `db.get_deals(report_id)` loads deals for analytics. `db.get_by_report_dir(path)` resolves by filesystem path.
- `export_deals_csv` tool writes CSV on demand.
## DB Schema Evolution
Add new tables/columns in `ReportDb::init()` using `CREATE TABLE IF NOT EXISTS` / `CREATE INDEX IF NOT EXISTS` — runs on every startup, is idempotent. No migration files needed.
## Wine / MT5 Notes
- Wine executable paths differ across platforms:
- macOS MT5.app: `/Applications/MetaTrader 5.app/Contents/SharedSupport/wine/bin/wine64`
- macOS CrossOver: `/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wine64`
- Linux: `/usr/bin/wine64` (or first `wine64` on PATH)
- MT5 terminal_dir is auto-resolved from wine_executable path.
- Apple Silicon: append `arch -x86_64` prefix for x86 Wine binaries (auto-detected).
## Common Pitfalls
- **M1/Memory issues**: Wine on Apple Silicon runs x86 via Rosetta 2. Watch memory pressure.
- **Model mismatch**: Grid/martingale EAs need `model=0` (every tick). Model 1/2 will produce zero deals.
- **Set file encoding**: Always use UTF-16LE with `chmod 444`. UTF-8 strips `||Y` flags.
- **OptMode=-1**: Must clear after optimization. Handled in pipeline cleanup stage.
- **Report not found**: Check `<terminal_dir>/history/` for correct symbol name (broker suffix matters: `XAUUSDc` vs `XAUUSD`).
- **Journal-only backtest**: `ShutdownTerminal=1` doesn't exit MT5 on Wine. Inactivity watchdog (in `launch_backtest`) handles this with `inactivity_kill_secs` param.
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## [1.34.2] — 2026-08-25
- ci(release): publish to crates.io via trusted publishing (OIDC)
- fix: resolve clippy lints surfaced by newer toolchain in CI gate
- fix(release): update server.json via auto-merged PR to satisfy branch ruleset
- chore(templates): link Discussions from issue chooser
- chore(templates): add issue forms and PR template
- ci(security): add OpenSSF Scorecard analysis and badge
- ci(hardening): least-privilege GITHUB_TOKEN permissions across workflows
- ci(security): add cargo-deny checks for advisories, licenses, bans, sources
- ci(security): add dependency review on PRs
- docs(security): add security policy
- fix(ci): activate Dependabot for cargo and github-actions ecosystems
## [1.34.1] — 2026-08-25
- fix: remove PayPal funding link, bump anyhow for RUSTSEC-2026-0190, resolve all clippy warnings
Generated
+1 -1
View File
@@ -481,7 +481,7 @@ dependencies = [
[[package]]
name = "mt5-quant"
version = "1.34.1"
version = "1.34.2"
dependencies = [
"anyhow",
"base64",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mt5-quant"
version = "1.34.1"
version = "1.34.2"
edition = "2021"
description = "MCP server for MT5 strategy development on macOS/Linux"
authors = ["masdevid <masdevid@example.com>"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 masdevid
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+2
View File
@@ -1,5 +1,7 @@
# MT5-Quant
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/masdevid/mt5-quant/badge)](https://scorecard.dev/viewer/?uri=github.com/masdevid/mt5-quant) [![Rust CI](https://github.com/masdevid/mt5-quant/actions/workflows/rust.yml/badge.svg)](https://github.com/masdevid/mt5-quant/actions/workflows/rust.yml) [![crates.io](https://img.shields.io/crates/v/mt5-quant.svg)](https://crates.io/crates/mt5-quant) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
**MCP server for MT5 strategy development on macOS/Linux.** 89 tools to compile, backtest, analyze, optimize, debug crashes, and manage MQL5 Expert Advisors — no Windows required.
```
+35
View File
@@ -0,0 +1,35 @@
# Security Policy
## Supported Versions
Only the latest minor release line receives security fixes. Older versions should upgrade.
| Version | Supported |
| ------- | ------------------ |
| 1.34.x | :white_check_mark: |
| < 1.34 | :x: |
## Reporting a Vulnerability
**Please do not open public GitHub issues for security matters.**
Preferred: use GitHub [Private Vulnerability Reporting](https://github.com/masdevid/mt5-quant/security/advisories/new) — **Security tab → Report a vulnerability**. This keeps the report confidential while we work on a fix.
Please include as much of the following as you can:
- Affected version (and commit SHA, if relevant)
- Steps to reproduce or a proof of concept
- Expected vs. actual behavior
- Any known mitigations
## Response Commitment
- **Acknowledgement:** within **72 hours** of your report
- **Fix target:** vulnerabilities rated **critical/high** are targeted for a patch release within **14 days**
- We will keep you informed of progress and credit you in the advisory unless you prefer otherwise
## Scope Notes
MT5-Quant drives Wine and MetaTrader 5 locally — it launches external processes with paths, arguments, and configuration sourced from its YAML config file, `.set` parameter files, and MCP tool arguments. Reports concerning path traversal, config injection, or unsafe command construction through these surfaces are very much in scope and welcome.
Out of scope: vulnerabilities in Wine, MetaTrader 5 itself, or broker infrastructure — please report those upstream to the respective vendors.
+27
View File
@@ -0,0 +1,27 @@
# cargo-deny configuration — https://embarkstud.io.github.io/cargo-deny/
[advisories]
version = 2
yanked = "deny"
[licenses]
version = 2
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"Unicode-3.0",
"Zlib",
"CDLA-Permissive-2.0",
# via option-ext ← dirs-sys ← dirs
"MPL-2.0",
]
[bans]
multiple-versions = "warn"
[sources]
unknown-registry = "warn"
+4 -4
View File
@@ -7,13 +7,13 @@
"url": "https://github.com/masdevid/mt5-quant",
"source": "github"
},
"version": "1.34.1",
"version": "1.34.2",
"packages": [
{
"registryType": "mcpb",
"version": "1.34.1",
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.34.1/mcp-mt5-quant-macos-arm64.tar.gz",
"fileSha256": "TBD_CI_WILL_UPDATE",
"version": "1.34.2",
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.34.2/mcp-mt5-quant-macos-arm64.tar.gz",
"fileSha256": "8c26ac2384654c550e23995ab8646cb78f6be04b809ee92acf33e949315aa310",
"transport": {
"type": "stdio"
},
+1 -1
View File
@@ -412,7 +412,7 @@ impl DealAnalyzer {
}
}
events.sort_by(|a, b| a.0.cmp(&b.0));
events.sort_by_key(|event| event.0);
let mut count = 0;
let mut peak = 0;
+3 -1
View File
@@ -334,7 +334,9 @@ impl ReportExtractor {
if raw.starts_with(&[0xFF, 0xFE]) || raw.starts_with(&[0xFE, 0xFF]) {
// UTF-16 BOM
let text = String::from_utf16_lossy(
raw.chunks_exact(2)
raw.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect::<Vec<_>>()
.as_slice(),
+3 -1
View File
@@ -359,7 +359,9 @@ impl MqlCompiler {
};
if raw.starts_with(&[0xFF, 0xFE]) {
raw[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.filter_map(|c| char::from_u32(c as u32))
.collect()
+3 -1
View File
@@ -16,7 +16,9 @@ fn read_file_as_utf8(path: &Path) -> Result<String> {
if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
// UTF-16LE with BOM - skip the 2-byte BOM and decode
let utf16_data: Vec<u16> = bytes[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
String::from_utf16(&utf16_data).map_err(|e| anyhow!("Failed to decode UTF-16LE: {}", e))
+9 -3
View File
@@ -106,14 +106,18 @@ impl OptimizationParser {
if raw[0] == 0xFF && raw[1] == 0xFE {
// UTF-16 LE with BOM
let u16_vec: Vec<u16> = raw[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect();
return Ok(String::from_utf16_lossy(&u16_vec));
} else if raw[0] == 0xFE && raw[1] == 0xFF {
// UTF-16 BE with BOM
let u16_vec: Vec<u16> = raw[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_be_bytes([c[0], c[1]]))
.collect();
return Ok(String::from_utf16_lossy(&u16_vec));
@@ -128,7 +132,9 @@ impl OptimizationParser {
// Try UTF-16 without BOM
if raw.len() % 2 == 0 {
let u16_vec: Vec<u16> = raw
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect();
let text = String::from_utf16_lossy(&u16_vec);
+12 -4
View File
@@ -22,7 +22,9 @@ fn read_set_file_as_utf8(path: &Path) -> Result<String> {
let bytes = fs::read(path)?;
if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
let utf16_data: Vec<u16> = bytes[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
String::from_utf16(&utf16_data).map_err(|e| anyhow!("Failed to decode UTF-16LE: {}", e))
@@ -870,7 +872,9 @@ impl BacktestPipeline {
let text = if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
// UTF-16 LE with BOM
let words: Vec<u16> = bytes[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect();
String::from_utf16_lossy(&words).to_string()
@@ -1288,7 +1292,9 @@ impl BacktestPipeline {
if content.starts_with(&[0xFF, 0xFE]) || content.starts_with(&[0xFE, 0xFF]) {
let text = String::from_utf16_lossy(
content
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect::<Vec<_>>()
.as_slice(),
@@ -1450,7 +1456,9 @@ impl BacktestPipeline {
let raw = fs::read(&terminal_ini).unwrap_or_default();
let text = if raw.starts_with(&[0xFF, 0xFE]) {
raw[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect::<Vec<_>>()
.iter()
+3 -1
View File
@@ -8,7 +8,9 @@ fn read_file_as_utf8(path: &str) -> Result<String> {
let bytes = fs::read(path)?;
if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
let utf16_data: Vec<u16> = bytes[2..]
.chunks_exact(2)
.as_chunks::<2>()
.0
.iter()
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
String::from_utf16(&utf16_data)
+1 -1
View File
@@ -1207,7 +1207,7 @@ pub async fn handle_get_mt5_logs(config: &Config, args: &Value) -> Result<Value>
}
// Sort by modification time (newest first)
log_files.sort_by(|a, b| b.1.cmp(&a.1));
log_files.sort_by_key(|file| std::cmp::Reverse(file.1));
if let Some((latest_log, _)) = log_files.first() {
result["found"] = json!(true);