spread_tracker/lib.rs
1//! # Xylex spread tracker
2//!
3//! `spread_tracker` is a library for tracking the spread of various symbols in the forex market.
4//!
5//! Every forex broker has a different spread for each symbol. This library is used to track the spread of various symbols in the forex market.
6//! Retrieve & scrape the spread of various symbols from different brokers.
7//!
8//!
9//!
10//! ### Features
11//! - Track the spread of various symbols in the forex market.
12//! - Retrieve & scrape the spread of various symbols from different brokers.
13//! - Store the spread data in a database.
14//! - Cache the spread data to minimize the number of requests to the broker.
15//!
16//! ### Granularity
17//! - Query the spread of all symbols from all listed brokers.
18//! - Query the spread of a specific symbol from all listed brokers.
19//! - Query the spread of all symbols from a specific broker.
20//! - Query the spread of a specific symbol from a specific broker.
21//!
22//!
23//! ### Usage
24//! Add this to your `Cargo.toml`:
25//! ```toml
26//! [dependencies]
27//! spread_tracker = "0.1.0"
28//! ```
29//!
30//! ### How to assign the correct Broker type
31//! ```rust
32//! use spread_tracker::config::{
33//! SpreadBrokerUrl,
34//! Brokers
35//! };
36//! ```
37//!
38//! ### How to get their spreads
39//!
40//!
41//! <div class="warning">
42//! This is currently strictly available for asynchronous context!
43//! </div>
44//!
45//!
46//! ```rust
47//! use spread_tracker::config::SpreadBrokerUrl;
48//! use spread_tracker::model::{
49//! Symbol,
50//! SymbolSpread
51//! };
52//!
53//! let config: SpreadBrokerUrl = SpreadBrokerUrl::new();
54//!
55//! // In this example, we are tracking the spread of the symbols from the Vantage and EightCap brokers.
56//! let brokers: Vec<Brokers> = vec![
57//! Brokers::Vantage,
58//! Brokers::EightCap,
59//! ];
60//!
61//! // Get the spread of the symbols from the brokers
62//! let spread: Value = SpreadTracker::get_spread(
63//! config,
64//! brokers
65//! ).await.unwrap();
66//!
67//! println!("Spread: {:#?}", spread);
68//!
69//! Result:
70//!
71//! {
72//! "spread": {
73//! "eightcap": [
74//! {
75//! "ask": 2197.92,
76//! "bid": 2209.92,
77//! "spread": 12.0,
78//! "symbol": "XAUUSD"
79//! },
80//! {
81//! "ask": 2031.57,
82//! "bid": 2060.8973,
83//! "spread": 29.3273,
84//! "symbol": "XAUEUR"
85//! },
86//! {
87//! ---------------- cut for brevity ----------------
88//! ```
89//! ### Return type
90//! The return type is a `serde_json::Value` object, which is a JSON object.
91//!
92//! #### Structure
93//! Object[spread]Vector[broker] -> Object[symbol, ask, bid, spread]
94//!
95//! * `spread` is the key for the spread data.
96//! * `broker` is the key for the broker name, which will differ based on the broker.
97//!
98//! - Notes:
99//! A Vector of objects is sometimes referred to as a list of objects. It is a collection of objects that are stored in no particular order.
100//!
101//! ### Configuration
102//! The `config.yaml` is used to store the broker URLs for the spread tracking, and the `model.rs` is used to store the data model for the spread tracking. The `config.yaml` file should be in the following format:
103//! ```yaml
104//! BrokerSpreadUrls:
105//! Vantage: "https://www.vantagefx.com/trading-info/forex-market-hours/"
106//! MyFxBook: "https://www.myfxbook.com/forex-broker-quotes/vantage/6052"
107//! ```
108//!
109//! Where `Vantage` and `MyFxBook` are the broker names and the URLs are the URLs of the brokers for spread tracking, derived from the MyFxBook website.
110//!
111//!
112//!
113//! ### Caching
114//! wip
115//! You can use the `db` module to store the spread data in a database.
116//! Currently, the library relies on `supabase_rs` to store the data in a Supabase database.
117//!
118//! If you want to use a different database, you can implement the `Db` trait for your database.
119//! Or simply call the `store_spread` method in the `model.rs` file to store the spread data in your database via a manual file like `.json`
120//!
121//!
122//! ### Asynchronous vs Synchronous API
123//! The main library is already asynchronous, so you can use it in an asynchronous context.
124//! Sync API is **not** due to more testing being needed
125//!
126//!
127//!
128//! ### Database
129//! wip
130//! You can use the `db` module to store the spread data in a database.
131//! Currently, the library relies on `supabase_rs` to store the data in a Supabase database.
132//!
133//! If you want to use a different database, you can implement the `Db` trait for your database.
134//! Or simply call the `store_spread` method in the `model.rs` file to store the spread data in your database via a manual file like `.json`
135//!
136//! ### Errors
137//!
138//! <div class="warning">
139//! Custom type errors are not yet implemented!
140//! </div>
141//!
142//! * `url_invalid` will be returned if the URL is invalid.
143//! * `url_not_reachable` will be returned if the URL is not reachable.
144//! * `url_not_found` will be returned if the URL is not found.
145//! * `invalid_body` will be returned if the body is invalid.
146//! * `internal_error` will be returned if there is an internal error.
147//! * `empty_body` will be returned if the body is empty.
148//! * `could_not_extract_broker_name` will be returned if the broker name could not be extracted from the URL.
149//! * `failed_to_open_config` will be returned if the config.yaml file is not found.
150//! * `failed_to_read_yaml` will be returned if the file is not in the correct format.
151//! * `failed_to_parse_symbol_spread` will be returned if the Vec<String> could not be parsed into a Vec<SymbolSpread>.
152//! * `failed_to_parse_symbol` will be returned if the Symbol could not be parsed from the string.
153//! * `failed_to_parse_ask_price` will be returned if the ask price could not be parsed from the string.
154//! * `failed_to_parse_bid_price` will be returned if the bid price could not be parsed from the string.
155//! * `failed_to_parse_spread` will be returned if the spread could not be parsed from the string.
156//! * `could_not_retrieve_spread_data` will be returned if the spread data could not be retrieved from the broker URL.
157//! * `failed_to_bind_json_object_to_key` will be returned if the JSON object could not be bound to the key.
158//!
159//!
160//!
161//! ### Modules
162//! - `db`: This module is used to store the spread data in a database.
163//! - `caching`: This module is used to cache the spread data to minimize the number of requests to the broker.
164//! - `utils`: This module is used to save the spread data to a `.json` file.
165//! - `config`: This module is used to load the configuration from the `config.yaml` file.
166//! - `model`: This module is used to store the data model for the spread tracking.
167//! - `errors`: This module is used to handle the errors in the library.
168//!
169#![doc(
170 html_logo_url = "https://storage.googleapis.com/xylex_images/Svg/svgviewer-output%20(1).svg",
171 html_favicon_url = "https://storage.googleapis.com/xylex_images/Svg/svgviewer-output%20(1).svg"
172)]
173
174
175#![allow(unused_imports)]
176#![allow(dead_code)]
177#![allow(rustdoc::invalid_rust_codeblocks)]
178#![allow(rustdoc::invalid_html_tags)]
179
180// import the necessary modules into the hierarchy
181pub mod caching;
182pub mod db;
183pub mod utils;
184pub mod config;
185pub mod model;
186pub mod errors;
187
188
189
190
191// import the necessary external crates into the hierarchy
192use std::fs::File;
193use std::io::Write;
194use std::error::Error as StdError;
195use reqwest::get;
196use serde_json::{
197 Value,
198 Map
199};
200
201
202// import the necessary modules into the hierarchy
203use crate::utils::regex_finder::find_symbol_spread;
204use crate::model::{
205 Symbol,
206 SymbolSpread
207};
208use crate::config::{
209 SpreadBrokerUrl,
210 Brokers
211};
212use crate::utils::format::{
213 wrap_json_under_key,
214 vec_to_json,
215 extract_broker_name
216};
217
218
219
220
221/// ### The `SpreadTracker` struct is used to track the spread of various symbols in the forex market.
222///
223/// This struct is used to track the spread of various symbols in the forex market.
224///
225pub struct SpreadTracker {
226 spread_broker_url: SpreadBrokerUrl,
227 spread: SymbolSpread
228}
229
230
231impl SpreadTracker {
232 /// The `get_spreads` function is used to get the spread of various symbols from the broker URL.
233 ///
234 /// This function is used to get the spread of various symbols from the broker URL.
235 ///
236 /// ### Example
237 ///
238 /// ```
239 /// use spread_tracker::SpreadTracker;
240 /// use spread_tracker::config::SpreadBrokerUrl;
241 ///
242 /// let result = SpreadTracker::get_spreads(SpreadBrokerUrl::new());
243 ///
244 /// println!("Result: {:#?}", result);
245 /// Output:
246 /// {
247 /// "ask": Number(0.90451),
248 /// "bid": Number(1.37551),
249 /// "spread": Number(0.471),
250 /// "symbol": String("USDCHF"),
251 /// });
252 /// ```
253 ///
254 /// ### Errors
255 /// `url_not_reachable` will be returned if the URL is not reachable.
256 /// `url_not_found` will be returned if the URL is not found.
257 /// `invalid_url` will be returned if the URL is invalid.
258 /// `invalid_body` will be returned if the body is invalid.
259 /// `internal_error` will be returned if there is an internal error.
260 ///
261 pub async fn get_spread(
262 config: SpreadBrokerUrl,
263 brokers: Vec<Brokers>
264 ) -> Result<Value, Box<dyn StdError + Send + Sync + 'static>> {
265
266 let mut all_broker_spreads: Map<String, Value> = serde_json::Map::new();
267
268 for broker in brokers {
269 let url: String = config.get_url(broker.clone());
270 let url: &str = url.as_str();
271
272 let name: String = extract_broker_name(url).unwrap_or_else(|_| broker.to_string());
273
274 let spread_tracker: Result<String, Box<dyn StdError + Send + Sync>> = SpreadTracker::download_html_body(url).await;
275
276
277 if let Ok(body) = spread_tracker {
278 if let Ok(results) = SpreadTracker::regex_find_symbol_spread(&body).await {
279 if let Ok(json_output) = vec_to_json(results) {
280
281 // Wrap the JSON output under the broker's name
282 all_broker_spreads.insert(name, json_output);
283 }
284 }
285 }
286 }
287
288 // Wrap all broker spreads under the key "spread"
289 let wrapped_all_broker_spreads: Value = serde_json::json!({ "spread": all_broker_spreads });
290
291 Ok(wrapped_all_broker_spreads)
292 }
293
294 /// The `download_html_body` function is used to download the HTML body from the URL.
295 /// This function is used to download the HTML body from the URL.
296 ///
297 /// ### Example
298 ///
299 /// ```
300 /// use spread_tracker::SpreadTracker;
301 ///
302 /// let url = "https://www.vantagefx.com/trading-info/forex-market-hours/";
303 ///
304 /// let body = SpreadTracker::download_html_body(url);
305 ///
306 /// assert_eq!(body, "EURUSD 0.0001 1.1234 1.1233");
307 /// ```
308 pub async fn download_html_body(
309 url: &str
310 ) -> Result<String, Box<dyn StdError + Send + Sync + 'static>> {
311
312 // Download the HTML body
313 let response = get(url).await;
314 if response.is_err() {
315 return Err(Box::new(response.err().unwrap()));
316 }
317 let text = response.unwrap().text().await;
318 if text.is_err() {
319 return Err(Box::new(text.err().unwrap()));
320 }
321 let body: String = text.unwrap();
322
323 let file_result = File::create("body.txt");
324 if file_result.is_err() {
325 return Err(Box::new(file_result.err().unwrap()));
326 }
327 let mut file: File = file_result.unwrap();
328 let write_result = write!(file, "{}", body);
329 if write_result.is_err() {
330 return Err(Box::new(write_result.err().unwrap()));
331 }
332
333 Ok(body)
334 }
335
336 /// The `regex_find_symbol_spread` function is used to find the symbol spread in the HTML body.
337 /// This function is used to find the symbol spread in the HTML body.
338 ///
339 /// ### Example
340 ///
341 /// ```
342 /// use spread_tracker::SpreadTracker;
343 ///
344 /// let body = "<span></span> 1.1234 1.1233 0.0001Spread";
345 ///
346 /// let spread = SpreadTracker::regex_find_symbol_spread(& body);
347 ///
348 /// assert_eq!(spread, vec!["1.1234 1.1233 0.0001Spread"]);
349 ///
350 /// ```
351 ///
352 /// ### Errors
353 /// `empty_body` will be returned if the body is empty.
354 ///
355 pub async fn regex_find_symbol_spread(
356 body: &str
357 ) -> Result<Vec<String>, Box<dyn StdError + Send + Sync + 'static>>{
358 let results: Vec<String> = find_symbol_spread(body);
359
360
361 Ok(results)
362 }
363
364
365}