Vale Monitor

Vale Monitor

MCP server for a self-hosted Bybit dashboard providing read-only access to candles, prices, positions, balances, trading reports, and CRUD for alerts and strategies, enabling Claude Code to analyze markets and monitor accounts.

Category
Visit Server

README

Vale Monitor

A self-hosted dashboard for watching Bybit trading accounts. It aggregates positions, balances and realised P&L across several accounts, reads candles, watches price alerts, stores strategy definitions, and exposes all of it to AI agents over MCP.

The dashboard is read-only against the exchange — it never places, modifies or cancels orders. Use read-only API keys.

This repository also contains backend-setup/, a separate Python trading bot that does place real leveraged orders. It is an independent system with its own setup, config and credentials — nothing in the dashboard imports it. Its setup and every config key are documented below.

  • Local SQLite — no database to provision. First boot creates the file.
  • Multi-account — aggregates any number of Bybit accounts.
  • Candles and prices — work with no exchange keys at all.
  • Alerts — price thresholds, evaluated on a background poll.
  • Strategies — stored definitions with free-form JSON rules.
  • AI analysis — optional, provider-agnostic (Claude or OpenAI).
  • MCP server — lets Claude Code read candles and manage alerts/strategies.

ContentsQuick start · Using the dashboard · Configuration · Claude Code / MCP · AI layer · Trading backend · Scripts · Architecture · Security


Quick start

Requires Node.js 20+ (tested on 24).

git clone <your-fork-url> vale-monitor
cd vale-monitor
npm install
npm run dev

Open http://127.0.0.1:5000. There is no configuration step and no login — the SQLite database is created at ./data/vale.db on first boot.

Copy .env.example to .env when you want to connect an exchange account or enable the AI layer. Nothing in it is required.

Verify a running instance end to end:

npm run smoke

There is no authentication. Anything that can reach the port can read your exchange balances and add or delete accounts. HOST defaults to 127.0.0.1 so the server is reachable only from your own machine — see Security notes before changing that.


Using the dashboard

The app is a single page with five tabs:

Tab What it does
Positions (default) Open positions across every configured account, with live P&L, and a TradingView chart
Dashboard Weekly highlights, trading reports by timeframe, and a per-account balance card (equity, 7-day and week-to-date performance, per-symbol breakdown, 1–5% position sizing)
Accounts Add, test and remove exchange accounts
Streaming Live wallet balances pushed over the WebSocket
Calculator Compound growth projection across multiple accounts

Positions, Dashboard and Streaming stay empty until you connect an account — they read credentialed exchange endpoints. Candles, prices and market hours work immediately.

Alerts and strategies are API- and MCP-first: they have full REST and MCP coverage but no dedicated tab yet. Drive them from Claude Code (below), or directly over HTTP — see docs/API.md.


Configuration

Every setting lives in .env; see .env.example for the annotated list. Nothing is required — the app runs with no .env at all.

Variable Default Purpose
PORT / HOST 5000 / 127.0.0.1 Only change HOST behind an authenticating proxy
DATABASE_PATH ./data/vale.db Where the SQLite file lives
ALERT_POLL_MS 30000 How often active alerts are re-checked
BYBIT_API_KEY / BYBIT_API_SECRET Single-account fallback; prefer the Accounts tab

Connecting exchange accounts

Candles, prices and market hours need no credentials. Positions, balances and realised P&L do.

Add accounts in the Accounts tab so you can track several at once. Credentials are stored in the local database and are never returned by the API — account responses only report whether credentials are present. The BYBIT_API_KEY environment pair is a single-account fallback, used only when no accounts are configured.


Using it from Claude Code

The MCP server exposes 22 tools — candles, prices, positions, balances, trading reports, and full CRUD for alerts and strategies. No credential is needed.

1. Point Claude Code at it:

cp .mcp.json.example .mcp.json
{
  "mcpServers": {
    "vale-monitor": {
      "command": "npx",
      "args": ["tsx", "server/mcp/index.ts"],
      "env": { "VALE_API_URL": "http://127.0.0.1:5000" }
    }
  }
}

2. Start the app (npm run dev) — the MCP server talks to it over HTTP, so it must be running. Then ask Claude Code things like:

Read the last 200 hourly BTCUSDT candles and tell me where support is sitting.

Set an alert for ETHUSDT below 3000, and list what's already active.

Save a strategy for SOLUSDT on the 4h with these rules, then review it.

Available tools: server_status, get_candles, get_price, get_market_hours, get_positions, get_account_balances, get_trading_report, get_weekly_highlights, list_accounts, list_alerts, create_alert, cancel_alert, delete_alert, list_strategies, get_strategy, create_strategy, update_strategy, delete_strategy, ai_config, analyze_market, analyze_performance, review_strategy.

The HTTP API is equally open — no headers required:

curl "http://127.0.0.1:5000/api/market/candles?symbol=BTCUSDT&interval=60&limit=5"

The AI layer

Optional. Without a provider key, the AI endpoints return 503 and everything else works normally.

Three analyses are available — market commentary over a candle window, performance comparison across accounts, and strategy review — via POST /api/ai/market, /api/ai/performance and /api/ai/strategies/:id/review, and via the matching MCP tools.

Switching models

Two environment variables:

AI_PROVIDER=anthropic        # anthropic | openai
AI_MODEL=claude-opus-5       # any id in the registry
AI_EFFORT=medium             # low | medium | high | xhigh | max
ANTHROPIC_API_KEY=sk-ant-...

Registered by default:

Model Provider Context $/Mtok in $/Mtok out
claude-opus-5 (default) Anthropic 1M 5 25
claude-sonnet-5 Anthropic 1M 3 15
claude-haiku-4-5 Anthropic 200K 1 5
claude-opus-4-8 Anthropic 1M 5 25
gpt-5.1, gpt-5, gpt-5-mini OpenAI 400K 1.25 / 0.25 10 / 2

GET /api/ai/config returns the registry with an available flag per model based on which keys are set. Each response reports the model that answered, the token counts and an estimated cost.

The OpenAI ids and pricing are best-effort and move quickly — check https://platform.openai.com/docs/models before relying on them.

Adding a model or provider

Nothing outside server/ai/ hardcodes a model id.

A new model on an existing provider — add an entry to MODELS in server/ai/config.ts:

"claude-sonnet-4-6": {
  id: "claude-sonnet-4-6",
  provider: "anthropic",
  label: "Claude Sonnet 4.6",
  contextWindow: 1_000_000,
  maxOutputTokens: 128_000,
  pricing: { inputPerMTok: 3, outputPerMTok: 15 },
  supportsEffort: true,
},

Then set AI_MODEL=claude-sonnet-4-6.

A new provider — add its name to ProviderName, implement the Provider interface (a single complete() method) under server/ai/providers/, and wire it into the switch in createProvider. The switch is exhaustively typed, so TypeScript will point at anything you miss.

Note the request interface deliberately has no temperature: current Claude models reject temperature/top_p/top_k with a 400. Reasoning depth is expressed as effort and each provider maps it onto its own knob.

Prompts live in server/ai/analyze.ts — one system prompt plus one builder per analysis.


The trading backend (backend-setup/)

A separate Python system that runs a signal strategy across several Bybit accounts, manages take-profit ladders and trailing stops, and reports over Telegram.

WARNING: this one places real orders

Everything above is read-only. backend-setup/ opens and closes leveraged positions with real money and needs API keys with trade permission. A wrong position_usdt or leverage mis-sizes trades on every enabled account at once. Run on testnet first, and enable live accounts one at a time.

It has no automated tests. Nothing in the dashboard imports it, and the two share no configuration.

Setup

Requires Python 3.10+ (developed on 3.12).

cd backend-setup
python -m venv .venv
.venv\Scripts\activate        # Windows
source .venv/bin/activate      # macOS / Linux

pip install -r requirements.txt

cp .env.example .env
cp accounts_config.example.json accounts_config.json
cp trading_config.example.json  trading_config.json

All three copies are gitignored. Fill in .env with testnet keys and leave USE_TESTNET=true. Then check credentials before anything trades:

python account_balance_report.py

Run every script from inside backend-setup/. They open their config by relative path, so launching from the repository root fails to find it.

Configuration

Three files, split deliberately: credentials only in .env, account identity and sizing in accounts_config.json, strategy behaviour in trading_config.json.

accounts_config.json - who trades, and how big

{
  "accounts": [
    {
      "name": "testnet",
      "api_key_env": "BYBIT_TESTNET_API_KEY",
      "api_secret_env": "BYBIT_TESTNET_API_SECRET",
      "enabled": true,
      "position_usdt": 5,
      "leverage": 10
    }
  ],
  "trading_symbol": "BTCUSDT",
  "parallel_execution": true
}
Field Meaning
api_key_env / api_secret_env Names of .env variables, never the keys themselves
enabled false takes the account out of every trade
position_usdt Margin committed per trade, in USDT
leverage Leverage applied to that margin
parallel_execution Submit to all accounts at once rather than in sequence

How size is computed - qty = (position_usdt * leverage) / price. So position_usdt is the margin and the notional is position_usdt x leverage: 10 at 40x is a 400 USDT position, not 10. Raising leverage multiplies exposure on every enabled account simultaneously.

If accounts_config.json is absent, trade_manager.py falls back to single-account mode using the BYBIT_API_KEY pair from .env.

trading_config.json - how positions are managed

Key Meaning
auto_mode false keeps a human in the loop; true lets it act unattended
take_profit_mode simple or strategic, selecting which ladder applies
simple_tp_levels / strategic_tp_levels Ladders of {profit_pct, size_pct} - close size_pct of the position at profit_pct ROI
trailing_stop.target_roi_pct ROI at which the trailing stop activates
trailing_stop.callback_rate How far price may retrace before it fires
trailing_stop.check_interval_minutes Poll interval for the trailing logic
signal_strength_threshold Minimum signal score to act on
trading_filters.min_signal_strength Weak / Moderate / Strong gate
trading_filters.allow_counter_trend Permit entries against the higher-timeframe trend
enforce_highest_timeframe_trend Require agreement from the top timeframe
skip_signals_for_open_positions Ignore new signals while a position is open (no stacking)
trading_pairs Symbols the strategy watches
symbol_config.<SYMBOL> Per-symbol leverage and position_usdt override

size_pct values are portions of the position and need not total 100 - whatever is left rides to the stop.

The backend's AI model

s1.py can ask an LLM to comment on a signal before acting. Optional: with no key set, that step is skipped and the strategy still runs. The model is not hardcoded.

Setting Effect
AI_MODEL Model id to use (MODEL is accepted as an alias)
(neither set) Falls back to gpt-5-mini
OPENROUTER_API_KEY set Routed via https://openrouter.ai/api/v1
OPENROUTER_API_KEY unset Goes to OpenAI using OPENAI_API_KEY
AI_MODEL=gpt-5-mini                    # OpenAI - bare ids
OPENAI_API_KEY=sk-...

AI_MODEL=anthropic/claude-sonnet-5     # or OpenRouter - "vendor/model" ids
OPENROUTER_API_KEY=sk-or-...

An unknown id fails at call time, not startup. This is separate from the dashboard's AI layer above - each has its own AI_MODEL, so you can run different models in each.

Running it

Script Role
s1.py Multi-timeframe EMA-cross strategy; emits signals from Bybit WebSocket data
trade_manager.py Opens and manages positions - the main trading loop
multi_account_trader.py Fans one decision out to every enabled account, sizing each
tpsl.py Take-profit / stop-loss monitor and trailing stops
martingale_manager.py Martingale ladder state
tele.py Telegram bot - pushes signals, accepts commands
trading_watchdog.py Supervises the other processes and restarts them
account_balance_report.py Balance / PnL / position-sizing report
smi_indicator.py Stochastic Momentum Index
time_sync.py Keeps local time aligned with the exchange
cd backend-setup
python s1.py                      # strategy / signals
python tpsl.py                    # TP/SL monitor
python tele.py                    # Telegram bot
python trading_watchdog.py        # supervisor
python account_balance_report.py  # one-off report

Read the top of a script first - several assume others are already running.

Before enabling a live account

  • USE_TESTNET=true with testnet keys until the behaviour is understood.
  • auto_mode: false so nothing acts unattended.
  • One account enabled: true, the rest false.
  • Smallest position_usdt and lowest leverage the exchange accepts.
  • Confirm position_usdt x leverage is the notional you actually intend.
  • Watch a full open -> TP -> close cycle before adding a second account.

What it writes (all gitignored)

accounts_config.json, trading_config.json, .env, plus runtime state (all_trade_signals.json, pending_signals.json, session_state.json, tpsl.json, martingale_log.json, trading_decision_history.json, stream_data.json, pnl_history.json), the logs, and backups/. Deleting the state files resets it; the bot recreates what it needs - but not while running.

Deeper reference, including per-script detail: backend-setup/README.md.


Scripts

Command What it does
npm run dev API + Vite dev server on one port
npm run build Client to dist/public, server bundle to dist/index.js
npm start Run the production build
npm run check TypeScript, no emit — the project's only static gate
npm run smoke End-to-end HTTP check against a running server
npm run mcp Run the MCP server directly (normally launched by the client)
npm run db:push Push shared/schema.ts with drizzle-kit

There is no test runner or linter configured; npm run check and npm run smoke are the checks.


Architecture

One Express process serves both the API and the client. In development Vite runs as middleware inside it, so there is a single port and no separate dev server; in production the built assets are served statically. Vite owns the catch-all route and is registered last, so new routes must be added inside registerRoutes().

server/
  index.ts        Bootstrap: schema init, middleware, listen
  routes.ts       All HTTP routes
  storage.ts      Every database query
  db.ts           SQLite connection and schema bootstrap
  trading.ts      Multi-account aggregation and analytics
  bybit-api.ts    Authenticated exchange reads
  market.ts       Public market data (candles, prices)
  market-hours.ts Exchange open/closed calculation
  alerts.ts       Background alert evaluation
  websocket.ts    Session-authenticated /ws stream
  vite.ts         Dev middleware / static serving, logger
  ai/             Model registry, providers, analysis prompts
  mcp/            MCP server for agents
shared/schema.ts  Drizzle tables, Zod schemas, shared types
client/src/       React + Vite front end
scripts/          smoke test CLI
docs/API.md       HTTP and WebSocket reference

backend-setup/    Separate Python trading bot — places real orders, own README

Three tables: accounts, alerts, strategies. There are no users.

Two conventions run through the analytics:

  • ROI is margin-based, not notional. A trade's committed capital is cumEntryValue / leverage, so a 10x position reports return on the margin actually put up.
  • Weeks run Monday 00:00:00 UTC to Sunday 23:59:59.999 UTC, independent of the server's timezone.

Ratios are null rather than Infinity when there are no losses to divide by.

No authentication

Every route is open, and the WebSocket at /ws accepts any connection. There are no users, sessions, passwords or tokens in the codebase.

This is deliberate for a single-user tool bound to loopback. It also means the only thing standing between the internet and your exchange credentials is the HOST binding — see below.

Full endpoint and WebSocket reference: docs/API.md.


Security notes

  • There is no authentication. Any process or person who can reach the port can read your balances and positions, and add or delete exchange accounts.
  • HOST defaults to 127.0.0.1, which is the control that makes the above acceptable — the server is reachable only from your own machine. The app logs a warning if you bind it anywhere else. Before exposing it, put a reverse proxy with TLS and access control in front; do not simply set HOST=0.0.0.0.
  • Use read-only exchange API keys. Nothing here needs trade permissions, and a leaked read-only key cannot move funds.
  • Exchange credentials are stored unencrypted in the local SQLite file. Protect the file; it is as sensitive as the keys themselves.
  • .env, data/ and .mcp.json are gitignored. Keep it that way.

License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured