diff --git a/.gitignore b/.gitignore index ad66c6c..448d732 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ Cargo.lock # test data files _data +tmp diff --git a/Cargo.toml b/Cargo.toml index abb00d9..994fab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ rayon = "^1" criterion = { version = "0.5", features = ["html_reports"] } anyhow = "1" protobuf = "3" -polars = "^0.32" +polars = { version = "^0.32", features = ["polars-io"] } [[bench]] harness = false diff --git a/Justfile b/Justfile index bc191d7..5cef151 100644 --- a/Justfile +++ b/Justfile @@ -45,6 +45,9 @@ replay-example: study-example: cargo run --package tradingview-rs --example study +csv_export-example: + cargo run --package tradingview-rs --example csv_export + lines-of-code: @git ls-files | grep '\.rs' | xargs wc -l diff --git a/examples/chart.rs b/examples/chart.rs index b0e96a3..545ffe5 100644 --- a/examples/chart.rs +++ b/examples/chart.rs @@ -8,21 +8,44 @@ use tradingview_rs::{ }, models::Interval, socket::DataServer, - user::User, }; #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); - let session = env::var("TV_SESSION").unwrap(); - let signature = env::var("TV_SIGNATURE").unwrap(); + // let session = env::var("TV_SESSION").unwrap(); + // let signature = env::var("TV_SIGNATURE").unwrap(); - let user = User::build() - .session(&session, &signature) - .get() - .await - .unwrap(); + // let user = User::build() + // .session(&session, &signature) + // .get() + // .await + // .unwrap(); + + // let access_id = env::var("CF_ACCESS_CLIENT_ID").unwrap(); + // let access_secret = env::var("CF_ACCESS_CLIENT_SECRET").unwrap(); + + // let mut headers = HeaderMap::new(); + // headers.insert("CF-Access-Client-Id", HeaderValue::from_static(&access_id)); + // headers.insert( + // "CF-Access-Client-Secret", + // HeaderValue::from_static(&access_secret), + // ); + + // let client = Client::builder() + // .default_headers(headers) + // .build() + // .unwrap() + // .get("https://tvmisc.bitbytelab.io/api/token/free") + // .send() + // .await + // .unwrap() + // .json() + // .await + // .unwrap(); + + let auth_token = env::var("TV_AUTH_TOKEN").unwrap(); let handlers = ChartCallbackFn { on_chart_data: Box::new(|data| Box::pin(on_chart_data(data))), @@ -32,7 +55,7 @@ async fn main() { let mut socket = WebSocket::build() .server(DataServer::ProData) - .auth_token(user.auth_token) + .auth_token(auth_token) .connect(handlers) .await .unwrap(); @@ -76,7 +99,7 @@ async fn main() { async fn on_chart_data(data: ChartSeries) -> Result<(), tradingview_rs::error::Error> { // info!("on_chart_data: {:?}", data); - let end = data.data.first().unwrap().0; + let end = data.data.first().unwrap().timestamp; info!("on_chart_data: {:?} - {:?}", data.data.len(), end); Ok(()) } diff --git a/examples/csv_export.rs b/examples/csv_export.rs new file mode 100644 index 0000000..1b1455c --- /dev/null +++ b/examples/csv_export.rs @@ -0,0 +1,90 @@ +use std::{env, process::exit}; + +use polars::prelude::*; + +use tracing::info; +use tradingview_rs::{ + chart::{ + session::{ChartCallbackFn, WebSocket}, + ChartOptions, ChartSeries, + }, + models::Interval, + socket::DataServer, +}; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + let auth_token = env::var("TV_AUTH_TOKEN").unwrap(); + + let handlers = ChartCallbackFn { + on_chart_data: Box::new(|data| Box::pin(on_chart_data(data))), + on_symbol_resolved: Box::new(|data| Box::pin(on_symbol_resolved(data))), + on_series_completed: Box::new(|data| Box::pin(on_series_completed(data))), + }; + + let mut socket = WebSocket::build() + .server(DataServer::ProData) + .auth_token(auth_token) + .connect(handlers) + .await + .unwrap(); + + socket + .set_market( + "NASDAQ:AMZN", + ChartOptions { + resolution: Interval::Daily, + bar_count: 50_000, + ..Default::default() + }, + ) + .await + .unwrap(); + socket.subscribe().await; +} + +async fn on_chart_data(data: ChartSeries) -> Result<(), tradingview_rs::error::Error> { + // Assuming you have the OHLCV data in a Vec named `data` + + // Extract the individual fields from OHLCV objects + let timestamps: Vec = data.data.iter().map(|item| item.timestamp).collect(); + let opens: Vec = data.data.iter().map(|item| item.open).collect(); + let highs: Vec = data.data.iter().map(|item| item.high).collect(); + let lows: Vec = data.data.iter().map(|item| item.low).collect(); + let closes: Vec = data.data.iter().map(|item| item.close).collect(); + let volumes: Vec = data.data.iter().map(|item| item.volume).collect(); + + // Create a Polars DataFrame + let mut df = DataFrame::new(vec![ + Series::new("timestamp", timestamps), + Series::new("open", opens), + Series::new("high", highs), + Series::new("low", lows), + Series::new("close", closes), + Series::new("volume", volumes), + ]) + .unwrap(); + + let csv_out = "tmp/nasdaq_amzn.csv"; + if std::fs::metadata(csv_out).is_err() { + let f = std::fs::File::create(csv_out).unwrap(); + CsvWriter::new(f).finish(&mut df).unwrap(); + } + + Ok(()) +} + +async fn on_symbol_resolved( + data: tradingview_rs::chart::SymbolInfo, +) -> Result<(), tradingview_rs::error::Error> { + info!("on_symbol_resolved: {:?}", data); + Ok(()) +} + +async fn on_series_completed( + data: tradingview_rs::chart::SeriesCompletedMessage, +) -> Result<(), tradingview_rs::error::Error> { + info!("on_series_completed: {:?}", data); + exit(0); +} diff --git a/examples/replay.rs b/examples/replay.rs index dde4815..8bb5968 100644 --- a/examples/replay.rs +++ b/examples/replay.rs @@ -77,7 +77,7 @@ async fn main() { } async fn on_chart_data(data: ChartSeries) -> Result<(), tradingview_rs::error::Error> { - let end = data.data.first().unwrap().0; + let end = data.data.first().unwrap().timestamp; info!("on_chart_data: {:?} - {:?}", data.data.len(), end); Ok(()) } diff --git a/examples/study.rs b/examples/study.rs index 51e1c36..9defb4a 100644 --- a/examples/study.rs +++ b/examples/study.rs @@ -8,21 +8,22 @@ use tradingview_rs::{ }, models::Interval, socket::DataServer, - user::User, }; #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); - let session = env::var("TV_SESSION").unwrap(); - let signature = env::var("TV_SIGNATURE").unwrap(); + let auth_token = env::var("TV_AUTH_TOKEN").unwrap(); - let user = User::build() - .session(&session, &signature) - .get() - .await - .unwrap(); + // let session = env::var("TV_SESSION").unwrap(); + // let signature = env::var("TV_SIGNATURE").unwrap(); + + // let user = User::build() + // .session(&session, &signature) + // .get() + // .await + // .unwrap(); let handlers = ChartCallbackFn { on_chart_data: Box::new(|data| Box::pin(on_chart_data(data))), @@ -32,7 +33,7 @@ async fn main() { let mut socket = WebSocket::build() .server(DataServer::ProData) - .auth_token(user.auth_token) + .auth_token(auth_token) .connect(handlers) .await .unwrap(); diff --git a/src/chart/mod.rs b/src/chart/mod.rs index d3a847a..6a0f1ab 100644 --- a/src/chart/mod.rs +++ b/src/chart/mod.rs @@ -77,7 +77,7 @@ pub struct ChartDataChanges { pub struct ChartSeries { pub symbol_id: String, pub interval: Interval, - pub data: Vec<(f64, f64, f64, f64, f64, f64)>, + pub data: Vec, } #[derive(Debug, Default, Clone, Serialize)] diff --git a/src/chart/utils.rs b/src/chart/utils.rs index df1a388..0941871 100644 --- a/src/chart/utils.rs +++ b/src/chart/utils.rs @@ -6,10 +6,38 @@ use rayon::prelude::*; use serde_json::Value; use std::sync::Mutex; +/// Extracts OHLCV data from a `ChartResponseData` object and returns a vector of `OHLCV` structs. +/// +/// # Arguments +/// +/// * `data` - A reference to a `ChartResponseData` object containing the series data. +/// +/// # Returns +/// +/// A vector of `OHLCV` structs containing the extracted data. pub fn extract_ohlcv_data(data: &ChartResponseData) -> Vec { - data.series.iter().map(|point| point.value).collect() + data.series + .iter() + .map(|point| OHLCV { + timestamp: point.value.0, + open: point.value.1, + high: point.value.2, + low: point.value.3, + close: point.value.4, + volume: point.value.5, + }) + .collect() } +/// Extracts the data from a `StudyResponseData` object and returns it as a vector of vectors. +/// +/// # Arguments +/// +/// * `data` - A reference to a `StudyResponseData` object. +/// +/// # Returns +/// +/// A vector of vectors containing the data from the `StudyResponseData` object. pub fn extract_studies_data(data: &StudyResponseData) -> Vec> { data.studies .iter() @@ -17,26 +45,82 @@ pub fn extract_studies_data(data: &StudyResponseData) -> Vec> { .collect() } +/// Extracts OHLCV data from a `ChartResponseData` struct in parallel. +/// +/// # Arguments +/// +/// * `data` - A reference to a `ChartResponseData` struct. +/// +/// # Returns +/// +/// A vector of `OHLCV` structs. pub fn par_extract_ohlcv_data(data: &ChartResponseData) -> Vec { - data.series.par_iter().map(|point| point.value).collect() + data.series + .par_iter() + .map(|point| OHLCV { + timestamp: point.value.0, + open: point.value.1, + high: point.value.2, + low: point.value.3, + close: point.value.4, + volume: point.value.5, + }) + .collect() } +/// Sorts an array of OHLCV tuples by their timestamp in ascending order. +/// +/// # Arguments +/// +/// * `tuples` - A mutable reference to an array of OHLCV tuples to be sorted. +/// pub fn sort_ohlcv_tuples(tuples: &mut [OHLCV]) { - tuples.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); + tuples.sort_by(|a, b| { + a.timestamp + .partial_cmp(&b.timestamp) + .unwrap_or(std::cmp::Ordering::Equal) + }); } +/// Update an OHLCV data point in a vector of OHLCV data. +/// +/// If a data point with the same timestamp as `new_data` exists in `data`, it will be updated with `new_data`. +/// Otherwise, `new_data` will be added to `data` and the vector will be sorted by timestamp. +/// +/// # Arguments +/// +/// * `data` - A mutable reference to a vector of OHLCV data. +/// * `new_data` - The new OHLCV data point to update or add to `data`. pub fn update_ohlcv_data_point(data: &mut Vec, new_data: OHLCV) { if let Some(index) = data .iter() - .position(|&x| (x.0 - new_data.0).abs() < f64::EPSILON) + .position(|&x| (x.timestamp - new_data.timestamp).abs() < f64::EPSILON) { data[index] = new_data; } else { data.push(new_data); - data.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); + data.sort_by(|a, b| { + a.timestamp + .partial_cmp(&b.timestamp) + .unwrap_or(std::cmp::Ordering::Equal) + }); } } +/// Updates the OHLCV data by merging the new data into the old data. +/// +/// # Arguments +/// +/// * `old_data` - A mutable reference to the old OHLCV data. +/// * `new_data` - A reference to the new OHLCV data to be merged. +/// +/// # Example +/// +/// ``` +/// use tradingview_rs::tools::update_ohlcv_data; +/// use tradingview_rs::models::OHLCV; +/// +/// ``` pub fn update_ohlcv_data(old_data: &mut Vec, new_data: &Vec) { let mutex = Mutex::new(old_data); new_data.par_iter().for_each(|op| { @@ -45,6 +129,8 @@ pub fn update_ohlcv_data(old_data: &mut Vec, new_data: &Vec) { }); } +/// Returns the string value at the given index in the provided slice of `Value`s. +/// If the value at the given index is not a string, an empty string is returned. pub fn get_string_value(values: &[Value], index: usize) -> String { match values.get(index) { Some(value) => match value.as_str() { @@ -64,21 +150,21 @@ mod tests { #[test] fn test_sort_ohlcv_tuples() { let mut tuples = vec![ - (1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0), - (1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0), - (1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0), - (1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0), - (1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0), + OHLCV::new((1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0)), + OHLCV::new((1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0)), + OHLCV::new((1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0)), + OHLCV::new((1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0)), + OHLCV::new((1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0)), ]; sort_ohlcv_tuples(&mut tuples); let expected_output = vec![ - (1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0), - (1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0), - (1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0), - (1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0), - (1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0), + OHLCV::new((1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0)), + OHLCV::new((1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0)), + OHLCV::new((1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0)), + OHLCV::new((1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0)), + OHLCV::new((1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0)), ]; assert_eq!(tuples, expected_output); @@ -87,23 +173,23 @@ mod tests { #[test] fn test_update_ohlcv_data_point() { let mut data = vec![ - (1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0), - (1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0), - (1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0), - (1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0), - (1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0), + OHLCV::new((1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0)), + OHLCV::new((1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0)), + OHLCV::new((1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0)), + OHLCV::new((1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0)), + OHLCV::new((1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0)), ]; - let new_data = (1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0); + let new_data = OHLCV::new((1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0)); update_ohlcv_data_point(&mut data, new_data); let expected_output = vec![ - (1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0), - (1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0), - (1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0), - (1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0), - (1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0), + OHLCV::new((1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0)), + OHLCV::new((1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0)), + OHLCV::new((1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0)), + OHLCV::new((1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0)), + OHLCV::new((1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0)), ]; assert_eq!(data, expected_output); @@ -112,29 +198,29 @@ mod tests { #[test] fn test_update_ohlcv_data() { let mut old_data = vec![ - (1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0), - (1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0), - (1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0), - (1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0), - (1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0), + OHLCV::new((1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0)), + OHLCV::new((1691647200.0, 82600.0, 82800.0, 82200.0, 82200.0, 784500.0)), + OHLCV::new((1691733600.0, 81600.0, 83000.0, 81500.0, 82000.0, 625500.0)), + OHLCV::new((1691632800.0, 83100.0, 83300.0, 82300.0, 82600.0, 558800.0)), + OHLCV::new((1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0)), ]; let new_data = vec![ - (1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0), - (1691733600.0, 81700.0, 83100.0, 81600.0, 83000.0, 700000.0), - (1691632800.0, 83200.0, 83400.0, 82400.0, 82700.0, 600000.0), - (1691805600.0, 82500.0, 82700.0, 82000.0, 82100.0, 900000.0), + OHLCV::new((1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0)), + OHLCV::new((1691733600.0, 81700.0, 83100.0, 81600.0, 83000.0, 700000.0)), + OHLCV::new((1691632800.0, 83200.0, 83400.0, 82400.0, 82700.0, 600000.0)), + OHLCV::new((1691805600.0, 82500.0, 82700.0, 82000.0, 82100.0, 900000.0)), ]; update_ohlcv_data(&mut old_data, &new_data); let expected_output = vec![ - (1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0), - (1691632800.0, 83200.0, 83400.0, 82400.0, 82700.0, 600000.0), - (1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0), - (1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0), - (1691733600.0, 81700.0, 83100.0, 81600.0, 83000.0, 700000.0), - (1691805600.0, 82500.0, 82700.0, 82000.0, 82100.0, 900000.0), + OHLCV::new((1691560800.0, 83800.0, 83900.0, 83000.0, 83100.0, 708100.0)), + OHLCV::new((1691632800.0, 83200.0, 83400.0, 82400.0, 82700.0, 600000.0)), + OHLCV::new((1691647200.0, 82700.0, 82900.0, 82100.0, 82300.0, 800000.0)), + OHLCV::new((1691719200.0, 82000.0, 82200.0, 81600.0, 81600.0, 517400.0)), + OHLCV::new((1691733600.0, 81700.0, 83100.0, 81600.0, 83000.0, 700000.0)), + OHLCV::new((1691805600.0, 82500.0, 82700.0, 82000.0, 82100.0, 900000.0)), ]; assert_eq!(old_data, expected_output, "values don't match"); @@ -188,16 +274,16 @@ mod tests { ], }; let expected_output = vec![ - (1.0, 2.0, 3.0, 4.0, 5.0, 6.0), - (2.0, 3.0, 4.0, 5.0, 6.0, 7.0), - (3.0, 4.0, 5.0, 6.0, 7.0, 8.0), - (4.0, 5.0, 6.0, 7.0, 8.0, 9.0), - (5.0, 6.0, 7.0, 8.0, 9.0, 10.0), - (6.0, 7.0, 8.0, 9.0, 10.0, 11.0), - (7.0, 8.0, 9.0, 10.0, 11.0, 12.0), - (8.0, 9.0, 10.0, 11.0, 12.0, 13.0), - (9.0, 10.0, 11.0, 12.0, 13.0, 14.0), - (10.0, 11.0, 12.0, 13.0, 14.0, 15.0), + OHLCV::new((1.0, 2.0, 3.0, 4.0, 5.0, 6.0)), + OHLCV::new((2.0, 3.0, 4.0, 5.0, 6.0, 7.0)), + OHLCV::new((3.0, 4.0, 5.0, 6.0, 7.0, 8.0)), + OHLCV::new((4.0, 5.0, 6.0, 7.0, 8.0, 9.0)), + OHLCV::new((5.0, 6.0, 7.0, 8.0, 9.0, 10.0)), + OHLCV::new((6.0, 7.0, 8.0, 9.0, 10.0, 11.0)), + OHLCV::new((7.0, 8.0, 9.0, 10.0, 11.0, 12.0)), + OHLCV::new((8.0, 9.0, 10.0, 11.0, 12.0, 13.0)), + OHLCV::new((9.0, 10.0, 11.0, 12.0, 13.0, 14.0)), + OHLCV::new((10.0, 11.0, 12.0, 13.0, 14.0, 15.0)), ]; let output = extract_ohlcv_data(&chart_data); let output2 = par_extract_ohlcv_data(&chart_data); diff --git a/src/lib.rs b/src/lib.rs index b891130..b75d4c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,12 +4,13 @@ pub mod error; pub mod models; pub mod quote; pub mod socket; + pub mod user; mod prelude; mod session; mod utils; -static UA: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"; +static UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"; pub mod tools { pub use crate::chart::utils::{ diff --git a/src/models/mod.rs b/src/models/mod.rs index 6942a9e..52c806a 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -3,7 +3,28 @@ use std::collections::HashMap; use serde::{Deserialize, Deserializer, Serialize}; pub mod pine_indicator; -pub type OHLCV = (f64, f64, f64, f64, f64, f64); +#[derive(Debug, Clone, Serialize, Copy, PartialEq)] +pub struct OHLCV { + pub timestamp: f64, + pub open: f64, + pub high: f64, + pub low: f64, + pub close: f64, + pub volume: f64, +} + +impl OHLCV { + pub fn new(entry: (f64, f64, f64, f64, f64, f64)) -> Self { + OHLCV { + timestamp: entry.0, + open: entry.1, + high: entry.2, + low: entry.3, + close: entry.4, + volume: entry.5, + } + } +} #[derive(Debug, Clone, Default)] pub struct SimpleTA { diff --git a/src/socket.rs b/src/socket.rs index 89b2417..d471d2d 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -81,6 +81,8 @@ impl From for TradingViewDataEvent { "series_error" => TradingViewDataEvent::OnError(TradingViewError::SeriesError), "critical_error" => TradingViewDataEvent::OnError(TradingViewError::CriticalError), "study_error" => TradingViewDataEvent::OnError(TradingViewError::StudyError), + "protocol_error" => TradingViewDataEvent::OnError(TradingViewError::ProtocolError), + s => TradingViewDataEvent::UnknownEvent(s.to_string()), } }