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
26 changed files with 347 additions and 2120 deletions
-35
View File
@@ -1,35 +0,0 @@
name: CodeQL
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: '22 4 * * 1'
workflow_dispatch:
permissions:
contents: read
jobs:
analyze:
name: Analyze (Rust)
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize CodeQL
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
with:
languages: rust
- name: Autobuild
uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
-1
View File
@@ -4,7 +4,6 @@ on: [pull_request]
permissions: permissions:
contents: read contents: read
pull-requests: read
jobs: jobs:
dependency-review: dependency-review:
+52 -148
View File
@@ -27,7 +27,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1 uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with: with:
toolchain: stable toolchain: stable
@@ -66,7 +66,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1 uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with: with:
toolchain: stable toolchain: stable
@@ -110,7 +110,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1 uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with: with:
toolchain: stable toolchain: stable
@@ -143,11 +143,6 @@ jobs:
contents: write contents: write
env: env:
RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }} RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }}
# secrets context is NOT available in step-level if: expressions —
# referencing it there makes the whole workflow fail validation
# (startup-failure runs on every push). Route the check through a
# job-level env var instead: env IS readable in if:.
HAS_GPG_SECRETS: ${{ secrets.GPG_PRIVATE_KEY != '' && secrets.GPG_PASSPHRASE != '' }}
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -163,49 +158,14 @@ jobs:
name: linux-binary name: linux-binary
path: dist path: dist
# ── GPG signing of release artifacts (delivery_unsigned) ────────────────
# Guarded: only runs when BOTH signing secrets are configured. When they
# are absent the release still ships (unsigned) instead of failing — so
# forks/contributors without the secrets are not blocked. When present,
# each binary gets a detached ASCII-armored signature and the public key
# (docs/signing-key.asc) is attached to the release for verification.
- name: Import GPG key and sign artifacts
if: env.HAS_GPG_SECRETS == 'true'
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_KEYID: EF1CA6A2E2D0CEFF61DC7F080256A0C36A6D5E72
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
for f in dist/mcp-mt5-quant-macos-arm64.tar.gz \
dist/mcp-mt5-quant-linux-x64.tar.gz; do
gpg --batch --yes --pinentry-mode loopback \
--passphrase "$GPG_PASSPHRASE" \
--detach-sign --armor -u "$GPG_KEYID" "$f"
done
- name: Build release asset list
id: assets
run: |
{
echo "files<<EOF"
echo "dist/mcp-mt5-quant-macos-arm64.tar.gz"
echo "dist/mcp-mt5-quant-linux-x64.tar.gz"
echo "docs/signing-key.asc"
if [ -f dist/mcp-mt5-quant-macos-arm64.tar.gz.asc ] && \
[ -f dist/mcp-mt5-quant-linux-x64.tar.gz.asc ]; then
echo "dist/mcp-mt5-quant-macos-arm64.tar.gz.asc"
echo "dist/mcp-mt5-quant-linux-x64.tar.gz.asc"
fi
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with: with:
tag_name: ${{ env.RELEASE_TAG }} tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_TAG }} name: ${{ env.RELEASE_TAG }}
files: ${{ steps.assets.outputs.files }} files: |
dist/mcp-mt5-quant-macos-arm64.tar.gz
dist/mcp-mt5-quant-linux-x64.tar.gz
draft: false draft: false
prerelease: false prerelease: false
generate_release_notes: true generate_release_notes: true
@@ -214,17 +174,13 @@ jobs:
# ── MCP Package ────────────────────────────────────────────────────────────── # ── MCP Package ──────────────────────────────────────────────────────────────
# Builds the installable MCP package (binary + config + docs), # Builds the installable MCP package (binary + config + docs),
# computes its SHA256, and uploads to the release. Also attests the FINAL # computes its SHA256, and uploads to the release.
# release bytes — the macOS tarball is re-packaged here (server.json added),
# so attesting in the build jobs would cover digests that never ship.
build-mcp-package: build-mcp-package:
needs: release needs: release
runs-on: macos-latest runs-on: macos-latest
permissions: permissions:
contents: write # Attach the package to the GitHub Release contents: write
id-token: write # Required for build provenance attestation (OIDC)
attestations: write # Required to publish the attestation
outputs: outputs:
sha256: ${{ steps.compute-sha256.outputs.sha256 }} sha256: ${{ steps.compute-sha256.outputs.sha256 }}
env: env:
@@ -238,12 +194,6 @@ jobs:
name: macos-binary name: macos-binary
path: dist path: dist
- name: Download Linux binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: linux-binary
path: dist
- name: Build MCP package - name: Build MCP package
run: | run: |
mkdir -p mcp-package/mcp-server/bin mkdir -p mcp-package/mcp-server/bin
@@ -255,19 +205,6 @@ jobs:
cp server.json mcp-package/ cp server.json mcp-package/
cd mcp-package && tar -czf ../mcp-mt5-quant-macos-arm64.tar.gz . && cd .. cd mcp-package && tar -czf ../mcp-mt5-quant-macos-arm64.tar.gz . && cd ..
# Attest the exact bytes attached to the Release: the re-packaged macOS
# tarball at the repo root and the pass-through Linux tarball from the
# artifact — so attested digests match the published assets.
- name: Generate build provenance attestation (macOS)
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: mcp-mt5-quant-macos-arm64.tar.gz
- name: Generate build provenance attestation (Linux)
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: dist/mcp-mt5-quant-linux-x64.tar.gz
- name: Compute SHA256 - name: Compute SHA256
id: compute-sha256 id: compute-sha256
run: | run: |
@@ -276,7 +213,7 @@ jobs:
echo "MCP Package SHA256: $SHA256" echo "MCP Package SHA256: $SHA256"
- name: Upload MCP package to release - name: Upload MCP package to release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with: with:
tag_name: ${{ env.RELEASE_TAG }} tag_name: ${{ env.RELEASE_TAG }}
files: mcp-mt5-quant-macos-arm64.tar.gz files: mcp-mt5-quant-macos-arm64.tar.gz
@@ -295,16 +232,6 @@ jobs:
# checks, which github-actions[bot] cannot bypass on a direct push. The PR # checks, which github-actions[bot] cannot bypass on a direct push. The PR
# runs those checks legitimately, then auto-merges once green. This job # runs those checks legitimately, then auto-merges once green. This job
# blocks until the merge lands so mcp-publish reads the patched server.json. # blocks until the merge lands so mcp-publish reads the patched server.json.
#
# Bot identity (RELEASE_PAT): when the RELEASE_PAT secret exists (a
# fine-grained personal access token owned by masdevid with Contents+PR
# write), the branch push and the PR are attributed to masdevid instead of
# github-actions[bot]. GitHub only holds pull_request workflows for manual
# approval when the PR author is an OUTSIDE contributor — same-owner PRs
# run their checks immediately, making releases fully hands-off. Without
# the secret everything falls back to GITHUB_TOKEN: the release still
# completes, but the server.json PR's required status checks sit in
# "action_required" until manually approved once per release.
update-server-json: update-server-json:
needs: build-mcp-package needs: build-mcp-package
@@ -312,14 +239,6 @@ jobs:
permissions: permissions:
contents: write # push the ci/* release branch contents: write # push the ci/* release branch
pull-requests: write # create the PR and enable auto-merge pull-requests: write # create the PR and enable auto-merge
env:
# Single bot identity used for BOTH the git push and the gh pr calls —
# GitHub ties workflow-trigger approval to the PR-authoring identity,
# so push and PR must match. Resolves to RELEASE_PAT when configured,
# otherwise the default GITHUB_TOKEN, so nothing breaks before the
# secret is added. Declared here instead of interpolated inline so the
# token never lands on a command line; GitHub masks it in logs anyway.
RELEASE_PAT_OR_TOKEN: ${{ secrets.RELEASE_PAT || github.token }}
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
@@ -365,13 +284,6 @@ jobs:
id: commit id: commit
run: | run: |
BRANCH="ci/server-json-sha-${VERSION}" BRANCH="ci/server-json-sha-${VERSION}"
# Authenticate ALL pushes (stale-branch cleanup + branch upload)
# with the SAME identity that creates the PR below: GitHub ties
# workflow-trigger approval to the PR author, so a mismatch here
# would defeat the RELEASE_PAT hands-off path. Token comes from the
# job-level env (never interpolated inline, never echoed).
git remote set-url --push origin \
"https://x-access-token:${RELEASE_PAT_OR_TOKEN}@github.com/masdevid/mt5-quant.git"
# Drop any stale branch/PR left over from a previous attempt at # Drop any stale branch/PR left over from a previous attempt at
# this release so every run starts from a clean slate # this release so every run starts from a clean slate
git push origin --delete "$BRANCH" 2>/dev/null || true git push origin --delete "$BRANCH" 2>/dev/null || true
@@ -384,14 +296,7 @@ jobs:
echo "server.json unchanged — no commit needed" echo "server.json unchanged — no commit needed"
echo "changed=false" >> "$GITHUB_OUTPUT" echo "changed=false" >> "$GITHUB_OUTPUT"
else else
# Never add a CI-skip marker to this commit message: under the git commit -m "ci: update server.json SHA256 for v${VERSION} [skip ci]"
# protect-master ruleset the server.json PR carries required
# status checks, and a CI-skip marker suppresses
# pull_request-triggered workflows, so those checks never report
# and auto-merge hangs until timeout. Omitting it is safe —
# release.yml triggers only on tags (no loop risk) and
# GITHUB_TOKEN-attributed merges never retrigger workflows.
git commit -m "ci: update server.json SHA256 for v${VERSION}"
git push origin "$BRANCH" git push origin "$BRANCH"
echo "changed=true" >> "$GITHUB_OUTPUT" echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Committed server.json update to ${BRANCH}" echo "Committed server.json update to ${BRANCH}"
@@ -401,23 +306,20 @@ jobs:
if: steps.commit.outputs.changed == 'true' if: steps.commit.outputs.changed == 'true'
run: | run: |
BRANCH="ci/server-json-sha-${VERSION}" BRANCH="ci/server-json-sha-${VERSION}"
# Same rule as above: no CI-skip marker in the title — GitHub GH_TOKEN="${{ github.token }}" gh pr create \
# honors markers on PR titles too, which would suppress the --title "ci: update server.json SHA256 for v${VERSION} [skip ci]" \
# required checks.
GH_TOKEN="$RELEASE_PAT_OR_TOKEN" gh pr create \
--title "ci: update server.json SHA256 for v${VERSION}" \
--body "Automated MCP package hash update." \ --body "Automated MCP package hash update." \
--base master \ --base master \
--head "$BRANCH" --head "$BRANCH"
GH_TOKEN="$RELEASE_PAT_OR_TOKEN" gh pr merge "$BRANCH" --auto --squash GH_TOKEN="${{ github.token }}" gh pr merge "$BRANCH" --auto --squash
# Block until required checks (build, lint, cargo-deny x4) finish # Block until required checks (build, lint, cargo-deny x4) finish
# and auto-merge completes — mcp-publish depends on this job and # and auto-merge completes — mcp-publish depends on this job and
# checks out master expecting the patched server.json # checks out master expecting the patched server.json
DEADLINE=$((SECONDS + 1800)) DEADLINE=$((SECONDS + 1800))
while (( SECONDS < DEADLINE )); do while (( SECONDS < DEADLINE )); do
STATE=$(GH_TOKEN="$RELEASE_PAT_OR_TOKEN" gh pr view "$BRANCH" --json state --jq .state) STATE=$(GH_TOKEN="${{ github.token }}" gh pr view "$BRANCH" --json state --jq .state)
case "$STATE" in case "$STATE" in
MERGED) MERGED)
echo "server.json PR merged to master" echo "server.json PR merged to master"
@@ -454,50 +356,52 @@ jobs:
- name: Install mcp-publisher - name: Install mcp-publisher
id: install-publisher id: install-publisher
env:
# Hash-pinned download (OpenSSF Scorecard Pinned-Dependencies):
# PUB_SHA256 is the mcp-publisher_linux_amd64.tar.gz entry from
# registry_1.8.1_checksums.txt shipped in the same release.
# Bump both values together when upgrading.
PUB_VERSION: v1.8.1
PUB_SHA256: a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc
run: | run: |
URL="https://github.com/modelcontextprotocol/registry/releases/download/${PUB_VERSION}/mcp-publisher_linux_amd64.tar.gz" # Fetch the download URL for linux_amd64 directly from the release assets API
# (asset name format changed in v1.6.0 — look it up rather than constructing it)
URL=$(curl -sf \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/modelcontextprotocol/registry/releases/latest" \
| python3 -c "
import sys, json
r = json.load(sys.stdin)
tag = r['tag_name']
assets = r.get('assets', [])
match = next((a['browser_download_url'] for a in assets
if 'linux' in a['name'] and 'amd64' in a['name']
and a['name'].endswith('.tar.gz')
and 'sbom' not in a['name']
and 'sigstore' not in a['name']), None)
if match:
print(match)
else:
print(f'https://github.com/modelcontextprotocol/registry/releases/download/{tag}/mcp-publisher_linux_amd64.tar.gz', file=sys.stderr)
sys.exit(1)
" 2>/dev/null)
if [[ -z "$URL" ]]; then
echo "installed=false" >> "$GITHUB_OUTPUT"
echo "Could not resolve mcp-publisher download URL"
else
echo "Downloading: $URL" echo "Downloading: $URL"
if ! curl -fsSL -o "$RUNNER_TEMP/mcp-publisher.tar.gz" "$URL"; then if curl -fsSL -o mcp-publisher.tar.gz "$URL"; then
echo "installed=false" >> "$GITHUB_OUTPUT" tar -xzf mcp-publisher.tar.gz 2>/dev/null || true
echo "Download failed: $URL" BINARY=$(find . -maxdepth 3 -name "mcp-publisher" ! -path "./.git/*" | head -1)
exit 0 if [[ -n "$BINARY" && -f "$BINARY" ]]; then
fi chmod +x "$BINARY"
sudo mv "$BINARY" /usr/local/bin/mcp-publisher
# Never execute unverified bytes: hard-fail the job on any digest
# mismatch (upstream re-release or tampering)
ACTUAL=$(sha256sum "$RUNNER_TEMP/mcp-publisher.tar.gz" | awk '{print $1}')
if [[ "$ACTUAL" != "$PUB_SHA256" ]]; then
echo "::error::SHA256 mismatch for mcp-publisher ${PUB_VERSION}: expected ${PUB_SHA256}, got ${ACTUAL}"
exit 1
fi
echo "SHA256 verified: $ACTUAL"
EXTRACT_DIR="$(mktemp -d)"
if ! tar -xzf "$RUNNER_TEMP/mcp-publisher.tar.gz" -C "$EXTRACT_DIR"; then
rm -rf "$EXTRACT_DIR" "$RUNNER_TEMP/mcp-publisher.tar.gz"
echo "installed=false" >> "$GITHUB_OUTPUT"
echo "Extraction failed"
exit 0
fi
if [[ -f "$EXTRACT_DIR/mcp-publisher" ]]; then
chmod +x "$EXTRACT_DIR/mcp-publisher"
sudo mv "$EXTRACT_DIR/mcp-publisher" /usr/local/bin/mcp-publisher
echo "installed=true" >> "$GITHUB_OUTPUT" echo "installed=true" >> "$GITHUB_OUTPUT"
echo "Installed: $(mcp-publisher --version 2>/dev/null || echo 'ok')" echo "Installed: $(mcp-publisher --version 2>/dev/null || echo 'ok')"
else else
echo "installed=false" >> "$GITHUB_OUTPUT" echo "installed=false" >> "$GITHUB_OUTPUT"
echo "Binary not found in archive" echo "Binary not found in archive"
fi fi
rm -rf "$EXTRACT_DIR" "$RUNNER_TEMP/mcp-publisher.tar.gz" rm -f mcp-publisher.tar.gz
else
echo "installed=false" >> "$GITHUB_OUTPUT"
echo "Download failed: $URL"
fi
fi
- name: Validate server.json before publish - name: Validate server.json before publish
id: validate id: validate
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # v1 - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
with: with:
toolchain: stable toolchain: stable
components: clippy, rustfmt components: clippy, rustfmt
+3 -46
View File
@@ -1,14 +1,12 @@
name: Scorecard supply-chain security name: Scorecard supply-chain security
on: on:
branch_protection_rule: branch_protection_rule:
workflow_dispatch:
schedule: schedule:
- cron: '30 6 * * 3' - cron: '30 6 * * 3'
push: push:
branches: [master] branches: [master]
permissions: permissions: read-all
contents: read
jobs: jobs:
analysis: analysis:
@@ -17,7 +15,7 @@ jobs:
permissions: permissions:
security-events: write security-events: write
id-token: write id-token: write
contents: write contents: read
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -35,47 +33,6 @@ jobs:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
- uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4 - uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4
with: with:
sarif_file: results.sarif sarif_file: results.sarif
- name: Update badge JSON
if: github.ref == 'refs/heads/master'
run: |
set -euo pipefail
python3 - <<'PY'
import json
import urllib.request
url = "https://api.scorecard.dev/projects/github.com/masdevid/mt5-quant"
with urllib.request.urlopen(url) as resp:
data = json.load(resp)
score = float(data["score"])
if score >= 9:
color = "brightgreen"
elif score >= 7:
color = "yellowgreen"
elif score >= 5:
color = "yellow"
else:
color = "red"
badge = {
"schemaVersion": 1,
"label": "OpenSSF Scorecard",
"message": f"{score:g}",
"color": color,
}
with open("badge.json", "w") as f:
json.dump(badge, f)
print("Wrote badge.json:", badge)
PY
git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
git checkout -B badges
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -f badge.json
git commit -m "chore: update OpenSSF Scorecard badge" || echo "no changes to commit"
git push -f origin badges
-12
View File
@@ -1,17 +1,5 @@
# Changelog # Changelog
## [1.34.3] — 2026-08-25
- chore(deps): bump dirs from 5.0.1 to 6.0.0 (#6)
- chore(deps): bump base64 from 0.22.1 to 0.23.1 (#4)
- chore(deps): bump rusqlite from 0.31.0 to 0.40.2 (#5)
- ci(release): generate build provenance attestations for artifacts
- chore(deps): bump the minor-and-patch group across 1 directory with 7 updates (#3)
- fix(release): drop [skip ci] from server.json PR so required checks can run
- docs(readme): fix Scorecard badge, add CI, crates.io, and license badges
- ci(scorecard): rename invalid 'publish' input to 'publish_results' so results are published
## [1.34.2] — 2026-08-25 ## [1.34.2] — 2026-08-25
- ci(release): publish to crates.io via trusted publishing (OIDC) - ci(release): publish to crates.io via trusted publishing (OIDC)
-40
View File
@@ -1,40 +0,0 @@
# Contributing to MT5-Quant
Thanks for your interest in improving MT5-Quant! This document explains how to
contribute and what we expect from contributions.
## How to contribute
1. **Fork** the repository and create a topic branch from `master`.
2. Make your change, keeping it focused (one logical change per PR).
3. Open a **pull request against `master`**. A pull request template
(`.github/PULL_REQUEST_TEMPLATE.md`) is provided — please fill it in.
4. Ensure CI passes (build, `cargo test`, clippy, formatting, cargo-deny,
CodeQL). Maintainers review and merge once checks and review are satisfied.
## Development setup
- Install Rust (stable toolchain) and, for full backtests, Wine/MT5 as described
in `README.md` and `docs/QUICKSTART.md`.
- Build the release binary with either:
- `bash scripts/build-rust.sh`, or
- `cargo build --release`
- The binary is produced at `target/release/mt5-quant`.
## Testing requirement
- **Non-trivial changes must include tests.** New functionality should come with
unit or integration tests where practical.
- `cargo test` runs automatically in CI (`rust.yml`) on every push and pull
request, so a change that breaks tests cannot merge.
## Commit style
- Follow **Conventional Commits** (`feat:`, `fix:`, `docs:`, `refactor:`,
`test:`, `release:`), as documented in `AGENTS.md`.
## Issue tracker
- Bugs and enhancement requests are tracked on the project's
[GitHub Issues](https://github.com/masdevid/mt5-quant/issues), with
bug-report and feature-request templates under `.github/ISSUE_TEMPLATE/`.
Generated
+180 -142
View File
@@ -2,6 +2,18 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.4" version = "1.1.4"
@@ -56,7 +68,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -67,7 +79,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell_polyfill", "once_cell_polyfill",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -84,9 +96,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.23.1" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@@ -124,9 +136,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.45" version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
@@ -138,9 +150,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.6.7" version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -148,9 +160,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.6.7" version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -160,14 +172,14 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.6.7" version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn",
] ]
[[package]] [[package]]
@@ -188,45 +200,34 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core_detect"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "7.0.0" version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys",
] ]
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys"
version = "0.5.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.42" version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"core_detect",
"multiversion_no_op",
"rustversion",
"scopeguard",
"simdutf8",
] ]
[[package]] [[package]]
@@ -242,7 +243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -275,12 +276,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -307,20 +302,20 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [ dependencies = [
"foldhash 0.1.5", "ahash",
] ]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [ dependencies = [
"foldhash 0.2.0", "foldhash",
] ]
[[package]] [[package]]
@@ -328,17 +323,14 @@ name = "hashbrown"
version = "0.17.0" version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
dependencies = [
"foldhash 0.2.0",
]
[[package]] [[package]]
name = "hashlink" name = "hashlink"
version = "0.12.1" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [ dependencies = [
"hashbrown 0.17.0", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -440,9 +432,9 @@ dependencies = [
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.38.2" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",
@@ -484,12 +476,12 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
name = "mt5-quant" name = "mt5-quant"
version = "1.34.3" version = "1.34.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -511,19 +503,13 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "multiversion_no_op"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.3" version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -595,7 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"syn 2.0.117", "syn",
] ]
[[package]] [[package]]
@@ -633,9 +619,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.5.2" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [ dependencies = [
"getrandom 0.2.17", "getrandom 0.2.17",
"libredox", "libredox",
@@ -644,9 +630,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.13.1" version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -656,9 +642,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.4.18" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -667,9 +653,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.11" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "roxmltree" name = "roxmltree"
@@ -680,21 +666,11 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "rsqlite-vfs"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
dependencies = [
"hashbrown 0.16.1",
"thiserror",
]
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.40.2" version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"fallible-iterator", "fallible-iterator",
@@ -702,7 +678,6 @@ dependencies = [
"hashlink", "hashlink",
"libsqlite3-sys", "libsqlite3-sys",
"smallvec", "smallvec",
"sqlite-wasm-rs",
] ]
[[package]] [[package]]
@@ -715,7 +690,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -753,9 +728,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.229" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [ dependencies = [
"serde_core", "serde_core",
"serde_derive", "serde_derive",
@@ -763,29 +738,29 @@ dependencies = [
[[package]] [[package]]
name = "serde_core" name = "serde_core"
version = "1.0.229" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.229" version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.151" version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -832,12 +807,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.1"
@@ -851,19 +820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys 0.61.2",
]
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
dependencies = [
"cc",
"js-sys",
"rsqlite-vfs",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -883,17 +840,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "syn"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.27.0" version = "3.27.0"
@@ -904,27 +850,27 @@ dependencies = [
"getrandom 0.4.2", "getrandom 0.4.2",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.20" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "2.0.20" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.4", "syn",
] ]
[[package]] [[package]]
@@ -938,9 +884,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.2" version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@@ -950,7 +896,7 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -961,7 +907,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn",
] ]
[[package]] [[package]]
@@ -983,7 +929,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn",
] ]
[[package]] [[package]]
@@ -1047,9 +993,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.27.0" version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
dependencies = [ dependencies = [
"getrandom 0.4.2", "getrandom 0.4.2",
"js-sys", "js-sys",
@@ -1068,6 +1014,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"
@@ -1134,7 +1086,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -1187,7 +1139,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -1211,7 +1163,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn",
] ]
[[package]] [[package]]
@@ -1222,7 +1174,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn",
] ]
[[package]] [[package]]
@@ -1249,6 +1201,15 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@@ -1258,6 +1219,63 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.51.0" version = "0.51.0"
@@ -1294,7 +1312,7 @@ dependencies = [
"heck", "heck",
"indexmap", "indexmap",
"prettyplease", "prettyplease",
"syn 2.0.117", "syn",
"wasm-metadata", "wasm-metadata",
"wit-bindgen-core", "wit-bindgen-core",
"wit-component", "wit-component",
@@ -1310,7 +1328,7 @@ dependencies = [
"prettyplease", "prettyplease",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn",
"wit-bindgen-core", "wit-bindgen-core",
"wit-bindgen-rust", "wit-bindgen-rust",
] ]
@@ -1352,6 +1370,26 @@ dependencies = [
"wasmparser", "wasmparser",
] ]
[[package]]
name = "zerocopy"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"
+4 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mt5-quant" name = "mt5-quant"
version = "1.34.3" version = "1.34.2"
edition = "2021" edition = "2021"
description = "MCP server for MT5 strategy development on macOS/Linux" description = "MCP server for MT5 strategy development on macOS/Linux"
authors = ["masdevid <masdevid@example.com>"] authors = ["masdevid <masdevid@example.com>"]
@@ -30,11 +30,11 @@ tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
uuid = { version = "1.0", features = ["v4"] } uuid = { version = "1.0", features = ["v4"] }
regex = "1.0" regex = "1.0"
dirs = "7.0" dirs = "5.0"
walkdir = "2.0" walkdir = "2.0"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
encoding_rs = "0.8" encoding_rs = "0.8"
tempfile = "3.0" tempfile = "3.0"
roxmltree = "0.21.1" roxmltree = "0.21.1"
rusqlite = { version = "0.40", features = ["bundled"] } rusqlite = { version = "0.31", features = ["bundled"] }
base64 = "0.23" base64 = "0.22"
+7 -74
View File
@@ -1,6 +1,6 @@
# MT5-Quant # MT5-Quant
[![OpenSSF Scorecard](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/masdevid/mt5-quant/badges/badge.json)](https://scorecard.dev/viewer/?uri=github.com/masdevid/mt5-quant) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/14227/badge)](https://bestpractices.coreinfrastructure.org/projects/14227) [![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) [![CodeQL](https://img.shields.io/badge/CodeQL-enabled-brightgreen)](https://github.com/masdevid/mt5-quant/security/code-scanning) [![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. **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.
@@ -33,68 +33,14 @@ Claude: [compile → clean → backtest → analyze 1,847 deals]
### Install MT5-Quant ### Install MT5-Quant
#### Option A — Cargo (recommended, needs Rust toolchain) Ask your LLM coding platform to install and configure MT5-Quant:
```bash > "Please install mt5-quant. It's a Rust-based MCP server for MT5 backtesting at https://github.com/masdevid/mt5-quant. Run its `scripts/setup.sh` to auto-detect Wine and MT5 paths, register the MCP server, and configure everything."
cargo install mt5-quant --locked
```
#### Option B — Pre-built binaries (no toolchain) The LLM will:
1. Download the pre-built binary (or `cargo build --release`)
```bash 2. Run `scripts/setup.sh` to auto-detect Wine/MT5 and write config
# macOS Apple Silicon 3. Register the MCP server with your coding platform
curl -LO https://github.com/masdevid/mt5-quant/releases/latest/download/mcp-mt5-quant-macos-arm64.tar.gz
tar xzf mcp-mt5-quant-macos-arm64.tar.gz # → mcp-mt5-quant-macos/mt5-quant
# Linux x64
curl -LO https://github.com/masdevid/mt5-quant/releases/latest/download/mcp-mt5-quant-linux-x64.tar.gz
tar xzf mcp-mt5-quant-linux-x64.tar.gz # → mcp-mt5-quant-linux/mt5-quant
```
Move the binary somewhere on your `PATH` (e.g. `/usr/local/bin` or `~/.local/bin`) so clients can launch it as `mt5-quant`.
#### Option C — Register with your LLM client (copy-paste)
**Claude Code**
```bash
claude mcp add mt5-quant -- mt5-quant --stdio
```
**Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json`
```json
{ "mcpServers": { "mt5-quant": { "command": "mt5-quant", "args": ["--stdio"] } } }
```
**Cursor** — `.cursor/mcp.json` (same JSON shape as Claude Desktop)
```json
{ "mcpServers": { "mt5-quant": { "command": "mt5-quant", "args": ["--stdio"] } } }
```
**opencode** — `.opencode/opencode.jsonc`
```jsonc
{ "mcp": { "mt5-quant": { "type": "local", "command": ["mt5-quant", "--stdio"], "enabled": true } } }
```
**Codex CLI** — `~/.codex/config.toml`
```toml
[mcp_servers.mt5-quant]
command = "mt5-quant"
args = ["--stdio"]
```
Then finish setup (auto-detects Wine + MT5 paths):
```bash
bash scripts/setup.sh
```
> **Tip:** you can also just ask your LLM coding agent to do all of the above:
> *"Please install mt5-quant from https://github.com/masdevid/mt5-quant — download or build it, run its `scripts/setup.sh` to auto-detect Wine and MT5 paths, and register it as an MCP server."*
### First Backtest ### First Backtest
@@ -282,19 +228,6 @@ For crashes or unexplained failures during backtest/compile/optimization:
**[Full Troubleshooting Guide →](docs/TROUBLESHOOTING.md)** **[Full Troubleshooting Guide →](docs/TROUBLESHOOTING.md)**
## Documentation
| Doc | Description |
|-----|-------------|
| [QUICKSTART](docs/QUICKSTART.md) | Step-by-step install, setup, and first backtest |
| [MCP_TOOLS](docs/MCP_TOOLS.md) | Full input/output schemas for all 90 tools |
| [CONFIG](docs/CONFIG.md) | Configuration reference (`config/mt5-quant.yaml`) |
| [ARCHITECTURE](docs/ARCHITECTURE.md) | Design principles and internal module layout |
| [REMOTE_AGENTS](docs/REMOTE_AGENTS.md) | Distribute optimization across Linux agents via Wine |
| [TROUBLESHOOTING](docs/TROUBLESHOOTING.md) | Platform-specific fixes for Wine, MT5, and backtests |
Full index with reading order: [docs/INDEX.md](docs/INDEX.md)
--- ---
## Acknowledgements ## Acknowledgements
+21 -140
View File
@@ -1,154 +1,35 @@
# Security Policy # Security Policy
This document describes how to report security issues in **mt5-quant**, and the ## Supported Versions
secure-development practices the project follows. It backs the OpenSSF Best
Practices criteria `vulnerability_report_process`, `know_secure_design`, and
`know_common_errors`, and the signed-release process described under
[Signing](#signing-of-release-artifacts) backs `delivery_unsigned`.
mt5-quant is a Rust MCP server that drives MetaTrader 5 (under Wine/CrossOver) Only the latest minor release line receives security fixes. Older versions should upgrade.
to compile, backtest, and analyze trading Expert Advisors. It runs **locally**;
it never exposes a network listener and never transmits user data off the
machine except for the opt-in version self-check (HTTPS to GitHub). The security
notes below focus on the real risks in that model: untrusted input from MT5
report files, the user-supplied Wine/MT5 configuration, and the build/publish
pipeline.
## Vulnerability reporting process | Version | Supported |
| ------- | ------------------ |
| 1.34.x | :white_check_mark: |
| < 1.34 | :x: |
We encourage responsible disclosure. If you discover a vulnerability, please ## Reporting a Vulnerability
report it through **one** of the following channels (in order of preference):
1. **GitHub Security Advisories (recommended).** Open a private security **Please do not open public GitHub issues for security matters.**
advisory at <https://github.com/masdevid/mt5-quant/security/advisories/new>.
This keeps the details confidential while we prepare a fix.
2. **Public GitHub Issue.** If you prefer, open a normal issue at
<https://github.com/masdevid/mt5-quant/issues>. Public reports are accepted;
please avoid posting working exploits in the open issue body — link to a
private gist or email the maintainer instead.
### What to include 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.
A useful report contains: Please include as much of the following as you can:
- A clear description of the vulnerability and its impact. - Affected version (and commit SHA, if relevant)
- The affected version(s) (`mt5-quant --version` or the `version` field in - Steps to reproduce or a proof of concept
`Cargo.toml`). - Expected vs. actual behavior
- Steps to reproduce, or a proof-of-concept input (e.g. a crafted MT5 report, - Any known mitigations
`.set` file, or tool argument).
- Any relevant logs, crashes, or sanitizer/fuzzer output.
### Expected handling timeline ## Response Commitment
- **Acknowledgement:** within 7 days of a complete report. - **Acknowledgement:** within **72 hours** of your report
- **Triage & severity assessment:** within 14 days. - **Fix target:** vulnerabilities rated **critical/high** are targeted for a patch release within **14 days**
- **Fix or mitigation plan:** timeline depends on severity; critical issues are - We will keep you informed of progress and credit you in the advisory unless you prefer otherwise
addressed as soon as a patch is ready, and a coordinated disclosure date is
agreed with the reporter when a private advisory is used.
- **Disclosure:** once a fixed release is published, the advisory (if used) is
made public and the fix is credited to the reporter unless they request
anonymity.
There is no formal contractual SLA; reports are handled on a best-effort, ## Scope Notes
case-by-case basis through the public channels above.
## Secure development practices (`know_secure_design`) 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.
mt5-quant follows these practices to keep the code trustworthy: Out of scope: vulnerabilities in Wine, MetaTrader 5 itself, or broker infrastructure — please report those upstream to the respective vendors.
- **Input validation at every tool boundary.** All 89 MCP tool handlers in
`src/tools/handlers/` parse and validate their arguments at the entry point
(using helpers such as `required_str`, `resolve_report`, and
`prepare_analysis`). Untrusted data — tool arguments, the filesystem, and MT5
output — is never trusted inside core logic.
- **No hardcoded secrets.** The source tree contains no embedded credentials,
API tokens, or private keys. The only secrets are user-provided: a local
`config/mt5-quant.yaml` (gitignored) holding the Wine/MT5 paths, and an
optional release PAT used only in CI. Neither is persisted by the project
itself.
- **Safe parsing of untrusted MT5 reports.** Backtest/optimization reports come
from MetaTrader 5 as either HTML or SpreadsheetML XML (`.htm.xml`, Build 48+).
Parsers in `src/analytics/extract.rs` and `src/optimization/parser.rs` use
explicit, bounded parsing (no `eval`, no regex-backed HTML/script execution)
and fail fast on malformed input. These parsers are additionally exercised by
the cargo-fuzz harness in `fuzz/` (see `dynamic_analysis*` criteria).
- **Least privilege at runtime.** The architecture enforces **a single MT5
instance** and never runs two backtests in parallel on the same Wine prefix.
When no display is available the pipeline falls back to headless/Xvfb mode; the
only external process launched is the user-configured Wine/MT5 binary.
- **Dependency auditing in CI.** Every push and pull request runs
`cargo-deny` (license + advisory + source checks) and the GitHub
`dependency-review` action. Vulnerable or non-compliant dependencies block the
build via the `protect-master` ruleset's required status checks.
- **Fuzzing of parsers.** `fuzz/` contains libFuzzer targets that feed arbitrary
bytes into the report/metrics parsers with debug assertions and integer-
overflow traps enabled, catching panics and invariant violations.
- **Isolation of the Wine/MT5 sandbox.** MT5 runs under the user's Wine prefix,
which is isolated from the host shell environment. The server does not grant
MT5 any elevated privileges.
- **Static analysis.** Clippy runs with `-D warnings` (warnings fail the build)
and GitHub CodeQL (Rust) scans every push/PR.
## Common implementation errors we avoid (`know_common_errors`)
The codebase is deliberately written to avoid well-known Rust/systems pitfalls:
- **Integer overflow.** Rust's release builds disable overflow checks by
default, so arithmetic on deal volumes, prices, and cumulative PnL uses
`checked_*`/`saturating_*` operations or is validated at parse time. The
fuzz harness re-enables `debug-assertions` so overflow traps fire during
testing.
- **Path traversal in `.set` file handling.** `.set` files (UTF-16LE parameter
files for EAs) are read/written through helpers in
`src/tools/handlers/setfiles.rs`. Paths are resolved against the configured
tester profiles directory and normalized; user-supplied filenames are never
interpolated into shell commands and are confined to that directory.
- **UTF-16LE encoding pitfalls.** MetaTrader strips `||Y` flags from UTF-8 `.set`
files, so all writes use UTF-16LE with an explicit BOM and `chmod 444`. The
helpers never assume UTF-8, avoiding silent mojibake / flag loss.
- **Avoiding `unsafe`.** The crate uses no `unsafe` blocks in application logic;
all FFI with Wine/MT5 is performed via the `std::process` API and the system
`curl`/`gpg` binaries, not raw pointer manipulation.
- **Proper error propagation.** Errors are returned as typed `Result`s and
propagated with `?`; there are no swallowed errors or empty `catch`-and-ignore
paths. Invalid states halt fast with descriptive messages (Fail-Fast
philosophy) rather than being silently patched.
- **No injection via shell.** Wine/MT5 is launched with an argument vector (not
a constructed shell string), eliminating command-injection from config values.
## Signing of release artifacts
To satisfy `delivery_unsigned`, **release artifacts are GPG-signed**:
- Each GitHub Release publishes the macOS (`mcp-mt5-quant-macos-arm64.tar.gz`)
and Linux (`mcp-mt5-quant-linux-x64.tar.gz`) binaries **plus** their detached
ASCII-armored signatures (`*.tar.gz.asc`), and the project's public key at
**`docs/signing-key.asc`**.
- The signing key is an RSA-4096 key generated for the `mt5-quant releases`
identity. The public half is committed to the repository; the private half is
held only in CI secrets (`GPG_PRIVATE_KEY`, `GPG_PASSPHRASE`) and is never
committed.
- Signing is performed in CI (`release.yml`) with
`gpg --detach-sign --armor` and is **guarded** so the release still succeeds
if the signing secrets are not configured — it simply omits the signatures in
that case.
### Verifying a downloaded release
```bash
# 1. Import the project public key
gpg --import docs/signing-key.asc
# 2. Verify a release artifact
gpg --verify mcp-mt5-quant-linux-x64.tar.gz.asc \
mcp-mt5-quant-linux-x64.tar.gz
# A good signature from "mt5-quant releases <releases@mt5-quant.local>"
# confirms the artifact was produced by the project's release pipeline.
```
## Supported versions
Security fixes are applied to the latest released version on the `master`
branch. Older releases are not back-ported; users should upgrade to the current
release to receive fixes.
-23
View File
@@ -1,23 +0,0 @@
# Documentation Index
**Start here:** [QUICKSTART.md](QUICKSTART.md) — install MT5-Quant, run `scripts/setup.sh` to auto-detect Wine/MT5 paths, register it with your LLM client, and complete your first backtest.
| Doc | Description |
|-----|-------------|
| [QUICKSTART.md](QUICKSTART.md) | Step-by-step install, setup, and first backtest |
| [MCP_TOOLS.md](MCP_TOOLS.md) | Full input/output schemas for all 90 tools |
| [CONFIG.md](CONFIG.md) | Configuration reference (`config/mt5-quant.yaml`) |
| [ARCHITECTURE.md](ARCHITECTURE.md) | Design principles and internal module layout |
| [REMOTE_AGENTS.md](REMOTE_AGENTS.md) | Distribute optimization across Linux agents via Wine |
| [TROUBLESHOOTING.md](TROUBLESHOOTING.md) | Platform-specific fixes for Wine, MT5, and backtests |
## Suggested reading order
1. [QUICKSTART.md](QUICKSTART.md) — get a working installation and first report
2. [MCP_TOOLS.md](MCP_TOOLS.md) — browse what your LLM agent can call
3. [CONFIG.md](CONFIG.md) — tune symbols, timeframes, deposit defaults
4. [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — when something fails
5. [ARCHITECTURE.md](ARCHITECTURE.md) — how the pipeline works under the hood
6. [REMOTE_AGENTS.md](REMOTE_AGENTS.md) — scale up optimization throughput
These files are also bundled in every release tarball and MCP package, so installed copies stay available offline.
-65
View File
@@ -1,65 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGqN954BEAC8tSNRLfu4BgrTk/k83jWkB3aHBrgohPTGXemEGSTP6WZbeI+6
NSHw70lB9MlPx65AhzJoLVxIzI9SwBBtoQ+DzoK6QIYaJZkstbMZi5U8rxNXTpU7
4jyScoLb097ZYpxNhDK5hrxRxN0rg82jmSgC+PrVXuGohgkbHbVHjQJouLr3MuDZ
d+y1ET+WQ5kBSN/KIzKmnIxjaXFxSi/S90n3pLJADhtcJ0QKdOzKbNf+G2mdZrcG
XbnphbfgRRWhg+mq/om+SNVz9LEdrrueoI2vAphWvuKSXJxUEAb/hqvQnInbiBIq
E02K3A3Dypf80jGfB/93wGTx7thxlWzedczm57iZJ0DJu2OT1aXF0uK1hnG9jEUB
yrruXBuziSLNnQS8X3prbOAMCK3TWEV+l1+F6uK9Dp6vuvSFzmMfEJW+MRK7Yhl7
7Bi4TlEaWo72DOV7U3XvvcU6kxSdU/CnC79ibMhM8+u0rPa9089Mo0BSqyoP/GFX
5G2hKjB+gBX4brCT0KMGPal18aaZ9Ej6q3XiziLNY2K9AyXMrFtmwFcnpNQxuA1e
yewIQ7daV6MJM/DQc8haLr8DxisWEX57OxJywu8l7aldsrQQrxSOO24Or7AUG2lA
eaGNM14njRU4zJ6BNKnozv3JJTRT6TpqWv6ZSPgXh+BvhdC032uVOJdKCQARAQAB
tC1tdDUtcXVhbnQgcmVsZWFzZXMgPHJlbGVhc2VzQG10NS1xdWFudC5sb2NhbD6J
Am4EEwEIAFgWIQTvHKai4tDO/2HcfwgCVqDDam1ecgUCao33nhsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwwLDMDGy8EBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA
AAoJEAJWoMNqbV5y71IP+we/bZUebidePUvLLlHmL2QKWd4iOqlw4zeQDTuEwRTf
S3qDmiIDytEAJ6AyhQIC+Po4in9hywDOUZnCXgz+VPsWWjdeIyEGNPzmmUU8vcMr
R3VgeQni3b2qsx5RQnDGLz6y2aF6QdMK8TMlG8QVypsJ8N74A68+voEE2vli6nGJ
Jf1Hucn4t7cCWCDgpsSrL+dSM4DXZ0Uz1XByXUXvrIf8/XkFgk6l0V7V24uUdOUl
X5QVL/414wOC5UvzrsuZtE19gdP7BH5IHWAmBwx8CGOuKGiwEZydqnjrGykMCY1s
80pe6LWYOWsWAmu12lzNlFG/1JxL106YqnMukPn1CTMzWnrEAriRSKVuLN2KvUHd
+QNAbxdBbZCaAmEkoUoBnk/7OEgcmeri8F0PnElRIN7IzJBcqG0JZKx5VAudLveX
+vNU469Jh9isxCmev6rpI7QreCWUF/QeKgDGcCKp8acJQqfmACMgYGFkcS9OrCnL
qVGGj16IVEIYs4yRde9X4pYL4PTlv10QWpcTaoaMpOmsr2YkYORHE/QWya6+Pf4h
Dj1NZSof+RuaYjkIFryog8IPtVuRbg7T8UIT9K9wTKf1sRSFCcF1EAYwy0yv5Ch6
qCZUQtjhHf7zttsSiFtAfcF0ymnI8Vunv099tpTqKwB0u0ke5+yjNJqPOb7y8Nmm
uQINBGqN954BEADmekxbtTMLr0ayftAh3ZcmNCVSJrn3tSEkmyPCeI2yBxEg7J+H
ebu+jzleI1jOi4Us6l4mD+GFfXub09LldryugjFLcvI05/vgtlFErC9zEFv+1L5v
0RylV94PJXJFxxdDUUp4/cdERg+QT/h5jLZhoHKvUQ4PnoTOpEjTElLuH/1Zk480
MbHkB0fjm4DV1MzG1pYf8QaSkTCEhnXC7SvnlcerU1LuH33VWXlkXHhho3mho3qK
alf8yjG0PlH+ikvqEHvzOH5SCzUD2hI84/p7vtBvPoQvGdzBIhP+m6QsbeGbxt94
UYBUudM1c405cYPf1p1wl9Oz96MvH4IM4ucS6c3WK33ALwgdYqA7kbNyhSqk+o+a
T7zSroCHRhDaVxHH+Qlad8YRVoIGoX24650CbmuiJ7N0SHyny2AFk15EMzoHcI28
3joSfkUqg7io7/w/k7q0b9fbVkvMn+zQqpYeo7xPqXVDcBpfPXLQZzQgCUiweUbi
j8rxFEHYFGXxKm2NxZdX5t8ss14Ggv16MNM8/fwDfJ7Scxo5y1TFFSZgNs3dAcdd
zhBbxHRZTlxyan24O3xrF+aP6BsiXyHUQs/WPYewen0eEWXrfRTr/GgfIONGSk6S
IWKwbMLnPvkMATwFee8Pf75QO9keNB1nX4dEGeKLhEC1xLJvS8yaNmJorQARAQAB
iQSIBBgBCAA8FiEE7xymouLQzv9h3H8IAlagw2ptXnIFAmqN954bFIAAAAAABAAO
bWFudTIsMi41KzEuMTIsMCwzAhsuAkAJEAJWoMNqbV5ywXQgBBkBCAAdFiEETywc
/FBIkF+vVFdyEHpgkPDwwZsFAmqN954ACgkQEHpgkPDwwZvVwQ//cLwJyiAOfpce
NOBmsuvz4M5xSvxiMpW3N93of3FwGXtWxp0pdlljVxmx1iv+wwK5uYQsbBPgC48J
JvUr7W1V/6QcFNQbOevLA5GVcm533gGgeQ+hrptUPL0l3W610DnUO6yQEWRdGyKG
EM+uQZe20CKb/NyUiOTf9s9zO3rJT/7IYWTitpxvK1t/83FAOvAD6oVQNTwgHVGL
oKTC2i7MdQ2M9Hc64UGG2D28L/plLUwkLKLDtlcueTv+qSi7RZMjQhpBfhnoH9by
pOXLNJCFUhVIAIMB5WncjqpAbXneGgLPhDe8napFKB4Mxky1+jycFq+AgVA98/y9
y3hmOIrK+lnTan1rtUYh6O0Tf9DlDM58/Dr4Pbt7S949WGccEtArCX1B7SqcgvO7
TP5ENFHmaSTwheTtPnzgiwrICO+BXL9bYyb/oUj0U1zIiVQxDmgiz5M9CM8/5221
tPH8lFLAGtGvnpjlRzcKX7WL8+fD8V4fZCC4mzt7xMZdRZB5D5upE+7N/EdjNEM0
UBoBbpZ0muFWtp5f1m2Fu1zJpEDIimfKKl+YHFkODyxOXGeOrRLQB42ecFDOCg0X
ie5hqQ+PnASiaHMC48ZVTYy6T4+rcGRIcRg+FzvK0Lef8J1klQwN85rc71AIV7a1
ZWzn35vUqW1YTjBA4mKSGp050BFpNFYFUA/+MW1iqDGwvZC76KXlrA1q9OlTs41q
9oBW2BgpV7Xaj9AEYC87LTbXGDL5QfLisI7NAVkXKUkRRyhG6J2NiDyPKxnE9V2Y
eFzq6ENSj7MWSonatQmEyGXeMkbBLfddCssb0FU934ACCzccchB1mHA+CqimKiPe
mMMW3OPZ0u4lvTrFPktL/PkPHyV75xgQvr7JsKnjdT9h3VHsm4krhC3f2yi5+orb
nicS5NSGv1l4xNJbqI2nfMk02VY64WOMqduVo12KQeazPO0JBRVFXOMGrmiuluPU
EFDIbu8AZdU67mhIvIDuP9bkm53EOXT3xk0GZ9iiUhlnvbEW9Oewbw9Lircz3Wwp
VeQtp+fbaLrWosAmpeUIxhUP9RJmfHjEMJ+AGrLeAZ3UJQMoR2uUoN45/c+aiUPT
6tNH8BXhloyr6hIfCpkPzbTKpu2jhf1vDTyIigIt5wZoshCbVDy680+1keIPxEuM
HR1s1Sm5NkvX9VRxk7ZSjv9BPL2GVDmPez0+LlBI1hxHDrzhH8iCNka15GL8eNTR
JW3zeFWpgfbqm6XP90bKLRSZXybr1vz5k0znWXje4uIW8iuHiPBNi0pHRJktZ0Fo
z/wtRZUl9wzbQnYqmewAt7hzLc379KsYexOesJoDLgq8iCSyWZbUgC/euMZV9emL
GqF/jfDJVQqPMNY=
=sjIH
-----END PGP PUBLIC KEY BLOCK-----
-4
View File
@@ -1,4 +0,0 @@
target
corpus
artifacts
coverage
-1204
View File
File diff suppressed because it is too large Load Diff
-33
View File
@@ -1,33 +0,0 @@
[package]
name = "mt5-quant-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
[dependencies.mt5-quant]
path = ".."
[[bin]]
name = "parse_metrics"
path = "fuzz_targets/parse_metrics.rs"
test = false
doc = false
bench = false
# Enable debug/overflow assertions during fuzzing. cargo-fuzz builds fuzz
# targets in release mode by default, so we explicitly turn debug-assertions
# back on for the release profile. This backs the
# `dynamic_analysis_enable_assertions` CII criterion: any debug_assert! or
# arithmetic-overflow check inside the crate is active while libFuzzer feeds
# arbitrary input, so crashes from invariant violations are caught as bugs.
[profile.release]
debug-assertions = true
[profile.dev]
debug-assertions = true
-36
View File
@@ -1,36 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use mt5_quant::models::Metrics;
// Fuzz target for the pure, I/O-free MT5 backtest report metrics parser.
//
// `Metrics::from_html` takes an arbitrary `&str` and extracts the numeric
// summary metrics from an MT5 HTML/XML report. It is panic-free on bad input:
// every numeric parse uses `unwrap_or`, regex patterns are compile-time
// constants (never panicking), and the function returns `Option<Metrics>`
// (None when no metrics are found). We return early on non-UTF-8 input and on
// the None case, so the fuzzer only ever exercises the parser itself.
fuzz_target!(|data: &[u8]| {
let Ok(text) = std::str::from_utf8(data) else {
// Parser only accepts UTF-8 text; arbitrary byte sequences are
// out of scope and must not cause a crash.
return;
};
if let Some(metrics) = Metrics::from_html(text) {
// Force the computed fields to be materialized so any logic error
// inside the parser surfaces during fuzzing.
let _ = (
metrics.net_profit,
metrics.profit_factor,
metrics.max_dd_pct,
metrics.sharpe_ratio,
metrics.total_trades,
metrics.recovery_factor,
metrics.win_rate_pct,
metrics.gross_profit,
metrics.gross_loss,
);
}
});
+4 -4
View File
@@ -7,13 +7,13 @@
"url": "https://github.com/masdevid/mt5-quant", "url": "https://github.com/masdevid/mt5-quant",
"source": "github" "source": "github"
}, },
"version": "1.34.3", "version": "1.34.2",
"packages": [ "packages": [
{ {
"registryType": "mcpb", "registryType": "mcpb",
"version": "1.34.3", "version": "1.34.2",
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.34.3/mcp-mt5-quant-macos-arm64.tar.gz", "identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.34.2/mcp-mt5-quant-macos-arm64.tar.gz",
"fileSha256": "2f303cb3deb62e47742e5eb4d73a0e3edf200aadf7db9da39a0ef2151d064171", "fileSha256": "8c26ac2384654c550e23995ab8646cb78f6be04b809ee92acf33e949315aa310",
"transport": { "transport": {
"type": "stdio" "type": "stdio"
}, },
-6
View File
@@ -7,12 +7,6 @@ use crate::models::metrics::Metrics;
pub struct DealAnalyzer; pub struct DealAnalyzer;
impl Default for DealAnalyzer {
fn default() -> Self {
Self::new()
}
}
impl DealAnalyzer { impl DealAnalyzer {
pub fn new() -> Self { pub fn new() -> Self {
Self Self
-6
View File
@@ -8,12 +8,6 @@ use crate::models::{Deal, Metrics};
pub struct ReportExtractor; pub struct ReportExtractor;
impl Default for ReportExtractor {
fn default() -> Self {
Self::new()
}
}
impl ReportExtractor { impl ReportExtractor {
pub fn new() -> Self { pub fn new() -> Self {
Self Self
-60
View File
@@ -1,60 +0,0 @@
pub mod analytics;
pub mod compile;
pub mod models;
pub mod optimization;
pub mod pipeline;
pub mod storage;
pub mod tools;
pub mod mcp_server;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct McpRequest {
pub jsonrpc: String,
pub id: Option<serde_json::Value>,
pub method: String,
pub params: Option<serde_json::Value>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct McpResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<McpError>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct McpError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
#[derive(Debug, serde::Serialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, serde::Serialize)]
pub struct InitializeResult {
pub protocol_version: String,
pub capabilities: ServerCapabilities,
pub server_info: ServerInfo,
}
#[derive(Debug, serde::Serialize)]
pub struct ServerCapabilities {
pub experimental: serde_json::Value,
pub tools: ToolCapabilities,
}
#[derive(Debug, serde::Serialize)]
pub struct ToolCapabilities {
pub list_changed: bool,
}
+68 -9
View File
@@ -1,16 +1,24 @@
mod analytics;
mod compile;
mod models;
mod optimization;
mod pipeline;
mod storage;
mod tools;
mod mcp_server;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use serde_json::json; use serde_json::{json, Value};
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use std::time::Instant; use std::time::Instant;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::{error, info}; use tracing::{error, info};
use mt5_quant::mcp_server::{McpServer, Notification}; use crate::models::Config;
use mt5_quant::models::Config; use crate::pipeline::backtest::{BacktestParams, BacktestPipeline};
use mt5_quant::pipeline::backtest::{BacktestParams, BacktestPipeline};
use mt5_quant::{McpError, McpRequest, McpResponse};
#[derive(Parser)] #[derive(Parser)]
#[command(name = "mt5-quant")] #[command(name = "mt5-quant")]
@@ -37,6 +45,57 @@ struct Cli {
startup_delay: Option<u64>, startup_delay: Option<u64>,
} }
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct McpRequest {
jsonrpc: String,
id: Option<Value>,
method: String,
params: Option<Value>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct McpResponse {
jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<McpError>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct McpError {
code: i32,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
}
#[derive(Debug, serde::Serialize)]
pub struct ServerInfo {
name: String,
version: String,
}
#[derive(Debug, serde::Serialize)]
pub struct InitializeResult {
protocol_version: String,
capabilities: ServerCapabilities,
server_info: ServerInfo,
}
#[derive(Debug, serde::Serialize)]
pub struct ServerCapabilities {
experimental: Value,
tools: ToolCapabilities,
}
#[derive(Debug, serde::Serialize)]
pub struct ToolCapabilities {
list_changed: bool,
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
tracing_subscriber::fmt() tracing_subscriber::fmt()
@@ -119,9 +178,9 @@ async fn run_test_launch(ea: Option<String>, startup_delay: Option<u64>) -> Resu
async fn run_stdio_server() -> Result<()> { async fn run_stdio_server() -> Result<()> {
info!("Starting MT5-Quant MCP server on stdio"); info!("Starting MT5-Quant MCP server on stdio");
let server = std::sync::Arc::new(McpServer::new()); let server = std::sync::Arc::new(mcp_server::McpServer::new());
let (notification_tx, mut notification_rx) = let (notification_tx, mut notification_rx) =
tokio::sync::mpsc::unbounded_channel::<Notification>(); tokio::sync::mpsc::unbounded_channel::<mcp_server::Notification>();
server.set_notification_sender(notification_tx).await; server.set_notification_sender(notification_tx).await;
// Spawn notification sender task // Spawn notification sender task
@@ -195,7 +254,7 @@ async fn run_tcp_server(port: u16) -> Result<()> {
info!("Starting MT5-Quant MCP server on TCP port {}", port); info!("Starting MT5-Quant MCP server on TCP port {}", port);
let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?; let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).await?;
info!("Running TCP server on port {}", port); info!("Listening on 127.0.0.1:{}", port);
loop { loop {
let (socket, addr) = listener.accept().await?; let (socket, addr) = listener.accept().await?;
@@ -213,7 +272,7 @@ async fn handle_connection(socket: tokio::net::TcpStream) -> Result<()> {
let (reader, mut writer) = socket.into_split(); let (reader, mut writer) = socket.into_split();
let mut reader = BufReader::new(reader); let mut reader = BufReader::new(reader);
let mut line = String::new(); let mut line = String::new();
let server = McpServer::new(); let server = mcp_server::McpServer::new();
loop { loop {
line.clear(); line.clear();
-6
View File
@@ -31,12 +31,6 @@ pub struct McpServer {
notification_tx: Arc<Mutex<Option<mpsc::UnboundedSender<Notification>>>>, notification_tx: Arc<Mutex<Option<mpsc::UnboundedSender<Notification>>>>,
} }
impl Default for McpServer {
fn default() -> Self {
Self::new()
}
}
impl McpServer { impl McpServer {
pub fn new() -> Self { pub fn new() -> Self {
let config = ModelsConfig::load().unwrap_or_default(); let config = ModelsConfig::load().unwrap_or_default();
-6
View File
@@ -17,12 +17,6 @@ pub struct OptimizationPass {
pub struct OptimizationParser; pub struct OptimizationParser;
impl Default for OptimizationParser {
fn default() -> Self {
Self::new()
}
}
impl OptimizationParser { impl OptimizationParser {
pub fn new() -> Self { pub fn new() -> Self {
Self Self
-7
View File
@@ -40,13 +40,6 @@ impl Stage {
#[allow(dead_code)] #[allow(dead_code)]
pub struct StageExecutor; pub struct StageExecutor;
#[allow(dead_code)]
impl Default for StageExecutor {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)] #[allow(dead_code)]
impl StageExecutor { impl StageExecutor {
pub fn new() -> Self { pub fn new() -> Self {
+3 -4
View File
@@ -367,8 +367,7 @@ impl ReportDb {
keep_last: usize, keep_last: usize,
) -> Result<Vec<(String, String, Option<String>)>> { ) -> Result<Vec<(String, String, Option<String>)>> {
let conn = self.connect()?; let conn = self.connect()?;
let count: i64 = conn.query_row("SELECT COUNT(*) FROM reports", [], |r| r.get(0))?; let count: usize = conn.query_row("SELECT COUNT(*) FROM reports", [], |r| r.get(0))?;
let count = count as usize;
if count <= keep_last { if count <= keep_last {
return Ok(Vec::new()); return Ok(Vec::new());
@@ -405,8 +404,8 @@ impl ReportDb {
pub fn count(&self) -> Result<usize> { pub fn count(&self) -> Result<usize> {
let conn = self.connect()?; let conn = self.connect()?;
let n: i64 = conn.query_row("SELECT COUNT(*) FROM reports", [], |r| r.get(0))?; let n: usize = conn.query_row("SELECT COUNT(*) FROM reports", [], |r| r.get(0))?;
Ok(n as usize) Ok(n)
} }
/// Get the latest report by created_at (most recent first) /// Get the latest report by created_at (most recent first)