Skip to main content

tvscreener/
lib.rs

1// Copyright 2026 tvscreener-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Unofficial `TradingView` screener API client (Rust), inspired by
5//! [deepentropy/tvscreener](https://github.com/deepentropy/tvscreener).
6//!
7//! Canonical repository: [Interchouette-ITC/tvscreener-rs](https://github.com/Interchouette-ITC/tvscreener-rs).
8//!
9//! Builds JSON payloads and POSTs them to
10//! `https://scanner.tradingview.com/{subtype}/scan`.
11//!
12//! # Payload shape
13//!
14//! [`core::Screener::build_payload`] emits a JSON object with `filter`, `columns`,
15//! `range`, `options`, `symbols`, optional `sort`, and optional top-level keys from
16//! [`core::Screener::add_misc`]. Filters with the same `left` key are merged: values
17//! accumulate and the operator may promote to `in_range`.
18//!
19//! # Rows
20//!
21//! Scan responses decode to [`ScreenerRow`]: each row holds a symbol string and a
22//! `data` map keyed by **column label** (not technical field name). Labels come from
23//! the `(technical, label)` pairs produced by [`util::get_columns_to_request`].
24//!
25//! # Example (offline payload)
26//!
27//! ```
28//! use tvscreener::core::Screener;
29//! use tvscreener::field::default_crypto_fields;
30//! use tvscreener::filter::{FieldCondition, FilterOperator};
31//! use serde_json::json;
32//!
33//! let mut screener = Screener::new("crypto");
34//! screener
35//!.select(default_crypto_fields())
36//!.where_condition(FieldCondition::new(
37//! "name",
38//! FilterOperator::Match,
39//! json!("btc"),
40//! )).unwrap()
41//!.set_range(0, 5);
42//! let payload = screener.build_payload().unwrap();
43//! assert!(payload.get("columns").and_then(|v| v.as_array()).is_some());
44//! ```
45//!
46//! Not affiliated with [TradingView](https://www.tradingview.com).
47//! Use is subject to `TradingView` terms.
48
49#![deny(missing_docs)]
50#![warn(rust_2018_idioms)]
51
52pub mod beautify;
53pub mod core;
54pub mod error;
55pub mod field;
56pub mod filter;
57pub mod logging;
58#[cfg(feature = "mcp")]
59pub mod mcp;
60pub mod query_config;
61pub mod resolve;
62pub mod ta;
63#[cfg(feature = "tui")]
64pub mod tui;
65pub mod util;
66
67pub use beautify::{
68    format_cell, format_cell_for_field, format_rows_table, CellTone, FormattedCell, RowTechMap,
69    TableFormatOptions, DEFAULT_TABLE_MAX_COLUMNS,
70};
71pub use error::{Result, TvscreenerError};
72pub use field::{
73    catalog_len, get_preset, list_fields, list_presets, resolve_field, search_fields, Asset,
74    FieldDef, IndexSymbolDef, Market, NamedValue, RatingBand,
75};
76pub use filter::{ExtraFilter, FieldCondition, Filter, FilterOperator};
77pub use util::{format_recommendation, format_row, format_value};
78
79/// One row from a screener scan response.
80///
81/// Holds a symbol plus a map of column **label** → JSON value. Labels align with the
82/// second element of each `(technical_name, label)` pair from
83/// [`util::get_columns_to_request`], not wire keys.
84#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
85#[allow(clippy::derive_partial_eq_without_eq)] // `serde_json::Value` is not `Eq`
86pub struct ScreenerRow {
87    /// `TradingView` symbol (e.g. `"BINANCE:BTCUSDT"`).
88    pub symbol: String,
89    /// Column label → value for the selected fields.
90    pub data: serde_json::Map<String, serde_json::Value>,
91}