TradingView MCP Bridge
by Tejas-040303
README.md
# TradingView MCP Bridge
Personal AI assistant for your charts and your broker account. Connects Claude Code to your locally running TradingView Desktop app via Chrome DevTools Protocol for AI-assisted chart analysis, Pine Script development, and workflow automation — and, optionally, to a **MetaTrader 5 terminal** for read-only account, market and economic-calendar data.
Two independent MCP servers:
| Server | Tools | Talks to | Needs |
|---|---|---|---|
| `tradingview` | 85 | TradingView Desktop over CDP | port 9222 |
| `mt5` | 19, read-only | MetaTrader 5 via a local Python bridge | port 8765 |
They are separate processes on purpose: closing TradingView must not take the broker tools down, and either can be registered alone. Everything stays on localhost.
> [!NOTE]
> This is a fork of [tradesdontlie/tradingview-mcp](https://github.com/tradesdontlie/tradingview-mcp). The TradingView side is upstream's work; the `mt5-bridge/` subsystem and the `mt5` MCP server are additions in this fork.
> [!WARNING]
> **This tool is not affiliated with, endorsed by, or associated with TradingView Inc., MetaQuotes Software Corp., or any broker.** It interacts with applications already running on your own machine. Review the [Disclaimer](#disclaimer) before use.
> [!IMPORTANT]
> **Requires a valid TradingView subscription.** This tool does not bypass or circumvent any TradingView paywall or access control. It reads from and controls the TradingView Desktop app already running on your machine.
> [!NOTE]
> **All data processing occurs locally on your machine.** No TradingView data is transmitted, stored, or redistributed externally by this tool.
> [!CAUTION]
> This tool accesses undocumented internal TradingView APIs via the Electron debug interface. These can change or break without notice in any TradingView update. Pin your TradingView Desktop version if stability matters to you.
## Documentation
| File | What it covers |
|---|---|
| `README.md` | What the tools do and how to run them — you are here |
| **[`HANDOVER.md`](HANDOVER.md)** | **Why the code is shaped this way.** Design rules, the mistakes that produced each guard, and the things that will bite you. Read this before changing analytics |
| **[`ROADMAP.md`](ROADMAP.md)** | What is planned, what it depends on, and what is deliberately not planned |
| [`mt5-bridge/README.md`](mt5-bridge/README.md) | Bridge setup, routes, and the full timestamp contract |
## How It Works (and why it's safe to run)
This tool does not connect to TradingView's servers, modify any TradingView files, or intercept any network traffic. It communicates exclusively with your locally running TradingView Desktop instance via Chrome DevTools Protocol (CDP) — a standard debugging interface built into all Chromium/Electron applications by Google, including VS Code, Slack, and Discord.
The debug port is disabled by default and must be explicitly enabled by you using a standard Chromium flag (`--remote-debugging-port=9222`). Nothing happens without that deliberate step.
## What This Tool Does Not Do
- Connect to TradingView's servers or APIs
- Store, transmit, or redistribute any market data
- Work without a valid TradingView subscription and installed Desktop app
- Bypass any TradingView paywall or access restriction
- Execute real trades — the TradingView side is chart interaction only, and the MT5 side is read-only with no order-placement code
- Work if TradingView changes their internal Electron structure
## Research Context
This project explores an open research question: **how can LLM-based agents interact with professional trading interfaces to support human decision-making?**
Specifically it investigates:
- How structured tool APIs (MCP) can bridge LLMs and stateful desktop financial applications
- What latency, context, and reliability constraints emerge when an agent operates on live chart data
- How agents handle ambiguous financial UI state (e.g. interpreting Pine Script output, reading indicator tables)
- Whether natural language is an effective interface for chart navigation and Pine Script development
- The failure modes of LLM agents operating in real-time data environments
This is not a trading bot. It is an interface layer that makes a trading application legible to an LLM agent, allowing researchers and developers to study human-AI collaboration in financial workflows.
See [RESEARCH.md](RESEARCH.md) for open questions, findings, and related work.
## Prerequisites
For the TradingView side:
- **TradingView Desktop app** (paid subscription required for real-time data)
- **Node.js 18+**
- **Claude Code** with MCP support (for MCP tools) or any terminal (for CLI)
- **macOS, Windows, or Linux**
For the optional MT5 side:
- **Windows** — the official `MetaTrader5` Python package does not exist for macOS or Linux
- **MetaTrader 5 terminal**, logged in to a broker account
- **Python 3.9+** and `pip install MetaTrader5`
Neither side depends on the other. Run one, the other, or both.
## What It Does
Gives your AI assistant eyes and hands on your own chart:
- **Pine Script development** — write, inject, compile, debug, and iterate on scripts with AI assistance
- **Chart navigation** — change symbols, timeframes, zoom to dates, add/remove indicators
- **Visual analysis** — read your chart's indicator values, price levels, and annotations
- **Draw on charts** — trend lines, horizontal lines, rectangles, text annotations
- **Manage alerts** — create, list, and delete price alerts
- **Replay practice** — step through historical bars, practice entries/exits
- **Screenshots** — capture chart state for AI visual analysis
- **Multi-pane layouts** — set up 2x2, 3x1, etc. grids with different symbols per pane
- **Monitor your chart** — stream JSONL from your locally running chart for local monitoring scripts
- **CLI access** — every MCP tool is also a `tv` CLI command, pipe-friendly with JSON output
- **Launch TradingView** — auto-detect and launch with debug mode from any platform
And, with the optional MT5 bridge running:
- **Broker account state** — balance, equity, margin, open positions and pending orders
- **Broker market data** — bid/ask/mid, spread, and OHLCV straight from your broker's feed
- **Trade history** — closed fills summarised into win rate, net P&L and exit reasons, for journaling
- **Economic calendar** — scheduled events with importance, forecast, previous, and `actual` once released
- **News blackout check** — one deterministic answer to "is it safe to act right now"
- **Trade chart capture** — a real trade marked on the chart and filed against its `position_id`, in an entry frame that stops at the entry bar so the outcome is not in the picture
## Install with Claude Code
Paste this into Claude Code and it will handle the rest:
> Install the TradingView MCP server. Clone https://github.com/Tejas-040303/tradingview-mcp.git, run npm install, add it to my MCP config at ~/.claude/.mcp.json, and launch TradingView with the debug port. Then verify the connection with tv_health_check.
Or follow the manual steps below.
## Quick Start
### 1. Install
```bash
git clone https://github.com/Tejas-040303/tradingview-mcp.git
cd tradingview-mcp
npm install
```
### 2. Launch TradingView with CDP
TradingView Desktop must be running with Chrome DevTools Protocol enabled on port 9222.
**Mac:**
```bash
./scripts/launch_tv_debug_mac.sh
```
**Windows:**
```bash
scripts\launch_tv_debug.bat
```
**Linux:**
```bash
./scripts/launch_tv_debug_linux.sh
```
**Or launch manually on any platform:**
```bash
/path/to/TradingView --remote-debugging-port=9222
```
**Or use the MCP tool** (auto-detects your install):
> "Use tv_launch to start TradingView in debug mode"
### 3. Add to Claude Code
Add to your Claude Code MCP config (`~/.claude/.mcp.json` or project `.mcp.json`):
```json
{
"mcpServers": {
"tradingview": {
"command": "node",
"args": ["/path/to/tradingview-mcp/src/server.js"]
},
"mt5": {
"command": "node",
"args": ["/path/to/tradingview-mcp/src/mt5-server.js"]
}
}
}
```
Replace `/path/to/tradingview-mcp` with your actual path. Omit the `mt5` entry if you are not using the broker side.
MCP servers only load at startup, so **fully exit and relaunch Claude Code** after editing this — reloading a session is not enough.
### 4. Verify
Ask Claude: *"Use tv_health_check to verify TradingView is connected"*
And, if you registered the MT5 server: *"Use mt5_health to check the broker connection"* (the bridge must be running — see below).
### 5. One command to start everything
Instead of juggling terminals for TradingView and the bridge:
```bash
npm run start:all # start whatever is not already running
npm run status # report what is up, start nothing
```
It probes both services first, starts only what is missing, and leaves anything
already running alone. It then prints the addresses worth knowing — dashboard
URL, API endpoint, CDP endpoint, every bridge route, and the log paths — rather
than just "up" or "down". Child output is streamed with a `[tv]` / `[bridge]`
prefix and mirrored to `logs/`; lines that look like errors are surfaced even
without `--verbose`, and a service that exits non-zero prints the tail of its
log. Ctrl+C stops only the services it started.
```bash
node scripts/start.js --no-tv # bridge only
node scripts/start.js --no-bridge # TradingView only
node scripts/start.js --no-dashboard # skip the React build
node scripts/start.js --verbose # stream all child output
```
It also builds the React dashboard when the sources have changed, installing its
dependencies on first run. A build failure is **not fatal** — the bridge and
TradingView still come up, and the previously built bundle keeps serving.
After the bridge is up it reports whether MT5 is actually connected — a bridge
answering on its port and a terminal being reachable are different things, and
conflating them is how "it's running" turns into a confusing debugging session.
### 6. Dashboard
With the bridge running, open:
```
http://127.0.0.1:8765/
```
Four tabs on one page, no navigation between them:
- **Status** — account, open positions, pending orders, realised P&L by period,
and a news blackout banner. Polls every 5s.
- **Analytics** — closed trade history: 15 stat tiles, a rule-based trading
coach, equity curve with brush-zoom, a GitHub-style trading calendar,
heatmaps, session/symbol/weekday/exit breakdowns, holding-time and
position-size analysis, P&L distribution, and a virtualised trade explorer
with CSV/JSON export.
- **Bot** — the simulated strategy: the open paper position with its stop,
target and lot, any confirmation waiting on an entry bar, the detected setups
list, and the backtest behind them. The setups table deliberately carries no
profit column — it is the list to check against a chart, and a P&L figure
there invites reading it as a result.
- **Research** — parameter sweeps and signal-versus-fill reconciliation. The
sweep is opt-in behind a button because it is the slowest route on the
bridge, and its verdict leads rather than its table: a leaderboard always has
a winner, so when the result cannot be endorsed the rows render subdued with
the reason above them.
Read-only throughout — the page cannot place, modify or close anything.
The Analytics, Bot and Research tabs are a React app built by the launcher on first run. If that
build is skipped or fails, `/` falls back to a plain no-build page, and both
fallbacks stay reachable at `/dashboard/index.html` and
`/dashboard/history.html`.
Two conventions worth knowing before reading any number on it:
- **Nothing is computed in the browser.** Every statistic comes from the API, so
a tile can never disagree with the table beneath it.
- **Sample size is visible.** Cells and bars below the reliability threshold are
drained of colour, and each grid states how many of its own cells are
unreliable. A deep green cell built on two trades looks identical to one built
on two hundred — that is the failure mode this guards against.
### 7. Optional — the MT5 bridge
MetaTrader 5 has no Node binding, so the `mt5` server talks to a small local Python process:
```bash
pip install MetaTrader5
python mt5-bridge/bridge.py
```
It binds `127.0.0.1` only and serves GET requests exclusively — every other verb returns 405, and no route can place, modify or cancel an order.
For the economic calendar, compile and run `mt5-bridge/calendar_export.mq5` inside MetaEditor once. The Python package exposes no calendar API, so the terminal-side script writes the data out; the bridge then finds the file on its own.
Full setup, routes and caveats: **[mt5-bridge/README.md](mt5-bridge/README.md)**.
## CLI
Every MCP tool is also accessible as a `tv` CLI command. All output is JSON for piping with `jq`.
```bash
# Install globally (optional)
npm link
# Or run directly
node src/cli/index.js <command>
```
### Quick Examples
```bash
tv status # check connection
tv quote # current price
tv symbol AAPL # change symbol
tv ohlcv --summary # price summary
tv screenshot -r chart # capture chart
tv pine compile # compile Pine Script
tv pane layout 2x2 # 4-chart grid
tv pane symbol 1 ES1! # set pane symbol
tv stream quote | jq '.close' # monitor price changes
```
### All Commands
```
tv status / launch / state / symbol / timeframe / type / info / search
tv quote / ohlcv / values
tv data lines/labels/tables/boxes/strategy/trades/equity/depth/indicator
tv pine get/set/compile/analyze/check/save/new/open/list/errors/console
tv draw shape/list/get/remove/clear
tv alert list/create/delete
tv watchlist get/add
tv indicator add/remove/toggle/set/get
tv layout list/switch
tv pane list/layout/focus/symbol
tv tab list/new/close/switch
tv replay start/step/stop/status/autoplay/trade
tv stream quote/bars/values/lines/labels/tables/all
tv ui click/keyboard/hover/scroll/find/eval/type/panel/fullscreen/mouse
tv screenshot / discover / ui-state / range / scroll
```
## Streaming
The `tv stream` commands poll your locally running TradingView Desktop instance at regular intervals via Chrome DevTools Protocol on localhost.
No connection is made to TradingView's servers. All data stays on your machine.
> [!WARNING]
> Programmatic consumption of TradingView data may conflict with their Terms of Use regardless of the data source. You are solely responsible for ensuring your usage complies.
```bash
tv stream quote # price tick monitoring
tv stream bars # bar-by-bar updates
tv stream values # indicator value monitoring
tv stream lines --filter "NY Levels" # price level monitoring
tv stream tables --filter Profiler # table data monitoring
tv stream all # all panes at once (multi-symbol)
```
## How Claude Knows Which Tool to Use
Claude reads [`CLAUDE.md`](CLAUDE.md) automatically when working in this project. It contains a complete decision tree:
| You say... | Claude uses... |
|------------|---------------|
| "What's on my chart?" | `chart_get_state` → `data_get_study_values` → `quote_get` |
| "What levels are showing?" | `data_get_pine_lines` → `data_get_pine_labels` |
| "Read the session table" | `data_get_pine_tables` with `study_filter` |
| "Give me a full analysis" | `quote_get` → `data_get_study_values` → `data_get_pine_lines` → `data_get_pine_labels` → `data_get_pine_tables` → `data_get_ohlcv` (summary) → `capture_screenshot` |
| "Switch to AAPL daily" | `chart_set_symbol` → `chart_set_timeframe` |
| "Write a Pine Script for..." | `pine_set_source` → `pine_smart_compile` → `pine_get_errors` |
| "Start replay at March 1st" | `replay_start` → `replay_step` → `replay_trade` |
| "Set up a 4-chart grid" | `pane_set_layout` → `pane_set_symbol` for each pane |
| "Draw a level at 24500" | `draw_shape` (horizontal_line) |
| "Take a screenshot" | `capture_screenshot` |
| "Is it safe to trade right now?" | `mt5_blackout` |
| "How did last month go?" | `mt5_deals` (summary) |
| "What's my account state?" | `mt5_health` → `mt5_account` → `mt5_positions` |
| "What does my broker call gold?" | `mt5_symbol_search` |
| "Mark my real entries on the chart" | `mt5_deals` → `draw_shape` for each fill |
| "Which losses landed near news?" | `mt5_deals` → `mt5_calendar`, joined on `time_utc` |
The last two are the point of running both servers together — neither system can answer them alone.
## Tool Reference — TradingView (85 MCP tools)
### Chart Reading
| Tool | When to use | Output size |
|------|------------|-------------|
| `chart_get_state` | First call — get symbol, timeframe, all indicator names + IDs | ~500B |
| `data_get_study_values` | Read current RSI, MACD, BB, EMA values from all indicators | ~500B |
| `quote_get` | Get latest price, OHLC, volume | ~200B |
| `data_get_ohlcv` | Get price bars. **Use `summary: true`** for compact stats | 500B (summary) / 8KB (100 bars) |
### Custom Indicator Data (Pine Drawings)
Read `line.new()`, `label.new()`, `table.new()`, `box.new()` output from any visible Pine indicator.
| Tool | When to use | Output size |
|------|------------|-------------|
| `data_get_pine_lines` | Read horizontal price levels (support/resistance, session levels) | ~1-3KB |
| `data_get_pine_labels` | Read text annotations + prices ("PDH 24550", "Bias Long") | ~2-5KB |
| `data_get_pine_tables` | Read data tables (session stats, analytics dashboards) | ~1-4KB |
| `data_get_pine_boxes` | Read price zones / ranges as {high, low} pairs | ~1-2KB |
**Always use `study_filter`** to target a specific indicator: `study_filter: "Profiler"`.
### Chart Control
| Tool | What it does |
|------|-------------|
| `chart_set_symbol` | Change ticker (BTCUSD, AAPL, ES1!, NYMEX:CL1!) |
| `chart_set_timeframe` | Change resolution (1, 5, 15, 60, D, W, M) |
| `chart_set_type` | Change style (Candles, HeikinAshi, Line, Area, Renko) |
| `chart_manage_indicator` | Add/remove indicators. **Use full names**: "Relative Strength Index" not "RSI" |
| `chart_scroll_to_date` | Jump to a date (ISO: "2025-01-15") |
| `chart_set_visible_range` | Zoom to exact range (unix timestamps) |
| `symbol_info` / `symbol_search` | Symbol metadata and search |
| `indicator_set_inputs` / `indicator_toggle_visibility` | Change indicator settings, show/hide |
### Multi-Pane Layouts
| Tool | What it does |
|------|-------------|
| `pane_list` | List all panes with symbols and active state |
| `pane_set_layout` | Change grid: `s`, `2h`, `2v`, `2x2`, `4`, `6`, `8` |
| `pane_focus` | Focus a specific pane by index |
| `pane_set_symbol` | Set symbol on any pane |
### Tab Management
| Tool | What it does |
|------|-------------|
| `tab_list` | List open chart tabs |
| `tab_new` / `tab_close` | Open/close tabs |
| `tab_switch` | Switch to a tab by index |
### Pine Script Development
| Tool | Step |
|------|------|
| `pine_set_source` | 1. Inject code into editor |
| `pine_smart_compile` | 2. Compile with auto-detection + error check |
| `pine_get_errors` | 3. Read compilation errors if any |
| `pine_get_console` | 4. Read log.info() output |
| `pine_save` | 5. Save to TradingView cloud |
| `pine_get_source` | Read current script (**warning: can be 200KB+ for complex scripts**) |
| `pine_new` | Create blank indicator/strategy/library |
| `pine_open` / `pine_list_scripts` | Open or list saved scripts |
| `pine_analyze` | Offline static analysis (no chart needed) |
| `pine_check` | Server-side compile check (no chart needed) |
### Replay Mode
| Tool | Step |
|------|------|
| `replay_start` | Enter replay at a date |
| `replay_step` | Advance one bar |
| `replay_autoplay` | Auto-advance (set speed in ms) |
| `replay_trade` | Buy/sell/close positions |
| `replay_status` | Check position, P&L, date |
| `replay_stop` | Return to realtime |
### Drawing, Alerts, UI Automation
| Tool | What it does |
|------|-------------|
| `draw_shape` | Draw horizontal_line, trend_line, rectangle, text |
| `draw_list` / `draw_remove_one` / `draw_clear` | Manage drawings |
| `alert_create` / `alert_list` / `alert_delete` | Manage price alerts |
| `capture_screenshot` | Screenshot (regions: full, chart, strategy_tester) |
| `capture_trade` | Screenshot a real MT5 trade marked up on the chart, bound to its `position_id` |
| `batch_run` | Run action across multiple symbols/timeframes |
| `watchlist_get` / `watchlist_add` | Read/modify watchlist |
| `layout_list` / `layout_switch` | Manage saved layouts |
| `ui_open_panel` / `ui_click` / `ui_evaluate` | UI automation |
| `tv_launch` / `tv_health_check` / `tv_discover` | Connection management |
## Tool Reference — MT5 (19 read-only tools)
Served by the separate `mt5` server. Requires `mt5-bridge/bridge.py` running. **None of these can open, modify or close a position.**
| Tool | When to use | Output size |
|------|------------|-------------|
| `mt5_health` | First call — bridge and terminal state, account identity, broker clock offset | ~300 B |
| `mt5_account` | Balance, equity, margin, free margin, leverage | ~300 B |
| `mt5_symbol_search` | Find what your broker calls an instrument. **Names are not guessable** — spot gold is `GOLD.i#` on XM, and `XAUUSD` may not exist | ~1 KB |
| `mt5_quote` | Bid, ask, **mid**, spread. CFDs report no last price or volume, so both are null — use `mid` | ~250 B |
| `mt5_bars` | Broker OHLCV. Summary by default | ~600 B (summary) |
| `mt5_positions` / `mt5_orders` | What is open right now, types decoded | varies |
| `mt5_deals` | Closed fills. **Summary by default** — win rate, net P&L, exit reasons | ~500 B / ~15 KB paged |
| `mt5_trades` | Fills **paired into trades** by position_id — entry, exit, duration, partial closes. MT5 reports deals, not trades; this is the only way to see how long a position was held | ~500 B / paged |
| `mt5_excursions` | What price did **during** each trade and **after** you left: MAE/MFE, capture ratio, and whether price returned to entry within 5/15/60 min of a losing exit | ~2-6 KB |
| `mt5_analytics` | Expectancy, payoff ratio, drawdown, streaks, and performance by session / exit reason / symbol / hour | ~2-5 KB |
| `mt5_calendar` | Scheduled events with importance, forecast, previous, `actual` | ~2-4 KB |
| `mt5_blackout` | **"Is it safe to act right now"** — one deterministic answer | ~600 B |
| `mt5_setups` | Entry signals the strategy detects, **no P&L attached** — the checkpoint before any backtest number matters | ~2-6 KB |
| `mt5_backtest` | Those signals replayed with stops, targets, partials and the breakeven trail | ~1-2 KB |
| `mt5_sweep` | Every parameter combination judged on bars it was not chosen on. **Slow** — thirty configurations by default | ~5-15 KB |
| `mt5_paper` | What the strategy would be doing right now: open position, pending confirmation, recent trades | ~2-4 KB |
| `mt5_reconcile` | Signals against actual fills: followed, missed, discretionary | ~1-3 KB |
Two things worth knowing before building on these:
- **`mt5_deals` only shows trades that were taken.** Skipped setups leave no trace in MT5, so any journal that needs them must log signals separately.
- **`mt5_excursions` needs M1 bar history downloaded** in the terminal for the period, and is slower than the rest because it fetches bars. The figure it exists to produce is the share of stop-outs where price came back to your entry within five minutes — the direct test of "my stop sat inside normal noise" against "my entry was wrong".
- **`actual` is `null` until an event is released.** That is correct, not missing data — `forecast` and `previous` are known ahead of time.
### Strategy engine (bridge routes, not yet MCP tools)
Two read-only routes on the same bridge replay a strategy over broker bars. The
strategy is a dict — every entry condition a toggle, every threshold a number —
so a belief about the setup is a field that can be swept rather than an opinion
baked into a branch.
| Route | What it answers |
|-------|-----------------|
| `/setups?symbol=GOLD.i%23` | Every entry signal in the window, with timestamps, levels and which conditions fired. **No P&L.** This is the checkpoint: pull a few up on a chart and see whether they are setups you would take |
| `/backtest?symbol=GOLD.i%23` | The same signals replayed with stops, targets, partials and the breakeven trail, summarised as expectancy, win rate and average R |
| `/sweep?symbol=GOLD.i%23` | Every parameter combination run on both halves of the window — the first 70% chooses, the last 30% judges |
| `/reconcile?symbol=GOLD.i%23` | What the strategy signalled against what the account actually did: followed, missed, and discretionary |
| `/strategy1/setups?symbol=GOLD.i%23` | Strategy 1 (multi-timeframe liquidity sweep) detection, with its rejections |
| `/strategy1/backtest?symbol=GOLD.i%23` | Strategy 1 replayed, with stops, structural targets and the spread-clearing trail |
| `/strategy1/walkforward?symbol=GOLD.i%23` | Strategy 1's parameter grid across a chronological split, with the same noise floor `/sweep` applies |
| `/paper?symbol=GOLD.i%23` | What the strategy would be doing right now — open position with its levels, a pending confirmation, recent closed trades |
Both accept `conditions=fvg,liquidity_sweep`, `required=`, `mode=any|all|at_least`,
`confirmation=close_beyond|engulfing|rejection`, `buffer_pips=`, `risk_pct=`,
`target_r=`, `partial_pct=`, `partial_at_r=`, `trail=1.0` or `trail=none`,
`timeframe=`, `count=`, `balance=`.
Three rules keep the numbers honest, and each one flatters a strategy when
broken:
- **Entry fills at the next bar's open**, never at the confirmation candle's
close — that close is not known until the bar has ended.
- **A bar containing both the stop and the target resolves as the stop.** Bar
data cannot order two intrabar touches; assuming the target is how a losing
strategy backtests profitably.
- **Sizing goes through the same `position_size` the live bot will use**, so a
balance too small for the instrument produces *refused* setups with reasons,
not fractional lots the broker would reject. On a random-walk series the
simulator returns an average R of roughly zero, which is the point.
Simulated trades come out in exactly the shape `mt5_trades` produces, so they
flow through the existing analytics and dashboards with no second code path.
`/sweep` is built to resist a conclusion rather than produce one. Thirty
configurations always have a best one — thirty coin-flipping strategies do too
— so it reports the median alongside the winner, the rank correlation between
the two halves, and the in-sample-to-out-of-sample gap that measures fitting.
It refuses to call a result trustworthy unless the winner is actually
profitable out of sample, the ordering survives the split, *and* the winner is
clear of the median. On a random walk it correctly declines to endorse
anything, even though the rank correlation there is a healthy 0.55 —
management parameters reorder the R distribution the same way in any window,
which looks like signal and is not.
Axes are set from the URL: `axes=manage.trail_to_be_at_r:0.5,1.0,none|target.r:2,3`.
**Strategy 1** is the multi-timeframe liquidity sweep: levels are swept on 4H,
1H, 30M or 15M, and the entry trigger is found on 3M or 1M. Its two routes take
`sweep_timeframes=`, `entry_timeframe=`, `trigger=`, `fallback=` (`none`
disables the MSS fallback), `wait_bars=`, `max_uses=`, `buffer_price=`,
`target_mode=`, `min_r=`, `partial_pct=`, `trail_at_r=`, `size_mode=`,
`risk_pct=`, `fixed_lot=`, `balance=` and `spread=`.
Bars are fetched over **one aligned window**: the entry timeframe sets the
span, and each sweep timeframe gets only enough bars to cover it plus a
lookback. A fixed count per timeframe would pull years of 4H candles against
days of 3M ones, and every ancient sweep would then trigger against the first
few entry bars — signals manufactured by the shape of the request rather than
found in the market.
`/strategy1/walkforward` splits by **time, not index**. Cutting six series each
at a fraction of its own length puts the boundary at a different instant on
every one — 70% of 84 four-hour bars and 70% of 6667 three-minute bars are not
the same moment, and the halves would silently overlap. The entry timeframe
defines one cut time and every series is sliced against it. The sweep
timeframes carry a warm-up of earlier bars so swings can form, and those bars
can only *detect*: a sweep older than the entry window is refused, so no signal
crosses the boundary. Its default grid includes both controls — `trail_at_r:
null` for "does trailing help at all" and `fallback: null` for "does MSS add
anything".
All three routes report **rejections with reasons**, because "the strategy found
nothing" and "the target rule is too strict" produce the same trade count and
are entirely different findings. If a timeframe has no history the strategy
narrows rather than failing, and the missing ones are named.
`/paper` is reconstructed from bars on every call rather than accumulated in a
state file. A paper runner that carries state drifts: restart it and the
position is gone, run two and they disagree, and the record of what it "would
have done" quietly becomes a record of when the process was alive.
It also drops the bar that is **still forming**. MT5 returns the current
incomplete candle as the last row of `copy_rates_from_pos`, looking exactly
like a finished one, with a high, low and close that keep changing. Acting on
it is the live twin of the lookahead `knowable_at` exists to prevent — signals
appear and vanish as the minute progresses, and live results stop resembling
the backtest for reasons nobody can reproduce afterwards.
`/reconcile` exists because **MT5 records only trades that were taken** — a
setup you saw and passed on leaves no trace anywhere. Matching signals against
fills recovers it, in three buckets: `followed`, `missed` (signalled, not
traded) and `discretionary` (traded with no signal behind it).
The comparison it makes is deliberately narrow. Simulated P&L assumes perfect
fills, no spread and no slippage; real P&L does not. So "signals I took" versus
"signals I skipped" is compared simulated-to-simulated, with the real numbers
reported separately. The difference between a followed signal's simulated and
real result is its own diagnostic — the execution cost, and the reason a
backtest saying 1.2R can sit above an account saying 0.4R.
### The journal (the only writable service)
`journal_service.py` runs as its **own process on its own port** (8766). That
separation is the architecture, not an accident: `bridge.py` answers GET and
returns 405 for anything else, and a reader that cannot be made to write is a
reader nobody has to audit. Adding POST routes to it would spend that guarantee
to save a port number.
What the journal can write is a **local SQLite file**. It imports no broker
client, and a test asserts it never will.
```
GET /health /summary /signals
POST /signals /decision /trade /screenshot /prune
```
It records what MetaTrader structurally cannot: the setups you **passed on**,
and why. A skipped setup leaves no trace in any broker's history, so the two
questions most likely to explain a losing account — were the ones I skipped the
good ones, and why did I close early — are unanswerable unless something writes
them down at the time.
Three things it is careful about:
- **A self-report is not a fact.** "Skipped for news" records what was *said*,
and a reason given after the outcome is known may be a rationalisation.
Fields are named `skip_reasons_claimed` and `exit_kinds_claimed` so nothing
downstream can quietly promote them into measurements. What *is* evidence is
the join: replay the skipped signals and see what they would have done.
- **Coverage leads.** The summary reports how many signals have no decision
recorded, because a journal covering a fifth of them cannot support a claim
about which ones get skipped.
- **Retention is a dry run by default.** `POST /prune` lists the screenshot
files it *would* delete; deleting requires `{"apply": true}`. The database
rows survive with `pruned_at` set — losing the picture is not the same as
losing the fact that one existed. There is no delete route at all: a record
you can quietly remove after a bad trade is not a record.
### Trade chart capture — the picture the journal cannot take
`capture_trade` (a TradingView tool, since it drives the chart) reads a trade
from the bridge, marks it on the chart, screenshots it, and tells the journal
the image exists. Broker symbol in, `OANDA:XAUUSD` out, `position_id` attached.
```bash
tv capture-trade # the most recent closed trade
tv capture-trade -p 998877 --stop 1944.75 # a specific one, stop drawn
tv capture-trade -p 998877 -k review # the whole trade, exit and all
```
There are two kinds, and only one of them has rules:
| Kind | Shows |
|---|---|
| `entry` (default) | Everything knowable at the entry, and nothing else |
| `review` | The whole trade, entry through exit — the outcome is the subject |
**A screenshot taken after the trade closed contains the answer.** It is a fine
record of what happened and worthless for reviewing the entry *decision* —
worse than worthless, because it feels like review. Four things leak the future
into an entry frame, and only the first is obvious:
1. **The visible range.** It ends at the entry bar. The planner computes the
window, and a test asserts no shape and no edge sits past it.
2. **Exit markup.** Not drawn at all — not the line, not the P&L, not the exit
reason in the label.
3. **The timeframe.** Deriving it from the trade's *duration* is right for a
review, but on an entry frame it tells the reader how long the trade lasted
before they have looked at a candle. Duration is an outcome; entry captures
use a fixed timeframe unless you name one.
4. **Levels inferred from the exit fill.** A trade closed at stop loss exits
*at* its stop, which recovers a level MT5 never stored — sound, and still
inadmissible here. It only yields a level for trades that were stopped out,
so the mere presence of a stop rectangle would announce the outcome. An
entry capture draws a stop only from one you supply (`--stop`, from the
plan, the journal or order history) and says so in its notes when it has
none.
Each of those four is pinned by a test that was verified by reintroducing the
bug and watching it fail.
Two smaller things. There is no long/short position widget in `draw_shape`, so
risk and reward are rectangles from entry to stop and entry to target. And the
markup is removed again after the shot — by entity id, so anything you drew
yourself stays.
**Symbols are mapped from a table, never guessed.** XM's `GOLD.i#` is
`OANDA:XAUUSD`; a wrong mapping does not throw, it produces a plausible chart of
a *different instrument* filed against your position, and nothing downstream
could tell. Broker decorations are stripped to a root (`GOLD.i#` → `GOLD`,
`EURUSDm` → `EURUSD`) and the root is looked up. An unrecognised one is an error
naming what to add — put your own in `symbol-map.json` at the repo root, or
point `TV_SYMBOL_MAP` at a file:
```json
{ "GOLD.i#": "OANDA:XAUUSD", "US30.cash": "TVC:DJI" }
```
**TradingView prices are not your broker's**, so every capture is labelled with
that. Read the picture for structure — where the entry sat relative to the swing
— not to check a fill.
Captures are a **cache, not an archive**: a pure function of trade data plus
chart settings, so deleting one loses nothing that re-running this cannot
rebuild. Retention lives in the journal's `POST /prune`, which deletes files and
keeps the rows.
### Execution — the only process that can move money
Three processes, three capabilities. `bridge.py` reads and cannot write. The
journal writes a local file and cannot trade. `execution_service.py` can trade
and does nothing else. Two of the three are incapable of the failure that
matters, so only one needs the paranoid review.
**It contains no strategy.** It never decides *what* to trade — something
outside hands it an explicit order and it refuses or forwards. A bug in the
detectors cannot become a bug that places orders, because the detectors are not
importable from there, and a test asserts it.
Started only with `npm run start:all -- --exec`. The other services start by
default; this one is a decision.
```
GET /status POST /arm /disarm /order
```
Every default is the safe one, and each has to be overridden deliberately:
| Default | Why |
|---|---|
| **Disarmed** | Nothing can be placed until `POST /arm` |
| **Arming expires** | An arm-once flag is one nobody remembers to clear |
| **Dry run** | Armed is not the same as firing |
| **Demo accounts only** | `allow_live` must be set at arm time |
| **Symbols are an allowlist** | Empty means none, never everything |
| **Stop required** | No configuration allows an order without one |
Arming also **names the account**, and the terminal's own login must match it —
a session pointed somewhere you did not expect refuses rather than trades. On
top of that sit a max lot, a max risk %, a max open position count, a daily
loss limit that ends the session rather than letting it be won back, a news
blackout check, and a `client_id` guard so a retried request cannot fill twice.
Two failures resolve toward stopping rather than trading: an unreadable
terminal is a refusal ("refusing rather than trading blind"), and an unwritable
audit log is a refusal too — an order nobody can reconstruct afterwards should
not have happened. **Disarming is the exception**: it never fails, because a
kill switch with preconditions is not a kill switch.
All of that policy lives in `execution.py`, which is pure — no MetaTrader5, no
network, no filesystem — and carries 38 tests. `execution_service.py` holds
exactly one function that can place an order.
Setup, routes, and the full timestamp contract: **[mt5-bridge/README.md](mt5-bridge/README.md)**.
## Context Management
Tools return compact output by default to minimize context usage. For a typical "analyze my chart" workflow, total context is ~5-10KB instead of ~80KB.
| Feature | How it saves context |
|---------|---------------------|
| Pine lines | Returns deduplicated price levels only, not every line object |
| Pine labels | Capped at 50 per study, text+price only |
| Pine tables | Pre-formatted row strings, no cell metadata |
| Pine boxes | Deduplicated {high, low} zones only |
| OHLCV summary mode | Stats + last 5 bars instead of all bars |
| Indicator inputs | Encrypted/encoded blobs auto-filtered |
| `verbose: true` | Pass on any pine tool to get raw data with IDs/colors when needed |
| `study_filter` | Target one indicator instead of scanning all |
## Finding TradingView on Your System
Launch scripts and `tv_launch` auto-detect TradingView. If auto-detection fails:
| Platform | Common Locations |
|----------|-----------------|
| **Mac** | `/Applications/TradingView.app/Contents/MacOS/TradingView` |
| **Windows** | `%LOCALAPPDATA%\TradingView\TradingView.exe`, `%PROGRAMFILES%\WindowsApps\TradingView*\TradingView.exe` |
| **Linux** | `/opt/TradingView/tradingview`, `~/.local/share/TradingView/TradingView`, `/snap/tradingview/current/tradingview` |
The key flag: `--remote-debugging-port=9222`
## Testing
```bash
npm run test:unit # 241 Node tests, no TradingView needed
python -m unittest discover mt5-bridge # 710 Python tests, no terminal needed
npm test # adds e2e — needs TradingView on port 9222
```
`test:unit` covers Pine Script static analysis, server-side compilation, CLI routing, chart-readiness detection, the MT5 bridge client, and the trade-capture planner — including the four ways the future can leak into an entry capture, each of which was verified by reintroducing the bug and watching the test fail. The Python suite covers the bridge's pure logic — timeframe resolution, bar and deal summaries, calendar filtering, news blackout windows, broker-clock conversion — plus every route against a faked MetaTrader 5 module.
Both run in CI on Node 20 and 22. Neither needs TradingView, a broker terminal, or a network.
## Architecture
```
Claude Code ─┬─ MCP "tradingview" (stdio) ─→ CDP :9222 ─→ TradingView Desktop (Electron)
│
└─ MCP "mt5" (stdio) ─→ HTTP :8765 ─→ bridge.py ─→ MetaTrader 5 terminal
↑
calendar_export.mq5 ┘ (writes MQL5/Files/*.json)
```
- **Transport**: MCP over stdio — 85 TradingView tools + 19 MT5 tools — plus a `tv` CLI (30 commands, 66 subcommands)
- **Connections**: Chrome DevTools Protocol on localhost:9222; read-only HTTP bridge on localhost:8765
- **Streaming**: Poll-and-diff loop with deduplication, JSONL output to stdout
- **Dashboard**: React app built to static assets the bridge serves, same-origin with its API. The Python side stays stdlib-only
- **No runtime dependencies** beyond `@modelcontextprotocol/sdk` and `chrome-remote-interface` on the Node side, and `MetaTrader5` on the Python side. The dashboard's build-time dependencies live in `dashboard-app/` and are not needed to run the MCP servers
### Why MT5 needs a Python bridge
MetaTrader 5 has no Node binding — the official package is Python and Windows-only. And its economic calendar is reachable only from MQL5 (`CalendarValueHistory()`), not from the Python package, so a terminal-side script exports it to JSON that the bridge reads back.
The bridge is **read-only by construction**: `order_send` and friends are simply absent, only `GET` is accepted, and no route maps to an order operation. Execution, if ever added, belongs in a separate process.
### Timestamps
MetaTrader 5 reports times against the **broker clock**, not UTC. Every MT5 timestamp is labelled twice — `time_server` (no `Z`, because it is not UTC) and `time_utc` — and consumers join on `time_utc`. Where the offset cannot be established, `time_utc` is `null` rather than a guess, and the blackout check refuses to answer rather than comparing mismatched clocks.
## Attributions
This project is not affiliated with, endorsed by, or associated with:
- **TradingView Inc.** — TradingView is a trademark of TradingView Inc.
- **MetaQuotes Software Corp.** — MetaTrader and MetaTrader 5 are trademarks of MetaQuotes Software Corp.
- **Any broker** — broker names and symbols appearing in documentation are examples only.
- **Anthropic** — Claude and Claude Code are trademarks of Anthropic, PBC.
This tool is an independent MCP server that connects to Claude Code via the standard MCP protocol. It does not contain or modify any Anthropic software.
The TradingView portion originates from [tradesdontlie/tradingview-mcp](https://github.com/tradesdontlie/tradingview-mcp), MIT licensed. The `mt5-bridge/` subsystem and `mt5` MCP server are additions in this fork.
## Disclaimer
This project is provided **for personal, educational, and research purposes only**.
**How this tool works:** This tool uses Chrome DevTools Protocol (CDP), the standard debugging interface built into Chromium-based applications. It does not reverse engineer any proprietary TradingView protocol, connect to TradingView's servers, or bypass any access controls. The debug port must be explicitly enabled by the user via a standard Chromium command-line flag (`--remote-debugging-port=9222`).
By using this software, you acknowledge and agree that:
1. **You are solely responsible** for ensuring your use of this tool complies with [TradingView's Terms of Use](https://www.tradingview.com/policies/) and all applicable laws.
2. TradingView's Terms of Use **restrict automated data collection, scraping, and non-display usage** of their platform and data. This tool uses Chrome DevTools Protocol to programmatically interact with the TradingView Desktop app, which may conflict with those terms.
3. **You assume all risk** associated with using this tool. The authors are not responsible for any account bans, suspensions, legal actions, or other consequences resulting from its use.
4. This tool **must not be used** for, including but not limited to:
- Redistributing, reselling, or commercially exploiting TradingView's market data
- Circumventing TradingView's access controls or subscription restrictions
- Performing automated trading or algorithmic decision-making using extracted data
- Violating the intellectual property rights of Pine Script indicator authors
- Connecting to TradingView's servers or infrastructure (all access is via the locally running Desktop app)
5. The streaming functionality monitors your locally running TradingView Desktop instance only. It does not connect to TradingView's servers or extract data from TradingView's infrastructure.
6. Market data accessed through this tool remains subject to exchange and data provider licensing terms. **Do not redistribute, store, or commercially exploit any data obtained through this tool.**
7. This tool accesses internal, undocumented TradingView application interfaces that may change or break at any time without notice.
**Use at your own risk.** If you are unsure whether your intended use complies with TradingView's terms, do not use this tool.
### Additionally, for the MT5 side
8. The MT5 bridge is **read-only**. It cannot open, modify or close a position, and contains no order-placement code. Nothing in this repository trades your account.
9. **You are solely responsible** for ensuring your use complies with your broker's terms and with the financial regulations that apply where you live. Retail leveraged forex and CFD trading is restricted or prohibited in some jurisdictions.
10. Account data, trade history and market data read through the bridge are **your own**, obtained from a terminal you are already logged into. They remain subject to your broker's terms — do not redistribute them.
11. **Nothing here is financial advice.** Summaries such as win rate, net P&L or news-blackout windows are arithmetic over your own data, not a recommendation to act. Historical performance does not predict future results.
12. If you extend this project toward automated execution, note that point 4 above already excludes automated trading on TradingView-extracted data. Deriving signals from your broker's own feed, and using TradingView only for visualization, avoids that conflict.
## License
MIT — see [LICENSE](LICENSE) for details.
The MIT license applies to the source code of this project only. It does not grant any rights to TradingView's software, data, trademarks, or intellectual property.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues