tradingview-mcp 1.0.0__py3-none-any.whl → 1.0__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.
Files changed (34) hide show
  1. tradingview_mcp/constants.py +6 -4
  2. tradingview_mcp/data/__init__.py +9 -0
  3. tradingview_mcp/data/column_display_names.json +827 -0
  4. tradingview_mcp/data/extracted/__init__.py +1 -0
  5. tradingview_mcp/data/extracted/ai_quick_reference.json +212 -0
  6. tradingview_mcp/data/extracted/common_fields.json +3627 -0
  7. tradingview_mcp/data/extracted/fields_by_market.json +23299 -0
  8. tradingview_mcp/data/extracted/screener_code_examples.json +9 -0
  9. tradingview_mcp/data/markets.json +83 -0
  10. tradingview_mcp/data/metainfo/bond.json +17406 -0
  11. tradingview_mcp/data/metainfo/bonds.json +1303 -0
  12. tradingview_mcp/data/metainfo/cfd.json +21603 -0
  13. tradingview_mcp/data/metainfo/coin.json +21111 -0
  14. tradingview_mcp/data/metainfo/crypto.json +23078 -0
  15. tradingview_mcp/data/metainfo/economics2.json +1026 -0
  16. tradingview_mcp/data/metainfo/forex.json +21003 -0
  17. tradingview_mcp/data/metainfo/futures.json +2972 -0
  18. tradingview_mcp/data/metainfo/ireland.json +22517 -0
  19. tradingview_mcp/data/metainfo/options.json +499 -0
  20. tradingview_mcp/data/metainfo/stocks.json +29808 -0
  21. tradingview_mcp/data/screeners/main_screeners.json +540 -0
  22. tradingview_mcp/data/screeners/markets.json +70 -0
  23. tradingview_mcp/data/screeners/stocks.json +416 -0
  24. tradingview_mcp/data/screeners/stocks_failed.json +36081 -0
  25. tradingview_mcp/docs_data.py +297 -0
  26. tradingview_mcp/scanner.py +2 -1
  27. tradingview_mcp/server.py +839 -51
  28. tradingview_mcp-1.0.dist-info/METADATA +334 -0
  29. tradingview_mcp-1.0.dist-info/RECORD +37 -0
  30. {tradingview_mcp-1.0.0.dist-info → tradingview_mcp-1.0.dist-info}/licenses/LICENSE +0 -2
  31. tradingview_mcp-1.0.0.dist-info/METADATA +0 -182
  32. tradingview_mcp-1.0.0.dist-info/RECORD +0 -13
  33. {tradingview_mcp-1.0.0.dist-info → tradingview_mcp-1.0.dist-info}/WHEEL +0 -0
  34. {tradingview_mcp-1.0.0.dist-info → tradingview_mcp-1.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,297 @@
1
+ """Load packaged docs and reference datasets for MCP resources.
2
+
3
+ This module provides access to:
4
+ - Market metadata (markets.json)
5
+ - Column/field display names (column_display_names.json)
6
+ - Screener presets (screeners/*.json)
7
+ - Market metainfo with field definitions (metainfo/*.json)
8
+ - AI-friendly quick reference (extracted/ai_quick_reference.json)
9
+ - Screener code examples (extracted/screener_code_examples.json)
10
+ - Field definitions by market (extracted/fields_by_market.json)
11
+
12
+ Data is loaded from packaged files first, with fallback to the reference path.
13
+ Set TRADINGVIEW_SCREENER_DOCS_DATA env var to override the reference path.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ from functools import lru_cache
21
+ from importlib.resources import files
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+
26
+ DEFAULT_REFERENCE_ROOT = Path(
27
+ os.getenv(
28
+ "TRADINGVIEW_SCREENER_DOCS_DATA",
29
+ "/home/henrik/tradingview-mcp/referance/TradingView-Screener-docs/data",
30
+ )
31
+ )
32
+
33
+ # Token/size limits for AI-friendly responses
34
+ MAX_ITEMS_DEFAULT = 100
35
+ MAX_ITEMS_LARGE = 500
36
+ MAX_CHARS_DEFAULT = 50000 # ~12.5k tokens
37
+ MAX_CHARS_LARGE = 200000 # ~50k tokens
38
+
39
+ # Large data warnings
40
+ LARGE_DATA_WARNING = (
41
+ "⚠️ This data is large ({size} items). "
42
+ "Use `limit` parameter to reduce output. "
43
+ "Set `confirm_large=True` to retrieve full data."
44
+ )
45
+
46
+
47
+ def _data_path(*parts: str):
48
+ """Get path to packaged data file."""
49
+ return files("tradingview_mcp.data").joinpath(*parts)
50
+
51
+
52
+ def _reference_path(*parts: str) -> Path:
53
+ """Get path to reference data file."""
54
+ return DEFAULT_REFERENCE_ROOT.joinpath(*parts)
55
+
56
+
57
+ @lru_cache(maxsize=128)
58
+ def load_json(*parts: str) -> Any:
59
+ """Load JSON file from package data or reference fallback."""
60
+ try:
61
+ path = _data_path(*parts)
62
+ with path.open("r", encoding="utf-8") as handle:
63
+ return json.load(handle)
64
+ except (FileNotFoundError, TypeError):
65
+ fallback = _reference_path(*parts)
66
+ with fallback.open("r", encoding="utf-8") as handle:
67
+ return json.load(handle)
68
+
69
+
70
+ def get_markets_data() -> dict[str, list[str]]:
71
+ """Get markets metadata."""
72
+ return load_json("markets.json")
73
+
74
+
75
+ def get_column_display_names() -> dict[str, str]:
76
+ """Get field name to display name mapping."""
77
+ data = load_json("column_display_names.json")
78
+ return data.get("fields", data)
79
+
80
+
81
+ def get_screener_presets() -> list[dict[str, Any]]:
82
+ """Get predefined screener presets."""
83
+ return load_json("screeners", "main_screeners.json")
84
+
85
+
86
+ def get_screener_markets() -> list[str]:
87
+ """Get list of screener market identifiers."""
88
+ return load_json("screeners", "markets.json")
89
+
90
+
91
+ def get_metainfo(market: str) -> dict[str, Any]:
92
+ """Get field metainfo for a specific market."""
93
+ return load_json("metainfo", f"{market}.json")
94
+
95
+
96
+ def get_screeners_data(name: str) -> Any:
97
+ """Get screeners data by filename."""
98
+ return load_json("screeners", name)
99
+
100
+
101
+ # ============================================================================
102
+ # AI-Friendly Data Access with Output Limits
103
+ # ============================================================================
104
+
105
+
106
+ def get_ai_quick_reference() -> dict[str, Any]:
107
+ """Get AI quick reference guide with common patterns and examples."""
108
+ return load_json("extracted", "ai_quick_reference.json")
109
+
110
+
111
+ def get_screener_code_examples() -> dict[str, str]:
112
+ """Get screener code examples by name."""
113
+ return load_json("extracted", "screener_code_examples.json")
114
+
115
+
116
+ def get_common_fields() -> dict[str, dict[str, Any]]:
117
+ """Get common field definitions (from stocks, most comprehensive)."""
118
+ return load_json("extracted", "common_fields.json")
119
+
120
+
121
+ def get_fields_by_market(market: str | None = None) -> dict[str, Any]:
122
+ """Get field definitions by market type."""
123
+ data = load_json("extracted", "fields_by_market.json")
124
+ if market:
125
+ return data.get(market, {})
126
+ return data
127
+
128
+
129
+ def paginate_data(
130
+ data: list | dict,
131
+ offset: int = 0,
132
+ limit: int = MAX_ITEMS_DEFAULT,
133
+ confirm_large: bool = False,
134
+ ) -> dict[str, Any]:
135
+ """Paginate large data with size warnings.
136
+
137
+ Args:
138
+ data: List or dict to paginate
139
+ offset: Starting index (for lists) or skip count (for dicts)
140
+ limit: Maximum items to return
141
+ confirm_large: If True, allow large outputs without warning
142
+
143
+ Returns:
144
+ Dict with 'data', 'total', 'offset', 'limit', and optional 'warning'
145
+ """
146
+ if isinstance(data, list):
147
+ total = len(data)
148
+ items = data[offset:offset + limit]
149
+ else:
150
+ keys = list(data.keys())
151
+ total = len(keys)
152
+ selected_keys = keys[offset:offset + limit]
153
+ items = {k: data[k] for k in selected_keys}
154
+
155
+ result: dict[str, Any] = {
156
+ "data": items,
157
+ "total": total,
158
+ "offset": offset,
159
+ "limit": limit,
160
+ "has_more": (offset + limit) < total,
161
+ }
162
+
163
+ # Add warning for large data if not confirmed
164
+ if total > MAX_ITEMS_DEFAULT and not confirm_large and limit >= total:
165
+ result["warning"] = LARGE_DATA_WARNING.format(size=total)
166
+ result["hint"] = f"Use limit={MAX_ITEMS_DEFAULT} or set confirm_large=True"
167
+
168
+ return result
169
+
170
+
171
+ def search_fields(
172
+ query: str,
173
+ market: str | None = None,
174
+ limit: int = 20,
175
+ ) -> list[dict[str, Any]]:
176
+ """Search fields by name or display name.
177
+
178
+ Args:
179
+ query: Search term (case-insensitive)
180
+ market: Optional market to search in (stocks, crypto, forex, etc.)
181
+ limit: Maximum results to return
182
+
183
+ Returns:
184
+ List of matching field definitions
185
+ """
186
+ query_lower = query.lower()
187
+
188
+ if market:
189
+ fields_data = get_fields_by_market(market)
190
+ if isinstance(fields_data, list):
191
+ fields = fields_data
192
+ else:
193
+ return []
194
+ else:
195
+ fields = list(get_common_fields().items())
196
+ fields = [
197
+ {"name": k, **v} if isinstance(v, dict) else {"name": k, "display_name": str(v)}
198
+ for k, v in fields[:500] # Limit iteration
199
+ ]
200
+
201
+ matches = []
202
+ for field in fields:
203
+ if isinstance(field, dict):
204
+ name = field.get("name", "")
205
+ display = field.get("display_name", "")
206
+ if query_lower in name.lower() or query_lower in display.lower():
207
+ matches.append(field)
208
+ if len(matches) >= limit:
209
+ break
210
+
211
+ return matches
212
+
213
+
214
+ def get_field_summary(field_name: str) -> dict[str, Any] | None:
215
+ """Get summary info for a specific field.
216
+
217
+ Args:
218
+ field_name: Field name to look up
219
+
220
+ Returns:
221
+ Field info dict or None if not found
222
+ """
223
+ # Check common fields first
224
+ common = get_common_fields()
225
+ if field_name in common:
226
+ info = common[field_name]
227
+ return {
228
+ "name": field_name,
229
+ "display_name": info.get("display_name"),
230
+ "type": info.get("type"),
231
+ "variants": info.get("variants"),
232
+ }
233
+
234
+ # Check display names mapping
235
+ display_names = get_column_display_names()
236
+ if field_name in display_names:
237
+ return {
238
+ "name": field_name,
239
+ "display_name": display_names[field_name],
240
+ }
241
+
242
+ return None
243
+
244
+
245
+ def estimate_tokens(data: Any) -> int:
246
+ """Estimate token count for data (rough: 4 chars = 1 token)."""
247
+ json_str = json.dumps(data, ensure_ascii=False, default=str)
248
+ return len(json_str) // 4
249
+
250
+
251
+ def truncate_for_tokens(
252
+ data: Any,
253
+ max_tokens: int = 10000,
254
+ ) -> tuple[Any, bool]:
255
+ """Truncate data to fit within token limit.
256
+
257
+ Args:
258
+ data: Data to potentially truncate
259
+ max_tokens: Maximum tokens allowed
260
+
261
+ Returns:
262
+ Tuple of (truncated_data, was_truncated)
263
+ """
264
+ estimated = estimate_tokens(data)
265
+ if estimated <= max_tokens:
266
+ return data, False
267
+
268
+ # Try to truncate intelligently
269
+ if isinstance(data, list):
270
+ # Binary search for right size
271
+ low, high = 0, len(data)
272
+ while low < high:
273
+ mid = (low + high + 1) // 2
274
+ if estimate_tokens(data[:mid]) <= max_tokens:
275
+ low = mid
276
+ else:
277
+ high = mid - 1
278
+ return data[:low], True
279
+
280
+ elif isinstance(data, dict):
281
+ keys = list(data.keys())
282
+ low, high = 0, len(keys)
283
+ while low < high:
284
+ mid = (low + high + 1) // 2
285
+ subset = {k: data[k] for k in keys[:mid]}
286
+ if estimate_tokens(subset) <= max_tokens:
287
+ low = mid
288
+ else:
289
+ high = mid - 1
290
+ return {k: data[k] for k in keys[:low]}, True
291
+
292
+ # Fallback: stringify and truncate
293
+ json_str = json.dumps(data, ensure_ascii=False, default=str)
294
+ max_chars = max_tokens * 4
295
+ if len(json_str) > max_chars:
296
+ return json_str[:max_chars] + "... (truncated)", True
297
+ return data, False
@@ -10,7 +10,8 @@ from tradingview_mcp.column import Column
10
10
  from tradingview_mcp.query import Query
11
11
 
12
12
 
13
- DEFAULT_COLUMNS = ["name", "close", "volume", "market_cap_basic"]
13
+ # Always include 'description' for full name (e.g., "Apple Inc." instead of just "AAPL")
14
+ DEFAULT_COLUMNS = ["name", "description", "close", "change", "volume", "market_cap_basic"]
14
15
 
15
16
 
16
17
  class Scanner: