tradingview-mcp 26.3.1__tar.gz → 26.3.2__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.3.1 → tradingview_mcp-26.3.2}/PKG-INFO +1 -1
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/pyproject.toml +1 -1
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/tools/reference.py +13 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/tools/screener.py +4 -4
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/tools/search.py +82 -15
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/tools/technical.py +4 -4
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/utils.py +18 -9
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/.gitignore +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/LICENSE +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/README.md +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/__init__.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/column.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/constants.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/__init__.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/column_display_names.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/__init__.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/ai_quick_reference.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/common_fields.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/fields_by_market.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/screener_code_examples.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/stock_screener_presets.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/markets.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/bond.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/bonds.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/cfd.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/coin.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/crypto.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/economics2.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/forex.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/futures.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/ireland.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/options.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/stocks.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/screeners/main_screeners.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/screeners/markets.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/screeners/stocks.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/screeners/stocks_failed.json +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/docs_data.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/models.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/query.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/resources.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/scanner.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/server.py +0 -0
- {tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/tools/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tradingview-mcp
|
|
3
|
-
Version: 26.3.
|
|
3
|
+
Version: 26.3.2
|
|
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
|
|
@@ -49,8 +49,21 @@ def get_filter_example(type: str = "number") -> dict[str, Any]:
|
|
|
49
49
|
"""Get example filter format."""
|
|
50
50
|
return get_filter_format(type)
|
|
51
51
|
|
|
52
|
+
from tradingview_mcp.utils import sanitize_market
|
|
53
|
+
|
|
52
54
|
def check_market(market: str) -> dict[str, Any]:
|
|
53
55
|
"""Validate a market name."""
|
|
56
|
+
# Resolve alias first (e.g. 'tw' -> 'taiwan') for consistency with other tools
|
|
57
|
+
# We use strict=False because we want the default 'america' if fails? No, check_market should be explicit.
|
|
58
|
+
# sanitize_market returns default 'america' if invalid.
|
|
59
|
+
# We want to know if it's strictly valid or valid alias.
|
|
60
|
+
|
|
61
|
+
# Check if it's a known alias
|
|
62
|
+
clean = market.strip().lower()
|
|
63
|
+
from tradingview_mcp.utils import COUNTRY_ALIASES
|
|
64
|
+
if clean in COUNTRY_ALIASES:
|
|
65
|
+
return validate_market(COUNTRY_ALIASES[clean])
|
|
66
|
+
|
|
54
67
|
return validate_market(market)
|
|
55
68
|
|
|
56
69
|
def get_help() -> dict[str, Any]:
|
|
@@ -15,7 +15,7 @@ def screen_market(
|
|
|
15
15
|
filters: Optional[list[dict[str, Any]]] = None,
|
|
16
16
|
) -> dict[str, Any]:
|
|
17
17
|
"""Run a custom market screening query."""
|
|
18
|
-
market = sanitize_market(market)
|
|
18
|
+
market = sanitize_market(market, strict=True)
|
|
19
19
|
limit = max(1, min(limit, 500))
|
|
20
20
|
|
|
21
21
|
# Ensure description is always included
|
|
@@ -71,17 +71,17 @@ def screen_market(
|
|
|
71
71
|
|
|
72
72
|
def get_top_gainers(market: str = "america", limit: int = 25) -> dict[str, Any]:
|
|
73
73
|
"""Get top gainers for a market."""
|
|
74
|
-
market = sanitize_market(market)
|
|
74
|
+
market = sanitize_market(market, strict=True)
|
|
75
75
|
return screen_market(market, sort_by="change", ascending=False, limit=limit)
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def get_top_losers(market: str = "america", limit: int = 25) -> dict[str, Any]:
|
|
79
79
|
"""Get top losers for a market."""
|
|
80
|
-
market = sanitize_market(market)
|
|
80
|
+
market = sanitize_market(market, strict=True)
|
|
81
81
|
return screen_market(market, sort_by="change", ascending=True, limit=limit)
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def get_most_active(market: str = "america", limit: int = 25) -> dict[str, Any]:
|
|
85
85
|
"""Get most active symbols by volume."""
|
|
86
|
-
market = sanitize_market(market)
|
|
86
|
+
market = sanitize_market(market, strict=True)
|
|
87
87
|
return screen_market(market, sort_by="volume", ascending=False, limit=limit)
|
|
@@ -55,13 +55,92 @@ def search_symbols(
|
|
|
55
55
|
|
|
56
56
|
try:
|
|
57
57
|
# Standard search in requested market
|
|
58
|
-
|
|
58
|
+
# We start this async to allow parallel peeking
|
|
59
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
|
60
|
+
future_main = executor.submit(_execute_search, queries, market, limit, cols, sort_col)
|
|
61
|
+
|
|
62
|
+
# Smart Peek: Always check CFD/Forex/Crypto for EXACT matches or high relevance
|
|
63
|
+
# This solves the "Silver" problem: "Silver" exists in America (Stocks), but user might want "SILVER" (CFD)
|
|
64
|
+
# We don't want to wait for main search to fail (lazy fallback), we want to augment results.
|
|
65
|
+
peek_markets = ["cfd", "crypto", "forex"]
|
|
66
|
+
if market in peek_markets:
|
|
67
|
+
peek_markets.remove(market)
|
|
68
|
+
|
|
69
|
+
future_peeks = []
|
|
70
|
+
for pm in peek_markets:
|
|
71
|
+
# For peeking, we only care about high relevance, so limit is small
|
|
72
|
+
p_cols = get_default_columns_for_market(pm)
|
|
73
|
+
p_sort = "name" # Sort by name match
|
|
74
|
+
future_peeks.append(executor.submit(_execute_search, queries, pm, 5, p_cols, p_sort))
|
|
75
|
+
|
|
76
|
+
# Gather Main Results
|
|
77
|
+
results = future_main.result()
|
|
78
|
+
|
|
79
|
+
# Gather Peek Results
|
|
80
|
+
extra_matches = []
|
|
81
|
+
for f in future_peeks:
|
|
82
|
+
try:
|
|
83
|
+
res = f.result()
|
|
84
|
+
if res.get("results"):
|
|
85
|
+
extra_matches.extend(res["results"])
|
|
86
|
+
except:
|
|
87
|
+
pass
|
|
59
88
|
|
|
60
|
-
#
|
|
89
|
+
# Merge Logic
|
|
90
|
+
# 1. Start with Main Results
|
|
91
|
+
final_list = results.get("results", [])
|
|
92
|
+
|
|
93
|
+
# 2. Inject Exact Matches from Peeks at the TOP
|
|
94
|
+
# (e.g. if query="Silver" and we found "SILVER" in CFD, put it first)
|
|
95
|
+
high_priority = []
|
|
96
|
+
low_priority = []
|
|
97
|
+
|
|
98
|
+
query_upper = query.upper()
|
|
99
|
+
|
|
100
|
+
for m in extra_matches:
|
|
101
|
+
# Check for exact ticker/name match
|
|
102
|
+
n = m.get("name", "").upper()
|
|
103
|
+
tick = m.get("ticker", "").split(":")[-1].upper()
|
|
104
|
+
|
|
105
|
+
if n == query_upper or tick == query_upper or f"{query_upper}USD" in n:
|
|
106
|
+
high_priority.append(m)
|
|
107
|
+
else:
|
|
108
|
+
low_priority.append(m)
|
|
109
|
+
|
|
110
|
+
# specific uniqueness check
|
|
111
|
+
seen = {f"{r.get('exchange')}:{r.get('name')}" for r in final_list}
|
|
112
|
+
|
|
113
|
+
merged = []
|
|
114
|
+
# Add High Priority Peeks (if new)
|
|
115
|
+
for r in high_priority:
|
|
116
|
+
k = f"{r.get('exchange')}:{r.get('name')}"
|
|
117
|
+
if k not in seen:
|
|
118
|
+
merged.append(r)
|
|
119
|
+
seen.add(k)
|
|
120
|
+
|
|
121
|
+
# Add Main Results
|
|
122
|
+
merged.extend(final_list)
|
|
123
|
+
|
|
124
|
+
# Add Low Priority Peeks (only if we have space or main list is empty)
|
|
125
|
+
# Actually, let's append them if main list is small, or specialized matching
|
|
126
|
+
if not final_list:
|
|
127
|
+
for r in low_priority:
|
|
128
|
+
k = f"{r.get('exchange')}:{r.get('name')}"
|
|
129
|
+
if k not in seen:
|
|
130
|
+
merged.append(r)
|
|
131
|
+
seen.add(k)
|
|
132
|
+
|
|
133
|
+
# Update results
|
|
134
|
+
results["results"] = merged
|
|
135
|
+
results["total_found"] = len(merged)
|
|
136
|
+
results["returned"] = len(merged)
|
|
137
|
+
|
|
138
|
+
# Fallback Logic (Only if truly empty)
|
|
61
139
|
if not results["results"] and market == "america":
|
|
62
140
|
|
|
63
141
|
# 1. Asian Numeric Heuristic
|
|
64
142
|
if query.isdigit() and len(query) in [4, 5, 6]:
|
|
143
|
+
# ... existing heuristic code ...
|
|
65
144
|
asian_markets = ["taiwan", "hongkong", "japan", "china", "korea"]
|
|
66
145
|
for asian_market in asian_markets:
|
|
67
146
|
asian_cols = get_default_columns_for_market(asian_market)
|
|
@@ -98,7 +177,7 @@ def search_symbols(
|
|
|
98
177
|
# Add locators for global results
|
|
99
178
|
records = df.to_dict("records")
|
|
100
179
|
for r in records:
|
|
101
|
-
_enrich_locator(r, "stock", query)
|
|
180
|
+
_enrich_locator(r, "stock", query)
|
|
102
181
|
|
|
103
182
|
return {
|
|
104
183
|
"query": query,
|
|
@@ -111,18 +190,6 @@ def search_symbols(
|
|
|
111
190
|
except Exception:
|
|
112
191
|
pass
|
|
113
192
|
|
|
114
|
-
# 3. Check Crypto/Forex/CFD
|
|
115
|
-
for other in ["crypto", "forex", "cfd"]:
|
|
116
|
-
if other == market: continue
|
|
117
|
-
|
|
118
|
-
cols = get_default_columns_for_market(other)
|
|
119
|
-
sort = "market_cap_basic" if "market_cap_basic" in cols else "name"
|
|
120
|
-
if other == "forex" or other == "cfd": sort = "name"
|
|
121
|
-
|
|
122
|
-
res = _execute_search(queries, other, 5, cols, sort)
|
|
123
|
-
if res["results"]:
|
|
124
|
-
return {**res, "note": f"Found matches in '{other}'."}
|
|
125
|
-
|
|
126
193
|
return results
|
|
127
194
|
|
|
128
195
|
except Exception as e:
|
|
@@ -25,7 +25,7 @@ def get_technical_analysis(symbol: str, market: str) -> dict[str, Any]:
|
|
|
25
25
|
# Parse strictly formatted symbol
|
|
26
26
|
exchange, ticker = symbol.split(":", 1)
|
|
27
27
|
|
|
28
|
-
market = sanitize_market(market)
|
|
28
|
+
market = sanitize_market(market, strict=True)
|
|
29
29
|
|
|
30
30
|
# Use standard technical columns
|
|
31
31
|
cols = list(TECHNICAL_COLUMNS)
|
|
@@ -60,7 +60,7 @@ def get_technical_analysis(symbol: str, market: str) -> dict[str, Any]:
|
|
|
60
60
|
|
|
61
61
|
def scan_rsi_extremes(market: str = "america", limit: int = 25) -> dict[str, Any]:
|
|
62
62
|
"""Find symbols with extreme RSI values (<30 or >70)."""
|
|
63
|
-
market = sanitize_market(market)
|
|
63
|
+
market = sanitize_market(market, strict=True)
|
|
64
64
|
|
|
65
65
|
q = (
|
|
66
66
|
Query()
|
|
@@ -100,7 +100,7 @@ def scan_rsi_extremes(market: str = "america", limit: int = 25) -> dict[str, Any
|
|
|
100
100
|
|
|
101
101
|
def scan_bollinger_bands(market: str = "america", limit: int = 25) -> dict[str, Any]:
|
|
102
102
|
"""Find symbols trading outside Bollinger Bands."""
|
|
103
|
-
market = sanitize_market(market)
|
|
103
|
+
market = sanitize_market(market, strict=True)
|
|
104
104
|
# This is complex to do with simple filters.
|
|
105
105
|
# We'll just return basic BB values for top volume stocks.
|
|
106
106
|
|
|
@@ -120,7 +120,7 @@ def scan_bollinger_bands(market: str = "america", limit: int = 25) -> dict[str,
|
|
|
120
120
|
|
|
121
121
|
def scan_macd_crossover(market: str = "america", limit: int = 25) -> dict[str, Any]:
|
|
122
122
|
"""scan for MACD crossovers."""
|
|
123
|
-
market = sanitize_market(market)
|
|
123
|
+
market = sanitize_market(market, strict=True)
|
|
124
124
|
q = (
|
|
125
125
|
Query()
|
|
126
126
|
.set_markets(market)
|
|
@@ -99,33 +99,42 @@ COUNTRY_ALIASES = {
|
|
|
99
99
|
"za": "rsa", # South Africa
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
def sanitize_market(market: str | None, default: str = "america") -> str:
|
|
102
|
+
def sanitize_market(market: str | None, default: str = "america", strict: bool = False) -> str:
|
|
103
103
|
"""
|
|
104
104
|
Validate and sanitize a market name.
|
|
105
105
|
|
|
106
106
|
Args:
|
|
107
107
|
market: Market to validate (e.g. 'america', 'tw', 'crypto')
|
|
108
|
-
default: Default value if invalid
|
|
108
|
+
default: Default value if invalid (only used if strict=False)
|
|
109
|
+
strict: If True, raise ValueError for invalid markets instead of returning default.
|
|
109
110
|
|
|
110
111
|
Returns:
|
|
111
112
|
Valid market string
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
ValueError: If strict=True and market is invalid.
|
|
112
116
|
"""
|
|
113
117
|
if not market:
|
|
118
|
+
if strict:
|
|
119
|
+
raise ValueError("Market parameter is required (e.g. 'america', 'taiwan').")
|
|
114
120
|
return default
|
|
115
121
|
|
|
116
|
-
|
|
122
|
+
market_clean = market.strip().lower()
|
|
117
123
|
|
|
118
124
|
# Check exact match
|
|
119
|
-
if
|
|
120
|
-
return
|
|
125
|
+
if market_clean in MARKETS:
|
|
126
|
+
return market_clean
|
|
121
127
|
|
|
122
128
|
# Check alias match
|
|
123
|
-
if
|
|
124
|
-
return COUNTRY_ALIASES[
|
|
129
|
+
if market_clean in COUNTRY_ALIASES:
|
|
130
|
+
return COUNTRY_ALIASES[market_clean]
|
|
125
131
|
|
|
126
132
|
# Check if user passed exchange (e.g. 'nasdaq') by mistake
|
|
127
|
-
if
|
|
128
|
-
return EXCHANGE_SCREENER[
|
|
133
|
+
if market_clean in EXCHANGE_SCREENER:
|
|
134
|
+
return EXCHANGE_SCREENER[market_clean]
|
|
135
|
+
|
|
136
|
+
if strict:
|
|
137
|
+
raise ValueError(f"Invalid market '{market}'. Please use a valid country code or name (e.g. 'america', 'taiwan', 'crypto').")
|
|
129
138
|
|
|
130
139
|
return default
|
|
131
140
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/column_display_names.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/extracted/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/bond.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/bonds.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/cfd.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/coin.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/crypto.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/economics2.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/forex.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/futures.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/ireland.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/options.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/metainfo/stocks.json
RENAMED
|
File without changes
|
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/screeners/markets.json
RENAMED
|
File without changes
|
{tradingview_mcp-26.3.1 → tradingview_mcp-26.3.2}/src/tradingview_mcp/data/screeners/stocks.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|