tradingview-mcp 1.0.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.
- tradingview_mcp/__init__.py +14 -0
- tradingview_mcp/column.py +231 -0
- tradingview_mcp/constants.py +425 -0
- tradingview_mcp/models.py +154 -0
- tradingview_mcp/query.py +367 -0
- tradingview_mcp/scanner.py +256 -0
- tradingview_mcp/server.py +1361 -0
- tradingview_mcp/utils.py +382 -0
- tradingview_mcp-1.0.0.dist-info/METADATA +182 -0
- tradingview_mcp-1.0.0.dist-info/RECORD +13 -0
- tradingview_mcp-1.0.0.dist-info/WHEEL +4 -0
- tradingview_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- tradingview_mcp-1.0.0.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type definitions and models for TradingView Screener.
|
|
3
|
+
|
|
4
|
+
Provides TypedDicts for API request/response structures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Literal, TypedDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FilterOperationDict(TypedDict):
|
|
13
|
+
"""Filter operation for WHERE clauses."""
|
|
14
|
+
|
|
15
|
+
left: str
|
|
16
|
+
operation: Literal[
|
|
17
|
+
"greater",
|
|
18
|
+
"egreater",
|
|
19
|
+
"less",
|
|
20
|
+
"eless",
|
|
21
|
+
"equal",
|
|
22
|
+
"nequal",
|
|
23
|
+
"in_range",
|
|
24
|
+
"not_in_range",
|
|
25
|
+
"empty",
|
|
26
|
+
"nempty",
|
|
27
|
+
"crosses",
|
|
28
|
+
"crosses_above",
|
|
29
|
+
"crosses_below",
|
|
30
|
+
"match",
|
|
31
|
+
"nmatch",
|
|
32
|
+
"smatch",
|
|
33
|
+
"has",
|
|
34
|
+
"has_none_of",
|
|
35
|
+
"above%",
|
|
36
|
+
"below%",
|
|
37
|
+
"in_range%",
|
|
38
|
+
"not_in_range%",
|
|
39
|
+
"in_day_range",
|
|
40
|
+
"in_week_range",
|
|
41
|
+
"in_month_range",
|
|
42
|
+
]
|
|
43
|
+
right: Any
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SortByDict(TypedDict, total=False):
|
|
47
|
+
"""Sort configuration."""
|
|
48
|
+
|
|
49
|
+
sortBy: str
|
|
50
|
+
sortOrder: Literal["asc", "desc"]
|
|
51
|
+
nullsFirst: bool
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SymbolsDict(TypedDict, total=False):
|
|
55
|
+
"""Symbols configuration for queries."""
|
|
56
|
+
|
|
57
|
+
query: dict[Literal["types"], list[str]]
|
|
58
|
+
tickers: list[str]
|
|
59
|
+
symbolset: list[str]
|
|
60
|
+
watchlist: dict[Literal["id"], int]
|
|
61
|
+
groups: list[dict[Literal["type", "values"], str]]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ExpressionDict(TypedDict):
|
|
65
|
+
"""Single expression wrapper."""
|
|
66
|
+
|
|
67
|
+
expression: FilterOperationDict
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class OperationComparisonDict(TypedDict):
|
|
71
|
+
"""AND/OR operation for complex filters."""
|
|
72
|
+
|
|
73
|
+
operator: Literal["and", "or"]
|
|
74
|
+
operands: list[Any] # Can be OperationDict or ExpressionDict
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class OperationDict(TypedDict):
|
|
78
|
+
"""Operation wrapper for complex filters."""
|
|
79
|
+
|
|
80
|
+
operation: OperationComparisonDict
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class QueryDict(TypedDict, total=False):
|
|
84
|
+
"""Complete query structure for TradingView API."""
|
|
85
|
+
|
|
86
|
+
markets: list[str]
|
|
87
|
+
symbols: SymbolsDict
|
|
88
|
+
options: dict[str, Any]
|
|
89
|
+
columns: list[str]
|
|
90
|
+
filter: list[FilterOperationDict]
|
|
91
|
+
filter2: OperationComparisonDict
|
|
92
|
+
sort: SortByDict
|
|
93
|
+
range: list[int]
|
|
94
|
+
ignore_unknown_fields: bool
|
|
95
|
+
preset: str
|
|
96
|
+
price_conversion: dict[str, Any]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ScreenerRowDict(TypedDict):
|
|
100
|
+
"""Single row in screener response."""
|
|
101
|
+
|
|
102
|
+
s: str # symbol (NASDAQ:AAPL)
|
|
103
|
+
d: list[Any] # data values
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ScreenerDict(TypedDict):
|
|
107
|
+
"""Screener API response structure."""
|
|
108
|
+
|
|
109
|
+
totalCount: int
|
|
110
|
+
data: list[ScreenerRowDict]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class IndicatorMap(TypedDict, total=False):
|
|
114
|
+
"""Common technical indicators."""
|
|
115
|
+
|
|
116
|
+
open: float | None
|
|
117
|
+
high: float | None
|
|
118
|
+
low: float | None
|
|
119
|
+
close: float | None
|
|
120
|
+
volume: float | None
|
|
121
|
+
change: float | None
|
|
122
|
+
SMA20: float | None
|
|
123
|
+
SMA50: float | None
|
|
124
|
+
SMA200: float | None
|
|
125
|
+
EMA20: float | None
|
|
126
|
+
EMA50: float | None
|
|
127
|
+
EMA200: float | None
|
|
128
|
+
RSI: float | None
|
|
129
|
+
RSI7: float | None
|
|
130
|
+
MACD_macd: float | None
|
|
131
|
+
MACD_signal: float | None
|
|
132
|
+
BB_upper: float | None
|
|
133
|
+
BB_lower: float | None
|
|
134
|
+
ADX: float | None
|
|
135
|
+
ATR: float | None
|
|
136
|
+
Stoch_K: float | None
|
|
137
|
+
Stoch_D: float | None
|
|
138
|
+
VWAP: float | None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class AnalysisResult(TypedDict, total=False):
|
|
142
|
+
"""Complete analysis result for a symbol."""
|
|
143
|
+
|
|
144
|
+
symbol: str
|
|
145
|
+
exchange: str
|
|
146
|
+
timeframe: str
|
|
147
|
+
timestamp: str
|
|
148
|
+
price: float
|
|
149
|
+
change_percent: float
|
|
150
|
+
indicators: IndicatorMap
|
|
151
|
+
bollinger: dict[str, Any]
|
|
152
|
+
signals: list[str]
|
|
153
|
+
rating: int
|
|
154
|
+
recommendation: str
|
tradingview_mcp/query.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Query class for building and executing TradingView screener queries.
|
|
3
|
+
|
|
4
|
+
Provides a SQL-like interface for market screening.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import pprint
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
from tradingview_mcp.column import Column
|
|
17
|
+
from tradingview_mcp.constants import (
|
|
18
|
+
COLUMNS,
|
|
19
|
+
DEFAULT_RANGE,
|
|
20
|
+
HEADERS,
|
|
21
|
+
MARKETS,
|
|
22
|
+
URL,
|
|
23
|
+
get_column_name,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from tradingview_mcp.models import (
|
|
28
|
+
FilterOperationDict,
|
|
29
|
+
OperationDict,
|
|
30
|
+
QueryDict,
|
|
31
|
+
SortByDict,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _impl_and_or_chaining(
|
|
36
|
+
expressions: tuple[FilterOperationDict | OperationDict, ...], operator: Literal["and", "or"]
|
|
37
|
+
) -> OperationDict:
|
|
38
|
+
"""Internal helper for AND/OR expression chaining."""
|
|
39
|
+
lst = []
|
|
40
|
+
for expr in expressions:
|
|
41
|
+
if "left" in expr: # FilterOperationDict
|
|
42
|
+
lst.append({"expression": expr})
|
|
43
|
+
else:
|
|
44
|
+
lst.append(expr)
|
|
45
|
+
return {"operation": {"operator": operator, "operands": lst}}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def And(*expressions: FilterOperationDict | OperationDict) -> OperationDict:
|
|
49
|
+
"""
|
|
50
|
+
Combine filter expressions with AND logic.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
>>> And(Column('close') > 10, Column('volume') > 1000000)
|
|
54
|
+
"""
|
|
55
|
+
return _impl_and_or_chaining(expressions, operator="and")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def Or(*expressions: FilterOperationDict | OperationDict) -> OperationDict:
|
|
59
|
+
"""
|
|
60
|
+
Combine filter expressions with OR logic.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
>>> Or(Column('type') == 'stock', Column('type') == 'fund')
|
|
64
|
+
"""
|
|
65
|
+
return _impl_and_or_chaining(expressions, operator="or")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Query:
|
|
69
|
+
"""
|
|
70
|
+
Build and execute TradingView screener queries.
|
|
71
|
+
|
|
72
|
+
Provides a SQL-like interface for market screening with support for:
|
|
73
|
+
- Column selection (SELECT)
|
|
74
|
+
- Filtering (WHERE)
|
|
75
|
+
- Sorting (ORDER BY)
|
|
76
|
+
- Pagination (OFFSET, LIMIT)
|
|
77
|
+
- Multiple markets
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
Basic query:
|
|
81
|
+
>>> Query().get_scanner_data()
|
|
82
|
+
|
|
83
|
+
Custom columns:
|
|
84
|
+
>>> Query().select('name', 'close', 'RSI', 'MACD.macd').get_scanner_data()
|
|
85
|
+
|
|
86
|
+
With filters:
|
|
87
|
+
>>> Query().select('name', 'close').where(Column('RSI') < 30).get_scanner_data()
|
|
88
|
+
|
|
89
|
+
Multiple conditions:
|
|
90
|
+
>>> Query().where(
|
|
91
|
+
... Column('close') > 10,
|
|
92
|
+
... Column('volume') > 1000000,
|
|
93
|
+
... Column('RSI').between(30, 70)
|
|
94
|
+
... ).get_scanner_data()
|
|
95
|
+
|
|
96
|
+
Crypto market:
|
|
97
|
+
>>> Query().set_markets('crypto').get_scanner_data()
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
"""Initialize a new Query with default settings."""
|
|
102
|
+
self.query: QueryDict = {
|
|
103
|
+
"markets": ["america"],
|
|
104
|
+
"symbols": {"query": {"types": []}, "tickers": []},
|
|
105
|
+
"options": {"lang": "en"},
|
|
106
|
+
"columns": ["name", "close", "volume", "market_cap_basic"],
|
|
107
|
+
"sort": {"sortBy": "Value.Traded", "sortOrder": "desc"},
|
|
108
|
+
"range": DEFAULT_RANGE.copy(),
|
|
109
|
+
}
|
|
110
|
+
self.url = "https://scanner.tradingview.com/america/scan"
|
|
111
|
+
|
|
112
|
+
def set_markets(self, *markets: str) -> "Query":
|
|
113
|
+
"""
|
|
114
|
+
Set the market(s) to query.
|
|
115
|
+
|
|
116
|
+
Available markets include:
|
|
117
|
+
- Crypto: 'crypto', 'coin'
|
|
118
|
+
- Countries: 'america', 'uk', 'germany', etc. (67 countries)
|
|
119
|
+
- Other: 'forex', 'futures', 'bonds', 'cfd', 'options'
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
>>> Query().set_markets('crypto')
|
|
123
|
+
>>> Query().set_markets('america', 'uk', 'germany')
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
markets: One or more market identifiers
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Self for method chaining
|
|
130
|
+
"""
|
|
131
|
+
if len(markets) == 1:
|
|
132
|
+
market = markets[0]
|
|
133
|
+
if market not in MARKETS:
|
|
134
|
+
raise ValueError(f"Unknown market: {market}. Available: {sorted(MARKETS)}")
|
|
135
|
+
self.url = URL.format(market=market)
|
|
136
|
+
self.query["markets"] = [market]
|
|
137
|
+
elif len(markets) > 1:
|
|
138
|
+
for m in markets:
|
|
139
|
+
if m not in MARKETS:
|
|
140
|
+
raise ValueError(f"Unknown market: {m}. Available: {sorted(MARKETS)}")
|
|
141
|
+
self.url = URL.format(market="global")
|
|
142
|
+
self.query["markets"] = list(markets)
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
def set_tickers(self, *tickers: str) -> "Query":
|
|
146
|
+
"""
|
|
147
|
+
Set specific tickers to query.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
tickers: One or more tickers in format 'EXCHANGE:SYMBOL'
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
>>> Query().set_tickers('NASDAQ:TSLA', 'NYSE:GME', 'BINANCE:BTCUSDT')
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Self for method chaining
|
|
157
|
+
"""
|
|
158
|
+
self.query.pop("markets", None)
|
|
159
|
+
self.query["symbols"] = {"tickers": list(tickers)}
|
|
160
|
+
self.url = "https://scanner.tradingview.com/global/scan"
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
def select(self, *columns: Column | str) -> "Query":
|
|
164
|
+
"""
|
|
165
|
+
Set columns to return in results.
|
|
166
|
+
|
|
167
|
+
Accepts both Column objects and string names.
|
|
168
|
+
Human-readable names are automatically converted to API names.
|
|
169
|
+
|
|
170
|
+
Examples:
|
|
171
|
+
>>> Query().select('name', 'close', 'RSI', 'MACD.macd')
|
|
172
|
+
>>> Query().select(Column('name'), 'close', 'Relative Strength Index (14)')
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
columns: Column objects or column names
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Self for method chaining
|
|
179
|
+
"""
|
|
180
|
+
self.query["columns"] = [
|
|
181
|
+
col.name if isinstance(col, Column) else get_column_name(col) for col in columns
|
|
182
|
+
]
|
|
183
|
+
return self
|
|
184
|
+
|
|
185
|
+
def where(self, *expressions: FilterOperationDict) -> "Query":
|
|
186
|
+
"""
|
|
187
|
+
Add filter conditions (combined with AND).
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
>>> Query().where(Column('RSI') < 30)
|
|
191
|
+
>>> Query().where(
|
|
192
|
+
... Column('close') > 10,
|
|
193
|
+
... Column('volume') > 1000000
|
|
194
|
+
... )
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
expressions: Filter expressions created by Column comparisons
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Self for method chaining
|
|
201
|
+
"""
|
|
202
|
+
self.query["filter"] = list(expressions)
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
def where2(self, operation: OperationDict) -> "Query":
|
|
206
|
+
"""
|
|
207
|
+
Add complex filter with AND/OR logic.
|
|
208
|
+
|
|
209
|
+
Use And() and Or() functions to build complex expressions.
|
|
210
|
+
|
|
211
|
+
Examples:
|
|
212
|
+
>>> Query().where2(
|
|
213
|
+
... Or(
|
|
214
|
+
... And(Column('type') == 'stock', Column('RSI') < 30),
|
|
215
|
+
... And(Column('type') == 'fund', Column('change') > 5)
|
|
216
|
+
... )
|
|
217
|
+
... )
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
operation: Operation created by And() or Or()
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Self for method chaining
|
|
224
|
+
"""
|
|
225
|
+
self.query["filter2"] = operation["operation"]
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
def order_by(self, column: Column | str, ascending: bool = True) -> "Query":
|
|
229
|
+
"""
|
|
230
|
+
Set sort order for results.
|
|
231
|
+
|
|
232
|
+
Examples:
|
|
233
|
+
>>> Query().order_by('volume', ascending=False) # Highest volume first
|
|
234
|
+
>>> Query().order_by('RSI', ascending=True) # Lowest RSI first
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
column: Column to sort by
|
|
238
|
+
ascending: True for ascending, False for descending
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Self for method chaining
|
|
242
|
+
"""
|
|
243
|
+
column_name = column.name if isinstance(column, Column) else get_column_name(column)
|
|
244
|
+
sort_order: Literal["asc", "desc"] = "asc" if ascending else "desc"
|
|
245
|
+
self.query["sort"] = {"sortBy": column_name, "sortOrder": sort_order}
|
|
246
|
+
return self
|
|
247
|
+
|
|
248
|
+
def offset(self, offset: int) -> "Query":
|
|
249
|
+
"""
|
|
250
|
+
Set starting position for results (for pagination).
|
|
251
|
+
|
|
252
|
+
Examples:
|
|
253
|
+
>>> Query().offset(50).limit(50) # Get results 50-100
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
offset: Starting index (0-based)
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Self for method chaining
|
|
260
|
+
"""
|
|
261
|
+
self.query["range"][0] = offset
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
def limit(self, limit: int) -> "Query":
|
|
265
|
+
"""
|
|
266
|
+
Set maximum number of results to return.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
>>> Query().limit(100)
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
limit: Maximum results (default 50)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Self for method chaining
|
|
276
|
+
"""
|
|
277
|
+
self.query["range"][1] = limit
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def get_scanner_data(
|
|
281
|
+
self, *, timeout: int = 20, **kwargs: Any
|
|
282
|
+
) -> tuple[int, pd.DataFrame]:
|
|
283
|
+
"""
|
|
284
|
+
Execute the query and return results.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
timeout: Request timeout in seconds
|
|
288
|
+
**kwargs: Additional arguments passed to requests.post()
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Tuple of (total_count, DataFrame)
|
|
292
|
+
- total_count: Total matching records (may be more than returned)
|
|
293
|
+
- DataFrame: Results with columns as specified in select()
|
|
294
|
+
|
|
295
|
+
Raises:
|
|
296
|
+
requests.HTTPError: If the API request fails
|
|
297
|
+
"""
|
|
298
|
+
kwargs.setdefault("headers", HEADERS)
|
|
299
|
+
kwargs.setdefault("timeout", timeout)
|
|
300
|
+
|
|
301
|
+
r = requests.post(self.url, json=self.query, **kwargs)
|
|
302
|
+
|
|
303
|
+
if r.status_code >= 400:
|
|
304
|
+
r.reason += f"\nBody: {r.text}\n"
|
|
305
|
+
r.raise_for_status()
|
|
306
|
+
|
|
307
|
+
json_obj = r.json()
|
|
308
|
+
rows_count = json_obj.get("totalCount", 0)
|
|
309
|
+
data = json_obj.get("data", [])
|
|
310
|
+
|
|
311
|
+
df = pd.DataFrame(
|
|
312
|
+
data=([row["s"], *row["d"]] for row in data),
|
|
313
|
+
columns=["ticker", *self.query.get("columns", [])],
|
|
314
|
+
)
|
|
315
|
+
return rows_count, df
|
|
316
|
+
|
|
317
|
+
def get_scanner_data_raw(self, *, timeout: int = 20, **kwargs: Any) -> dict[str, Any]:
|
|
318
|
+
"""
|
|
319
|
+
Execute the query and return raw JSON response.
|
|
320
|
+
|
|
321
|
+
Useful when you don't need DataFrame conversion.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Raw JSON response dict
|
|
325
|
+
"""
|
|
326
|
+
kwargs.setdefault("headers", HEADERS)
|
|
327
|
+
kwargs.setdefault("timeout", timeout)
|
|
328
|
+
|
|
329
|
+
r = requests.post(self.url, json=self.query, **kwargs)
|
|
330
|
+
r.raise_for_status()
|
|
331
|
+
return r.json()
|
|
332
|
+
|
|
333
|
+
def copy(self) -> "Query":
|
|
334
|
+
"""Create a deep copy of this query."""
|
|
335
|
+
new = Query()
|
|
336
|
+
new.query = copy.deepcopy(self.query)
|
|
337
|
+
new.url = self.url
|
|
338
|
+
return new
|
|
339
|
+
|
|
340
|
+
def __repr__(self) -> str:
|
|
341
|
+
return f"<Query {pprint.pformat(self.query)}>"
|
|
342
|
+
|
|
343
|
+
def __eq__(self, other: object) -> bool:
|
|
344
|
+
return isinstance(other, Query) and self.query == other.query
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def get_all_symbols(market: str = "america") -> list[str]:
|
|
348
|
+
"""
|
|
349
|
+
Get all symbols for a given market.
|
|
350
|
+
|
|
351
|
+
Examples:
|
|
352
|
+
>>> get_all_symbols('america')
|
|
353
|
+
['NASDAQ:AAPL', 'NYSE:GME', ...]
|
|
354
|
+
|
|
355
|
+
>>> get_all_symbols('crypto')
|
|
356
|
+
['BINANCE:BTCUSDT', 'COINBASE:ETHUSD', ...]
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
market: Market identifier (default 'america')
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
List of ticker strings in 'EXCHANGE:SYMBOL' format
|
|
363
|
+
"""
|
|
364
|
+
r = requests.get(URL.format(market=market), headers=HEADERS, timeout=20)
|
|
365
|
+
r.raise_for_status()
|
|
366
|
+
data = r.json().get("data", [])
|
|
367
|
+
return [dct["s"] for dct in data]
|