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}