From 349044164936e7602da4355edeac5b5e3a2c6087 Mon Sep 17 00:00:00 2001 From: FXDavid Date: Mon, 8 Jun 2026 12:08:23 -0700 Subject: [PATCH] Audit fixes: public repo readiness + MCP improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MIT LICENSE - Anonymize broker path in config.example.yaml; add slippage_points + risk_per_trade - Fix encoding ansi→cp1252 in signals.py (cross-platform safe) - Fix install.py silently ignoring pip errors - Cap get_ohlcv at 2000 bars; add ping tool; clarify HTML report UX in descriptions - Escape symbol/timeframe in HTML report to prevent malformed output - Move run_*.py to scripts/; move example CSVs to examples/ - Update .gitignore to block accidental CSV commits from root - README: Python 3.9+ requirement, Chart.js internet note, new sections for examples/ and scripts/ Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 5 +++ LICENSE | 21 +++++++++++++ README.md | 29 +++++++++++++++-- config.example.yaml | 10 +++++- core/report_html.py | 7 +++-- core/signals.py | 2 +- .../arm_signals_EURUSD_H1.csv | 0 gold_signals.csv => examples/gold_signals.csv | 0 test_signals.csv => examples/test_signals.csv | 0 install.py | 6 ++-- mcp_server.py | 31 +++++++++++++++++-- run_2021.py => scripts/run_2021.py | 0 .../run_adx_filter.py | 0 run_byyear.py => scripts/run_byyear.py | 0 .../run_efficiency.py | 0 run_exit_opt.py => scripts/run_exit_opt.py | 0 run_fade.py => scripts/run_fade.py | 0 .../run_feature_analysis.py | 0 .../run_feature_split.py | 0 run_htf.py => scripts/run_htf.py | 0 run_paths.py => scripts/run_paths.py | 0 21 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 LICENSE rename arm_signals_EURUSD_H1.csv => examples/arm_signals_EURUSD_H1.csv (100%) rename gold_signals.csv => examples/gold_signals.csv (100%) rename test_signals.csv => examples/test_signals.csv (100%) rename run_2021.py => scripts/run_2021.py (100%) rename run_adx_filter.py => scripts/run_adx_filter.py (100%) rename run_byyear.py => scripts/run_byyear.py (100%) rename run_efficiency.py => scripts/run_efficiency.py (100%) rename run_exit_opt.py => scripts/run_exit_opt.py (100%) rename run_fade.py => scripts/run_fade.py (100%) rename run_feature_analysis.py => scripts/run_feature_analysis.py (100%) rename run_feature_split.py => scripts/run_feature_split.py (100%) rename run_htf.py => scripts/run_htf.py (100%) rename run_paths.py => scripts/run_paths.py (100%) diff --git a/.gitignore b/.gitignore index 95855bb..9d5ddb4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ __pycache__/ *.pyc .venv/ venv/ + +# signal CSVs — example files in examples/ are tracked; any CSV dropped in +# the root or scripts/ is personal data and should not be committed +/*.csv +/scripts/*.csv diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4fd98b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 davidcyberdash + +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. diff --git a/README.md b/README.md index 5fe5e50..1f66373 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ **Backtest any MetaTrader 5 indicator by talking to Claude.** +**Requirements:** Python 3.9+ · MetaTrader 5 (Windows) · Claude Code + Drop one include into your indicator, and MBT lets you fetch live data, read your indicator's signals, and run a full backtest with an HTML report — all through Claude, no scripting. @@ -129,8 +131,31 @@ a timestamp. | Tool | Purpose | |------|---------| -| `get_ohlcv` | live OHLCV bars for any symbol/timeframe | +| `ping` | check MT5 is running and reachable | +| `get_ohlcv` | live OHLCV bars for any symbol/timeframe (max 2000) | | `get_signals` | read your indicator's logged signals | -| `backtest` | replay + full metrics + HTML report | +| `backtest` | replay + full metrics + HTML report (requires internet for chart) | | `validate_signals` | check SL/TP geometry of every signal | | `get_config` | show the active terminal + signal file | + +> **Note:** the HTML report is written to `reports/` on your machine and opened in a +> browser. Claude cannot open it directly — it will give you the file path. + +--- + +## Examples + +`examples/` contains sample signal CSVs you can use to try MBT without attaching +an indicator. Point `signal_file` in `config.yaml` to any of them: + +```yaml +signal_file: "C:/abs/path/to/MBT/examples/test_signals.csv" +``` + +--- + +## Research scripts + +`scripts/` contains one-off analysis scripts used during strategy development. +They are not part of the core toolkit and have no documentation — treat them as +reference material, not user tools. diff --git a/config.example.yaml b/config.example.yaml index f560445..9c438e9 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -3,7 +3,7 @@ # Full path to the terminal64.exe of the MT5 instance your indicator runs on. # Right-click your MT5 shortcut -> Properties -> Target to find it. -mt5_path: "C:/Users/Administrator/Desktop/Ava Trade MT5 Terminal/terminal64.exe" +mt5_path: "C:/Path/To/Your/terminal64.exe" # The signal file your indicator writes (via SignalLogger.mqh). # Either a bare filename (looked up inside the MT5 .../MQL5/Files folder) @@ -22,3 +22,11 @@ ambiguous_bar: "loss" # Where generated HTML reports are written (relative to the MBT folder). reports_dir: "reports" + +# Spread + slippage allowance in points, used to estimate transaction cost per trade. +# 5 is a reasonable default for most forex pairs; increase for gold/indices. +slippage_points: 5.0 + +# Risk per trade as a decimal (0.01 = 1%) — used only to translate R units into +# account-percentage returns in the backtest report. Does not affect WIN/LOSS outcomes. +risk_per_trade: 0.01 diff --git a/core/report_html.py b/core/report_html.py index eddc433..8f50827 100644 --- a/core/report_html.py +++ b/core/report_html.py @@ -5,6 +5,7 @@ Uses Chart.js from a CDN so the output is a single self-contained .html the user can open in any browser or screenshot for content. """ +import html as _html import os from datetime import datetime @@ -33,6 +34,8 @@ def render(rep: BacktestReport, filename: str = None) -> str: path = os.path.join(reports_dir(), filename) + sym = _html.escape(rep.symbol) + tf = _html.escape(rep.timeframe) pf_str = "∞" if rep.profit_factor == float("inf") else f"{rep.profit_factor:.2f}" cards = "".join([ @@ -79,7 +82,7 @@ def render(rep: BacktestReport, filename: str = None) -> str: -MBT Backtest — {rep.symbol} {rep.timeframe} +MBT Backtest — {sym} {tf} -

Backtest Report — {rep.symbol} {rep.timeframe}

+

Backtest Report — {sym} {tf}

{rep.total} trades taken · {rep.signals_skipped} signals skipped (trade already open) · generated {datetime.now().strftime('%Y-%m-%d %H:%M')} · ambiguous bars counted as losses (conservative)
diff --git a/core/signals.py b/core/signals.py index ea7609e..608d85e 100644 --- a/core/signals.py +++ b/core/signals.py @@ -62,7 +62,7 @@ def load_signals(path: Optional[str] = None) -> List[Signal]: cfg = load_config() path = path or signal_file_path() - with open(path, encoding="ansi", newline="") as f: + with open(path, encoding="cp1252", errors="replace", newline="") as f: rows = list(csv.reader(f)) if not rows: diff --git a/arm_signals_EURUSD_H1.csv b/examples/arm_signals_EURUSD_H1.csv similarity index 100% rename from arm_signals_EURUSD_H1.csv rename to examples/arm_signals_EURUSD_H1.csv diff --git a/gold_signals.csv b/examples/gold_signals.csv similarity index 100% rename from gold_signals.csv rename to examples/gold_signals.csv diff --git a/test_signals.csv b/examples/test_signals.csv similarity index 100% rename from test_signals.csv rename to examples/test_signals.csv diff --git a/install.py b/install.py index 70d17ff..e35d62b 100644 --- a/install.py +++ b/install.py @@ -25,8 +25,10 @@ def step(msg): def install_deps(): step("Installing Python dependencies") - subprocess.run([sys.executable, "-m", "pip", "install", "-r", - os.path.join(ROOT, "requirements.txt")], check=False) + result = subprocess.run([sys.executable, "-m", "pip", "install", "-r", + os.path.join(ROOT, "requirements.txt")]) + if result.returncode != 0: + print("WARNING: pip install failed. Check the error above before continuing.") def make_config(): diff --git a/mcp_server.py b/mcp_server.py index 3ce0847..df42c23 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -15,7 +15,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from mcp.server.fastmcp import FastMCP -from core.connection import load_config, signal_file_path +from core.connection import load_config, connect, signal_file_path from core.ohlcv import fetch_recent from core.signals import load_signals from core.backtest import run_backtest, report_to_dict @@ -29,7 +29,9 @@ def get_ohlcv(symbol: str, timeframe: str, count: int = 100) -> dict: """ Fetch live OHLCV bars from the configured MT5 terminal (newest first). timeframe: 1m 5m 15m 30m 1h 4h 1d 1w. Generic — works for any symbol. + count: max 2000 bars. """ + count = min(count, 2000) try: bars = fetch_recent(symbol, timeframe, count) return {"symbol": symbol, "timeframe": timeframe, "count": len(bars), "bars": bars} @@ -75,7 +77,9 @@ def backtest(since_date: str = None, html_report: bool = True) -> dict: profit factor, expectancy, drawdown, per-regime breakdown) in R units. since_date: optional 'YYYY-MM-DD' filter on signals. - html_report: also write a standalone HTML report with an equity curve. + html_report: write a standalone HTML report to reports/ and return its path. + The report requires an internet connection (loads Chart.js from CDN). + Claude cannot open the file — share the path with the user to open in a browser. """ try: sigs = load_signals() @@ -124,7 +128,8 @@ def validate_signals() -> dict: @mcp.tool() def get_config() -> dict: - """Show the active MBT configuration (which terminal and signal file are in use).""" + """Show the active MBT configuration (which terminal and signal file are in use). + Note: the output includes local file paths — do not share it publicly.""" try: cfg = load_config() return {"config": cfg, "resolved_signal_file": signal_file_path()} @@ -132,5 +137,25 @@ def get_config() -> dict: return {"error": str(e)} +@mcp.tool() +def ping() -> dict: + """Check whether the MT5 terminal is running and reachable. Use this first + if any other tool returns an error, to confirm the connection is alive.""" + try: + connect() + import MetaTrader5 as mt5 + info = mt5.terminal_info() + if info is None: + return {"connected": False, "error": "terminal_info() returned None"} + return { + "connected": True, + "terminal": info.name, + "build": info.build, + "trade_allowed": info.trade_allowed, + } + except Exception as e: + return {"connected": False, "error": str(e)} + + if __name__ == "__main__": mcp.run(transport="stdio") diff --git a/run_2021.py b/scripts/run_2021.py similarity index 100% rename from run_2021.py rename to scripts/run_2021.py diff --git a/run_adx_filter.py b/scripts/run_adx_filter.py similarity index 100% rename from run_adx_filter.py rename to scripts/run_adx_filter.py diff --git a/run_byyear.py b/scripts/run_byyear.py similarity index 100% rename from run_byyear.py rename to scripts/run_byyear.py diff --git a/run_efficiency.py b/scripts/run_efficiency.py similarity index 100% rename from run_efficiency.py rename to scripts/run_efficiency.py diff --git a/run_exit_opt.py b/scripts/run_exit_opt.py similarity index 100% rename from run_exit_opt.py rename to scripts/run_exit_opt.py diff --git a/run_fade.py b/scripts/run_fade.py similarity index 100% rename from run_fade.py rename to scripts/run_fade.py diff --git a/run_feature_analysis.py b/scripts/run_feature_analysis.py similarity index 100% rename from run_feature_analysis.py rename to scripts/run_feature_analysis.py diff --git a/run_feature_split.py b/scripts/run_feature_split.py similarity index 100% rename from run_feature_split.py rename to scripts/run_feature_split.py diff --git a/run_htf.py b/scripts/run_htf.py similarity index 100% rename from run_htf.py rename to scripts/run_htf.py diff --git a/run_paths.py b/scripts/run_paths.py similarity index 100% rename from run_paths.py rename to scripts/run_paths.py