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.
@@ -0,0 +1,14 @@
1
+ """
2
+ TradingView MCP Server - A comprehensive MCP server for TradingView market screening.
3
+
4
+ This package provides:
5
+ - Direct TradingView API integration for market screening
6
+ - SQL-like query interface for building complex filters
7
+ - MCP tools for market analysis and technical indicators
8
+ - Support for 76+ markets including crypto, stocks, forex, and futures
9
+ """
10
+
11
+ from tradingview_mcp.server import mcp, main
12
+
13
+ __version__ = "1.0.0"
14
+ __all__ = ["mcp", "main"]
@@ -0,0 +1,231 @@
1
+ """
2
+ Column class for building filter expressions.
3
+
4
+ Provides a SQL-like interface for creating WHERE clauses.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Iterable, Optional
10
+
11
+ if TYPE_CHECKING:
12
+ from tradingview_mcp.models import FilterOperationDict
13
+
14
+
15
+ class Column:
16
+ """
17
+ A Column object represents a field in the TradingView stock screener.
18
+
19
+ Used in SELECT queries and WHERE queries with the Query object.
20
+ Supports all comparison operations: <, <=, >, >=, ==, !=
21
+ Plus methods like between(), isin(), crosses(), etc.
22
+
23
+ Examples:
24
+ >>> Column('close') > 2.5
25
+ >>> Column('close').between(2.5, 15)
26
+ >>> Column('high') > Column('VWAP')
27
+ >>> Column('type').isin(['stock', 'fund'])
28
+ >>> Column('description').like('apple')
29
+ >>> Column('RSI') < 30
30
+ """
31
+
32
+ def __init__(self, name: str) -> None:
33
+ """
34
+ Create a Column object from a column name.
35
+
36
+ Args:
37
+ name: The column/field name (e.g., 'close', 'RSI', 'volume')
38
+ """
39
+ self.name = name
40
+
41
+ @staticmethod
42
+ def _extract_name(obj: "Column | str | int | float") -> str | int | float:
43
+ """Extract the name from a Column or return the value as-is."""
44
+ if isinstance(obj, Column):
45
+ return obj.name
46
+ return obj
47
+
48
+ def __gt__(self, other: "Column | str | int | float") -> "FilterOperationDict":
49
+ """Greater than comparison."""
50
+ return {"left": self.name, "operation": "greater", "right": self._extract_name(other)}
51
+
52
+ def __ge__(self, other: "Column | str | int | float") -> "FilterOperationDict":
53
+ """Greater than or equal comparison."""
54
+ return {"left": self.name, "operation": "egreater", "right": self._extract_name(other)}
55
+
56
+ def __lt__(self, other: "Column | str | int | float") -> "FilterOperationDict":
57
+ """Less than comparison."""
58
+ return {"left": self.name, "operation": "less", "right": self._extract_name(other)}
59
+
60
+ def __le__(self, other: "Column | str | int | float") -> "FilterOperationDict":
61
+ """Less than or equal comparison."""
62
+ return {"left": self.name, "operation": "eless", "right": self._extract_name(other)}
63
+
64
+ def __eq__(self, other: "Column | str | int | float") -> "FilterOperationDict": # type: ignore[override]
65
+ """Equal comparison."""
66
+ return {"left": self.name, "operation": "equal", "right": self._extract_name(other)}
67
+
68
+ def __ne__(self, other: "Column | str | int | float") -> "FilterOperationDict": # type: ignore[override]
69
+ """Not equal comparison."""
70
+ return {"left": self.name, "operation": "nequal", "right": self._extract_name(other)}
71
+
72
+ def crosses(self, other: "Column | str") -> "FilterOperationDict":
73
+ """Check if column crosses another column/value."""
74
+ return {"left": self.name, "operation": "crosses", "right": self._extract_name(other)}
75
+
76
+ def crosses_above(self, other: "Column | str") -> "FilterOperationDict":
77
+ """Check if column crosses above another column/value."""
78
+ return {"left": self.name, "operation": "crosses_above", "right": self._extract_name(other)}
79
+
80
+ def crosses_below(self, other: "Column | str") -> "FilterOperationDict":
81
+ """Check if column crosses below another column/value."""
82
+ return {"left": self.name, "operation": "crosses_below", "right": self._extract_name(other)}
83
+
84
+ def between(
85
+ self, left: "Column | str | int | float", right: "Column | str | int | float"
86
+ ) -> "FilterOperationDict":
87
+ """
88
+ Check if value is between two bounds (inclusive).
89
+
90
+ Examples:
91
+ >>> Column('close').between(10, 50)
92
+ >>> Column('close').between(Column('EMA5'), Column('EMA20'))
93
+ """
94
+ return {
95
+ "left": self.name,
96
+ "operation": "in_range",
97
+ "right": [self._extract_name(left), self._extract_name(right)],
98
+ }
99
+
100
+ def not_between(
101
+ self, left: "Column | str | int | float", right: "Column | str | int | float"
102
+ ) -> "FilterOperationDict":
103
+ """Check if value is NOT between two bounds."""
104
+ return {
105
+ "left": self.name,
106
+ "operation": "not_in_range",
107
+ "right": [self._extract_name(left), self._extract_name(right)],
108
+ }
109
+
110
+ def isin(self, values: Iterable) -> "FilterOperationDict":
111
+ """
112
+ Check if value is in a list of values.
113
+
114
+ Examples:
115
+ >>> Column('type').isin(['stock', 'fund'])
116
+ >>> Column('exchange').isin(['NASDAQ', 'NYSE'])
117
+ """
118
+ return {"left": self.name, "operation": "in_range", "right": list(values)}
119
+
120
+ def not_in(self, values: Iterable) -> "FilterOperationDict":
121
+ """Check if value is NOT in a list of values."""
122
+ return {"left": self.name, "operation": "not_in_range", "right": list(values)}
123
+
124
+ def has(self, values: str | list[str]) -> "FilterOperationDict":
125
+ """
126
+ Check if field contains any of the values (for set-type fields).
127
+
128
+ Examples:
129
+ >>> Column('typespecs').has(['common', 'preferred'])
130
+ """
131
+ return {"left": self.name, "operation": "has", "right": values}
132
+
133
+ def has_none_of(self, values: str | list[str]) -> "FilterOperationDict":
134
+ """Check if field contains NONE of the values (for set-type fields)."""
135
+ return {"left": self.name, "operation": "has_none_of", "right": values}
136
+
137
+ def in_day_range(self, a: int, b: int) -> "FilterOperationDict":
138
+ """Check if date is within day range (0 = today)."""
139
+ return {"left": self.name, "operation": "in_day_range", "right": [a, b]}
140
+
141
+ def in_week_range(self, a: int, b: int) -> "FilterOperationDict":
142
+ """Check if date is within week range."""
143
+ return {"left": self.name, "operation": "in_week_range", "right": [a, b]}
144
+
145
+ def in_month_range(self, a: int, b: int) -> "FilterOperationDict":
146
+ """Check if date is within month range."""
147
+ return {"left": self.name, "operation": "in_month_range", "right": [a, b]}
148
+
149
+ def above_pct(self, column: "Column | str", pct: float) -> "FilterOperationDict":
150
+ """
151
+ Check if value is above another column by a percentage.
152
+
153
+ Args:
154
+ column: Reference column
155
+ pct: Percentage multiplier (1.03 = 3% above)
156
+
157
+ Examples:
158
+ >>> Column('close').above_pct('VWAP', 1.03) # 3% above VWAP
159
+ >>> Column('close').above_pct('price_52_week_low', 2.5) # 150% above 52w low
160
+ """
161
+ return {
162
+ "left": self.name,
163
+ "operation": "above%",
164
+ "right": [self._extract_name(column), pct],
165
+ }
166
+
167
+ def below_pct(self, column: "Column | str", pct: float) -> "FilterOperationDict":
168
+ """
169
+ Check if value is below another column by a percentage.
170
+
171
+ Examples:
172
+ >>> Column('close').below_pct('VWAP', 1.03) # 3% below VWAP
173
+ """
174
+ return {
175
+ "left": self.name,
176
+ "operation": "below%",
177
+ "right": [self._extract_name(column), pct],
178
+ }
179
+
180
+ def between_pct(
181
+ self, column: "Column | str", pct1: float, pct2: Optional[float] = None
182
+ ) -> "FilterOperationDict":
183
+ """
184
+ Check if percentage change relative to column is within range.
185
+
186
+ Examples:
187
+ >>> Column('close').between_pct('EMA200', 1.2, 1.5) # 20-50% above EMA200
188
+ """
189
+ return {
190
+ "left": self.name,
191
+ "operation": "in_range%",
192
+ "right": [self._extract_name(column), pct1, pct2],
193
+ }
194
+
195
+ def not_between_pct(
196
+ self, column: "Column | str", pct1: float, pct2: Optional[float] = None
197
+ ) -> "FilterOperationDict":
198
+ """Check if percentage change relative to column is NOT within range."""
199
+ return {
200
+ "left": self.name,
201
+ "operation": "not_in_range%",
202
+ "right": [self._extract_name(column), pct1, pct2],
203
+ }
204
+
205
+ def like(self, pattern: str) -> "FilterOperationDict":
206
+ """
207
+ Pattern matching (case-insensitive LIKE '%pattern%').
208
+
209
+ Examples:
210
+ >>> Column('description').like('apple')
211
+ """
212
+ return {"left": self.name, "operation": "match", "right": pattern}
213
+
214
+ def not_like(self, pattern: str) -> "FilterOperationDict":
215
+ """Negative pattern matching."""
216
+ return {"left": self.name, "operation": "nmatch", "right": pattern}
217
+
218
+ def empty(self) -> "FilterOperationDict":
219
+ """Check if field is empty/null."""
220
+ return {"left": self.name, "operation": "empty", "right": None}
221
+
222
+ def not_empty(self) -> "FilterOperationDict":
223
+ """Check if field is NOT empty/null."""
224
+ return {"left": self.name, "operation": "nempty", "right": None}
225
+
226
+ def __repr__(self) -> str:
227
+ return f"Column({self.name!r})"
228
+
229
+
230
+ # Convenience alias
231
+ col = Column
@@ -0,0 +1,425 @@
1
+ """
2
+ Constants for TradingView Screener integration.
3
+
4
+ Contains API URLs, headers, supported markets, and column definitions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ # API Configuration
10
+ URL = "https://scanner.tradingview.com/{market}/scan"
11
+
12
+ HEADERS = {
13
+ "authority": "scanner.tradingview.com",
14
+ "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"',
15
+ "accept": "text/plain, */*; q=0.01",
16
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
17
+ "sec-ch-ua-mobile": "?0",
18
+ "user-agent": (
19
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
20
+ "(KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
21
+ ),
22
+ "sec-ch-ua-platform": '"Windows"',
23
+ "origin": "https://www.tradingview.com",
24
+ "sec-fetch-site": "same-site",
25
+ "sec-fetch-mode": "cors",
26
+ "sec-fetch-dest": "empty",
27
+ "referer": "https://www.tradingview.com/",
28
+ "accept-language": "en-US,en;q=0.9",
29
+ }
30
+
31
+ DEFAULT_RANGE = [0, 50]
32
+
33
+ # Supported Markets (76 total)
34
+ MARKETS = {
35
+ # Financial Instruments
36
+ "bonds",
37
+ "cfd",
38
+ "coin",
39
+ "crypto",
40
+ "economics2",
41
+ "forex",
42
+ "futures",
43
+ "options",
44
+ # Countries (67)
45
+ "america",
46
+ "argentina",
47
+ "australia",
48
+ "austria",
49
+ "bahrain",
50
+ "bangladesh",
51
+ "belgium",
52
+ "brazil",
53
+ "canada",
54
+ "chile",
55
+ "china",
56
+ "colombia",
57
+ "cyprus",
58
+ "czech",
59
+ "denmark",
60
+ "egypt",
61
+ "estonia",
62
+ "finland",
63
+ "france",
64
+ "germany",
65
+ "greece",
66
+ "hongkong",
67
+ "hungary",
68
+ "iceland",
69
+ "india",
70
+ "indonesia",
71
+ "israel",
72
+ "italy",
73
+ "japan",
74
+ "kenya",
75
+ "korea",
76
+ "ksa",
77
+ "kuwait",
78
+ "latvia",
79
+ "lithuania",
80
+ "luxembourg",
81
+ "malaysia",
82
+ "mexico",
83
+ "morocco",
84
+ "netherlands",
85
+ "newzealand",
86
+ "nigeria",
87
+ "norway",
88
+ "pakistan",
89
+ "peru",
90
+ "philippines",
91
+ "poland",
92
+ "portugal",
93
+ "qatar",
94
+ "romania",
95
+ "rsa",
96
+ "russia",
97
+ "serbia",
98
+ "singapore",
99
+ "slovakia",
100
+ "spain",
101
+ "srilanka",
102
+ "sweden",
103
+ "switzerland",
104
+ "taiwan",
105
+ "thailand",
106
+ "tunisia",
107
+ "turkey",
108
+ "uae",
109
+ "uk",
110
+ "venezuela",
111
+ "vietnam",
112
+ }
113
+
114
+ # Exchange to Screener Mapping
115
+ EXCHANGE_SCREENER = {
116
+ # Crypto Exchanges
117
+ "all": "crypto",
118
+ "huobi": "crypto",
119
+ "kucoin": "crypto",
120
+ "coinbase": "crypto",
121
+ "gateio": "crypto",
122
+ "binance": "crypto",
123
+ "bitfinex": "crypto",
124
+ "bitget": "crypto",
125
+ "bybit": "crypto",
126
+ "okx": "crypto",
127
+ "kraken": "crypto",
128
+ "bitstamp": "crypto",
129
+ "gemini": "crypto",
130
+ "poloniex": "crypto",
131
+ # Stock Exchanges
132
+ "bist": "turkey",
133
+ "nasdaq": "america",
134
+ "nyse": "america",
135
+ "amex": "america",
136
+ # Malaysia
137
+ "bursa": "malaysia",
138
+ "myx": "malaysia",
139
+ "klse": "malaysia",
140
+ "ace": "malaysia",
141
+ "leap": "malaysia",
142
+ # Hong Kong
143
+ "hkex": "hongkong",
144
+ "hk": "hongkong",
145
+ "hsi": "hongkong",
146
+ # Others
147
+ "lse": "uk",
148
+ "tsx": "canada",
149
+ "asx": "australia",
150
+ "nse": "india",
151
+ "bse": "india",
152
+ "sse": "china",
153
+ "szse": "china",
154
+ "tse": "japan",
155
+ "krx": "korea",
156
+ }
157
+
158
+ # Allowed Timeframes
159
+ ALLOWED_TIMEFRAMES = {"1m", "5m", "15m", "30m", "1h", "2h", "4h", "1D", "1W", "1M"}
160
+
161
+ # Timeframe to TradingView Resolution
162
+ TIMEFRAME_MAP = {
163
+ "1m": "1",
164
+ "5m": "5",
165
+ "15m": "15",
166
+ "30m": "30",
167
+ "1h": "60",
168
+ "2h": "120",
169
+ "4h": "240",
170
+ "1D": "1D",
171
+ "1W": "1W",
172
+ "1M": "1M",
173
+ }
174
+
175
+ # Column Definitions - Human readable names to API column names
176
+ COLUMNS = {
177
+ # Technical Indicators
178
+ "Average Day Range (14)": "ADR",
179
+ "Average Directional Index (14)": "ADX",
180
+ "Positive Directional Indicator (14)": "ADX+DI",
181
+ "Negative Directional Indicator (14)": "ADX-DI",
182
+ "Awesome Oscillator": "AO",
183
+ "Average True Range (14)": "ATR",
184
+ "Aroon Down (14)": "Aroon.Down",
185
+ "Aroon Up (14)": "Aroon.Up",
186
+ "Bollinger Lower Band (20)": "BB.lower",
187
+ "Bollinger Upper Band (20)": "BB.upper",
188
+ "Bull Bear Power": "BBPower",
189
+ "Commodity Channel Index (20)": "CCI20",
190
+ "Chaikin Money Flow (20)": "ChaikinMoneyFlow",
191
+ "Donchian Channels Lower Band (20)": "DonchCh20.Lower",
192
+ "Donchian Channels Upper Band (20)": "DonchCh20.Upper",
193
+ # EMAs
194
+ "Exponential Moving Average (5)": "EMA5",
195
+ "Exponential Moving Average (10)": "EMA10",
196
+ "Exponential Moving Average (20)": "EMA20",
197
+ "Exponential Moving Average (30)": "EMA30",
198
+ "Exponential Moving Average (50)": "EMA50",
199
+ "Exponential Moving Average (100)": "EMA100",
200
+ "Exponential Moving Average (200)": "EMA200",
201
+ # Highs/Lows
202
+ "1-Month High": "High.1M",
203
+ "3-Month High": "High.3M",
204
+ "6-Month High": "High.6M",
205
+ "All Time High": "High.All",
206
+ "1-Month Low": "Low.1M",
207
+ "3-Month Low": "Low.3M",
208
+ "6-Month Low": "Low.6M",
209
+ "All Time Low": "Low.All",
210
+ # Hull MA
211
+ "Hull Moving Average (9)": "HullMA9",
212
+ # Ichimoku
213
+ "Ichimoku Base Line (9, 26, 52, 26)": "Ichimoku.BLine",
214
+ "Ichimoku Conversion Line (9, 26, 52, 26)": "Ichimoku.CLine",
215
+ "Ichimoku Leading Span A (9, 26, 52, 26)": "Ichimoku.Lead1",
216
+ "Ichimoku Leading Span B (9, 26, 52, 26)": "Ichimoku.Lead2",
217
+ # Keltner Channels
218
+ "Keltner Channels Lower Band (20)": "KltChnl.lower",
219
+ "Keltner Channels Upper Band (20)": "KltChnl.upper",
220
+ # MACD
221
+ "MACD Level (12, 26)": "MACD.macd",
222
+ "MACD Signal (12, 26)": "MACD.signal",
223
+ # Other Indicators
224
+ "Momentum (10)": "Mom",
225
+ "Money Flow (14)": "MoneyFlow",
226
+ "Parabolic SAR": "P.SAR",
227
+ # Performance
228
+ "Monthly Performance": "Perf.1M",
229
+ "3-Month Performance": "Perf.3M",
230
+ "5Y Performance": "Perf.5Y",
231
+ "6-Month Performance": "Perf.6M",
232
+ "All Time Performance": "Perf.All",
233
+ "Weekly Performance": "Perf.W",
234
+ "Yearly Performance": "Perf.Y",
235
+ "YTD Performance": "Perf.YTD",
236
+ # Pivot Points (Classic)
237
+ "Pivot Classic P": "Pivot.M.Classic.Middle",
238
+ "Pivot Classic R1": "Pivot.M.Classic.R1",
239
+ "Pivot Classic R2": "Pivot.M.Classic.R2",
240
+ "Pivot Classic R3": "Pivot.M.Classic.R3",
241
+ "Pivot Classic S1": "Pivot.M.Classic.S1",
242
+ "Pivot Classic S2": "Pivot.M.Classic.S2",
243
+ "Pivot Classic S3": "Pivot.M.Classic.S3",
244
+ # Pivot Points (Fibonacci)
245
+ "Pivot Fibonacci P": "Pivot.M.Fibonacci.Middle",
246
+ "Pivot Fibonacci R1": "Pivot.M.Fibonacci.R1",
247
+ "Pivot Fibonacci R2": "Pivot.M.Fibonacci.R2",
248
+ "Pivot Fibonacci R3": "Pivot.M.Fibonacci.R3",
249
+ "Pivot Fibonacci S1": "Pivot.M.Fibonacci.S1",
250
+ "Pivot Fibonacci S2": "Pivot.M.Fibonacci.S2",
251
+ "Pivot Fibonacci S3": "Pivot.M.Fibonacci.S3",
252
+ # Rate of Change
253
+ "Rate Of Change (9)": "ROC",
254
+ # RSI
255
+ "Relative Strength Index (14)": "RSI",
256
+ "Relative Strength Index (7)": "RSI7",
257
+ # Recommendations
258
+ "Technical Rating": "Recommend.All",
259
+ "Moving Averages Rating": "Recommend.MA",
260
+ "Oscillators Rating": "Recommend.Other",
261
+ # SMAs
262
+ "Simple Moving Average (5)": "SMA5",
263
+ "Simple Moving Average (10)": "SMA10",
264
+ "Simple Moving Average (20)": "SMA20",
265
+ "Simple Moving Average (30)": "SMA30",
266
+ "Simple Moving Average (50)": "SMA50",
267
+ "Simple Moving Average (100)": "SMA100",
268
+ "Simple Moving Average (200)": "SMA200",
269
+ # Stochastic
270
+ "Stochastic %D (14, 3, 3)": "Stoch.D",
271
+ "Stochastic %K (14, 3, 3)": "Stoch.K",
272
+ "Stochastic RSI Slow (3, 3, 14, 14)": "Stoch.RSI.D",
273
+ "Stochastic RSI Fast (3, 3, 14, 14)": "Stoch.RSI.K",
274
+ # Other
275
+ "Ultimate Oscillator (7, 14, 28)": "UO",
276
+ "Volume Weighted Average Price": "VWAP",
277
+ "Volume Weighted Moving Average (20)": "VWMA",
278
+ "Volume*Price": "Value.Traded",
279
+ "Volatility": "Volatility.D",
280
+ "Volatility Month": "Volatility.M",
281
+ "Volatility Week": "Volatility.W",
282
+ "Williams Percent Range (14)": "W.R",
283
+ # Fundamentals
284
+ "Net Margin (TTM)": "after_tax_margin",
285
+ "Average Volume (10 day)": "average_volume_10d_calc",
286
+ "Average Volume (30 day)": "average_volume_30d_calc",
287
+ "Average Volume (60 day)": "average_volume_60d_calc",
288
+ "Average Volume (90 day)": "average_volume_90d_calc",
289
+ "Basic EPS (FY)": "basic_eps_net_income",
290
+ "1-Year Beta": "beta_1_year",
291
+ "Cash & Equivalents (MRQ)": "cash_n_equivalents_fq",
292
+ "Cash & Equivalents (FY)": "cash_n_equivalents_fy",
293
+ # Price/Change
294
+ "Change %": "change",
295
+ "Change": "change_abs",
296
+ "Change from Open %": "change_from_open",
297
+ "Change from Open": "change_from_open_abs",
298
+ "Price": "close",
299
+ "Country": "country",
300
+ "Currency": "currency",
301
+ # Debt
302
+ "Current Ratio (MRQ)": "current_ratio",
303
+ "Debt to Equity Ratio (MRQ)": "debt_to_equity",
304
+ # Dividends
305
+ "Dividend Yield Forward": "dividend_yield_recent",
306
+ "Dividends Paid (FY)": "dividends_paid",
307
+ "Dividends per Share (MRQ)": "dividends_per_share_fq",
308
+ # EPS
309
+ "Basic EPS (TTM)": "earnings_per_share_basic_ttm",
310
+ "EPS Diluted (TTM)": "earnings_per_share_diluted_ttm",
311
+ "EPS Forecast (MRQ)": "earnings_per_share_forecast_next_fq",
312
+ "EPS Diluted (MRQ)": "earnings_per_share_fq",
313
+ # Earnings
314
+ "Recent Earnings Date": "earnings_release_date",
315
+ "Upcoming Earnings Date": "earnings_release_next_date",
316
+ "EBITDA (TTM)": "ebitda",
317
+ # Enterprise Value
318
+ "Enterprise Value/EBITDA (TTM)": "enterprise_value_ebitda_ttm",
319
+ "Enterprise Value (MRQ)": "enterprise_value_fq",
320
+ # Basic Info
321
+ "Exchange": "exchange",
322
+ "Shares Float": "float_shares_outstanding",
323
+ # Gap
324
+ "Gap %": "gap",
325
+ # Margins
326
+ "Gross Margin (TTM)": "gross_margin",
327
+ "Gross Profit (FY)": "gross_profit",
328
+ # OHLCV
329
+ "High": "high",
330
+ "Low": "low",
331
+ "Open": "open",
332
+ "Volume": "volume",
333
+ # Identification
334
+ "Industry": "industry",
335
+ "Sector": "sector",
336
+ "Market": "market",
337
+ "Name": "name",
338
+ "Description": "description",
339
+ "Type": "type",
340
+ # Market Cap
341
+ "Market Capitalization": "market_cap_basic",
342
+ # Net Income
343
+ "Net Debt (MRQ)": "net_debt",
344
+ "Net Income (FY)": "net_income",
345
+ # P/E Ratios
346
+ "Price to Earnings Ratio (TTM)": "price_earnings_ttm",
347
+ "Price to Book (MRQ)": "price_book_fq",
348
+ "Price to Sales (FY)": "price_sales_ratio",
349
+ "Price to Free Cash Flow (TTM)": "price_free_cash_flow_ttm",
350
+ # 52 Week
351
+ "52 Week High": "price_52_week_high",
352
+ "52 Week Low": "price_52_week_low",
353
+ # Pre/Post Market
354
+ "Pre-market Change %": "premarket_change",
355
+ "Pre-market Change": "premarket_change_abs",
356
+ "Pre-market Close": "premarket_close",
357
+ "Pre-market High": "premarket_high",
358
+ "Pre-market Low": "premarket_low",
359
+ "Pre-market Open": "premarket_open",
360
+ "Pre-market Volume": "premarket_volume",
361
+ "Pre-market Gap %": "premarket_gap",
362
+ "Post-market Change %": "postmarket_change",
363
+ "Post-market Change": "postmarket_change_abs",
364
+ "Post-market Close": "postmarket_close",
365
+ "Post-market High": "postmarket_high",
366
+ "Post-market Low": "postmarket_low",
367
+ "Post-market Open": "postmarket_open",
368
+ "Post-market Volume": "postmarket_volume",
369
+ # Relative Volume
370
+ "Relative Volume": "relative_volume_10d_calc",
371
+ "Relative Volume at Time": "relative_volume_intraday|5",
372
+ # Returns
373
+ "Return on Assets (TTM)": "return_on_assets",
374
+ "Return on Equity (TTM)": "return_on_equity",
375
+ "Return on Invested Capital (TTM)": "return_on_invested_capital",
376
+ # Revenue
377
+ "Total Revenue (FY)": "total_revenue",
378
+ "Revenue per Employee (FY)": "revenue_per_employee",
379
+ # Shares
380
+ "Total Shares Outstanding": "total_shares_outstanding_fundamental",
381
+ # Assets
382
+ "Total Assets (MRQ)": "total_assets",
383
+ "Total Current Assets (MRQ)": "total_current_assets",
384
+ "Total Debt (MRQ)": "total_debt",
385
+ "Total Liabilities (MRQ)": "total_liabilities_fq",
386
+ # Crypto specific
387
+ "24h Volume": "24h_vol|5",
388
+ "24h Change %": "24h_close_change|5",
389
+ "24h Volume Change %": "24h_vol_change|5",
390
+ "24h Volume to Market Cap": "24h_vol_to_market_cap",
391
+ # Quick ratio
392
+ "Quick Ratio (MRQ)": "quick_ratio",
393
+ }
394
+
395
+ # Default columns for different query types
396
+ DEFAULT_COLUMNS = ["name", "close", "volume", "market_cap_basic"]
397
+
398
+ PREMARKET_COLUMNS = ["name", "close", "volume", "premarket_change", "premarket_change_abs", "premarket_volume"]
399
+
400
+ POSTMARKET_COLUMNS = ["name", "close", "volume", "postmarket_change", "postmarket_change_abs", "postmarket_volume"]
401
+
402
+ CRYPTO_COLUMNS = ["name", "close", "volume", "change", "market_cap_basic", "24h_vol|5"]
403
+
404
+ TECHNICAL_COLUMNS = [
405
+ "name",
406
+ "close",
407
+ "volume",
408
+ "change",
409
+ "RSI",
410
+ "MACD.macd",
411
+ "MACD.signal",
412
+ "BB.upper",
413
+ "BB.lower",
414
+ "SMA20",
415
+ "EMA50",
416
+ "EMA200",
417
+ "ADX",
418
+ "Stoch.K",
419
+ "Stoch.D",
420
+ ]
421
+
422
+
423
+ def get_column_name(name: str) -> str:
424
+ """Get the API column name from a human-readable name or return as-is."""
425
+ return COLUMNS.get(name, name)