Skip to main content

tse_client/
lib.rs

1//! # tse-client
2//!
3//! A Rust port of the `tse-client` npm package: a client for fetching stock
4//! data from the Tehran Stock Exchange (TSETMC).
5//!
6//! The public surface mirrors the original JS API:
7//! - [`Client::get_prices`] ↔ `getPrices`
8//! - [`Client::get_instruments`] ↔ `getInstruments`
9//! - [`Client::get_intraday`] ↔ `getIntraday`
10//! - [`Client::get_intraday_instruments`] ↔ `getIntradayInstruments`
11//!
12//! Unlike the JS version, all per-call working state is owned locally and
13//! dropped when a call returns, so there is no cross-call accumulation.
14//!
15//! # Example
16//!
17//! ```no_run
18//! use tse_client::{Client, IntradaySettings, PriceSettings};
19//!
20//! # async fn run() -> tse_client::Result<()> {
21//! let client = Client::new()?;
22//!
23//! // getPrices
24//! let res = client
25//!     .get_prices(&["فملی".to_string()], &PriceSettings::default())
26//!     .await?;
27//! println!("{:#?}", res.data);
28//!
29//! // getInstruments
30//! let instruments = client.get_instruments().await?;
31//! println!("{} instruments", instruments.len());
32//!
33//! // getIntraday
34//! let itd = client
35//!     .get_intraday(&["فملی".to_string()], &IntradaySettings::default())
36//!     .await?;
37//! println!("{:#?}", itd.error);
38//! # Ok(())
39//! # }
40//! ```
41
42pub mod adjust;
43pub mod client;
44pub mod config;
45pub mod error;
46pub mod group;
47pub mod intraday;
48pub mod models;
49pub mod request;
50pub mod storage;
51pub mod util;
52
53pub use client::{Cell, Client, InstrumentPrices, PricesResult};
54pub use config::{Config, IntradaySettings, PriceSettings};
55pub use error::{Error, Result, ResultError};
56pub use group::{Period, group};
57pub use models::{
58    COLS, COLS_FA, ClosingPrice, Column, Instrument, InstrumentItd, Share, itd_group_cols,
59};
60
61use std::collections::HashMap;
62
63impl Client {
64    /// Port of JS `getInstruments`. Returns instruments keyed by `InsCode`.
65    pub async fn get_instruments(&self) -> Result<HashMap<String, Instrument>> {
66        let last_update = self.storage().get_item("tse.lastInstrumentUpdate")?;
67        if let Some(err) = self.update_instruments().await? {
68            if last_update.is_empty() {
69                return Err(Error::FailedRequest {
70                    title: format!("{err:?}"),
71                    detail: String::new(),
72                });
73            }
74        }
75        let rows = self.storage().get_item("tse.instruments")?;
76        let mut map = HashMap::new();
77        if rows.is_empty() {
78            return Ok(map);
79        }
80        for row in rows.split('\n').filter(|r| !r.is_empty()) {
81            let ins = Instrument::parse(row)?;
82            map.insert(ins.ins_code.clone(), ins);
83        }
84        Ok(map)
85    }
86
87    /// Port of JS `getIntradayInstruments`. Returns intraday instruments keyed
88    /// by `InsCode`.
89    pub async fn get_intraday_instruments(&self) -> Result<HashMap<String, InstrumentItd>> {
90        let rows = self.storage().get_item("tse.instruments.intraday")?;
91        let mut map = HashMap::new();
92        if rows.is_empty() {
93            return Ok(map);
94        }
95        for row in rows.split('\n').filter(|r| !r.is_empty()) {
96            let ins = InstrumentItd::parse(row)?;
97            map.insert(ins.ins_code.clone(), ins);
98        }
99        Ok(map)
100    }
101}