Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0135ffa3b4 | ||
|
|
4dded9b083 | ||
|
|
46e6de2474 | ||
|
|
a23dbee52f | ||
|
|
ffe55863ba | ||
|
|
ed82452200 | ||
|
|
941be795c1 | ||
|
|
6a54b2f54a | ||
|
|
e17e6b82fa | ||
|
|
a6618e6811 | ||
|
|
e6111c421f | ||
|
|
5056be3ee9 | ||
|
|
059141a3d5 | ||
|
|
53e9cd6ee6 | ||
|
|
11e7b27fe2 | ||
|
|
f1e4538b6a | ||
|
|
415fb90f72 | ||
|
|
ce172a077f | ||
|
|
9aa9bb83b7 |
@@ -0,0 +1,2 @@
|
||||
# Default owners for everything
|
||||
* @masdevid
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
@@ -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: ["*"]
|
||||
|
||||
@@ -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
|
||||
@@ -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: .
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: 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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -481,7 +481,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mt5-quant"
|
||||
version = "1.34.1"
|
||||
version = "1.34.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
+1
-1
@@ -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>"]
|
||||
|
||||
@@ -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.
|
||||
@@ -1,5 +1,7 @@
|
||||
# MT5-Quant
|
||||
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/masdevid/mt5-quant)
|
||||
|
||||
**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
@@ -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.
|
||||
@@ -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"
|
||||
+3
-3
@@ -7,12 +7,12 @@
|
||||
"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",
|
||||
"version": "1.34.2",
|
||||
"identifier": "https://github.com/masdevid/mt5-quant/releases/download/v1.34.2/mcp-mt5-quant-macos-arm64.tar.gz",
|
||||
"fileSha256": "TBD_CI_WILL_UPDATE",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user