tradingview-mcp 26.2.0__tar.gz → 26.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/PKG-INFO +10 -3
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/README.md +9 -2
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/pyproject.toml +1 -1
- tradingview_mcp-26.3.0/src/tradingview_mcp/constants.py +196 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/docs_data.py +41 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/resources.py +63 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/server.py +249 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/tools/__init__.py +0 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/tools/reference.py +70 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/tools/screener.py +87 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/tools/search.py +326 -0
- tradingview_mcp-26.3.0/src/tradingview_mcp/tools/technical.py +113 -0
- tradingview_mcp-26.2.0/src/tradingview_mcp/constants.py +0 -427
- tradingview_mcp-26.2.0/src/tradingview_mcp/server.py +0 -1615
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/.gitignore +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/LICENSE +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/__init__.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/column.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/__init__.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/column_display_names.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/__init__.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/ai_quick_reference.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/common_fields.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/fields_by_market.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/screener_code_examples.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/extracted/stock_screener_presets.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/markets.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/bond.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/bonds.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/cfd.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/coin.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/crypto.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/economics2.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/forex.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/futures.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/ireland.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/options.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/metainfo/stocks.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/main_screeners.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/markets.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/stocks.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/data/screeners/stocks_failed.json +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/models.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/query.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/scanner.py +0 -0
- {tradingview_mcp-26.2.0 → tradingview_mcp-26.3.0}/src/tradingview_mcp/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tradingview-mcp
|
|
3
|
-
Version: 26.
|
|
3
|
+
Version: 26.3.0
|
|
4
4
|
Summary: A comprehensive MCP server for TradingView market screening with integrated screener functionality
|
|
5
5
|
Project-URL: Homepage, https://github.com/k73a/tradingview-mcp
|
|
6
6
|
Project-URL: Documentation, https://github.com/k73a/tradingview-mcp#readme
|
|
@@ -43,7 +43,6 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
MCP server for TradingView market screening. Supports stocks, crypto, forex, futures, bonds across 76+ markets.
|
|
44
44
|
|
|
45
45
|
## Quick Start
|
|
46
|
-
|
|
47
46
|
```json
|
|
48
47
|
{
|
|
49
48
|
"mcpServers": {
|
|
@@ -55,6 +54,12 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
|
|
|
55
54
|
}
|
|
56
55
|
```
|
|
57
56
|
|
|
57
|
+
## Smart Features
|
|
58
|
+
|
|
59
|
+
- **Auto-Market Detection**: If you search for `EURUSD` or `BTCUSDT`, the server automatically checks Forex and Crypto markets if not found in stocks.
|
|
60
|
+
- **Strict Exchange Support**: Use `EXCHANGE:SYMBOL` format (e.g., `BINANCE:BTCUSDT`, `FX:EURUSD`) to target specific markets.
|
|
61
|
+
- **Smart Filters**: You can use human-readable names in filters (e.g., `"Relative Strength Index (14)"` instead of `"RSI"`).
|
|
62
|
+
|
|
58
63
|
## Main Tools
|
|
59
64
|
|
|
60
65
|
| Tool | Description |
|
|
@@ -87,7 +92,9 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
|
|
|
87
92
|
| `list_fields_for_market` | List fields for a market |
|
|
88
93
|
| `get_screener_preset` | Get predefined screener presets |
|
|
89
94
|
|
|
90
|
-
## Resources
|
|
95
|
+
## Reference Resources
|
|
96
|
+
|
|
97
|
+
These resources provide raw lists of available markets and fields for reference.
|
|
91
98
|
|
|
92
99
|
| Resource | Description |
|
|
93
100
|
|----------|-------------|
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
MCP server for TradingView market screening. Supports stocks, crypto, forex, futures, bonds across 76+ markets.
|
|
8
8
|
|
|
9
9
|
## Quick Start
|
|
10
|
-
|
|
11
10
|
```json
|
|
12
11
|
{
|
|
13
12
|
"mcpServers": {
|
|
@@ -19,6 +18,12 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
|
|
|
19
18
|
}
|
|
20
19
|
```
|
|
21
20
|
|
|
21
|
+
## Smart Features
|
|
22
|
+
|
|
23
|
+
- **Auto-Market Detection**: If you search for `EURUSD` or `BTCUSDT`, the server automatically checks Forex and Crypto markets if not found in stocks.
|
|
24
|
+
- **Strict Exchange Support**: Use `EXCHANGE:SYMBOL` format (e.g., `BINANCE:BTCUSDT`, `FX:EURUSD`) to target specific markets.
|
|
25
|
+
- **Smart Filters**: You can use human-readable names in filters (e.g., `"Relative Strength Index (14)"` instead of `"RSI"`).
|
|
26
|
+
|
|
22
27
|
## Main Tools
|
|
23
28
|
|
|
24
29
|
| Tool | Description |
|
|
@@ -51,7 +56,9 @@ MCP server for TradingView market screening. Supports stocks, crypto, forex, fut
|
|
|
51
56
|
| `list_fields_for_market` | List fields for a market |
|
|
52
57
|
| `get_screener_preset` | Get predefined screener presets |
|
|
53
58
|
|
|
54
|
-
## Resources
|
|
59
|
+
## Reference Resources
|
|
60
|
+
|
|
61
|
+
These resources provide raw lists of available markets and fields for reference.
|
|
55
62
|
|
|
56
63
|
| Resource | Description |
|
|
57
64
|
|----------|-------------|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for TradingView Screener integration.
|
|
3
|
+
|
|
4
|
+
Contains API URLs, headers, supported markets, and column definitions.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# API Configuration
|
|
12
|
+
URL = "https://scanner.tradingview.com/{market}/scan"
|
|
13
|
+
|
|
14
|
+
HEADERS = {
|
|
15
|
+
"authority": "scanner.tradingview.com",
|
|
16
|
+
"sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"',
|
|
17
|
+
"accept": "text/plain, */*; q=0.01",
|
|
18
|
+
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
19
|
+
"sec-ch-ua-mobile": "?0",
|
|
20
|
+
"user-agent": (
|
|
21
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
22
|
+
"(KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
|
|
23
|
+
),
|
|
24
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
25
|
+
"origin": "https://www.tradingview.com",
|
|
26
|
+
"sec-fetch-site": "same-site",
|
|
27
|
+
"sec-fetch-mode": "cors",
|
|
28
|
+
"sec-fetch-dest": "empty",
|
|
29
|
+
"referer": "https://www.tradingview.com/",
|
|
30
|
+
"accept-language": "en-US,en;q=0.9",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
DEFAULT_RANGE = [0, 50]
|
|
34
|
+
|
|
35
|
+
# =============================================================================
|
|
36
|
+
# Load Data from JSON (Dynamic)
|
|
37
|
+
# =============================================================================
|
|
38
|
+
|
|
39
|
+
_DATA_DIR = Path(__file__).parent / "data"
|
|
40
|
+
|
|
41
|
+
def _load_markets() -> set[str]:
|
|
42
|
+
"""Load supported markets from JSON."""
|
|
43
|
+
try:
|
|
44
|
+
with open(_DATA_DIR / "markets.json", "r", encoding="utf-8") as f:
|
|
45
|
+
data = json.load(f)
|
|
46
|
+
return set(data.get("countries", []) + data.get("other", []))
|
|
47
|
+
except Exception:
|
|
48
|
+
# Fallback if file not found (dev/test env)
|
|
49
|
+
return {"america", "crypto", "forex"}
|
|
50
|
+
|
|
51
|
+
def _load_columns() -> dict[str, str]:
|
|
52
|
+
"""
|
|
53
|
+
Load column definitions from JSON.
|
|
54
|
+
Returns: Dict[Display Name, Field Name] (Reverse of JSON)
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
with open(_DATA_DIR / "column_display_names.json", "r", encoding="utf-8") as f:
|
|
58
|
+
data = json.load(f)
|
|
59
|
+
fields = data.get("fields", {})
|
|
60
|
+
# Reverse mapping: Display Name -> Field Name
|
|
61
|
+
# e.g. "Relative Strength Index (14)" -> "RSI"
|
|
62
|
+
return {v: k for k, v in fields.items()}
|
|
63
|
+
except Exception:
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
# Public Constants
|
|
67
|
+
MARKETS = _load_markets()
|
|
68
|
+
COLUMNS = _load_columns()
|
|
69
|
+
|
|
70
|
+
# Exchange to Screener Mapping (Expanded)
|
|
71
|
+
EXCHANGE_SCREENER = {
|
|
72
|
+
# Crypto
|
|
73
|
+
"all": "crypto",
|
|
74
|
+
"binance": "crypto",
|
|
75
|
+
"coinbase": "crypto",
|
|
76
|
+
"kraken": "crypto",
|
|
77
|
+
"kucoin": "crypto",
|
|
78
|
+
"bitfinex": "crypto",
|
|
79
|
+
"bitstamp": "crypto",
|
|
80
|
+
"okx": "crypto",
|
|
81
|
+
"bybit": "crypto",
|
|
82
|
+
"gateio": "crypto",
|
|
83
|
+
"huobi": "crypto",
|
|
84
|
+
"gemini": "crypto",
|
|
85
|
+
"poloniex": "crypto",
|
|
86
|
+
"bittrex": "crypto",
|
|
87
|
+
"bitmex": "crypto",
|
|
88
|
+
"deribit": "crypto",
|
|
89
|
+
"mexc": "crypto",
|
|
90
|
+
"bingx": "crypto",
|
|
91
|
+
"phemex": "crypto",
|
|
92
|
+
"bitget": "crypto",
|
|
93
|
+
# Forex
|
|
94
|
+
"fx": "forex",
|
|
95
|
+
"fx_idc": "forex",
|
|
96
|
+
"oanda": "forex",
|
|
97
|
+
"forex": "forex",
|
|
98
|
+
"ice": "forex", # Often forex/futures
|
|
99
|
+
"saxo": "forex",
|
|
100
|
+
"cityindex": "forex",
|
|
101
|
+
# Futures
|
|
102
|
+
"cme": "futures",
|
|
103
|
+
"cbot": "futures",
|
|
104
|
+
"comex": "futures",
|
|
105
|
+
"nymex": "futures",
|
|
106
|
+
"eurex": "futures",
|
|
107
|
+
# US Stocks
|
|
108
|
+
"nasdaq": "america",
|
|
109
|
+
"nyse": "america",
|
|
110
|
+
"amex": "america",
|
|
111
|
+
"arca": "america",
|
|
112
|
+
"otc": "america",
|
|
113
|
+
# Global Stocks
|
|
114
|
+
"lse": "uk", # London
|
|
115
|
+
"tsx": "canada", # Toronto
|
|
116
|
+
"asx": "australia",
|
|
117
|
+
"sse": "china", # Shanghai
|
|
118
|
+
"szse": "china", # Shenzhen
|
|
119
|
+
"hkex": "hongkong",
|
|
120
|
+
"hk": "hongkong",
|
|
121
|
+
"tse": "japan", # Tokyo
|
|
122
|
+
"tyo": "japan",
|
|
123
|
+
"ebs": "switzerland", # Swiss SIX
|
|
124
|
+
"six": "switzerland",
|
|
125
|
+
"xetra": "germany",
|
|
126
|
+
"fwb": "germany",
|
|
127
|
+
"euronext": "france", # Generic Euronext
|
|
128
|
+
"nse": "india",
|
|
129
|
+
"bse": "india",
|
|
130
|
+
"krx": "korea",
|
|
131
|
+
"kosdaq": "korea",
|
|
132
|
+
"b3": "brazil",
|
|
133
|
+
"moex": "russia",
|
|
134
|
+
"sgx": "singapore",
|
|
135
|
+
"twse": "taiwan",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Allowed Timeframes
|
|
139
|
+
ALLOWED_TIMEFRAMES = {"1m", "5m", "15m", "30m", "1h", "2h", "4h", "1D", "1W", "1M"}
|
|
140
|
+
|
|
141
|
+
# Timeframe to TradingView Resolution
|
|
142
|
+
TIMEFRAME_MAP = {
|
|
143
|
+
"1m": "1",
|
|
144
|
+
"5m": "5",
|
|
145
|
+
"15m": "15",
|
|
146
|
+
"30m": "30",
|
|
147
|
+
"1h": "60",
|
|
148
|
+
"2h": "120",
|
|
149
|
+
"4h": "240",
|
|
150
|
+
"1D": "1D",
|
|
151
|
+
"1W": "1W",
|
|
152
|
+
"1M": "1M",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Default columns for different query types
|
|
156
|
+
# Always include 'description' for full name (e.g., "Apple Inc." instead of just "AAPL")
|
|
157
|
+
DEFAULT_COLUMNS = ["name", "description", "close", "change", "volume", "market_cap_basic"]
|
|
158
|
+
|
|
159
|
+
PREMARKET_COLUMNS = ["name", "description", "close", "volume", "premarket_change", "premarket_change_abs", "premarket_volume"]
|
|
160
|
+
|
|
161
|
+
POSTMARKET_COLUMNS = ["name", "description", "close", "volume", "postmarket_change", "postmarket_change_abs", "postmarket_volume"]
|
|
162
|
+
|
|
163
|
+
CRYPTO_COLUMNS = ["name", "description", "close", "change", "volume", "market_cap_basic", "24h_vol|5"]
|
|
164
|
+
|
|
165
|
+
TECHNICAL_COLUMNS = [
|
|
166
|
+
"name",
|
|
167
|
+
"description",
|
|
168
|
+
"close",
|
|
169
|
+
"volume",
|
|
170
|
+
"change",
|
|
171
|
+
"RSI",
|
|
172
|
+
"MACD.macd",
|
|
173
|
+
"MACD.signal",
|
|
174
|
+
"BB.upper",
|
|
175
|
+
"BB.lower",
|
|
176
|
+
"SMA20",
|
|
177
|
+
"EMA50",
|
|
178
|
+
"EMA200",
|
|
179
|
+
"ADX",
|
|
180
|
+
"Stoch.K",
|
|
181
|
+
"Stoch.D",
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
def get_column_name(name: str) -> str:
|
|
185
|
+
"""Get the API column name from a human-readable name or return as-is."""
|
|
186
|
+
# Try exact match first
|
|
187
|
+
if name in COLUMNS:
|
|
188
|
+
return COLUMNS[name]
|
|
189
|
+
|
|
190
|
+
# Try case-insensitive keys
|
|
191
|
+
name_lower = name.lower()
|
|
192
|
+
for k, v in COLUMNS.items():
|
|
193
|
+
if k.lower() == name_lower:
|
|
194
|
+
return v
|
|
195
|
+
|
|
196
|
+
return name
|
|
@@ -165,6 +165,47 @@ def get_fields_by_market(market: str | None = None) -> dict[str, Any]:
|
|
|
165
165
|
return data
|
|
166
166
|
|
|
167
167
|
|
|
168
|
+
def get_default_columns_for_market(market: str) -> list[str]:
|
|
169
|
+
"""Get smart default columns for a market to avoid 400 errors."""
|
|
170
|
+
# Base columns safe for all
|
|
171
|
+
cols = ["name", "description", "close", "change", "change_abs", "exchange", "type"]
|
|
172
|
+
|
|
173
|
+
# Check if market has specific fields in data
|
|
174
|
+
fields = get_fields_by_market(market)
|
|
175
|
+
if not fields:
|
|
176
|
+
# Fallback defaults if market data missing
|
|
177
|
+
if market == "crypto":
|
|
178
|
+
return cols + ["volume", "market_cap_basic", "24h_vol|5"]
|
|
179
|
+
if market == "forex":
|
|
180
|
+
return cols + ["bid", "ask"]
|
|
181
|
+
return cols + ["volume", "market_cap_basic", "price_earnings_ttm", "sector", "industry"]
|
|
182
|
+
|
|
183
|
+
# Extract available field names
|
|
184
|
+
valid_names = {f.get("name") for f in fields if f.get("name")}
|
|
185
|
+
|
|
186
|
+
# Add common extra columns if they exist in this market
|
|
187
|
+
extras = [
|
|
188
|
+
"open", "high", "low", "volume", "market_cap_basic",
|
|
189
|
+
"24h_vol|5", "bid", "ask", "price_earnings_ttm",
|
|
190
|
+
"earnings_per_share_basic_ttm", "dividend_yield_recent",
|
|
191
|
+
"sector", "industry"
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
for extra in extras:
|
|
195
|
+
if extra in valid_names:
|
|
196
|
+
cols.append(extra)
|
|
197
|
+
|
|
198
|
+
# Deduplicate while preserving order
|
|
199
|
+
seen = set()
|
|
200
|
+
unique_cols = []
|
|
201
|
+
for c in cols:
|
|
202
|
+
if c not in seen:
|
|
203
|
+
seen.add(c)
|
|
204
|
+
unique_cols.append(c)
|
|
205
|
+
|
|
206
|
+
return unique_cols
|
|
207
|
+
|
|
208
|
+
|
|
168
209
|
def paginate_data(
|
|
169
210
|
data: list | dict,
|
|
170
211
|
offset: int = 0,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from tradingview_mcp.docs_data import (
|
|
5
|
+
get_markets_data,
|
|
6
|
+
get_column_display_names,
|
|
7
|
+
get_screener_presets,
|
|
8
|
+
get_screener_markets,
|
|
9
|
+
get_stock_screeners,
|
|
10
|
+
get_stock_screeners_failed,
|
|
11
|
+
get_fields_by_market,
|
|
12
|
+
get_ai_quick_reference,
|
|
13
|
+
get_screener_code_examples,
|
|
14
|
+
)
|
|
15
|
+
from tradingview_mcp.constants import EXCHANGE_SCREENER
|
|
16
|
+
|
|
17
|
+
def list_markets() -> dict[str, Any]:
|
|
18
|
+
"""List all available markets."""
|
|
19
|
+
return get_markets_data()
|
|
20
|
+
|
|
21
|
+
def list_columns() -> dict[str, str]:
|
|
22
|
+
"""List all available columns."""
|
|
23
|
+
return get_column_display_names()
|
|
24
|
+
|
|
25
|
+
def list_crypto_exchanges() -> list[str]:
|
|
26
|
+
"""List all crypto exchanges."""
|
|
27
|
+
return [k for k, v in EXCHANGE_SCREENER.items() if v == "crypto"]
|
|
28
|
+
|
|
29
|
+
def list_scanner_presets() -> list[dict[str, Any]]:
|
|
30
|
+
"""List scanner presets."""
|
|
31
|
+
return get_screener_presets()
|
|
32
|
+
|
|
33
|
+
def docs_params() -> dict[str, Any]:
|
|
34
|
+
"""Documentation for parameters."""
|
|
35
|
+
return {"limit": "Max 500", "range": "[0, 100]"}
|
|
36
|
+
|
|
37
|
+
def docs_screeners() -> list[str]:
|
|
38
|
+
"""Documentation for screeners."""
|
|
39
|
+
return get_screener_markets()
|
|
40
|
+
|
|
41
|
+
def docs_stock_screeners() -> list[dict[str, Any]]:
|
|
42
|
+
"""Documentation for stock screeners."""
|
|
43
|
+
return get_stock_screeners()
|
|
44
|
+
|
|
45
|
+
def docs_stock_screeners_failed() -> list[dict[str, Any]]:
|
|
46
|
+
"""Documentation for failed stock screeners."""
|
|
47
|
+
return get_stock_screeners_failed()
|
|
48
|
+
|
|
49
|
+
def docs_fields() -> dict[str, Any]:
|
|
50
|
+
"""Documentation for fields."""
|
|
51
|
+
return get_fields_by_market("stocks") # Default to stocks
|
|
52
|
+
|
|
53
|
+
def docs_markets() -> dict[str, Any]:
|
|
54
|
+
"""Documentation for markets."""
|
|
55
|
+
return get_markets_data()
|
|
56
|
+
|
|
57
|
+
def docs_ai_reference() -> dict[str, Any]:
|
|
58
|
+
"""Documentation for AI reference."""
|
|
59
|
+
return get_ai_quick_reference()
|
|
60
|
+
|
|
61
|
+
def docs_code_examples() -> dict[str, Any]:
|
|
62
|
+
"""Documentation for code examples."""
|
|
63
|
+
return get_screener_code_examples()
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TradingView MCP Server - Main server implementation.
|
|
3
|
+
|
|
4
|
+
Provides comprehensive MCP tools and resources for TradingView market screening.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import traceback
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from functools import wraps
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from mcp.server.fastmcp import FastMCP
|
|
18
|
+
|
|
19
|
+
from tradingview_mcp.docs_data import suggest_fields_for_error
|
|
20
|
+
from tradingview_mcp.tools.search import search_symbols, get_symbol_info
|
|
21
|
+
from tradingview_mcp.tools.screener import (
|
|
22
|
+
screen_market,
|
|
23
|
+
get_top_gainers,
|
|
24
|
+
get_top_losers,
|
|
25
|
+
get_most_active
|
|
26
|
+
)
|
|
27
|
+
from tradingview_mcp.tools.technical import (
|
|
28
|
+
get_technical_analysis,
|
|
29
|
+
scan_rsi_extremes,
|
|
30
|
+
scan_bollinger_bands,
|
|
31
|
+
scan_macd_crossover
|
|
32
|
+
)
|
|
33
|
+
from tradingview_mcp.tools.reference import (
|
|
34
|
+
ai_get_reference,
|
|
35
|
+
search_available_fields,
|
|
36
|
+
get_code_example,
|
|
37
|
+
list_fields_for_market,
|
|
38
|
+
get_common_fields_summary,
|
|
39
|
+
lookup_single_field,
|
|
40
|
+
get_filter_example,
|
|
41
|
+
check_market,
|
|
42
|
+
get_help,
|
|
43
|
+
get_field_info,
|
|
44
|
+
get_screener_preset,
|
|
45
|
+
get_market_metainfo,
|
|
46
|
+
)
|
|
47
|
+
from tradingview_mcp.resources import (
|
|
48
|
+
list_markets,
|
|
49
|
+
list_columns,
|
|
50
|
+
list_crypto_exchanges,
|
|
51
|
+
list_scanner_presets,
|
|
52
|
+
docs_params,
|
|
53
|
+
docs_screeners,
|
|
54
|
+
docs_stock_screeners,
|
|
55
|
+
docs_stock_screeners_failed,
|
|
56
|
+
docs_fields,
|
|
57
|
+
docs_markets,
|
|
58
|
+
docs_ai_reference,
|
|
59
|
+
docs_code_examples,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Initialize MCP Server
|
|
63
|
+
mcp = FastMCP(
|
|
64
|
+
name="TradingView Screener MCP",
|
|
65
|
+
instructions="""TradingView Market Screener MCP
|
|
66
|
+
|
|
67
|
+
Quick start:
|
|
68
|
+
1. Call `get_help()` for usage
|
|
69
|
+
2. Call `ai_get_reference()` for markets and fields
|
|
70
|
+
|
|
71
|
+
Common tools:
|
|
72
|
+
- `screen_market(market, limit)`
|
|
73
|
+
- `get_top_gainers(market, limit)`
|
|
74
|
+
- `get_top_losers(market, limit)`
|
|
75
|
+
- `search_symbols(query, market)`
|
|
76
|
+
- `get_symbol_info(symbol)`
|
|
77
|
+
|
|
78
|
+
Notes:
|
|
79
|
+
- `limit` default 25, max 500
|
|
80
|
+
- Results include `description`
|
|
81
|
+
|
|
82
|
+
Resources:
|
|
83
|
+
- docs://ai-reference
|
|
84
|
+
- markets://list
|
|
85
|
+
- columns://list
|
|
86
|
+
""",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
DEBUG_MODE = os.getenv("TRADINGVIEW_MCP_DEBUG", "0").lower() in {"1", "true", "yes"}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _safe_json(value: Any) -> Any:
|
|
93
|
+
try:
|
|
94
|
+
return json.loads(json.dumps(value, default=str))
|
|
95
|
+
except Exception:
|
|
96
|
+
return str(value)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _debug_hint(exc: Exception, context: dict[str, Any] | None = None) -> dict[str, Any] | str | None:
|
|
100
|
+
"""Generate helpful hints based on error type."""
|
|
101
|
+
message = str(exc).lower()
|
|
102
|
+
|
|
103
|
+
# Extract market from context if available
|
|
104
|
+
market = None
|
|
105
|
+
if context:
|
|
106
|
+
market = context.get("kwargs", {}).get("market") or context.get("market")
|
|
107
|
+
|
|
108
|
+
if "market" in message and "invalid" in message:
|
|
109
|
+
return "Check the market name using the markets://list or docs://screeners resources."
|
|
110
|
+
|
|
111
|
+
if "column" in message or "unknown" in message or "field" in message:
|
|
112
|
+
# Return structured field suggestions
|
|
113
|
+
market_type = market or "stocks"
|
|
114
|
+
if market_type in ["crypto", "coin"]:
|
|
115
|
+
market_type = market_type
|
|
116
|
+
elif market_type in ["forex"]:
|
|
117
|
+
market_type = "forex"
|
|
118
|
+
else:
|
|
119
|
+
# Default to stocks for field suggestions
|
|
120
|
+
market_type = "stocks"
|
|
121
|
+
|
|
122
|
+
return suggest_fields_for_error(str(exc), market_type)
|
|
123
|
+
|
|
124
|
+
if "http" in message or "status" in message:
|
|
125
|
+
return "TradingView API may be rate-limiting or blocked. Retry with fewer requests."
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _build_error_response(tool_name: str, exc: Exception, context: dict[str, Any]) -> dict[str, Any]:
|
|
131
|
+
payload: dict[str, Any] = {
|
|
132
|
+
"ok": False,
|
|
133
|
+
"tool": tool_name,
|
|
134
|
+
"error": {"type": exc.__class__.__name__, "message": str(exc)},
|
|
135
|
+
"context": _safe_json(context),
|
|
136
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
hint = _debug_hint(exc, context)
|
|
140
|
+
if hint:
|
|
141
|
+
if isinstance(hint, dict):
|
|
142
|
+
payload["field_suggestions"] = hint
|
|
143
|
+
else:
|
|
144
|
+
payload["hint"] = hint
|
|
145
|
+
|
|
146
|
+
if DEBUG_MODE:
|
|
147
|
+
payload["trace"] = traceback.format_exc()
|
|
148
|
+
return payload
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def debug_tool(fn):
|
|
152
|
+
"""Decorator to return structured debug responses on failure."""
|
|
153
|
+
|
|
154
|
+
@wraps(fn)
|
|
155
|
+
def wrapper(*args, **kwargs):
|
|
156
|
+
try:
|
|
157
|
+
result = fn(*args, **kwargs)
|
|
158
|
+
if isinstance(result, dict) and "error" in result:
|
|
159
|
+
return _build_error_response(
|
|
160
|
+
fn.__name__,
|
|
161
|
+
Exception(str(result.get("error"))),
|
|
162
|
+
{"args": args, "kwargs": kwargs, "note": "error returned by tool"},
|
|
163
|
+
)
|
|
164
|
+
if (
|
|
165
|
+
isinstance(result, list)
|
|
166
|
+
and len(result) == 1
|
|
167
|
+
and isinstance(result[0], dict)
|
|
168
|
+
and "error" in result[0]
|
|
169
|
+
):
|
|
170
|
+
return _build_error_response(
|
|
171
|
+
fn.__name__,
|
|
172
|
+
Exception(str(result[0].get("error"))),
|
|
173
|
+
{"args": args, "kwargs": kwargs, "note": "error returned by tool"},
|
|
174
|
+
)
|
|
175
|
+
return result
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
context = {"args": args, "kwargs": kwargs}
|
|
178
|
+
return _build_error_response(fn.__name__, exc, context)
|
|
179
|
+
|
|
180
|
+
return wrapper
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
_original_mcp_tool = mcp.tool
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _debug_mcp_tool(*args, **kwargs):
|
|
187
|
+
"""Wrap FastMCP tool registration with debug handling."""
|
|
188
|
+
|
|
189
|
+
def decorator(fn):
|
|
190
|
+
return _original_mcp_tool(*args, **kwargs)(debug_tool(fn))
|
|
191
|
+
|
|
192
|
+
return decorator
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
mcp.tool = _debug_mcp_tool
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# =============================================================================
|
|
199
|
+
# Register Tools
|
|
200
|
+
# =============================================================================
|
|
201
|
+
|
|
202
|
+
mcp.tool()(search_symbols)
|
|
203
|
+
mcp.tool()(get_symbol_info)
|
|
204
|
+
mcp.tool()(screen_market)
|
|
205
|
+
mcp.tool()(get_top_gainers)
|
|
206
|
+
mcp.tool()(get_top_losers)
|
|
207
|
+
mcp.tool()(get_most_active)
|
|
208
|
+
|
|
209
|
+
mcp.tool()(get_technical_analysis)
|
|
210
|
+
mcp.tool()(scan_rsi_extremes)
|
|
211
|
+
mcp.tool()(scan_bollinger_bands)
|
|
212
|
+
mcp.tool()(scan_macd_crossover)
|
|
213
|
+
|
|
214
|
+
mcp.tool()(ai_get_reference)
|
|
215
|
+
mcp.tool()(search_available_fields)
|
|
216
|
+
mcp.tool()(get_code_example)
|
|
217
|
+
mcp.tool()(list_fields_for_market)
|
|
218
|
+
mcp.tool()(get_common_fields_summary)
|
|
219
|
+
mcp.tool()(lookup_single_field)
|
|
220
|
+
mcp.tool()(get_field_info)
|
|
221
|
+
mcp.tool()(get_filter_example)
|
|
222
|
+
mcp.tool()(check_market)
|
|
223
|
+
mcp.tool()(get_help)
|
|
224
|
+
mcp.tool()(get_screener_preset)
|
|
225
|
+
mcp.tool()(get_market_metainfo)
|
|
226
|
+
|
|
227
|
+
# =============================================================================
|
|
228
|
+
# Register Resources
|
|
229
|
+
# =============================================================================
|
|
230
|
+
|
|
231
|
+
mcp.resource("markets://list")(list_markets)
|
|
232
|
+
mcp.resource("columns://list")(list_columns)
|
|
233
|
+
mcp.resource("exchanges://crypto")(list_crypto_exchanges)
|
|
234
|
+
mcp.resource("scanners://presets")(list_scanner_presets)
|
|
235
|
+
mcp.resource("docs://params")(docs_params)
|
|
236
|
+
mcp.resource("docs://screeners")(docs_screeners)
|
|
237
|
+
mcp.resource("docs://stock-screeners")(docs_stock_screeners)
|
|
238
|
+
mcp.resource("docs://stock-screeners-failed")(docs_stock_screeners_failed)
|
|
239
|
+
mcp.resource("docs://fields")(docs_fields)
|
|
240
|
+
mcp.resource("docs://markets")(docs_markets)
|
|
241
|
+
mcp.resource("docs://ai-reference")(docs_ai_reference)
|
|
242
|
+
mcp.resource("docs://code-examples")(docs_code_examples)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def main():
|
|
246
|
+
mcp.run()
|
|
247
|
+
|
|
248
|
+
if __name__ == "__main__":
|
|
249
|
+
main()
|
|
File without changes
|