tradingview-mcp 26.3.0__py3-none-any.whl → 26.3.1__py3-none-any.whl

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.
@@ -10,14 +10,34 @@ from tradingview_mcp.docs_data import get_default_columns_for_market, STOCK_MARK
10
10
  from tradingview_mcp.query import Query
11
11
  from tradingview_mcp.utils import sanitize_market
12
12
 
13
+ # Common aliases for commodities/forex that don't match TV names
14
+ SYMBOL_ALIASES = {
15
+ # Precious Metals
16
+ "XAG": ["SILVER", "XAGUSD"],
17
+ "XAU": ["GOLD", "XAUUSD"],
18
+ "XPT": ["PLATINUM", "XPTUSD"],
19
+ "XPD": ["PALLADIUM", "XPDUSD"],
20
+
21
+ # Energy
22
+ "OIL": ["USOIL", "UKOIL", "WTI", "BRENT"],
23
+ "WTI": ["USOIL"],
24
+ "BRENT": ["UKOIL"],
25
+ "NATGAS": ["NG1!", "NATURALGAS"],
26
+
27
+ # Others
28
+ "COPPER": ["HG1!", "XCUUSD"],
29
+ }
30
+
13
31
  def search_symbols(
14
32
  query: str,
15
33
  market: str = "america",
16
34
  limit: int = 25,
17
35
  ) -> dict[str, Any]:
18
36
  """
19
- Search symbols by name or ticker.
20
- If default market returns no results, automatically searches crypto and forex.
37
+ Search symbols by name or ticker. Returns strict locator format for downstream tools.
38
+
39
+ Output format includes 'locator': 'EXCHANGE:SYMBOL, market_name'.
40
+ Use this locator to call get_technical_analysis() or other tools precisely.
21
41
  """
22
42
  market = sanitize_market(market)
23
43
  limit = max(1, min(limit, 100))
@@ -28,29 +48,31 @@ def search_symbols(
28
48
  # Determine sort column
29
49
  sort_col = "market_cap_basic" if "market_cap_basic" in cols else "name"
30
50
 
51
+ # Expand query with aliases if available
52
+ queries = [query]
53
+ if query.upper() in SYMBOL_ALIASES:
54
+ queries.extend(SYMBOL_ALIASES[query.upper()])
55
+
31
56
  try:
32
57
  # Standard search in requested market
33
- results = _execute_search(query, market, limit, cols, sort_col)
58
+ results = _execute_search(queries, market, limit, cols, sort_col)
34
59
 
35
60
  # Smart Fallback: If default market ('america') yielded no results, try others
36
61
  if not results["results"] and market == "america":
37
62
 
38
- # 1. Asian Numeric Heuristic (Prioritized for speed/accuracy)
63
+ # 1. Asian Numeric Heuristic
39
64
  if query.isdigit() and len(query) in [4, 5, 6]:
40
65
  asian_markets = ["taiwan", "hongkong", "japan", "china", "korea"]
41
66
  for asian_market in asian_markets:
42
67
  asian_cols = get_default_columns_for_market(asian_market)
43
- asian_sort = "volume" # Volume is good for general Asian scanners
44
- asian_res = _execute_search(query, asian_market, 5, asian_cols, asian_sort)
68
+ asian_sort = "volume"
69
+ asian_res = _execute_search(queries, asian_market, 5, asian_cols, asian_sort)
45
70
  if asian_res["results"]:
46
- return {**asian_res, "note": f"No results in 'america', found matches in '{asian_market}' (numeric heuristic)"}
71
+ return {**asian_res, "note": f"No results in 'america', using numeric heuristic for '{asian_market}'."}
47
72
 
48
- # 2. True Global Search (Check ALL 68+ Stock Markets)
73
+ # 2. True Global Search
49
74
  try:
50
- # Use standard global columns safe for all
51
75
  global_cols = ["name", "description", "close", "change", "volume", "market_cap_basic", "exchange", "type"]
52
-
53
- # Check Description First (often better for "Maybank")
54
76
  q = (
55
77
  Query()
56
78
  .set_markets(*STOCK_MARKETS)
@@ -62,7 +84,6 @@ def search_symbols(
62
84
  _, df = q.get_scanner_data()
63
85
 
64
86
  if df.empty:
65
- # Check Name (Ticker)
66
87
  q = (
67
88
  Query()
68
89
  .set_markets(*STOCK_MARKETS)
@@ -74,31 +95,33 @@ def search_symbols(
74
95
  _, df = q.get_scanner_data()
75
96
 
76
97
  if not df.empty:
77
- # Basic Global Result
78
- total = len(df) # Roughly
98
+ # Add locators for global results
99
+ records = df.to_dict("records")
100
+ for r in records:
101
+ _enrich_locator(r, "stock", query) # We can't know exact market easily from batch query, assume global context usage
102
+
79
103
  return {
80
104
  "query": query,
81
105
  "market": "global",
82
- "total_found": total,
106
+ "total_found": len(df),
83
107
  "returned": len(df),
84
- "results": df.to_dict("records"),
85
- "note": "No results in 'america', found matches in global stock markets."
108
+ "results": records,
109
+ "note": "Found matches in global stock markets."
86
110
  }
87
-
88
111
  except Exception:
89
- pass # Continue to other fallbacks
112
+ pass
90
113
 
91
- # 3. Check Crypto/Forex
92
- for other in ["crypto", "forex"]:
114
+ # 3. Check Crypto/Forex/CFD
115
+ for other in ["crypto", "forex", "cfd"]:
93
116
  if other == market: continue
94
117
 
95
118
  cols = get_default_columns_for_market(other)
96
119
  sort = "market_cap_basic" if "market_cap_basic" in cols else "name"
97
- if other == "forex": sort = "name"
120
+ if other == "forex" or other == "cfd": sort = "name"
98
121
 
99
- res = _execute_search(query, other, 5, cols, sort)
122
+ res = _execute_search(queries, other, 5, cols, sort)
100
123
  if res["results"]:
101
- return {**res, "note": f"No results in stocks, found matches in '{other}'"}
124
+ return {**res, "note": f"Found matches in '{other}'."}
102
125
 
103
126
  return results
104
127
 
@@ -106,74 +129,86 @@ def search_symbols(
106
129
  return _search_symbols_fallback(query, market, limit, cols, str(e))
107
130
 
108
131
 
109
- def _execute_search(query: str, market: str, limit: int, cols: list[str], sort_col: str) -> dict[str, Any]:
110
- """Execute a single search query against a market."""
111
- try:
112
- # 1. Search description (company name)
113
- query_obj = (
114
- Query()
115
- .set_markets(market)
116
- .select(*cols)
117
- .where(Column("description").like(query))
118
- .order_by(sort_col, ascending=False)
119
- .limit(limit)
120
- )
121
- total, df = query_obj.get_scanner_data()
122
- results = df.to_dict("records")
132
+ def _enrich_locator(record: dict, market: str, category: str = "stock"):
133
+ """Add standard locator string to a record."""
134
+ ex = record.get("exchange", "UNKNOWN")
135
+ name = record.get("name", "UNKNOWN")
136
+
137
+ # Try to deduce market if 'stock' is generic
138
+ # (This is hard without a map of Exchange->Country, but we do our best)
139
+
140
+ # Construct Locator
141
+ # Format: EXCHANGE:SYMBOL, Market
142
+ record["locator"] = f"{ex}:{name}, {market}"
143
+ record["market"] = market
144
+
145
+
146
+ def _execute_search(queries: list[str] | str, market: str, limit: int, cols: list[str], sort_col: str) -> dict[str, Any]:
147
+ """Execute search, supporting multiple query terms (aliases)."""
148
+ if isinstance(queries, str):
149
+ queries = [queries]
150
+
151
+ all_results = []
152
+
153
+ # Try each query term until we get enough results
154
+ for q_term in queries:
155
+ if len(all_results) >= limit: break
123
156
 
124
- # 2. If nothing found, search name (ticker)
125
- if not results:
126
- query_obj2 = (
157
+ try:
158
+ # 1. Search description
159
+ q = (
127
160
  Query()
128
161
  .set_markets(market)
129
162
  .select(*cols)
130
- .where(Column("name").like(query))
163
+ .where(Column("description").like(q_term))
131
164
  .order_by(sort_col, ascending=False)
132
165
  .limit(limit)
133
166
  )
134
- total, df = query_obj2.get_scanner_data()
135
- results = df.to_dict("records")
167
+ _, df = q.get_scanner_data()
168
+ if not df.empty:
169
+ all_results.extend(df.to_dict("records"))
136
170
 
137
- return {
138
- "query": query,
139
- "market": market,
140
- "total_found": total if results else 0,
141
- "returned": len(results),
142
- "results": results,
143
- "hint": "Use 'name' field as symbol for other tools" if results else "No matches found.",
144
- }
145
- except Exception:
146
- return {"results": [], "market": market}
171
+ # 2. Search name
172
+ if len(all_results) < limit:
173
+ q2 = (
174
+ Query()
175
+ .set_markets(market)
176
+ .select(*cols)
177
+ .where(Column("name").like(q_term))
178
+ .order_by(sort_col, ascending=False)
179
+ .limit(limit)
180
+ )
181
+ _, df2 = q2.get_scanner_data()
182
+ if not df2.empty:
183
+ all_results.extend(df2.to_dict("records"))
184
+ except:
185
+ pass
186
+
187
+ # Dedup
188
+ unique = {r.get("name")+r.get("exchange"): r for r in all_results}
189
+ results = list(unique.values())[:limit]
190
+
191
+ # Enhance with locator
192
+ for r in results:
193
+ _enrich_locator(r, market)
194
+
195
+ return {
196
+ "query": queries[0],
197
+ "market": market,
198
+ "total_found": len(results),
199
+ "returned": len(results),
200
+ "results": results,
201
+ }
147
202
 
148
203
 
149
204
  def _search_symbols_fallback(query: str, market: str, limit: int, cols: list[str], error: str) -> dict[str, Any]:
150
- """Fallback search when API operations fail."""
151
- try:
152
- q = Query().set_markets(market).select(*cols).limit(100)
153
- _, df = q.get_scanner_data()
154
- q_lower = query.lower()
155
- mask = df["name"].str.lower().str.contains(q_lower) | df["description"].str.lower().str.contains(q_lower)
156
- df_filtered = df[mask].head(limit)
157
- return {
158
- "query": query,
159
- "market": market,
160
- "returned": len(df_filtered),
161
- "results": df_filtered.to_dict("records"),
162
- "note": "Used fallback local search.",
163
- "original_error": error,
164
- }
165
- except Exception as e:
166
- return {"error": f"Search failed: {str(e)}", "original_error": error}
205
+ return {"error": f"Search failed: {str(e)}", "original_error": error}
167
206
 
168
207
 
169
208
  def get_symbol_info(symbol: str, include_technical: bool = False) -> dict[str, Any]:
170
209
  """
171
210
  Get detailed information for a symbol.
172
-
173
- Modes:
174
- 1. Strict: 'EXCHANGE:SYMBOL' (e.g. 'NASDAQ:AAPL') - searches only that exchange/market.
175
- 2. Universal: 'SYMBOL' (e.g. 'AAA') - searches ALL global markets, Crypto, and Forex.
176
- Returns ALL matches found to verify ambiguity.
211
+ Returns matches with strict locators: 'EXCHANGE:SYMBOL, Market'.
177
212
  """
178
213
  technical_cols = [
179
214
  "RSI", "RSI7", "MACD.macd", "MACD.signal", "SMA20", "SMA50", "SMA200",
@@ -187,12 +222,13 @@ def get_symbol_info(symbol: str, include_technical: bool = False) -> dict[str, A
187
222
  exchange, ticker = symbol.split(":", 1)
188
223
  market = EXCHANGE_SCREENER.get(exchange.lower()) or "america"
189
224
 
190
- # Setup Columns
225
+ # Allow override via sanitized lookup? No, specific exchange implies specific market logic usually.
226
+ # But let's be safe.
227
+
191
228
  cols = get_default_columns_for_market(market)
192
229
  if include_technical: cols.extend(technical_cols)
193
230
  cols = list(dict.fromkeys(cols))
194
231
 
195
- # Query
196
232
  q = (
197
233
  Query()
198
234
  .set_markets(market)
@@ -204,6 +240,10 @@ def get_symbol_info(symbol: str, include_technical: bool = False) -> dict[str, A
204
240
  results = df.to_dict("records")
205
241
 
206
242
  if results:
243
+ # Add strict locators
244
+ for r in results:
245
+ _enrich_locator(r, market)
246
+
207
247
  if len(results) == 1:
208
248
  return {"symbol": symbol, "found": True, "market": market, "data": results[0]}
209
249
  return {"symbol": symbol, "found": True, "market": market, "matches": results}
@@ -211,92 +251,76 @@ def get_symbol_info(symbol: str, include_technical: bool = False) -> dict[str, A
211
251
  return {"symbol": symbol, "found": False, "hint": f"Symbol not found in {market} ({exchange}). Check format."}
212
252
 
213
253
  # --- Universal Mode (Implicit Market) ---
214
- # We search Stocks (Global), Crypto, vs Forex in parallel using ThreadPoolExecutor
215
254
  all_matches = []
216
255
 
217
- def check_global_stocks():
218
- try:
219
- stock_cols = ["name", "description", "close", "change", "volume", "market_cap_basic", "exchange", "type"]
220
- if include_technical: stock_cols.extend(technical_cols)
221
- stock_cols = list(dict.fromkeys(stock_cols))
256
+ # Targets
257
+ targets = [symbol, symbol.upper(), f"{symbol}USDT", f"{symbol}USD"]
258
+ if symbol.upper() in SYMBOL_ALIASES:
259
+ targets.extend(SYMBOL_ALIASES[symbol.upper()])
260
+
261
+ # Helper
262
+ def run_search(market, col_getter):
263
+ try:
264
+ cols = col_getter(market)
265
+ if include_technical: cols.extend(technical_cols)
266
+ cols = list(dict.fromkeys(cols))
222
267
 
223
- q_stocks = (
224
- Query()
225
- .set_markets(*STOCK_MARKETS)
226
- .select(*stock_cols)
227
- .where(Column("name").isin([symbol, symbol.upper()]))
228
- .order_by("market_cap_basic", ascending=False)
229
- .limit(10)
230
- )
231
- _, df_stocks = q_stocks.get_scanner_data()
232
- if not df_stocks.empty:
233
- matches = df_stocks.to_dict("records")
234
- for m in matches: m["_category"] = "stock"
235
- return matches
236
- except Exception:
237
- pass
238
- return []
239
-
240
- def check_crypto():
241
- try:
242
- crypto_cols = get_default_columns_for_market("crypto")
243
- if include_technical: crypto_cols.extend(technical_cols)
244
- crypto_cols = list(dict.fromkeys(crypto_cols))
245
-
246
- q_crypto = (
247
- Query()
248
- .set_markets("crypto")
249
- .select(*crypto_cols)
250
- .where(Column("name").isin([symbol, symbol.upper(), f"{symbol}USDT", f"{symbol}USD"]))
251
- .limit(5)
252
- )
253
- _, df_crypto = q_crypto.get_scanner_data()
254
- if not df_crypto.empty:
255
- matches = df_crypto.to_dict("records")
256
- for m in matches: m["_category"] = "crypto"
268
+ # We need to set markets properly
269
+ q = Query()
270
+ if market == "global_stocks":
271
+ q.set_markets(*STOCK_MARKETS)
272
+ else:
273
+ q.set_markets(market)
274
+
275
+ q.select(*cols).where(Column("name").isin(targets))
276
+
277
+ if market == "global_stocks":
278
+ q.order_by("market_cap_basic", ascending=False).limit(10)
279
+ else:
280
+ q.limit(5)
281
+
282
+ _, df = q.get_scanner_data()
283
+ if not df.empty:
284
+ matches = df.to_dict("records")
285
+ cat = "stock" if market == "global_stocks" else market
286
+
287
+ # Fixup locator logic
288
+ for m in matches:
289
+ m["_category"] = cat
290
+ # If global stock, we don't know exact country easily without map.
291
+ # But for now we pass 'global_stocks' or try to guess?
292
+ # It's better to tell AI "taiwan" if possible.
293
+ # But we queried ALL markets.
294
+ # HACK: We should query 'taiwan' explicitly if heuristics match?
295
+ # No, just return EXCHANGE:SYMBOL and let AI use 'search_symbols' if it needs precise market.
296
+ # Actually user wants "TWSE:0050, taiwan".
297
+ # If we used set_markets(*ALL), we lose the origin market info in the response unless we select 'market' column?
298
+ # TradingView API doesn't usually return 'market' column. It returns 'exchange'.
299
+ # We can map Exchange -> Market via EXCHANGE_SCREENER?
300
+ rec_market = EXCHANGE_SCREENER.get(m.get("exchange", "").lower(), cat)
301
+ _enrich_locator(m, rec_market)
302
+
257
303
  return matches
258
- except Exception:
304
+ except:
259
305
  pass
260
- return []
261
-
262
- def check_forex():
263
- try:
264
- forex_cols = get_default_columns_for_market("forex")
265
- if include_technical: forex_cols.extend(technical_cols)
266
- forex_cols = list(dict.fromkeys(forex_cols))
267
-
268
- q_forex = (
269
- Query()
270
- .set_markets("forex")
271
- .select(*forex_cols)
272
- .where(Column("name").isin([symbol, symbol.upper()]))
273
- .limit(5)
274
- )
275
- _, df_forex = q_forex.get_scanner_data()
276
- if not df_forex.empty:
277
- matches = df_forex.to_dict("records")
278
- for m in matches: m["_category"] = "forex"
279
- return matches
280
- except Exception:
281
- pass
282
- return []
306
+ return []
283
307
 
284
308
  # Execute in parallel
285
- with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
309
+ with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
286
310
  futures = [
287
- executor.submit(check_global_stocks),
288
- executor.submit(check_crypto),
289
- executor.submit(check_forex)
311
+ executor.submit(run_search, "global_stocks", lambda m: ["name", "description", "close", "change", "volume", "market_cap_basic", "exchange", "type"]),
312
+ executor.submit(run_search, "crypto", get_default_columns_for_market),
313
+ executor.submit(run_search, "forex", get_default_columns_for_market),
314
+ executor.submit(run_search, "cfd", get_default_columns_for_market)
290
315
  ]
291
316
  for future in concurrent.futures.as_completed(futures):
292
317
  all_matches.extend(future.result())
293
318
 
294
319
  # --- Aggregate Results ---
295
320
  if not all_matches:
296
- # Try search_symbols as last resort (fuzzy search)
297
321
  return search_symbols(symbol, "america", 5)
298
322
 
299
- # Remove duplicates based on ticker
323
+ # Remove duplicates
300
324
  unique_matches = {}
301
325
  for m in all_matches:
302
326
  key = m.get("ticker", m.get("name"))
@@ -6,8 +6,25 @@ from tradingview_mcp.constants import TECHNICAL_COLUMNS
6
6
  from tradingview_mcp.query import Query
7
7
  from tradingview_mcp.utils import sanitize_market
8
8
 
9
- def get_technical_analysis(symbol: str, market: str = "america") -> dict[str, Any]:
10
- """Get technical indicators for a symbol."""
9
+ def get_technical_analysis(symbol: str, market: str) -> dict[str, Any]:
10
+ """
11
+ Get technical indicators for a symbol.
12
+
13
+ IMPORTANT: You must use a strictly formatted symbol and market.
14
+ 1. Call `get_symbol_info(symbol)` first.
15
+ 2. Use the `locator` field from the response (e.g. "EXCHANGE:SYMBOL") and the exact `market`.
16
+ 3. Do NOT guess the market.
17
+ """
18
+ # Strict validation
19
+ if ":" not in symbol:
20
+ return {
21
+ "error": "Ambiguous symbol format. Missing exchange prefix.",
22
+ "hint": f"Please run get_symbol_info('{symbol}') first to find the correct 'EXCHANGE:{symbol}' locator and 'market'."
23
+ }
24
+
25
+ # Parse strictly formatted symbol
26
+ exchange, ticker = symbol.split(":", 1)
27
+
11
28
  market = sanitize_market(market)
12
29
 
13
30
  # Use standard technical columns
@@ -18,13 +35,19 @@ def get_technical_analysis(symbol: str, market: str = "america") -> dict[str, An
18
35
  Query()
19
36
  .set_markets(market)
20
37
  .select(*cols)
21
- .where(Column("name").isin([symbol, symbol.upper()]))
38
+ .where(Column("name").isin([ticker, ticker.upper()])) # Use ticker part for name lookup
22
39
  .limit(1)
23
40
  )
41
+
24
42
  _, df = q.get_scanner_data()
25
43
 
26
44
  if df.empty:
27
- return {"error": f"Symbol {symbol} not found in {market}"}
45
+ # Check if user maybe used wrong market?
46
+ # But we want to be strict.
47
+ return {
48
+ "error": f"Symbol '{symbol}' not found in market '{market}'.",
49
+ "hint": "Ensure you are using the correct market from get_symbol_info()."
50
+ }
28
51
 
29
52
  data = df.iloc[0].to_dict()
30
53
  return {
tradingview_mcp/utils.py CHANGED
@@ -54,12 +54,57 @@ def sanitize_exchange(ex: str | None, default: str = "america") -> str:
54
54
  return default
55
55
 
56
56
 
57
+ # Country Code mappings
58
+ COUNTRY_ALIASES = {
59
+ "us": "america",
60
+ "usa": "america",
61
+ "uk": "uk",
62
+ "gb": "uk",
63
+ "de": "germany",
64
+ "fr": "france",
65
+ "it": "italy",
66
+ "es": "spain",
67
+ "pt": "portugal",
68
+ "ch": "switzerland",
69
+ "se": "sweden",
70
+ "no": "norway",
71
+ "fi": "finland",
72
+ "nl": "netherlands",
73
+ "be": "belgium",
74
+ "at": "austria",
75
+ "ie": "ireland",
76
+ "ru": "russia",
77
+ "cn": "china",
78
+ "jp": "japan",
79
+ "kr": "korea",
80
+ "in": "india",
81
+ "id": "indonesia",
82
+ "my": "malaysia",
83
+ "th": "thailand",
84
+ "vn": "vietnam",
85
+ "tw": "taiwan",
86
+ "sg": "singapore",
87
+ "hk": "hongkong",
88
+ "au": "australia",
89
+ "nz": "newzealand",
90
+ "ca": "canada",
91
+ "br": "brazil",
92
+ "mx": "mexico",
93
+ "ar": "argentina",
94
+ "cl": "chile",
95
+ "co": "colombia",
96
+ "pe": "peru",
97
+ "eg": "egypt",
98
+ "tr": "turkey",
99
+ "za": "rsa", # South Africa
100
+ }
101
+
57
102
  def sanitize_market(market: str | None, default: str = "america") -> str:
58
103
  """
59
104
  Validate and sanitize a market name.
60
-
105
+
61
106
  Args:
62
- market: Market to validate
107
+ market: Market to validate (e.g. 'america', 'tw', 'crypto')
63
108
  default: Default value if invalid
64
109
 
65
110
  Returns:
@@ -67,8 +112,22 @@ def sanitize_market(market: str | None, default: str = "america") -> str:
67
112
  """
68
113
  if not market:
69
114
  return default
115
+
70
116
  market = market.strip().lower()
71
- return market if market in MARKETS else default
117
+
118
+ # Check exact match
119
+ if market in MARKETS:
120
+ return market
121
+
122
+ # Check alias match
123
+ if market in COUNTRY_ALIASES:
124
+ return COUNTRY_ALIASES[market]
125
+
126
+ # Check if user passed exchange (e.g. 'nasdaq') by mistake
127
+ if market in EXCHANGE_SCREENER:
128
+ return EXCHANGE_SCREENER[market]
129
+
130
+ return default
72
131
 
73
132
 
74
133
  def timeframe_to_resolution(tf: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tradingview-mcp
3
- Version: 26.3.0
3
+ Version: 26.3.1
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
@@ -7,7 +7,7 @@ tradingview_mcp/query.py,sha256=gkC4t-2_jtuUK3KgzU62wrIcGoaOmY--1EwAshGQb0Q,1087
7
7
  tradingview_mcp/resources.py,sha256=6cbsPhXsq6SFh3bmmSnMtrVkyCx1_xpWWiyqNLCCULk,1910
8
8
  tradingview_mcp/scanner.py,sha256=oEfMzaYhQ7ggBdLlPLqKZCMxHkd6MBMaUMcm3p-YvPA,7649
9
9
  tradingview_mcp/server.py,sha256=opC61fKfchYn3-XL5ZlPGvsPRZZoLXe-VqGSulhrJHA,7165
10
- tradingview_mcp/utils.py,sha256=6O89qIaZ_eYoN3m12r2q4-D8VpJajP5WuwKQbj4ZVHo,10238
10
+ tradingview_mcp/utils.py,sha256=5W0vLcrguwZv8q-ohZfQCqrkxVF1OCWPPVyCJ-bxhOo,11486
11
11
  tradingview_mcp/data/__init__.py,sha256=3kot_ZG4a6jbZgyksE9z56n3skI407lsd4y2ThHotKY,298
12
12
  tradingview_mcp/data/column_display_names.json,sha256=AFdo6Q2n-wSXWHi2wEExOd2pTItyjocRbrnxqYpCa94,30137
13
13
  tradingview_mcp/data/markets.json,sha256=CehqCeVdQhWeCkMX1HWOrgCRjOomWa3VH1v2ZvPnIck,1482
@@ -35,10 +35,10 @@ tradingview_mcp/data/screeners/stocks_failed.json,sha256=RqBp9XCZ3xvdWZMwuVGysEE
35
35
  tradingview_mcp/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  tradingview_mcp/tools/reference.py,sha256=BPip9ntzyRwIyE0SoD8TWqSGLYj7Evt-OM1SQ1uPG8o,2160
37
37
  tradingview_mcp/tools/screener.py,sha256=K6uOUFq4GzPeCE4w2oa1LkAHbodQdQcmexw0W3sofeo,3188
38
- tradingview_mcp/tools/search.py,sha256=40Wpcf-noEkcB8Yv3f1NPd0UtmixWEf_AZoGf_GGibM,12970
39
- tradingview_mcp/tools/technical.py,sha256=l5nhXLQu-5iGJ0G-pdVFAghQsXv7JqoEGlphqrEjxQ8,3620
40
- tradingview_mcp-26.3.0.dist-info/METADATA,sha256=pBPcg95atEn_vedXIz7dwJ3WWeVgCv6HCT1xWjsQ3bU,4818
41
- tradingview_mcp-26.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
42
- tradingview_mcp-26.3.0.dist-info/entry_points.txt,sha256=GZxjGqgVbUlWDp5OzFQoCN_g1UBLyOmfVqCR5uzscnU,57
43
- tradingview_mcp-26.3.0.dist-info/licenses/LICENSE,sha256=1Hdpp7qGWCXVw1BP6vpdAPO4KrgO0T_c0N3ipkYHKAo,1070
44
- tradingview_mcp-26.3.0.dist-info/RECORD,,
38
+ tradingview_mcp/tools/search.py,sha256=_BNdmf4qzUUG5A6orODzm3R2evDcdgBQhY6aRdg09-o,13791
39
+ tradingview_mcp/tools/technical.py,sha256=x5raTT-l-Kn6uGUI_U7MLZRGjHXdgPB8Vss9nox3m5A,4500
40
+ tradingview_mcp-26.3.1.dist-info/METADATA,sha256=XNaWsK_-K7ei3R8sLivRp4VPEYD4BewD_iCSePjOsXw,4818
41
+ tradingview_mcp-26.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
42
+ tradingview_mcp-26.3.1.dist-info/entry_points.txt,sha256=GZxjGqgVbUlWDp5OzFQoCN_g1UBLyOmfVqCR5uzscnU,57
43
+ tradingview_mcp-26.3.1.dist-info/licenses/LICENSE,sha256=1Hdpp7qGWCXVw1BP6vpdAPO4KrgO0T_c0N3ipkYHKAo,1070
44
+ tradingview_mcp-26.3.1.dist-info/RECORD,,