diff --git a/Cargo.toml b/Cargo.toml index 7868b66c..350fda94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.0" +version = "7.2.15" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] @@ -26,10 +26,34 @@ path = "src/lib.rs" regex = { version = "1.5.5", optional = true} serde = { version = "1.0.133", optional = true} serde_derive = { version = "1.0.133", optional = true} +num-traits = "0.2.14" +indexmap = "2.4.0" +deepsize = "0.2.0" +ndarray = "0.15" +ndarray-linalg = { version = "0.15" } +linregress = "0.5" +workdays = "0.1.3" +#csv = "1.1" +#anyhow = "1.0.86" +thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_object", branch = "main" } +chrono = { version = "0.4.19", default-features = false, features = ["clock", "std"] } +paste = "1.0.14" +percent-encoding = "2.3.1" +lazy_static = "1.5.0" + [features] serde_support = ["serde", "serde_derive"] +default = ["serde_support"] regex_support = ["regex"] +serde_json_support = ["serde_json", "log"] + +[dependencies.serde_json] +version = "*" +optional = true +[dependencies.log] +version = "*" +optional = true [dev-dependencies] ron = "0.7.0" diff --git a/README.md b/README.md index e4c535bd..2ed5e7ea 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE)); assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context), Err(EvalexprError::expected_int(Value::from(5.0)))); // We can check which value the context stores for a like this -assert_eq!(context.get_value("a"), Some(&Value::from(5))); +assert_eq!(context.get_value("a"), Some(Value::from(5))); // And use the value in another expression like this assert_eq!(eval_int_with_context_mut("a = a + 2; a", &mut context), Ok(7)); // It is also possible to save a bit of typing by using an operator-assignment operator @@ -223,7 +223,7 @@ assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE)); assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context), Err(EvalexprError::expected_int(5.0.into()))); assert_eq!(eval_int_with_context("a", &context), Ok(5)); -assert_eq!(context.get_value("a"), Some(5.into()).as_ref()); +assert_eq!(context.get_value("a"), Some(5.into())); ``` For each binary operator, there exists an equivalent operator-assignment operator. @@ -314,8 +314,8 @@ assert_eq!(eval_int_with_context("a", &context), Ok(5)); // We can write or overwrite variables in expressions... assert_eq!(eval_with_context_mut("a = 10; b = 1.0;", &mut context), Ok(().into())); // ...and read the value in code like this -assert_eq!(context.get_value("a"), Some(&Value::from(10))); -assert_eq!(context.get_value("b"), Some(&Value::from(1.0))); +assert_eq!(context.get_value("a"), Some(Value::from(10))); +assert_eq!(context.get_value("b"), Some(Value::from(1.0))); ``` Contexts are also required for user-defined functions. diff --git a/src/context/mod.rs b/src/context/mod.rs index 200ae939..33262a89 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -4,30 +4,25 @@ //! This crate implements two basic variants, the `EmptyContext`, that returns `None` for each identifier and cannot be manipulated, and the `HashMapContext`, that stores its mappings in hash maps. //! The HashMapContext is type-safe and returns an error if the user tries to assign a value of a different type than before to an identifier. -use std::collections::HashMap; - -use crate::{ - function::Function, - value::{value_type::ValueType, Value}, - EvalexprError, EvalexprResult, -}; +use std::{borrow::Cow, collections::HashMap}; +use std::collections::HashSet; +use std::fmt::{Debug, Formatter}; +use indexmap::IndexMap; +use thin_trait_object::thin_trait_object; +use crate::{function::Function, value::{value_type::ValueType, Value}, Error, EvalexprError, EvalexprResult}; mod predefined; -/// An immutable context. -pub trait Context { - /// Returns the value that is linked to the given identifier. - fn get_value(&self, identifier: &str) -> Option<&Value>; - /// Calls the function that is linked to the given identifier with the given argument. - /// If no function with the given identifier is found, this method returns `EvalexprError::FunctionIdentifierNotFound`. - fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult; -} /// A context that allows to assign to variables. pub trait ContextWithMutableVariables: Context { /// Sets the variable with the given identifier to the given value. - fn set_value(&mut self, _identifier: String, _value: Value) -> EvalexprResult<()> { + fn set_value(&mut self, _identifier: String, _value: Value, _track_changes: bool) -> EvalexprResult<()> { + Err(EvalexprError::ContextNotMutable) + } + + fn get_changed_variables(&self) -> EvalexprResult> { Err(EvalexprError::ContextNotMutable) } } @@ -54,7 +49,11 @@ pub trait GetFunctionContext: Context { pub struct EmptyContext; impl Context for EmptyContext { - fn get_value(&self, _identifier: &str) -> Option<&Value> { + fn get_value(&self, _identifier: &str) -> Option> { + None + } + + fn get_value_by_index(&self, identifier: &usize) -> Option> { None } @@ -76,6 +75,14 @@ pub struct HashMapContext { variables: HashMap, #[cfg_attr(feature = "serde_support", serde(skip))] functions: HashMap, + changed_variables: HashSet +} + +#[derive(Clone, Debug, Default)] +pub struct IndexMapContext { + pub variables: IndexMap, + pub changed_variables: HashSet, + pub functions: HashMap, } impl HashMapContext { @@ -85,9 +92,62 @@ impl HashMapContext { } } +impl IndexMapContext { + /// Constructs a `HashMapContext` with no mappings. + pub fn new() -> Self { + Default::default() + } +} + + + impl Context for HashMapContext { - fn get_value(&self, identifier: &str) -> Option<&Value> { - self.variables.get(identifier) + fn get_value(&self, identifier: &str) -> Option> { + self.variables.get(identifier).map(Cow::Borrowed) + } + + fn get_value_by_index(&self, identifier: &usize) -> Option> { + todo!("Get value by index not implemented for hashmap context") + } + + fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { + if let Some(function) = self.functions.get(identifier) { + function.call(argument) + } else { + Err(EvalexprError::FunctionIdentifierNotFound( + identifier.to_string(), + )) + } + } +} + +impl Context for &mut IndexMapContext { + fn get_value(&self, identifier: &str) -> Option> { + self.variables.get(identifier).map(Cow::Borrowed) + } + + fn get_value_by_index(&self, identifier: &usize) -> Option> { + self.variables.get_index(*identifier).map(|(_, v)| Cow::Borrowed(v)) + } + + fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { + if let Some(function) = self.functions.get(identifier) { + function.call(argument) + } else { + Err(EvalexprError::FunctionIdentifierNotFound( + identifier.to_string(), + )) + } + } +} + +impl Context for IndexMapContext { + fn get_value(&self, identifier: &str) -> Option> { + self.variables.get(identifier).map(Cow::Borrowed) + } + + fn get_value_by_index(&self, identifier: &usize) -> Option> { + self.variables.get_index(*identifier).map(|(_, v)| Cow::Borrowed(v)) } fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { @@ -102,20 +162,185 @@ impl Context for HashMapContext { } impl ContextWithMutableVariables for HashMapContext { - fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> { + fn set_value(&mut self, identifier: String, value: Value, track_changes: bool) -> EvalexprResult<()> { if let Some(existing_value) = self.variables.get_mut(&identifier) { - if ValueType::from(&existing_value) == ValueType::from(&value) { + if ValueType::from(&existing_value) == ValueType::from(&value) || existing_value.is_empty() { *existing_value = value; + if track_changes { + self.changed_variables.insert(identifier); + } return Ok(()); } else { return Err(EvalexprError::expected_type(existing_value, value)); } } + if track_changes { + self.changed_variables.insert(identifier.clone()); + } // Implicit else, because `self.variables` and `identifier` are not unborrowed in else self.variables.insert(identifier, value); Ok(()) } + + fn get_changed_variables(&self) -> EvalexprResult> { + Ok(self.changed_variables.clone()) + } +} + +impl ContextWithMutableVariables for &mut IndexMapContext { + fn set_value(&mut self, identifier: String, value: Value, track_changes: bool) -> EvalexprResult<()> { + if let Some(existing_value) = self.variables.get_mut(&identifier) { + if ValueType::from(&existing_value) == ValueType::from(&value) || existing_value.is_empty() { + *existing_value = value; + if track_changes { + self.changed_variables.insert(identifier); + } + return Ok(()); + } else { + return Err(EvalexprError::expected_type(existing_value, value)); + } + } + if track_changes { + self.changed_variables.insert(identifier.clone()); + } + self.variables.insert(identifier, value); + Ok(()) + } + + fn get_changed_variables(&self) -> EvalexprResult> { + Ok(self.changed_variables.clone()) + } +} +impl ContextWithMutableVariables for IndexMapContext { + fn set_value(&mut self, identifier: String, value: Value, track_changes: bool) -> EvalexprResult<()> { + if let Some(existing_value) = self.variables.get_mut(&identifier) { + if ValueType::from(&existing_value) == ValueType::from(&value) || existing_value.is_empty() { + *existing_value = value; + if track_changes { + self.changed_variables.insert(identifier); + } + return Ok(()); + } else { + return Err(EvalexprError::expected_type(existing_value, value)); + } + } + if track_changes { + self.changed_variables.insert(identifier.clone()); + } + self.variables.insert(identifier, value); + Ok(()) + } + + fn get_changed_variables(&self) -> EvalexprResult> { + Ok(self.changed_variables.clone()) + } +} + +impl OperatorRowTrait for &mut IndexMapContext { + fn get_value(&self, identifier: &str) -> Result { + Ok(Context::get_value(self, identifier).map(|v|v.clone().into_owned()).unwrap_or(Value::Empty)) + } + + fn get_values(&self) -> Result, Error> { + Ok(self.variables.values().map(|v|v.clone()).collect()) + } + + fn get_values_for_columns(&self, columns: Vec) -> Result, Error> { + let mut values = vec![Value::Empty; self.variables.len()]; + for column in columns { + values[column] = self.get_value_for_column(column)?; + } + Ok(values) + } + + fn set_value(&mut self, identifier: &str, value: Value) -> Result<(), Error> { + Ok(ContextWithMutableVariables::set_value(self, identifier.to_string(), value, true)?) + } + + fn get_value_for_column(&self, col: usize) -> Result { + Ok(Context::get_value_by_index(self, &col).map(|v|v.clone().into_owned()).unwrap_or(Value::Empty)) + } + + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(), Error> { + todo!() + } + + fn set_values_for_columns(&mut self, colums: Vec, mut values: Vec) -> Result<(), Error> { + for column in colums { + self.set_value_for_column(column.clone(), values.remove(column))?; + } + Ok(()) + } + + fn set_row(&mut self, row: usize) { + todo!() + } + + fn call_function(&self, idt: &str, argument: Value) -> Result { + todo!() + } + + fn has_changes(&self) -> Result { + Ok(!self.changed_variables.is_empty()) + } + + fn get_dirty_flags(&self) -> Result, Error> { + todo!() + } +} + +impl OperatorRowTrait for IndexMapContext { + fn get_value(&self, identifier: &str) -> Result { + Ok(Context::get_value(self, identifier).map(|v|v.clone().into_owned()).unwrap_or(Value::Empty)) + } + + fn get_values(&self) -> Result, Error> { + Ok(self.variables.values().map(|v|v.clone()).collect()) + } + + fn get_values_for_columns(&self, columns: Vec) -> Result, Error> { + let mut values = vec![Value::Empty; self.variables.len()]; + for column in columns { + values[column] = self.get_value_for_column(column)?; + } + Ok(values) + } + + fn set_value(&mut self, identifier: &str, value: Value) -> Result<(), Error> { + Ok(ContextWithMutableVariables::set_value(self, identifier.to_string(), value, true)?) + } + + fn get_value_for_column(&self, col: usize) -> Result { + Ok(Context::get_value_by_index(self, &col).map(|v|v.clone().into_owned()).unwrap_or(Value::Empty)) + } + + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(), Error> { + todo!() + } + + fn set_values_for_columns(&mut self, columns: Vec, mut values: Vec) -> Result<(), Error> { + for column in columns { + self.set_value_for_column(column, values.remove(column))?; + } + Ok(()) + } + + fn set_row(&mut self, row: usize) { + todo!() + } + + fn call_function(&self, idt: &str, argument: Value) -> Result { + todo!() + } + + fn has_changes(&self) -> Result { + Ok(!self.changed_variables.is_empty()) + } + + fn get_dirty_flags(&self) -> Result, Error> { + todo!() + } } impl ContextWithMutableFunctions for HashMapContext { @@ -125,6 +350,110 @@ impl ContextWithMutableFunctions for HashMapContext { } } +pub trait Context { +/// A context defines methods to retrieve variable values and call functions for literals in an expression tree. + fn get_value(&self, identifier: &str) -> Option>; + fn get_value_by_index(&self, identifier: &usize) -> Option>; +/// Retrieves the value of the given identifier. + fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; +} + + +#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=true))] +#[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] +pub trait OperatorRowTrait { + fn get_value(&self, identifier: &str) -> Result; + fn get_values(&self) -> Result,crate::Error>; + fn get_values_for_columns(&self, columns: Vec) -> Result,crate::Error>; + fn set_value(&mut self, identifier: &str, value: Value) -> Result<(),crate::Error>; + fn get_value_for_column(&self, col: usize) -> Result; + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(),crate::Error>; + fn set_values_for_columns(&mut self, columns: Vec, values: Vec) -> Result<(),crate::Error>; + fn set_row(&mut self, row: usize); + fn call_function(&self, idt: &str, argument: Value) -> Result; + fn has_changes(&self) -> Result; + fn get_dirty_flags(&self) -> Result,crate::Error>; +} + +#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=true))] +#[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] +pub trait ActiveRowTrackerTrait { + fn all_active_rows(&self) -> Result, crate::Error>; + fn all_changes(&self) -> Result, crate::Error>; + fn handle_add(&mut self, row: usize) -> Result<(),crate::Error>; + fn handle_update(&mut self, row: usize) -> Result<(),crate::Error>; + fn handle_remove(&mut self, row: usize) -> Result<(),crate::Error>; + fn is_active(&self, row: usize) -> Result; +} + +#[repr(C)] +#[derive(Serialize, Deserialize, Debug)] +pub struct FFIColumn { + pub name: String, + pub data_type: ValueType, + #[serde(default = "default_is_pk")] + pub is_pk: bool, + #[serde(default = "default_meta_data")] + pub meta_data: String, +} + +// Provide default values for the fields +fn default_is_pk() -> bool { + false +} + +fn default_meta_data() -> String { + String::from("") +} + +#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=true))] +#[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] +pub trait OperatorSchemaTrait { + fn get_schema(&self) -> Result, crate::Error>; + fn get_column_for_index(&self, column: usize) -> Result; + fn get_index_for_column(&self, column: String) -> Result; + + fn add_column(&mut self, column: FFIColumn) -> Result<(), crate::Error>; + fn remove_column(&mut self, column_name: String) -> Result<(), crate::Error>; + + fn get_value(&self, identifier: String, row: usize) -> Result; + fn set_value(&mut self, identifier: String, row: usize, value: Value) -> Result<(),crate::Error>; + fn get_value_for_column(&self, col: usize, row: usize) -> Result; + fn set_value_for_column(&mut self, col: usize, value: Value, row: usize) -> Result<(),crate::Error>; +} + + +#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=false))] +#[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] +pub trait TransposeColumnIndex { + fn col_idx(&self, transpose_index: usize) -> Result; +} + +#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=false))] +#[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] +pub trait TransposeColumnIndexHolder { + fn get_index_for_column(&self, column_name: String) -> Result,crate::Error>; + fn get_index_vec(&self, column_name: String) -> Result,crate::Error>; +} + +impl<'a> Debug for BoxedTransposeColumnIndex<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "BoxedTransposeColumnIndex") + } +} + + + +#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=true))] +#[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] +pub trait OperatorStatusContainerTrait { + fn statuses(&self) -> Result, crate::Error>; + fn changes(&self) -> Result, crate::Error>; + fn add(&mut self, status: u8, context: String) -> Result; + fn remove(&mut self, status: u8) -> Result<(), crate::Error>; + fn contains(&mut self, status: u8) -> Result; +} + /// This macro provides a convenient syntax for creating a static context. /// /// # Examples @@ -156,7 +485,7 @@ macro_rules! context_map { }}; // add a value, and chain the eventual error with the ones in the next values ( ($ctx:expr) $k:expr => $v:expr , $($tt:tt)*) => {{ - $crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $v.into()) + $crate::ContextWithMutableVariables::set_value($ctx, $k.into(), $v.into(),false) .and($crate::context_map!(($ctx) $($tt)*)) }}; diff --git a/src/custom_functions/atan.rs b/src/custom_functions/atan.rs new file mode 100644 index 00000000..3daddd27 --- /dev/null +++ b/src/custom_functions/atan.rs @@ -0,0 +1,78 @@ +use std::convert::TryInto; +use std::fmt::Debug; +use crate::{Error, Value}; +use crate::Error::CustomError; + +pub fn atan>(value: T) -> Result +where + >::Error: Debug, +{ + // Try converting the value to a Value type + match value.try_into().map_err(|err| CustomError(format!("{err:?}")))? { + Value::Float(fl) => { + Ok(Value::Float(fl.atan())) // Calculate atan for floating-point values + } + Value::Int(nn) => { + Ok(Value::Float((nn as f64).atan())) // Convert int to float and calculate atan + } + Value::Empty => Ok(Value::Empty), + _ => Err(Error::CustomError("Invalid argument type passed to atan function".to_string())), + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_atan_with_positive_float() { + let value = Value::Float(1.0); + let result = atan(value).unwrap(); + assert_eq!(result, Value::Float(1.0f64.atan())); + } + + #[test] + fn test_atan_with_positive_integer() { + let value = Value::Int(1); + let result = atan(value).unwrap(); + assert_eq!(result, Value::Float((1 as f64).atan())); + } + + #[test] + fn test_atan_with_zero() { + let value = Value::Int(0); + let result = atan(value).unwrap(); + assert_eq!(result, Value::Float(0.0f64.atan())); + } + + #[test] + fn test_atan_with_negative_float() { + let value = Value::Float(-1.0); + let result = atan(value).unwrap(); + assert_eq!(result, Value::Float((-1.0f64).atan())); + } + + #[test] + fn test_atan_with_negative_integer() { + let value = Value::Int(-1); + let result = atan(value).unwrap(); + assert_eq!(result, Value::Float((-1 as f64).atan())); + } + + #[test] + fn test_atan_with_empty_value() { + let value = Value::Empty; + let result = atan(value).unwrap(); + assert_eq!(result, Value::Empty); + } + + #[test] + fn test_atan_invalid_conversion() { + struct InvalidType; + let value = Value::String("invalid".to_owned().into()); + let result = atan(value); + assert!(result.is_err()); + } +} + diff --git a/src/custom_functions/back.rs b/src/custom_functions/back.rs new file mode 100644 index 00000000..f86325c8 --- /dev/null +++ b/src/custom_functions/back.rs @@ -0,0 +1,9 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn back(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + Ok(row.get_value_for_column(columns[0])?) +} + diff --git a/src/custom_functions/bucket_functions.rs b/src/custom_functions/bucket_functions.rs new file mode 100644 index 00000000..d6b5f716 --- /dev/null +++ b/src/custom_functions/bucket_functions.rs @@ -0,0 +1,177 @@ +use crate::Value; +use crate::Error; +use crate::IntType; +use crate::Error::CustomError; +macro_rules! generate_bucket_functions { + ($($name:ident($($stop:ident),+)),+) => { + $( + paste::paste! { + pub fn $name]),+>(value_to_bucket: T, $($stop: []),+) -> Result + where + T: std::convert::TryInto, + >::Error: core::fmt::Debug, + $([]: std::convert::TryInto, <[] as std::convert::TryInto>::Error: core::fmt::Debug),+ + { + let value_to_bucket: Value = value_to_bucket.try_into().map_err(|_| Error::CustomError("Failed to convert value_to_bucket".to_string()))?; + let stops = vec![$($stop.try_into().map_err(|_| Error::CustomError("Failed to convert stop".to_string()))?),+]; + + // Ensure stops are in ascending order + for i in 0..stops.len() - 1 { + if stops[i] > stops[i + 1] { + return Err(Error::CustomError("Stops must be in ascending order".to_string())); + } + } + + // Determine which bucket the value belongs to + for (i, stop) in stops.iter().enumerate() { + if value_to_bucket <= *stop { + return Ok(Value::Int(i as IntType)); + } + } + + Ok(Value::Int(stops.len() as IntType)) + } + + pub fn [<$name _desc>] ]),+>(value_to_bucket: T, $($stop: []),+) -> Result + where + T: std::convert::TryInto, + >::Error: core::fmt::Debug, + $([]: std::convert::TryInto, <[] as std::convert::TryInto>::Error: core::fmt::Debug),+ + { + let value_to_bucket: Value = value_to_bucket.try_into().map_err(|_| Error::CustomError("Failed to convert value_to_bucket".to_string()))?; + let stops = vec![$($stop.try_into().map_err(|_| Error::CustomError("Failed to convert stop".to_string()))?),+]; + + // Ensure stops are in ascending order + for i in 0..stops.len() - 1 { + if stops[i] > stops[i + 1] { + return Err(Error::CustomError("Stops must be in ascending order".to_string())); + } + } + + // Determine which bucket the value belongs to and return the description + for (i, stop) in stops.iter().enumerate() { + if value_to_bucket <= *stop { + if i == 0 { + return Ok(Value::String(format!(".<= {}", stop).into())); + } else { + return Ok(Value::String(format!("{} - {}", stops[i - 1], stop).into())); + } + } + } + + Ok(Value::String(format!("> {}", stops.last().unwrap()).into())) + } + } + )+ + }; +} + +// Generate bucket and bucket description functions with 2 to 5 stops +generate_bucket_functions! { + bucket_2(stop_1, stop_2), + bucket_3(stop_1, stop_2, stop_3), + bucket_4(stop_1, stop_2, stop_3, stop_4), + bucket_5(stop_1, stop_2, stop_3, stop_4, stop_5) +} + + + +#[cfg(test)] +mod tests { + use crate::Value; + use super::*; + + // Assuming generate_bucket_functions! macro has already generated these functions: + // bucket_2, bucket_2_desc, bucket_3, bucket_3_desc, etc. + + #[test] + fn test_bucket_2() { + let stop_1 = Value::Int(10); + let stop_2 = Value::Int(20); + + assert_eq!(bucket_2(Value::Int(5), stop_1.clone(), stop_2.clone()).unwrap(), Value::Int(0)); + assert_eq!(bucket_2(Value::Int(15), stop_1.clone(), stop_2.clone()).unwrap(), Value::Int(1)); + assert_eq!(bucket_2(Value::Int(25), stop_1.clone(), stop_2.clone()).unwrap(), Value::Int(2)); + } + + #[test] + fn test_bucket_2_desc() { + let stop_1 = Value::Int(10); + let stop_2 = Value::Int(20); + + assert_eq!(bucket_2_desc(Value::Int(5), stop_1.clone(), stop_2.clone()).unwrap(), Value::String("<= 10".into())); + assert_eq!(bucket_2_desc(Value::Int(15), stop_1.clone(), stop_2.clone()).unwrap(), Value::String("10 - 20".into())); + assert_eq!(bucket_2_desc(Value::Int(25), stop_1.clone(), stop_2.clone()).unwrap(), Value::String("> 20".into())); + } + + #[test] + fn test_bucket_3() { + let stop_1 = Value::Int(10); + let stop_2 = Value::Int(20); + let stop_3 = Value::Int(30); + + assert_eq!(bucket_3(Value::Int(5), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::Int(0)); + assert_eq!(bucket_3(Value::Int(15), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::Int(1)); + assert_eq!(bucket_3(Value::Int(25), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::Int(2)); + assert_eq!(bucket_3(Value::Int(35), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::Int(3)); + } + + #[test] + fn test_bucket_3_desc() { + let stop_1 = Value::Int(10); + let stop_2 = Value::Int(20); + let stop_3 = Value::Int(30); + + assert_eq!(bucket_3_desc(Value::Int(5), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::String("<= 10".into())); + assert_eq!(bucket_3_desc(Value::Int(15), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::String("10 - 20".into())); + assert_eq!(bucket_3_desc(Value::Int(25), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::String("20 - 30".into())); + assert_eq!(bucket_3_desc(Value::Int(35), stop_1.clone(), stop_2.clone(), stop_3.clone()).unwrap(), Value::String("> 30".into())); + } + + + #[test] + fn test_bucket_5_desc_with_float_stops() { + let stop_1 = Value::Float(10.5); + let stop_2 = Value::Float(20.5); + let stop_3 = Value::Float(30.5); + let stop_4 = Value::Float(40.5); + let stop_5 = Value::Float(50.5); + + assert_eq!( + bucket_5_desc(Value::Int(5), stop_1.clone(), stop_2.clone(), stop_3.clone(), stop_4.clone(), stop_5.clone()).unwrap(), + Value::String("<= 10.5".into()) + ); + assert_eq!( + bucket_5_desc(Value::Int(15), stop_1.clone(), stop_2.clone(), stop_3.clone(), stop_4.clone(), stop_5.clone()).unwrap(), + Value::String("10.5 - 20.5".into()) + ); + assert_eq!( + bucket_5_desc(Value::Int(25), stop_1.clone(), stop_2.clone(), stop_3.clone(), stop_4.clone(), stop_5.clone()).unwrap(), + Value::String("20.5 - 30.5".into()) + ); + assert_eq!( + bucket_5_desc(Value::Int(35), stop_1.clone(), stop_2.clone(), stop_3.clone(), stop_4.clone(), stop_5.clone()).unwrap(), + Value::String("30.5 - 40.5".into()) + ); + assert_eq!( + bucket_5_desc(Value::Int(45), stop_1.clone(), stop_2.clone(), stop_3.clone(), stop_4.clone(), stop_5.clone()).unwrap(), + Value::String("40.5 - 50.5".into()) + ); + assert_eq!( + bucket_5_desc(Value::Int(55), stop_1.clone(), stop_2.clone(), stop_3.clone(), stop_4.clone(), stop_5.clone()).unwrap(), + Value::String("> 50.5".into()) + ); + } + + #[test] + fn test_invalid_stops() { + let stop_1 = Value::Int(20); + let stop_2 = Value::Int(10); + + let result = bucket_2(stop_1.clone(), stop_1.clone(), stop_2.clone()); + assert!(result.is_err()); + + let desc_result = bucket_2_desc(stop_1.clone(), stop_1.clone(), stop_2.clone()); + assert!(desc_result.is_err()); + } +} diff --git a/src/custom_functions/date_utils.rs b/src/custom_functions/date_utils.rs new file mode 100644 index 00000000..f03fad08 --- /dev/null +++ b/src/custom_functions/date_utils.rs @@ -0,0 +1,89 @@ +use std::convert::TryInto; +use std::fmt::Debug; +use chrono::NaiveDate; +use lazy_static::lazy_static; +use workdays::WorkCalendar; +use crate::{Error, Value}; +use crate::Error::CustomError; + +lazy_static! { + static ref BUSINESS_DAY_CALENDAR: WorkCalendar = WorkCalendar::new(); +} +pub fn business_days_between, TR: TryInto>(start_date: TL, end_date: TR) -> Result +where + >::Error: Debug, + >::Error: Debug, +{ + // Convert inputs to strings and then to NaiveDate + let mut start_date_str = start_date.try_into().map_err(|err| CustomError(format!("{err:?}")))?.as_string()?; + let mut end_date_str = end_date.try_into().map_err(|err| CustomError(format!("{err:?}")))?.as_string()?; + + // Trim the date strings if they are longer than 10 characters + if start_date_str.len() > 10 { + start_date_str = start_date_str[..10].to_string(); + } + if end_date_str.len() > 10 { + end_date_str = end_date_str[..10].to_string(); + } + + // Parse the strings into NaiveDate + let start_date = NaiveDate::parse_from_str(&start_date_str, "%Y-%m-%d") + .map_err(|err| CustomError(format!("Invalid start date format: {err:?}")))?; + let end_date = NaiveDate::parse_from_str(&end_date_str, "%Y-%m-%d") + .map_err(|err| CustomError(format!("Invalid end date format: {err:?}")))?; + + // Ensure end_date is after or equal to start_date + if end_date < start_date { + return Err(CustomError("End date must be after start date".into())); + } + let days = BUSINESS_DAY_CALENDAR.work_days_between(start_date, end_date); + // Return the result as an integer value + Ok(Value::Int(days - 1)) +} + + +#[cfg(test)] +mod tests { + use super::*; + use chrono::NaiveDate; + + #[test] + fn test_business_days_between_valid_range() { + let start_date = "2024-10-20"; // Sunday + let end_date = "2024-10-30"; // Wednesday + let result = business_days_between(start_date, end_date).unwrap(); + assert_eq!(result, Value::Int(8)); // Excludes weekends + } + + #[test] + fn test_business_days_between_same_day() { + let start_date = "2024-10-23"; // Wednesday + let end_date = "2024-10-23"; // Same Wednesday + let result = business_days_between(start_date, end_date).unwrap(); + assert_eq!(result, Value::Int(1)); // 1 business day + } + + #[test] + fn test_business_days_between_weekend_range() { + let start_date = "2024-10-19"; // Saturday + let end_date = "2024-10-22"; // Tuesday + let result = business_days_between(start_date, end_date).unwrap(); + assert_eq!(result, Value::Int(2)); // Excludes the weekend + } + + #[test] + fn test_business_days_between_invalid_date() { + let start_date = "invalid-date"; + let end_date = "2024-10-22"; + let result = business_days_between(start_date, end_date); + assert!(result.is_err()); // Should return an error for invalid date + } + + #[test] + fn test_business_days_between_end_before_start() { + let start_date = "2024-10-25"; // Friday + let end_date = "2024-10-23"; // Wednesday + let result = business_days_between(start_date, end_date); + assert!(result.is_err()); // Should return an error when end_date is before start_date + } +} diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs new file mode 100644 index 00000000..610bcda4 --- /dev/null +++ b/src/custom_functions/expression_functions.rs @@ -0,0 +1,389 @@ +use std::convert::{TryFrom, TryInto}; +use std::fmt::Debug; +use crate::{Error, FloatType, Value}; +use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; +use lazy_static::lazy_static; +use num_traits::real::Real; +use crate::Error::CustomError; + +pub fn is_null>(value: T) -> Result { + Ok(match value.into() { + Value::Empty => Value::Int(0), + v => v, + }) +}pub fn negate>(value: T) -> Result { + Ok(match value.into() { + Value::Empty => Value::Empty, + Value::Int(v) => Value::Int(-v), + Value::Float(v) => Value::Float(-v), + Value::Boolean(v) => Value::Boolean(!v), + v => return Err(Error::CustomError(format!("Cannot negate a value {v:?}"))), + }) +} + +pub fn is_null_or,S: Into>(value: T, alternative: S) -> Result { + Ok(match value.into() { + Value::Empty => alternative.into(), + v => v, + }) +} + +pub fn clip_value_to_range, S: Into>(value: T, constant: S) -> Result { + let value: Value = value.into(); + let constant: Value = constant.into(); + + let value_as_float = value.as_float_or_none()?.unwrap_or(0.0); + let constant_as_float = constant.as_float_or_none()?.unwrap_or(0.0); + + let adjusted_value = if value_as_float > constant_as_float { + constant_as_float + } else if value_as_float < -constant_as_float { + -constant_as_float + } else { + value_as_float + }; + + Ok(Value::Float(adjusted_value)) +} + +pub fn fallback_with_range_clipping, L: Into, C: Into, D: Into>( + should_use_primary: P, + rel_score_long_temp_ps: L, + rel_score_long_temp: L, + range_to_clip: C, + empty_value: D, +) -> Result { + let should_use_primary: Value = should_use_primary.into(); + let rel_score_long_temp_ps: Value = rel_score_long_temp_ps.into(); + let rel_score_long_temp: Value = rel_score_long_temp.into(); + let constant: Value = range_to_clip.into(); + + if should_use_primary.as_boolean_or_none()?.unwrap_or(false) { + if !rel_score_long_temp_ps.is_empty() { + clip_value_to_range(rel_score_long_temp_ps, constant) + } else { + Ok(empty_value.into()) + } + } else { + if !rel_score_long_temp.is_empty() { + clip_value_to_range(rel_score_long_temp, constant) + } else { + Ok(Value::Float(0.0)) // Assuming a default return value + } + } +} + + + +pub fn abs>(value: T) -> Result + where >::Error: Debug +{ + + match value.try_into().map_err(|err| CustomError(format!("{err:?}")))? { + Value::Float(fl) => { Ok(Value::Float(fl.abs())) } + Value::Int(nn) => { Ok(Value::Int(nn.abs())) } + Value::Empty => {Ok(Value::Empty)} + _ => Err(Error::InvalidArgumentType), + } +} +pub fn safe_divide,TR: Into>(left: TL, right: TR) -> Result { + match (left.into(), right.into()) { + (Value::Float(left), Value::Float(right)) => { + if right == 0.0 { + Ok(Value::Empty) + } else { + Ok(Value::Float(left / right)) + } + } + (Value::Int(left), Value::Int(right)) => { + if right == 0 { + Ok(Value::Empty) + } else { + Ok(Value::Int(left / right)) + } + } + (Value::Float(left), Value::Int(right)) => { + if right == 0 { + Ok(Value::Empty) + } else { + Ok(Value::Float(left / right as FloatType)) + } + } + (Value::Int(left), Value::Float(right)) => { + if right == 0.0 { + Ok(Value::Empty) + } else { + Ok(Value::Float(left as FloatType / right)) + } + } + (_, Value::Empty) => { + Ok(Value::Empty) + }, + (Value::Empty,_) => { + Ok(Value::Empty) + } + + _ => Err(Error::InvalidArgumentType), + } +} + +impl TryFrom> for Value { + type Error = String; + + fn try_from(value: Result) -> Result { + match value { + Ok(num) => Ok(num), + Err(e) => Err(e), + } + } +} + +impl TryFrom for bool { + type Error = Error; + + fn try_from(value: Value) -> Result { + match value { + Value::Boolean(b) => Ok(b), + _ => Err(Error::CustomError("Value is not a boolean".into())), + } + } +} + +pub fn substring,TR: TryInto, TC: TryInto>(message: TL, start: TR, len: TC) -> Result +where >::Error: Debug,>::Error: Debug,>::Error: Debug +{ + if let Value::String(message) = message.try_into().map_err(|err| CustomError(format!("{err:?}"))) ?{ + // Ensure start is within bounds and len does not exceed the message length + let start_int = TryInto::::try_into(start).map_err(|err| CustomError(format!("{err:?}")))?.as_int()? as usize; + let len_int = TryInto::::try_into(len).map_err(|err| CustomError(format!("{err:?}")))?.as_int()? as usize; + let message = message.into_owned(); + if start_int < message.len() { + let end = if start_int + len_int > message.len() { message.len() } else { start_int + len_int }; + let substring = &message[start_int..end]; + return Ok(substring.to_string().into()); + } + } + Ok("".to_string().into()) +} + +use chrono::NaiveDate; + + + +pub fn concat, TR: TryInto>(left: TL, right: TR) -> Result +where + >::Error: Debug, + >::Error: Debug, +{ + if let Value::String(left_string) = left.try_into().map_err(|err| CustomError(format!("{err:?}")))? { + if let Value::String(right_string) = right.try_into().map_err(|err| CustomError(format!("{err:?}")))? { + // Concatenate the two strings + let concatenated = format!("{}{}", left_string, right_string); + return Ok(concatenated.into()); + } + } + Ok("".to_string().into()) +} + + +pub fn starts_with,TR: TryInto>(message: TL, prefix: TR) -> Result +where >::Error: Debug,>::Error: Debug +{ + if let (Value::String(message), Value::String(prefix)) = (message.try_into().map_err(|err| CustomError(format!("{err:?}")))?, prefix.try_into().map_err(|err| CustomError(format!("{err:?}")))?) { + let message = message.ref_into_owned(); + let prefix = prefix.ref_into_owned(); + if message.starts_with(&prefix) { + return Ok(Value::Boolean(true)); + } + } + Ok(Value::Boolean(false)) +} + +pub fn ends_with,TR: TryInto>(message: TL, suffix: TR) -> Result +where >::Error: Debug,>::Error: Debug +{ + if let (Value::String(message), Value::String(prefix)) = (message.try_into().map_err(|err| CustomError(format!("{err:?}")))?, suffix.try_into().map_err(|err| CustomError(format!("{err:?}")))?) { + let message = message.ref_into_owned(); + let prefix = prefix.ref_into_owned(); + if message.ends_with(&prefix) { + return Ok(Value::Boolean(true)); + } + } + Ok(Value::Boolean(false)) +} + +pub fn or(left: TL, right: TR) -> Result +where + TL: TryInto, + TR: TryInto, + >::Error: Debug, + >::Error: Debug, +{ + let left: Value = left.try_into().map_err(|err| Error::CustomError(format!("{err:?}")))?; + let right: Value = right.try_into().map_err(|err| Error::CustomError(format!("{err:?}")))?; + + match (left, right) { + (Value::Boolean(l), Value::Boolean(r)) => { + Ok(Value::Boolean(l || r)) + } + _ => Err(Error::CustomError("Both operands must be booleans".to_owned())), + } +} +pub fn and(left: TL, right: TR) -> Result +where + TL: TryInto, + TR: TryInto, + >::Error: Debug, + >::Error: Debug, +{ + let left: Value = left.try_into().map_err(|err| Error::CustomError(format!("{err:?}")))?; + let right: Value = right.try_into().map_err(|err| Error::CustomError(format!("{err:?}")))?; + + match (left, right) { + (Value::Boolean(l), Value::Boolean(r)) => { + Ok(Value::Boolean(l && r)) + } + _ => Err(Error::CustomError("Both operands must be booleans".to_owned())), + } +} +pub fn ternary,TL: Into,TR: Into>(condition: TC, true_value: TL, false_value: TR) -> Result { + if let Value::Boolean(cond) = condition.into() { + if cond { + return Ok(true_value.into()); + } else { + return Ok(false_value.into()); + } + } + // Return an error if the first parameter is not a boolean + Err(Error::CustomError("First parameter must be a boolean".to_owned())) +} + +fn round_datetime_to_precision(datetime: DateTime, precision: &str) -> Result, crate::Error> { + Ok(match precision { + "m1" => datetime.date().and_hms(datetime.hour(), datetime.minute(), 0), + "m5" => datetime.date().and_hms(datetime.hour(), (datetime.minute() / 5) * 5, 0), + "m15" => datetime.date().and_hms(datetime.hour(), (datetime.minute() / 15) * 15, 0), + "m30" => datetime.date().and_hms(datetime.hour(), (datetime.minute() / 30) * 30, 0), + "h1" => datetime.date().and_hms(datetime.hour(), 0, 0), + "h4" => datetime.date().and_hms((datetime.hour() / 4) * 4, 0, 0), + "d1" => datetime.date().and_hms(0, 0, 0), + "1w" => (datetime - Duration::days(datetime.date().weekday().num_days_from_sunday() as i64)).date().and_hms(0, 0, 0), + "1M" => datetime.date().with_day(1).unwrap().and_hms(0, 0, 0), + val => { + return Err(Error::CustomError(format!("Precision {val} is not recognised"))); + } // If the precision is not recognized, return the original datetime + }) +} + +pub fn round_date_to_precision,TR: Into>(string: TL, precision: TR) -> Result { + if let (Value::String(string), Value::String(precision)) = (string.into(), precision.into()) { + // Extract the date-time part from the input string + let string = string.into_owned(); + let precision = precision.into_owned(); + let parts: Vec<&str> = string.split('_').collect(); + let datetime_str = parts.last().ok_or_else(|| Error::InvalidInputString)?; + + let naive_datetime = NaiveDateTime::parse_from_str(datetime_str, "%Y.%m.%d %H:%M:%S") + .map_err(|_| Error::InvalidDateFormat)?; + let datetime = Utc.from_utc_datetime(&naive_datetime); + let rounded_datetime = round_datetime_to_precision(datetime, &precision.to_lowercase())?; + let mut string1 = parts.iter().take(parts.len() - 1).map(|prt| prt.to_string()).collect::>().join("_"); + if string1.len() > 0 { + string1.push_str("_"); + } + let result = format!("{}{}", string1, rounded_datetime.format("%Y.%m.%d %H:%M:%S").to_string()); + Ok(result.into()) + } else { + // If arguments are not strings, return an error + Err(Error::InvalidArgumentType) + } +} + +pub fn max,TR: Into>(value1: TL, value2: TR) -> Result { + let x = value1.into(); + let x1 = value2.into(); + Ok(if x > x1 { + x + } else { + x1 + }) +} +// pub fn atan>(value: T) -> Result +// where >::Error: Debug +// { +// let x = value.try_into()?; +// +// // Apply atan to the value +// let atan_x = x. atan(); +// +// Ok(atan_x) +// } + +pub fn min,TR: Into>(value1: TL, value2: TR) -> Result { + let x = value1.into(); + let x1 = value2.into(); + Ok(if x < x1 { + x + } else { + x1 + }) +} + + +mod test{ + + use super::*; + #[test] + fn test_round_date_to_m1() { + let input = ( + Value::String("BTCUSD_2024.02.13 10:05:23".into()), + Value::String("m1".into()) + ); + let expected = Utc.ymd(2024, 2, 13).and_hms(10, 5, 0).format("%Y.%m.%d %H:%M:%S").to_string(); + let result = round_date_to_precision(&input.0, &input.1).unwrap(); + assert_eq!(result, format!("BTCUSD_{}", expected).into()); + } + + #[test] + fn test_round_date_to_h1() { + let input = ( + Value::String("BTCUSD_2024.02.13 10:05:23".into()), + Value::String("h1".into()) + ); + let expected = Utc.ymd(2024, 2, 13).and_hms(10, 0, 0).format("%Y.%m.%d %H:%M:%S").to_string(); + let result = round_date_to_precision(&input.0, &input.1).unwrap(); + assert_eq!(result, format!("BTCUSD_{}", expected).into()); + } + + #[test] + fn test_round_date_to_1w() { + let input = ( + Value::String("BTCUSD_2024.02.13 10:05:23".into()), + Value::String("1w".into()) + ); + // Assuming 2024-02-13 is a Wednesday, rounding to the start of the week (Sunday) + let expected = Utc.ymd(2024, 2, 11).and_hms(0, 0, 0).format("%Y.%m.%d %H:%M:%S").to_string(); + let result = round_date_to_precision(&input.0, &input.1).unwrap(); + assert_eq!(result, format!("BTCUSD_{}", expected).into()); + } + + #[test] + fn test_invalid_date_format() { + let input = ( + Value::String("BTCUSD_ThisIsNotADate".into()), + Value::String("m1".into()) + ); + let result = round_date_to_precision(&input.0, &input.1); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_precision() { + let input = ( + Value::String("BTCUSD_2024.02.13 10:05:00".into()), + Value::String("m60".into()) + ); + let result = round_date_to_precision(&input.0, &input.1); + assert!(result.is_err(), "Expected an error for invalid precision"); + } +} diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs new file mode 100644 index 00000000..1dc592b8 --- /dev/null +++ b/src/custom_functions/mod.rs @@ -0,0 +1,60 @@ +pub mod triangular_moving_average; +pub mod expression_functions; +pub mod simple_moving_average; +pub mod simple_cumulative_sum; +pub mod back; +pub mod simple_cumilative_returns; +mod rolling_stdev; +mod rolling_min; +pub mod pow; +mod sqrt; +mod atan; +pub mod bucket_functions; +pub mod report_uri_functions; +mod date_utils; + +use std::fmt::Display; +// Re-export the functions to the root of the crate +pub use triangular_moving_average::triangular_moving_average; +pub use triangular_moving_average::columns_len; +pub use expression_functions::*; +pub use back::*; +pub use pow::*; +pub use sqrt::*; +pub use atan::*; +pub use bucket_functions::*; +pub use date_utils::*; +pub use simple_moving_average::simple_moving_average; +pub use simple_cumulative_sum::simple_cumulative_sum; +pub use simple_cumilative_returns::simple_cumulative_returns; +pub use rolling_stdev::rolling_stdev; +pub use rolling_min::rolling_min; +pub use report_uri_functions::*; +use crate::Value; + +pub fn generate_column_name(field: &str, p1: &Value) -> String { + format!("{}__{}", field.to_string(), sanitize_with_char(&get_string(p1), 'x')) +} + +pub fn sanitize_with_char(value: &T, ch: char) -> String { + let mut sanitized = String::new(); + for c in format!("{}", value).chars() { + if c.is_ascii_alphanumeric() { + sanitized.push(c); + } else { + sanitized.push(ch); + } + } + sanitized.replace(&format!("{ch}{ch}"), &format!("{ch}")) +} + +pub fn get_string(value: &Value) -> String { + match value { + Value::String(v) => {format!("{}", v)} + Value::Float(v) => {format!("{}", v)} + Value::Int(v) => {format!("{}", v)} + Value::Boolean(v) => {format!("{}", v)} + Value::Tuple(v) => {format!("{:?}", v)} + Value::Empty => {"null".to_owned()} + } +} diff --git a/src/custom_functions/pow.rs b/src/custom_functions/pow.rs new file mode 100644 index 00000000..0fc7a076 --- /dev/null +++ b/src/custom_functions/pow.rs @@ -0,0 +1,115 @@ +use std::convert::TryInto; +use std::fmt::Debug; +use crate::{Error, Value}; +use crate::Error::CustomError; + +pub fn pow ,E: TryInto>(base: T, exp: E ) -> Result +where + >::Error: Debug, + >::Error: Debug, +{ + // Try converting the base and exponent to Value types + let base = base.try_into().map_err(|err| CustomError(format!("{err:?}")))?; + let exp = exp.try_into().map_err(|err| CustomError(format!("{err:?}")))?; + + // Match on the base value and compute the power + match (base, exp) { + (Value::Float(bf), Value::Float(ef)) => { + Ok(Value::Float(bf.powf(ef))) + } + (Value::Int(bi), Value::Int(ei)) => { + Ok(Value::Int(bi.pow(ei as u32))) // assuming `ei` fits in a `u32` + } + (Value::Float(bf), Value::Int(ei)) => { + Ok(Value::Float(bf.powi(ei as i32))) // assuming `ei` fits in an `i32` + } + (Value::Int(bi), Value::Float(ef)) => { + Ok(Value::Float((bi as f64).powf(ef))) + } + _ => Err(Error::CustomError("Invalidat artument type passed to pow function".to_string())), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_pow_with_floats() { + let base = Value::Float(2.0); + let exp = Value::Float(3.0); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Float(8.0)); + } + + #[test] + fn test_pow_with_integers() { + let base = Value::Int(2); + let exp = Value::Int(3); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Int(8)); + } + + #[test] + fn test_pow_float_base_integer_exp() { + let base = Value::Float(2.0); + let exp = Value::Int(3); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Float(8.0)); + } + + #[test] + fn test_pow_integer_base_float_exp() { + let base = Value::Int(2); + let exp = Value::Float(3.0); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Float(8.0)); + } + + #[test] + fn test_pow_with_zero_exponent() { + let base = Value::Int(5); + let exp = Value::Int(0); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Int(1)); + } + + #[test] + fn test_pow_with_zero_base() { + let base = Value::Int(0); + let exp = Value::Int(5); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Int(0)); + } + + #[test] + fn test_pow_with_negative_exponent() { + let base = Value::Float(2.0); + let exp = Value::Int(-3); + let result = pow(base, exp).unwrap(); + assert_eq!(result, Value::Float(0.125)); + } + + #[test] + fn test_pow_invalid_argument_type() { + let base = Value::Empty; + let exp = Value::Int(2); + let result = pow(base, exp); + assert!(result.is_err()); + if let Err(Error::InvalidArgumentType) = result { + // Test passes + } else { + panic!("Expected Error::InvalidArgumentType"); + } + } + + #[test] + fn test_pow_invalid_conversion() { + struct InvalidType; + let base = Value::String("invalid".to_owned().into()); + let exp = Value::Int(2); + let result = pow(base, &exp); + assert!(result.is_err()); + } +} + diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs new file mode 100644 index 00000000..21250389 --- /dev/null +++ b/src/custom_functions/report_uri_functions.rs @@ -0,0 +1,106 @@ +use std::collections::HashMap; +use std::convert::TryInto; +use std::fmt::{Display}; +use crate::{Error, Value}; +use crate::Error::CustomError; +#[cfg(feature = "serde_json_support")] +use serde_json::json; +//#[cfg(feature = "serde_json_support")] + +#[cfg(feature = "serde_json_support")] +pub fn create_report_reference_nodes_from_operator_uri,TR: TryInto,TRS: TryInto>(uri: TL, output_property_name: TR, report_key_suffix: TRS) -> Result +where >::Error: std::fmt::Display,>::Error: std::fmt::Display,>::Error: std::fmt::Display +{ + let operator_uri = uri.try_into().map_err(|err| CustomError(format!("{err}")))?.as_string()?; + let output_property_name = output_property_name.try_into().map_err(|err| CustomError(format!("{err}")))?.as_string()?; + let report_key_suffix = report_key_suffix.try_into().map_err(|err| CustomError(format!("{err}")))?.as_string()?; + let params = extract_parameters(&operator_uri)? ; + let report_key = extract_report_key(&operator_uri); + let result = json!([{ + "name": "report_reference_node", + "operatorType": "report_reference", + "nodeType": "Persistent", + "parameterValues": params, + "outputOperatorName": output_property_name, + "reportKey": format!("{}{}",report_key,report_key_suffix), + }]); + Ok(Value::String(serde_json::to_string(&result).map_err(|err| CustomError(format!("{err}")))?.into())) +} +pub fn extract_report_key_from_operator_uri>(uri: TL) -> Result +where >::Error: std::fmt::Display +{ + let operator_uri = uri.try_into().map_err(|err| CustomError(format!("{}",err)))?.as_string()?; + Ok(Value::String(extract_report_key(&operator_uri).into())) +} + +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; + +pub fn url_encode(value: &T) -> String { + let encoded = utf8_percent_encode(&format!("{}", value), NON_ALPHANUMERIC).to_string(); + encoded +} + +use percent_encoding::percent_decode_str; + +pub fn url_decode>(value: T) -> Result { + match percent_decode_str(value.as_ref()).decode_utf8() { + Ok(decoded) => Ok(decoded.to_string()), + Err(err) => Err(CustomError(format!("Decoding error: {:?}", err))), + } +} + + + +fn extract_parameters(input_string: &str) -> Result,Error> { + // Find the index of the word 'params' in the string + if let Some(params_index) = input_string.find("params") { + // Strip everything before 'params' + let stripped_string = &input_string[params_index + "params".len()..]; + // Split the string by 'bbb' to get the individual property pairs + let pairs = stripped_string.split("bbb"); + + // Create an empty HashMap to store the parameter values + let mut params_dict = HashMap::new(); + + // Iterate over each pair + for pair in pairs { + // Split each pair by 'yyy' to separate the key and the value + let parts: Vec<&str> = pair.split("yyy").collect(); + if parts.len() == 2 { + let key = parts[0].trim().to_string(); + let value = parts[1].trim().to_string(); + // Add them to the dictionary if both key and value exist + params_dict.insert(key, url_decode(value)?); + } + } + + return Ok(params_dict); + } + + // Return an empty HashMap if 'params' is not found + Ok(HashMap::new()) +} + + +fn extract_report_key(input_string: &str) -> String { + // Find the index of the forward slash '/' + if let Some(slash_index) = input_string.find('/') { + // Extract the part before the slash + let prefix = &input_string[..slash_index]; + + // Remove the starting 'xx' if it exists + if prefix.starts_with("xx") { + return prefix[2..].to_string(); + } else { + return prefix.to_string(); + } + } + + // If no slash is found, handle the prefix normally + let prefix = input_string; + if prefix.starts_with("xx") { + prefix[2..].to_string() + } else { + prefix.to_string() + } +} \ No newline at end of file diff --git a/src/custom_functions/rolling_min.rs b/src/custom_functions/rolling_min.rs new file mode 100644 index 00000000..961d7c7a --- /dev/null +++ b/src/custom_functions/rolling_min.rs @@ -0,0 +1,143 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn rolling_min(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + + let mut min_value: Option = None; + + for &col_index in columns.iter() { + if let Some(value) = row.get_value_for_column(col_index).ok() { + match value { + Value::Float(val) => { + min_value = Some(min_value.map_or(val, |min| min.min(val))); + }, + Value::Int(val) => { + let val = val as f64; + min_value = Some(min_value.map_or(val, |min| min.min(val))); + }, + _ => {} + } + } + } + + match min_value { + Some(min) => Ok(Value::Float(min)), + None => Ok(Value::Empty), + } +} + +#[cfg(test)] +mod tests { + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + use super::*; + + #[test] + fn test_rolling_min_basic_case() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(10.0), + Value::Float(12.0), + Value::Float(23.0), + Value::Float(23.0), + Value::Float(16.0), + Value::Float(23.0), + Value::Float(21.0), + Value::Float(16.0), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Float(10.0), "Expected min value to be 10.0"); + } + + #[test] + fn test_rolling_min_with_empty_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(0.1), + Value::Empty, + Value::Float(0.3), + Value::Empty, + Value::Float(0.5), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Float(0.1), "Expected min value to be 0.1"); + } + + #[test] + fn test_rolling_min_mixed_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + Value::Int(2), + Value::Float(5.0), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Float(1.0), "Expected min value to be 1.0"); + } + + #[test] + fn test_rolling_min_empty_columns() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + ],&mock_holder).into_boxed(); + let columns: Vec = vec![]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Empty, "Expected an Empty value when no columns are provided"); + } + + #[test] + fn test_rolling_min_single_value() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(2.5), + ],&mock_holder).into_boxed(); + let columns = vec![0]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Float(2.5), "Expected min value to be 2.5 when only one value is provided"); + } + + #[test] + fn test_rolling_min_all_empty_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Empty, + Value::Empty, + Value::Empty, + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Empty, "Expected an Empty value when all columns contain Empty values"); + } + + #[test] + fn test_rolling_min_negative_and_positive_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(-10.0), + Value::Float(12.0), + Value::Float(-23.0), + Value::Float(23.0), + Value::Float(-16.0), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = rolling_min(&row, &columns).unwrap(); + + assert_eq!(result, Value::Float(-23.0), "Expected min value to be -23.0"); + } +} + diff --git a/src/custom_functions/rolling_stdev.rs b/src/custom_functions/rolling_stdev.rs new file mode 100644 index 00000000..031450f7 --- /dev/null +++ b/src/custom_functions/rolling_stdev.rs @@ -0,0 +1,147 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn rolling_stdev(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + + // Calculate the mean (average) + let (sum, count) = columns.iter() + .filter_map(|&col_index| match row.get_value_for_column(col_index).ok() { + Some(Value::Float(val)) => Some(val), + Some(Value::Int(val)) => Some(val as f64), + _ => None, + }) + .fold((0.0f64, 0usize), |(acc_sum, acc_count), val| (acc_sum + val, acc_count + 1)); + + if count == 0 { + return Ok(Value::Empty); + } + + let mean = sum / count as f64; + + // Calculate the sum of squared deviations from the mean + let sum_of_squared_diffs = columns.iter() + .filter_map(|&col_index| match row.get_value_for_column(col_index).ok() { + Some(Value::Float(val)) => Some(val), + Some(Value::Int(val)) => Some(val as f64), + _ => None, + }) + .fold(0.0f64, |acc, val| acc + (val - mean).powi(2)); + + // Calculate the standard deviation + let variance = sum_of_squared_diffs / count as f64; + let stdev = variance.sqrt(); + + Ok(Value::Float(stdev)) +} + +#[cfg(test)] +mod tests { + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + use super::*; + + #[test] + fn test_rolling_stdev_basic_case() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(10.0), + Value::Float(12.0), + Value::Float(23.0), + Value::Float(23.0), + Value::Float(16.0), + Value::Float(23.0), + Value::Float(21.0), + Value::Float(16.0), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4, 5, 6, 7]; + let result = rolling_stdev(&row, &columns).unwrap(); + + if let Value::Float(stdev) = result { + assert!((stdev - 4.899).abs() < 1e-3, "Expected stdev to be approximately 4.899, got {}", stdev); + } else { + panic!("Expected a Float value"); + } + } + + #[test] + fn test_rolling_stdev_mixed_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + Value::Int(2), + Value::Float(5.0), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = rolling_stdev(&row, &columns).unwrap(); + + if let Value::Float(stdev) = result { + assert!((stdev - 1.414).abs() < 1e-3, "Expected stdev to be approximately 1.414, got {}", stdev); + } else { + panic!("Expected a Float value"); + } + } + + #[test] + fn test_rolling_stdev_with_empty_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(0.1), + Value::Empty, + Value::Float(0.3), + Value::Empty, + Value::Float(0.5), + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = rolling_stdev(&row, &columns).unwrap(); + + if let Value::Float(stdev) = result { + assert!((stdev - 0.163).abs() < 1e-3, "Expected stdev to be approximately 0.163, got {}", stdev); + } else { + panic!("Expected a Float value"); + } + } + + #[test] + fn test_rolling_stdev_empty_columns() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + ],&mock_holder).into_boxed(); + let columns: Vec = vec![]; + let result = rolling_stdev(&row, &columns).unwrap(); + + assert_eq!(result, Value::Empty, "Expected an Empty value when no columns are provided"); + } + + #[test] + fn test_rolling_stdev_single_value() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(2.5), + ],&mock_holder).into_boxed(); + let columns = vec![0]; + let result = rolling_stdev(&row, &columns).unwrap(); + + assert_eq!(result, Value::Float(0.0), "Expected stdev to be 0.0 when only one value is provided"); + } + + #[test] + fn test_rolling_stdev_all_empty_values() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Empty, + Value::Empty, + Value::Empty, + ],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2]; + let result = rolling_stdev(&row, &columns).unwrap(); + + assert_eq!(result, Value::Empty, "Expected an Empty value when all columns contain Empty values"); + } +} + diff --git a/src/custom_functions/simple_cumilative_returns.rs b/src/custom_functions/simple_cumilative_returns.rs new file mode 100644 index 00000000..07ad821b --- /dev/null +++ b/src/custom_functions/simple_cumilative_returns.rs @@ -0,0 +1,94 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn simple_cumulative_returns(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + + let mut cumulative_return = 1.0f64; + let mut last_val = None; + for &col_index in columns { + if let Some(val) = row.get_value_for_column(col_index).ok() { + last_val = Some(val.clone()); + match val { + Value::Float(val) => { + cumulative_return *= 1.0 + val; + } + Value::Int(val) => { + cumulative_return *= 1.0 + (val as f64); + } + Value::Empty => { + match last_val { + None => {} + Some(val) => { + match val { + Value::Float(val) => { + cumulative_return *= 1.0 + val; + }, + Value::Int(val) => { + cumulative_return *= 1.0 + (val as f64); + }, + Value::Empty => {} + _ => return Err(Error::NonNumericType), + } + } + } + } + _ => return Err(Error::NonNumericType), + } + } + } + + Ok(Value::Float(cumulative_return - 1.0)) +} + +#[cfg(test)] +mod tests { + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + use super::*; + + #[test] + fn test_simple_cumulative_returns_normal_operation() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(0.1), Value::Float(0.2), Value::Float(0.3)],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2]; + let result = simple_cumulative_returns(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(1.1 * 1.2 * 1.3 - 1.0)); + } + + #[test] + fn test_simple_cumulative_returns_partial_data() { + let mock_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(0.1), Value::Empty, Value::Float(0.3)],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2]; + let result = simple_cumulative_returns(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(1.1 * 1.3 - 1.0)); + } + + #[test] + fn test_simple_cumulative_returns_empty_input() { + let mock_holder = MockIndexHolder::new(); + let row= MockRow::from_values(vec![],&mock_holder).into_boxed(); + let columns: Vec = vec![]; + let result = simple_cumulative_returns(&row, &columns).unwrap(); + assert_eq!(result, Value::Empty); + } + + #[test] + fn test_simple_cumulative_returns_no_valid_columns() { + let mock_holder: MockIndexHolder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Empty, Value::Empty],&mock_holder).into_boxed(); + let columns = vec![0, 1]; + let result = simple_cumulative_returns(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(0.0)); // Assuming no valid data means a return of 0.0 + } + + #[test] + fn test_simple_cumulative_returns_mixed_data_types() { + let mock_holder: MockIndexHolder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(0.1), Value::Int(2), Value::Float(-0.1)],&mock_holder).into_boxed(); + let columns = vec![0, 1, 2]; + let result = simple_cumulative_returns(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(1.1 * 3.0 * 0.9 - 1.0)); + } +} diff --git a/src/custom_functions/simple_cumulative_sum.rs b/src/custom_functions/simple_cumulative_sum.rs new file mode 100644 index 00000000..946bcbe2 --- /dev/null +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -0,0 +1,74 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn simple_cumulative_sum(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + // Initialize sum directly as f64 to avoid repeated matching and unwrapping of Value::Float. + let mut sum = 0.0f64; + // Iterate through columns once, accumulating only if the value is a Float. + for &col_index in columns { + // Directly access the value without intermediate matching if it's safe (i.e., within bounds) + // This avoids the need for matching on Option from row.get() + if let Some(val) = row.get_value_for_column(col_index).ok() { + match val { + Value::Float(val) => { + sum += val; + continue; + } + Value::Int(val) => { + sum += val as f64; + continue; + } + Value::Empty => {} + _ => return Err(Error::NonNumericType), + } + } + } + + // Only wrap the final sum into Value::Float here. + Ok(Value::Float(sum)) +} + + +#[cfg(test)] +mod tests { + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + use super::*; + + #[test] + fn test_simple_cumulative_sum_normal_operation() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(10.0), Value::Float(20.0), Value::Float(30.0), Value::Float(40.0)],&holder).into_boxed(); + let columns = vec![0, 1, 2, 3]; + let result = simple_cumulative_sum(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(100.0)); + } + + #[test] + fn test_simple_cumulative_sum_partial_data() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(10.0), Value::Empty, Value::Float(30.0), Value::Empty],&holder).into_boxed(); + let columns = vec![0, 1, 2, 3]; + let result = simple_cumulative_sum(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(40.0)); + } + + #[test] + fn test_simple_cumulative_sum_empty_input() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![],&holder).into_boxed(); + let columns: Vec = vec![]; + let result = simple_cumulative_sum(&row, &columns).unwrap(); + assert_eq!(result, Value::Empty); + } + + #[test] + fn test_simple_cumulative_sum_no_valid_columns() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Empty, Value::Empty],&holder).into_boxed(); + let columns = vec![0, 1]; + let result = simple_cumulative_sum(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(0.0)); // Assuming Value::Float(0.0) for no valid data + } +} diff --git a/src/custom_functions/simple_moving_average.rs b/src/custom_functions/simple_moving_average.rs new file mode 100644 index 00000000..05c9cf75 --- /dev/null +++ b/src/custom_functions/simple_moving_average.rs @@ -0,0 +1,87 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn simple_moving_average(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + + let (sum, count) = columns.iter() + .filter_map(|&col_index| match row.get_value_for_column(col_index).ok() { + Some(Value::Float(val)) => Some(val), + Some(Value::Int(val)) => Some(val as f64), + _ => None, + }) + .fold((0.0f64, 0usize), |(acc_sum, acc_count), val| (acc_sum + val, acc_count + 1)); + + if count > 0 { + Ok(Value::Float(sum / count as f64)) + } else { + Ok(Value::Empty) + } +} + + +#[cfg(test)] +mod tests { + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + use super::*; + + #[test] + fn test_simple_moving_average_normal_operation() { + let column_index = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(10.0), Value::Float(20.0), Value::Float(30.0), Value::Float(40.0)], &column_index).into_boxed(); + let columns = vec![0, 1, 2, 3]; + let result = simple_moving_average(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(25.0)); + } + + #[test] + fn test_simple_moving_average_partial_data() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Float(10.0), Value::Empty, Value::Float(30.0), Value::Empty],&holder).into_boxed(); + let columns = vec![0, 1, 2, 3]; + let result = simple_moving_average(&row, &columns).unwrap(); + assert_eq!(result, Value::Float(20.0)); + } + + #[test] + fn test_simple_moving_average_empty_input() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![],&holder).into_boxed(); + let columns: Vec = vec![]; + let result = simple_moving_average(&row, &columns).unwrap(); + assert_eq!(result, Value::Empty); + } + + #[test] + fn test_simple_moving_average_no_valid_columns() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Empty, Value::Empty],&holder).into_boxed(); + let columns = vec![0, 1]; + let result = simple_moving_average(&row, &columns).unwrap(); + assert_eq!(result, Value::Empty); + } + + //Time: 4.481µs + #[test] + fn test_triangular_moving_average_normal_operation() { + let holder = MockIndexHolder::new(); + let row = MockRow::from_values((0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(),&holder).into_boxed(); // Simple case with enough columns + let columns = (0..110).collect::>(); // Simple case with enough columns + let start = std::time::Instant::now(); + let result = simple_moving_average(&row, &columns); + assert!(result.is_ok()); + let value = result.unwrap(); + match value { + Value::Float(avg) => { + // Perform your assertion here based on expected calculation + // This is just an example; the exact value will depend on your calculation + assert!(avg > 0.0); + }, + _ => panic!("Expected Value::Float from TMA calculation"), + } + println!("Time: {:?}", start.elapsed()); + } + + +} diff --git a/src/custom_functions/sqrt.rs b/src/custom_functions/sqrt.rs new file mode 100644 index 00000000..937612ae --- /dev/null +++ b/src/custom_functions/sqrt.rs @@ -0,0 +1,95 @@ +use std::convert::TryInto; +use std::fmt::Debug; +use crate::{Error, Value}; +use crate::Error::CustomError; + +pub fn sqrt>(value: T) -> Result +where + >::Error: Debug, +{ + // Try converting the value to a Value type + match value.try_into().map_err(|err| CustomError(format!("{err:?}")))? { + Value::Float(fl) => { + if fl < 0.0 { + Err(Error::CustomError("Negative fl value passed to sqrt function".to_string())) + } else { + Ok(Value::Float(fl.sqrt())) + } + } + Value::Int(nn) => { + if nn < 0 { + Err(Error::CustomError("Negative int value passed to sqrt function".to_string())) + } else { + Ok(Value::Float((nn as f64).sqrt())) // Convert to float for square root + } + } + Value::Empty => Ok(Value::Empty), + _ => Err(Error::CustomError("Invalid argument type passed to sqrt function".to_string())), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sqrt_with_positive_float() { + let value = Value::Float(4.0); + let result = sqrt(value).unwrap(); + assert_eq!(result, Value::Float(2.0)); + } + + #[test] + fn test_sqrt_with_positive_integer() { + let value = Value::Int(16); + let result = sqrt(value).unwrap(); + assert_eq!(result, Value::Float(4.0)); + } + + #[test] + fn test_sqrt_with_zero() { + let value = Value::Int(0); + let result = sqrt(value).unwrap(); + assert_eq!(result, Value::Float(0.0)); + } + + #[test] + fn test_sqrt_with_negative_float() { + let value = Value::Float(-4.0); + let result = sqrt(value); + assert!(result.is_err()); + if let Err(Error::InvalidArgumentType) = result { + // Test passes + } else { + panic!("Expected Error::InvalidArgumentType"); + } + } + + #[test] + fn test_sqrt_with_negative_integer() { + let value = Value::Int(-16); + let result = sqrt(value); + assert!(result.is_err()); + if let Err(Error::InvalidArgumentType) = result { + // Test passes + } else { + panic!("Expected Error::InvalidArgumentType"); + } + } + + #[test] + fn test_sqrt_with_empty_value() { + let value = Value::Empty; + let result = sqrt(value).unwrap(); + assert_eq!(result, Value::Empty); + } + + #[test] + fn test_sqrt_invalid_conversion() { + struct InvalidType; + let value = Value::String("invalid".to_owned().into()); + let result = sqrt(value); + assert!(result.is_err()); + } +} + diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs new file mode 100644 index 00000000..432c0f61 --- /dev/null +++ b/src/custom_functions/triangular_moving_average.rs @@ -0,0 +1,162 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; +use crate::Error::UnsupportedOperation; + +pub fn columns_len(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + Ok(Value::Int(columns.len() as i64)) +} +pub fn triangular_moving_average(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { + if columns.len() < 3 { + return Ok(Value::Empty); // Not engh data to calculate + } + + let mut total_sum = 0.0; + let mut total_weight = 0; + let half_length = columns.len() / 2; + + for i in 0..columns.len() { + let mut sum: f64 = 0.0; + let mut sum_weight = 0; + let mut k = 1; + + // Sum over the symmetric window around the current index `i` + for j in 0..=half_length { + // Forward direction + if i + j < columns.len() { + if let Some(price) = get_price(row, columns, i + j) { + let weight = if j == 0 { half_length + 1 } else { half_length + 1 - j }; + sum += price * weight as f64; + sum_weight += weight; + } + } + + // Backward direction, skipping the center when j == 0 + if j != 0 && i >= j { + if let Some(price) = get_price(row, columns, i - j) { + let weight = half_length + 1 - j; + sum += price * weight as f64; + sum_weight += weight; + } + } + } + + total_sum += sum; + total_weight += sum_weight; + } + + if total_weight > 0 { + Ok(Value::Float(total_sum / total_weight as f64)) + } else { + Ok(Value::Empty) + } +} + +fn get_price(row: &BoxedOperatorRowTrait, columns: &[usize], index: usize) -> Option { + row.get_value_for_column(columns[index]).ok().and_then(|value| match value { + Value::Float(val) => Some(val), + Value::Int(val) => Some(val as f64), + _ => None, + }) +} + + +#[cfg(test)] +mod tests { + use std::process::id; + use crate::templates::test_utils::{MockIndex, MockIndexHolder, MockRow}; + use super::*; + + //Time: 4.481µs + #[test] + fn test_triangular_moving_average_normal_operation() { + let mock_index = MockIndexHolder::new(); + let row = MockRow::from_values((0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(), &mock_index); // Simple case with enough columns + let columns = (0..5).collect::>(); // Simple case with enough columns + let start = std::time::Instant::now(); + let result = triangular_moving_average(&BoxedOperatorRowTrait::new(row), &columns); + assert!(result.is_ok()); + let value = result.unwrap(); + match value { + Value::Float(avg) => { + // Perform your assertion here based on expected calculation + // This is just an example; the exact value will depend on your calculation + println!("Average: {}", avg); + assert!(avg > 0.0); + } + _ => panic!("Expected Value::Float from TMA calculation"), + } + println!("Time: {:?}", start.elapsed()); + } + + + #[test] + fn test_triangular_moving_average_empty_input() { + let mock_index = MockIndexHolder::new(); + let row = MockRow::from_values(vec![], &mock_index).into_boxed(); + let columns: Vec = vec![]; + let result = triangular_moving_average(&row, &columns); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Value::Empty); // Assuming Value::Empty for empty input + } + + + #[test] + fn test_triangular_moving_average_empty() { + let mock_index = MockIndexHolder::new(); + let row = MockRow::from_values(vec![],&mock_index).into_boxed(); + let columns = vec![]; + let result = triangular_moving_average(&row, &columns).unwrap(); + assert_eq!(result, Value::Empty); + } + + #[test] + fn test_triangular_moving_average_basic() { + // Setup a simple scenario + + let mock_index_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Int(10), Value::Int(20), Value::Int(30), Value::Int(40), Value::Int(50)], &mock_index_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; // Direct mapping for simplicity + let result = triangular_moving_average(&row, &columns).unwrap(); + + // Expected calculation goes here based on the specific logic of triangular_moving_average + // For simplicity, let's say we expect the average of all values + let expected_average = Value::Float(36.08695652173913); // Placeholder for the actual expected result + + assert_eq!(result, expected_average); + } + + #[test] + fn test_triangular_moving_average_with_floats() { + // Test the function with floating point numbers + let mock_index_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![ + Value::Float(10.5), + Value::Float(20.5), + Value::Float(30.5), + Value::Float(40.5), + Value::Float(50.5) + ], &mock_index_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = triangular_moving_average(&row, &columns).unwrap(); + + // Calculate expected result based on provided logic + let expected_average = Value::Float(30.5); // Placeholder + + assert_eq!(result, expected_average); + } + + #[test] + fn test_triangular_moving_average_invalid_values() { + // Test how the function handles invalid (non-numeric) values + let mock_index_holder = MockIndexHolder::new(); + let row = MockRow::from_values(vec![Value::Empty, Value::Int(20), Value::Empty, Value::Int(40), Value::Empty], &mock_index_holder).into_boxed(); + let columns = vec![0, 1, 2, 3, 4]; + let result = triangular_moving_average(&row, &columns).unwrap(); + + // Expected result considering how non-numeric values are handled + let expected_average = Value::Float(30.0); // Placeholder, assuming non-numeric values are ignored + + assert_eq!(result, expected_average); + } + + // Add more tests as needed, especially to cover the error cases your implementation might have +} diff --git a/src/error/display.rs b/src/error/display.rs index 61e53b92..611a4541 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -44,7 +44,7 @@ impl fmt::Display for EvalexprError { expected_len, actual ), ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual), - AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."), + AppendedToLeafNode(identifier) => write!(f, "Tried to append a node to a leaf node. {:?}", identifier), PrecedenceViolation => write!( f, "Tried to append a node to another node with higher precedence." diff --git a/src/error/mod.rs b/src/error/mod.rs index a4b2d442..75e93327 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -93,7 +93,7 @@ pub enum EvalexprError { /// Tried to append a child to a leaf node. /// Leaf nodes cannot have children. - AppendedToLeafNode, + AppendedToLeafNode(String), /// Tried to append a child to a node such that the precedence of the child is not higher. /// This error should never occur. @@ -382,7 +382,7 @@ mod tests { } ); assert_eq!( - EvalexprError::expected_type(&Value::String("abc".to_string()), Value::Empty), + EvalexprError::expected_type(&"abc".to_string().into(), Value::Empty), EvalexprError::expected_string(Value::Empty) ); assert_eq!( @@ -394,8 +394,8 @@ mod tests { EvalexprError::expected_tuple(Value::Empty) ); assert_eq!( - EvalexprError::expected_type(&Value::Empty, Value::String("abc".to_string())), - EvalexprError::expected_empty(Value::String("abc".to_string())) + EvalexprError::expected_type(&Value::Empty, "abc".to_string().into()), + EvalexprError::expected_empty("abc".to_string().into()) ); } } diff --git a/src/function/builtin.rs b/src/function/builtin.rs index c24e2dc5..51fd245c 100644 --- a/src/function/builtin.rs +++ b/src/function/builtin.rs @@ -184,7 +184,7 @@ pub fn builtin_function(identifier: &str) -> Option { let repl = arguments[2].as_string()?; match Regex::new(&re_str) { Ok(re) => Ok(Value::String( - re.replace_all(&subject, repl.as_str()).to_string(), + re.replace_all(&subject, repl.as_str()).to_string().into(), )), Err(err) => Err(EvalexprError::invalid_regex( re_str.to_string(), @@ -205,7 +205,7 @@ pub fn builtin_function(identifier: &str) -> Option { Ok(Value::from(subject.trim())) })), "str::from" => Some(Function::new(|argument| { - Ok(Value::String(argument.to_string())) + Ok(argument.to_string().into()) })), // Bitwise operators "bitand" => int_function!(bitand, 2), diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 332b704b..578160de 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -142,7 +142,7 @@ pub fn eval_empty(string: &str) -> EvalexprResult { /// *See the [crate doc](index.html) for more examples and explanations of the expression format.* pub fn eval_string_with_context(string: &str, context: &C) -> EvalexprResult { match eval_with_context(string, context) { - Ok(Value::String(string)) => Ok(string), + Ok(Value::String(string)) => Ok(string.into_owned()), Ok(value) => Err(EvalexprError::expected_string(value)), Err(error) => Err(error), } @@ -227,7 +227,7 @@ pub fn eval_string_with_context_mut( context: &mut C, ) -> EvalexprResult { match eval_with_context_mut(string, context) { - Ok(Value::String(string)) => Ok(string), + Ok(Value::String(string)) => Ok(string.into_owned()), Ok(value) => Err(EvalexprError::expected_string(value)), Err(error) => Err(error), } diff --git a/src/lib.rs b/src/lib.rs index 823ed8cf..1be4e94b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ //! assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context), //! Err(EvalexprError::expected_int(Value::from(5.0)))); //! // We can check which value the context stores for a like this -//! assert_eq!(context.get_value("a"), Some(&Value::from(5))); +//! assert_eq!(context.get_value("a").map(std::borrow::Cow::into_owned), Some(Value::from(5))); //! // And use the value in another expression like this //! assert_eq!(eval_int_with_context_mut("a = a + 2; a", &mut context), Ok(7)); //! // It is also possible to save a bit of typing by using an operator-assignment operator @@ -206,7 +206,7 @@ //! assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context), //! Err(EvalexprError::expected_int(5.0.into()))); //! assert_eq!(eval_int_with_context("a", &context), Ok(5)); -//! assert_eq!(context.get_value("a"), Some(5.into()).as_ref()); +//! assert_eq!(context.get_value("a").map(std::borrow::Cow::into_owned), Some(5.into())); //! ``` //! //! For each binary operator, there exists an equivalent operator-assignment operator. @@ -297,8 +297,8 @@ //! // We can write or overwrite variables in expressions... //! assert_eq!(eval_with_context_mut("a = 10; b = 1.0;", &mut context), Ok(().into())); //! // ...and read the value in code like this -//! assert_eq!(context.get_value("a"), Some(&Value::from(10))); -//! assert_eq!(context.get_value("b"), Some(&Value::from(1.0))); +//! assert_eq!(context.get_value("a").map(std::borrow::Cow::into_owned), Some(Value::from(10))); +//! assert_eq!(context.get_value("b").map(std::borrow::Cow::into_owned), Some(Value::from(1.0))); //! ``` //! //! Contexts are also required for user-defined functions. @@ -519,8 +519,6 @@ //! See [LICENSE](LICENSE) for details. //! -#![deny(missing_docs)] -#![forbid(unsafe_code)] #[cfg(feature = "regex_support")] extern crate regex; @@ -534,16 +532,69 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, + Context, + ContextWithMutableFunctions, + ContextWithMutableVariables, + EmptyContext, + OperatorRowTrait, + TransposeColumnIndexHolder, + TransposeColumnIndex, + BoxedTransposeColumnIndex, + BoxedTransposeColumnIndexHolder, + BoxedOperatorRowTrait, + OperatorStatusContainerTrait, + BoxedOperatorStatusContainerTrait, + OperatorSchemaTrait, + BoxedOperatorSchemaTrait, + ActiveRowTrackerTrait, + BoxedActiveRowTrackerTrait, + FFIColumn, HashMapContext, + IndexMapContext }, error::{EvalexprError, EvalexprResult}, function::Function, interface::*, + custom_functions::*, + templates::*, operator::Operator, token::PartialToken, tree::Node, - value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE}, + value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult, to_ffi_result,to_ffi_result_func, to_nested_ffi_result,Error}, +}; + +#[cfg(feature = "serde_json_support")] +pub use crate::{ + context::{ + ActiveRowTrackerTrait_all_active_rows, + ActiveRowTrackerTrait_all_changes, + ActiveRowTrackerTrait_handle_add, + ActiveRowTrackerTrait_handle_update, + ActiveRowTrackerTrait_handle_remove, + ActiveRowTrackerTrait_is_active, + OperatorRowTrait_get_value, + OperatorRowTrait_set_value, + OperatorRowTrait_get_value_for_column, + OperatorRowTrait_set_value_for_column, + OperatorRowTrait_set_row, + OperatorRowTrait_call_function, + OperatorRowTrait_has_changes, + OperatorRowTrait_get_dirty_flags, + OperatorSchemaTrait_get_schema, + OperatorSchemaTrait_get_column_for_index, + OperatorSchemaTrait_get_index_for_column, + OperatorSchemaTrait_add_column, + OperatorSchemaTrait_get_value, + OperatorSchemaTrait_set_value, + OperatorSchemaTrait_get_value_for_column, + OperatorSchemaTrait_set_value_for_column, + OperatorSchemaTrait_remove_column, + OperatorStatusContainerTrait_add, + OperatorStatusContainerTrait_remove, + OperatorStatusContainerTrait_changes, + OperatorStatusContainerTrait_contains, + OperatorStatusContainerTrait_statuses, + } }; mod context; @@ -555,6 +606,8 @@ mod interface; mod operator; mod token; mod tree; -mod value; +mod custom_functions; +mod templates; +pub mod value; // Exports diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 3ebf4a1c..a366e650 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -188,7 +188,7 @@ impl Operator { let mut result = String::with_capacity(a.len() + b.len()); result.push_str(&a); result.push_str(&b); - Ok(Value::String(result)) + Ok(result.into()) } else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { let result = a.checked_add(b); if let Some(result) = result { @@ -425,8 +425,8 @@ impl Operator { VariableIdentifier { identifier } => { expect_operator_argument_amount(arguments.len(), 0)?; - if let Some(value) = context.get_value(identifier).cloned() { - Ok(value) + if let Some(value) = context.get_value(identifier) { + Ok(value.into_owned()) } else { Err(EvalexprError::VariableIdentifierNotFound( identifier.clone(), @@ -464,7 +464,7 @@ impl Operator { Assign => { expect_operator_argument_amount(arguments.len(), 2)?; let target = arguments[0].as_string()?; - context.set_value(target, arguments[1].clone())?; + context.set_value(target, arguments[1].clone(),false)?; Ok(Value::Empty) }, @@ -493,7 +493,7 @@ impl Operator { self ), }?; - context.set_value(target, result)?; + context.set_value(target, result, false)?; Ok(Value::Empty) }, diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs new file mode 100644 index 00000000..bc6712b7 --- /dev/null +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -0,0 +1,172 @@ +use std::collections::HashMap; +use std::fmt::Display; +use crate::{context, get_string, IntType}; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder}; + +pub struct AdaptiveStopLossTradeModel { + signal_field: String, + close_value_field: String, + ask_value_field: String, + trading_range_field_name: String, + instrument_field_name: String, + stop_loss_threshold: FloatType, + take_profit_threshold: FloatType, + break_even_threshold: FloatType +} + + +impl AdaptiveStopLossTradeModel { + pub fn new(instrument_field_name: &str,signal_field_name: &str, close_value_field_name: &str, ask_value_field_name: &str, trading_range_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + AdaptiveStopLossTradeModel { + instrument_field_name: instrument_field_name.to_string(), + signal_field: signal_field_name.to_string(), + close_value_field: close_value_field_name.to_string(), + ask_value_field: ask_value_field_name.to_string(), + trading_range_field_name: trading_range_field_name.to_string(), + stop_loss_threshold, + take_profit_threshold, + break_even_threshold + } + } + } + + +impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { + + fn schema(&self) -> HashMap { + vec![ + ("active_trade", ValueType::Boolean), + ("initiation_date", ValueType::String), + ("trade_id", ValueType::String), + ("reason", ValueType::String), + ("initiation_price", ValueType::Float), + ("exit_price", ValueType::Float), + ("stop_loss", ValueType::Float), + ("trade_age", ValueType::Int), + ("delta", ValueType::Float), + ("take_profit", ValueType::Float), + ("avg_daily_range", ValueType::Float), + ("break_even", ValueType::Float) + ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + } + fn dependencies(&self) -> Vec { + vec![self.instrument_field_name.to_string(), self.signal_field.to_string(), self.close_value_field.to_string(), self.trading_range_field_name.to_string(), self.ask_value_field .to_string()] + } + fn commit_row(&self, row: &mut BoxedOperatorRowTrait,indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + let mut prev_trade_signal: Option = None; + let mut active_trade: Option = None; + let mut initiation_price: Option = None; + let mut exit_price: Option = None; + let mut initiation_date: Option = None; + let mut trade_id: Option = None; + let mut stop_loss: Option = None; + let mut take_profit: Option = None; + //let mut break_even: Option = None; + let mut delta: Option = None; + let mut reason: Option = None; + let mut trade_age: Option = None; + let MIN_TRADE_DURATION = 5; + + if cycle_epoch > 0 { + let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 1]; + active_trade = row.get_value(&generate_column_name("active_trade", transpose_value_before_epoch))?.as_boolean_or_none()?; + trade_id = row.get_value(&generate_column_name("trade_id", transpose_value_before_epoch))?.as_string_or_none()?; + initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_or_none()?; + trade_age = row.get_value(&generate_column_name("trade_age", transpose_value_before_epoch))?.as_int_or_none()?; + initiation_date = row.get_value(&generate_column_name("initiation_date", transpose_value_before_epoch))?.as_string_or_none()?; + stop_loss = row.get_value(&generate_column_name("stop_loss", transpose_value_before_epoch))?.as_float_or_none()?; + take_profit = row.get_value(&generate_column_name("take_profit", transpose_value_before_epoch))?.as_float_or_none()?; + //break_even = row.get_value(&generate_column_name("break_even", transpose_value_before_epoch))?.as_float_or_none()?; + reason = row.get_value(&generate_column_name("reason", transpose_value_before_epoch))?.as_string_or_none()?; + } + + if cycle_epoch > 1 { + let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; + prev_trade_signal = row.get_value(&generate_column_name(&self.signal_field, transpose_value_before_epoch))?.as_boolean_or_none()?; + } + + for i in cycle_epoch ..ordered_transpose_values.len() { + let transpose_value = &ordered_transpose_values[i]; + if let (Some(instrument_name),Some(current_close_value),Some(current_ask_value),Some(trading_range)) = + ( + row.get_value(&generate_column_name(&self.instrument_field_name, transpose_value))?.as_string_or_none()?, + row.get_value(&generate_column_name(&self.close_value_field, transpose_value))?.as_float_or_none()?, + row.get_value(&generate_column_name(&self.ask_value_field, transpose_value))?.as_float_or_none()?, + row.get_value(&generate_column_name(&self.trading_range_field_name, transpose_value))?.as_float_or_none()? + ) + { + let current_signal = row.get_value(&generate_column_name(&self.signal_field, transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + let loop_active_trade = active_trade.is_some_and(|tv| tv); + let mut loop_trade_closed = false; + if loop_active_trade { + let loop_initiation_price = context(initiation_price, "Should have trade initiation price for active trade")?; + let loop_stop_loss = context(stop_loss, "Should have stop loss for active trade")?; + let loop_take_profit = context(take_profit, "Should have take profit active trade")?; + //let loop_break_even = context(break_even, "Should break even on active trade")?; + let next_stop_loss_step = loop_stop_loss + ((trading_range * self.break_even_threshold) * 2f64); + trade_age = Some(context(trade_age, "Should have trade age for active trade")? + 1); + if current_close_value <= loop_stop_loss { + if trade_age.unwrap_or_default() >= MIN_TRADE_DURATION { + loop_trade_closed = true; + exit_price = Some(current_close_value); + delta = Some(current_close_value - loop_initiation_price); + reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss {}({}) from entry price ({}).", delta.unwrap(), current_close_value, loop_stop_loss, self.stop_loss_threshold, loop_initiation_price)); + } + } else if current_close_value >= loop_take_profit { + loop_trade_closed = true; + exit_price = Some(current_close_value); + delta = Some(current_close_value - loop_initiation_price); + reason = Some(format!("Won {} Closing trade. Current price ({}) has reached or exceeded take profit level {} from entry price ({}).",delta.unwrap(), current_close_value,loop_take_profit, loop_initiation_price)); + } else if current_close_value > next_stop_loss_step { + stop_loss = Some(loop_stop_loss + (trading_range * self.break_even_threshold)); + } + } else { + let loop_trade_signal = row.get_value(&generate_column_name(&self.signal_field, transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + if loop_trade_signal { + if !prev_trade_signal.unwrap_or_default() { + initiation_price = Some(current_ask_value); + initiation_date = Some(get_string(&transpose_value)); + trade_id = Some(get_string(&transpose_value) + &instrument_name); + stop_loss = Some(current_close_value - (trading_range *self.stop_loss_threshold)); + take_profit = Some(current_close_value + (trading_range *self.take_profit_threshold)); + //break_even = Some(current_close_value + (trading_range *self.break_even_threshold)); + active_trade = Some(true); + trade_age = Some(0); + } + } + } + + row.set_value(&generate_column_name("active_trade", transpose_value), Value::Boolean(active_trade.unwrap_or_default()))?; + row.set_value(&generate_column_name("reason", transpose_value), reason.clone().map(|rs| Value::String(rs.into())).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("initiation_price", transpose_value), initiation_price.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("initiation_date", transpose_value), initiation_date.clone().map(|rs| Value::String(rs.into())).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("trade_id", transpose_value), trade_id.clone().map(|rs| Value::String(rs.into())).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("delta", transpose_value), delta.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("exit_price", transpose_value), exit_price.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("stop_loss", transpose_value), stop_loss.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("take_profit", transpose_value), take_profit.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("trade_age", transpose_value), trade_age.clone().map(|rs| Value::Int(rs)).unwrap_or(Value::Empty))?; + //row.set_value(&generate_column_name("break_even", transpose_value), break_even.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("avg_daily_range", transpose_value), Value::Float(trading_range.clone()))?; + prev_trade_signal = Some(current_signal); + reason = None; + delta = None; + exit_price = None; + if loop_trade_closed { + active_trade = Some(false); + initiation_price = None; + initiation_date = None; + trade_id = None; + trade_age = None; + stop_loss = None; + take_profit = None; + // break_even = None; + } + } + } + Ok(()) + } +} + + diff --git a/src/templates/bucket_data_discrete.rs b/src/templates/bucket_data_discrete.rs new file mode 100644 index 00000000..1d04a028 --- /dev/null +++ b/src/templates/bucket_data_discrete.rs @@ -0,0 +1,252 @@ +use std::cmp; +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Display; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType, IntType, EvalexprResult}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder, TransposeColumnIndex, TransposeColumnIndexHolder}; +use crate::Error::CustomError; +use crate::templates::test_utils::MockIndexHolder; +use crate::templates::utils::{get_value_indirect, set_value_indirect}; + + +fn to_bucket_field_name(field_name: &str) -> String { + format!("{}bucket", field_name) +} + +fn to_bucket_range_field_name(field_name: &str) -> String { + format!("{}bucketRange", field_name) +} + +pub struct BucketSpec{ + pub field_to_bucket: String, + pub range_stops: Vec<(Value)>, +} + +impl BucketSpec { + pub fn get_bucket(&self, value: &Value) -> u8 { + for (idx, range_stop) in self.range_stops.iter().enumerate() { + if value <= range_stop { + return idx as u8; + } + } + (self.range_stops.len()) as u8 + } +} + +pub struct BucketDataDiscrete { + fields_to_bucket: Vec, + pub generate_bucket_range: bool, +} + + +pub fn bucket_rng(field: &str, mut range_stops: Vec) -> BucketSpec { + range_stops.sort(); + BucketSpec { + field_to_bucket: field.to_string(), + range_stops, + } +} + +impl BucketDataDiscrete { + pub fn new(fields_to_bucket: Vec, generate_bucket_range: bool) -> BucketDataDiscrete { + BucketDataDiscrete { + generate_bucket_range, + fields_to_bucket, + } + } +} +impl CompiledTransposeCalculationTemplate for BucketDataDiscrete { + fn schema(&self) -> HashMap { + self.fields_to_bucket.iter().map(|field| { + let bucket_field_name = to_bucket_field_name(&field.field_to_bucket); + let bucket_range_field_name = to_bucket_range_field_name(&field.field_to_bucket); + vec![ + (bucket_field_name.clone(), ValueType::Int), + (bucket_range_field_name.clone(), ValueType::String), + ] + }).flatten().collect() + } + fn dependencies(&self) -> Vec { + self.fields_to_bucket.iter().map(|field| field.field_to_bucket.clone()).collect() + } + fn commit_row( + &self, + row: &mut BoxedOperatorRowTrait, + indexes: &BoxedTransposeColumnIndexHolder, + ordered_transpose_values: &[Value], + cycle_epoch: usize, + ) -> Result<(), Error> { + + // Maps to hold value-to-bucket and range information + + let values = &row.get_values()?; + let mut output_values = vec![Value::Empty; values.len()]; + let mut modified_columns = vec![]; + + for bucket_spec in &self.fields_to_bucket { + let mut min_values_for_bucket: HashMap = HashMap::new(); + let mut max_values_for_bucket: HashMap = HashMap::new(); + let mut index_to_bucket_map: HashMap = HashMap::new(); + // Get column indexes + let field_name = bucket_spec.field_to_bucket.clone(); + let field_to_bucket_index = indexes.get_index_vec(field_name.clone())?; + let bucket_range_index = indexes.get_index_vec(to_bucket_range_field_name(&field_name))?; + let bucket_index = indexes.get_index_vec(to_bucket_field_name(&field_name))?; + + // Populate transpose_value_to_field_value_map + for idx in cycle_epoch ..ordered_transpose_values.len() { + let bucket_value = get_value_indirect(values, &field_to_bucket_index, idx)?; + if bucket_value == &Value::Empty { + continue; + } + let bucket = bucket_spec.get_bucket(bucket_value); + set_value_indirect(&mut output_values, &mut modified_columns, &bucket_index, idx, Value::Int(bucket as IntType))?; + if self.generate_bucket_range { + index_to_bucket_map.insert(idx, bucket); + min_values_for_bucket + .entry(bucket) + .and_modify(|min_value| if bucket_value < min_value { *min_value = bucket_value.clone(); }) + .or_insert(bucket_value.clone()); + max_values_for_bucket + .entry(bucket) + .and_modify(|max_value| if bucket_value > max_value { *max_value = bucket_value.clone(); }) + .or_insert(bucket_value.clone()); + } + } + + if self.generate_bucket_range { + for (idx, bucket) in index_to_bucket_map.iter() { + let bucket_range = format!( + "{} to {}", + min_values_for_bucket.get(&bucket).unwrap_or(&Value::Empty), + max_values_for_bucket.get(&bucket).unwrap_or(&Value::Empty) + ); + set_value_indirect(&mut output_values, &mut modified_columns, &bucket_range_index, *idx, Value::String(bucket_range.clone().into()))?; + } + } + } + row.set_values_for_columns(modified_columns, output_values)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + + #[test] + fn test_commit_row_basic() { + // Create a mock row with initial values + let field_to_bucket = "price"; + + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + // Instantiate BucketSpec with dummy range stops + let bucket_spec = bucket_rng(field_to_bucket, vec![Value::Float(1.5), Value::Float(2.5)]); + let bucket_data = BucketDataDiscrete { + fields_to_bucket: vec![bucket_spec], + generate_bucket_range: true, + }; + + let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); + let mut row = MockRow::new(&mock_index); + row.set_value(&generate_column_name(field_to_bucket, &"date1".into()), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date2".into()), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date3".into()), Value::Float(2.0)).unwrap(); + + // Call commit_row and check the results + let mut operator_row = BoxedOperatorRowTrait::new(row); + let mut mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + bucket_data.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(operator_row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date1".into())).unwrap(), Value::Int(0)); + assert_eq!(operator_row.get_value(&generate_column_name(&to_bucket_range_field_name(field_to_bucket), &"date1".into())).unwrap(), Value::String("1 to 1".into())); + assert_eq!(operator_row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date2".into())).unwrap(), Value::Int(2)); + assert_eq!(operator_row.get_value(&generate_column_name(&to_bucket_range_field_name(field_to_bucket), &"date2".into())).unwrap(), Value::String("3 to 3".into())); + assert_eq!(operator_row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date3".into())).unwrap(), Value::Int(1)); + assert_eq!(operator_row.get_value(&generate_column_name(&to_bucket_range_field_name(field_to_bucket), &"date3".into())).unwrap(), Value::String("2 to 2".into())); + } + + fn create_mock_index(ordered_transpose_values: &Vec, bucket_data: &BucketDataDiscrete) -> MockIndexHolder { + let mut mock_index = MockIndexHolder::new(); + for field in &bucket_data.fields_to_bucket { + mock_index.register_index(field.field_to_bucket.to_string(), &ordered_transpose_values); + mock_index.register_index(to_bucket_field_name(&field.field_to_bucket), &ordered_transpose_values); + mock_index.register_index(to_bucket_range_field_name(&field.field_to_bucket), &ordered_transpose_values); + } + mock_index + } + + #[test] + fn test_commit_row_with_equal_values() { + // Create a mock row with identical values + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + let field_to_bucket = "price"; + + // Instantiate BucketSpec with dummy range stops + let bucket_spec = bucket_rng(field_to_bucket, vec![Value::Float(1.5), Value::Float(2.5)]); + let bucket_data = BucketDataDiscrete { + fields_to_bucket: vec![bucket_spec], + generate_bucket_range: false, + }; + + let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); + let mut row = MockRow::new(&mock_index); + row.set_value(&generate_column_name(field_to_bucket, &"date1".to_owned().into()), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date2".to_owned().into()), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date3".to_owned().into()), Value::Float(2.0)).unwrap(); + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + let mut mock_index = BoxedTransposeColumnIndexHolder::new(&mock_index); + bucket_data.commit_row(&mut row, &mock_index, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date1".into())).unwrap(), Value::Int(1)); + assert_eq!(row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date2".into())).unwrap(), Value::Int(1)); + assert_eq!(row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date3".into())).unwrap(), Value::Int(1)); + } + + #[test] + fn test_commit_row_with_varied_values() { + // Create a mock row with varied values + let field_to_bucket = "price"; + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + // Instantiate BucketSpec with dummy range stops + let bucket_spec = bucket_rng(field_to_bucket, vec![Value::Float(1.5), Value::Float(2.5)]); + let bucket_data = BucketDataDiscrete { + fields_to_bucket: vec![bucket_spec], + generate_bucket_range: false, + }; + + let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); + let mut row = MockRow::new(&mock_index); + row.set_value(&generate_column_name(field_to_bucket, &"date1".to_owned().into()), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date2".to_owned().into()), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date3".to_owned().into()), Value::Float(2.0)).unwrap(); + + let mut index = BoxedTransposeColumnIndexHolder::new(&mock_index); + let mut row = BoxedOperatorRowTrait::new(row); + bucket_data.commit_row(&mut row, &index, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date1".into())).unwrap(), Value::Int(0)); + assert_eq!(row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date2".into())).unwrap(), Value::Int(2)); + assert_eq!(row.get_value(&generate_column_name(&to_bucket_field_name(field_to_bucket), &"date3".into())).unwrap(), Value::Int(1)); + } +} diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs new file mode 100644 index 00000000..5e0c8c25 --- /dev/null +++ b/src/templates/bucket_data_template.rs @@ -0,0 +1,238 @@ +use std::cmp; +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Display; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType, IntType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder, TransposeColumnIndex, TransposeColumnIndexHolder}; +use crate::Error::CustomError; +use crate::templates::test_utils::MockIndexHolder; +use crate::templates::utils::{get_value_indirect, set_value_indirect}; + + +fn to_bucket_field_name(field_name: &str) -> String { + format!("{}bucket", field_name) +} + +fn to_bucket_range_field_name(field_name: &str) -> String { + format!("{}bucketRange", field_name) +} +pub struct BucketData { + fields_to_bucket: Vec, + no_buckets: u8, +} + +impl BucketData { + pub fn new(fields_to_bucket: Vec<&str>, mut no_buckets: u8) -> BucketData { + if no_buckets == 0 { + no_buckets = 1; + } + BucketData { + fields_to_bucket: fields_to_bucket.iter().map(|fld| fld.to_string()).collect(), + no_buckets, + } + } +} +impl CompiledTransposeCalculationTemplate for BucketData { + fn schema(&self) -> HashMap { + self.fields_to_bucket.iter().map(|field| { + let bucket_field_name = to_bucket_field_name(field); + let bucket_range_field_name = to_bucket_range_field_name(field); + vec![ + (bucket_field_name.clone(), ValueType::Int), + (bucket_range_field_name.clone(), ValueType::String), + ] + }).flatten().collect() + } + fn dependencies(&self) -> Vec { + self.fields_to_bucket.clone() + } + fn commit_row( + &self, + row: &mut BoxedOperatorRowTrait, + indexes: &BoxedTransposeColumnIndexHolder, + ordered_transpose_values: &[Value], + cycle_epoch: usize, + ) -> Result<(), Error> { + + // Maps to hold value-to-bucket and range information + + let values = &row.get_values()?; + let mut output_values = vec![Value::Empty; values.len()]; + let mut modified_columns = vec![]; + + for field in &self.fields_to_bucket { + let mut value_to_bucket_map: BTreeMap> = BTreeMap::new(); + let mut min_values_for_bucket: HashMap = HashMap::new(); + let mut max_values_for_bucket: HashMap = HashMap::new(); + + // Get column indexes + let field_to_bucket_index = indexes.get_index_vec(field.clone())?; + let bucket_range_index = indexes.get_index_vec(to_bucket_range_field_name(field))?; + let bucket_index = indexes.get_index_vec(to_bucket_field_name(&field))?; + + // Populate transpose_value_to_field_value_map + for (idx, transpose_value) in ordered_transpose_values.iter().enumerate() { + let field_to_bucket = get_value_indirect(values, &field_to_bucket_index, idx)?; + if field_to_bucket == &Value::Empty { + continue; + } + value_to_bucket_map.entry(field_to_bucket.clone()).or_default().push(idx); + } + + let num_buckets = self.no_buckets; + let no_values_in_value_to_bucket_map = value_to_bucket_map.len(); + for (idx, (bucket_value, transpose_columns_for_value)) in value_to_bucket_map.iter().enumerate() { + let bucket = ((idx * num_buckets as usize) / no_values_in_value_to_bucket_map) as u8; + + for val in transpose_columns_for_value { + set_value_indirect(&mut output_values, &mut modified_columns, &bucket_index, *val, Value::Int((num_buckets - bucket) as IntType))?; + } + min_values_for_bucket + .entry(bucket) + .and_modify(|min_value| if bucket_value < min_value { *min_value = bucket_value.clone(); }) + .or_insert(bucket_value.clone()); + max_values_for_bucket + .entry(bucket) + .and_modify(|max_value| if bucket_value > max_value { *max_value = bucket_value.clone(); }) + .or_insert(bucket_value.clone()); + } + + for (idx, (bucket_value, transpose_columns_for_value)) in value_to_bucket_map.iter().enumerate() { + let bucket = ((idx * num_buckets as usize) / no_values_in_value_to_bucket_map) as u8; + let bucket_range = format!( + "{} to {}", + min_values_for_bucket.get(&bucket).unwrap_or(&Value::Empty), + max_values_for_bucket.get(&bucket).unwrap_or(&Value::Empty) + ); + for val in transpose_columns_for_value { + set_value_indirect(&mut output_values, &mut modified_columns, &bucket_range_index, *val, Value::String(bucket_range.clone().into()))?; + } + } + } + row.set_values_for_columns(modified_columns, output_values)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + + // Mock implementation of BoxedOperatorRowTrait for testing purposes + + + #[test] + fn test_commit_row_basic() { + // Create a mock row with initial values + + let field_to_bucket = "price"; + + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + // Instantiate BucketData with dummy values + let bucket_data = BucketData { + fields_to_bucket: vec![field_to_bucket.to_string()], + no_buckets: 3, + }; + + let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); + let mut row = MockRow::new(&mock_index); + row.set_value(&generate_column_name(field_to_bucket, &"date1".into()), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date2".into()), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date3".into()), Value::Float(2.0)).unwrap(); + + + // Ordered transpose values (these should correspond to the field names) + + // Call commit_row and check the results + let mut operator_row = BoxedOperatorRowTrait::new(row); + let mut mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + bucket_data.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(operator_row.get_value("pricebucket__date1").unwrap(), Value::Int(3)); + assert_eq!(operator_row.get_value("pricebucketRange__date1").unwrap(), "1 to 1".into()); + assert_eq!(operator_row.get_value("pricebucket__date2").unwrap(), Value::Int(1)); + assert_eq!(operator_row.get_value("pricebucketRange__date2").unwrap(), "3 to 3".into()); + assert_eq!(operator_row.get_value("pricebucket__date3").unwrap(), Value::Int(2)); + assert_eq!(operator_row.get_value("pricebucketRange__date3").unwrap(), "2 to 2".into()); + } + + fn create_mock_index(ordered_transpose_values: &Vec, bucket_data: &BucketData) -> MockIndexHolder { + let mut mock_index = MockIndexHolder::new(); + for field in &bucket_data.fields_to_bucket { + mock_index.register_index(field.to_string(), &ordered_transpose_values); + mock_index.register_index(to_bucket_field_name(field), &ordered_transpose_values); + mock_index.register_index(to_bucket_range_field_name(field), &ordered_transpose_values); + } + mock_index + } + + #[test] + fn test_commit_row_with_equal_values() { + // Create a mock row with identical values + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + let field_to_bucket = "price"; + // Instantiate BucketData with dummy values + let bucket_data = BucketData { + fields_to_bucket: vec![field_to_bucket.to_string()], + no_buckets: 3, + }; + let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); + let mut row = MockRow::new(&mock_index); + row.set_value(&generate_column_name(field_to_bucket, &"date1".to_owned().into()), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date2".to_owned().into()), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date3".to_owned().into()), Value::Float(2.0)).unwrap(); + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + let mut mock_index = BoxedTransposeColumnIndexHolder::new(&mock_index); + bucket_data.commit_row(&mut row, &mock_index, &ordered_transpose_values, 0).unwrap(); + // Check that the correct bucket values have been set + assert_eq!(row.get_value("pricebucket__date1").unwrap(), Value::Int(3)); + assert_eq!(row.get_value("pricebucket__date2").unwrap(), Value::Int(3)); + assert_eq!(row.get_value("pricebucket__date3").unwrap(), Value::Int(3)); + } + + + #[test] + fn test_commit_row_with_varied_values() { + // Create a mock row with varied values + let field_to_bucket = "price"; + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + // Instantiate BucketData with dummy values + let bucket_data = BucketData { + fields_to_bucket: vec![field_to_bucket.to_string()], + no_buckets: 3, + }; + let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); + let mut row = MockRow::new(&mock_index); + // Ordered transpose values (these should correspond to the field names) + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + row.set_value(&generate_column_name(field_to_bucket, &"date1".to_owned().into()), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date2".to_owned().into()), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &"date3".to_owned().into()), Value::Float(2.0)).unwrap(); + + let mut index = BoxedTransposeColumnIndexHolder::new(&mock_index); + bucket_data.commit_row(&mut row, &index, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(row.get_value("pricebucket__date1").unwrap(), Value::Int(3)); + assert_eq!(row.get_value("pricebucket__date2").unwrap(), Value::Int(1)); + assert_eq!(row.get_value("pricebucket__date3").unwrap(), Value::Int(2)); + } +} diff --git a/src/templates/channel_monitoring_template.rs b/src/templates/channel_monitoring_template.rs new file mode 100644 index 00000000..4d96b9bb --- /dev/null +++ b/src/templates/channel_monitoring_template.rs @@ -0,0 +1,219 @@ +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder}; + +pub struct ChannelMonitor { + lower_band_field_name: String, + upper_band_field_name: String, + value_field_name: String, + output_field_name: String, + within_bounds_field_name: String, + left_above_field_name: String, + left_below_field_name: String, + re_entered_above_field_name: String, + re_entered_below_field_name: String +} + +impl ChannelMonitor { + pub fn new(lower_band_field_name: &str, upper_band_field_name: &str, value_field_name: &str, output_field_name: &str) -> ChannelMonitor { + + let within_bounds_field_name = format!("{}_within_bounds", output_field_name); + let left_above_field_name = format!("{}_left_above", output_field_name); + let left_below_field_name = format!("{}_left_below", output_field_name); + let re_entered_above_field_name = format!("{}_re_entered_above", output_field_name); + let re_entered_below_field_name = format!("{}_re_entered_below", output_field_name); + + ChannelMonitor { + lower_band_field_name : lower_band_field_name.to_owned(), + upper_band_field_name : upper_band_field_name.to_owned(), + value_field_name : value_field_name.to_owned(), + within_bounds_field_name, + left_above_field_name, + left_below_field_name, + re_entered_above_field_name, + re_entered_below_field_name, + output_field_name : output_field_name.to_owned() + } + } +} + +impl CompiledTransposeCalculationTemplate for ChannelMonitor { + + fn schema(&self) -> HashMap { + vec![ + (self.within_bounds_field_name.clone(), ValueType::Boolean), + (self.within_bounds_field_name.clone(), ValueType::Boolean), + (self.left_above_field_name.clone(), ValueType::Boolean), + (self.left_below_field_name.clone(), ValueType::Boolean), + (self.re_entered_above_field_name.clone(), ValueType::Boolean), + (self.re_entered_below_field_name.clone(), ValueType::Boolean), + ].iter().map(|(nm, val)| (nm.to_string(), *val)).collect() + } + fn dependencies(&self) -> Vec { + vec![ + self.lower_band_field_name.clone(), + self.upper_band_field_name.clone(), + self.value_field_name.clone() + ] + } + fn commit_row(&self, row: &mut BoxedOperatorRowTrait ,indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + + let mut within_bounds: Option = None; + let mut left_above: Option = None; + let mut left_below: Option = None; + + if cycle_epoch > 0 { + let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 1]; + within_bounds = row.get_value(&generate_column_name(&self.within_bounds_field_name, transpose_value_before_epoch))?.as_boolean_or_none()?; + left_above = row.get_value(&generate_column_name(&self.left_above_field_name, transpose_value_before_epoch))?.as_boolean_or_none()?; + left_below = row.get_value(&generate_column_name(&self.left_below_field_name, transpose_value_before_epoch))?.as_boolean_or_none()?; + } + + for i in cycle_epoch ..ordered_transpose_values.len() { + let transpose_value = &ordered_transpose_values[i]; + + let loop_lower_band = row.get_value(&generate_column_name(&self.lower_band_field_name, transpose_value))?.as_float_or_none()?; + let loop_upper_band = row.get_value(&generate_column_name(&self.upper_band_field_name, transpose_value))?.as_float_or_none()?; + let loop_value = row.get_value(&generate_column_name(&self.value_field_name, transpose_value))?.as_float_or_none()?; + + if let (Some(lower_band), Some(upper_band), Some(value)) = (loop_lower_band, loop_upper_band, loop_value) { + let loop_within_bounds = value >= lower_band && value <= upper_band; + let loop_left_above = value > upper_band; + let loop_left_below = value < lower_band; + let loop_re_entered_above = loop_within_bounds && left_above.unwrap_or_default(); + let loop_re_entered_below = loop_within_bounds && left_below.unwrap_or_default(); + + row.set_value(&generate_column_name(&self.within_bounds_field_name, transpose_value), Value::Boolean(loop_within_bounds))?; + row.set_value(&generate_column_name(&self.left_above_field_name, transpose_value), Value::Boolean(loop_left_above))?; + row.set_value(&generate_column_name(&self.left_below_field_name, transpose_value), Value::Boolean(loop_left_below))?; + row.set_value(&generate_column_name(&self.re_entered_above_field_name, transpose_value), Value::Boolean(loop_re_entered_above))?; + row.set_value(&generate_column_name(&self.re_entered_below_field_name, transpose_value), Value::Boolean(loop_re_entered_below))?; + + within_bounds = Some(loop_within_bounds); + left_above = Some(loop_left_above); + left_below = Some(loop_left_below); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod commit_row_tests { + use super::*; + use std::collections::HashMap; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + + #[test] + fn test_commit_row_value_below_lower_band() -> Result<(), Error> { + let monitor = ChannelMonitor::new( + "lower_band", + "upper_band", + "value", + "output", + ); + + let holder = MockIndexHolder::new(); + let mut row = MockRow::new(&holder); + let cycle_epoch = 0; + let transpose_value = Value::String("02_01_2024".into()); + let ordered_transpose_values = vec![transpose_value.clone()]; + + row.insert_value(generate_column_name("lower_band", &transpose_value), Value::Float(10.0)); + row.insert_value(generate_column_name("upper_band", &transpose_value), Value::Float(20.0)); + row.insert_value(generate_column_name("value", &transpose_value), Value::Float(5.0)); + + let mut row_trait = BoxedOperatorRowTrait::new(row); + let mut index_hodlder_trait = BoxedTransposeColumnIndexHolder::new(&holder); + monitor.commit_row(&mut row_trait,&index_hodlder_trait, &ordered_transpose_values, cycle_epoch)?; + + assert_eq!(row_trait.get_value(&generate_column_name("output_within_bounds", &transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_left_above", &transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_left_below", &transpose_value))?, Value::Boolean(true)); + + Ok(()) + } + + #[test] + fn test_commit_row_re_entered_above_after_being_below() -> Result<(), Error> { + let monitor = ChannelMonitor::new( + "lower_band", + "upper_band", + "value", + "output", + ); + + let mock_index_column_holder = MockIndexHolder::new(); + let mut row = MockRow::new(&mock_index_column_holder); + let prev_transpose_value = Value::String("01_01_2024".into()); + let current_transpose_value = Value::String("02_01_2024".into()); + let ordered_transpose_values = vec![prev_transpose_value.clone(), current_transpose_value.clone()]; + + // Previous epoch - below lower band + + row.insert_value(generate_column_name("lower_band", &prev_transpose_value), Value::Float(10.0)); + row.insert_value(generate_column_name("upper_band", &prev_transpose_value), Value::Float(20.0)); + row.insert_value(generate_column_name("value", &prev_transpose_value), Value::Float(5.0)); + + // Current epoch - within bounds, simulating a re-entry from below + row.insert_value(generate_column_name("lower_band", ¤t_transpose_value), Value::Float(10.0)); + row.insert_value(generate_column_name("upper_band", ¤t_transpose_value), Value::Float(20.0)); + row.insert_value(generate_column_name("value", ¤t_transpose_value), Value::Float(15.0)); + + let mut row_trait = BoxedOperatorRowTrait::new(row); + let mut index_holder_trait = BoxedTransposeColumnIndexHolder::new(&mock_index_column_holder); + monitor.commit_row(&mut row_trait,&index_holder_trait, &ordered_transpose_values, 0)?; + + assert_eq!(row_trait.get_value(&generate_column_name("output_within_bounds", &prev_transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_left_below", &prev_transpose_value))?, Value::Boolean(true)); + assert_eq!(row_trait.get_value(&generate_column_name("output_left_above", &prev_transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_re_entered_above", &prev_transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_re_entered_below", &prev_transpose_value))?, Value::Boolean(false)); + + + assert_eq!(row_trait.get_value(&generate_column_name("output_within_bounds", ¤t_transpose_value))?, Value::Boolean(true)); + assert_eq!(row_trait.get_value(&generate_column_name("output_re_entered_above", ¤t_transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_re_entered_below", ¤t_transpose_value))?, Value::Boolean(true)); + + Ok(()) + } + + #[test] + fn test_commit_row_re_entered_below_after_being_above() -> Result<(), Error> { + let monitor = ChannelMonitor::new( + "lower_band", + "upper_band", + "value", + "output", + ); + + let mut mock_index_column_holder = MockIndexHolder::new(); + let mut row = MockRow::new(&mock_index_column_holder); + let prev_transpose_value = Value::String("01_01_2024".into()); + let current_transpose_value = Value::String("02_01_2024".into()); + let ordered_transpose_values = vec![prev_transpose_value.clone(), current_transpose_value.clone()]; + + // Previous epoch - above upper band + + row.insert_value(generate_column_name("lower_band", &prev_transpose_value), Value::Float(10.0)); + row.insert_value(generate_column_name("upper_band", &prev_transpose_value), Value::Float(20.0)); + row.insert_value(generate_column_name("value", &prev_transpose_value), Value::Float(25.0)); + + // Current epoch - within bounds, simulating a re-entry from above + row.insert_value(generate_column_name("lower_band", ¤t_transpose_value), Value::Float(10.0)); + row.insert_value(generate_column_name("upper_band", ¤t_transpose_value), Value::Float(20.0)); + row.insert_value(generate_column_name("value", ¤t_transpose_value), Value::Float(15.0)); + + let mut row_trait = BoxedOperatorRowTrait::new(row); + let mut index_holder_trait = BoxedTransposeColumnIndexHolder::new(&mock_index_column_holder); + monitor.commit_row(&mut row_trait,&index_holder_trait, &ordered_transpose_values, 0)?; + + assert_eq!(row_trait.get_value(&generate_column_name("output_re_entered_below", ¤t_transpose_value))?, Value::Boolean(false)); + assert_eq!(row_trait.get_value(&generate_column_name("output_re_entered_above", ¤t_transpose_value))?, Value::Boolean(true)); + + Ok(()) + } + +} \ No newline at end of file diff --git a/src/templates/compiled_transpose_calculation_template.rs b/src/templates/compiled_transpose_calculation_template.rs new file mode 100644 index 00000000..71df8749 --- /dev/null +++ b/src/templates/compiled_transpose_calculation_template.rs @@ -0,0 +1,50 @@ +use std::any::Any; +use std::collections::HashMap; +use std::fmt::{Debug, Display}; +use std::panic; +use std::panic::AssertUnwindSafe; +use thin_trait_object::thin_trait_object; +use crate::{BoxedOperatorRowTrait, Error, Value, ValueType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder}; +use crate::Error::CustomError; + + +#[thin_trait_object] +pub trait CompiledTransposeCalculationTemplate : Send { + + fn schema(&self) -> HashMap; + fn dependencies(&self) -> Vec; + fn commit_row(&self, row: &mut BoxedOperatorRowTrait,indexes: &BoxedTransposeColumnIndexHolder,ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>; + fn commit_row_wrapped(&self, row: &mut BoxedOperatorRowTrait,indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>{ + panic::catch_unwind(AssertUnwindSafe(|| self.commit_row(row, indexes,ordered_transpose_values, cycle_epoch))).map_err(|err| Error::CustomError(format!("{}", extract_message_from_any_error(err))))? + } +} + +pub fn extract_message_from_any_error(err: Box) -> String { + if let Some(s) = err.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = err.downcast_ref::() { + s.to_string() + } else { + format!("Caught a panic with an unknown type.") + } +} + +pub fn context(sself : Option, context: C) -> Result + where + C: Display + Send + Sync + 'static, +{ + match sself { + Some(ok) => Ok(ok), + None => Err(CustomError(format!("{}", context))), + } +} +pub fn context_result(sself : Result, context: C) -> Result + where + C: Display + Send + Sync + 'static, +{ + match sself { + Ok(ok) => Ok(ok), + Err(err) => Err(CustomError(format!("{} - {:?}", context, err))), + } +} \ No newline at end of file diff --git a/src/templates/correlation_analysis_template.rs b/src/templates/correlation_analysis_template.rs new file mode 100644 index 00000000..a279b4ad --- /dev/null +++ b/src/templates/correlation_analysis_template.rs @@ -0,0 +1,186 @@ +use std::cmp; +use std::collections::{BTreeMap, HashMap}; +use std::fmt::Display; +// use ndarray::{array, stack, Array, Axis}; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType, IntType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder, TransposeColumnIndex, TransposeColumnIndexHolder}; +use crate::Error::CustomError; +use crate::templates::test_utils::MockIndexHolder; +use crate::templates::utils::{get_value_indirect, set_value_indirect}; +use ndarray::prelude::*; +use linregress::{FormulaRegressionBuilder, RegressionDataBuilder}; + +pub struct CorrelationAnalysis { + independent_variables: Vec, + dependent_variable: String, +} + +fn to_coeficient(field_name: &str) -> String { + format!("{}coefficient", field_name) +} +fn to_pvalue(field_name: &str) -> String { + format!("{}pvalue", field_name) +} +fn to_rsquared(field_name: &str) -> String { + format!("{}rsquared", field_name) +} +fn to_adjusted_rsquared(field_name: &str) -> String { + format!("{}adjustedrsquared", field_name) +} + +impl CorrelationAnalysis { + pub fn new(dependent_variable: &str, independent_variables: Vec<&str>) -> CorrelationAnalysis { + CorrelationAnalysis { + independent_variables: independent_variables.iter().map(|fld| fld.to_string()).collect(), + dependent_variable: dependent_variable.to_string(), + } + } +} +impl CompiledTransposeCalculationTemplate for CorrelationAnalysis { + fn schema(&self) -> HashMap { + let mut result = vec![]; + for fld in self.independent_variables.iter() { + result.push((to_coeficient(fld), ValueType::Float)); + result.push((to_pvalue(fld), ValueType::Float)); + result.push((to_rsquared(fld), ValueType::Float)); + result.push((to_adjusted_rsquared(fld), ValueType::Float)); + } + result.iter().map(|(nm, val)| (nm.to_string(), *val)).collect() + } + fn dependencies(&self) -> Vec { + let mut vec1 = self.independent_variables.clone(); + vec1.extend(vec![self.dependent_variable.clone()]); + vec1 + } + fn commit_row( + &self, + row: &mut BoxedOperatorRowTrait, + indexes: &BoxedTransposeColumnIndexHolder, + ordered_transpose_values: &[Value], + cycle_epoch: usize, + ) -> Result<(), Error> { + let values = &row.get_values()?; + //let mut output_values = vec![Value::Empty; values.len()]; + //let mut modified_columns = vec![]; + + let mut y = vec![0f64; ordered_transpose_values.len()]; + let mut x_data = vec![vec![]; self.independent_variables.len()]; + let dependent_variables = indexes.get_index_vec(self.dependent_variable.clone())?; + + let mut previous_value = 0f64; + for index in &dependent_variables { + let result = get_value_indirect(values, &dependent_variables, *index)?.as_float_or_none()?; + if let Some(val) = result { + previous_value = val; + y.push(val); + } else { + y.push(previous_value); + } + } + + for dep in &self.independent_variables { + let dependent_variables = indexes.get_index_vec(dep.clone())?; + let mut previous_value = 0f64; + let mut result = vec![]; + for index in &dependent_variables { + let val = get_value_indirect(values, &dependent_variables, *index)?.as_float_or_none()?; + if let Some(val) = val { + previous_value = val; + result.push(val); + } else { + result.push(previous_value); + } + } + x_data.push(result); + } + + + let x: Array2 = Array::from_shape_vec( + (y.len(), x_data.len() + 1), // +1 for the intercept column + x_data.into_iter().flat_map(|v| v.into_iter()).collect(), + ).map_err(|err| CustomError(format!("Shape error found {err}")))?; + + + + let y = Array::from_vec(y); + + // // Perform the regression: beta = (X'X)^(-1)X'y + // let xtx = x.t().dot(&x); // X'X + // let xty = x.t().dot(&y); // X'y + // let beta = xtx.solve_into(xty).map_err(|err| CustomError(format!("Regression failed {err}")))?; + // + // // Calculate the predicted values + // let y_pred = x.dot(&beta); + // + // // Calculate R-squared + // let ss_total = y.mapv(|yi| (yi - y.mean().unwrap()).powi(2)).sum(); + // let ss_residual = y.iter().zip(y_pred.iter()).map(|(yi, y_pred_i)| (yi - y_pred_i).powi(2)).sum::(); + // let r_squared = 1.0 - (ss_residual / ss_total); + // + // // Output the coefficients (betas) + // println!("Regression coefficients (betas): {:?}", beta); + // + // // Output the R-squared value + // println!("R-squared: {}", r_squared); + + Ok(()) + } +} + +mod tests { + use super::*; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + use ndarray::array; + + fn create_mock_row<'a>(mock_index: &'a MockIndexHolder) -> MockRow<'a> { + // Mock row with some values + let mut row = MockRow::new(mock_index); + + row.set_value("independent_var1__date1", Value::Float(1.0)).unwrap(); + row.set_value("independent_var1__date2", Value::Float(2.0)).unwrap(); + row.set_value("independent_var1__date3", Value::Float(3.0)).unwrap(); + row.set_value("dependent_var__date1", Value::Float(4.0)).unwrap(); + row.set_value("dependent_var__date2", Value::Float(5.0)).unwrap(); + row.set_value("dependent_var__date3", Value::Float(6.0)).unwrap(); + + row + } + + #[test] + fn test_correlation_analysis_basic() { + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + let independent_vars = vec!["independent_var1"]; + let dependent_var = "dependent_var"; + + let analysis = CorrelationAnalysis::new(dependent_var, independent_vars); + let mut mock_index = MockIndexHolder::new(); + + let mut row = create_mock_row(&mock_index); + let mock_index = create_mock_index(&ordered_transpose_values); + let mut operator_row = BoxedOperatorRowTrait::new(row); + let mut mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + + analysis + .commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 0) + .unwrap(); + + // Expected outputs + assert!(operator_row.get_value(&to_coeficient("independent_var1")).is_ok()); + assert!(operator_row.get_value(&to_pvalue("independent_var1")).is_ok()); + assert!(operator_row.get_value(&to_rsquared("independent_var1")).is_ok()); + assert!(operator_row.get_value(&to_adjusted_rsquared("independent_var1")).is_ok()); + } + + fn create_mock_index(ordered_transpose_values: &[Value]) -> MockIndexHolder { + let mut mock_index = MockIndexHolder::new(); + mock_index.register_index("independent_var1".to_string(), ordered_transpose_values); + mock_index.register_index("dependent_var".to_string(), ordered_transpose_values); + mock_index + } +} + diff --git a/src/templates/mod.rs b/src/templates/mod.rs new file mode 100644 index 00000000..2316b208 --- /dev/null +++ b/src/templates/mod.rs @@ -0,0 +1,24 @@ +pub mod adaptive_stop_loss_trade_model; +pub mod ordered_float; +pub mod bucket_data_template; +pub mod compiled_transpose_calculation_template; +pub mod channel_monitoring_template; +pub mod simple_trade_model; +pub(crate) mod test_utils; +pub mod rolling_zscore; +mod utils; +pub mod bucket_data_discrete; +pub mod correlation_analysis_template; + +use std::fmt::Display; +// Re-export the functions to the root of the crate +pub use channel_monitoring_template::*; +pub use adaptive_stop_loss_trade_model::*; +pub use simple_trade_model::*; +pub use bucket_data_template::*; +pub use bucket_data_discrete::*; +pub use correlation_analysis_template::*; +pub use rolling_zscore::*; +pub use compiled_transpose_calculation_template::*; +use crate::Value; + diff --git a/src/templates/ordered_float.rs b/src/templates/ordered_float.rs new file mode 100644 index 00000000..7e0a4ab7 --- /dev/null +++ b/src/templates/ordered_float.rs @@ -0,0 +1,828 @@ +// Original work Copyright (C) 2014-2022 Jonathan Reem + +use core::cmp::Ordering; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::iter::{Product, Sum}; +use core::num::FpCategory; +use core::ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}; +use core::str::FromStr; + +use num_traits::{Bounded, FromPrimitive, Num, NumCast, One, Signed, ToPrimitive, Zero}; +use num_traits::Float; +use serde::{Deserialize, Serialize}; + +// masks for the parts of the IEEE 754 float +const SIGN_MASK: u64 = 0x8000000000000000u64; +const EXP_MASK: u64 = 0x7ff0000000000000u64; +const MAN_MASK: u64 = 0x000fffffffffffffu64; + +// canonical raw bit patterns (for hashing) +const CANONICAL_NAN_BITS: u64 = 0x7ff8000000000000u64; +const CANONICAL_ZERO_BITS: u64 = 0x0u64; + +/// A wrapper around floats providing implementations of `Eq`, `Ord`, and `Hash`. +/// +/// NaN is sorted as *greater* than all other values and *equal* +/// to itself, in contradiction with the IEEE standard. +/// +/// ``` +/// use viewserver_core::ordered_float::OrderedFloat; +/// use std::f32::NAN; +/// +/// let mut v = [OrderedFloat(NAN), OrderedFloat(2.0), OrderedFloat(1.0)]; +/// v.sort(); +/// assert_eq!(v, [OrderedFloat(1.0), OrderedFloat(2.0), OrderedFloat(NAN)]); +/// ``` +/// +/// Because `OrderedFloat` implements `Ord` and `Eq`, it can be used as a key in a `HashSet`, +/// `HashMap`, `BTreeMap`, or `BTreeSet` (unlike the primitive `f32` or `f64` types): +/// +/// ``` +/// # use viewserver_core::ordered_float::OrderedFloat; +/// # use std::collections::HashSet; +/// # use std::f32::NAN; +/// +/// let mut s: HashSet> = HashSet::new(); +/// s.insert(OrderedFloat(NAN)); +/// assert!(s.contains(&OrderedFloat(NAN))); +/// ``` +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)] +#[repr(transparent)] +pub struct OrderedFloat(pub T); + +impl OrderedFloat { + /// Get the value out. + #[inline] + pub fn into_inner(self) -> T { + self.0 + } +} + + + +impl AsRef for OrderedFloat { + #[inline] + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for OrderedFloat { + #[inline] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl<'a, T: Float> From<&'a T> for &'a OrderedFloat { + #[inline] + fn from(t: &'a T) -> &'a OrderedFloat { + // Safety: OrderedFloat is #[repr(transparent)] and has no invalid values. + unsafe { &*(t as *const T as *const OrderedFloat) } + } +} + +impl<'a, T: Float> From<&'a mut T> for &'a mut OrderedFloat { + #[inline] + fn from(t: &'a mut T) -> &'a mut OrderedFloat { + // Safety: OrderedFloat is #[repr(transparent)] and has no invalid values. + unsafe { &mut *(t as *mut T as *mut OrderedFloat) } + } +} + +impl PartialOrd for OrderedFloat { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for OrderedFloat { + fn cmp(&self, other: &Self) -> Ordering { + let lhs = &self.0; + let rhs = &other.0; + match lhs.partial_cmp(rhs) { + Some(ordering) => ordering, + None => { + if lhs.is_nan() { + if rhs.is_nan() { + Ordering::Equal + } else { + Ordering::Greater + } + } else { + Ordering::Less + } + } + } + } +} + +impl PartialEq for OrderedFloat { + #[inline] + fn eq(&self, other: &OrderedFloat) -> bool { + if self.0.is_nan() { + other.0.is_nan() + } else { + self.0 == other.0 + } + } +} + +impl PartialEq for OrderedFloat { + #[inline] + fn eq(&self, other: &T) -> bool { + self.0 == *other + } +} + +impl Hash for OrderedFloat { + fn hash(&self, state: &mut H) { + if self.is_nan() { + // normalize to one representation of NaN + hash_float(&T::nan(), state) + } else { + hash_float(&self.0, state) + } + } +} + +#[inline] +fn hash_float(f: &F, state: &mut H) { + raw_double_bits(f).hash(state); +} + +#[inline] +fn raw_double_bits(f: &F) -> u64 { + if f.is_nan() { + return CANONICAL_NAN_BITS; + } + + let (man, exp, sign) = f.integer_decode(); + if man == 0 { + return CANONICAL_ZERO_BITS; + } + + let exp_u64 = exp as u16 as u64; + let sign_u64 = if sign > 0 { 1u64 } else { 0u64 }; + (man & MAN_MASK) | ((exp_u64 << 52) & EXP_MASK) | ((sign_u64 << 63) & SIGN_MASK) +} + +impl fmt::Display for OrderedFloat { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From> for f32 { + #[inline] + fn from(f: OrderedFloat) -> f32 { + f.0 + } +} + +impl From> for f64 { + #[inline] + fn from(f: OrderedFloat) -> f64 { + f.0 + } +} + +impl From for OrderedFloat { + #[inline] + fn from(val: T) -> Self { + OrderedFloat(val) + } +} + +impl Deref for OrderedFloat { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OrderedFloat { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Eq for OrderedFloat {} + +macro_rules! impl_ordered_float_binop { + ($imp:ident, $method:ident, $assign_imp:ident, $assign_method:ident) => { + impl $imp for OrderedFloat { + type Output = OrderedFloat; + + #[inline] + fn $method(self, other: Self) -> Self::Output { + OrderedFloat((self.0).$method(other.0)) + } + } + + impl $imp for OrderedFloat { + type Output = OrderedFloat; + + #[inline] + fn $method(self, other: T) -> Self::Output { + OrderedFloat((self.0).$method(other)) + } + } + + impl<'a, T> $imp<&'a T> for OrderedFloat + where + T: $imp<&'a T>, + { + type Output = OrderedFloat<>::Output>; + + #[inline] + fn $method(self, other: &'a T) -> Self::Output { + OrderedFloat((self.0).$method(other)) + } + } + + impl<'a, T> $imp<&'a Self> for OrderedFloat + where + T: $imp<&'a T>, + { + type Output = OrderedFloat<>::Output>; + + #[inline] + fn $method(self, other: &'a Self) -> Self::Output { + OrderedFloat((self.0).$method(&other.0)) + } + } + + impl<'a, T> $imp for &'a OrderedFloat + where + &'a T: $imp, + { + type Output = OrderedFloat<<&'a T as $imp>::Output>; + + #[inline] + fn $method(self, other: Self) -> Self::Output { + OrderedFloat((self.0).$method(&other.0)) + } + } + + impl<'a, T> $imp> for &'a OrderedFloat + where + &'a T: $imp, + { + type Output = OrderedFloat<<&'a T as $imp>::Output>; + + #[inline] + fn $method(self, other: OrderedFloat) -> Self::Output { + OrderedFloat((self.0).$method(other.0)) + } + } + + impl<'a, T> $imp for &'a OrderedFloat + where + &'a T: $imp, + { + type Output = OrderedFloat<<&'a T as $imp>::Output>; + + #[inline] + fn $method(self, other: T) -> Self::Output { + OrderedFloat((self.0).$method(other)) + } + } + + impl<'a, T> $imp<&'a T> for &'a OrderedFloat + where + &'a T: $imp, + { + type Output = OrderedFloat<<&'a T as $imp>::Output>; + + #[inline] + fn $method(self, other: &'a T) -> Self::Output { + OrderedFloat((self.0).$method(other)) + } + } + + #[doc(hidden)] // Added accidentally; remove in next major version + impl<'a, T> $imp<&'a Self> for &'a OrderedFloat + where + &'a T: $imp, + { + type Output = OrderedFloat<<&'a T as $imp>::Output>; + + #[inline] + fn $method(self, other: &'a Self) -> Self::Output { + OrderedFloat((self.0).$method(&other.0)) + } + } + + impl $assign_imp for OrderedFloat { + #[inline] + fn $assign_method(&mut self, other: T) { + (self.0).$assign_method(other); + } + } + + impl<'a, T: $assign_imp<&'a T>> $assign_imp<&'a T> for OrderedFloat { + #[inline] + fn $assign_method(&mut self, other: &'a T) { + (self.0).$assign_method(other); + } + } + + impl $assign_imp for OrderedFloat { + #[inline] + fn $assign_method(&mut self, other: Self) { + (self.0).$assign_method(other.0); + } + } + + impl<'a, T: $assign_imp<&'a T>> $assign_imp<&'a Self> for OrderedFloat { + #[inline] + fn $assign_method(&mut self, other: &'a Self) { + (self.0).$assign_method(&other.0); + } + } + }; +} + +impl_ordered_float_binop! {Add, add, AddAssign, add_assign} +impl_ordered_float_binop! {Sub, sub, SubAssign, sub_assign} +impl_ordered_float_binop! {Mul, mul, MulAssign, mul_assign} +impl_ordered_float_binop! {Div, div, DivAssign, div_assign} +impl_ordered_float_binop! {Rem, rem, RemAssign, rem_assign} + +/// Adds a float directly. +impl Sum for OrderedFloat { + fn sum>>(iter: I) -> Self { + OrderedFloat(iter.map(|v| v.0).sum()) + } +} + +impl<'a, T: Float + Sum + 'a> Sum<&'a OrderedFloat> for OrderedFloat { + #[inline] + fn sum>>(iter: I) -> Self { + iter.cloned().sum() + } +} + +impl Product for OrderedFloat { + fn product>>(iter: I) -> Self { + OrderedFloat(iter.map(|v| v.0).product()) + } +} + +impl<'a, T: Float + Product + 'a> Product<&'a OrderedFloat> for OrderedFloat { + #[inline] + fn product>>(iter: I) -> Self { + iter.cloned().product() + } +} + +impl Signed for OrderedFloat { + #[inline] + fn abs(&self) -> Self { + OrderedFloat(self.0.abs()) + } + + fn abs_sub(&self, other: &Self) -> Self { + OrderedFloat(Signed::abs_sub(&self.0, &other.0)) + } + + #[inline] + fn signum(&self) -> Self { + OrderedFloat(self.0.signum()) + } + #[inline] + fn is_positive(&self) -> bool { + self.0.is_positive() + } + #[inline] + fn is_negative(&self) -> bool { + self.0.is_negative() + } +} + +impl Bounded for OrderedFloat { + #[inline] + fn min_value() -> Self { + OrderedFloat(T::min_value()) + } + + #[inline] + fn max_value() -> Self { + OrderedFloat(T::max_value()) + } +} + +impl FromStr for OrderedFloat { + type Err = T::Err; + + /// Convert a &str to `OrderedFloat`. Returns an error if the string fails to parse. + /// + /// ``` + /// use viewserver_core::ordered_float::OrderedFloat; + /// + /// assert!("-10".parse::>().is_ok()); + /// assert!("abc".parse::>().is_err()); + /// assert!("NaN".parse::>().is_ok()); + /// ``` + fn from_str(s: &str) -> Result { + T::from_str(s).map(OrderedFloat) + } +} + +impl Neg for OrderedFloat { + type Output = OrderedFloat; + + #[inline] + fn neg(self) -> Self::Output { + OrderedFloat(-self.0) + } +} + +impl<'a, T> Neg for &'a OrderedFloat +where + &'a T: Neg, +{ + type Output = OrderedFloat<<&'a T as Neg>::Output>; + + #[inline] + fn neg(self) -> Self::Output { + OrderedFloat(-(&self.0)) + } +} + +impl Zero for OrderedFloat { + #[inline] + fn zero() -> Self { + OrderedFloat(T::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.is_zero() + } +} + +impl One for OrderedFloat { + #[inline] + fn one() -> Self { + OrderedFloat(T::one()) + } +} + +impl NumCast for OrderedFloat { + #[inline] + fn from(n: F) -> Option { + T::from(n).map(OrderedFloat) + } +} + +impl Num for OrderedFloat { + type FromStrRadixErr = T::FromStrRadixErr; + fn from_str_radix(str: &str, radix: u32) -> Result { + T::from_str_radix(str, radix).map(OrderedFloat) + } +} + +impl FromPrimitive for OrderedFloat { + fn from_i64(n: i64) -> Option { + T::from_i64(n).map(OrderedFloat) + } + fn from_u64(n: u64) -> Option { + T::from_u64(n).map(OrderedFloat) + } + fn from_isize(n: isize) -> Option { + T::from_isize(n).map(OrderedFloat) + } + fn from_i8(n: i8) -> Option { + T::from_i8(n).map(OrderedFloat) + } + fn from_i16(n: i16) -> Option { + T::from_i16(n).map(OrderedFloat) + } + fn from_i32(n: i32) -> Option { + T::from_i32(n).map(OrderedFloat) + } + fn from_usize(n: usize) -> Option { + T::from_usize(n).map(OrderedFloat) + } + fn from_u8(n: u8) -> Option { + T::from_u8(n).map(OrderedFloat) + } + fn from_u16(n: u16) -> Option { + T::from_u16(n).map(OrderedFloat) + } + fn from_u32(n: u32) -> Option { + T::from_u32(n).map(OrderedFloat) + } + fn from_f32(n: f32) -> Option { + T::from_f32(n).map(OrderedFloat) + } + fn from_f64(n: f64) -> Option { + T::from_f64(n).map(OrderedFloat) + } +} + +impl ToPrimitive for OrderedFloat { + fn to_i64(&self) -> Option { + self.0.to_i64() + } + fn to_u64(&self) -> Option { + self.0.to_u64() + } + fn to_isize(&self) -> Option { + self.0.to_isize() + } + fn to_i8(&self) -> Option { + self.0.to_i8() + } + fn to_i16(&self) -> Option { + self.0.to_i16() + } + fn to_i32(&self) -> Option { + self.0.to_i32() + } + fn to_usize(&self) -> Option { + self.0.to_usize() + } + fn to_u8(&self) -> Option { + self.0.to_u8() + } + fn to_u16(&self) -> Option { + self.0.to_u16() + } + fn to_u32(&self) -> Option { + self.0.to_u32() + } + fn to_f32(&self) -> Option { + self.0.to_f32() + } + fn to_f64(&self) -> Option { + self.0.to_f64() + } +} + +impl num_traits::float::FloatCore for OrderedFloat { + fn nan() -> Self { + OrderedFloat(T::nan()) + } + fn infinity() -> Self { + OrderedFloat(T::infinity()) + } + fn neg_infinity() -> Self { + OrderedFloat(T::neg_infinity()) + } + fn neg_zero() -> Self { + OrderedFloat(T::neg_zero()) + } + fn min_value() -> Self { + OrderedFloat(T::min_value()) + } + fn min_positive_value() -> Self { + OrderedFloat(T::min_positive_value()) + } + fn max_value() -> Self { + OrderedFloat(T::max_value()) + } + fn is_nan(self) -> bool { + self.0.is_nan() + } + fn is_infinite(self) -> bool { + self.0.is_infinite() + } + fn is_finite(self) -> bool { + self.0.is_finite() + } + fn is_normal(self) -> bool { + self.0.is_normal() + } + fn classify(self) -> FpCategory { + self.0.classify() + } + fn floor(self) -> Self { + OrderedFloat(self.0.floor()) + } + fn ceil(self) -> Self { + OrderedFloat(self.0.ceil()) + } + fn round(self) -> Self { + OrderedFloat(self.0.round()) + } + fn trunc(self) -> Self { + OrderedFloat(self.0.trunc()) + } + fn fract(self) -> Self { + OrderedFloat(self.0.fract()) + } + fn abs(self) -> Self { + OrderedFloat(self.0.abs()) + } + fn signum(self) -> Self { + OrderedFloat(self.0.signum()) + } + fn is_sign_positive(self) -> bool { + self.0.is_sign_positive() + } + fn is_sign_negative(self) -> bool { + self.0.is_sign_negative() + } + fn recip(self) -> Self { + OrderedFloat(self.0.recip()) + } + fn powi(self, n: i32) -> Self { + OrderedFloat(self.0.powi(n)) + } + fn integer_decode(self) -> (u64, i16, i8) { + self.0.integer_decode() + } + fn epsilon() -> Self { + OrderedFloat(T::epsilon()) + } + fn to_degrees(self) -> Self { + OrderedFloat(self.0.to_degrees()) + } + fn to_radians(self) -> Self { + OrderedFloat(self.0.to_radians()) + } +} + +impl Float for OrderedFloat { + fn nan() -> Self { + OrderedFloat(T::nan()) + } + fn infinity() -> Self { + OrderedFloat(T::infinity()) + } + fn neg_infinity() -> Self { + OrderedFloat(T::neg_infinity()) + } + fn neg_zero() -> Self { + OrderedFloat(T::neg_zero()) + } + fn min_value() -> Self { + OrderedFloat(T::min_value()) + } + fn min_positive_value() -> Self { + OrderedFloat(T::min_positive_value()) + } + fn max_value() -> Self { + OrderedFloat(T::max_value()) + } + fn is_nan(self) -> bool { + self.0.is_nan() + } + fn is_infinite(self) -> bool { + self.0.is_infinite() + } + fn is_finite(self) -> bool { + self.0.is_finite() + } + fn is_normal(self) -> bool { + self.0.is_normal() + } + fn classify(self) -> FpCategory { + self.0.classify() + } + fn floor(self) -> Self { + OrderedFloat(self.0.floor()) + } + fn ceil(self) -> Self { + OrderedFloat(self.0.ceil()) + } + fn round(self) -> Self { + OrderedFloat(self.0.round()) + } + fn trunc(self) -> Self { + OrderedFloat(self.0.trunc()) + } + fn fract(self) -> Self { + OrderedFloat(self.0.fract()) + } + fn abs(self) -> Self { + OrderedFloat(self.0.abs()) + } + fn signum(self) -> Self { + OrderedFloat(self.0.signum()) + } + fn is_sign_positive(self) -> bool { + self.0.is_sign_positive() + } + fn is_sign_negative(self) -> bool { + self.0.is_sign_negative() + } + fn mul_add(self, a: Self, b: Self) -> Self { + OrderedFloat(self.0.mul_add(a.0, b.0)) + } + fn recip(self) -> Self { + OrderedFloat(self.0.recip()) + } + fn powi(self, n: i32) -> Self { + OrderedFloat(self.0.powi(n)) + } + fn powf(self, n: Self) -> Self { + OrderedFloat(self.0.powf(n.0)) + } + fn sqrt(self) -> Self { + OrderedFloat(self.0.sqrt()) + } + fn exp(self) -> Self { + OrderedFloat(self.0.exp()) + } + fn exp2(self) -> Self { + OrderedFloat(self.0.exp2()) + } + fn ln(self) -> Self { + OrderedFloat(self.0.ln()) + } + fn log(self, base: Self) -> Self { + OrderedFloat(self.0.log(base.0)) + } + fn log2(self) -> Self { + OrderedFloat(self.0.log2()) + } + fn log10(self) -> Self { + OrderedFloat(self.0.log10()) + } + fn max(self, other: Self) -> Self { + OrderedFloat(self.0.max(other.0)) + } + fn min(self, other: Self) -> Self { + OrderedFloat(self.0.min(other.0)) + } + fn abs_sub(self, other: Self) -> Self { + OrderedFloat(self.0.abs_sub(other.0)) + } + fn cbrt(self) -> Self { + OrderedFloat(self.0.cbrt()) + } + fn hypot(self, other: Self) -> Self { + OrderedFloat(self.0.hypot(other.0)) + } + fn sin(self) -> Self { + OrderedFloat(self.0.sin()) + } + fn cos(self) -> Self { + OrderedFloat(self.0.cos()) + } + fn tan(self) -> Self { + OrderedFloat(self.0.tan()) + } + fn asin(self) -> Self { + OrderedFloat(self.0.asin()) + } + fn acos(self) -> Self { + OrderedFloat(self.0.acos()) + } + fn atan(self) -> Self { + OrderedFloat(self.0.atan()) + } + fn atan2(self, other: Self) -> Self { + OrderedFloat(self.0.atan2(other.0)) + } + fn sin_cos(self) -> (Self, Self) { + let (a, b) = self.0.sin_cos(); + (OrderedFloat(a), OrderedFloat(b)) + } + fn exp_m1(self) -> Self { + OrderedFloat(self.0.exp_m1()) + } + fn ln_1p(self) -> Self { + OrderedFloat(self.0.ln_1p()) + } + fn sinh(self) -> Self { + OrderedFloat(self.0.sinh()) + } + fn cosh(self) -> Self { + OrderedFloat(self.0.cosh()) + } + fn tanh(self) -> Self { + OrderedFloat(self.0.tanh()) + } + fn asinh(self) -> Self { + OrderedFloat(self.0.asinh()) + } + fn acosh(self) -> Self { + OrderedFloat(self.0.acosh()) + } + fn atanh(self) -> Self { + OrderedFloat(self.0.atanh()) + } + fn integer_decode(self) -> (u64, i16, i8) { + self.0.integer_decode() + } + fn epsilon() -> Self { + OrderedFloat(T::epsilon()) + } + fn to_degrees(self) -> Self { + OrderedFloat(self.0.to_degrees()) + } + fn to_radians(self) -> Self { + OrderedFloat(self.0.to_radians()) + } +} diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs new file mode 100644 index 00000000..78a9f8e1 --- /dev/null +++ b/src/templates/rolling_zscore.rs @@ -0,0 +1,342 @@ +use std::cmp; +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType, AdaptiveStopLossTradeModel}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder, TransposeColumnIndex, TransposeColumnIndexHolder}; +use crate::templates::utils::{get_value_indirect, set_value_indirect}; + +pub struct RollingZScore { + fields_to_zscore: Vec, + window_size: u32 +} + +impl RollingZScore { + pub fn new(fields_to_zscore: Vec<&str>, mut window_size: u32) -> Self { + if window_size == 0 { + window_size = 1; + } + RollingZScore { + fields_to_zscore: fields_to_zscore.iter().map(|fld|fld.to_string()).collect(), + window_size, + } + } + + +} +fn to_has_data_output_field_name(field: &String) -> String { + format!("{}hasdata", field) +} + +fn to_avg_output_field_name(field: &str) -> String { + format!("{}avg", field) +} +fn to_std_dev_output_field_name(field: &str) -> String { + format!("{}stdev", field) +}fn to_zscore_output_field_name(field: &str) -> String { + format!("{}zscore", field) +} +impl CompiledTransposeCalculationTemplate for RollingZScore { + + fn schema(&self) -> HashMap { + let mut result = vec![]; + for field in &self.fields_to_zscore { + result.push((to_avg_output_field_name(field), ValueType::Float)); + result.push((to_std_dev_output_field_name(field), ValueType::Float)); + result.push((to_has_data_output_field_name(field), ValueType::Boolean)); + result.push((to_zscore_output_field_name(field), ValueType::Float)); + } + result + .into_iter() + .collect() + } + + fn dependencies(&self) -> Vec { + self.fields_to_zscore.clone() + } + + fn commit_row(&self, row: &mut BoxedOperatorRowTrait,indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + let mut score_window = Vec::with_capacity(self.window_size as usize); + let mut window_sum = 0.0; + let mut window_sum_squares = 0.0; + + // These variables will store the last valid values for avg, stdev, and zscore as Options + + + let row_values = &row.get_values()?; + let mut output_values = vec![Value::Empty; row_values.len()]; + let mut modified_columns = vec![]; + + + for field in &self.fields_to_zscore { + + let mut last_avg: Option = None; + let mut last_stdev: Option = None; + let mut last_zscore: Option = None; + let mut last_value: Option = None; + + let zscore_output_field_name = to_zscore_output_field_name(field); + let stdev_output_field_names = to_std_dev_output_field_name(field); + let avg_output_field_names = to_avg_output_field_name(field); + let has_data_output_field_names = to_has_data_output_field_name(field); + + let zscore_output_index = &indexes.get_index_vec(zscore_output_field_name.clone())?; + let stdev_index = &indexes.get_index_vec(stdev_output_field_names.clone())?; + let avg_index = &indexes.get_index_vec(avg_output_field_names.clone())?; + let has_data_index = &indexes.get_index_vec(has_data_output_field_names.clone())?; + let input_field_index = &indexes.get_index_vec(field.clone())?; + + + for i in cmp::max(cycle_epoch as isize - self.window_size as isize, 0) as usize..ordered_transpose_values.len() { + let transpose_value = &ordered_transpose_values[i]; + let value_opt = get_value_indirect(row_values, input_field_index, i)?.as_float_or_none()?; + + // Process non-null values and update the window + if let Some(value) = value_opt.or(last_value) { + last_value = Some(value); + if score_window.len() == self.window_size as usize { + let oldest_value = score_window.remove(0); + + // Remove the oldest value from the window + window_sum -= oldest_value; + window_sum_squares -= oldest_value * oldest_value; + } + + // Add the new value to the window + score_window.push(value); + window_sum += value; + window_sum_squares += value * value; + + // Only proceed if the window is fully populated + if score_window.len() == self.window_size as usize { + // Calculate the average and standard deviation + let avg = window_sum / self.window_size as FloatType; + let variance = (window_sum_squares / self.window_size as FloatType) - (avg * avg); + let stdev = variance.sqrt(); + let zscore = (value - avg) / stdev; + + // Update the last valid values + last_avg = Some(avg); + last_stdev = Some(stdev); + last_zscore = Some(zscore); + + + set_value_indirect(&mut output_values,&mut modified_columns,zscore_output_index,i,Value::Float(zscore))?; + set_value_indirect(&mut output_values,&mut modified_columns,stdev_index,i,Value::Float(stdev))?; + set_value_indirect(&mut output_values,&mut modified_columns,avg_index,i,Value::Float(avg))?; + set_value_indirect(&mut output_values,&mut modified_columns,has_data_index,i,Value::Boolean(true))?; + + + } + } else if score_window.len() == self.window_size as usize { + // If the current value is null but the window is full, output the last valid values + set_value_indirect(&mut output_values,&mut modified_columns,zscore_output_index,i,last_zscore.map(Value::Float).unwrap_or(Value::Empty))?; + set_value_indirect(&mut output_values,&mut modified_columns,stdev_index,i,last_stdev.map(Value::Float).unwrap_or(Value::Empty))?; + set_value_indirect(&mut output_values,&mut modified_columns,avg_index,i,last_avg.map(Value::Float).unwrap_or(Value::Empty))?; + set_value_indirect(&mut output_values,&mut modified_columns,has_data_index,i, last_zscore.map(|fl| Value::Boolean(true)).unwrap_or(Value::Empty))?; + } + } + + } + + + row.set_values_for_columns(modified_columns,output_values)?; + + Ok(()) + } + +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::time::Instant; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + + #[test] + fn test_commit_row_basic() { + // Create a mock row with initial values + + let ordered_transpose_values = vec![ + Value::String("date1".into()), + Value::String("date2".into()), + Value::String("date3".into()), + Value::String("date4".into()), + ]; + + let field_to_zscore = "price"; + let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); + + let mock_index_column_holder = create_index_holder(&ordered_transpose_values, &rolling_zscore); + let mut row = MockRow::new(&mock_index_column_holder); + + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date1".into())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".into())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".into())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".into())), Value::Float(4.0)).unwrap(); + + // Instantiate RollingZScore with dummy values + + + // Ordered transpose values (these should correspond to the field names) + + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + let mut index_holder_trait = BoxedTransposeColumnIndexHolder::new(&mock_index_column_holder); + rolling_zscore.commit_row(&mut row,&index_holder_trait, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct zscore, avg, stdev, and has_data values have been set + assert!(row.get_value("pricezscore__date3").unwrap() != Value::Empty); // First full window + assert!(row.get_value("pricezscore__date4").unwrap() != Value::Empty); // Second full window + assert_eq!(row.get_value("pricehasdata__date3").unwrap(), Value::Boolean(true)); + assert_eq!(row.get_value("pricehasdata__date4").unwrap(), Value::Boolean(true)); + + + // Test for the actual computed values + // Replace `expected_zscore_date3` with the exact value. + + assert_eq!(row.get_value("pricezscore__date1").unwrap(), Value::Empty); + assert_eq!(row.get_value("pricezscore__date2").unwrap(), Value::Empty); + assert_eq!(row.get_value("pricezscore__date3").unwrap(), Value::Float(0f64)); + assert_eq!(row.get_value("pricezscore__date4").unwrap(), Value::Float(1.2247448713915896f64)); + } + + fn create_index_holder(ordered_transpose_values: &Vec, rolling_zscore: &RollingZScore) -> MockIndexHolder { + let mut mock_index_column_holder = MockIndexHolder::new(); + for field in &rolling_zscore.fields_to_zscore { + mock_index_column_holder.register_index(field.to_string(), &ordered_transpose_values); + mock_index_column_holder.register_index(to_zscore_output_field_name(&field), &ordered_transpose_values); + mock_index_column_holder.register_index(to_avg_output_field_name(&field), &ordered_transpose_values); + mock_index_column_holder.register_index(to_std_dev_output_field_name(&field), &ordered_transpose_values); + mock_index_column_holder.register_index(to_has_data_output_field_name(&field), &ordered_transpose_values); + } + + mock_index_column_holder + } + + #[test] + fn test_commit_row_with_nulls() { + // Create a mock row with initial values, including a null value + let field_to_zscore = "price"; + + let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); + let ordered_transpose_values = vec![ + Value::String("date1".into()), + Value::String("date2".into()), + Value::String("date3".into()), + Value::String("date4".into()), + ]; + let mock_index_column_holder = create_index_holder(&ordered_transpose_values, &rolling_zscore); + let mut row = MockRow::new(&mock_index_column_holder); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date1".into())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".into())), Value::Empty).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".into())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".into())), Value::Float(3.0)).unwrap(); + + // Instantiate RollingZScore with dummy values + + // Ordered transpose values (these should correspond to the field names) + + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + let mut index_holder_trait = BoxedTransposeColumnIndexHolder::new(&mock_index_column_holder); + rolling_zscore.commit_row(&mut row,&index_holder_trait, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct zscore, avg, stdev, and has_data values have been set + assert!(row.get_value("pricezscore__date2").unwrap() == Value::Empty); // Should be empty because it's null + assert!(row.get_value("pricezscore__date3").unwrap() != Value::Empty); // Should have a value + assert!(row.get_value("pricezscore__date4").unwrap() != Value::Empty); // Should have a value + assert_eq!(row.get_value("pricehasdata__date3").unwrap(), Value::Boolean(true)); + assert_eq!(row.get_value("pricehasdata__date4").unwrap(), Value::Boolean(true)); + + + // Test for the actual computed values + // Replace with the actual expected values based on your calculations + assert_eq!(row.get_value("pricezscore__date3").unwrap(), Value::Float(1.414213562373095f64)); + } + + #[test] + fn test_commit_row_window_not_full() { + + // Create a mock row with initial values + + let field_to_zscore = "price"; + let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); + let ordered_transpose_values = vec![ + Value::String("date1".into()), + Value::String("date2".into()), + ]; + + let mock_index_column_holder = create_index_holder(&ordered_transpose_values, &rolling_zscore); + let mut row = MockRow::new(&mock_index_column_holder); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date1".into())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".into())), Value::Float(3.0)).unwrap(); + + // Instantiate RollingZScore with dummy values + + // Ordered transpose values (these should correspond to the field names) + + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + let mut index_holder_trait = BoxedTransposeColumnIndexHolder::new(&mock_index_column_holder); + rolling_zscore.commit_row(&mut row,&index_holder_trait, &ordered_transpose_values, 2).unwrap(); + + // Check that no values have been set because the window is not full + assert!(row.get_value("zscore_zscore_date1").unwrap() == Value::Empty); // Should be empty because the window isn't full + assert!(row.get_value("zscore_zscore_date2").unwrap() == Value::Empty); // Should be empty because the window isn't full + } + + #[test] + fn test_commit_row_performance() { + // Create a mock row with initial values + + let ordered_transpose_values: Vec = (1..=15000) + .map(|i| Value::String(format!("date{}", i).into())) + .collect(); + + + let field_to_zscore = "price"; + let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 522); + let mock_index_column_holder = create_index_holder(&ordered_transpose_values, &rolling_zscore); + let mut row = MockRow::new(&mock_index_column_holder); + + // Fill the row with 15,000 values + for i in 1..=15000 { + row.set_value( + &generate_column_name(field_to_zscore, &Value::String(format!("date{}", i).into())), + Value::Float((i % 10) as f64 + 1.0), + ).unwrap(); + } + + // Instantiate RollingZScore with window size of 522 + + // Generate the ordered transpose values + + + // Call commit_row multiple times and measure the time + let iterations = 10; + let mut total_duration = 0; + + let mut index_holder_trait = BoxedTransposeColumnIndexHolder::new(&mock_index_column_holder); + for _ in 0..iterations { + let mut row_clone = BoxedOperatorRowTrait::new(row.clone()); // Clone the row to reset the state for each iteration + let start_time = Instant::now(); + rolling_zscore.commit_row(&mut row_clone,&index_holder_trait, &ordered_transpose_values, 0).unwrap(); + let duration = start_time.elapsed(); + total_duration += duration.as_millis(); + } + + let average_duration = total_duration as f64 / iterations as f64; + + println!("Average execution time for commit_row: {} millis", average_duration); + + // Asserting that the average execution time is within an acceptable range (this is optional) + assert!(average_duration < 1_000_000f64); // Example threshold: 1 second + } +} + diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs new file mode 100644 index 00000000..f6177b99 --- /dev/null +++ b/src/templates/simple_trade_model.rs @@ -0,0 +1,531 @@ +use std::collections::HashMap; +use std::fmt::Display; +use crate::{context, get_string, IntType, TransposeColumnIndexHolder}; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder}; +use crate::templates::utils::{get_value_indirect, get_value_indirect_from_row, set_value_indirect, set_value_indirect_if_some}; + +pub struct SimpleTradeModel { + signal_field: String, + price_value_field: String, + conviction_field_name: String, + instrument_field_name: String, + initial_stop_loss_field_name: String, + initial_take_profit_field_name: String, + holding_period: IntType, + re_entry_time: IntType +} + + +impl SimpleTradeModel { + pub fn new(instrument_field_name: &str, signal_field_name: &str, price_value_field_name: &str, stop_loss_field_name:&str, take_profit_field_name:&str, conviction_field_name: &str, holding_period: IntType, re_entry_time: IntType) -> SimpleTradeModel { + SimpleTradeModel { + instrument_field_name: instrument_field_name.to_string(), + signal_field: signal_field_name.to_string(), + price_value_field: price_value_field_name.to_string(), + conviction_field_name: conviction_field_name.to_string(), + initial_stop_loss_field_name: stop_loss_field_name.to_string(), + initial_take_profit_field_name: take_profit_field_name.to_string(), + holding_period, + re_entry_time + } + } + } + + +impl CompiledTransposeCalculationTemplate for SimpleTradeModel { + + fn schema(&self) -> HashMap { + vec![ + ("active_trade", ValueType::Boolean), + ("initiation_date", ValueType::String), + ("trade_id", ValueType::String), + ("reason", ValueType::String), + ("initiation_price", ValueType::Float), + ("trade_daily_return", ValueType::Float), + ("exit_price", ValueType::Float), + ("stop_loss", ValueType::Float), + ("trade_age", ValueType::Int), + ("days_since_last_trade", ValueType::Int), + ("delta", ValueType::Float), + ("take_profit", ValueType::Float) + ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + } + fn dependencies(&self) -> Vec { + vec![self.instrument_field_name.to_string(), self.signal_field.to_string(), self.price_value_field.to_string(), self.conviction_field_name.to_string(), self.initial_stop_loss_field_name.to_string(), self.initial_take_profit_field_name.to_string()] + } + fn commit_row(&self, row: &mut BoxedOperatorRowTrait,indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + let mut prev_trade_signal: Option = None; + let mut prev_close_value: Option = None; + let mut active_trade: Option = None; + let mut initiation_price: Option = None; + let mut trade_daily_return: Option = None; + let mut exit_price: Option = None; + let mut initiation_date: Option = None; + let mut trade_id: Option = None; + let mut stop_loss: Option = None; + let mut take_profit: Option = None; + let mut delta: Option = None; + let mut conviction: Option = None; + let mut reason: Option = None; + let mut trade_age: Option = None; + let mut days_since_last_trade: Option = None; + + let active_trade_index = indexes.get_index_vec("active_trade".to_owned())?; + let trade_id_index = indexes.get_index_vec("trade_id".to_owned())?; + let initiation_price_index = indexes.get_index_vec("initiation_price".to_owned())?; + let trade_age_index = indexes.get_index_vec("trade_age".to_owned())?; + let days_since_last_trade_index = indexes.get_index_vec("days_since_last_trade".to_owned())?; + let initiation_date_index = indexes.get_index_vec("initiation_date".to_owned())?; + let current_stop_loss_index = indexes.get_index_vec("stop_loss".to_owned())?; + let current_take_profit_index = indexes.get_index_vec("take_profit".to_owned())?; + let reason_index = indexes.get_index_vec("reason".to_owned())?; + let delta_index = indexes.get_index_vec("delta".to_owned())?; + let trade_daily_return_index = indexes.get_index_vec("trade_daily_return".to_owned())?; + let exit_price_index = indexes.get_index_vec("exit_price".to_owned())?; + + let instrument_index = indexes.get_index_vec(self.instrument_field_name.clone())?; + let signal_index = indexes.get_index_vec(self.signal_field.clone())?; + let price_index = indexes.get_index_vec(self.price_value_field.clone())?; + let conviction_index = indexes.get_index_vec(self.conviction_field_name.clone())?; + + let initial_stop_loss_index = indexes.get_index_vec(self.initial_stop_loss_field_name.clone())?; + let initial_take_profit_index = indexes.get_index_vec(self.initial_take_profit_field_name.clone())?; + let mut instrument_name = None; + + let mut all_cols = signal_index.clone(); + all_cols.extend(price_index.clone()); + + + let mut all_values = row.get_values_for_columns(all_cols)?; + let mut dirty_columns = vec![]; + + if cycle_epoch > 0 { + let transpose_index_before_epoch = cycle_epoch - 1; + + active_trade = get_value_indirect_from_row(row,&active_trade_index,transpose_index_before_epoch)?.as_boolean_or_none()?; + trade_id = get_value_indirect_from_row(row,&trade_id_index,transpose_index_before_epoch)?.as_string_or_none()?; + initiation_price = get_value_indirect_from_row(row,&initiation_price_index,transpose_index_before_epoch)?.as_float_or_none()?; + trade_age = get_value_indirect_from_row(row,&trade_age_index,transpose_index_before_epoch)?.as_int_or_none()?; + initiation_date = get_value_indirect_from_row(row,&initiation_date_index,transpose_index_before_epoch)?.as_string_or_none()?; + stop_loss = get_value_indirect_from_row(row,¤t_stop_loss_index,transpose_index_before_epoch)?.as_float_or_none()?; + trade_daily_return = get_value_indirect_from_row(row, &trade_daily_return_index, transpose_index_before_epoch)?.as_float_or_none()?; + take_profit = get_value_indirect_from_row(row,¤t_take_profit_index,transpose_index_before_epoch)?.as_float_or_none()?; + conviction = get_value_indirect_from_row(row,&conviction_index,transpose_index_before_epoch)?.as_float_or_none()?; + reason = get_value_indirect_from_row(row,&reason_index,transpose_index_before_epoch)?.as_string_or_none()?; + days_since_last_trade = get_value_indirect_from_row(row,&days_since_last_trade_index,transpose_index_before_epoch)?.as_int_or_none()?; + } + + if cycle_epoch > 1 { + prev_trade_signal = get_value_indirect(&all_values, &signal_index, cycle_epoch - 2)?.as_boolean_or_none()?; + prev_close_value = get_value_indirect(&all_values, &price_index, cycle_epoch - 2)?.as_float_or_none()?; + } + + for i in cycle_epoch ..ordered_transpose_values.len() { + if !instrument_name.is_some(){ + instrument_name = get_value_indirect_from_row(row,&instrument_index,i)?.as_string_or_none()?; + } + let transpose_value = &ordered_transpose_values[i]; + let current_signal = get_value_indirect(&all_values, &signal_index,i)?.as_boolean_or_none()?.unwrap_or_default(); + + if let (Some(instrument_name),Some(current_close_value)) = + ( + instrument_name.as_ref(), + get_value_indirect(&all_values, &price_index,i)?.as_float_or_none()? + ) + { + //let current_signal = row.get_value(&generate_column_name(&self.signal_field, transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + let loop_active_trade = active_trade.is_some_and(|tv| tv); + let mut loop_trade_closed = false; + if loop_active_trade { + let loop_initiation_price = context(initiation_price, "Should have trade initiation price for active trade")?; + let loop_stop_loss = stop_loss; + let loop_take_profit = take_profit; + let loop_conviction = conviction.as_ref().unwrap_or(&1f64); + let prev_val = context(prev_close_value, "We need to know the previous close price to calculate the trade daily return")?; + trade_daily_return = Some(((current_close_value - prev_val))/prev_val * loop_conviction); + trade_age = Some(context(trade_age, "Should have trade age for active trade")? + 1); + if loop_stop_loss.is_some_and(|sl| current_close_value <= sl) { + loop_trade_closed = true; + exit_price = Some(current_close_value); + let delta_value = (current_close_value - loop_initiation_price) * loop_conviction; + delta = Some(delta_value); + reason = Some(format!("{} {:.2} Closing trade. Current price ({:.2}) has fallen to or below stop loss {:.2} from entry price ({:.2}).", get_delta_label(delta_value), delta_value, current_close_value, loop_stop_loss.as_ref().unwrap(), loop_initiation_price)); + } else if loop_take_profit.is_some_and(|tp| current_close_value >= tp) { + loop_trade_closed = true; + exit_price = Some(current_close_value); + let delta_value = (current_close_value - loop_initiation_price) * loop_conviction; + delta = Some(delta_value); + reason = Some(format!("{} {:.2} Closing trade. Current price ({:.2}) has reached or exceeded take profit level {:.2} from entry price ({:.2}).",get_delta_label(delta_value),delta.unwrap(), current_close_value,loop_take_profit.as_ref().unwrap(), loop_initiation_price)); + } else if trade_age.is_some_and(|age| age >= self.holding_period) { + loop_trade_closed = true; + exit_price = Some(current_close_value); + let delta_value = (current_close_value - loop_initiation_price) * loop_conviction; + delta = Some(delta_value); + reason = Some(format!("{} {:.2} Closing trade. Trade has reached holding period of ({}).",get_delta_label(delta_value),delta.unwrap(),trade_age.as_ref().unwrap())); + } + } else { + if current_signal { + if !prev_trade_signal.unwrap_or_default() && (days_since_last_trade.is_none() || days_since_last_trade.unwrap() >= self.re_entry_time) { + initiation_price = Some(current_close_value); + initiation_date = Some(get_string(&transpose_value)); + trade_id = Some(get_string(&transpose_value) + &instrument_name); + stop_loss = get_value_indirect_from_row(&row, &initial_stop_loss_index,i)?.as_float_or_none()?; + take_profit = get_value_indirect_from_row(&row, &initial_take_profit_index,i)?.as_float_or_none()?; + active_trade = Some(true); + trade_age = Some(0); + days_since_last_trade = Some(0) + } + }else{ + days_since_last_trade = Some(days_since_last_trade.unwrap_or_default() + 1); + } + } + + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &active_trade_index, i, active_trade.map(Value::Boolean))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &reason_index, i, reason.map(|r|r.into()))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &initiation_price_index, i, initiation_price.map(Value::Float))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &trade_daily_return_index, i, trade_daily_return.map(Value::Float))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &initiation_date_index, i, initiation_date.clone().map(|r|r.into()))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &trade_id_index, i, trade_id.clone().map(|r|r.into()))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &delta_index, i, delta.clone().map(Value::Float))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, ¤t_stop_loss_index, i, stop_loss.clone().map(Value::Float))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &exit_price_index, i, exit_price.clone().map(Value::Float))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &trade_age_index, i, trade_age.clone().map(Value::Int))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, ¤t_take_profit_index, i, take_profit.clone().map(Value::Float))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &days_since_last_trade_index, i, days_since_last_trade.clone().map(Value::Int))?; + prev_trade_signal = Some(current_signal); + prev_close_value = Some(current_close_value); + reason = None; + delta = None; + exit_price = None; + if loop_trade_closed { + active_trade = Some(false); + days_since_last_trade = Some(0); + initiation_price = None; + trade_daily_return = None; + initiation_date = None; + trade_id = None; + trade_age = None; + stop_loss = None; + take_profit = None; + } + } + } + row.set_values_for_columns(dirty_columns, all_values)?; + + Ok(()) + } +} + +fn get_delta_label(delta: FloatType) -> String { + if delta == 0.0 { + "" + } + else if delta > 0.0 { + "Won" + } else { + "Lost" + }.to_string() +} + + +mod tests { + use super::*; + use std::collections::HashMap; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; + + #[test] + fn test_commit_row_initial_trade() -> Result<(), Error> { + // Set up initial values for a new trade + let ordered_transpose_values = vec![ + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + let model = SimpleTradeModel::new( + "instrument", + "signal", + "price", + "stop_loss_initial", + "take_profit_initial", + "conviction", + 10, // holding_period + 5 // re_entry_time + ); + + let mock_index = create_mock_index(&ordered_transpose_values, &model); + let mut row = MockRow::new(&mock_index); + + row.set_value_for_transpose_index("instrument",0, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",0, Value::Boolean(true))?; + row.set_value_for_transpose_index("price",0, Value::Float(100.0))?; + row.set_value_for_transpose_index("stop_loss",0, Value::Float(95.0))?; + row.set_value_for_transpose_index("take_profit",0, Value::Float(110.0))?; + row.set_value_for_transpose_index("conviction",0, Value::Float(1.0))?; + + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 0).unwrap(); + } + + // Verify that a new trade has been initiated correctly + assert_eq!(row.get_value_for_transpose_index("active_trade",0)?, Value::Boolean(true)); + assert_eq!(row.get_value_for_transpose_index("initiation_date",0)?, "date1".to_owned().into()); + assert_eq!(row.get_value_for_transpose_index("trade_id",0)?, "date1instrument1".to_owned().into()); + assert_eq!(row.get_value_for_transpose_index("initiation_price",0)?, Value::Float(100.0)); + assert_eq!(row.get_value_for_transpose_index("stop_loss",0)?, Value::Float(95.0)); + assert_eq!(row.get_value_for_transpose_index("take_profit",0)?, Value::Float(110.0)); + assert_eq!(row.get_value_for_transpose_index("trade_age",0)?, Value::Int(0)); + assert_eq!(row.get_value_for_transpose_index("reason",0)?, Value::Empty); + assert_eq!(row.get_value_for_transpose_index("delta",0)?, Value::Empty); + assert_eq!(row.get_value_for_transpose_index("exit_price",0)?, Value::Empty); + + Ok(()) + } + + #[test] + fn test_commit_row_trade_closure_on_stop_loss() -> Result<(), Error> { + // Set up a scenario where the stop loss is hit, and the trade should be closed + let ordered_transpose_values = vec![ + "date0".to_string().into(), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + let model = SimpleTradeModel::new( + "instrument", + "signal", + "price", + "initial_stop_loss", + "initial_take_profit", + "conviction", + 10, // holding_period + 5 // re_entry_time + ); + + let mock_index = create_mock_index(&ordered_transpose_values, &model); + let mut row = MockRow::new(&mock_index); + + // set price before trade + row.set_value_for_transpose_index("instrument",0, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",0, Value::Boolean(false))?; + row.set_value_for_transpose_index("price",0, Value::Float(99.0))?; + + + // Set initial values to initiate the trade + row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",1, Value::Boolean(true))?; + row.set_value_for_transpose_index("price",1, Value::Float(100.0))?; + row.set_value_for_transpose_index("initial_stop_loss",1, Value::Float(95.0))?; + row.set_value_for_transpose_index("take_profit",1, Value::Float(110.0))?; + row.set_value_for_transpose_index("conviction",1, Value::Float(1.0))?; + // Set initial values to initiate the trade + + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 1).unwrap(); + } + + row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; + // Simulate a price drop to hit the stop loss + row.set_value_for_transpose_index("price",2, Value::Float(94.0))?; + row.set_value_for_transpose_index("instrument",3, "instrument1".to_owned().into())?; + // Simulate a price drop to hit the stop loss + row.set_value_for_transpose_index("price",3, Value::Float(94.0))?; + + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 2).unwrap(); + } + + // Verify that the trade has been closed due to stop loss + assert_eq!(row.get_value_for_transpose_index("active_trade",1)?, Value::Boolean(true)); + assert_eq!(row.get_value_for_transpose_index("active_trade",2)?, Value::Boolean(true)); + assert_eq!(row.get_value_for_transpose_index("active_trade",3)?, Value::Boolean(false)); + assert_eq!(row.get_value_for_transpose_index("exit_price",2)?, Value::Float(94.0)); + assert_eq!(row.get_value_for_transpose_index("trade_daily_return",2)?, Value::Float(-0.050505050505050504)); + assert!(matches!(row.get_value_for_transpose_index("reason",2)?, Value::String(ref reason) if reason.ref_into_owned().contains("stop loss"))); + + Ok(()) + } + + #[test] + fn test_commit_row_trade_closure_on_take_profit() -> Result<(), Error> { + // Set up a scenario where the take profit is hit, and the trade should be closed + let ordered_transpose_values = vec![ + "date0".to_string().into(), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + ]; + + let model = SimpleTradeModel::new( + "instrument", + "signal", + "price", + "initial_stop_loss", + "initial_take_profit", + "conviction", + 10, // holding_period + 5 // re_entry_time + ); + + let mock_index = create_mock_index(&ordered_transpose_values, &model); + let mut row = MockRow::new(&mock_index); + + + // set price before trade + row.set_value_for_transpose_index("instrument",0, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",0, Value::Boolean(false))?; + row.set_value_for_transpose_index("price",0, Value::Float(99.0))?; + + + // Set initial values to initiate the trade + row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",1, Value::Boolean(true))?; + row.set_value_for_transpose_index("price",1, Value::Float(100.0))?; + row.set_value_for_transpose_index("stop_loss",1, Value::Float(95.0))?; + row.set_value_for_transpose_index("take_profit",1, Value::Float(110.0))?; + row.set_value_for_transpose_index("conviction",1, Value::Float(1.0))?; + + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 1).unwrap(); + } + + // Simulate a price rise to hit the take profit + row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("price",2, Value::Float(111.0))?; + // Simulate a price rise to hit the take profit + row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("price",3, Value::Float(111.0))?; + + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 2).unwrap(); + } + + // Verify that the trade has been closed due to take profit + assert_eq!(row.get_value_for_transpose_index("active_trade",2)?, Value::Boolean(true)); + assert_eq!(row.get_value_for_transpose_index("trade_daily_return",2)?, Value::Float(0.12121212121212122)); + assert_eq!(row.get_value_for_transpose_index("active_trade",3)?, Value::Boolean(false)); + assert_eq!(row.get_value_for_transpose_index("trade_daily_return",3)?, Value::Empty); + assert_eq!(row.get_value_for_transpose_index("exit_price",2)?, Value::Float(111.0)); + assert!(matches!(row.get_value_for_transpose_index("reason",2)?, Value::String(ref reason) if reason.ref_into_owned().contains("take profit"))); + + Ok(()) + } + + #[test] + fn test_commit_row_trade_holding_period_expiry() -> Result<(), Error> { + // Set up a scenario where the trade is closed due to holding period expiry + let ordered_transpose_values = vec![ + "date0".to_string().into(), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + "date4".to_string().into(), + ]; + + let model = SimpleTradeModel::new( + "instrument", + "signal", + "price", + "initial_stop_loss", + "initial_take_profit", + "conviction", + 2, // holding_period set to 2 + 5 // re_entry_time + ); + + let mock_index = create_mock_index(&ordered_transpose_values, &model); + let mut row = MockRow::new(&mock_index); + + // set price before trade + row.set_value_for_transpose_index("instrument",0, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",0, Value::Boolean(false))?; + row.set_value_for_transpose_index("price",0, Value::Float(99.0))?; + + // Set initial values to initiate the trade + row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("signal",1, Value::Boolean(true))?; + row.set_value_for_transpose_index("price",1, Value::Float(100.0))?; + row.set_value_for_transpose_index("initial_stop_loss",1, Value::Float(95.0))?; + row.set_value_for_transpose_index("initial_take_profit",1, Value::Float(110.0))?; + row.set_value_for_transpose_index("conviction",1, Value::Float(1.0))?; + + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 1).unwrap(); + } + + // Simulate the passing of time beyond the holding period + row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("price",2, Value::Float(102.0))?; + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 2).unwrap(); + } + + row.set_value_for_transpose_index("instrument",3, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("price",3, Value::Float(103.0))?; + + row.set_value_for_transpose_index("instrument",4, "instrument1".to_owned().into())?; + row.set_value_for_transpose_index("price",4, Value::Float(103.0))?; + { + let mut operator_row = BoxedOperatorRowTrait::new(&mut row); + let mock_index_holder = BoxedTransposeColumnIndexHolder::new(&mock_index); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 3).unwrap(); + } + + // Verify that the trade has been closed due to holding period expiry + assert_eq!(row.get_value_for_transpose_index("active_trade",3)?, Value::Boolean(true)); + assert_eq!(row.get_value_for_transpose_index("exit_price",3)?, Value::Float(103.0)); + assert!(matches!(row.get_value_for_transpose_index("reason",3)?, Value::String(ref reason) if reason.ref_into_owned().contains("holding period"))); + assert_eq!(row.get_value_for_transpose_index("active_trade",4)?, Value::Boolean(false)); + + Ok(()) + } + + + fn create_mock_index(ordered_transpose_values: &Vec, model: &SimpleTradeModel) -> MockIndexHolder { + let mut mock_index = MockIndexHolder::new(); + let fields = vec![ + &model.instrument_field_name, + &model.signal_field, + &model.price_value_field, + &model.conviction_field_name, + &model.initial_stop_loss_field_name, + &model.initial_take_profit_field_name, + "active_trade", + "initiation_price", + "trade_daily_return", + "days_since_last_trade", + "exit_price", + "initiation_date", + "trade_id", + "stop_loss", + "take_profit", + "trade_age", + "reason", + "delta" + ]; + + for field in fields { + mock_index.register_index(field.to_string(), &ordered_transpose_values); + } + mock_index + } +} + + + diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs new file mode 100644 index 00000000..5168e3e0 --- /dev/null +++ b/src/templates/test_utils.rs @@ -0,0 +1,311 @@ +use std::collections::HashMap; +use indexmap::IndexMap; +use crate::{context, generate_column_name, BoxedOperatorRowTrait, Error, EvalexprResult, OperatorRowTrait, Value}; +use crate::context::{BoxedTransposeColumnIndex, TransposeColumnIndex, TransposeColumnIndexHolder}; + +#[derive(Clone)] +pub struct MockRow<'a> { + values: HashMap, + mock_index: &'a MockIndexHolder, +} + +pub struct MockIndexHolder { + offset: usize, + values: IndexMap, + output_column_indexes: IndexMap, +} + +impl MockIndexHolder { + pub fn new() -> Self { + MockIndexHolder { + offset: 0, + values: IndexMap::new(), + output_column_indexes: Default::default(), + } + } + pub fn register_index(&mut self, index_name: String, transpose_values: &[Value]) { + let index = MockIndex::from_transpose_values(index_name.clone(), transpose_values, self.offset); + if self.values.contains_key(&index_name) { + panic!("index {} already exists", index_name); + } + self.values.insert(index_name.clone(), index); + for (key, idx) in &self.values.get(&index_name).unwrap().values { + if self.output_column_indexes.contains_key(key) { + panic!("Column {} already exists",key); + } + self.output_column_indexes.insert(key.clone(), *idx); + } + self.offset += transpose_values.len(); + } + + pub fn get_index_for_column_full(&self, column_name: String) -> Result { + let option = self.output_column_indexes.get(&column_name); + match option { + None => { Err(Error::CustomError(format!("Column name {column_name} not found"))) } + Some(val) => { + Ok(*val) + } + } + } +} + +impl<'a> TransposeColumnIndexHolder for &'a MockIndexHolder { + fn get_index_for_column(&self, column_name: String) -> Result, Error> { + let option = self.values.get(&column_name); + match option { + None => { Err(Error::CustomError(format!("Column name {column_name} not found"))) } + Some(val) => { + let raw = BoxedTransposeColumnIndex::new(val).into_raw(); + let val = unsafe { BoxedTransposeColumnIndex::from_raw(raw) }; + Ok(val) + } + } + } + + fn get_index_vec(&self, column_name: String) -> Result, Error> { + let option = self.values.get(&column_name); + match option { + None => { Err(Error::CustomError(format!("Column name {column_name} not found"))) } + Some(val) => { + let vec = val.values.values().cloned().collect(); + Ok(vec) + } + } + } +} + +#[derive(Debug, Clone)] +pub struct MockIndex { + values: IndexMap, +} + +impl MockIndex { + pub fn from_transpose_values(index_name: String, transpose_values: &[Value], offset: usize) -> Self { + let mut mock_index = MockIndex { + values: IndexMap::new(), + }; + for (idx, value) in transpose_values.into_iter().enumerate() { + let key = generate_column_name(&index_name, value); // Generate a key for each value + let i = offset + idx; + mock_index.values.insert(key, i); + } + mock_index + } +} + +impl TransposeColumnIndex for &MockIndex { + fn col_idx(&self, transpose_index: usize) -> Result { + let option = self.values.get_index(transpose_index); + match option { + None => { Err(Error::CustomError("Transpose index not found".to_string())) } + Some((_nm, val)) => { + Ok(*val) + } + } + } +} + +impl<'a> MockRow<'a> { + pub fn new(mock_index: &'a MockIndexHolder) -> Self { + MockRow { + values: HashMap::new(), + mock_index, + } + } + + pub fn from_values(row: Vec, mock_index: &'a MockIndexHolder) -> Self { + let mut mock_row = MockRow::new(mock_index); + + // Iterate over the values and insert them into the mock_row with a key + for (idx, value) in row.into_iter().enumerate() { + let key = format!("col_{}", idx); // Generate a key for each value + mock_row.insert_value(key, value); + } + + mock_row + } + + + pub fn set_value_for_transpose_index(&mut self, column_name: &str, transpose_idx: usize, value: Value) -> Result<(), crate::Error> { + let output_column_index = self.mock_index.get_index_for_column(column_name.to_owned())?.col_idx(transpose_idx)?; + self.set_value_for_column(output_column_index, value)?; + Ok(()) + } + + pub fn get_value_for_transpose_index(&self, column_name: &str, transpose_idx: usize) -> Result { + let output_column_index = self.mock_index.get_index_for_column(column_name.to_owned())?.col_idx(transpose_idx)?; + self.get_value_for_column(output_column_index) + } + + pub fn into_boxed(self) -> BoxedOperatorRowTrait<'a> { + BoxedOperatorRowTrait::new(self) + } + pub fn insert_value(&mut self, key: String, value: Value) -> Result<(), Error> { + let index = self.mock_index.get_index_for_column_full(key.clone())?; + self.values.insert(index, value); + Ok(()) + } +} + +impl<'a> OperatorRowTrait for MockRow<'a> { + // Implement required methods, simply accessing the `values` HashMap. + // For simplicity, assuming methods to get and set values by column name. + fn get_value(&self, column_name: &str) -> Result { + let index_for_column = self.mock_index.get_index_for_column_full(column_name.to_string())?; + let option = self.values.get(&index_for_column); + match option { + None => { Ok(Value::Empty) } + Some(val) => { + Ok(val.clone()) + } + } + } + + fn get_value_for_column(&self, col: usize) -> Result { + let option = self.values.get(&col); + match option { + None => { Ok(Value::Empty) } + Some(val) => { + Ok(val.clone()) + } + } + } + + fn set_value(&mut self, column_name: &str, value: Value) -> Result<(), crate::Error> { + let index_for_column = self.mock_index.get_index_for_column_full(column_name.to_string())?; + self.values.insert(index_for_column, value); + Ok(()) + } + + + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(), crate::Error> { + self.values.insert(col, value); + Ok(()) + } + + fn set_row(&mut self, row: usize) { + todo!() + } + + fn call_function(&self, idt: &str, argument: Value) -> Result { + todo!() + } + + fn has_changes(&self) -> Result { + todo!() + } + + + fn get_dirty_flags(&self) -> Result, crate::Error> { + todo!() + } + + fn get_values(&self) -> Result, Error> { + let mut result = vec![Value::Empty;self.mock_index.output_column_indexes.len()]; + for (nm, val) in &self.mock_index.output_column_indexes { + result[*val] = self.values.get(val).cloned().unwrap_or(Value::Empty); + } + Ok(result) + } + + fn set_values_for_columns(&mut self, columns: Vec, mut values: Vec) -> Result<(), Error> { + for column in columns { + self.set_value_for_column(column.clone(), values.get(column).unwrap().clone())?; + } + Ok(()) + } + + fn get_values_for_columns(&self, columns: Vec) -> Result, Error> { + let mut result = vec![Value::Empty;self.mock_index.output_column_indexes.len()]; + println!("Getting values for columns {:?}", columns); + for column in columns { + let value = self.get_value_for_column(column)?; + println!("Value for column {} is {:?}", column, value); + result[column] = value; + } + Ok(result) + } +} + + +impl<'a> OperatorRowTrait for &mut MockRow<'a> { + // Implement required methods, simply accessing the `values` HashMap. + // For simplicity, assuming methods to get and set values by column name. + fn get_value(&self, column_name: &str) -> Result { + let index_for_column = self.mock_index.get_index_for_column_full(column_name.to_string())?; + let option = self.values.get(&index_for_column); + match option { + None => { Ok(Value::Empty) } + Some(val) => { + Ok(val.clone()) + } + } + } + + fn get_value_for_column(&self, col: usize) -> Result { + let option = self.values.get(&col); + match option { + None => { Ok(Value::Empty) } + Some(val) => { + Ok(val.clone()) + } + } + } + + fn set_value(&mut self, column_name: &str, value: Value) -> Result<(), crate::Error> { + println!("Setting value for column {} to {:?}", column_name, value); + let index_for_column = self.mock_index.get_index_for_column_full(column_name.to_string())?; + self.values.insert(index_for_column, value); + Ok(()) + } + + fn get_values_for_columns(&self, columns: Vec) -> Result, Error> { + let mut result = vec![Value::Empty;self.mock_index.output_column_indexes.len()]; + println!("Getting values for columns {:?}", columns); + for column in columns { + let value = self.get_value_for_column(column)?; + println!("Value for column {} is {:?}", column, value); + result[column] = value; + } + Ok(result) + } + + + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(), crate::Error> { + let (column_name, idx) = self.mock_index.output_column_indexes.get_index(col).ok_or_else(|| Error::CustomError(format!("Column not found {}", col)))?; + self.set_value(column_name, value.clone())?; + Ok(()) + } + + fn set_row(&mut self, row: usize) { + todo!() + } + + fn call_function(&self, idt: &str, argument: Value) -> Result { + todo!() + } + + fn has_changes(&self) -> Result { + todo!() + } + + + fn get_dirty_flags(&self) -> Result, crate::Error> { + todo!() + } + + fn get_values(&self) -> Result, Error> { + let mut result = vec![Value::Empty;self.mock_index.output_column_indexes.len()]; + for (nm, val) in &self.mock_index.output_column_indexes { + result[*val] = self.values.get(val).cloned().unwrap_or(Value::Empty); + } + Ok(result) + } + + fn set_values_for_columns(&mut self, columns: Vec, mut values: Vec) -> Result<(), Error> { + for column in columns { + self.set_value_for_column(column.clone(), values.get(column).unwrap().clone())?; + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/templates/utils.rs b/src/templates/utils.rs new file mode 100644 index 00000000..155797b6 --- /dev/null +++ b/src/templates/utils.rs @@ -0,0 +1,28 @@ +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; + +pub fn get_value_indirect<'a>(values: &'a Vec, column_index: &Vec, idx: usize) -> Result<&'a Value, Error> { + let column = column_index.get(idx).ok_or_else(|| Error::CustomError(format!("Column not found in index{}", idx)))?; + let result = values.get(*column).ok_or_else(|| Error::CustomError(format!("Column {column} not found in row")))?; + Ok(result) +} + +pub fn get_value_indirect_from_row<'a>(row: &BoxedOperatorRowTrait, column_index: &Vec, idx: usize) -> Result { + let column = column_index.get(idx).ok_or_else(|| Error::CustomError(format!("Column not found in index{}", idx)))?; + row.get_value_for_column(*column) +} + +pub fn set_value_indirect<'a>(values: &'a mut Vec,dirty_columns: &'a mut Vec, column_index: &Vec, idx: usize, value: Value) -> Result<(), Error> { + let column = column_index.get(idx).ok_or_else(|| Error::CustomError(format!("Column not found in index{}", idx)))?; + let mut result = values.get_mut(*column).ok_or_else(|| Error::CustomError(format!("Column {column} not found in row")))?; + *result = value.into_owned(); + dirty_columns.push(*column); + Ok(()) +}pub fn set_value_indirect_if_some<'a>(values: &'a mut Vec,dirty_columns: &'a mut Vec, column_index: &Vec, idx: usize, value: Option) -> Result<(), Error> { + if let Some(value) = value { + let column = column_index.get(idx).ok_or_else(|| Error::CustomError(format!("Column not found in index{}", idx)))?; + let mut result = values.get_mut(*column).ok_or_else(|| Error::CustomError(format!("Column {column} not found in row")))?; + *result = value.into_owned(); + dirty_columns.push(*column); + } + Ok(()) +} \ No newline at end of file diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 69f44944..c5d187ae 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -154,7 +154,7 @@ impl Node { /// Fails, if one of the operators in the expression tree fails. pub fn eval_string_with_context(&self, context: &C) -> EvalexprResult { match self.eval_with_context(context) { - Ok(Value::String(string)) => Ok(string), + Ok(Value::String(string)) => Ok(string.into_owned()), Ok(value) => Err(EvalexprError::expected_string(value)), Err(error) => Err(error), } @@ -236,7 +236,7 @@ impl Node { context: &mut C, ) -> EvalexprResult { match self.eval_with_context_mut(context) { - Ok(Value::String(string)) => Ok(string), + Ok(Value::String(string)) => Ok(string.into_owned()), Ok(value) => Err(EvalexprError::expected_string(value)), Err(error) => Err(error), } @@ -421,7 +421,7 @@ impl Node { || (self.operator().precedence() == node.operator().precedence() && !self.operator().is_left_to_right() && !node.operator().is_left_to_right()) { if self.operator().is_leaf() { - Err(EvalexprError::AppendedToLeafNode) + Err(EvalexprError::AppendedToLeafNode(format!("{:?}", self.operator()))) } else if self.has_enough_children() { // Unwrap cannot fail because is_leaf being false and has_enough_children being true implies that the operator wants and has at least one child let last_child_operator = self.children.last().unwrap().operator(); @@ -441,7 +441,7 @@ impl Node { } else { // println!("Rotating"); if node.operator().is_leaf() { - return Err(EvalexprError::AppendedToLeafNode); + return Err(EvalexprError::AppendedToLeafNode(format!("{:?}", node.operator()))); } // Unwrap cannot fail because is_leaf being false and has_enough_children being true implies that the operator wants and has at least one child @@ -557,7 +557,7 @@ fn collapse_all_sequences(root_stack: &mut Vec) -> EvalexprResult<()> { Ok(()) } -pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult { +pub fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult { let mut root_stack = vec![Node::root_node()]; let mut last_token_is_rightsided_value = false; let mut token_iter = tokens.iter().peekable(); @@ -629,7 +629,7 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult Some(Node::new(Operator::value(Value::Float(float)))), Token::Int(int) => Some(Node::new(Operator::value(Value::Int(int)))), Token::Boolean(boolean) => Some(Node::new(Operator::value(Value::Boolean(boolean)))), - Token::String(string) => Some(Node::new(Operator::value(Value::String(string)))), + Token::String(string) => Some(Node::new(Operator::value(string.into()))), }; if let Some(mut node) = node { diff --git a/src/value/mod.rs b/src/value/mod.rs index b5b14a42..deb5224b 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,5 +1,8 @@ +use std::cmp::Ordering; +use std::convert::TryInto; +use std::fmt; use crate::error::{EvalexprError, EvalexprResult}; - +use std::hash::{Hash, Hasher}; mod display; pub mod value_type; @@ -20,11 +23,12 @@ pub const EMPTY_VALUE: () = (); /// The value type used by the parser. /// Values can be of different subtypes that are the variants of this enum. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] +#[repr(C)] pub enum Value { /// A string value. - String(String), + String(CowData), /// A float value. Float(FloatType), /// An integer value. @@ -37,6 +41,354 @@ pub enum Value { Empty, } + +// Implement PartialEq for CowData and &str +impl PartialEq for CowData { + fn eq(&self, other: &str) -> bool { + unsafe { self.as_ref() == other } + } +} + +// Implement PartialEq for &str and CowData +impl PartialEq for str { + fn eq(&self, other: &CowData) -> bool { + other == self + } +} + +#[cfg(feature = "serde_json_support")] +impl From for serde_json::Value { + fn from(value: CowData) -> Self { + Self::String(value.into_owned()) + } +} + +impl Value{ + pub fn into_owned(self) -> Value { + match self { + Value::String(s) => Value::String(s.into_owned().into()), + v => v + } + } +} + + +/// A helper enum for handling owned data or references with raw pointers. +#[derive(Clone)] +pub enum CowData{ + Owned(Box>), + Borrowed { + data: *const u8, + length: usize, + }, +} + + +impl From for String { + fn from(cow_data: CowData) -> String { + match cow_data { + CowData::Owned(s) => Pin::into_inner(*s), + CowData::Borrowed { data, length, .. } => { + // Safely convert the borrowed data to a String + unsafe { + let slice = std::slice::from_raw_parts(data, length); + std::str::from_utf8(slice).unwrap().to_string() + } + } + } + } +} + +impl DeepSizeOf for CowData{ + fn deep_size_of_children(&self, context: &mut Context) -> usize { + return match self { + CowData::Owned(data) => data.deep_size_of_children(context), + CowData::Borrowed{data,length} => 0, + } + } +} + +impl FromStr for CowData{ + type Err = EvalexprError; + + fn from_str(s: &str) -> Result { + Ok(CowData::Owned(Box::new(Pin::new(s.to_string())))) + } +} +unsafe impl Send for CowData{} +unsafe impl Sync for CowData{} + +impl Hash for CowData +{ + fn hash(&self, state: &mut H) { + match self { + CowData::Owned(ref data) => data.hash(state), + CowData::Borrowed{data,length} => unsafe { + let slice = std::slice::from_raw_parts(*data, *length); + std::str::from_utf8_unchecked(slice).hash(state) + }, + } + } +} + + + +impl fmt::Display for CowData + +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CowData::Owned(ref data) => write!(f, "{}", data), + CowData::Borrowed{data,length} => unsafe { + let slice = std::slice::from_raw_parts(*data, *length); + write!(f, "{:?}", std::str::from_utf8_unchecked(slice)) + + }, + } + } +} + +impl CowData { + /// Access the data, either as a reference or as a mutable reference if it's owned. + pub unsafe fn as_ref(&self) -> &str { + match self { + CowData::Owned(ref data) => data, + CowData::Borrowed{data,length} => { + let slice = std::slice::from_raw_parts(*data, *length); + std::str::from_utf8_unchecked(slice) + } + } + } + + pub fn len(&self) -> usize { + match self { + CowData::Owned(ref data) => data.len(), + CowData::Borrowed{length, ..} => *length, + } + } + + /// Convert to an owned version, cloning the data if it was borrowed. + pub fn into_owned(self) -> String + { + match self { + CowData::Owned(data) => Pin::into_inner(*data), + CowData::Borrowed{data,length} => unsafe { + let slice = std::slice::from_raw_parts(data, length); + std::str::from_utf8_unchecked(slice).to_string() + }, + } + } pub fn ref_into_owned(&self) -> String + + { + match self { + CowData::Owned(data) => data.to_string(), + CowData::Borrowed{data,length} => unsafe { + let slice = std::slice::from_raw_parts(*data, *length); + std::str::from_utf8_unchecked(slice).to_string() + }, + } + } +} + + +impl fmt::Debug for CowData + +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CowData::Owned(ref data) => { + f.debug_tuple("Owned") + .field(data) + .finish() + }, + CowData::Borrowed{data,length} => { + // Attempt to safely print the borrowed data + + unsafe { + let slice = std::slice::from_raw_parts(*data, *length); + let value = std::str::from_utf8_unchecked(slice).to_string(); + f.debug_tuple("Borrowed") + .field(&value) + .finish() + } + }, + } + } +} + +impl Serialize for CowData +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + CowData::Owned(ref data) => data.serialize(serializer), + CowData::Borrowed { data,length}=> unsafe { + let slice = std::slice::from_raw_parts(*data, *length); + let value = std::str::from_utf8_unchecked(slice).to_string(); + value.serialize(serializer) + + }, + } + } +} + +impl<'de> Deserialize<'de> for CowData +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let owned_data = String::deserialize(deserializer)?; + Ok(CowData::Owned(Box::new(Pin::new(owned_data)))) + } +} + + +impl Eq for Value {} + +// Implement Hash for Value +impl Hash for Value { + fn hash(&self, state: &mut H) { + match self { + Value::String(s) => { + s.hash(state); + } + Value::Float(f) => { + OrderedFloat::from(*f).to_bits().hash(state); // Hash the bit representation of the float + } + Value::Int(i) => { + i.hash(state); + } + Value::Boolean(b) => { + b.hash(state); + } + Value::Tuple(t) => { + t.hash(state); + } + Value::Empty => { + // Use a constant to represent the Empty variant + std::mem::discriminant(self).hash(state); + } + } + } +} + + +impl From for CowData { + fn from(s: String) -> Self { + CowData::Owned(Box::new(Pin::new(s))) + } +} + +impl From<&str> for CowData { + fn from(s: &str) -> Self { + CowData::Owned(Box::new(Pin::new(s.to_string()))) + } +} + + +impl From<&Value> for Value { + fn from(value: &Value) -> Self { + match value { + Value::String(s) => Value::String(s.clone()), + Value::Float(f) => Value::Float(*f), + Value::Int(i) => Value::Int(*i), + Value::Boolean(b) => Value::Boolean(*b), + Value::Tuple(t) => Value::Tuple(t.iter().map(|v| v.into()).collect()), + Value::Empty => Value::Empty, + } + } +} + +impl Ord for Value { + fn cmp(&self, other: &Self) -> Ordering { + // Assuming that `partial_cmp` should never return `None` for `Ord` types + self.partial_cmp(other).expect(format!("Cannot compare {:?} and {:?}", self, other).as_str()) + } +} + + +impl Ord for CowData { + fn cmp(&self, other: &Self) -> Ordering { + unsafe { self.as_ref().cmp(other.as_ref()) } + } +} + +impl PartialOrd for CowData +{ + fn partial_cmp(&self, other: &Self) -> Option { + unsafe { self.as_ref().partial_cmp(other.as_ref()) } + } +} + +impl Eq for CowData {} + +impl PartialEq for CowData +{ + fn eq(&self, other: &Self) -> bool { + unsafe { self.as_ref() == other.as_ref() } + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Value::String(a), Value::String(b)) => a.partial_cmp(b), + (Value::Float(a), Value::Float(b)) => OrderedFloat::from(*a as FloatType).partial_cmp(&OrderedFloat::from(*b as FloatType)), + (Value::Int(a), Value::Int(b)) => a.partial_cmp(b), + (Value::Float(a), Value::Int(b)) => OrderedFloat::from(*a as FloatType).partial_cmp(&OrderedFloat::from((*b as FloatType))), + (Value::Int(a), Value::Float(b)) => (OrderedFloat::from(*a as FloatType)).partial_cmp(&OrderedFloat::from(*b as FloatType)), + (Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b), + // For simplicity, Tuple and Empty comparisons are not implemented + // Implementing tuple comparison would require comparing each element of the tuple, which is beyond this simple example + (Value::Tuple(_), Value::Tuple(_)) => None, + (Value::Empty, Value::Empty) => Some(Ordering::Equal), + (_, Value::Empty) => Some(Ordering::Greater), + (Value::Empty, _) => Some(Ordering::Greater), + // All other combinations are considered incomparable + _ => None, + } + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::String(a), Value::String(b)) => a == b, + (Value::Float(a), Value::Float(b)) => OrderedFloat::from(*a) == OrderedFloat::from(*b), + (Value::Float(a), Value::Int(b)) => OrderedFloat::from(*a) == OrderedFloat::from(*b as FloatType), + (Value::Int(a), Value::Float(b)) => OrderedFloat::from(*a as FloatType) == OrderedFloat::from(*b), + (Value::Int(a), Value::Int(b)) => a == b, + (Value::Boolean(a), Value::Boolean(b)) => a == b, + // For simplicity, Tuple and Empty equality checks are not fully implemented + (Value::Tuple(_), Value::Tuple(_)) => false, // Simplified; real implementation would require element-wise comparison + (Value::Empty, Value::Empty) => true, + (_, Value::Empty) => false, + (Value::Empty, _) => false, + (left,right) => panic!("Cannot compare {:?} and {:?}", left, right), + } + } +} + +impl Default for Value { + fn default() -> Self { + Value::Empty + } +} + +impl TryInto for &Value{ + type Error = Error; + + fn try_into(self) -> Result { + match self { + Value::Boolean(b) => Ok(b.clone()), + value => Err(EvalexprError::expected_boolean(value.clone()).into()), + } + } +} + impl Value { /// Returns true if `self` is a `Value::String`. pub fn is_string(&self) -> bool { @@ -75,7 +427,15 @@ impl Value { /// Clones the value stored in `self` as `String`, or returns `Err` if `self` is not a `Value::String`. pub fn as_string(&self) -> EvalexprResult { match self { - Value::String(string) => Ok(string.clone()), + Value::String(string) => Ok(string.ref_into_owned()), + value => Err(EvalexprError::expected_string(value.clone())), + } + } + /// Clones the value stored in `self` as `String`, or returns `Err` if `self` is not a `Value::String`. + pub fn as_string_or_none(&self) -> EvalexprResult> { + match self { + Value::String(string) => Ok(Some(string.ref_into_owned())), + Value::Empty => Ok(None), value => Err(EvalexprError::expected_string(value.clone())), } } @@ -88,10 +448,29 @@ impl Value { } } + /// Clones the value stored in `self` as `IntType`, or returns `Err` if `self` is not a `Value::Int`. + pub fn as_int_or_none(&self) -> EvalexprResult> { + match self { + Value::Int(i) => Ok(Some(*i)), + Value::Empty => Ok(None), + value => Err(EvalexprError::expected_int(value.clone())), + } + } + /// Clones the value stored in `self` as `FloatType`, or returns `Err` if `self` is not a `Value::Float`. pub fn as_float(&self) -> EvalexprResult { match self { Value::Float(f) => Ok(*f), + Value::Int(i) => Ok(*i as FloatType), + value => Err(EvalexprError::expected_float(value.clone())), + } + } + /// Clones the value stored in `self` as `FloatType`, or returns `Err` if `self` is not a `Value::Float`. + pub fn as_float_or_none(&self) -> EvalexprResult> { + match self { + Value::Float(f) => Ok(Some(*f)), + Value::Int(i) => Ok(Some(*i as FloatType)), + Value::Empty => Ok(None), value => Err(EvalexprError::expected_float(value.clone())), } } @@ -106,6 +485,17 @@ impl Value { } } + /// Clones the value stored in `self` as `FloatType`, or returns `Err` if `self` is not a `Value::Float` or `Value::Int`. + /// Note that this method silently converts `IntType` to `FloatType`, if `self` is a `Value::Int`. + pub fn as_number_or_none(&self) -> EvalexprResult> { + match self { + Value::Float(f) => Ok(Some(*f)), + Value::Int(i) => Ok(Some(*i as FloatType)), + Value::Empty => Ok(None), + value => Err(EvalexprError::expected_number(value.clone())), + } + } + /// Clones the value stored in `self` as `bool`, or returns `Err` if `self` is not a `Value::Boolean`. pub fn as_boolean(&self) -> EvalexprResult { match self { @@ -113,6 +503,14 @@ impl Value { value => Err(EvalexprError::expected_boolean(value.clone())), } } + /// Clones the value stored in `self` as `bool`, or returns `Err` if `self` is not a `Value::Boolean`. + pub fn as_boolean_or_none(&self) -> EvalexprResult> { + match self { + Value::Boolean(boolean) => Ok( Some(*boolean)), + Value::Empty => Ok(None), + value => Err(EvalexprError::expected_boolean(value.clone())), + } + } /// Clones the value stored in `self` as `TupleType`, or returns `Err` if `self` is not a `Value::Tuple`. pub fn as_tuple(&self) -> EvalexprResult { @@ -147,13 +545,13 @@ impl Value { impl From for Value { fn from(string: String) -> Self { - Value::String(string) + Value::String(CowData::Owned(Box::new(Pin::new(string)))) } } impl From<&str> for Value { fn from(string: &str) -> Self { - Value::String(string.to_string()) + Value::String(CowData::Owned(Box::new(Pin::new(string.to_string())))) } } @@ -193,10 +591,391 @@ impl From<()> for Value { } } +use std::ops::{Div, Rem}; + +use std::ops::Mul; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Error { + UnsupportedArithmeticBetweenTypes, + UnsupportedOperation, + DivisionByZero, + NonNumericType, + InvalidArgumentType, + InvalidInputString, + InvalidDateFormat, + CustomError(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::UnsupportedArithmeticBetweenTypes => write!(f, "Unsupported arithmetic between types"), + Error::UnsupportedOperation => write!(f, "Unsupported operation"), + Error::DivisionByZero => write!(f, "Division by zero"), + Error::NonNumericType => write!(f, "Non-numeric type"), + Error::InvalidArgumentType => write!(f, "Invalid argument type"), + Error::InvalidInputString => write!(f, "Invalid input string"), + Error::InvalidDateFormat => write!(f, "Invalid date format"), + Error::CustomError(ref msg) => write!(f, "Custom error: {}", msg), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl From for Error { + fn from(err: EvalexprError) -> Self { + Error::CustomError(format!("{}",err)) + } +} + +pub trait ToErrorType { + fn to_error_code(&self) -> i32; + fn to_error_message(&self) -> Option; +} + +impl Error{ + pub fn from_error_code(code: i32, custom_error: Option) -> Self { + match code { + 1 => Error::UnsupportedOperation, + 2 => Error::DivisionByZero, + 3 => Error::NonNumericType, + 4 => Error::UnsupportedArithmeticBetweenTypes, + 5 => Error::InvalidArgumentType, + 6 => Error::InvalidInputString, + 7 => Error::InvalidDateFormat, + 8 => Error::CustomError(custom_error.unwrap_or("Custom error".to_string())), + _ => Error::UnsupportedOperation, + } + } +} + +impl ToErrorType for Error { + fn to_error_code(&self) -> i32 { + match self { + Error::UnsupportedOperation => 1, + Error::DivisionByZero => 2, + Error::NonNumericType => 3, + Error::UnsupportedArithmeticBetweenTypes => 4, + Error::InvalidArgumentType => 5, + Error::InvalidInputString => 6, + Error::InvalidDateFormat => 7, + Error::CustomError(_) => 8 + } + } + + fn to_error_message(&self) -> Option { + match self { + Error::CustomError(message) => Some(message.clone()), + _ => None, + } + } +} + +use std::ops::Sub; + +use std::ops::Add; + +use std::ops::Neg; +use std::pin::Pin; +use std::str::FromStr; +use deepsize::{Context, DeepSizeOf}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::ordered_float::OrderedFloat; +use crate::value; + +impl Neg for Value { + type Output = Result; + + fn neg(self) -> Self::Output { + match self { + (Value::Empty) => Ok(Value::Empty), + Value::Int(a) => Ok(Value::Int(-a)), + Value::Float(a) => Ok(Value::Float(-a)), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), + } + } +} + + + + +impl Rem for &Value { + type Output = Result; + + fn rem(self, other: Self) -> Self::Output { + match (self, other) { + (Value::Empty, Value::Empty) => Ok(Value::Empty), + (Value::Empty, _) => Ok(Value::Empty), + (_, Value::Empty) => Ok(Value::Empty), + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a % b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a % b)), + (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 % b)), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float(*a % *b as f64)), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), + } + } +} + +impl Add for &Value { + type Output = Result; // Assuming you have an error type defined + + fn add(self, other: Self) -> Self::Output { + match (self, other) { + (Value::Empty, Value::Empty) => Ok(Value::Empty), + (Value::Empty, Value::String(b)) => Ok(Value::String(b.to_owned())), + (Value::String(b),Value::Empty) => Ok(Value::String(b.to_owned())), + (Value::Empty, _) => Ok(Value::Empty), + (_, Value::Empty) => Ok(Value::Empty), + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a + b)), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float(*a + *b as f64)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), + (Value::Int(a), Value::Float(b)) | (Value::Float(b), Value::Int(a)) => Ok(Value::Float(*a as FloatType + b)), + (Value::String(a), Value::String(b)) => Ok(Value::String(format!("{}{}", a.to_owned(), b.to_owned()).into())), + // Handle combinations with strings and numeric types if desired + (Value::Int(a), Value::String(b)) | (Value::String(b), Value::Int(a)) => Ok(Value::String(format!("{}{}", a, b).into())), + (Value::Float(a), Value::String(b)) | (Value::String(b), Value::Float(a)) => Ok(Value::String(format!("{}{}", a, b).into())), + // Add cases for other Value variants as necessary + _ => Err(Error::UnsupportedArithmeticBetweenTypes), + } + } +} + + +impl Sub for &Value { + type Output = Result; + + fn sub(self, other: Self) -> Self::Output { + match (self, other) { + (Value::Empty, Value::Empty) => Ok(Value::Empty), + (Value::Empty, _) => Ok(Value::Empty), + (_, Value::Empty) => Ok(Value::Empty), + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a - b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float((&OrderedFloat::from(*a) - &OrderedFloat::from(*b)).into())), + (Value::Int(a), Value::Float(b)) => Ok(Value::Float((&OrderedFloat::from(*a as f64) - &OrderedFloat::from(*b)).into())), + (Value::Float(a), Value::Int(b)) => Ok(Value::Float((&OrderedFloat::from(*a) - &OrderedFloat::from(*b as f64)).into())), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), + } + } +} + +impl Mul for &Value { + type Output = Result; + + fn mul(self, other: Self) -> Self::Output { + match (self, other) { + (Value::Empty, Value::Empty) => Ok(Value::Empty), + (Value::Empty, _) => Ok(Value::Empty), + (_, Value::Empty) => Ok(Value::Empty), + (Value::Int(a), Value::Int(b)) => Ok(Value::Int(a * b)), + (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)), + (Value::Int(a), Value::Float(b)) | (Value::Float(b), Value::Int(a)) => Ok(Value::Float(*a as f64 * b)), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), + } + } +} + +impl Div for &Value { + type Output = Result; + + fn div(self, other: Self) -> Self::Output { + match (self, other) { + (Value::Empty, Value::Empty) => Ok(Value::Empty), + (Value::Empty, _) => Ok(Value::Empty), + (_, Value::Empty) => Ok(Value::Empty), + (Value::Int(a), Value::Int(b)) => { + if b == &0 { + Err(Error::DivisionByZero) + } else { + Ok(Value::Int(a / b)) + } + }, + (Value::Float(a), Value::Float(b)) => { + if b == &0.0 { + Err(Error::DivisionByZero) + } else { + Ok(Value::Float(a / b)) + } + }, + (Value::Int(a), Value::Float(b)) => { + if b == &0.0 { + Err(Error::DivisionByZero) + } else { + Ok(Value::Float(*a as f64 / b)) + } + }, + (Value::Float(a), Value::Int(b)) => { + if b == &0 { + Err(Error::DivisionByZero) + } else { + Ok(Value::Float(*a / *b as f64)) + } + }, + // Add cases for other combinations as needed, returning UnsupportedOperation for non-numeric types + _ => Err(Error::UnsupportedArithmeticBetweenTypes), + } + } +} + + + +#[repr(C)] +pub struct FfiResult { + /// The value, which will be a default value in case of an error. + pub value: T, + /// An integer error code. 0 indicates success, non-zero indicates an error. + pub error_code: i32, + + pub error_message: String, +} + +pub fn to_ffi_result_func Result>(f: F) -> FfiResult { + match f() { + Ok(value) => FfiResult { + value, + error_code: 0, // Indicate success + error_message: "".to_string(), + }, + Err(e) => FfiResult { + value: T::default(), + error_code: e.to_error_code(), // Use the provided error code + error_message: format!("{}", e.to_error_message().unwrap_or_else(|| "".to_string())), + }, + } +} + +/// Converts a Rust `Result` to an `FfiResult`, where `T: Default`. +pub fn to_ffi_result(result: Result) -> FfiResult { + match result { + Ok(value) => FfiResult { + value, + error_code: 0, // Indicate success + error_message: "".to_string(), + }, + Err(e) => FfiResult { + value: T::default(), + error_code : e.to_error_code(), // Use the provided error code + error_message: format!("{}", e.to_error_message().unwrap_or("".to_string())), + }, + } +} + +pub fn to_nested_ffi_result( + result: Result, Box>, +) -> FfiResult { + match result { + Ok(inner_result) => match inner_result { + Ok(value) => FfiResult { + value, + error_code: 0, // Indicate success + error_message: "".to_string(), + }, + Err(e) => FfiResult { + value: T::default(), + error_code: e.to_error_code(), + error_message: e.to_error_message().unwrap_or_else(|| "".to_string()), + }, + }, + Err(panic_info) => { + let error_message = if let Some(s) = panic_info.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic_info.downcast_ref::() { + s.clone() + } else { + "Unknown panic".to_string() + }; + + FfiResult { + value: T::default(), + error_code: 8, + error_message, + } + } + } +} + + + + + +macro_rules! declare_arithmetic_for_result { + ($trait:ident, $fn:ident) => { + impl std::ops::$trait for Result { + type Output = Result; + + fn $fn(self, other: Value) -> Self::Output { + match self { + Ok(ref self_val) => self_val.$fn(&other), + Err(e) => Err(e), + } + } + } + + impl std::ops::$trait> for Value { + type Output = Result; + + fn $fn(self, other: Result) -> Self::Output { + match other { + Ok(ref other_val) => (&self).$fn(other_val), + Err(e) => Err(e), + } + } + } + + impl std::ops::$trait for Value { + type Output = Result; + + fn $fn(self, other: Self) -> Self::Output { + (&self).$fn(&other) + } + } + + impl std::ops::$trait<&Value> for Value { + type Output = Result; + fn $fn(self, other: &Self) -> Self::Output { + (&self).$fn(other) + } + } + + impl std::ops::$trait for &Value { + type Output = Result; + fn $fn(self, other: Value) -> Self::Output { + self.$fn(&other) + } + } + + impl std::ops::$trait> for &Value { + type Output = Result; + + fn $fn(self, other: Result) -> Self::Output { + match other { + Ok(ref other_val) => self.$fn(other_val), + Err(e) => Err(e), + } + } + } + + }; +} + + + +declare_arithmetic_for_result!(Rem, rem); +declare_arithmetic_for_result!(Add, add); +declare_arithmetic_for_result!(Sub, sub); +declare_arithmetic_for_result!(Mul, mul); +declare_arithmetic_for_result!(Div, div); + + #[cfg(test)] mod tests { use crate::value::{TupleType, Value}; - + use super::*; #[test] fn test_value_conversions() { assert_eq!( @@ -220,4 +999,188 @@ mod tests { assert!(Value::from(true).is_boolean()); assert!(Value::from(TupleType::new()).is_tuple()); } -} + + #[test] + fn test_add_integers() { + let a = Value::Int(10); + let b = Value::Int(20); + // Unwrap the result to compare the value directly + assert_eq!(a.add(b).unwrap(), Value::Int(30)); + } + + #[test] + fn test_add_integers_to_add() { + let a = Value::Int(10); + let b = Value::Int(20); + let c = Value::Int(20); + // Unwrap the result to compare the value directly + assert_eq!(a.add(b).add(c).unwrap(), Value::Int(50)); + } + + #[test] + fn test_subtract_floats() { + let a = Value::Float(20.5); + let b = Value::Float(10.25); + // Unwrap the result to compare the value directly + assert_eq!(a.sub(b).unwrap(), Value::Float(10.25)); + } + + #[test] + fn test_multiply_int_float() { + let a = Value::Int(2); + let b = Value::Float(3.5); + // Unwrap the result to compare the value directly + assert_eq!(a.mul(b).unwrap(), Value::Float(7.0)); + } + + #[test] + fn test_divide_float_by_int() { + let a = Value::Float(10.0); + let b = Value::Int(2); + // Unwrap the result to compare the value directly + assert_eq!(a.div(b).unwrap(), Value::Float(5.0)); + } + + #[test] + fn test_integer_remainder() { + let a = Value::Int(10); + let b = Value::Int(4); + // Unwrap the result to compare the value directly + assert_eq!(a.rem(b).unwrap(), Value::Int(2)); + } + + #[test] + fn test_error_on_divide_by_zero() { + let a = Value::Int(10); + let b = Value::Int(0); + // Here, we expect an error, so no unwrap is needed + assert!(matches!(a.div(b), Err(Error::DivisionByZero))); + } + #[test] + fn test_error_divide_zero_by_something() { + let a = Value::Int(10); + let b = Value::Int(0); + // Here, we expect an error, so no unwrap is needed + assert!(matches!(b.div(a).unwrap(), Value::Int(0))); + } + + + #[test] + fn test_add_integers_with_refs() { + let a = Value::Int(10); + let b = Value::Int(20); + // Using references in the add operation + assert_eq!((&a).add(&b).unwrap(), Value::Int(30)); + } + + #[test] + fn test_add_integers_to_add_with_refs() { + let a = Value::Int(10); + let b = Value::Int(20); + let c = Value::Int(20); + // Using references in chained add operations + assert_eq!((&a).add(&b).unwrap().add(&c).unwrap(), Value::Int(50)); + } + + #[test] + fn test_subtract_floats_with_refs() { + let a = Value::Float(20.5); + let b = Value::Float(10.25); + // Using references in the sub operation + assert_eq!((&a).sub(&b).unwrap(), Value::Float(10.25)); + } + + #[test] + fn test_multiply_int_float_with_refs() { + let a = Value::Int(2); + let b = Value::Float(3.5); + // Using references in the mul operation + assert_eq!((&a).mul(&b).unwrap(), Value::Float(7.0)); + } + + #[test] + fn test_divide_float_by_int_with_refs() { + let a = Value::Float(10.0); + let b = Value::Int(2); + // Using references in the div operation + assert_eq!((&a).div(&b).unwrap(), Value::Float(5.0)); + } + + #[test] + fn test_integer_remainder_with_refs() { + let a = Value::Int(10); + let b = Value::Int(4); + // Using references in the rem operation + assert_eq!((&a).rem(&b).unwrap(), Value::Int(2)); + } + + #[test] + fn test_error_on_divide_by_zero_with_refs() { + let a = Value::Int(10); + let b = Value::Int(0); + // Using references, expecting an error on division by zero + assert!(matches!((&a).div(&b), Err(Error::DivisionByZero))); + } + + #[test] + fn test_add_ref_and_value() { + let a = Value::Int(10); + let b = Value::Int(20); + // Reference on the left, value on the right + assert_eq!((&a).add(b.clone()).unwrap(), Value::Int(30)); + // Value on the left, reference on the right + assert_eq!(a.add(&b).unwrap(), Value::Int(30)); + } + + #[test] + fn test_subtract_ref_and_value() { + let a = Value::Float(20.5); + let b = Value::Float(10.25); + // Reference on the left, value on the right + assert_eq!((&a).sub(b.clone()).unwrap(), Value::Float(10.25)); + // Value on the left, reference on the right + assert_eq!(a.sub(&b).unwrap(), Value::Float(10.25)); + } + + #[test] + fn test_multiply_ref_and_value() { + let a = Value::Int(2); + let b = Value::Float(3.5); + // Reference on the left, value on the right + assert_eq!((&a).mul(b.clone()).unwrap(), Value::Float(7.0)); + // Value on the left, reference on the right + assert_eq!(a.mul(&b).unwrap(), Value::Float(7.0)); + } + + #[test] + fn test_divide_ref_and_value() { + let a = Value::Float(10.0); + let b = Value::Int(2); + // Reference on the left, value on the right + assert_eq!((&a).div(b.clone()).unwrap(), Value::Float(5.0)); + // Value on the left, reference on the right + assert_eq!(a.div(&b).unwrap(), Value::Float(5.0)); + } + + #[test] + fn test_remainder_ref_and_value() { + let a = Value::Int(10); + let b = Value::Int(4); + // Reference on the left, value on the right + assert_eq!((&a).rem(b.clone()).unwrap(), Value::Int(2)); + // Value on the left, reference on the right + assert_eq!(a.rem(&b).unwrap(), Value::Int(2)); + } + + #[test] + fn test_error_on_divide_by_zero_ref_and_value() { + let a = Value::Int(10); + let b = Value::Int(0); + // Reference on the left, value on the right + assert!(matches!((&a).div(b.clone()), Err(Error::DivisionByZero))); + // Value on the left, reference on the right + assert!(matches!(a.div(&b), Err(Error::DivisionByZero))); + } + + } + diff --git a/src/value/value_type.rs b/src/value/value_type.rs index 4ea7395f..db72087e 100644 --- a/src/value/value_type.rs +++ b/src/value/value_type.rs @@ -2,6 +2,8 @@ use crate::Value; /// The type of a `Value`. #[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Serialize, Deserialize)] +#[repr(C)] pub enum ValueType { /// The `Value::String` type. String, diff --git a/tests/close_values.csv b/tests/close_values.csv new file mode 100644 index 00000000..546db683 --- /dev/null +++ b/tests/close_values.csv @@ -0,0 +1,3392 @@ +dt,close +2024.06.10 00:19:00,199.681 +2024.06.10 00:20:00,199.7 +2024.06.10 00:21:00,199.655 +2024.06.10 00:22:00,199.66 +2024.06.10 00:23:00,199.653 +2024.06.10 00:24:00,199.652 +2024.06.10 00:25:00,199.621 +2024.06.10 00:26:00,199.588 +2024.06.10 00:27:00,199.623 +2024.06.10 00:28:00,199.623 +2024.06.10 00:29:00,199.64 +2024.06.10 00:30:00,199.649 +2024.06.10 00:31:00,199.631 +2024.06.10 00:32:00,199.656 +2024.06.10 00:33:00,199.645 +2024.06.10 00:34:00,199.636 +2024.06.10 00:35:00,199.605 +2024.06.10 00:36:00,199.594 +2024.06.10 00:37:00,199.59 +2024.06.10 00:38:00,199.585 +2024.06.10 00:39:00,199.585 +2024.06.10 00:40:00,199.597 +2024.06.10 00:41:00,199.596 +2024.06.10 00:42:00,199.601 +2024.06.10 00:43:00,199.603 +2024.06.10 00:44:00,199.623 +2024.06.10 00:45:00,199.642 +2024.06.10 00:46:00,199.652 +2024.06.10 00:47:00,199.664 +2024.06.10 00:48:00,199.66 +2024.06.10 00:49:00,199.656 +2024.06.10 00:50:00,199.66 +2024.06.10 00:51:00,199.651 +2024.06.10 00:52:00,199.653 +2024.06.10 00:53:00,199.65 +2024.06.10 00:54:00,199.636 +2024.06.10 00:55:00,199.623 +2024.06.10 00:56:00,199.621 +2024.06.10 00:57:00,199.621 +2024.06.10 00:58:00,199.606 +2024.06.10 00:59:00,199.597 +2024.06.10 01:00:00,199.628 +2024.06.10 01:01:00,199.635 +2024.06.10 01:02:00,199.606 +2024.06.10 01:03:00,199.586 +2024.06.10 01:04:00,199.606 +2024.06.10 01:05:00,199.559 +2024.06.10 01:06:00,199.548 +2024.06.10 01:07:00,199.507 +2024.06.10 01:08:00,199.487 +2024.06.10 01:09:00,199.488 +2024.06.10 01:10:00,199.473 +2024.06.10 01:11:00,199.498 +2024.06.10 01:12:00,199.494 +2024.06.10 01:13:00,199.487 +2024.06.10 01:14:00,199.503 +2024.06.10 01:15:00,199.531 +2024.06.10 01:16:00,199.548 +2024.06.10 01:17:00,199.578 +2024.06.10 01:18:00,199.569 +2024.06.10 01:19:00,199.566 +2024.06.10 01:20:00,199.59 +2024.06.10 01:21:00,199.611 +2024.06.10 01:22:00,199.606 +2024.06.10 01:23:00,199.618 +2024.06.10 01:24:00,199.644 +2024.06.10 01:25:00,199.652 +2024.06.10 01:26:00,199.634 +2024.06.10 01:27:00,199.636 +2024.06.10 01:28:00,199.631 +2024.06.10 01:29:00,199.602 +2024.06.10 01:30:00,199.57 +2024.06.10 01:31:00,199.591 +2024.06.10 01:32:00,199.576 +2024.06.10 01:33:00,199.571 +2024.06.10 01:34:00,199.58 +2024.06.10 01:35:00,199.6 +2024.06.10 01:36:00,199.581 +2024.06.10 01:37:00,199.577 +2024.06.10 01:38:00,199.62 +2024.06.10 01:39:00,199.605 +2024.06.10 01:40:00,199.603 +2024.06.10 01:41:00,199.618 +2024.06.10 01:42:00,199.599 +2024.06.10 01:43:00,199.642 +2024.06.10 01:44:00,199.652 +2024.06.10 01:45:00,199.666 +2024.06.10 01:46:00,199.659 +2024.06.10 01:47:00,199.652 +2024.06.10 01:48:00,199.607 +2024.06.10 01:49:00,199.589 +2024.06.10 01:50:00,199.621 +2024.06.10 01:51:00,199.618 +2024.06.10 01:52:00,199.615 +2024.06.10 01:53:00,199.65 +2024.06.10 01:54:00,199.655 +2024.06.10 01:55:00,199.681 +2024.06.10 01:56:00,199.657 +2024.06.10 01:57:00,199.605 +2024.06.10 01:58:00,199.576 +2024.06.10 01:59:00,199.552 +2024.06.10 02:00:00,199.521 +2024.06.10 02:01:00,199.51 +2024.06.10 02:02:00,199.517 +2024.06.10 02:03:00,199.508 +2024.06.10 02:04:00,199.53 +2024.06.10 02:05:00,199.531 +2024.06.10 02:06:00,199.523 +2024.06.10 02:07:00,199.538 +2024.06.10 02:08:00,199.59 +2024.06.10 02:09:00,199.599 +2024.06.10 02:10:00,199.656 +2024.06.10 02:11:00,199.678 +2024.06.10 02:12:00,199.666 +2024.06.10 02:13:00,199.666 +2024.06.10 02:14:00,199.666 +2024.06.10 02:15:00,199.674 +2024.06.10 02:16:00,199.689 +2024.06.10 02:17:00,199.685 +2024.06.10 02:18:00,199.646 +2024.06.10 02:19:00,199.634 +2024.06.10 02:20:00,199.627 +2024.06.10 02:21:00,199.593 +2024.06.10 02:22:00,199.567 +2024.06.10 02:23:00,199.582 +2024.06.10 02:24:00,199.562 +2024.06.10 02:25:00,199.584 +2024.06.10 02:26:00,199.588 +2024.06.10 02:27:00,199.625 +2024.06.10 02:28:00,199.633 +2024.06.10 02:29:00,199.638 +2024.06.10 02:30:00,199.666 +2024.06.10 02:31:00,199.683 +2024.06.10 02:32:00,199.702 +2024.06.10 02:33:00,199.696 +2024.06.10 02:34:00,199.676 +2024.06.10 02:35:00,199.693 +2024.06.10 02:36:00,199.696 +2024.06.10 02:37:00,199.679 +2024.06.10 02:38:00,199.694 +2024.06.10 02:39:00,199.681 +2024.06.10 02:40:00,199.674 +2024.06.10 02:41:00,199.662 +2024.06.10 02:42:00,199.684 +2024.06.10 02:43:00,199.699 +2024.06.10 02:44:00,199.696 +2024.06.10 02:45:00,199.699 +2024.06.10 02:46:00,199.699 +2024.06.10 02:47:00,199.712 +2024.06.10 02:48:00,199.714 +2024.06.10 02:49:00,199.703 +2024.06.10 02:50:00,199.677 +2024.06.10 02:51:00,199.677 +2024.06.10 02:52:00,199.679 +2024.06.10 02:53:00,199.663 +2024.06.10 02:54:00,199.678 +2024.06.10 02:55:00,199.643 +2024.06.10 02:56:00,199.627 +2024.06.10 02:57:00,199.634 +2024.06.10 02:58:00,199.639 +2024.06.10 02:59:00,199.643 +2024.06.10 03:00:00,199.644 +2024.06.10 03:01:00,199.645 +2024.06.10 03:02:00,199.643 +2024.06.10 03:03:00,199.648 +2024.06.10 03:04:00,199.642 +2024.06.10 03:05:00,199.639 +2024.06.10 03:06:00,199.676 +2024.06.10 03:07:00,199.664 +2024.06.10 03:08:00,199.65 +2024.06.10 03:09:00,199.646 +2024.06.10 03:10:00,199.635 +2024.06.10 03:11:00,199.615 +2024.06.10 03:12:00,199.608 +2024.06.10 03:13:00,199.625 +2024.06.10 03:14:00,199.606 +2024.06.10 03:15:00,199.632 +2024.06.10 03:16:00,199.634 +2024.06.10 03:17:00,199.647 +2024.06.10 03:18:00,199.645 +2024.06.10 03:19:00,199.661 +2024.06.10 03:20:00,199.658 +2024.06.10 03:21:00,199.646 +2024.06.10 03:22:00,199.655 +2024.06.10 03:23:00,199.668 +2024.06.10 03:24:00,199.659 +2024.06.10 03:25:00,199.671 +2024.06.10 03:26:00,199.656 +2024.06.10 03:27:00,199.642 +2024.06.10 03:28:00,199.649 +2024.06.10 03:29:00,199.649 +2024.06.10 03:30:00,199.687 +2024.06.10 03:31:00,199.708 +2024.06.10 03:32:00,199.705 +2024.06.10 03:33:00,199.687 +2024.06.10 03:34:00,199.685 +2024.06.10 03:35:00,199.671 +2024.06.10 03:36:00,199.682 +2024.06.10 03:37:00,199.669 +2024.06.10 03:38:00,199.675 +2024.06.10 03:39:00,199.672 +2024.06.10 03:40:00,199.666 +2024.06.10 03:41:00,199.66 +2024.06.10 03:42:00,199.66 +2024.06.10 03:43:00,199.661 +2024.06.10 03:44:00,199.653 +2024.06.10 03:45:00,199.654 +2024.06.10 03:46:00,199.651 +2024.06.10 03:47:00,199.654 +2024.06.10 03:48:00,199.634 +2024.06.10 03:49:00,199.62 +2024.06.10 03:50:00,199.608 +2024.06.10 03:51:00,199.612 +2024.06.10 03:52:00,199.626 +2024.06.10 03:53:00,199.631 +2024.06.10 03:54:00,199.641 +2024.06.10 03:55:00,199.649 +2024.06.10 03:56:00,199.664 +2024.06.10 03:57:00,199.674 +2024.06.10 03:58:00,199.677 +2024.06.10 03:59:00,199.684 +2024.06.10 04:00:00,199.697 +2024.06.10 04:01:00,199.7 +2024.06.10 04:02:00,199.688 +2024.06.10 04:03:00,199.697 +2024.06.10 04:04:00,199.691 +2024.06.10 04:05:00,199.682 +2024.06.10 04:06:00,199.695 +2024.06.10 04:07:00,199.719 +2024.06.10 04:08:00,199.72 +2024.06.10 04:09:00,199.714 +2024.06.10 04:10:00,199.722 +2024.06.10 04:11:00,199.727 +2024.06.10 04:12:00,199.739 +2024.06.10 04:13:00,199.772 +2024.06.10 04:14:00,199.802 +2024.06.10 04:15:00,199.91 +2024.06.10 04:16:00,199.886 +2024.06.10 04:17:00,199.883 +2024.06.10 04:18:00,199.871 +2024.06.10 04:19:00,199.888 +2024.06.10 04:20:00,199.861 +2024.06.10 04:21:00,199.881 +2024.06.10 04:22:00,199.876 +2024.06.10 04:23:00,199.855 +2024.06.10 04:24:00,199.852 +2024.06.10 04:25:00,199.836 +2024.06.10 04:26:00,199.852 +2024.06.10 04:27:00,199.863 +2024.06.10 04:28:00,199.859 +2024.06.10 04:29:00,199.845 +2024.06.10 04:30:00,199.866 +2024.06.10 04:31:00,199.883 +2024.06.10 04:32:00,199.868 +2024.06.10 04:33:00,199.878 +2024.06.10 04:34:00,199.851 +2024.06.10 04:35:00,199.858 +2024.06.10 04:36:00,199.845 +2024.06.10 04:37:00,199.823 +2024.06.10 04:38:00,199.826 +2024.06.10 04:39:00,199.834 +2024.06.10 04:40:00,199.854 +2024.06.10 04:41:00,199.867 +2024.06.10 04:42:00,199.865 +2024.06.10 04:43:00,199.852 +2024.06.10 04:44:00,199.862 +2024.06.10 04:45:00,199.852 +2024.06.10 04:46:00,199.839 +2024.06.10 04:47:00,199.842 +2024.06.10 04:48:00,199.856 +2024.06.10 04:49:00,199.858 +2024.06.10 04:50:00,199.879 +2024.06.10 04:51:00,199.866 +2024.06.10 04:52:00,199.873 +2024.06.10 04:53:00,199.862 +2024.06.10 04:54:00,199.854 +2024.06.10 04:55:00,199.821 +2024.06.10 04:56:00,199.806 +2024.06.10 04:57:00,199.815 +2024.06.10 04:58:00,199.79 +2024.06.10 04:59:00,199.793 +2024.06.10 05:00:00,199.816 +2024.06.10 05:01:00,199.804 +2024.06.10 05:02:00,199.806 +2024.06.10 05:03:00,199.808 +2024.06.10 05:04:00,199.781 +2024.06.10 05:05:00,199.76 +2024.06.10 05:06:00,199.753 +2024.06.10 05:07:00,199.756 +2024.06.10 05:08:00,199.735 +2024.06.10 05:09:00,199.726 +2024.06.10 05:10:00,199.72 +2024.06.10 05:11:00,199.721 +2024.06.10 05:12:00,199.739 +2024.06.10 05:13:00,199.742 +2024.06.10 05:14:00,199.731 +2024.06.10 05:15:00,199.724 +2024.06.10 05:16:00,199.742 +2024.06.10 05:17:00,199.761 +2024.06.10 05:18:00,199.758 +2024.06.10 05:19:00,199.746 +2024.06.10 05:20:00,199.75 +2024.06.10 05:21:00,199.728 +2024.06.10 05:22:00,199.712 +2024.06.10 05:23:00,199.706 +2024.06.10 05:24:00,199.712 +2024.06.10 05:25:00,199.711 +2024.06.10 05:26:00,199.723 +2024.06.10 05:27:00,199.723 +2024.06.10 05:28:00,199.722 +2024.06.10 05:29:00,199.706 +2024.06.10 05:30:00,199.684 +2024.06.10 05:31:00,199.667 +2024.06.10 05:32:00,199.698 +2024.06.10 05:33:00,199.687 +2024.06.10 05:34:00,199.702 +2024.06.10 05:35:00,199.672 +2024.06.10 05:36:00,199.678 +2024.06.10 05:37:00,199.692 +2024.06.10 05:38:00,199.702 +2024.06.10 05:39:00,199.741 +2024.06.10 05:40:00,199.76 +2024.06.10 05:41:00,199.801 +2024.06.10 05:42:00,199.794 +2024.06.10 05:43:00,199.811 +2024.06.10 05:44:00,199.795 +2024.06.10 05:45:00,199.809 +2024.06.10 05:46:00,199.807 +2024.06.10 05:47:00,199.799 +2024.06.10 05:48:00,199.784 +2024.06.10 05:49:00,199.785 +2024.06.10 05:50:00,199.792 +2024.06.10 05:51:00,199.794 +2024.06.10 05:52:00,199.793 +2024.06.10 05:53:00,199.817 +2024.06.10 05:54:00,199.812 +2024.06.10 05:55:00,199.803 +2024.06.10 05:56:00,199.811 +2024.06.10 05:57:00,199.798 +2024.06.10 05:58:00,199.795 +2024.06.10 05:59:00,199.795 +2024.06.10 06:00:00,199.785 +2024.06.10 06:01:00,199.782 +2024.06.10 06:02:00,199.788 +2024.06.10 06:03:00,199.803 +2024.06.10 06:04:00,199.789 +2024.06.10 06:05:00,199.774 +2024.06.10 06:06:00,199.775 +2024.06.10 06:07:00,199.787 +2024.06.10 06:08:00,199.776 +2024.06.10 06:09:00,199.784 +2024.06.10 06:10:00,199.791 +2024.06.10 06:11:00,199.791 +2024.06.10 06:12:00,199.793 +2024.06.10 06:13:00,199.769 +2024.06.10 06:14:00,199.775 +2024.06.10 06:15:00,199.762 +2024.06.10 06:16:00,199.744 +2024.06.10 06:17:00,199.756 +2024.06.10 06:18:00,199.743 +2024.06.10 06:19:00,199.738 +2024.06.10 06:20:00,199.731 +2024.06.10 06:21:00,199.745 +2024.06.10 06:22:00,199.723 +2024.06.10 06:23:00,199.698 +2024.06.10 06:24:00,199.701 +2024.06.10 06:25:00,199.676 +2024.06.10 06:26:00,199.66 +2024.06.10 06:27:00,199.634 +2024.06.10 06:28:00,199.642 +2024.06.10 06:29:00,199.672 +2024.06.10 06:30:00,199.7 +2024.06.10 06:31:00,199.72 +2024.06.10 06:32:00,199.733 +2024.06.10 06:33:00,199.74 +2024.06.10 06:34:00,199.746 +2024.06.10 06:35:00,199.711 +2024.06.10 06:36:00,199.706 +2024.06.10 06:37:00,199.719 +2024.06.10 06:38:00,199.712 +2024.06.10 06:39:00,199.717 +2024.06.10 06:40:00,199.729 +2024.06.10 06:41:00,199.734 +2024.06.10 06:42:00,199.761 +2024.06.10 06:43:00,199.746 +2024.06.10 06:44:00,199.751 +2024.06.10 06:45:00,199.742 +2024.06.10 06:46:00,199.742 +2024.06.10 06:47:00,199.761 +2024.06.10 06:48:00,199.76 +2024.06.10 06:49:00,199.755 +2024.06.10 06:50:00,199.728 +2024.06.10 06:51:00,199.729 +2024.06.10 06:52:00,199.722 +2024.06.10 06:53:00,199.724 +2024.06.10 06:54:00,199.734 +2024.06.10 06:55:00,199.744 +2024.06.10 06:56:00,199.755 +2024.06.10 06:57:00,199.763 +2024.06.10 06:58:00,199.772 +2024.06.10 06:59:00,199.751 +2024.06.10 07:00:00,199.703 +2024.06.10 07:01:00,199.66 +2024.06.10 07:02:00,199.682 +2024.06.10 07:03:00,199.681 +2024.06.10 07:04:00,199.698 +2024.06.10 07:05:00,199.705 +2024.06.10 07:06:00,199.698 +2024.06.10 07:07:00,199.701 +2024.06.10 07:08:00,199.703 +2024.06.10 07:09:00,199.71 +2024.06.10 07:10:00,199.735 +2024.06.10 07:11:00,199.714 +2024.06.10 07:12:00,199.688 +2024.06.10 07:13:00,199.677 +2024.06.10 07:14:00,199.693 +2024.06.10 07:15:00,199.742 +2024.06.10 07:16:00,199.72 +2024.06.10 07:17:00,199.738 +2024.06.10 07:18:00,199.741 +2024.06.10 07:19:00,199.731 +2024.06.10 07:20:00,199.729 +2024.06.10 07:21:00,199.725 +2024.06.10 07:22:00,199.718 +2024.06.10 07:23:00,199.725 +2024.06.10 07:24:00,199.704 +2024.06.10 07:25:00,199.67 +2024.06.10 07:26:00,199.659 +2024.06.10 07:27:00,199.685 +2024.06.10 07:28:00,199.672 +2024.06.10 07:29:00,199.648 +2024.06.10 07:30:00,199.692 +2024.06.10 07:31:00,199.667 +2024.06.10 07:32:00,199.677 +2024.06.10 07:33:00,199.667 +2024.06.10 07:34:00,199.686 +2024.06.10 07:35:00,199.731 +2024.06.10 07:36:00,199.748 +2024.06.10 07:37:00,199.769 +2024.06.10 07:38:00,199.772 +2024.06.10 07:39:00,199.775 +2024.06.10 07:40:00,199.777 +2024.06.10 07:41:00,199.764 +2024.06.10 07:42:00,199.772 +2024.06.10 07:43:00,199.752 +2024.06.10 07:44:00,199.739 +2024.06.10 07:45:00,199.772 +2024.06.10 07:46:00,199.735 +2024.06.10 07:47:00,199.707 +2024.06.10 07:48:00,199.702 +2024.06.10 07:49:00,199.735 +2024.06.10 07:50:00,199.734 +2024.06.10 07:51:00,199.691 +2024.06.10 07:52:00,199.677 +2024.06.10 07:53:00,199.657 +2024.06.10 07:54:00,199.653 +2024.06.10 07:55:00,199.656 +2024.06.10 07:56:00,199.66 +2024.06.10 07:57:00,199.667 +2024.06.10 07:58:00,199.652 +2024.06.10 07:59:00,199.681 +2024.06.10 08:00:00,199.582 +2024.06.10 08:01:00,199.56 +2024.06.10 08:02:00,199.504 +2024.06.10 08:03:00,199.497 +2024.06.10 08:04:00,199.51 +2024.06.10 08:05:00,199.568 +2024.06.10 08:06:00,199.641 +2024.06.10 08:07:00,199.582 +2024.06.10 08:08:00,199.607 +2024.06.10 08:09:00,199.611 +2024.06.10 08:10:00,199.589 +2024.06.10 08:11:00,199.621 +2024.06.10 08:12:00,199.629 +2024.06.10 08:13:00,199.645 +2024.06.10 08:14:00,199.658 +2024.06.10 08:15:00,199.69 +2024.06.10 08:16:00,199.649 +2024.06.10 08:17:00,199.61 +2024.06.10 08:18:00,199.641 +2024.06.10 08:19:00,199.624 +2024.06.10 08:20:00,199.623 +2024.06.10 08:21:00,199.622 +2024.06.10 08:22:00,199.641 +2024.06.10 08:23:00,199.597 +2024.06.10 08:24:00,199.594 +2024.06.10 08:25:00,199.569 +2024.06.10 08:26:00,199.579 +2024.06.10 08:27:00,199.607 +2024.06.10 08:28:00,199.625 +2024.06.10 08:29:00,199.64 +2024.06.10 08:30:00,199.624 +2024.06.10 08:31:00,199.628 +2024.06.10 08:32:00,199.635 +2024.06.10 08:33:00,199.633 +2024.06.10 08:34:00,199.666 +2024.06.10 08:35:00,199.642 +2024.06.10 08:36:00,199.659 +2024.06.10 08:37:00,199.658 +2024.06.10 08:38:00,199.64 +2024.06.10 08:39:00,199.636 +2024.06.10 08:40:00,199.64 +2024.06.10 08:41:00,199.643 +2024.06.10 08:42:00,199.628 +2024.06.10 08:43:00,199.591 +2024.06.10 08:44:00,199.56 +2024.06.10 08:45:00,199.598 +2024.06.10 08:46:00,199.636 +2024.06.10 08:47:00,199.655 +2024.06.10 08:48:00,199.691 +2024.06.10 08:49:00,199.687 +2024.06.10 08:50:00,199.701 +2024.06.10 08:51:00,199.707 +2024.06.10 08:52:00,199.71 +2024.06.10 08:53:00,199.723 +2024.06.10 08:54:00,199.695 +2024.06.10 08:55:00,199.738 +2024.06.10 08:56:00,199.733 +2024.06.10 08:57:00,199.751 +2024.06.10 08:58:00,199.736 +2024.06.10 08:59:00,199.743 +2024.06.10 09:00:00,199.749 +2024.06.10 09:01:00,199.758 +2024.06.10 09:02:00,199.766 +2024.06.10 09:03:00,199.741 +2024.06.10 09:04:00,199.729 +2024.06.10 09:05:00,199.677 +2024.06.10 09:06:00,199.609 +2024.06.10 09:07:00,199.603 +2024.06.10 09:08:00,199.608 +2024.06.10 09:09:00,199.606 +2024.06.10 09:10:00,199.592 +2024.06.10 09:11:00,199.635 +2024.06.10 09:12:00,199.638 +2024.06.10 09:13:00,199.63 +2024.06.10 09:14:00,199.642 +2024.06.10 09:15:00,199.627 +2024.06.10 09:16:00,199.611 +2024.06.10 09:17:00,199.6 +2024.06.10 09:18:00,199.639 +2024.06.10 09:19:00,199.638 +2024.06.10 09:20:00,199.641 +2024.06.10 09:21:00,199.648 +2024.06.10 09:22:00,199.64 +2024.06.10 09:23:00,199.641 +2024.06.10 09:24:00,199.636 +2024.06.10 09:25:00,199.644 +2024.06.10 09:26:00,199.656 +2024.06.10 09:27:00,199.715 +2024.06.10 09:28:00,199.736 +2024.06.10 09:29:00,199.732 +2024.06.10 09:30:00,199.722 +2024.06.10 09:31:00,199.726 +2024.06.10 09:32:00,199.705 +2024.06.10 09:33:00,199.712 +2024.06.10 09:34:00,199.686 +2024.06.10 09:35:00,199.669 +2024.06.10 09:36:00,199.654 +2024.06.10 09:37:00,199.657 +2024.06.10 09:38:00,199.661 +2024.06.10 09:39:00,199.644 +2024.06.10 09:40:00,199.659 +2024.06.10 09:41:00,199.647 +2024.06.10 09:42:00,199.633 +2024.06.10 09:43:00,199.638 +2024.06.10 09:44:00,199.638 +2024.06.10 09:45:00,199.617 +2024.06.10 09:46:00,199.631 +2024.06.10 09:47:00,199.59 +2024.06.10 09:48:00,199.555 +2024.06.10 09:49:00,199.557 +2024.06.10 09:50:00,199.546 +2024.06.10 09:51:00,199.55 +2024.06.10 09:52:00,199.521 +2024.06.10 09:53:00,199.476 +2024.06.10 09:54:00,199.443 +2024.06.10 09:55:00,199.391 +2024.06.10 09:56:00,199.424 +2024.06.10 09:57:00,199.412 +2024.06.10 09:58:00,199.425 +2024.06.10 09:59:00,199.448 +2024.06.10 10:00:00,199.45 +2024.06.10 10:01:00,199.392 +2024.06.10 10:02:00,199.374 +2024.06.10 10:03:00,199.353 +2024.06.10 10:04:00,199.35 +2024.06.10 10:05:00,199.337 +2024.06.10 10:06:00,199.314 +2024.06.10 10:07:00,199.306 +2024.06.10 10:08:00,199.319 +2024.06.10 10:09:00,199.359 +2024.06.10 10:10:00,199.344 +2024.06.10 10:11:00,199.313 +2024.06.10 10:12:00,199.308 +2024.06.10 10:13:00,199.285 +2024.06.10 10:14:00,199.282 +2024.06.10 10:15:00,199.289 +2024.06.10 10:16:00,199.271 +2024.06.10 10:17:00,199.271 +2024.06.10 10:18:00,199.312 +2024.06.10 10:19:00,199.305 +2024.06.10 10:20:00,199.317 +2024.06.10 10:21:00,199.319 +2024.06.10 10:22:00,199.281 +2024.06.10 10:23:00,199.321 +2024.06.10 10:24:00,199.314 +2024.06.10 10:25:00,199.282 +2024.06.10 10:26:00,199.262 +2024.06.10 10:27:00,199.231 +2024.06.10 10:28:00,199.204 +2024.06.10 10:29:00,199.176 +2024.06.10 10:30:00,199.185 +2024.06.10 10:31:00,199.2 +2024.06.10 10:32:00,199.23 +2024.06.10 10:33:00,199.227 +2024.06.10 10:34:00,199.248 +2024.06.10 10:35:00,199.231 +2024.06.10 10:36:00,199.252 +2024.06.10 10:37:00,199.241 +2024.06.10 10:38:00,199.253 +2024.06.10 10:39:00,199.257 +2024.06.10 10:40:00,199.254 +2024.06.10 10:41:00,199.252 +2024.06.10 10:42:00,199.267 +2024.06.10 10:43:00,199.243 +2024.06.10 10:44:00,199.258 +2024.06.10 10:45:00,199.245 +2024.06.10 10:46:00,199.246 +2024.06.10 10:47:00,199.23 +2024.06.10 10:48:00,199.204 +2024.06.10 10:49:00,199.225 +2024.06.10 10:50:00,199.262 +2024.06.10 10:51:00,199.247 +2024.06.10 10:52:00,199.257 +2024.06.10 10:53:00,199.244 +2024.06.10 10:54:00,199.215 +2024.06.10 10:55:00,199.179 +2024.06.10 10:56:00,199.149 +2024.06.10 10:57:00,199.181 +2024.06.10 10:58:00,199.214 +2024.06.10 10:59:00,199.214 +2024.06.10 11:00:00,199.212 +2024.06.10 11:01:00,199.149 +2024.06.10 11:02:00,199.133 +2024.06.10 11:03:00,199.085 +2024.06.10 11:04:00,199.061 +2024.06.10 11:05:00,199.046 +2024.06.10 11:06:00,199.016 +2024.06.10 11:07:00,199.008 +2024.06.10 11:08:00,198.993 +2024.06.10 11:09:00,199.009 +2024.06.10 11:10:00,198.992 +2024.06.10 11:11:00,198.989 +2024.06.10 11:12:00,198.929 +2024.06.10 11:13:00,198.968 +2024.06.10 11:14:00,198.967 +2024.06.10 11:15:00,198.947 +2024.06.10 11:16:00,198.973 +2024.06.10 11:17:00,198.982 +2024.06.10 11:18:00,199.004 +2024.06.10 11:19:00,199.011 +2024.06.10 11:20:00,199.006 +2024.06.10 11:21:00,199.013 +2024.06.10 11:22:00,198.976 +2024.06.10 11:23:00,198.992 +2024.06.10 11:24:00,198.985 +2024.06.10 11:25:00,198.979 +2024.06.10 11:26:00,198.946 +2024.06.10 11:27:00,198.969 +2024.06.10 11:28:00,198.99 +2024.06.10 11:29:00,199.015 +2024.06.10 11:30:00,199.042 +2024.06.10 11:31:00,199.04 +2024.06.10 11:32:00,199.028 +2024.06.10 11:33:00,199.017 +2024.06.10 11:34:00,199.054 +2024.06.10 11:35:00,199.053 +2024.06.10 11:36:00,199.073 +2024.06.10 11:37:00,199.096 +2024.06.10 11:38:00,199.089 +2024.06.10 11:39:00,199.101 +2024.06.10 11:40:00,199.071 +2024.06.10 11:41:00,199.081 +2024.06.10 11:42:00,199.066 +2024.06.10 11:43:00,199.069 +2024.06.10 11:44:00,199.08 +2024.06.10 11:45:00,199.117 +2024.06.10 11:46:00,199.118 +2024.06.10 11:47:00,199.118 +2024.06.10 11:48:00,199.111 +2024.06.10 11:49:00,199.09 +2024.06.10 11:50:00,199.085 +2024.06.10 11:51:00,199.084 +2024.06.10 11:52:00,199.114 +2024.06.10 11:53:00,199.099 +2024.06.10 11:54:00,199.109 +2024.06.10 11:55:00,199.119 +2024.06.10 11:56:00,199.118 +2024.06.10 11:57:00,199.111 +2024.06.10 11:58:00,199.102 +2024.06.10 11:59:00,199.096 +2024.06.10 12:00:00,199.086 +2024.06.10 12:01:00,199.094 +2024.06.10 12:02:00,199.095 +2024.06.10 12:03:00,199.114 +2024.06.10 12:04:00,199.132 +2024.06.10 12:05:00,199.169 +2024.06.10 12:06:00,199.198 +2024.06.10 12:07:00,199.203 +2024.06.10 12:08:00,199.228 +2024.06.10 12:09:00,199.234 +2024.06.10 12:10:00,199.248 +2024.06.10 12:11:00,199.266 +2024.06.10 12:12:00,199.293 +2024.06.10 12:13:00,199.3 +2024.06.10 12:14:00,199.282 +2024.06.10 12:15:00,199.303 +2024.06.10 12:16:00,199.283 +2024.06.10 12:17:00,199.283 +2024.06.10 12:18:00,199.281 +2024.06.10 12:19:00,199.275 +2024.06.10 12:20:00,199.285 +2024.06.10 12:21:00,199.324 +2024.06.10 12:22:00,199.317 +2024.06.10 12:23:00,199.33 +2024.06.10 12:24:00,199.358 +2024.06.10 12:25:00,199.371 +2024.06.10 12:26:00,199.396 +2024.06.10 12:27:00,199.382 +2024.06.10 12:28:00,199.372 +2024.06.10 12:29:00,199.377 +2024.06.10 12:30:00,199.405 +2024.06.10 12:31:00,199.409 +2024.06.10 12:32:00,199.41 +2024.06.10 12:33:00,199.448 +2024.06.10 12:34:00,199.447 +2024.06.10 12:35:00,199.441 +2024.06.10 12:36:00,199.456 +2024.06.10 12:37:00,199.438 +2024.06.10 12:38:00,199.444 +2024.06.10 12:39:00,199.448 +2024.06.10 12:40:00,199.447 +2024.06.10 12:41:00,199.467 +2024.06.10 12:42:00,199.46 +2024.06.10 12:43:00,199.472 +2024.06.10 12:44:00,199.467 +2024.06.10 12:45:00,199.47 +2024.06.10 12:46:00,199.47 +2024.06.10 12:47:00,199.494 +2024.06.10 12:48:00,199.5 +2024.06.10 12:49:00,199.497 +2024.06.10 12:50:00,199.532 +2024.06.10 12:51:00,199.536 +2024.06.10 12:52:00,199.516 +2024.06.10 12:53:00,199.504 +2024.06.10 12:54:00,199.465 +2024.06.10 12:55:00,199.473 +2024.06.10 12:56:00,199.433 +2024.06.10 12:57:00,199.468 +2024.06.10 12:58:00,199.497 +2024.06.10 12:59:00,199.457 +2024.06.10 13:00:00,199.471 +2024.06.10 13:01:00,199.454 +2024.06.10 13:02:00,199.459 +2024.06.10 13:03:00,199.439 +2024.06.10 13:04:00,199.448 +2024.06.10 13:05:00,199.443 +2024.06.10 13:06:00,199.436 +2024.06.10 13:07:00,199.464 +2024.06.10 13:08:00,199.521 +2024.06.10 13:09:00,199.526 +2024.06.10 13:10:00,199.519 +2024.06.10 13:11:00,199.544 +2024.06.10 13:12:00,199.537 +2024.06.10 13:13:00,199.526 +2024.06.10 13:14:00,199.516 +2024.06.10 13:15:00,199.535 +2024.06.10 13:16:00,199.525 +2024.06.10 13:17:00,199.506 +2024.06.10 13:18:00,199.532 +2024.06.10 13:19:00,199.536 +2024.06.10 13:20:00,199.558 +2024.06.10 13:21:00,199.595 +2024.06.10 13:22:00,199.612 +2024.06.10 13:23:00,199.616 +2024.06.10 13:24:00,199.637 +2024.06.10 13:25:00,199.675 +2024.06.10 13:26:00,199.686 +2024.06.10 13:27:00,199.699 +2024.06.10 13:28:00,199.72 +2024.06.10 13:29:00,199.726 +2024.06.10 13:30:00,199.753 +2024.06.10 13:31:00,199.742 +2024.06.10 13:32:00,199.737 +2024.06.10 13:33:00,199.76 +2024.06.10 13:34:00,199.744 +2024.06.10 13:35:00,199.754 +2024.06.10 13:36:00,199.746 +2024.06.10 13:37:00,199.756 +2024.06.10 13:38:00,199.774 +2024.06.10 13:39:00,199.743 +2024.06.10 13:40:00,199.774 +2024.06.10 13:41:00,199.775 +2024.06.10 13:42:00,199.774 +2024.06.10 13:43:00,199.781 +2024.06.10 13:44:00,199.787 +2024.06.10 13:45:00,199.753 +2024.06.10 13:46:00,199.689 +2024.06.10 13:47:00,199.676 +2024.06.10 13:48:00,199.656 +2024.06.10 13:49:00,199.644 +2024.06.10 13:50:00,199.644 +2024.06.10 13:51:00,199.625 +2024.06.10 13:52:00,199.624 +2024.06.10 13:53:00,199.604 +2024.06.10 13:54:00,199.596 +2024.06.10 13:55:00,199.582 +2024.06.10 13:56:00,199.541 +2024.06.10 13:57:00,199.539 +2024.06.10 13:58:00,199.557 +2024.06.10 13:59:00,199.545 +2024.06.10 14:00:00,199.526 +2024.06.10 14:01:00,199.508 +2024.06.10 14:02:00,199.49 +2024.06.10 14:03:00,199.486 +2024.06.10 14:04:00,199.481 +2024.06.10 14:05:00,199.49 +2024.06.10 14:06:00,199.472 +2024.06.10 14:07:00,199.515 +2024.06.10 14:08:00,199.47 +2024.06.10 14:09:00,199.471 +2024.06.10 14:10:00,199.469 +2024.06.10 14:11:00,199.414 +2024.06.10 14:12:00,199.429 +2024.06.10 14:13:00,199.383 +2024.06.10 14:14:00,199.359 +2024.06.10 14:15:00,199.332 +2024.06.10 14:16:00,199.333 +2024.06.10 14:17:00,199.333 +2024.06.10 14:18:00,199.37 +2024.06.10 14:19:00,199.345 +2024.06.10 14:20:00,199.359 +2024.06.10 14:21:00,199.342 +2024.06.10 14:22:00,199.335 +2024.06.10 14:23:00,199.338 +2024.06.10 14:24:00,199.331 +2024.06.10 14:25:00,199.322 +2024.06.10 14:26:00,199.348 +2024.06.10 14:27:00,199.34 +2024.06.10 14:28:00,199.348 +2024.06.10 14:29:00,199.337 +2024.06.10 14:30:00,199.361 +2024.06.10 14:31:00,199.361 +2024.06.10 14:32:00,199.358 +2024.06.10 14:33:00,199.363 +2024.06.10 14:34:00,199.371 +2024.06.10 14:35:00,199.364 +2024.06.10 14:36:00,199.381 +2024.06.10 14:37:00,199.408 +2024.06.10 14:38:00,199.411 +2024.06.10 14:39:00,199.398 +2024.06.10 14:40:00,199.37 +2024.06.10 14:41:00,199.409 +2024.06.10 14:42:00,199.42 +2024.06.10 14:43:00,199.442 +2024.06.10 14:44:00,199.443 +2024.06.10 14:45:00,199.431 +2024.06.10 14:46:00,199.416 +2024.06.10 14:47:00,199.38 +2024.06.10 14:48:00,199.396 +2024.06.10 14:49:00,199.415 +2024.06.10 14:50:00,199.418 +2024.06.10 14:51:00,199.419 +2024.06.10 14:52:00,199.444 +2024.06.10 14:53:00,199.447 +2024.06.10 14:54:00,199.396 +2024.06.10 14:55:00,199.402 +2024.06.10 14:56:00,199.415 +2024.06.10 14:57:00,199.412 +2024.06.10 14:58:00,199.393 +2024.06.10 14:59:00,199.386 +2024.06.10 15:00:00,199.342 +2024.06.10 15:01:00,199.343 +2024.06.10 15:02:00,199.329 +2024.06.10 15:03:00,199.312 +2024.06.10 15:04:00,199.328 +2024.06.10 15:05:00,199.381 +2024.06.10 15:06:00,199.42 +2024.06.10 15:07:00,199.403 +2024.06.10 15:08:00,199.415 +2024.06.10 15:09:00,199.401 +2024.06.10 15:10:00,199.402 +2024.06.10 15:11:00,199.405 +2024.06.10 15:12:00,199.399 +2024.06.10 15:13:00,199.425 +2024.06.10 15:14:00,199.443 +2024.06.10 15:15:00,199.47 +2024.06.10 15:16:00,199.488 +2024.06.10 15:17:00,199.49 +2024.06.10 15:18:00,199.474 +2024.06.10 15:19:00,199.425 +2024.06.10 15:20:00,199.421 +2024.06.10 15:21:00,199.421 +2024.06.10 15:22:00,199.444 +2024.06.10 15:23:00,199.444 +2024.06.10 15:24:00,199.437 +2024.06.10 15:25:00,199.455 +2024.06.10 15:26:00,199.449 +2024.06.10 15:27:00,199.433 +2024.06.10 15:28:00,199.439 +2024.06.10 15:29:00,199.448 +2024.06.10 15:30:00,199.469 +2024.06.10 15:31:00,199.461 +2024.06.10 15:32:00,199.483 +2024.06.10 15:33:00,199.479 +2024.06.10 15:34:00,199.507 +2024.06.10 15:35:00,199.512 +2024.06.10 15:36:00,199.523 +2024.06.10 15:37:00,199.503 +2024.06.10 15:38:00,199.496 +2024.06.10 15:39:00,199.508 +2024.06.10 15:40:00,199.518 +2024.06.10 15:41:00,199.516 +2024.06.10 15:42:00,199.492 +2024.06.10 15:43:00,199.453 +2024.06.10 15:44:00,199.483 +2024.06.10 15:45:00,199.497 +2024.06.10 15:46:00,199.54 +2024.06.10 15:47:00,199.517 +2024.06.10 15:48:00,199.532 +2024.06.10 15:49:00,199.508 +2024.06.10 15:50:00,199.554 +2024.06.10 15:51:00,199.54 +2024.06.10 15:52:00,199.542 +2024.06.10 15:53:00,199.54 +2024.06.10 15:54:00,199.537 +2024.06.10 15:55:00,199.51 +2024.06.10 15:56:00,199.531 +2024.06.10 15:57:00,199.531 +2024.06.10 15:58:00,199.532 +2024.06.10 15:59:00,199.521 +2024.06.10 16:00:00,199.518 +2024.06.10 16:01:00,199.551 +2024.06.10 16:02:00,199.571 +2024.06.10 16:03:00,199.569 +2024.06.10 16:04:00,199.566 +2024.06.10 16:05:00,199.57 +2024.06.10 16:06:00,199.578 +2024.06.10 16:07:00,199.601 +2024.06.10 16:08:00,199.584 +2024.06.10 16:09:00,199.562 +2024.06.10 16:10:00,199.518 +2024.06.10 16:11:00,199.511 +2024.06.10 16:12:00,199.516 +2024.06.10 16:13:00,199.519 +2024.06.10 16:14:00,199.527 +2024.06.10 16:15:00,199.589 +2024.06.10 16:16:00,199.593 +2024.06.10 16:17:00,199.56 +2024.06.10 16:18:00,199.581 +2024.06.10 16:19:00,199.604 +2024.06.10 16:20:00,199.624 +2024.06.10 16:21:00,199.607 +2024.06.10 16:22:00,199.626 +2024.06.10 16:23:00,199.645 +2024.06.10 16:24:00,199.649 +2024.06.10 16:25:00,199.69 +2024.06.10 16:26:00,199.681 +2024.06.10 16:27:00,199.702 +2024.06.10 16:28:00,199.697 +2024.06.10 16:29:00,199.702 +2024.06.10 16:30:00,199.723 +2024.06.10 16:31:00,199.721 +2024.06.10 16:32:00,199.711 +2024.06.10 16:33:00,199.727 +2024.06.10 16:34:00,199.777 +2024.06.10 16:35:00,199.775 +2024.06.10 16:36:00,199.748 +2024.06.10 16:37:00,199.773 +2024.06.10 16:38:00,199.776 +2024.06.10 16:39:00,199.774 +2024.06.10 16:40:00,199.753 +2024.06.10 16:41:00,199.761 +2024.06.10 16:42:00,199.765 +2024.06.10 16:43:00,199.759 +2024.06.10 16:44:00,199.723 +2024.06.10 16:45:00,199.699 +2024.06.10 16:46:00,199.669 +2024.06.10 16:47:00,199.654 +2024.06.10 16:48:00,199.657 +2024.06.10 16:49:00,199.669 +2024.06.10 16:50:00,199.672 +2024.06.10 16:51:00,199.674 +2024.06.10 16:52:00,199.681 +2024.06.10 16:53:00,199.687 +2024.06.10 16:54:00,199.696 +2024.06.10 16:55:00,199.706 +2024.06.10 16:56:00,199.738 +2024.06.10 16:57:00,199.75 +2024.06.10 16:58:00,199.741 +2024.06.10 16:59:00,199.777 +2024.06.10 17:00:00,199.793 +2024.06.10 17:01:00,199.809 +2024.06.10 17:02:00,199.807 +2024.06.10 17:03:00,199.806 +2024.06.10 17:04:00,199.803 +2024.06.10 17:05:00,199.793 +2024.06.10 17:06:00,199.793 +2024.06.10 17:07:00,199.784 +2024.06.10 17:08:00,199.812 +2024.06.10 17:09:00,199.818 +2024.06.10 17:10:00,199.861 +2024.06.10 17:11:00,199.861 +2024.06.10 17:12:00,199.863 +2024.06.10 17:13:00,199.861 +2024.06.10 17:14:00,199.848 +2024.06.10 17:15:00,199.861 +2024.06.10 17:16:00,199.838 +2024.06.10 17:17:00,199.822 +2024.06.10 17:18:00,199.834 +2024.06.10 17:19:00,199.841 +2024.06.10 17:20:00,199.877 +2024.06.10 17:21:00,199.841 +2024.06.10 17:22:00,199.859 +2024.06.10 17:23:00,199.86 +2024.06.10 17:24:00,199.863 +2024.06.10 17:25:00,199.845 +2024.06.10 17:26:00,199.876 +2024.06.10 17:27:00,199.854 +2024.06.10 17:28:00,199.834 +2024.06.10 17:29:00,199.849 +2024.06.10 17:30:00,199.855 +2024.06.10 17:31:00,199.838 +2024.06.10 17:32:00,199.836 +2024.06.10 17:33:00,199.848 +2024.06.10 17:34:00,199.85 +2024.06.10 17:35:00,199.84 +2024.06.10 17:36:00,199.836 +2024.06.10 17:37:00,199.838 +2024.06.10 17:38:00,199.828 +2024.06.10 17:39:00,199.831 +2024.06.10 17:40:00,199.852 +2024.06.10 17:41:00,199.835 +2024.06.10 17:42:00,199.83 +2024.06.10 17:43:00,199.838 +2024.06.10 17:44:00,199.831 +2024.06.10 17:45:00,199.824 +2024.06.10 17:46:00,199.799 +2024.06.10 17:47:00,199.808 +2024.06.10 17:48:00,199.816 +2024.06.10 17:49:00,199.805 +2024.06.10 17:50:00,199.81 +2024.06.10 17:51:00,199.821 +2024.06.10 17:52:00,199.829 +2024.06.10 17:53:00,199.823 +2024.06.10 17:54:00,199.818 +2024.06.10 17:55:00,199.82 +2024.06.10 17:56:00,199.819 +2024.06.10 17:57:00,199.819 +2024.06.10 17:58:00,199.818 +2024.06.10 17:59:00,199.819 +2024.06.10 18:00:00,199.812 +2024.06.10 18:01:00,199.828 +2024.06.10 18:02:00,199.866 +2024.06.10 18:03:00,199.908 +2024.06.10 18:04:00,199.927 +2024.06.10 18:05:00,199.935 +2024.06.10 18:06:00,199.868 +2024.06.10 18:07:00,199.874 +2024.06.10 18:08:00,199.848 +2024.06.10 18:09:00,199.865 +2024.06.10 18:10:00,199.86 +2024.06.10 18:11:00,199.864 +2024.06.10 18:12:00,199.849 +2024.06.10 18:13:00,199.847 +2024.06.10 18:14:00,199.833 +2024.06.10 18:15:00,199.821 +2024.06.10 18:16:00,199.824 +2024.06.10 18:17:00,199.817 +2024.06.10 18:18:00,199.839 +2024.06.10 18:19:00,199.837 +2024.06.10 18:20:00,199.84 +2024.06.10 18:21:00,199.844 +2024.06.10 18:22:00,199.851 +2024.06.10 18:23:00,199.835 +2024.06.10 18:24:00,199.82 +2024.06.10 18:25:00,199.829 +2024.06.10 18:26:00,199.811 +2024.06.10 18:27:00,199.8 +2024.06.10 18:28:00,199.794 +2024.06.10 18:29:00,199.804 +2024.06.10 18:30:00,199.804 +2024.06.10 18:31:00,199.803 +2024.06.10 18:32:00,199.809 +2024.06.10 18:33:00,199.819 +2024.06.10 18:34:00,199.822 +2024.06.10 18:35:00,199.791 +2024.06.10 18:36:00,199.789 +2024.06.10 18:37:00,199.787 +2024.06.10 18:38:00,199.79 +2024.06.10 18:39:00,199.814 +2024.06.10 18:40:00,199.817 +2024.06.10 18:41:00,199.813 +2024.06.10 18:42:00,199.847 +2024.06.10 18:43:00,199.843 +2024.06.10 18:44:00,199.851 +2024.06.10 18:45:00,199.87 +2024.06.10 18:46:00,199.876 +2024.06.10 18:47:00,199.876 +2024.06.10 18:48:00,199.901 +2024.06.10 18:49:00,199.913 +2024.06.10 18:50:00,199.919 +2024.06.10 18:51:00,199.929 +2024.06.10 18:52:00,199.921 +2024.06.10 18:53:00,199.923 +2024.06.10 18:54:00,199.911 +2024.06.10 18:55:00,199.92 +2024.06.10 18:56:00,199.921 +2024.06.10 18:57:00,199.913 +2024.06.10 18:58:00,199.91 +2024.06.10 18:59:00,199.909 +2024.06.10 19:00:00,199.915 +2024.06.10 19:01:00,199.915 +2024.06.10 19:02:00,199.901 +2024.06.10 19:03:00,199.905 +2024.06.10 19:04:00,199.892 +2024.06.10 19:05:00,199.885 +2024.06.10 19:06:00,199.899 +2024.06.10 19:07:00,199.91 +2024.06.10 19:08:00,199.914 +2024.06.10 19:09:00,199.892 +2024.06.10 19:10:00,199.893 +2024.06.10 19:11:00,199.893 +2024.06.10 19:12:00,199.884 +2024.06.10 19:13:00,199.894 +2024.06.10 19:14:00,199.891 +2024.06.10 19:15:00,199.876 +2024.06.10 19:16:00,199.873 +2024.06.10 19:17:00,199.881 +2024.06.10 19:18:00,199.88 +2024.06.10 19:19:00,199.865 +2024.06.10 19:20:00,199.854 +2024.06.10 19:21:00,199.852 +2024.06.10 19:22:00,199.849 +2024.06.10 19:23:00,199.834 +2024.06.10 19:24:00,199.834 +2024.06.10 19:25:00,199.841 +2024.06.10 19:26:00,199.829 +2024.06.10 19:27:00,199.832 +2024.06.10 19:28:00,199.829 +2024.06.10 19:29:00,199.832 +2024.06.10 19:30:00,199.828 +2024.06.10 19:31:00,199.834 +2024.06.10 19:32:00,199.826 +2024.06.10 19:33:00,199.828 +2024.06.10 19:34:00,199.828 +2024.06.10 19:35:00,199.849 +2024.06.10 19:36:00,199.862 +2024.06.10 19:37:00,199.861 +2024.06.10 19:38:00,199.864 +2024.06.10 19:39:00,199.868 +2024.06.10 19:40:00,199.871 +2024.06.10 19:41:00,199.887 +2024.06.10 19:42:00,199.884 +2024.06.10 19:43:00,199.922 +2024.06.10 19:44:00,199.944 +2024.06.10 19:45:00,199.954 +2024.06.10 19:46:00,199.951 +2024.06.10 19:47:00,199.961 +2024.06.10 19:48:00,199.972 +2024.06.10 19:49:00,199.966 +2024.06.10 19:50:00,199.971 +2024.06.10 19:51:00,199.992 +2024.06.10 19:52:00,200 +2024.06.10 19:53:00,200.01 +2024.06.10 19:54:00,199.992 +2024.06.10 19:55:00,199.985 +2024.06.10 19:56:00,199.971 +2024.06.10 19:57:00,200.01 +2024.06.10 19:58:00,200.003 +2024.06.10 19:59:00,199.996 +2024.06.10 20:00:00,200.018 +2024.06.10 20:01:00,200.033 +2024.06.10 20:02:00,200.025 +2024.06.10 20:03:00,200.018 +2024.06.10 20:04:00,200.013 +2024.06.10 20:05:00,200.018 +2024.06.10 20:06:00,200.013 +2024.06.10 20:07:00,200.014 +2024.06.10 20:08:00,200.014 +2024.06.10 20:09:00,200.011 +2024.06.10 20:10:00,199.998 +2024.06.10 20:11:00,199.997 +2024.06.10 20:12:00,200 +2024.06.10 20:13:00,199.995 +2024.06.10 20:14:00,200 +2024.06.10 20:15:00,199.998 +2024.06.10 20:16:00,199.995 +2024.06.10 20:17:00,199.983 +2024.06.10 20:18:00,199.985 +2024.06.10 20:19:00,199.991 +2024.06.10 20:20:00,199.997 +2024.06.10 20:21:00,199.996 +2024.06.10 20:22:00,199.992 +2024.06.10 20:23:00,199.991 +2024.06.10 20:24:00,199.977 +2024.06.10 20:25:00,199.959 +2024.06.10 20:26:00,199.946 +2024.06.10 20:27:00,199.945 +2024.06.10 20:28:00,199.956 +2024.06.10 20:29:00,199.962 +2024.06.10 20:30:00,199.945 +2024.06.10 20:31:00,199.935 +2024.06.10 20:32:00,199.931 +2024.06.10 20:33:00,199.915 +2024.06.10 20:34:00,199.915 +2024.06.10 20:35:00,199.916 +2024.06.10 20:36:00,199.921 +2024.06.10 20:37:00,199.923 +2024.06.10 20:38:00,199.924 +2024.06.10 20:39:00,199.929 +2024.06.10 20:40:00,199.926 +2024.06.10 20:41:00,199.929 +2024.06.10 20:42:00,199.925 +2024.06.10 20:43:00,199.915 +2024.06.10 20:44:00,199.916 +2024.06.10 20:45:00,199.917 +2024.06.10 20:46:00,199.921 +2024.06.10 20:47:00,199.916 +2024.06.10 20:48:00,199.92 +2024.06.10 20:49:00,199.902 +2024.06.10 20:50:00,199.904 +2024.06.10 20:51:00,199.898 +2024.06.10 20:52:00,199.889 +2024.06.10 20:53:00,199.9 +2024.06.10 20:54:00,199.886 +2024.06.10 20:55:00,199.887 +2024.06.10 20:56:00,199.874 +2024.06.10 20:57:00,199.868 +2024.06.10 20:58:00,199.86 +2024.06.10 20:59:00,199.874 +2024.06.10 21:00:00,199.86 +2024.06.10 21:01:00,199.842 +2024.06.10 21:02:00,199.865 +2024.06.10 21:03:00,199.869 +2024.06.10 21:04:00,199.883 +2024.06.10 21:05:00,199.884 +2024.06.10 21:06:00,199.887 +2024.06.10 21:07:00,199.904 +2024.06.10 21:08:00,199.903 +2024.06.10 21:09:00,199.903 +2024.06.10 21:10:00,199.905 +2024.06.10 21:11:00,199.909 +2024.06.10 21:12:00,199.919 +2024.06.10 21:13:00,199.922 +2024.06.10 21:14:00,199.925 +2024.06.10 21:15:00,199.923 +2024.06.10 21:16:00,199.921 +2024.06.10 21:17:00,199.919 +2024.06.10 21:18:00,199.919 +2024.06.10 21:19:00,199.919 +2024.06.10 21:20:00,199.936 +2024.06.10 21:21:00,199.938 +2024.06.10 21:22:00,199.937 +2024.06.10 21:23:00,199.938 +2024.06.10 21:24:00,199.937 +2024.06.10 21:25:00,199.936 +2024.06.10 21:27:00,199.936 +2024.06.10 21:28:00,199.936 +2024.06.10 21:29:00,199.932 +2024.06.10 21:30:00,199.937 +2024.06.10 21:32:00,199.932 +2024.06.10 21:33:00,199.923 +2024.06.10 21:34:00,199.912 +2024.06.10 21:35:00,199.919 +2024.06.10 21:36:00,199.923 +2024.06.10 21:37:00,199.933 +2024.06.10 21:38:00,199.932 +2024.06.10 21:39:00,199.933 +2024.06.10 21:40:00,199.935 +2024.06.10 21:41:00,199.932 +2024.06.10 21:42:00,199.935 +2024.06.10 21:43:00,199.915 +2024.06.10 21:44:00,199.916 +2024.06.10 21:45:00,199.916 +2024.06.10 21:46:00,199.914 +2024.06.10 21:47:00,199.913 +2024.06.10 21:48:00,199.927 +2024.06.10 21:49:00,199.934 +2024.06.10 21:50:00,199.905 +2024.06.10 21:51:00,199.905 +2024.06.10 21:52:00,199.904 +2024.06.10 21:53:00,199.901 +2024.06.10 21:54:00,199.906 +2024.06.10 21:55:00,199.901 +2024.06.10 21:56:00,199.892 +2024.06.10 21:57:00,199.886 +2024.06.10 21:58:00,199.882 +2024.06.10 21:59:00,199.825 +2024.06.10 22:00:00,199.764 +2024.06.10 22:01:00,199.763 +2024.06.10 22:02:00,199.762 +2024.06.10 22:03:00,199.765 +2024.06.10 22:04:00,199.798 +2024.06.10 22:05:00,199.743 +2024.06.10 22:06:00,199.771 +2024.06.10 22:07:00,199.771 +2024.06.10 22:08:00,199.776 +2024.06.10 22:09:00,199.768 +2024.06.10 22:10:00,199.761 +2024.06.10 22:11:00,199.789 +2024.06.10 22:12:00,199.793 +2024.06.10 22:13:00,199.81 +2024.06.10 22:14:00,199.588 +2024.06.10 22:15:00,199.608 +2024.06.10 22:16:00,199.777 +2024.06.10 22:17:00,199.802 +2024.06.10 22:18:00,199.756 +2024.06.10 22:19:00,199.754 +2024.06.10 22:20:00,199.753 +2024.06.10 22:21:00,199.761 +2024.06.10 22:22:00,199.754 +2024.06.10 22:23:00,199.762 +2024.06.10 22:24:00,199.788 +2024.06.10 22:25:00,199.793 +2024.06.10 22:26:00,199.793 +2024.06.10 22:28:00,199.779 +2024.06.10 22:29:00,199.786 +2024.06.10 22:30:00,199.788 +2024.06.10 22:31:00,199.787 +2024.06.10 22:32:00,199.786 +2024.06.10 22:33:00,199.785 +2024.06.10 22:34:00,199.785 +2024.06.10 22:35:00,199.784 +2024.06.10 22:37:00,199.785 +2024.06.10 22:38:00,199.788 +2024.06.10 22:39:00,199.787 +2024.06.10 22:40:00,199.769 +2024.06.10 22:42:00,199.765 +2024.06.10 22:43:00,199.766 +2024.06.10 22:44:00,199.765 +2024.06.10 22:45:00,199.77 +2024.06.10 22:46:00,199.766 +2024.06.10 22:47:00,199.761 +2024.06.10 22:48:00,199.758 +2024.06.10 22:49:00,199.756 +2024.06.10 22:50:00,199.756 +2024.06.10 22:51:00,199.754 +2024.06.10 22:52:00,199.753 +2024.06.10 22:53:00,199.752 +2024.06.10 22:54:00,199.753 +2024.06.10 22:55:00,199.744 +2024.06.10 22:56:00,199.613 +2024.06.10 22:57:00,199.733 +2024.06.10 22:58:00,199.644 +2024.06.10 22:59:00,199.652 +2024.06.10 23:00:00,199.803 +2024.06.10 23:01:00,199.844 +2024.06.10 23:02:00,199.832 +2024.06.10 23:03:00,199.859 +2024.06.10 23:04:00,199.87 +2024.06.10 23:05:00,199.892 +2024.06.10 23:06:00,199.895 +2024.06.10 23:07:00,199.894 +2024.06.10 23:08:00,199.898 +2024.06.10 23:09:00,199.899 +2024.06.10 23:10:00,199.883 +2024.06.10 23:11:00,199.882 +2024.06.10 23:12:00,199.881 +2024.06.10 23:13:00,199.856 +2024.06.10 23:14:00,199.875 +2024.06.10 23:15:00,199.88 +2024.06.10 23:16:00,199.864 +2024.06.10 23:17:00,199.851 +2024.06.10 23:18:00,199.857 +2024.06.10 23:19:00,199.846 +2024.06.10 23:20:00,199.846 +2024.06.10 23:21:00,199.85 +2024.06.10 23:22:00,199.871 +2024.06.10 23:23:00,199.868 +2024.06.10 23:24:00,199.868 +2024.06.10 23:25:00,199.84 +2024.06.10 23:26:00,199.844 +2024.06.10 23:27:00,199.85 +2024.06.10 23:28:00,199.855 +2024.06.10 23:29:00,199.856 +2024.06.10 23:30:00,199.856 +2024.06.10 23:31:00,199.853 +2024.06.10 23:32:00,199.857 +2024.06.10 23:33:00,199.854 +2024.06.10 23:34:00,199.867 +2024.06.10 23:35:00,199.867 +2024.06.10 23:36:00,199.868 +2024.06.10 23:37:00,199.864 +2024.06.10 23:38:00,199.867 +2024.06.10 23:39:00,199.869 +2024.06.10 23:40:00,199.895 +2024.06.10 23:41:00,199.888 +2024.06.10 23:42:00,199.9 +2024.06.10 23:43:00,199.902 +2024.06.10 23:44:00,199.903 +2024.06.10 23:45:00,199.898 +2024.06.10 23:46:00,199.886 +2024.06.10 23:47:00,199.897 +2024.06.10 23:48:00,199.884 +2024.06.10 23:49:00,199.873 +2024.06.10 23:50:00,199.873 +2024.06.10 23:51:00,199.888 +2024.06.10 23:52:00,199.888 +2024.06.10 23:53:00,199.887 +2024.06.10 23:54:00,199.888 +2024.06.10 23:55:00,199.886 +2024.06.10 23:56:00,199.881 +2024.06.10 23:57:00,199.895 +2024.06.10 23:58:00,199.896 +2024.06.10 23:59:00,199.897 +2024.06.11 00:00:00,199.913 +2024.06.11 00:01:00,199.92 +2024.06.11 00:02:00,199.922 +2024.06.11 00:03:00,199.921 +2024.06.11 00:04:00,199.916 +2024.06.11 00:05:00,199.917 +2024.06.11 00:06:00,199.916 +2024.06.11 00:07:00,199.915 +2024.06.11 00:08:00,199.903 +2024.06.11 00:09:00,199.889 +2024.06.11 00:10:00,199.876 +2024.06.11 00:11:00,199.873 +2024.06.11 00:12:00,199.869 +2024.06.11 00:13:00,199.867 +2024.06.11 00:14:00,199.867 +2024.06.11 00:15:00,199.867 +2024.06.11 00:16:00,199.867 +2024.06.11 00:17:00,199.868 +2024.06.11 00:18:00,199.902 +2024.06.11 00:19:00,199.901 +2024.06.11 00:20:00,199.893 +2024.06.11 00:21:00,199.884 +2024.06.11 00:22:00,199.888 +2024.06.11 00:23:00,199.882 +2024.06.11 00:24:00,199.886 +2024.06.11 00:25:00,199.884 +2024.06.11 00:26:00,199.877 +2024.06.11 00:27:00,199.874 +2024.06.11 00:28:00,199.872 +2024.06.11 00:29:00,199.857 +2024.06.11 00:30:00,199.86 +2024.06.11 00:31:00,199.858 +2024.06.11 00:32:00,199.854 +2024.06.11 00:33:00,199.853 +2024.06.11 00:34:00,199.856 +2024.06.11 00:35:00,199.851 +2024.06.11 00:36:00,199.85 +2024.06.11 00:37:00,199.839 +2024.06.11 00:38:00,199.834 +2024.06.11 00:39:00,199.834 +2024.06.11 00:40:00,199.836 +2024.06.11 00:41:00,199.83 +2024.06.11 00:42:00,199.829 +2024.06.11 00:43:00,199.839 +2024.06.11 00:44:00,199.835 +2024.06.11 00:45:00,199.858 +2024.06.11 00:46:00,199.867 +2024.06.11 00:47:00,199.872 +2024.06.11 00:48:00,199.866 +2024.06.11 00:49:00,199.854 +2024.06.11 00:50:00,199.842 +2024.06.11 00:51:00,199.839 +2024.06.11 00:52:00,199.82 +2024.06.11 00:53:00,199.826 +2024.06.11 00:54:00,199.82 +2024.06.11 00:55:00,199.821 +2024.06.11 00:56:00,199.826 +2024.06.11 00:57:00,199.837 +2024.06.11 00:58:00,199.841 +2024.06.11 00:59:00,199.839 +2024.06.11 01:00:00,199.893 +2024.06.11 01:01:00,199.897 +2024.06.11 01:02:00,199.917 +2024.06.11 01:03:00,199.905 +2024.06.11 01:04:00,199.904 +2024.06.11 01:05:00,199.91 +2024.06.11 01:06:00,199.921 +2024.06.11 01:07:00,199.937 +2024.06.11 01:08:00,199.958 +2024.06.11 01:09:00,199.965 +2024.06.11 01:10:00,199.949 +2024.06.11 01:11:00,199.975 +2024.06.11 01:12:00,199.999 +2024.06.11 01:13:00,199.987 +2024.06.11 01:14:00,199.979 +2024.06.11 01:15:00,199.954 +2024.06.11 01:16:00,199.976 +2024.06.11 01:17:00,199.986 +2024.06.11 01:18:00,199.991 +2024.06.11 01:19:00,199.978 +2024.06.11 01:20:00,199.953 +2024.06.11 01:21:00,199.978 +2024.06.11 01:22:00,200.009 +2024.06.11 01:23:00,200.024 +2024.06.11 01:24:00,200.01 +2024.06.11 01:25:00,200.009 +2024.06.11 01:26:00,200.019 +2024.06.11 01:27:00,200.012 +2024.06.11 01:28:00,200.018 +2024.06.11 01:29:00,200.014 +2024.06.11 01:30:00,200.035 +2024.06.11 01:31:00,200.026 +2024.06.11 01:32:00,200.024 +2024.06.11 01:33:00,200.042 +2024.06.11 01:34:00,200.034 +2024.06.11 01:35:00,200.02 +2024.06.11 01:36:00,200.008 +2024.06.11 01:37:00,200.011 +2024.06.11 01:38:00,199.999 +2024.06.11 01:39:00,199.999 +2024.06.11 01:40:00,199.971 +2024.06.11 01:41:00,199.988 +2024.06.11 01:42:00,199.988 +2024.06.11 01:43:00,199.976 +2024.06.11 01:44:00,199.996 +2024.06.11 01:45:00,200.013 +2024.06.11 01:46:00,200.016 +2024.06.11 01:47:00,200.014 +2024.06.11 01:48:00,200.012 +2024.06.11 01:49:00,200.011 +2024.06.11 01:50:00,200.001 +2024.06.11 01:51:00,200.032 +2024.06.11 01:52:00,200.069 +2024.06.11 01:53:00,200.084 +2024.06.11 01:54:00,200.137 +2024.06.11 01:55:00,200.134 +2024.06.11 01:56:00,200.133 +2024.06.11 01:57:00,200.125 +2024.06.11 01:58:00,200.108 +2024.06.11 01:59:00,200.089 +2024.06.11 02:00:00,200.092 +2024.06.11 02:01:00,200.069 +2024.06.11 02:02:00,200.064 +2024.06.11 02:03:00,200.086 +2024.06.11 02:04:00,200.068 +2024.06.11 02:05:00,200.048 +2024.06.11 02:06:00,200.054 +2024.06.11 02:07:00,200.047 +2024.06.11 02:08:00,200.054 +2024.06.11 02:09:00,200.036 +2024.06.11 02:10:00,200.037 +2024.06.11 02:11:00,200.027 +2024.06.11 02:12:00,200.013 +2024.06.11 02:13:00,200.013 +2024.06.11 02:14:00,200.029 +2024.06.11 02:15:00,200.031 +2024.06.11 02:16:00,200.016 +2024.06.11 02:17:00,200.015 +2024.06.11 02:18:00,199.992 +2024.06.11 02:19:00,199.984 +2024.06.11 02:20:00,199.982 +2024.06.11 02:21:00,199.977 +2024.06.11 02:22:00,199.995 +2024.06.11 02:23:00,200.027 +2024.06.11 02:24:00,200.03 +2024.06.11 02:25:00,200.032 +2024.06.11 02:26:00,200.031 +2024.06.11 02:27:00,200.026 +2024.06.11 02:28:00,200.021 +2024.06.11 02:29:00,200.056 +2024.06.11 02:30:00,200.047 +2024.06.11 02:31:00,200.076 +2024.06.11 02:32:00,200.053 +2024.06.11 02:33:00,200.051 +2024.06.11 02:34:00,200.083 +2024.06.11 02:35:00,200.074 +2024.06.11 02:36:00,200.062 +2024.06.11 02:37:00,200.07 +2024.06.11 02:38:00,200.091 +2024.06.11 02:39:00,200.071 +2024.06.11 02:40:00,200.103 +2024.06.11 02:41:00,200.112 +2024.06.11 02:42:00,200.099 +2024.06.11 02:43:00,200.119 +2024.06.11 02:44:00,200.114 +2024.06.11 02:45:00,200.094 +2024.06.11 02:46:00,200.093 +2024.06.11 02:47:00,200.097 +2024.06.11 02:48:00,200.104 +2024.06.11 02:49:00,200.104 +2024.06.11 02:50:00,200.106 +2024.06.11 02:51:00,200.109 +2024.06.11 02:52:00,200.121 +2024.06.11 02:53:00,200.126 +2024.06.11 02:54:00,200.114 +2024.06.11 02:55:00,200.09 +2024.06.11 02:56:00,200.11 +2024.06.11 02:57:00,200.107 +2024.06.11 02:58:00,200.133 +2024.06.11 02:59:00,200.134 +2024.06.11 03:00:00,200.148 +2024.06.11 03:01:00,200.144 +2024.06.11 03:02:00,200.15 +2024.06.11 03:03:00,200.143 +2024.06.11 03:04:00,200.132 +2024.06.11 03:05:00,200.128 +2024.06.11 03:06:00,200.113 +2024.06.11 03:07:00,200.113 +2024.06.11 03:08:00,200.109 +2024.06.11 03:09:00,200.119 +2024.06.11 03:10:00,200.111 +2024.06.11 03:11:00,200.109 +2024.06.11 03:12:00,200.109 +2024.06.11 03:13:00,200.108 +2024.06.11 03:14:00,200.12 +2024.06.11 03:15:00,200.128 +2024.06.11 03:16:00,200.128 +2024.06.11 03:17:00,200.14 +2024.06.11 03:18:00,200.158 +2024.06.11 03:19:00,200.148 +2024.06.11 03:20:00,200.156 +2024.06.11 03:21:00,200.158 +2024.06.11 03:22:00,200.159 +2024.06.11 03:23:00,200.146 +2024.06.11 03:24:00,200.134 +2024.06.11 03:25:00,200.15 +2024.06.11 03:26:00,200.147 +2024.06.11 03:27:00,200.155 +2024.06.11 03:28:00,200.156 +2024.06.11 03:29:00,200.17 +2024.06.11 03:30:00,200.156 +2024.06.11 03:31:00,200.144 +2024.06.11 03:32:00,200.143 +2024.06.11 03:33:00,200.124 +2024.06.11 03:34:00,200.126 +2024.06.11 03:35:00,200.128 +2024.06.11 03:36:00,200.14 +2024.06.11 03:37:00,200.167 +2024.06.11 03:38:00,200.16 +2024.06.11 03:39:00,200.155 +2024.06.11 03:40:00,200.155 +2024.06.11 03:41:00,200.155 +2024.06.11 03:42:00,200.155 +2024.06.11 03:43:00,200.164 +2024.06.11 03:44:00,200.161 +2024.06.11 03:45:00,200.171 +2024.06.11 03:46:00,200.169 +2024.06.11 03:47:00,200.16 +2024.06.11 03:48:00,200.17 +2024.06.11 03:49:00,200.173 +2024.06.11 03:50:00,200.177 +2024.06.11 03:51:00,200.173 +2024.06.11 03:52:00,200.194 +2024.06.11 03:53:00,200.191 +2024.06.11 03:54:00,200.19 +2024.06.11 03:55:00,200.194 +2024.06.11 03:56:00,200.224 +2024.06.11 03:57:00,200.224 +2024.06.11 03:58:00,200.235 +2024.06.11 03:59:00,200.247 +2024.06.11 04:00:00,200.267 +2024.06.11 04:01:00,200.288 +2024.06.11 04:02:00,200.268 +2024.06.11 04:03:00,200.281 +2024.06.11 04:04:00,200.269 +2024.06.11 04:05:00,200.251 +2024.06.11 04:06:00,200.245 +2024.06.11 04:07:00,200.235 +2024.06.11 04:08:00,200.244 +2024.06.11 04:09:00,200.248 +2024.06.11 04:10:00,200.24 +2024.06.11 04:11:00,200.239 +2024.06.11 04:12:00,200.215 +2024.06.11 04:13:00,200.216 +2024.06.11 04:14:00,200.231 +2024.06.11 04:15:00,200.227 +2024.06.11 04:16:00,200.229 +2024.06.11 04:17:00,200.232 +2024.06.11 04:18:00,200.236 +2024.06.11 04:19:00,200.236 +2024.06.11 04:20:00,200.231 +2024.06.11 04:21:00,200.203 +2024.06.11 04:22:00,200.195 +2024.06.11 04:23:00,200.219 +2024.06.11 04:24:00,200.261 +2024.06.11 04:25:00,200.255 +2024.06.11 04:26:00,200.273 +2024.06.11 04:27:00,200.276 +2024.06.11 04:28:00,200.274 +2024.06.11 04:29:00,200.258 +2024.06.11 04:30:00,200.275 +2024.06.11 04:31:00,200.276 +2024.06.11 04:32:00,200.28 +2024.06.11 04:33:00,200.277 +2024.06.11 04:34:00,200.272 +2024.06.11 04:35:00,200.267 +2024.06.11 04:36:00,200.269 +2024.06.11 04:37:00,200.243 +2024.06.11 04:38:00,200.251 +2024.06.11 04:39:00,200.252 +2024.06.11 04:40:00,200.247 +2024.06.11 04:41:00,200.249 +2024.06.11 04:42:00,200.243 +2024.06.11 04:43:00,200.236 +2024.06.11 04:44:00,200.228 +2024.06.11 04:45:00,200.236 +2024.06.11 04:46:00,200.22 +2024.06.11 04:47:00,200.202 +2024.06.11 04:48:00,200.203 +2024.06.11 04:49:00,200.218 +2024.06.11 04:50:00,200.238 +2024.06.11 04:51:00,200.237 +2024.06.11 04:52:00,200.248 +2024.06.11 04:53:00,200.232 +2024.06.11 04:54:00,200.235 +2024.06.11 04:55:00,200.236 +2024.06.11 04:56:00,200.215 +2024.06.11 04:57:00,200.198 +2024.06.11 04:58:00,200.206 +2024.06.11 04:59:00,200.226 +2024.06.11 05:00:00,200.232 +2024.06.11 05:01:00,200.222 +2024.06.11 05:02:00,200.219 +2024.06.11 05:03:00,200.21 +2024.06.11 05:04:00,200.207 +2024.06.11 05:05:00,200.206 +2024.06.11 05:06:00,200.208 +2024.06.11 05:07:00,200.212 +2024.06.11 05:08:00,200.225 +2024.06.11 05:09:00,200.233 +2024.06.11 05:10:00,200.247 +2024.06.11 05:11:00,200.234 +2024.06.11 05:12:00,200.228 +2024.06.11 05:13:00,200.245 +2024.06.11 05:14:00,200.242 +2024.06.11 05:15:00,200.25 +2024.06.11 05:16:00,200.249 +2024.06.11 05:17:00,200.261 +2024.06.11 05:18:00,200.263 +2024.06.11 05:19:00,200.262 +2024.06.11 05:20:00,200.275 +2024.06.11 05:21:00,200.26 +2024.06.11 05:22:00,200.256 +2024.06.11 05:23:00,200.263 +2024.06.11 05:24:00,200.258 +2024.06.11 05:25:00,200.257 +2024.06.11 05:26:00,200.256 +2024.06.11 05:27:00,200.257 +2024.06.11 05:28:00,200.258 +2024.06.11 05:29:00,200.257 +2024.06.11 05:30:00,200.259 +2024.06.11 05:31:00,200.287 +2024.06.11 05:32:00,200.288 +2024.06.11 05:33:00,200.286 +2024.06.11 05:34:00,200.286 +2024.06.11 05:35:00,200.288 +2024.06.11 05:36:00,200.304 +2024.06.11 05:37:00,200.307 +2024.06.11 05:38:00,200.316 +2024.06.11 05:39:00,200.323 +2024.06.11 05:40:00,200.324 +2024.06.11 05:41:00,200.332 +2024.06.11 05:42:00,200.321 +2024.06.11 05:43:00,200.319 +2024.06.11 05:44:00,200.319 +2024.06.11 05:45:00,200.324 +2024.06.11 05:46:00,200.317 +2024.06.11 05:47:00,200.317 +2024.06.11 05:48:00,200.322 +2024.06.11 05:49:00,200.322 +2024.06.11 05:50:00,200.294 +2024.06.11 05:51:00,200.293 +2024.06.11 05:52:00,200.29 +2024.06.11 05:53:00,200.295 +2024.06.11 05:54:00,200.303 +2024.06.11 05:55:00,200.278 +2024.06.11 05:56:00,200.262 +2024.06.11 05:57:00,200.277 +2024.06.11 05:58:00,200.262 +2024.06.11 05:59:00,200.264 +2024.06.11 06:00:00,200.274 +2024.06.11 06:01:00,200.262 +2024.06.11 06:02:00,200.261 +2024.06.11 06:03:00,200.279 +2024.06.11 06:04:00,200.28 +2024.06.11 06:05:00,200.291 +2024.06.11 06:06:00,200.286 +2024.06.11 06:07:00,200.272 +2024.06.11 06:08:00,200.263 +2024.06.11 06:09:00,200.258 +2024.06.11 06:10:00,200.264 +2024.06.11 06:11:00,200.264 +2024.06.11 06:12:00,200.278 +2024.06.11 06:13:00,200.297 +2024.06.11 06:14:00,200.296 +2024.06.11 06:15:00,200.296 +2024.06.11 06:16:00,200.297 +2024.06.11 06:17:00,200.285 +2024.06.11 06:18:00,200.281 +2024.06.11 06:19:00,200.28 +2024.06.11 06:20:00,200.282 +2024.06.11 06:21:00,200.287 +2024.06.11 06:22:00,200.292 +2024.06.11 06:23:00,200.293 +2024.06.11 06:24:00,200.287 +2024.06.11 06:25:00,200.288 +2024.06.11 06:26:00,200.278 +2024.06.11 06:27:00,200.279 +2024.06.11 06:28:00,200.271 +2024.06.11 06:29:00,200.269 +2024.06.11 06:30:00,200.284 +2024.06.11 06:31:00,200.287 +2024.06.11 06:32:00,200.29 +2024.06.11 06:33:00,200.286 +2024.06.11 06:34:00,200.284 +2024.06.11 06:35:00,200.283 +2024.06.11 06:36:00,200.283 +2024.06.11 06:37:00,200.293 +2024.06.11 06:38:00,200.295 +2024.06.11 06:39:00,200.306 +2024.06.11 06:40:00,200.306 +2024.06.11 06:41:00,200.308 +2024.06.11 06:42:00,200.213 +2024.06.11 06:43:00,200.249 +2024.06.11 06:44:00,200.292 +2024.06.11 06:45:00,200.307 +2024.06.11 06:46:00,200.28 +2024.06.11 06:47:00,200.275 +2024.06.11 06:48:00,200.301 +2024.06.11 06:49:00,200.33 +2024.06.11 06:50:00,200.311 +2024.06.11 06:51:00,200.28 +2024.06.11 06:52:00,200.285 +2024.06.11 06:53:00,200.278 +2024.06.11 06:54:00,200.282 +2024.06.11 06:55:00,200.223 +2024.06.11 06:56:00,200.27 +2024.06.11 06:57:00,200.256 +2024.06.11 06:58:00,200.277 +2024.06.11 06:59:00,200.283 +2024.06.11 07:00:00,200.16 +2024.06.11 07:01:00,200.183 +2024.06.11 07:02:00,200.16 +2024.06.11 07:03:00,200.168 +2024.06.11 07:04:00,200.122 +2024.06.11 07:05:00,200.135 +2024.06.11 07:06:00,200.096 +2024.06.11 07:07:00,200.083 +2024.06.11 07:08:00,200.083 +2024.06.11 07:09:00,200.12 +2024.06.11 07:10:00,200.151 +2024.06.11 07:11:00,200.138 +2024.06.11 07:12:00,200.139 +2024.06.11 07:13:00,200.077 +2024.06.11 07:14:00,200.048 +2024.06.11 07:15:00,200.056 +2024.06.11 07:16:00,200.088 +2024.06.11 07:17:00,200.121 +2024.06.11 07:18:00,200.118 +2024.06.11 07:19:00,200.1 +2024.06.11 07:20:00,200.098 +2024.06.11 07:21:00,200.108 +2024.06.11 07:22:00,200.115 +2024.06.11 07:23:00,200.156 +2024.06.11 07:24:00,200.154 +2024.06.11 07:25:00,200.159 +2024.06.11 07:26:00,200.171 +2024.06.11 07:27:00,200.175 +2024.06.11 07:28:00,200.164 +2024.06.11 07:29:00,200.171 +2024.06.11 07:30:00,200.136 +2024.06.11 07:31:00,200.069 +2024.06.11 07:32:00,200.042 +2024.06.11 07:33:00,200.006 +2024.06.11 07:34:00,199.979 +2024.06.11 07:35:00,199.978 +2024.06.11 07:36:00,199.967 +2024.06.11 07:37:00,199.983 +2024.06.11 07:38:00,200.013 +2024.06.11 07:39:00,200.02 +2024.06.11 07:40:00,200.051 +2024.06.11 07:41:00,200.027 +2024.06.11 07:42:00,200.029 +2024.06.11 07:43:00,200.028 +2024.06.11 07:44:00,200.031 +2024.06.11 07:45:00,200.05 +2024.06.11 07:46:00,200.011 +2024.06.11 07:47:00,200.029 +2024.06.11 07:48:00,200.038 +2024.06.11 07:49:00,200.033 +2024.06.11 07:50:00,200.071 +2024.06.11 07:51:00,200.066 +2024.06.11 07:52:00,200.068 +2024.06.11 07:53:00,200.076 +2024.06.11 07:54:00,200.068 +2024.06.11 07:55:00,200.078 +2024.06.11 07:56:00,200.045 +2024.06.11 07:57:00,200.028 +2024.06.11 07:58:00,200.021 +2024.06.11 07:59:00,200.045 +2024.06.11 08:00:00,200.046 +2024.06.11 08:01:00,200.065 +2024.06.11 08:02:00,200.039 +2024.06.11 08:03:00,200.048 +2024.06.11 08:04:00,200.014 +2024.06.11 08:05:00,199.998 +2024.06.11 08:06:00,199.985 +2024.06.11 08:07:00,199.975 +2024.06.11 08:08:00,199.959 +2024.06.11 08:09:00,199.97 +2024.06.11 08:10:00,199.979 +2024.06.11 08:11:00,199.982 +2024.06.11 08:12:00,199.968 +2024.06.11 08:13:00,199.982 +2024.06.11 08:14:00,199.981 +2024.06.11 08:15:00,200.004 +2024.06.11 08:16:00,200.003 +2024.06.11 08:17:00,199.97 +2024.06.11 08:18:00,199.972 +2024.06.11 08:19:00,199.996 +2024.06.11 08:20:00,199.97 +2024.06.11 08:21:00,199.985 +2024.06.11 08:22:00,200.019 +2024.06.11 08:23:00,200.034 +2024.06.11 08:24:00,200.009 +2024.06.11 08:25:00,200.01 +2024.06.11 08:26:00,199.997 +2024.06.11 08:27:00,200.001 +2024.06.11 08:28:00,200.026 +2024.06.11 08:29:00,200.057 +2024.06.11 08:30:00,200.116 +2024.06.11 08:31:00,200.092 +2024.06.11 08:32:00,200.107 +2024.06.11 08:33:00,200.096 +2024.06.11 08:34:00,200.093 +2024.06.11 08:35:00,200.12 +2024.06.11 08:36:00,200.121 +2024.06.11 08:37:00,200.12 +2024.06.11 08:38:00,200.122 +2024.06.11 08:39:00,200.123 +2024.06.11 08:40:00,200.108 +2024.06.11 08:41:00,200.09 +2024.06.11 08:42:00,200.095 +2024.06.11 08:43:00,200.124 +2024.06.11 08:44:00,200.113 +2024.06.11 08:45:00,200.109 +2024.06.11 08:46:00,200.076 +2024.06.11 08:47:00,200.076 +2024.06.11 08:48:00,200.103 +2024.06.11 08:49:00,200.094 +2024.06.11 08:50:00,200.11 +2024.06.11 08:51:00,200.119 +2024.06.11 08:52:00,200.119 +2024.06.11 08:53:00,200.098 +2024.06.11 08:54:00,200.078 +2024.06.11 08:55:00,200.11 +2024.06.11 08:56:00,200.114 +2024.06.11 08:57:00,200.158 +2024.06.11 08:58:00,200.172 +2024.06.11 08:59:00,200.168 +2024.06.11 09:00:00,200.135 +2024.06.11 09:01:00,200.129 +2024.06.11 09:02:00,200.129 +2024.06.11 09:03:00,200.133 +2024.06.11 09:04:00,200.142 +2024.06.11 09:05:00,200.135 +2024.06.11 09:06:00,200.144 +2024.06.11 09:07:00,200.144 +2024.06.11 09:08:00,200.143 +2024.06.11 09:09:00,200.114 +2024.06.11 09:10:00,200.124 +2024.06.11 09:11:00,200.143 +2024.06.11 09:12:00,200.145 +2024.06.11 09:13:00,200.177 +2024.06.11 09:14:00,200.193 +2024.06.11 09:15:00,200.239 +2024.06.11 09:16:00,200.274 +2024.06.11 09:17:00,200.266 +2024.06.11 09:18:00,200.252 +2024.06.11 09:19:00,200.242 +2024.06.11 09:20:00,200.234 +2024.06.11 09:21:00,200.221 +2024.06.11 09:22:00,200.187 +2024.06.11 09:23:00,200.218 +2024.06.11 09:24:00,200.223 +2024.06.11 09:25:00,200.225 +2024.06.11 09:26:00,200.216 +2024.06.11 09:27:00,200.273 +2024.06.11 09:28:00,200.296 +2024.06.11 09:29:00,200.307 +2024.06.11 09:30:00,200.302 +2024.06.11 09:31:00,200.299 +2024.06.11 09:32:00,200.303 +2024.06.11 09:33:00,200.308 +2024.06.11 09:34:00,200.3 +2024.06.11 09:35:00,200.258 +2024.06.11 09:36:00,200.245 +2024.06.11 09:37:00,200.253 +2024.06.11 09:38:00,200.243 +2024.06.11 09:39:00,200.261 +2024.06.11 09:40:00,200.298 +2024.06.11 09:41:00,200.326 +2024.06.11 09:42:00,200.323 +2024.06.11 09:43:00,200.347 +2024.06.11 09:44:00,200.353 +2024.06.11 09:45:00,200.332 +2024.06.11 09:46:00,200.318 +2024.06.11 09:47:00,200.32 +2024.06.11 09:48:00,200.269 +2024.06.11 09:49:00,200.228 +2024.06.11 09:50:00,200.25 +2024.06.11 09:51:00,200.241 +2024.06.11 09:52:00,200.191 +2024.06.11 09:53:00,200.183 +2024.06.11 09:54:00,200.181 +2024.06.11 09:55:00,200.17 +2024.06.11 09:56:00,200.168 +2024.06.11 09:57:00,200.114 +2024.06.11 09:58:00,200.089 +2024.06.11 09:59:00,200.046 +2024.06.11 10:00:00,199.978 +2024.06.11 10:01:00,200.041 +2024.06.11 10:02:00,200.083 +2024.06.11 10:03:00,200.087 +2024.06.11 10:04:00,200.133 +2024.06.11 10:05:00,200.213 +2024.06.11 10:06:00,200.21 +2024.06.11 10:07:00,200.144 +2024.06.11 10:08:00,200.123 +2024.06.11 10:09:00,200.142 +2024.06.11 10:10:00,200.185 +2024.06.11 10:11:00,200.209 +2024.06.11 10:12:00,200.22 +2024.06.11 10:13:00,200.202 +2024.06.11 10:14:00,200.176 +2024.06.11 10:15:00,200.156 +2024.06.11 10:16:00,200.133 +2024.06.11 10:17:00,200.134 +2024.06.11 10:18:00,200.141 +2024.06.11 10:19:00,200.181 +2024.06.11 10:20:00,200.167 +2024.06.11 10:21:00,200.182 +2024.06.11 10:22:00,200.209 +2024.06.11 10:23:00,200.2 +2024.06.11 10:24:00,200.183 +2024.06.11 10:25:00,200.136 +2024.06.11 10:26:00,200.114 +2024.06.11 10:27:00,200.117 +2024.06.11 10:28:00,200.111 +2024.06.11 10:29:00,200.089 +2024.06.11 10:30:00,200.166 +2024.06.11 10:31:00,200.132 +2024.06.11 10:32:00,200.141 +2024.06.11 10:33:00,200.147 +2024.06.11 10:34:00,200.181 +2024.06.11 10:35:00,200.148 +2024.06.11 10:36:00,200.148 +2024.06.11 10:37:00,200.149 +2024.06.11 10:38:00,200.135 +2024.06.11 10:39:00,200.129 +2024.06.11 10:40:00,200.175 +2024.06.11 10:41:00,200.169 +2024.06.11 10:42:00,200.159 +2024.06.11 10:43:00,200.129 +2024.06.11 10:44:00,200.105 +2024.06.11 10:45:00,200.12 +2024.06.11 10:46:00,200.11 +2024.06.11 10:47:00,200.098 +2024.06.11 10:48:00,200.111 +2024.06.11 10:49:00,200.112 +2024.06.11 10:50:00,200.163 +2024.06.11 10:51:00,200.146 +2024.06.11 10:52:00,200.194 +2024.06.11 10:53:00,200.147 +2024.06.11 10:54:00,200.204 +2024.06.11 10:55:00,200.193 +2024.06.11 10:56:00,200.214 +2024.06.11 10:57:00,200.217 +2024.06.11 10:58:00,200.229 +2024.06.11 10:59:00,200.225 +2024.06.11 11:00:00,200.208 +2024.06.11 11:01:00,200.213 +2024.06.11 11:02:00,200.213 +2024.06.11 11:03:00,200.224 +2024.06.11 11:04:00,200.23 +2024.06.11 11:05:00,200.219 +2024.06.11 11:06:00,200.212 +2024.06.11 11:07:00,200.297 +2024.06.11 11:08:00,200.353 +2024.06.11 11:09:00,200.336 +2024.06.11 11:10:00,200.341 +2024.06.11 11:11:00,200.341 +2024.06.11 11:12:00,200.301 +2024.06.11 11:13:00,200.28 +2024.06.11 11:14:00,200.287 +2024.06.11 11:15:00,200.253 +2024.06.11 11:16:00,200.24 +2024.06.11 11:17:00,200.244 +2024.06.11 11:18:00,200.225 +2024.06.11 11:19:00,200.256 +2024.06.11 11:20:00,200.276 +2024.06.11 11:21:00,200.285 +2024.06.11 11:22:00,200.286 +2024.06.11 11:23:00,200.303 +2024.06.11 11:24:00,200.252 +2024.06.11 11:25:00,200.257 +2024.06.11 11:26:00,200.259 +2024.06.11 11:27:00,200.29 +2024.06.11 11:28:00,200.3 +2024.06.11 11:29:00,200.346 +2024.06.11 11:30:00,200.34 +2024.06.11 11:31:00,200.371 +2024.06.11 11:32:00,200.36 +2024.06.11 11:33:00,200.359 +2024.06.11 11:34:00,200.38 +2024.06.11 11:35:00,200.355 +2024.06.11 11:36:00,200.32 +2024.06.11 11:37:00,200.304 +2024.06.11 11:38:00,200.25 +2024.06.11 11:39:00,200.237 +2024.06.11 11:40:00,200.249 +2024.06.11 11:41:00,200.275 +2024.06.11 11:42:00,200.257 +2024.06.11 11:43:00,200.25 +2024.06.11 11:44:00,200.228 +2024.06.11 11:45:00,200.175 +2024.06.11 11:46:00,200.185 +2024.06.11 11:47:00,200.173 +2024.06.11 11:48:00,200.169 +2024.06.11 11:49:00,200.163 +2024.06.11 11:50:00,200.214 +2024.06.11 11:51:00,200.219 +2024.06.11 11:52:00,200.223 +2024.06.11 11:53:00,200.187 +2024.06.11 11:54:00,200.156 +2024.06.11 11:55:00,200.176 +2024.06.11 11:56:00,200.155 +2024.06.11 11:57:00,200.218 +2024.06.11 11:58:00,200.242 +2024.06.11 11:59:00,200.168 +2024.06.11 12:00:00,200.129 +2024.06.11 12:01:00,200.143 +2024.06.11 12:02:00,200.147 +2024.06.11 12:03:00,200.163 +2024.06.11 12:04:00,200.181 +2024.06.11 12:05:00,200.197 +2024.06.11 12:06:00,200.195 +2024.06.11 12:07:00,200.207 +2024.06.11 12:08:00,200.192 +2024.06.11 12:09:00,200.172 +2024.06.11 12:10:00,200.143 +2024.06.11 12:11:00,200.135 +2024.06.11 12:12:00,200.116 +2024.06.11 12:13:00,200.135 +2024.06.11 12:14:00,200.142 +2024.06.11 12:15:00,200.151 +2024.06.11 12:16:00,200.141 +2024.06.11 12:17:00,200.159 +2024.06.11 12:18:00,200.132 +2024.06.11 12:19:00,200.178 +2024.06.11 12:20:00,200.157 +2024.06.11 12:21:00,200.138 +2024.06.11 12:22:00,200.135 +2024.06.11 12:23:00,200.161 +2024.06.11 12:24:00,200.107 +2024.06.11 12:25:00,200.096 +2024.06.11 12:26:00,200.139 +2024.06.11 12:27:00,200.149 +2024.06.11 12:28:00,200.164 +2024.06.11 12:29:00,200.189 +2024.06.11 12:30:00,200.17 +2024.06.11 12:31:00,200.175 +2024.06.11 12:32:00,200.168 +2024.06.11 12:33:00,200.185 +2024.06.11 12:34:00,200.179 +2024.06.11 12:35:00,200.157 +2024.06.11 12:36:00,200.152 +2024.06.11 12:37:00,200.124 +2024.06.11 12:38:00,200.136 +2024.06.11 12:39:00,200.087 +2024.06.11 12:40:00,200.062 +2024.06.11 12:41:00,200.088 +2024.06.11 12:42:00,200.093 +2024.06.11 12:43:00,200.111 +2024.06.11 12:44:00,200.085 +2024.06.11 12:45:00,200.082 +2024.06.11 12:46:00,200.105 +2024.06.11 12:47:00,200.12 +2024.06.11 12:48:00,200.105 +2024.06.11 12:49:00,200.098 +2024.06.11 12:50:00,200.074 +2024.06.11 12:51:00,200.095 +2024.06.11 12:52:00,200.091 +2024.06.11 12:53:00,200.121 +2024.06.11 12:54:00,200.086 +2024.06.11 12:55:00,200.088 +2024.06.11 12:56:00,200.086 +2024.06.11 12:57:00,200.06 +2024.06.11 12:58:00,200.089 +2024.06.11 12:59:00,200.109 +2024.06.11 13:00:00,200.072 +2024.06.11 13:01:00,200.068 +2024.06.11 13:02:00,200.02 +2024.06.11 13:03:00,200.055 +2024.06.11 13:04:00,200.059 +2024.06.11 13:05:00,200.014 +2024.06.11 13:06:00,200.067 +2024.06.11 13:07:00,200.114 +2024.06.11 13:08:00,200.084 +2024.06.11 13:09:00,200.06 +2024.06.11 13:10:00,200.069 +2024.06.11 13:11:00,200.039 +2024.06.11 13:12:00,200.066 +2024.06.11 13:13:00,200.1 +2024.06.11 13:14:00,200.079 +2024.06.11 13:15:00,200.107 +2024.06.11 13:16:00,200.069 +2024.06.11 13:17:00,200.042 +2024.06.11 13:18:00,200.047 +2024.06.11 13:19:00,200.042 +2024.06.11 13:20:00,200.015 +2024.06.11 13:21:00,200.032 +2024.06.11 13:22:00,200.025 +2024.06.11 13:23:00,200.004 +2024.06.11 13:24:00,199.987 +2024.06.11 13:25:00,199.933 +2024.06.11 13:26:00,199.957 +2024.06.11 13:27:00,199.963 +2024.06.11 13:28:00,199.955 +2024.06.11 13:29:00,199.969 +2024.06.11 13:30:00,199.946 +2024.06.11 13:31:00,199.877 +2024.06.11 13:32:00,199.877 +2024.06.11 13:33:00,199.828 +2024.06.11 13:34:00,199.849 +2024.06.11 13:35:00,199.843 +2024.06.11 13:36:00,199.883 +2024.06.11 13:37:00,199.9 +2024.06.11 13:38:00,199.876 +2024.06.11 13:39:00,199.884 +2024.06.11 13:40:00,199.911 +2024.06.11 13:41:00,199.913 +2024.06.11 13:42:00,199.964 +2024.06.11 13:43:00,199.953 +2024.06.11 13:44:00,199.995 +2024.06.11 13:45:00,199.998 +2024.06.11 13:46:00,200.009 +2024.06.11 13:47:00,200.001 +2024.06.11 13:48:00,199.972 +2024.06.11 13:49:00,199.956 +2024.06.11 13:50:00,199.949 +2024.06.11 13:51:00,199.91 +2024.06.11 13:52:00,199.88 +2024.06.11 13:53:00,199.88 +2024.06.11 13:54:00,199.87 +2024.06.11 13:55:00,199.852 +2024.06.11 13:56:00,199.825 +2024.06.11 13:57:00,199.846 +2024.06.11 13:58:00,199.897 +2024.06.11 13:59:00,199.929 +2024.06.11 14:00:00,199.961 +2024.06.11 14:01:00,199.97 +2024.06.11 14:02:00,199.961 +2024.06.11 14:03:00,199.947 +2024.06.11 14:04:00,199.948 +2024.06.11 14:05:00,199.891 +2024.06.11 14:06:00,199.937 +2024.06.11 14:07:00,199.95 +2024.06.11 14:08:00,200.019 +2024.06.11 14:09:00,200.045 +2024.06.11 14:10:00,200.008 +2024.06.11 14:11:00,200.034 +2024.06.11 14:12:00,200.074 +2024.06.11 14:13:00,200.051 +2024.06.11 14:14:00,200.039 +2024.06.11 14:15:00,200.078 +2024.06.11 14:16:00,200.059 +2024.06.11 14:17:00,199.997 +2024.06.11 14:18:00,199.976 +2024.06.11 14:19:00,199.926 +2024.06.11 14:20:00,199.881 +2024.06.11 14:21:00,199.909 +2024.06.11 14:22:00,199.882 +2024.06.11 14:23:00,199.887 +2024.06.11 14:24:00,199.861 +2024.06.11 14:25:00,199.915 +2024.06.11 14:26:00,199.897 +2024.06.11 14:27:00,199.866 +2024.06.11 14:28:00,199.938 +2024.06.11 14:29:00,199.953 +2024.06.11 14:30:00,199.891 +2024.06.11 14:31:00,199.868 +2024.06.11 14:32:00,199.854 +2024.06.11 14:33:00,199.869 +2024.06.11 14:34:00,199.844 +2024.06.11 14:35:00,199.852 +2024.06.11 14:36:00,199.863 +2024.06.11 14:37:00,199.882 +2024.06.11 14:38:00,199.94 +2024.06.11 14:39:00,199.945 +2024.06.11 14:40:00,199.939 +2024.06.11 14:41:00,199.938 +2024.06.11 14:42:00,199.956 +2024.06.11 14:43:00,199.984 +2024.06.11 14:44:00,199.956 +2024.06.11 14:45:00,199.907 +2024.06.11 14:46:00,199.891 +2024.06.11 14:47:00,199.8 +2024.06.11 14:48:00,199.761 +2024.06.11 14:49:00,199.791 +2024.06.11 14:50:00,199.771 +2024.06.11 14:51:00,199.761 +2024.06.11 14:52:00,199.777 +2024.06.11 14:53:00,199.76 +2024.06.11 14:54:00,199.743 +2024.06.11 14:55:00,199.771 +2024.06.11 14:56:00,199.758 +2024.06.11 14:57:00,199.735 +2024.06.11 14:58:00,199.74 +2024.06.11 14:59:00,199.765 +2024.06.11 15:00:00,199.76 +2024.06.11 15:01:00,199.76 +2024.06.11 15:02:00,199.736 +2024.06.11 15:03:00,199.729 +2024.06.11 15:04:00,199.699 +2024.06.11 15:05:00,199.679 +2024.06.11 15:06:00,199.754 +2024.06.11 15:07:00,199.787 +2024.06.11 15:08:00,199.863 +2024.06.11 15:09:00,199.857 +2024.06.11 15:10:00,199.868 +2024.06.11 15:11:00,199.872 +2024.06.11 15:12:00,199.884 +2024.06.11 15:13:00,199.938 +2024.06.11 15:14:00,199.958 +2024.06.11 15:15:00,199.903 +2024.06.11 15:16:00,199.91 +2024.06.11 15:17:00,199.938 +2024.06.11 15:18:00,199.919 +2024.06.11 15:19:00,199.893 +2024.06.11 15:20:00,199.855 +2024.06.11 15:21:00,199.819 +2024.06.11 15:22:00,199.824 +2024.06.11 15:23:00,199.85 +2024.06.11 15:24:00,199.874 +2024.06.11 15:25:00,199.841 +2024.06.11 15:26:00,199.861 +2024.06.11 15:27:00,199.907 +2024.06.11 15:28:00,199.935 +2024.06.11 15:29:00,199.911 +2024.06.11 15:30:00,199.904 +2024.06.11 15:31:00,199.918 +2024.06.11 15:32:00,199.929 +2024.06.11 15:33:00,199.957 +2024.06.11 15:34:00,199.941 +2024.06.11 15:35:00,199.922 +2024.06.11 15:36:00,199.934 +2024.06.11 15:37:00,199.934 +2024.06.11 15:38:00,199.952 +2024.06.11 15:39:00,199.941 +2024.06.11 15:40:00,199.969 +2024.06.11 15:41:00,199.981 +2024.06.11 15:42:00,199.982 +2024.06.11 15:43:00,199.951 +2024.06.11 15:44:00,199.956 +2024.06.11 15:45:00,199.935 +2024.06.11 15:46:00,199.926 +2024.06.11 15:47:00,199.974 +2024.06.11 15:48:00,199.942 +2024.06.11 15:49:00,199.935 +2024.06.11 15:50:00,199.903 +2024.06.11 15:51:00,199.965 +2024.06.11 15:52:00,199.986 +2024.06.11 15:53:00,199.998 +2024.06.11 15:54:00,199.999 +2024.06.11 15:55:00,200.006 +2024.06.11 15:56:00,199.984 +2024.06.11 15:57:00,199.947 +2024.06.11 15:58:00,199.956 +2024.06.11 15:59:00,199.972 +2024.06.11 16:00:00,200.04 +2024.06.11 16:01:00,200.093 +2024.06.11 16:02:00,200.079 +2024.06.11 16:03:00,200.08 +2024.06.11 16:04:00,200.076 +2024.06.11 16:05:00,200.076 +2024.06.11 16:06:00,200.122 +2024.06.11 16:07:00,200.132 +2024.06.11 16:08:00,200.164 +2024.06.11 16:09:00,200.158 +2024.06.11 16:10:00,200.175 +2024.06.11 16:11:00,200.157 +2024.06.11 16:12:00,200.15 +2024.06.11 16:13:00,200.132 +2024.06.11 16:14:00,200.11 +2024.06.11 16:15:00,200.088 +2024.06.11 16:16:00,200.088 +2024.06.11 16:17:00,200.075 +2024.06.11 16:18:00,200.115 +2024.06.11 16:19:00,200.109 +2024.06.11 16:20:00,200.069 +2024.06.11 16:21:00,200.025 +2024.06.11 16:22:00,200.039 +2024.06.11 16:23:00,200.017 +2024.06.11 16:24:00,200.029 +2024.06.11 16:25:00,200.072 +2024.06.11 16:26:00,200.07 +2024.06.11 16:27:00,200.058 +2024.06.11 16:28:00,200.095 +2024.06.11 16:29:00,200.085 +2024.06.11 16:30:00,200.1 +2024.06.11 16:31:00,200.1 +2024.06.11 16:32:00,200.12 +2024.06.11 16:33:00,200.133 +2024.06.11 16:34:00,200.16 +2024.06.11 16:35:00,200.156 +2024.06.11 16:36:00,200.168 +2024.06.11 16:37:00,200.163 +2024.06.11 16:38:00,200.161 +2024.06.11 16:39:00,200.169 +2024.06.11 16:40:00,200.189 +2024.06.11 16:41:00,200.212 +2024.06.11 16:42:00,200.188 +2024.06.11 16:43:00,200.183 +2024.06.11 16:44:00,200.159 +2024.06.11 16:45:00,200.159 +2024.06.11 16:46:00,200.189 +2024.06.11 16:47:00,200.186 +2024.06.11 16:48:00,200.192 +2024.06.11 16:49:00,200.177 +2024.06.11 16:50:00,200.16 +2024.06.11 16:51:00,200.15 +2024.06.11 16:52:00,200.178 +2024.06.11 16:53:00,200.185 +2024.06.11 16:54:00,200.163 +2024.06.11 16:55:00,200.156 +2024.06.11 16:56:00,200.142 +2024.06.11 16:57:00,200.166 +2024.06.11 16:58:00,200.15 +2024.06.11 16:59:00,200.134 +2024.06.11 17:00:00,200.133 +2024.06.11 17:01:00,200.145 +2024.06.11 17:02:00,200.152 +2024.06.11 17:03:00,200.133 +2024.06.11 17:04:00,200.103 +2024.06.11 17:05:00,200.114 +2024.06.11 17:06:00,200.103 +2024.06.11 17:07:00,200.105 +2024.06.11 17:08:00,200.117 +2024.06.11 17:09:00,200.131 +2024.06.11 17:10:00,200.156 +2024.06.11 17:11:00,200.164 +2024.06.11 17:12:00,200.171 +2024.06.11 17:13:00,200.201 +2024.06.11 17:14:00,200.201 +2024.06.11 17:15:00,200.185 +2024.06.11 17:16:00,200.187 +2024.06.11 17:17:00,200.193 +2024.06.11 17:18:00,200.188 +2024.06.11 17:19:00,200.174 +2024.06.11 17:20:00,200.168 +2024.06.11 17:21:00,200.179 +2024.06.11 17:22:00,200.176 +2024.06.11 17:23:00,200.166 +2024.06.11 17:24:00,200.141 +2024.06.11 17:25:00,200.175 +2024.06.11 17:26:00,200.174 +2024.06.11 17:27:00,200.14 +2024.06.11 17:28:00,200.15 +2024.06.11 17:29:00,200.138 +2024.06.11 17:30:00,200.138 +2024.06.11 17:31:00,200.132 +2024.06.11 17:32:00,200.121 +2024.06.11 17:33:00,200.13 +2024.06.11 17:34:00,200.136 +2024.06.11 17:35:00,200.153 +2024.06.11 17:36:00,200.165 +2024.06.11 17:37:00,200.182 +2024.06.11 17:38:00,200.206 +2024.06.11 17:39:00,200.231 +2024.06.11 17:40:00,200.229 +2024.06.11 17:41:00,200.231 +2024.06.11 17:42:00,200.239 +2024.06.11 17:43:00,200.266 +2024.06.11 17:44:00,200.273 +2024.06.11 17:45:00,200.28 +2024.06.11 17:46:00,200.28 +2024.06.11 17:47:00,200.28 +2024.06.11 17:48:00,200.301 +2024.06.11 17:49:00,200.3 +2024.06.11 17:50:00,200.305 +2024.06.11 17:51:00,200.301 +2024.06.11 17:52:00,200.299 +2024.06.11 17:53:00,200.297 +2024.06.11 17:54:00,200.312 +2024.06.11 17:55:00,200.316 +2024.06.11 17:56:00,200.318 +2024.06.11 17:57:00,200.315 +2024.06.11 17:58:00,200.321 +2024.06.11 17:59:00,200.317 +2024.06.11 18:00:00,200.292 +2024.06.11 18:01:00,200.275 +2024.06.11 18:02:00,200.204 +2024.06.11 18:03:00,200.202 +2024.06.11 18:04:00,200.198 +2024.06.11 18:05:00,200.171 +2024.06.11 18:06:00,200.149 +2024.06.11 18:07:00,200.164 +2024.06.11 18:08:00,200.155 +2024.06.11 18:09:00,200.156 +2024.06.11 18:10:00,200.176 +2024.06.11 18:11:00,200.2 +2024.06.11 18:12:00,200.191 +2024.06.11 18:13:00,200.191 +2024.06.11 18:14:00,200.183 +2024.06.11 18:15:00,200.183 +2024.06.11 18:16:00,200.139 +2024.06.11 18:17:00,200.141 +2024.06.11 18:18:00,200.153 +2024.06.11 18:19:00,200.146 +2024.06.11 18:20:00,200.142 +2024.06.11 18:21:00,200.124 +2024.06.11 18:22:00,200.11 +2024.06.11 18:23:00,200.112 +2024.06.11 18:24:00,200.1 +2024.06.11 18:25:00,200.12 +2024.06.11 18:26:00,200.129 +2024.06.11 18:27:00,200.144 +2024.06.11 18:28:00,200.113 +2024.06.11 18:29:00,200.102 +2024.06.11 18:30:00,200.109 +2024.06.11 18:31:00,200.101 +2024.06.11 18:32:00,200.1 +2024.06.11 18:33:00,200.098 +2024.06.11 18:34:00,200.099 +2024.06.11 18:35:00,200.099 +2024.06.11 18:36:00,200.108 +2024.06.11 18:37:00,200.094 +2024.06.11 18:38:00,200.1 +2024.06.11 18:39:00,200.096 +2024.06.11 18:40:00,200.099 +2024.06.11 18:41:00,200.093 +2024.06.11 18:42:00,200.092 +2024.06.11 18:43:00,200.093 +2024.06.11 18:44:00,200.082 +2024.06.11 18:45:00,200.065 +2024.06.11 18:46:00,200.084 +2024.06.11 18:47:00,200.066 +2024.06.11 18:48:00,200.045 +2024.06.11 18:49:00,200.043 +2024.06.11 18:50:00,200.042 +2024.06.11 18:51:00,200.06 +2024.06.11 18:52:00,200.055 +2024.06.11 18:53:00,200.053 +2024.06.11 18:54:00,200.05 +2024.06.11 18:55:00,200.049 +2024.06.11 18:56:00,200.032 +2024.06.11 18:57:00,200.045 +2024.06.11 18:58:00,200.026 +2024.06.11 18:59:00,200.031 +2024.06.11 19:00:00,200.025 +2024.06.11 19:01:00,200.042 +2024.06.11 19:02:00,200.042 +2024.06.11 19:03:00,200.044 +2024.06.11 19:04:00,200.027 +2024.06.11 19:05:00,200.039 +2024.06.11 19:06:00,200.023 +2024.06.11 19:07:00,200.002 +2024.06.11 19:08:00,200.003 +2024.06.11 19:09:00,200.005 +2024.06.11 19:10:00,200.012 +2024.06.11 19:11:00,200.004 +2024.06.11 19:12:00,200.016 +2024.06.11 19:13:00,200.033 +2024.06.11 19:14:00,200.027 +2024.06.11 19:15:00,200.022 +2024.06.11 19:16:00,200.032 +2024.06.11 19:17:00,200.028 +2024.06.11 19:18:00,200.014 +2024.06.11 19:19:00,200.015 +2024.06.11 19:20:00,200.039 +2024.06.11 19:21:00,200.074 +2024.06.11 19:22:00,200.064 +2024.06.11 19:23:00,200.076 +2024.06.11 19:24:00,200.061 +2024.06.11 19:25:00,200.049 +2024.06.11 19:26:00,200.052 +2024.06.11 19:27:00,200.068 +2024.06.11 19:28:00,200.078 +2024.06.11 19:29:00,200.079 +2024.06.11 19:30:00,200.084 +2024.06.11 19:31:00,200.105 +2024.06.11 19:32:00,200.112 +2024.06.11 19:33:00,200.097 +2024.06.11 19:34:00,200.102 +2024.06.11 19:35:00,200.117 +2024.06.11 19:36:00,200.115 +2024.06.11 19:37:00,200.114 +2024.06.11 19:38:00,200.123 +2024.06.11 19:39:00,200.117 +2024.06.11 19:40:00,200.126 +2024.06.11 19:41:00,200.132 +2024.06.11 19:42:00,200.126 +2024.06.11 19:43:00,200.113 +2024.06.11 19:44:00,200.104 +2024.06.11 19:45:00,200.102 +2024.06.11 19:46:00,200.089 +2024.06.11 19:47:00,200.118 +2024.06.11 19:48:00,200.122 +2024.06.11 19:49:00,200.109 +2024.06.11 19:50:00,200.114 +2024.06.11 19:51:00,200.122 +2024.06.11 19:52:00,200.102 +2024.06.11 19:53:00,200.094 +2024.06.11 19:54:00,200.107 +2024.06.11 19:55:00,200.128 +2024.06.11 19:56:00,200.122 +2024.06.11 19:57:00,200.114 +2024.06.11 19:58:00,200.143 +2024.06.11 19:59:00,200.122 +2024.06.11 20:00:00,200.143 +2024.06.11 20:01:00,200.175 +2024.06.11 20:02:00,200.164 +2024.06.11 20:03:00,200.168 +2024.06.11 20:04:00,200.174 +2024.06.11 20:05:00,200.169 +2024.06.11 20:06:00,200.161 +2024.06.11 20:07:00,200.171 +2024.06.11 20:08:00,200.174 +2024.06.11 20:09:00,200.145 +2024.06.11 20:10:00,200.16 +2024.06.11 20:11:00,200.156 +2024.06.11 20:12:00,200.143 +2024.06.11 20:13:00,200.118 +2024.06.11 20:14:00,200.101 +2024.06.11 20:15:00,200.117 +2024.06.11 20:16:00,200.141 +2024.06.11 20:17:00,200.125 +2024.06.11 20:18:00,200.122 +2024.06.11 20:19:00,200.101 +2024.06.11 20:20:00,200.092 +2024.06.11 20:21:00,200.093 +2024.06.11 20:22:00,200.103 +2024.06.11 20:23:00,200.103 +2024.06.11 20:24:00,200.1 +2024.06.11 20:25:00,200.097 +2024.06.11 20:26:00,200.082 +2024.06.11 20:27:00,200.085 +2024.06.11 20:28:00,200.071 +2024.06.11 20:29:00,200.074 +2024.06.11 20:30:00,200.082 +2024.06.11 20:31:00,200.107 +2024.06.11 20:32:00,200.1 +2024.06.11 20:33:00,200.102 +2024.06.11 20:34:00,200.109 +2024.06.11 20:35:00,200.098 +2024.06.11 20:36:00,200.12 +2024.06.11 20:37:00,200.134 +2024.06.11 20:38:00,200.132 +2024.06.11 20:39:00,200.126 +2024.06.11 20:40:00,200.124 +2024.06.11 20:41:00,200.133 +2024.06.11 20:42:00,200.116 +2024.06.11 20:43:00,200.119 +2024.06.11 20:44:00,200.143 +2024.06.11 20:45:00,200.148 +2024.06.11 20:46:00,200.116 +2024.06.11 20:47:00,200.116 +2024.06.11 20:48:00,200.114 +2024.06.11 20:49:00,200.102 +2024.06.11 20:50:00,200.096 +2024.06.11 20:51:00,200.115 +2024.06.11 20:52:00,200.115 +2024.06.11 20:53:00,200.11 +2024.06.11 20:54:00,200.104 +2024.06.11 20:55:00,200.091 +2024.06.11 20:56:00,200.064 +2024.06.11 20:57:00,200.071 +2024.06.11 20:58:00,200.09 +2024.06.11 20:59:00,200.144 +2024.06.11 21:00:00,200.128 +2024.06.11 21:01:00,200.112 +2024.06.11 21:02:00,200.108 +2024.06.11 21:03:00,200.114 +2024.06.11 21:04:00,200.104 +2024.06.11 21:05:00,200.131 +2024.06.11 21:06:00,200.143 +2024.06.11 21:07:00,200.138 +2024.06.11 21:08:00,200.143 +2024.06.11 21:09:00,200.125 +2024.06.11 21:10:00,200.128 +2024.06.11 21:11:00,200.124 +2024.06.11 21:12:00,200.128 +2024.06.11 21:13:00,200.127 +2024.06.11 21:14:00,200.153 +2024.06.11 21:15:00,200.153 +2024.06.11 21:16:00,200.155 +2024.06.11 21:17:00,200.156 +2024.06.11 21:18:00,200.14 +2024.06.11 21:19:00,200.144 +2024.06.11 21:20:00,200.145 +2024.06.11 21:21:00,200.143 +2024.06.11 21:22:00,200.14 +2024.06.11 21:23:00,200.129 +2024.06.11 21:24:00,200.13 +2024.06.11 21:25:00,200.125 +2024.06.11 21:26:00,200.122 +2024.06.11 21:27:00,200.109 +2024.06.11 21:28:00,200.115 +2024.06.11 21:29:00,200.109 +2024.06.11 21:30:00,200.115 +2024.06.11 21:31:00,200.116 +2024.06.11 21:32:00,200.11 +2024.06.11 21:33:00,200.129 +2024.06.11 21:34:00,200.129 +2024.06.11 21:35:00,200.13 +2024.06.11 21:36:00,200.131 +2024.06.11 21:37:00,200.13 +2024.06.11 21:38:00,200.129 +2024.06.11 21:39:00,200.129 +2024.06.11 21:40:00,200.137 +2024.06.11 21:41:00,200.134 +2024.06.11 21:42:00,200.141 +2024.06.11 21:43:00,200.14 +2024.06.11 21:44:00,200.138 +2024.06.11 21:45:00,200.143 +2024.06.11 21:46:00,200.146 +2024.06.11 21:47:00,200.146 +2024.06.11 21:48:00,200.145 +2024.06.11 21:49:00,200.147 +2024.06.11 21:50:00,200.147 +2024.06.11 21:51:00,200.148 +2024.06.11 21:52:00,200.142 +2024.06.11 21:53:00,200.128 +2024.06.11 21:54:00,200.135 +2024.06.11 21:55:00,200.146 +2024.06.11 21:56:00,200.147 +2024.06.11 21:57:00,200.15 +2024.06.11 21:58:00,200.144 +2024.06.11 21:59:00,200.079 +2024.06.11 22:00:00,200.069 +2024.06.11 22:01:00,200.055 +2024.06.11 22:02:00,200.002 +2024.06.11 22:03:00,200.002 +2024.06.11 22:04:00,199.977 +2024.06.11 22:05:00,199.971 +2024.06.11 22:06:00,199.949 +2024.06.11 22:07:00,199.949 +2024.06.11 22:08:00,199.944 +2024.06.11 22:09:00,199.942 +2024.06.11 22:10:00,199.944 +2024.06.11 22:11:00,199.95 +2024.06.11 22:12:00,200.072 +2024.06.11 22:13:00,200.042 +2024.06.11 22:14:00,200.036 +2024.06.11 22:15:00,200.028 +2024.06.11 22:16:00,200.036 +2024.06.11 22:17:00,200.04 +2024.06.11 22:19:00,200.04 +2024.06.11 22:20:00,200.04 +2024.06.11 22:21:00,200.036 +2024.06.11 22:24:00,200.033 +2024.06.11 22:25:00,200.033 +2024.06.11 22:26:00,200.032 +2024.06.11 22:27:00,200.043 +2024.06.11 22:28:00,200.057 +2024.06.11 22:29:00,200.028 +2024.06.11 22:30:00,200.068 +2024.06.11 22:31:00,200.068 +2024.06.11 22:32:00,200.066 +2024.06.11 22:33:00,200.061 +2024.06.11 22:34:00,200.007 +2024.06.11 22:35:00,200.032 +2024.06.11 22:36:00,200.032 +2024.06.11 22:37:00,200.098 +2024.06.11 22:39:00,200.083 +2024.06.11 22:40:00,200.082 +2024.06.11 22:41:00,200.081 +2024.06.11 22:42:00,200.063 +2024.06.11 22:43:00,200.068 +2024.06.11 22:44:00,200.072 +2024.06.11 22:45:00,200.079 +2024.06.11 22:46:00,200.073 +2024.06.11 22:47:00,200.083 +2024.06.11 22:48:00,200.086 +2024.06.11 22:49:00,200.088 +2024.06.11 22:50:00,200.093 +2024.06.11 22:51:00,200.094 +2024.06.11 22:52:00,200.093 +2024.06.11 22:53:00,200.092 +2024.06.11 22:54:00,200.095 +2024.06.11 22:55:00,200.096 +2024.06.11 22:56:00,200.071 +2024.06.11 22:57:00,200.075 +2024.06.11 22:58:00,200.077 +2024.06.11 22:59:00,200.083 +2024.06.11 23:00:00,200.03 +2024.06.11 23:01:00,200.07 +2024.06.11 23:02:00,200.06 +2024.06.11 23:03:00,200.061 +2024.06.11 23:04:00,200.082 +2024.06.11 23:05:00,200.099 +2024.06.11 23:06:00,200.127 +2024.06.11 23:07:00,200.129 +2024.06.11 23:08:00,200.124 +2024.06.11 23:09:00,200.13 +2024.06.11 23:10:00,200.129 +2024.06.11 23:11:00,200.123 +2024.06.11 23:12:00,200.154 +2024.06.11 23:13:00,200.156 +2024.06.11 23:14:00,200.157 +2024.06.11 23:15:00,200.153 +2024.06.11 23:16:00,200.142 +2024.06.11 23:17:00,200.138 +2024.06.11 23:18:00,200.142 +2024.06.11 23:19:00,200.143 +2024.06.11 23:20:00,200.154 +2024.06.11 23:21:00,200.155 +2024.06.11 23:22:00,200.149 +2024.06.11 23:23:00,200.153 +2024.06.11 23:24:00,200.153 +2024.06.11 23:25:00,200.151 +2024.06.11 23:26:00,200.155 +2024.06.11 23:27:00,200.151 +2024.06.11 23:28:00,200.156 +2024.06.11 23:29:00,200.157 +2024.06.11 23:30:00,200.157 +2024.06.11 23:31:00,200.162 +2024.06.11 23:32:00,200.146 +2024.06.11 23:33:00,200.149 +2024.06.11 23:34:00,200.166 +2024.06.11 23:35:00,200.173 +2024.06.11 23:36:00,200.17 +2024.06.11 23:37:00,200.169 +2024.06.11 23:38:00,200.169 +2024.06.11 23:39:00,200.163 +2024.06.11 23:40:00,200.157 +2024.06.11 23:41:00,200.161 +2024.06.11 23:42:00,200.158 +2024.06.11 23:43:00,200.149 +2024.06.11 23:44:00,200.152 +2024.06.11 23:45:00,200.157 +2024.06.11 23:46:00,200.159 +2024.06.11 23:47:00,200.16 +2024.06.11 23:48:00,200.161 +2024.06.11 23:49:00,200.156 +2024.06.11 23:50:00,200.15 +2024.06.11 23:51:00,200.15 +2024.06.11 23:52:00,200.167 +2024.06.11 23:53:00,200.172 +2024.06.11 23:54:00,200.18 +2024.06.11 23:55:00,200.172 +2024.06.11 23:56:00,200.174 +2024.06.11 23:57:00,200.172 +2024.06.11 23:59:00,200.171 +2024.06.12 00:00:00,200.174 +2024.06.12 00:01:00,200.19 +2024.06.12 00:02:00,200.189 +2024.06.12 00:03:00,200.19 +2024.06.12 00:04:00,200.184 +2024.06.12 00:05:00,200.181 +2024.06.12 00:06:00,200.174 +2024.06.12 00:07:00,200.172 +2024.06.12 00:08:00,200.172 +2024.06.12 00:09:00,200.175 +2024.06.12 00:10:00,200.188 +2024.06.12 00:11:00,200.186 +2024.06.12 00:12:00,200.183 +2024.06.12 00:13:00,200.182 +2024.06.12 00:14:00,200.189 +2024.06.12 00:15:00,200.188 +2024.06.12 00:16:00,200.188 +2024.06.12 00:17:00,200.189 +2024.06.12 00:18:00,200.18 +2024.06.12 00:19:00,200.189 +2024.06.12 00:20:00,200.188 +2024.06.12 00:21:00,200.172 +2024.06.12 00:22:00,200.172 +2024.06.12 00:23:00,200.188 +2024.06.12 00:24:00,200.19 +2024.06.12 00:25:00,200.187 +2024.06.12 00:26:00,200.201 +2024.06.12 00:27:00,200.184 +2024.06.12 00:28:00,200.184 +2024.06.12 00:29:00,200.184 +2024.06.12 00:30:00,200.186 +2024.06.12 00:31:00,200.189 +2024.06.12 00:32:00,200.188 +2024.06.12 00:33:00,200.187 +2024.06.12 00:34:00,200.196 +2024.06.12 00:35:00,200.189 +2024.06.12 00:36:00,200.19 +2024.06.12 00:37:00,200.191 +2024.06.12 00:38:00,200.189 +2024.06.12 00:39:00,200.187 +2024.06.12 00:40:00,200.16 +2024.06.12 00:41:00,200.163 +2024.06.12 00:42:00,200.16 +2024.06.12 00:43:00,200.16 +2024.06.12 00:44:00,200.142 +2024.06.12 00:45:00,200.111 +2024.06.12 00:46:00,200.111 +2024.06.12 00:47:00,200.127 +2024.06.12 00:48:00,200.099 +2024.06.12 00:49:00,200.093 +2024.06.12 00:50:00,200.079 +2024.06.12 00:51:00,200.094 +2024.06.12 00:52:00,200.081 +2024.06.12 00:53:00,200.081 +2024.06.12 00:54:00,200.092 +2024.06.12 00:55:00,200.097 +2024.06.12 00:56:00,200.113 +2024.06.12 00:57:00,200.105 +2024.06.12 00:58:00,200.131 +2024.06.12 00:59:00,200.145 +2024.06.12 01:00:00,200.128 +2024.06.12 01:01:00,200.141 +2024.06.12 01:02:00,200.144 +2024.06.12 01:03:00,200.173 +2024.06.12 01:04:00,200.186 +2024.06.12 01:05:00,200.179 +2024.06.12 01:06:00,200.183 +2024.06.12 01:07:00,200.176 +2024.06.12 01:08:00,200.189 +2024.06.12 01:09:00,200.178 +2024.06.12 01:10:00,200.203 +2024.06.12 01:11:00,200.213 +2024.06.12 01:12:00,200.207 +2024.06.12 01:13:00,200.209 +2024.06.12 01:14:00,200.205 +2024.06.12 01:15:00,200.212 +2024.06.12 01:16:00,200.225 +2024.06.12 01:17:00,200.228 +2024.06.12 01:18:00,200.221 +2024.06.12 01:19:00,200.231 +2024.06.12 01:20:00,200.214 +2024.06.12 01:21:00,200.205 +2024.06.12 01:22:00,200.188 +2024.06.12 01:23:00,200.173 +2024.06.12 01:24:00,200.181 +2024.06.12 01:25:00,200.177 +2024.06.12 01:26:00,200.18 +2024.06.12 01:27:00,200.147 +2024.06.12 01:28:00,200.124 +2024.06.12 01:29:00,200.125 +2024.06.12 01:30:00,200.097 +2024.06.12 01:31:00,200.095 +2024.06.12 01:32:00,200.1 +2024.06.12 01:33:00,200.097 +2024.06.12 01:34:00,200.08 +2024.06.12 01:35:00,200.107 +2024.06.12 01:36:00,200.106 +2024.06.12 01:37:00,200.117 +2024.06.12 01:38:00,200.107 +2024.06.12 01:39:00,200.112 +2024.06.12 01:40:00,200.136 +2024.06.12 01:41:00,200.162 +2024.06.12 01:42:00,200.174 +2024.06.12 01:43:00,200.165 +2024.06.12 01:44:00,200.177 +2024.06.12 01:45:00,200.175 +2024.06.12 01:46:00,200.162 +2024.06.12 01:47:00,200.132 +2024.06.12 01:48:00,200.135 +2024.06.12 01:49:00,200.133 +2024.06.12 01:50:00,200.073 +2024.06.12 01:51:00,200.06 +2024.06.12 01:52:00,200.085 +2024.06.12 01:53:00,200.149 +2024.06.12 01:54:00,200.096 +2024.06.12 01:55:00,200.124 +2024.06.12 01:56:00,200.166 +2024.06.12 01:57:00,200.129 +2024.06.12 01:58:00,200.123 +2024.06.12 01:59:00,200.137 +2024.06.12 02:00:00,200.133 +2024.06.12 02:01:00,200.123 +2024.06.12 02:02:00,200.11 +2024.06.12 02:03:00,200.07 +2024.06.12 02:04:00,200.088 +2024.06.12 02:05:00,200.127 +2024.06.12 02:06:00,200.142 +2024.06.12 02:07:00,200.161 +2024.06.12 02:08:00,200.139 +2024.06.12 02:09:00,200.15 +2024.06.12 02:10:00,200.172 +2024.06.12 02:11:00,200.139 +2024.06.12 02:12:00,200.108 +2024.06.12 02:13:00,200.129 +2024.06.12 02:14:00,200.091 +2024.06.12 02:15:00,200.113 +2024.06.12 02:16:00,200.114 +2024.06.12 02:17:00,200.114 +2024.06.12 02:18:00,200.139 +2024.06.12 02:19:00,200.105 +2024.06.12 02:20:00,200.093 +2024.06.12 02:21:00,200.097 +2024.06.12 02:22:00,200.095 +2024.06.12 02:23:00,200.111 +2024.06.12 02:24:00,200.126 +2024.06.12 02:25:00,200.153 +2024.06.12 02:26:00,200.153 +2024.06.12 02:27:00,200.132 +2024.06.12 02:28:00,200.128 +2024.06.12 02:29:00,200.11 +2024.06.12 02:30:00,200.105 +2024.06.12 02:31:00,200.107 +2024.06.12 02:32:00,200.095 +2024.06.12 02:33:00,200.102 +2024.06.12 02:34:00,200.08 +2024.06.12 02:35:00,200.091 +2024.06.12 02:36:00,200.066 +2024.06.12 02:37:00,200.083 +2024.06.12 02:38:00,200.088 +2024.06.12 02:39:00,200.095 +2024.06.12 02:40:00,200.127 +2024.06.12 02:41:00,200.116 +2024.06.12 02:42:00,200.129 +2024.06.12 02:43:00,200.139 +2024.06.12 02:44:00,200.144 +2024.06.12 02:45:00,200.114 +2024.06.12 02:46:00,200.109 +2024.06.12 02:47:00,200.107 +2024.06.12 02:48:00,200.088 +2024.06.12 02:49:00,200.116 +2024.06.12 02:50:00,200.114 +2024.06.12 02:51:00,200.102 +2024.06.12 02:52:00,200.11 +2024.06.12 02:53:00,200.113 +2024.06.12 02:54:00,200.106 +2024.06.12 02:55:00,200.127 +2024.06.12 02:56:00,200.128 +2024.06.12 02:57:00,200.15 +2024.06.12 02:58:00,200.175 +2024.06.12 02:59:00,200.18 +2024.06.12 03:00:00,200.185 +2024.06.12 03:01:00,200.187 +2024.06.12 03:02:00,200.195 +2024.06.12 03:03:00,200.205 +2024.06.12 03:04:00,200.194 +2024.06.12 03:05:00,200.189 +2024.06.12 03:06:00,200.193 +2024.06.12 03:07:00,200.195 +2024.06.12 03:08:00,200.189 +2024.06.12 03:09:00,200.175 +2024.06.12 03:10:00,200.171 +2024.06.12 03:11:00,200.176 +2024.06.12 03:12:00,200.182 +2024.06.12 03:13:00,200.179 +2024.06.12 03:14:00,200.187 +2024.06.12 03:15:00,200.173 +2024.06.12 03:16:00,200.161 +2024.06.12 03:17:00,200.172 +2024.06.12 03:18:00,200.164 +2024.06.12 03:19:00,200.153 +2024.06.12 03:20:00,200.161 +2024.06.12 03:21:00,200.206 +2024.06.12 03:22:00,200.206 +2024.06.12 03:23:00,200.198 +2024.06.12 03:24:00,200.183 +2024.06.12 03:25:00,200.181 +2024.06.12 03:26:00,200.172 +2024.06.12 03:27:00,200.173 +2024.06.12 03:28:00,200.176 +2024.06.12 03:29:00,200.194 +2024.06.12 03:30:00,200.204 +2024.06.12 03:31:00,200.23 +2024.06.12 03:32:00,200.227 +2024.06.12 03:33:00,200.228 +2024.06.12 03:34:00,200.228 +2024.06.12 03:35:00,200.238 +2024.06.12 03:36:00,200.237 +2024.06.12 03:37:00,200.211 +2024.06.12 03:38:00,200.198 +2024.06.12 03:39:00,200.191 +2024.06.12 03:40:00,200.191 +2024.06.12 03:41:00,200.193 +2024.06.12 03:42:00,200.22 +2024.06.12 03:43:00,200.21 +2024.06.12 03:44:00,200.214 +2024.06.12 03:45:00,200.222 +2024.06.12 03:46:00,200.219 +2024.06.12 03:47:00,200.221 +2024.06.12 03:48:00,200.225 +2024.06.12 03:49:00,200.222 +2024.06.12 03:50:00,200.223 +2024.06.12 03:51:00,200.225 +2024.06.12 03:52:00,200.238 +2024.06.12 03:53:00,200.241 +2024.06.12 03:54:00,200.245 +2024.06.12 03:55:00,200.234 +2024.06.12 03:56:00,200.24 +2024.06.12 03:57:00,200.25 +2024.06.12 03:58:00,200.265 +2024.06.12 03:59:00,200.27 +2024.06.12 04:00:00,200.286 +2024.06.12 04:01:00,200.286 +2024.06.12 04:02:00,200.285 +2024.06.12 04:03:00,200.292 +2024.06.12 04:04:00,200.287 +2024.06.12 04:05:00,200.268 +2024.06.12 04:06:00,200.277 +2024.06.12 04:07:00,200.273 +2024.06.12 04:08:00,200.272 +2024.06.12 04:09:00,200.259 +2024.06.12 04:10:00,200.257 +2024.06.12 04:11:00,200.259 +2024.06.12 04:12:00,200.28 +2024.06.12 04:13:00,200.301 +2024.06.12 04:14:00,200.281 +2024.06.12 04:15:00,200.281 +2024.06.12 04:16:00,200.293 +2024.06.12 04:17:00,200.286 +2024.06.12 04:18:00,200.3 +2024.06.12 04:19:00,200.285 +2024.06.12 04:20:00,200.289 +2024.06.12 04:21:00,200.284 +2024.06.12 04:22:00,200.277 +2024.06.12 04:23:00,200.271 +2024.06.12 04:24:00,200.274 +2024.06.12 04:25:00,200.287 +2024.06.12 04:26:00,200.278 +2024.06.12 04:27:00,200.282 +2024.06.12 04:28:00,200.29 +2024.06.12 04:29:00,200.304 +2024.06.12 04:30:00,200.317 +2024.06.12 04:31:00,200.299 +2024.06.12 04:32:00,200.3 +2024.06.12 04:33:00,200.323 +2024.06.12 04:34:00,200.324 +2024.06.12 04:35:00,200.315 +2024.06.12 04:36:00,200.313 +2024.06.12 04:37:00,200.317 +2024.06.12 04:38:00,200.324 +2024.06.12 04:39:00,200.313 +2024.06.12 04:40:00,200.305 +2024.06.12 04:41:00,200.304 +2024.06.12 04:42:00,200.298 +2024.06.12 04:43:00,200.31 +2024.06.12 04:44:00,200.316 +2024.06.12 04:45:00,200.313 +2024.06.12 04:46:00,200.315 +2024.06.12 04:47:00,200.314 +2024.06.12 04:48:00,200.316 +2024.06.12 04:49:00,200.311 +2024.06.12 04:50:00,200.301 +2024.06.12 04:51:00,200.312 +2024.06.12 04:52:00,200.3 +2024.06.12 04:53:00,200.3 +2024.06.12 04:54:00,200.291 +2024.06.12 04:55:00,200.287 +2024.06.12 04:56:00,200.28 +2024.06.12 04:57:00,200.279 +2024.06.12 04:58:00,200.285 +2024.06.12 04:59:00,200.285 +2024.06.12 05:00:00,200.281 +2024.06.12 05:01:00,200.27 +2024.06.12 05:02:00,200.259 +2024.06.12 05:03:00,200.259 +2024.06.12 05:04:00,200.272 +2024.06.12 05:05:00,200.268 +2024.06.12 05:06:00,200.253 +2024.06.12 05:07:00,200.251 +2024.06.12 05:08:00,200.253 +2024.06.12 05:09:00,200.255 +2024.06.12 05:10:00,200.256 +2024.06.12 05:11:00,200.236 +2024.06.12 05:12:00,200.222 +2024.06.12 05:13:00,200.209 +2024.06.12 05:14:00,200.208 +2024.06.12 05:15:00,200.234 +2024.06.12 05:16:00,200.238 +2024.06.12 05:17:00,200.238 +2024.06.12 05:18:00,200.25 +2024.06.12 05:19:00,200.245 +2024.06.12 05:20:00,200.246 +2024.06.12 05:21:00,200.26 +2024.06.12 05:22:00,200.266 +2024.06.12 05:23:00,200.255 +2024.06.12 05:24:00,200.269 +2024.06.12 05:25:00,200.269 +2024.06.12 05:26:00,200.269 +2024.06.12 05:27:00,200.274 +2024.06.12 05:28:00,200.265 +2024.06.12 05:29:00,200.265 +2024.06.12 05:30:00,200.26 +2024.06.12 05:31:00,200.264 +2024.06.12 05:32:00,200.266 +2024.06.12 05:33:00,200.255 +2024.06.12 05:34:00,200.273 +2024.06.12 05:35:00,200.303 +2024.06.12 05:36:00,200.298 +2024.06.12 05:37:00,200.313 +2024.06.12 05:38:00,200.301 +2024.06.12 05:39:00,200.299 +2024.06.12 05:40:00,200.298 +2024.06.12 05:41:00,200.303 +2024.06.12 05:42:00,200.297 +2024.06.12 05:43:00,200.287 +2024.06.12 05:44:00,200.277 +2024.06.12 05:45:00,200.284 +2024.06.12 05:46:00,200.291 +2024.06.12 05:47:00,200.301 +2024.06.12 05:48:00,200.308 +2024.06.12 05:49:00,200.303 +2024.06.12 05:50:00,200.296 +2024.06.12 05:51:00,200.297 +2024.06.12 05:52:00,200.3 +2024.06.12 05:53:00,200.292 +2024.06.12 05:54:00,200.29 +2024.06.12 05:55:00,200.29 +2024.06.12 05:56:00,200.302 +2024.06.12 05:57:00,200.303 +2024.06.12 05:58:00,200.297 +2024.06.12 05:59:00,200.304 +2024.06.12 06:00:00,200.299 +2024.06.12 06:01:00,200.293 +2024.06.12 06:02:00,200.302 +2024.06.12 06:03:00,200.313 +2024.06.12 06:04:00,200.313 +2024.06.12 06:05:00,200.314 +2024.06.12 06:06:00,200.311 +2024.06.12 06:07:00,200.298 +2024.06.12 06:08:00,200.298 +2024.06.12 06:09:00,200.296 +2024.06.12 06:10:00,200.296 +2024.06.12 06:11:00,200.296 +2024.06.12 06:12:00,200.283 +2024.06.12 06:13:00,200.292 +2024.06.12 06:14:00,200.283 +2024.06.12 06:15:00,200.278 +2024.06.12 06:16:00,200.276 +2024.06.12 06:17:00,200.264 +2024.06.12 06:18:00,200.267 +2024.06.12 06:19:00,200.269 +2024.06.12 06:20:00,200.269 +2024.06.12 06:21:00,200.267 +2024.06.12 06:22:00,200.285 +2024.06.12 06:23:00,200.282 +2024.06.12 06:24:00,200.279 +2024.06.12 06:25:00,200.286 +2024.06.12 06:26:00,200.283 +2024.06.12 06:27:00,200.293 +2024.06.12 06:28:00,200.319 +2024.06.12 06:29:00,200.331 +2024.06.12 06:30:00,200.349 +2024.06.12 06:31:00,200.362 +2024.06.12 06:32:00,200.378 +2024.06.12 06:33:00,200.376 +2024.06.12 06:34:00,200.398 +2024.06.12 06:35:00,200.373 +2024.06.12 06:36:00,200.376 +2024.06.12 06:37:00,200.375 +2024.06.12 06:38:00,200.362 +2024.06.12 06:39:00,200.396 +2024.06.12 06:40:00,200.392 +2024.06.12 06:41:00,200.407 +2024.06.12 06:42:00,200.4 +2024.06.12 06:43:00,200.411 +2024.06.12 06:44:00,200.414 +2024.06.12 06:45:00,200.412 +2024.06.12 06:46:00,200.421 +2024.06.12 06:47:00,200.417 +2024.06.12 06:48:00,200.423 +2024.06.12 06:49:00,200.415 +2024.06.12 06:50:00,200.421 +2024.06.12 06:51:00,200.439 +2024.06.12 06:52:00,200.439 +2024.06.12 06:53:00,200.438 +2024.06.12 06:54:00,200.434 +2024.06.12 06:55:00,200.416 +2024.06.12 06:56:00,200.409 +2024.06.12 06:57:00,200.376 +2024.06.12 06:58:00,200.392 +2024.06.12 06:59:00,200.435 +2024.06.12 07:00:00,200.398 +2024.06.12 07:01:00,200.452 +2024.06.12 07:02:00,200.456 +2024.06.12 07:03:00,200.484 +2024.06.12 07:04:00,200.452 +2024.06.12 07:05:00,200.452 +2024.06.12 07:06:00,200.442 +2024.06.12 07:07:00,200.42 +2024.06.12 07:08:00,200.446 +2024.06.12 07:09:00,200.43 +2024.06.12 07:10:00,200.416 +2024.06.12 07:11:00,200.424 +2024.06.12 07:12:00,200.443 +2024.06.12 07:13:00,200.469 +2024.06.12 07:14:00,200.452 +2024.06.12 07:15:00,200.449 +2024.06.12 07:16:00,200.457 +2024.06.12 07:17:00,200.467 +2024.06.12 07:18:00,200.45 +2024.06.12 07:19:00,200.441 +2024.06.12 07:20:00,200.438 +2024.06.12 07:21:00,200.4 +2024.06.12 07:22:00,200.381 +2024.06.12 07:23:00,200.353 +2024.06.12 07:24:00,200.362 +2024.06.12 07:25:00,200.363 +2024.06.12 07:26:00,200.365 +2024.06.12 07:27:00,200.364 +2024.06.12 07:28:00,200.374 +2024.06.12 07:29:00,200.374 +2024.06.12 07:30:00,200.395 +2024.06.12 07:31:00,200.397 +2024.06.12 07:32:00,200.395 +2024.06.12 07:33:00,200.398 +2024.06.12 07:34:00,200.401 +2024.06.12 07:35:00,200.376 +2024.06.12 07:36:00,200.364 +2024.06.12 07:37:00,200.367 +2024.06.12 07:38:00,200.367 +2024.06.12 07:39:00,200.378 +2024.06.12 07:40:00,200.392 +2024.06.12 07:41:00,200.362 +2024.06.12 07:42:00,200.365 +2024.06.12 07:43:00,200.361 +2024.06.12 07:44:00,200.345 +2024.06.12 07:45:00,200.358 +2024.06.12 07:46:00,200.331 +2024.06.12 07:47:00,200.327 +2024.06.12 07:48:00,200.342 +2024.06.12 07:49:00,200.345 +2024.06.12 07:50:00,200.322 +2024.06.12 07:51:00,200.326 +2024.06.12 07:52:00,200.31 +2024.06.12 07:53:00,200.306 +2024.06.12 07:54:00,200.29 +2024.06.12 07:55:00,200.319 +2024.06.12 07:56:00,200.329 +2024.06.12 07:57:00,200.344 +2024.06.12 07:58:00,200.331 +2024.06.12 07:59:00,200.346 +2024.06.12 08:00:00,200.336 +2024.06.12 08:01:00,200.362 +2024.06.12 08:02:00,200.355 +2024.06.12 08:03:00,200.352 +2024.06.12 08:04:00,200.357 +2024.06.12 08:05:00,200.377 +2024.06.12 08:06:00,200.406 +2024.06.12 08:07:00,200.423 +2024.06.12 08:08:00,200.407 +2024.06.12 08:09:00,200.426 +2024.06.12 08:10:00,200.451 +2024.06.12 08:11:00,200.451 +2024.06.12 08:12:00,200.454 +2024.06.12 08:13:00,200.471 +2024.06.12 08:14:00,200.483 +2024.06.12 08:15:00,200.505 +2024.06.12 08:16:00,200.475 +2024.06.12 08:17:00,200.451 +2024.06.12 08:18:00,200.437 +2024.06.12 08:19:00,200.458 +2024.06.12 08:20:00,200.416 +2024.06.12 08:21:00,200.425 +2024.06.12 08:22:00,200.443 +2024.06.12 08:23:00,200.441 +2024.06.12 08:24:00,200.405 +2024.06.12 08:25:00,200.395 +2024.06.12 08:26:00,200.41 +2024.06.12 08:27:00,200.411 +2024.06.12 08:28:00,200.442 +2024.06.12 08:29:00,200.407 +2024.06.12 08:30:00,200.433 +2024.06.12 08:31:00,200.452 +2024.06.12 08:32:00,200.454 +2024.06.12 08:33:00,200.463 +2024.06.12 08:34:00,200.453 +2024.06.12 08:35:00,200.444 +2024.06.12 08:36:00,200.426 +2024.06.12 08:37:00,200.429 +2024.06.12 08:38:00,200.428 +2024.06.12 08:39:00,200.463 +2024.06.12 08:40:00,200.487 +2024.06.12 08:41:00,200.465 +2024.06.12 08:42:00,200.452 +2024.06.12 08:43:00,200.485 +2024.06.12 08:44:00,200.5 +2024.06.12 08:45:00,200.49 +2024.06.12 08:46:00,200.477 +2024.06.12 08:47:00,200.47 +2024.06.12 08:48:00,200.489 +2024.06.12 08:49:00,200.488 +2024.06.12 08:50:00,200.514 +2024.06.12 08:51:00,200.492 +2024.06.12 08:52:00,200.505 +2024.06.12 08:53:00,200.503 +2024.06.12 08:54:00,200.514 +2024.06.12 08:55:00,200.526 +2024.06.12 08:56:00,200.543 +2024.06.12 08:57:00,200.521 +2024.06.12 08:58:00,200.524 +2024.06.12 08:59:00,200.508 diff --git a/tests/integration.rs b/tests/integration.rs index e028e1a2..bf02ea67 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2,6 +2,14 @@ use evalexpr::{error::*, *}; + +#[test] +fn test_ternary() { + assert_eq!( + eval("1 == 1 ? 2 : 3"), + Ok(Value::Int(2)) + ); +} #[test] fn test_unary_examples() { assert_eq!(eval("3"), Ok(Value::Int(3))); @@ -108,15 +116,15 @@ fn test_boolean_examples() { fn test_with_context() { let mut context = HashMapContext::new(); context - .set_value("tr".into(), Value::Boolean(true)) + .set_value("tr".into(), Value::Boolean(true),true) .unwrap(); context - .set_value("fa".into(), Value::Boolean(false)) + .set_value("fa".into(), Value::Boolean(false),true) .unwrap(); - context.set_value("five".into(), Value::Int(5)).unwrap(); - context.set_value("six".into(), Value::Int(6)).unwrap(); - context.set_value("half".into(), Value::Float(0.5)).unwrap(); - context.set_value("zero".into(), Value::Int(0)).unwrap(); + context.set_value("five".into(), Value::Int(5),true).unwrap(); + context.set_value("six".into(), Value::Int(6),true).unwrap(); + context.set_value("half".into(), Value::Float(0.5),true).unwrap(); + context.set_value("zero".into(), Value::Int(0),true).unwrap(); assert_eq!(eval_with_context("tr", &context), Ok(Value::Boolean(true))); assert_eq!(eval_with_context("fa", &context), Ok(Value::Boolean(false))); @@ -156,7 +164,7 @@ fn test_functions() { ) .unwrap(); context - .set_value("five".to_string(), Value::Int(5)) + .set_value("five".to_string(), Value::Int(5),true) .unwrap(); assert_eq!(eval_with_context("sub2 5", &context), Ok(Value::Int(3))); @@ -237,7 +245,7 @@ fn test_n_ary_functions() { ) .unwrap(); context - .set_value("five".to_string(), Value::Int(5)) + .set_value("five".to_string(), Value::Int(5),false) .unwrap(); context .set_function("function_four".into(), Function::new(|_| Ok(Value::Int(4)))) @@ -400,19 +408,19 @@ fn test_builtin_functions() { ); assert_eq!( eval("str::from(\"a\")"), - Ok(Value::String(String::from("\"a\""))) + Ok(Value::String(String::from("\"a\"").into())) ); - assert_eq!(eval("str::from(1.0)"), Ok(Value::String(String::from("1")))); - assert_eq!(eval("str::from(1)"), Ok(Value::String(String::from("1")))); + assert_eq!(eval("str::from(1.0)"), Ok(Value::String(String::from("1").into()))); + assert_eq!(eval("str::from(1)"), Ok(Value::String(String::from("1").into()))); assert_eq!( eval("str::from(true)"), - Ok(Value::String(String::from("true"))) + Ok(Value::String(String::from("true").into())) ); assert_eq!( eval("str::from(1, 2, 3)"), - Ok(Value::String(String::from("(1, 2, 3)"))) + Ok(Value::String(String::from("(1, 2, 3)").into())) ); - assert_eq!(eval("str::from()"), Ok(Value::String(String::from("()")))); + assert_eq!(eval("str::from()"), Ok(Value::String(String::from("()").into()))); // Bitwise assert_eq!(eval("bitand(5, -1)"), Ok(Value::Int(5))); assert_eq!(eval("bitand(6, 5)"), Ok(Value::Int(4))); @@ -430,7 +438,7 @@ fn test_builtin_functions() { assert_eq!(eval("if(false, -6, 5)"), Ok(Value::Int(5))); assert_eq!( eval("if(2-1==1, \"good\", 0)"), - Ok(Value::String(String::from("good"))) + Ok(Value::String(String::from("good").into())) ); } @@ -451,11 +459,11 @@ fn test_errors() { expected: 2, }) ); - assert_eq!(eval("!(()true)"), Err(EvalexprError::AppendedToLeafNode)); + assert_eq!(eval("!(()true)"), Err(EvalexprError::AppendedToLeafNode(format!("()")))); assert_eq!( eval("math::is_nan(\"xxx\")"), Err(EvalexprError::ExpectedNumber { - actual: Value::String("xxx".to_string()) + actual: Value::String("xxx".to_string().into()) }) ); } @@ -501,7 +509,7 @@ fn test_no_panic() { fn test_shortcut_functions() { let mut context = HashMapContext::new(); context - .set_value("string".into(), Value::from("a string")) + .set_value("string".into(), Value::from("a string"),true) .unwrap(); assert_eq!(eval_string("\"3.3\""), Ok("3.3".to_owned())); @@ -1539,14 +1547,16 @@ fn test_hashmap_context_clone_debug() { Function::new(move |_| Ok(Value::Int(four))), ) .unwrap(); - context.set_value("variable_five".into(), 5.into()).unwrap(); + context.set_value("variable_five".into(), 5.into(), false).unwrap(); let context = context; let cloned_context = context.clone(); assert_eq!(format!("{:?}", &context), format!("{:?}", &cloned_context)); assert_eq!( - cloned_context.get_value("variable_five"), - Some(&Value::from(5)) + cloned_context + .get_value("variable_five") + .map(std::borrow::Cow::into_owned), + Some(Value::from(5)) ); assert_eq!( eval_with_context("mult_3 2", &cloned_context), @@ -1674,7 +1684,7 @@ fn test_long_expression_i89() { #[test] fn test_value_type() { assert_eq!( - ValueType::from(&Value::String(String::new())), + ValueType::from(&Value::String(String::new().into())), ValueType::String ); assert_eq!(ValueType::from(&Value::Float(0.0)), ValueType::Float); @@ -1684,7 +1694,7 @@ fn test_value_type() { assert_eq!(ValueType::from(&Value::Empty), ValueType::Empty); assert_eq!( - ValueType::from(&mut Value::String(String::new())), + ValueType::from(&mut Value::String(String::new().into())), ValueType::String ); assert_eq!(ValueType::from(&mut Value::Float(0.0)), ValueType::Float); @@ -1699,14 +1709,14 @@ fn test_value_type() { ); assert_eq!(ValueType::from(&mut Value::Empty), ValueType::Empty); - assert!(!Value::String(String::new()).is_number()); + assert!(!Value::String(String::new().into()).is_number()); assert!(Value::Float(0.0).is_number()); assert!(Value::Int(0).is_number()); assert!(!Value::Boolean(true).is_number()); assert!(!Value::Tuple(Vec::new()).is_number()); assert!(!Value::Empty.is_number()); - assert!(!Value::String(String::new()).is_empty()); + assert!(!Value::String(String::new().into()).is_empty()); assert!(!Value::Float(0.0).is_empty()); assert!(!Value::Int(0).is_empty()); assert!(!Value::Boolean(true).is_empty()); @@ -1714,9 +1724,9 @@ fn test_value_type() { assert!(Value::Empty.is_empty()); assert_eq!( - Value::String(String::new()).as_float(), + Value::String(String::new().into()).as_float(), Err(EvalexprError::ExpectedFloat { - actual: Value::String(String::new()) + actual: Value::String(String::new().into()) }) ); assert_eq!(Value::Float(0.0).as_float(), Ok(0.0)); @@ -1746,9 +1756,9 @@ fn test_value_type() { ); assert_eq!( - Value::String(String::new()).as_tuple(), + Value::String(String::new().into()).as_tuple(), Err(EvalexprError::ExpectedTuple { - actual: Value::String(String::new()) + actual: Value::String(String::new().into()) }) ); assert_eq!( @@ -1778,9 +1788,9 @@ fn test_value_type() { ); assert_eq!( - Value::String(String::new()).as_fixed_len_tuple(0), + Value::String(String::new().into()).as_fixed_len_tuple(0), Err(EvalexprError::ExpectedTuple { - actual: Value::String(String::new()) + actual: Value::String(String::new().into()) }) ); assert_eq!( @@ -1813,9 +1823,9 @@ fn test_value_type() { ); assert_eq!( - Value::String(String::new()).as_empty(), + Value::String(String::new().into()).as_empty(), Err(EvalexprError::ExpectedEmpty { - actual: Value::String(String::new()) + actual: Value::String(String::new().into()) }) ); assert_eq!( @@ -1845,8 +1855,8 @@ fn test_value_type() { assert_eq!(Value::Empty.as_empty(), Ok(())); assert_eq!( - Result::from(Value::String(String::new())), - Ok(Value::String(String::new())) + Result::from(Value::String(String::new().into())), + Ok(Value::String(String::new().into())) ); } diff --git a/tests/moving_average_tests.rs b/tests/moving_average_tests.rs new file mode 100644 index 00000000..4b33992c --- /dev/null +++ b/tests/moving_average_tests.rs @@ -0,0 +1,60 @@ +// +// mod tests { +// +// use std::path::PathBuf; +// use anyhow::Context; +// use chrono::NaiveDateTime; +// use evalexpr::*; +// use csv::Reader; +// use serde::Deserialize; +// +// #[derive(Debug, Deserialize)] +// struct Record { +// #[serde(rename = "dt")] +// datetime: String, +// close: f64, +// } +// +// +// fn read_csv(file_path: &str) -> anyhow::Result<()> { +// +// println!("Reading file: {}", file_path); +// +// let mut rdr = Reader::from_path(file_path)?; +// let mut values = vec![]; +// let mut indexes = vec![]; +// let mut counter = 0; +// let window_size = 110; +// let mut last_value = 0f64; +// +// for result in rdr.deserialize() { +// let record: Record = result?; +// values.push(Value::Float(record.close.clone())); +// indexes.push(counter); +// +// if(counter >= window_size) { +// let result = triangular_moving_average(&values, &indexes[counter - window_size..counter]); +// let x = result.unwrap().as_float().unwrap(); +// println!("{:?} - tma = {:?} diff = {}", record, x, x - last_value); +// last_value = x; +// +// }else{ +// println!("{:?}", record); +// +// } +// +// +// counter += 1; +// } +// +// Ok(()) +// } +// #[test] +// fn moving_average_tests() -> anyhow::Result<()> { +// let mut pathname = PathBuf::from(env!("CARGO_MANIFEST_DIR")); +// pathname.push("tests"); +// pathname.push("close_values.csv"); +// +// read_csv(pathname.to_str().context("Unable to convert pathname to str")?) +// } +// } diff --git a/tests/regex.rs b/tests/regex.rs index 5f83eb3d..a0110e9a 100644 --- a/tests/regex.rs +++ b/tests/regex.rs @@ -3,6 +3,13 @@ use evalexpr::*; +#[test] +fn test_ternary() { + assert_eq!( + eval("1 == 1 ? 2 : 3"), + Ok(Value::Int(2)) + ); +} #[test] fn test_regex_functions() { assert_eq!( @@ -22,10 +29,10 @@ fn test_regex_functions() { }; assert_eq!( eval("str::regex_replace(\"foobar\", \".*?(o+)\", \"b$1\")"), - Ok(Value::String("boobar".to_owned())) + Ok(Value::String("boobar".to_owned().into())) ); assert_eq!( - eval("str::regex_replace(\"foobar\", \".*?(i+)\", \"b$1\")"), - Ok(Value::String("foobar".to_owned())) + eval("str::regex_replace(\"foobar\", \".*?(i+)\", \"b$1\")"), + Ok(Value::String("foobar".to_owned().into())) ); } diff --git a/tests/serde.rs b/tests/serde.rs index 410e167e..990cdd8e 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,7 +1,7 @@ #![cfg(not(tarpaulin_include))] #![cfg(feature = "serde")] -use evalexpr::{build_operator_tree, Node}; +use evalexpr::{build_operator_tree, Node, Value}; #[test] fn test_serde() { @@ -14,6 +14,13 @@ fn test_serde() { } } +#[test] +fn test_string_serialization() { + let string = Value::String("Item1".to_owned().into()); + + println!("{:?}", ron::ser::to_string(&string).unwrap()); +} + #[test] fn test_serde_errors() { assert_eq!(