Audit fixes: public repo readiness + MCP improvements

- 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 <noreply@anthropic.com>
This commit is contained in:
FXDavid
2026-06-08 12:08:23 -07:00
co-authored by Claude Sonnet 4.6
parent f47ac228b1
commit 3490441649
21 changed files with 100 additions and 11 deletions
+5
View File
@@ -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
+21
View File
@@ -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.
+27 -2
View File
@@ -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.
+9 -1
View File
@@ -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
+5 -2
View File
@@ -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:
<html lang="en">
<head>
<meta charset="utf-8">
<title>MBT Backtest — {rep.symbol} {rep.timeframe}</title>
<title>MBT Backtest — {sym} {tf}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {{ background:#0d1117; color:#e6edf3; font-family:-apple-system,Segoe UI,Roboto,sans-serif;
@@ -102,7 +105,7 @@ def render(rep: BacktestReport, filename: str = None) -> str:
</style>
</head>
<body>
<h1>Backtest Report — {rep.symbol} {rep.timeframe}</h1>
<h1>Backtest Report — {sym} {tf}</h1>
<div class="sub">{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)</div>
+1 -1
View File
@@ -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:
+4 -2
View File
@@ -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():
+28 -3
View File
@@ -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")
View File
View File
View File