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.
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.
Contents — Quick 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.
HOSTdefaults to127.0.0.1so 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 wrongposition_usdtorleveragemis-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=truewith testnet keys until the behaviour is understood.auto_mode: falseso nothing acts unattended.- One account
enabled: true, the restfalse. - Smallest
position_usdtand lowestleveragethe exchange accepts. - Confirm
position_usdt x leverageis 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.
HOSTdefaults to127.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 setHOST=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.jsonare gitignored. Keep it that way.
License
MIT — see LICENSE.
Recommended Servers
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.
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.
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.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
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.
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.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
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.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.