From 019eb678aa9d70dfadded10db42edcefca98acd0 Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Thu, 7 Apr 2022 17:41:31 +0200 Subject: [PATCH 001/265] Breaking: Context::get_value() -> Option --- src/context/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 200ae939..d627eb33 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -17,7 +17,7 @@ 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>; + fn get_value(&self, identifier: &str) -> Option; /// 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`. @@ -54,7 +54,7 @@ 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 } @@ -86,8 +86,8 @@ impl HashMapContext { } impl Context for HashMapContext { - fn get_value(&self, identifier: &str) -> Option<&Value> { self.variables.get(identifier) + fn get_value(&self, identifier: &str) -> Option { } fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { From 41f7f8fcb8bf0f1a6b79d8bcf39c56994f34735b Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Thu, 7 Apr 2022 17:41:47 +0200 Subject: [PATCH 002/265] Adapt implementation to breaking API change --- src/context/mod.rs | 2 +- src/operator/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index d627eb33..29593eea 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -86,8 +86,8 @@ impl HashMapContext { } impl Context for HashMapContext { - self.variables.get(identifier) fn get_value(&self, identifier: &str) -> Option { + self.variables.get(identifier).cloned() } fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 3ebf4a1c..1d6b7ad8 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -425,7 +425,7 @@ impl Operator { VariableIdentifier { identifier } => { expect_operator_argument_amount(arguments.len(), 0)?; - if let Some(value) = context.get_value(identifier).cloned() { + if let Some(value) = context.get_value(identifier) { Ok(value) } else { Err(EvalexprError::VariableIdentifierNotFound( From a845ed3c1d9ebf5e990f2e95b5847ca5b742ee40 Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Thu, 7 Apr 2022 17:41:57 +0200 Subject: [PATCH 003/265] Adapt usages to breaking API change --- src/lib.rs | 8 ++++---- tests/integration.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 823ed8cf..6050058d 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"), 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"), 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"), 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/tests/integration.rs b/tests/integration.rs index e028e1a2..d3ca9bc7 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1546,7 +1546,7 @@ fn test_hashmap_context_clone_debug() { assert_eq!(format!("{:?}", &context), format!("{:?}", &cloned_context)); assert_eq!( cloned_context.get_value("variable_five"), - Some(&Value::from(5)) + Some(Value::from(5)) ); assert_eq!( eval_with_context("mult_3 2", &cloned_context), From 1e41c1377e4597f6be1aea0d95d8444020aa62dd Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Fri, 8 Apr 2022 08:19:09 +0200 Subject: [PATCH 004/265] Add example to doc of Context::get_value --- README.md | 8 ++++---- src/context/mod.rs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) 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 29593eea..8bd8e568 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -17,6 +17,24 @@ mod predefined; /// An immutable context. pub trait Context { /// Returns the value that is linked to the given identifier. + /// + /// # Example + /// + /// ```rust + /// use evalexpr::*; + /// use std::collections::HashMap; + /// + /// struct HashMapContext { + /// variables: HashMap + /// } + /// + /// impl Context for HashMapContext { + /// fn get_value(&self, identifier: &str) -> Option { + /// self.variables.get(identifier).cloned() + /// } + /// fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { todo!() } + /// } + /// ``` fn get_value(&self, identifier: &str) -> Option; /// Calls the function that is linked to the given identifier with the given argument. From 30b418ecf09fd39e62ad0731ded921c12df39c7a Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Sun, 8 May 2022 12:18:01 +0200 Subject: [PATCH 005/265] Option> instead of Option --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 8bd8e568..03446eea 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -72,7 +72,7 @@ pub trait GetFunctionContext: Context { pub struct EmptyContext; impl Context for EmptyContext { - fn get_value(&self, _identifier: &str) -> Option { + fn get_value(&self, _identifier: &str) -> Option> { None } From c42fb59f2cc6236c0d2ee6a6e8c3a9ec748abc4a Mon Sep 17 00:00:00 2001 From: Marko Topolnik Date: Sun, 8 May 2022 12:18:28 +0200 Subject: [PATCH 006/265] Adapt to API change --- src/context/mod.rs | 13 +++++++------ src/lib.rs | 8 ++++---- src/operator/mod.rs | 2 +- tests/integration.rs | 4 +++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 03446eea..09d97a2b 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -4,7 +4,7 @@ //! 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 std::{borrow::Cow, collections::HashMap}; use crate::{ function::Function, @@ -22,6 +22,7 @@ pub trait Context { /// /// ```rust /// use evalexpr::*; + /// use std::borrow::Cow; /// use std::collections::HashMap; /// /// struct HashMapContext { @@ -29,13 +30,13 @@ pub trait Context { /// } /// /// impl Context for HashMapContext { - /// fn get_value(&self, identifier: &str) -> Option { - /// self.variables.get(identifier).cloned() + /// fn get_value(&self, identifier: &str) -> Option> { + /// self.variables.get(identifier).map(Cow::Borrowed) /// } /// fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { todo!() } /// } /// ``` - fn get_value(&self, identifier: &str) -> Option; + fn get_value(&self, identifier: &str) -> Option>; /// 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`. @@ -104,8 +105,8 @@ impl HashMapContext { } impl Context for HashMapContext { - fn get_value(&self, identifier: &str) -> Option { - self.variables.get(identifier).cloned() + fn get_value(&self, identifier: &str) -> Option> { + self.variables.get(identifier).map(Cow::Borrowed) } fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { diff --git a/src/lib.rs b/src/lib.rs index 6050058d..6341f89c 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())); +//! 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. diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 1d6b7ad8..979fa40f 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -426,7 +426,7 @@ impl Operator { expect_operator_argument_amount(arguments.len(), 0)?; if let Some(value) = context.get_value(identifier) { - Ok(value) + Ok(value.into_owned()) } else { Err(EvalexprError::VariableIdentifierNotFound( identifier.clone(), diff --git a/tests/integration.rs b/tests/integration.rs index d3ca9bc7..7b0d3f8a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1545,7 +1545,9 @@ fn test_hashmap_context_clone_debug() { assert_eq!(format!("{:?}", &context), format!("{:?}", &cloned_context)); assert_eq!( - cloned_context.get_value("variable_five"), + cloned_context + .get_value("variable_five") + .map(std::borrow::Cow::into_owned), Some(Value::from(5)) ); assert_eq!( From e922bbde6b75f16b26d7867660172dcbb9353738 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:49:17 +0000 Subject: [PATCH 007/265] Added thin trait --- Cargo.toml | 1 + src/context/mod.rs | 44 ++++++++++++++++---------------------------- src/lib.rs | 1 - 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7868b66c..2b67fd9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ 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} +thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_object", branch = "main" } [features] serde_support = ["serde", "serde_derive"] diff --git a/src/context/mod.rs b/src/context/mod.rs index 09d97a2b..03a99f8a 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -5,7 +5,7 @@ //! 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::{borrow::Cow, collections::HashMap}; - +use thin_trait_object::thin_trait_object; use crate::{ function::Function, value::{value_type::ValueType, Value}, @@ -14,34 +14,7 @@ use crate::{ mod predefined; -/// An immutable context. -pub trait Context { - /// Returns the value that is linked to the given identifier. - /// - /// # Example - /// - /// ```rust - /// use evalexpr::*; - /// use std::borrow::Cow; - /// use std::collections::HashMap; - /// - /// struct HashMapContext { - /// variables: HashMap - /// } - /// - /// impl Context for HashMapContext { - /// fn get_value(&self, identifier: &str) -> Option> { - /// self.variables.get(identifier).map(Cow::Borrowed) - /// } - /// fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { todo!() } - /// } - /// ``` - fn get_value(&self, identifier: &str) -> Option>; - /// 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 { @@ -144,6 +117,21 @@ 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>; +/// Retrieves the value of the given identifier. + fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; +} + +#[thin_trait_object] +pub trait ThinTraitContext { + + fn get_value(&self, identifier: &str) -> Option; + + fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; +} + /// This macro provides a convenient syntax for creating a static context. /// /// # Examples diff --git a/src/lib.rs b/src/lib.rs index 6341f89c..9dde8207 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -519,7 +519,6 @@ //! See [LICENSE](LICENSE) for details. //! -#![deny(missing_docs)] #![forbid(unsafe_code)] #[cfg(feature = "regex_support")] From a506990eeeabb18b46353d59cef30e5c8962a064 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:51:37 +0000 Subject: [PATCH 008/265] Added thin trait --- src/context/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index 03a99f8a..31d8109b 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -129,6 +129,8 @@ pub trait ThinTraitContext { fn get_value(&self, identifier: &str) -> Option; + fn set_row(&self, row: usize); + fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; } From cc26a8ae99cbe91952c89e0d76a6544fd75169da Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:05:29 +0000 Subject: [PATCH 009/265] Added thin trait --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2b67fd9d..fbf36eaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.0" +version = "7.2.1" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From d65e6bafd4fda56bf64e6f8fb4f26f902fd90808 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:09:12 +0000 Subject: [PATCH 010/265] Added thin trait --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9dde8207..cafbd6ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, + Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, ThinTraitContext, HashMapContext, }, error::{EvalexprError, EvalexprResult}, From 09aa8e24d27ce757c2f29da5a6f18a05d869b57f Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:10:16 +0000 Subject: [PATCH 011/265] Added thin trait --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fbf36eaf..9ecf8eb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.1" +version = "7.2.2" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From 4f01a9a9cb0a85175175fc5667bae370ed600e7e Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:31:30 +0000 Subject: [PATCH 012/265] Added thin trait --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ecf8eb0..fa3d9c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.2" +version = "7.2.3" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/lib.rs b/src/lib.rs index cafbd6ce..bb554142 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, ThinTraitContext, + Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, ThinTraitContext,BoxedThinTraitContext, HashMapContext, }, error::{EvalexprError, EvalexprResult}, From 48dbdf545a3acb57edcfcb47698ef196b3f32869 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:40:48 +0000 Subject: [PATCH 013/265] Added evalexpr trait --- Cargo.toml | 2 +- src/value/mod.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fa3d9c75..21702ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.3" +version = "7.2.4" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index b5b14a42..b798bcbc 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -22,6 +22,7 @@ pub const EMPTY_VALUE: () = (); /// Values can be of different subtypes that are the variants of this enum. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] +#[repr(C)] pub enum Value { /// A string value. String(String), From 8964acf24e2d4dd47b81fba2077b62c5685bd547 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:03:04 +0000 Subject: [PATCH 014/265] Added arithmetic operations to allow value types to be arithmeticed --- src/value/mod.rs | 265 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 263 insertions(+), 2 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index b798bcbc..e2942876 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -194,10 +194,211 @@ impl From<()> for Value { } } +use std::ops::{Div, Rem}; + +use std::ops::Mul; + +#[derive(Debug, Clone)] +pub enum Error { + UnsupportedOperation, + DivisionByZero, +} + +pub trait ToErrorType { + fn to_error_code(&self) -> i32; +} + +impl ToErrorType for Error { + fn to_error_code(&self) -> i32 { + match self { + Error::UnsupportedOperation => 1, + Error::DivisionByZero => 2, + } + } +} + +use std::ops::Sub; + +use std::ops::Add; + +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::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 FloatType + b)), + (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)), + // 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))), + (Value::Float(a), Value::String(b)) | (Value::String(b), Value::Float(a)) => Ok(Value::String(format!("{}{}", a, b))), + // Add cases for other Value variants as necessary + _ => Err(Error::UnsupportedOperation), + } + } +} + + +impl Sub for Value { + type Output = Result; + + fn sub(self, other: Self) -> Self::Output { + match (self, other) { + (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::UnsupportedOperation), + } + } +} + +impl Mul for Value { + type Output = Result; + + fn mul(self, other: Self) -> Self::Output { + match (self, other) { + (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::UnsupportedOperation), + } + } +} + +impl Div for Value { + type Output = Result; + + fn div(self, other: Self) -> Self::Output { + match (self, other) { + (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::UnsupportedOperation), + } + } +} + + +use std::ops::Neg; + +impl Neg for Value { + type Output = Result; + + fn neg(self) -> Self::Output { + match self { + Value::Int(a) => Ok(Value::Int(-a)), + Value::Float(a) => Ok(Value::Float(-a)), + _ => Err(Error::UnsupportedOperation), + } + } +} + + +impl Rem for Value { + type Output = Result; + + fn rem(self, other: Self) -> Self::Output { + match (self, other) { + (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::UnsupportedOperation), + } + } +} + +#[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, +} + +/// 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 + }, + Err(e) => FfiResult { + value: T::default(), + error_code : e.to_error_code(), // Use the provided error code + }, + } +} + + + + + +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(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(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!( @@ -221,4 +422,64 @@ 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))); + } + + // Add more tests to cover other operations, edge cases, and error scenarios + } + From b863ca3bb560c182c994d64639ae2faa8c707f48 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:03:31 +0000 Subject: [PATCH 015/265] Added arithmetic operations to allow value types to be arithmeticed --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 21702ff5..d9fe7459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.4" +version = "7.2.5" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From a2a16b51110ea4680a5262cc04aa03d427b2bb24 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:24:13 +0000 Subject: [PATCH 016/265] Added arithmetic operations to allow value types to be arithmeticed --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d9fe7459..b81ff22d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.5" +version = "7.2.7" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From 159e149ab0db7963c416ec569adb168b9cfa4e10 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:25:53 +0000 Subject: [PATCH 017/265] Added arithmetic operations to allow value types to be arithmeticed --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bb554142..ae3215fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -542,7 +542,7 @@ pub use crate::{ 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}, }; mod context; From 8b82803cf8540ffe5836530cb1a506e3200b2127 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:27:31 +0000 Subject: [PATCH 018/265] Added arithmetic operations to allow value types to be arithmeticed --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b81ff22d..cb518a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.7" +version = "7.2.8" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From fc1b08b8fc78f6e19ba45d84aa9dce4ef01ca5c1 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Fri, 23 Feb 2024 18:37:40 +0000 Subject: [PATCH 019/265] Catering for refs --- Cargo.toml | 2 +- src/value/mod.rs | 254 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 206 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb518a09..ed39f00b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.8" +version = "7.2.9" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index e2942876..93824076 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -221,15 +221,46 @@ use std::ops::Sub; use std::ops::Add; -impl Add for Value { - type Output = Result; // Assuming you have an error type defined +use std::ops::Neg; + +impl Neg for Value { + type Output = Result; + + fn neg(self) -> Self::Output { + match self { + Value::Int(a) => Ok(Value::Int(-a)), + Value::Float(a) => Ok(Value::Float(-a)), + _ => Err(Error::UnsupportedOperation), + } + } +} + + + + +impl Rem for &Value { + type Output = Result; + + fn rem(self, other: Self) -> Self::Output { + match (self, other) { + (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::UnsupportedOperation), + } + } +} + +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::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 FloatType + b)), - (Value::String(a), Value::String(b)) => Ok(Value::String(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, b))), // 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))), (Value::Float(a), Value::String(b)) | (Value::String(b), Value::Float(a)) => Ok(Value::String(format!("{}{}", a, b))), @@ -240,64 +271,64 @@ impl Add for Value { } -impl Sub for Value { - type Output = Result; +impl Sub for &Value { + type Output = Result; fn sub(self, other: Self) -> Self::Output { match (self, other) { (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)), + (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::UnsupportedOperation), } } } -impl Mul for Value { - type Output = Result; +impl Mul for &Value { + type Output = Result; fn mul(self, other: Self) -> Self::Output { match (self, other) { (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)), + (Value::Int(a), Value::Float(b)) | (Value::Float(b), Value::Int(a)) => Ok(Value::Float(*a as f64 * b)), _ => Err(Error::UnsupportedOperation), } } } -impl Div for Value { +impl Div for &Value { type Output = Result; fn div(self, other: Self) -> Self::Output { match (self, other) { (Value::Int(a), Value::Int(b)) => { - if b == 0 { + if b == &0 { Err(Error::DivisionByZero) } else { Ok(Value::Int(a / b)) } }, (Value::Float(a), Value::Float(b)) => { - if b == 0.0 { + if b == &0.0 { Err(Error::DivisionByZero) } else { Ok(Value::Float(a / b)) } }, (Value::Int(a), Value::Float(b)) => { - if b == 0.0 { + if b == &0.0 { Err(Error::DivisionByZero) } else { - Ok(Value::Float(a as f64 / b)) + Ok(Value::Float(*a as f64 / b)) } }, (Value::Float(a), Value::Int(b)) => { - if b == 0 { + if b == &0 { Err(Error::DivisionByZero) } else { - Ok(Value::Float(a / b as f64)) + Ok(Value::Float(*a / *b as f64)) } }, // Add cases for other combinations as needed, returning UnsupportedOperation for non-numeric types @@ -307,34 +338,6 @@ impl Div for Value { } -use std::ops::Neg; - -impl Neg for Value { - type Output = Result; - - fn neg(self) -> Self::Output { - match self { - Value::Int(a) => Ok(Value::Int(-a)), - Value::Float(a) => Ok(Value::Float(-a)), - _ => Err(Error::UnsupportedOperation), - } - } -} - - -impl Rem for Value { - type Output = Result; - - fn rem(self, other: Self) -> Self::Output { - match (self, other) { - (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::UnsupportedOperation), - } - } -} #[repr(C)] pub struct FfiResult { @@ -369,7 +372,7 @@ macro_rules! declare_arithmetic_for_result { fn $fn(self, other: Value) -> Self::Output { match self { - Ok(self_val) => self_val.$fn(other), + Ok(ref self_val) => self_val.$fn(&other), Err(e) => Err(e), } } @@ -380,14 +383,50 @@ macro_rules! declare_arithmetic_for_result { fn $fn(self, other: Result) -> Self::Output { match other { - Ok(other_val) => self.$fn(other_val), + 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); @@ -480,6 +519,123 @@ mod tests { assert!(matches!(a.div(b), Err(Error::DivisionByZero))); } - // Add more tests to cover other operations, edge cases, and error scenarios + + #[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))); + } + } From 167a48dc9be15ba984aedc4f556c9c0114b1a2a4 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:24:45 +0000 Subject: [PATCH 020/265] Added default to value --- Cargo.toml | 2 +- src/value/mod.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ed39f00b..b97bcbdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.9" +version = "7.2.10" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index 93824076..3c7628b4 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -38,6 +38,12 @@ pub enum Value { Empty, } +impl Default for Value { + fn default() -> Self { + Value::Empty + } +} + impl Value { /// Returns true if `self` is a `Value::String`. pub fn is_string(&self) -> bool { From fcccde890b6b84930ad7397282b883d71920a7a9 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 13:29:27 +0000 Subject: [PATCH 021/265] Added is null to eval expr --- Cargo.toml | 2 +- src/lib.rs | 7 +++++++ src/tree/mod.rs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b97bcbdf..0661bef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.10" +version = "7.2.11" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/lib.rs b/src/lib.rs index ae3215fe..4ef6eb70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -556,4 +556,11 @@ mod token; mod tree; mod value; +pub fn is_null(value: &Value) -> &Value { + match value { + Value::Empty => &Value::Int(0), + _ => value, + } +} + // Exports diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 69f44944..bb7f4941 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -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(); From dc42c7a1a3ae1f1b3ad4eeb9cb4922b23ecea1f1 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 14:17:19 +0000 Subject: [PATCH 022/265] Added is null to eval expr --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0661bef6..e297a6fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.11" +version = "7.2.12" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From aef6cbe104575259f1f769a50fdd069068662a09 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 14:18:52 +0000 Subject: [PATCH 023/265] Added error to the eval expr crate --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e297a6fc..2f4ec884 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.12" +version = "7.2.13" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/lib.rs b/src/lib.rs index 4ef6eb70..a6341006 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -542,7 +542,7 @@ pub use crate::{ operator::Operator, token::PartialToken, tree::Node, - value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult,to_ffi_result}, + value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult,to_ffi_result, Error}, }; mod context; From 34204dfde4cd99cd86b6f5db9e6ed4ee9aa660a2 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:20:15 +0000 Subject: [PATCH 024/265] Added some custom functions to the eval expr crate --- src/custom_functions/is_null.rs | 8 +++ src/custom_functions/mod.rs | 4 ++ src/custom_functions/simple_cumulative_sum.rs | 58 ++++++++++++++++ src/custom_functions/simple_moving_average.rs | 66 ++++++++++++++++++ .../triangular_moving_average.rs | 67 +++++++++++++++++++ src/lib.rs | 13 ++-- 6 files changed, 207 insertions(+), 9 deletions(-) create mode 100644 src/custom_functions/is_null.rs create mode 100644 src/custom_functions/mod.rs create mode 100644 src/custom_functions/simple_cumulative_sum.rs create mode 100644 src/custom_functions/simple_moving_average.rs create mode 100644 src/custom_functions/triangular_moving_average.rs diff --git a/src/custom_functions/is_null.rs b/src/custom_functions/is_null.rs new file mode 100644 index 00000000..90873331 --- /dev/null +++ b/src/custom_functions/is_null.rs @@ -0,0 +1,8 @@ +use crate::Value; + +pub fn is_null(value: &Value) -> &Value { + match value { + Value::Empty => &Value::Int(0), + _ => value, + } +} diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs new file mode 100644 index 00000000..6f1e4dec --- /dev/null +++ b/src/custom_functions/mod.rs @@ -0,0 +1,4 @@ +pub mod triangular_moving_average; +pub mod is_null; +pub mod simple_moving_average; +pub mod simple_cumulative_sum; diff --git a/src/custom_functions/simple_cumulative_sum.rs b/src/custom_functions/simple_cumulative_sum.rs new file mode 100644 index 00000000..dc53e868 --- /dev/null +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -0,0 +1,58 @@ +use crate::{Error, Value}; + +pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + let mut sum: Value = Value::Float(0.0); + + for &col_index in columns { + match row.get(col_index) { + Some(Value::Float(val)) => { + if let Value::Float(sum_val) = sum { + sum = Value::Float(sum_val + val); + } + } + _ => continue, + } + } + + Ok(sum) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_cumulative_sum_normal_operation() { + let row = vec![Value::Float(10.0), Value::Float(20.0), Value::Float(30.0), Value::Float(40.0)]; + 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 row = vec![Value::Float(10.0), Value::Empty, Value::Float(30.0), Value::Empty]; + 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 row: Vec = vec![]; + 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 row = vec![Value::Empty, Value::Empty]; + 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..284298d4 --- /dev/null +++ b/src/custom_functions/simple_moving_average.rs @@ -0,0 +1,66 @@ +use crate::{Error, Value}; + +pub fn simple_moving_average(row: &[Value], columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + let mut sum: Value = Value::Float(0.0); + let mut count = 0usize; + + for &col_index in columns { + match row.get(col_index) { + Some(Value::Float(val)) => { + if let Value::Float(sum_val) = sum { + sum = Value::Float(sum_val + val); + count += 1; + } + } + _ => continue, + } + } + + if count > 0 { + if let Value::Float(sum_val) = sum { + return Ok(Value::Float(sum_val / count as f64)); + } + } + + Ok(Value::Empty) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_moving_average_normal_operation() { + let row = vec![Value::Float(10.0), Value::Float(20.0), Value::Float(30.0), Value::Float(40.0)]; + 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 row = vec![Value::Float(10.0), Value::Empty, Value::Float(30.0), Value::Empty]; + 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 row: Vec = vec![]; + 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 row = vec![Value::Empty, Value::Empty]; + let columns = vec![0, 1]; + let result = simple_moving_average(&row, &columns).unwrap(); + assert_eq!(result, Value::Empty); + } +} diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs new file mode 100644 index 00000000..ead63de4 --- /dev/null +++ b/src/custom_functions/triangular_moving_average.rs @@ -0,0 +1,67 @@ +use crate::{Error, Value}; + +pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + let length = columns.len(); + let mut sum: Value = Value::Float(0f64); // Starting sum value + let mut sum_weight: usize = 0; + let half_length = (length / 2) + 1; + for i in (0..=half_length).rev() { + let weight = &half_length + 1; + sum = (&sum + &row[columns[i]])?; // Add current value + sum_weight += weight; // Update weight sum + + let mut k = half_length.clone(); + for j in 1..=half_length { + if i + j < columns.len() { + sum = (&sum + &row[columns[i + j]])?; // Add value with weight + sum_weight += k; + } + if i as isize - j as isize >= 0 { + sum = (&sum + &row[columns[i - j]])?; // Add value with weight + sum_weight += k; + } + k -= 1; + } + } + sum / Value::Float(sum_weight as f64) +} + + +#[cfg(test)] +mod tests { + use std::process::id; + use super::*; + + #[test] + fn test_triangular_moving_average_normal_operation() { + + let row = (0..110).map(|idx| Value::Float(idx as f64)).collect::>(); // Simple case with enough columns + let columns = (0..110).collect::>(); // Simple case with enough columns + let result = triangular_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"), + } + } + + + #[test] + fn test_triangular_moving_average_empty_input() { + let row: Vec = vec![]; + 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 + } + + // Add more tests as needed, especially to cover the error cases your implementation might have +} diff --git a/src/lib.rs b/src/lib.rs index a6341006..81cfac49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,16 +533,17 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, ThinTraitContext,BoxedThinTraitContext, + Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, ThinTraitContext, BoxedThinTraitContext, HashMapContext, }, error::{EvalexprError, EvalexprResult}, function::Function, interface::*, + custom_functions::*, operator::Operator, token::PartialToken, tree::Node, - value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult,to_ffi_result, Error}, + value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult, to_ffi_result, Error}, }; mod context; @@ -554,13 +555,7 @@ mod interface; mod operator; mod token; mod tree; +mod custom_functions; mod value; -pub fn is_null(value: &Value) -> &Value { - match value { - Value::Empty => &Value::Int(0), - _ => value, - } -} - // Exports From 4c90f23b97294d466cf175ba13a1449cfaf72890 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:20:43 +0000 Subject: [PATCH 025/265] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2f4ec884..41550c44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.13" +version = "7.2.14" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From 8c6149bce28e0e88ba66afe8030a6daec530f99c Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:34:40 +0000 Subject: [PATCH 026/265] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 41550c44..e7fe74db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.14" +version = "7.2.15" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From ce2c51070c9f9e347b0eccc7357a3ac8eb81bc5b Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 15:38:39 +0000 Subject: [PATCH 027/265] Bump version --- Cargo.toml | 2 +- src/custom_functions/mod.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e7fe74db..d4714a98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.15" +version = "7.2.16" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 6f1e4dec..cbd7b5bf 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -2,3 +2,10 @@ pub mod triangular_moving_average; pub mod is_null; pub mod simple_moving_average; pub mod simple_cumulative_sum; + + +// Re-export the functions to the root of the crate +pub use triangular_moving_average::triangular_moving_average; +pub use is_null::is_null; +pub use simple_moving_average::simple_moving_average; +pub use simple_cumulative_sum::simple_cumulative_sum; From 0bcd610882eaf0f3ae288283e2066255df130833 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:13:19 +0000 Subject: [PATCH 028/265] Improve calcs --- Cargo.toml | 2 +- src/custom_functions/simple_moving_average.rs | 51 ++++++++++++------- .../triangular_moving_average.rs | 45 +++++++++------- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4714a98..735aa30c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.16" +version = "7.2.17" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/simple_moving_average.rs b/src/custom_functions/simple_moving_average.rs index 284298d4..bd13ffab 100644 --- a/src/custom_functions/simple_moving_average.rs +++ b/src/custom_functions/simple_moving_average.rs @@ -4,30 +4,22 @@ pub fn simple_moving_average(row: &[Value], columns: &[usize]) -> Result { - if let Value::Float(sum_val) = sum { - sum = Value::Float(sum_val + val); - count += 1; - } - } - _ => continue, - } - } + let (sum, count) = columns.iter() + .filter_map(|&col_index| match row.get(col_index) { + Some(Value::Float(val)) => Some(val), + _ => None, + }) + .fold((0.0f64, 0usize), |(acc_sum, acc_count), &val| (acc_sum + val, acc_count + 1)); if count > 0 { - if let Value::Float(sum_val) = sum { - return Ok(Value::Float(sum_val / count as f64)); - } + Ok(Value::Float(sum / count as f64)) + } else { + Ok(Value::Empty) } - - Ok(Value::Empty) } + #[cfg(test)] mod tests { use super::*; @@ -63,4 +55,27 @@ mod tests { 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 row = (0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(); // 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/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index ead63de4..69860e78 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -1,45 +1,53 @@ use crate::{Error, Value}; +use crate::Error::UnsupportedOperation; pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { if columns.is_empty() { return Ok(Value::Empty); } - let length = columns.len(); - let mut sum: Value = Value::Float(0f64); // Starting sum value + + let mut sum: f64 = 0.0; // Use primitive type for intermediate summation let mut sum_weight: usize = 0; + let length = columns.len(); let half_length = (length / 2) + 1; - for i in (0..=half_length).rev() { - let weight = &half_length + 1; - sum = (&sum + &row[columns[i]])?; // Add current value - sum_weight += weight; // Update weight sum + for i in 0..=half_length { + if let Value::Float(val) = row[columns[i]] { + let weight = half_length + 1 - i; // Adjust weight based on distance from center + sum += val * weight as f64; + sum_weight += weight; - let mut k = half_length.clone(); - for j in 1..=half_length { - if i + j < columns.len() { - sum = (&sum + &row[columns[i + j]])?; // Add value with weight - sum_weight += k; + // Handle symmetric counterpart if it exists + if i != 0 && i < half_length { // Check to avoid double-counting the middle for odd lengths + if let Value::Float(sym_val) = row[columns[length - 1 - i]] { + sum += sym_val * weight as f64; // Same weight as its symmetric counterpart + sum_weight += weight; + } } - if i as isize - j as isize >= 0 { - sum = (&sum + &row[columns[i - j]])?; // Add value with weight - sum_weight += k; - } - k -= 1; + }else{ + return Err(UnsupportedOperation) } } - sum / Value::Float(sum_weight as f64) + if sum_weight > 0 { + Ok(Value::Float(sum / sum_weight as f64)) + } else { + Ok(Value::Empty) // Handle case where no valid data was added + } } + #[cfg(test)] mod tests { use std::process::id; use super::*; + //Time: 4.481µs #[test] fn test_triangular_moving_average_normal_operation() { - let row = (0..110).map(|idx| Value::Float(idx as f64)).collect::>(); // Simple case with enough columns + let row = (0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(); // Simple case with enough columns let columns = (0..110).collect::>(); // Simple case with enough columns + let start = std::time::Instant::now(); let result = triangular_moving_average(&row, &columns); assert!(result.is_ok()); let value = result.unwrap(); @@ -51,6 +59,7 @@ mod tests { }, _ => panic!("Expected Value::Float from TMA calculation"), } + println!("Time: {:?}", start.elapsed()); } From ff58957007cdf7189ab282cbb7d54b13be7687a8 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sun, 25 Feb 2024 09:05:18 +0000 Subject: [PATCH 029/265] Improve calcs --- src/custom_functions/simple_cumulative_sum.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/custom_functions/simple_cumulative_sum.rs b/src/custom_functions/simple_cumulative_sum.rs index dc53e868..dc33d035 100644 --- a/src/custom_functions/simple_cumulative_sum.rs +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -4,22 +4,23 @@ pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result { - if let Value::Float(sum_val) = sum { - sum = Value::Float(sum_val + val); - } - } - _ => continue, + // 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(Value::Float(val)) = row.get(col_index) { + sum += val; } } - Ok(sum) + // Only wrap the final sum into Value::Float here. + Ok(Value::Float(sum)) } + #[cfg(test)] mod tests { use super::*; From ce8021cddf0f6873e923ef8b09586484016f66f0 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sun, 25 Feb 2024 10:07:30 +0000 Subject: [PATCH 030/265] change calcs to cater for ints --- Cargo.toml | 2 +- src/custom_functions/simple_cumulative_sum.rs | 5 +-- src/custom_functions/simple_moving_average.rs | 5 ++- .../triangular_moving_average.rs | 41 ++++++++++++------- src/value/mod.rs | 2 + 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 735aa30c..612a430d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.17" +version = "7.2.19" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/simple_cumulative_sum.rs b/src/custom_functions/simple_cumulative_sum.rs index dc33d035..e68697d0 100644 --- a/src/custom_functions/simple_cumulative_sum.rs +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -6,13 +6,12 @@ pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result Result Some(val), + 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)); + .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)) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 69860e78..0d3dc45b 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -10,23 +10,36 @@ pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { + match $val { + Value::Float(val) => { + *$sum += *val as f64 * $weight as f64; + }, + Value::Int(val) => { + *$sum += *val as f64 * $weight as f64; + }, + + _ => {} // Optionally handle other variants + } + *$sum_weight += $weight; + + }; + } for i in 0..=half_length { - if let Value::Float(val) = row[columns[i]] { - let weight = half_length + 1 - i; // Adjust weight based on distance from center - sum += val * weight as f64; - sum_weight += weight; + let weight = half_length + 1 - i; // Adjust weight based on distance from center + + if let Some(val) = row.get(columns[i]) { + process_value!(val, weight, &mut sum, &mut sum_weight); + } - // Handle symmetric counterpart if it exists - if i != 0 && i < half_length { // Check to avoid double-counting the middle for odd lengths - if let Value::Float(sym_val) = row[columns[length - 1 - i]] { - sum += sym_val * weight as f64; // Same weight as its symmetric counterpart - sum_weight += weight; - } + if i != 0 && i < half_length { // Check to avoid double-counting the middle for odd lengths + if let Some(sym_val) = row.get(columns[length - 1 - i]) { + process_value!(sym_val, weight, &mut sum, &mut sum_weight); } - }else{ - return Err(UnsupportedOperation) } } + if sum_weight > 0 { Ok(Value::Float(sum / sum_weight as f64)) } else { @@ -35,7 +48,6 @@ pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result>(); // Simple case with enough columns let columns = (0..110).collect::>(); // Simple case with enough columns let start = std::time::Instant::now(); @@ -56,7 +67,7 @@ mod tests { // 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/value/mod.rs b/src/value/mod.rs index 3c7628b4..c97e550c 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -208,6 +208,7 @@ use std::ops::Mul; pub enum Error { UnsupportedOperation, DivisionByZero, + NonNumericType, } pub trait ToErrorType { @@ -219,6 +220,7 @@ impl ToErrorType for Error { match self { Error::UnsupportedOperation => 1, Error::DivisionByZero => 2, + Error::NonNumericType => 3, } } } From c891887672580cb33ae1abb10999caf7892bc7a4 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sun, 25 Feb 2024 11:20:58 +0000 Subject: [PATCH 031/265] change calcs to cater for ints --- Cargo.toml | 2 +- src/custom_functions/simple_cumulative_sum.rs | 13 ++++++++++++- src/value/mod.rs | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 612a430d..a86cd9c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.19" +version = "7.2.20" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/simple_cumulative_sum.rs b/src/custom_functions/simple_cumulative_sum.rs index e68697d0..6131723d 100644 --- a/src/custom_functions/simple_cumulative_sum.rs +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -11,7 +11,18 @@ pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result { + sum += val; + continue; + } + Value::Int(val) => { + sum += *val as f64; + continue; + } + Value::Empty => {} + _ => return Err(Error::NonNumericType), + } } } diff --git a/src/value/mod.rs b/src/value/mod.rs index c97e550c..45cc97cb 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -215,6 +215,17 @@ pub trait ToErrorType { fn to_error_code(&self) -> i32; } +impl Error{ + pub fn from_error_code(code: i32) -> Self { + match code { + 1 => Error::UnsupportedOperation, + 2 => Error::DivisionByZero, + 3 => Error::NonNumericType, + _ => Error::UnsupportedOperation, + } + } +} + impl ToErrorType for Error { fn to_error_code(&self) -> i32 { match self { From fd8136ff3cf9091513f7d1b51cfbe6cbb923368b Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sun, 25 Feb 2024 13:29:48 +0000 Subject: [PATCH 032/265] Gaurd against two value --- Cargo.toml | 2 +- src/custom_functions/triangular_moving_average.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a86cd9c3..4f19a0cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.20" +version = "7.2.21" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 0d3dc45b..184df630 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,7 +2,7 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { - if columns.is_empty() { + if columns.is_empty() || columns.len() <=2 { return Ok(Value::Empty); } @@ -57,7 +57,7 @@ mod tests { #[test] fn test_triangular_moving_average_normal_operation() { let row = (0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(); // Simple case with enough columns - let columns = (0..110).collect::>(); // 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(&row, &columns); assert!(result.is_ok()); From f95d6bc22e25c1d6e58aa7ded3c5fc49a6e7a588 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Mon, 26 Feb 2024 09:43:57 +0000 Subject: [PATCH 033/265] Gaurd against two value --- Cargo.toml | 2 +- src/custom_functions/back.rs | 9 ++++++++ src/custom_functions/is_null.rs | 15 +++++++++++++ src/custom_functions/mod.rs | 4 +++- src/value/mod.rs | 38 ++++++++++++++++++++++++++++++++- 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/custom_functions/back.rs diff --git a/Cargo.toml b/Cargo.toml index 4f19a0cb..5d6aff71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.21" +version = "7.2.22" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/back.rs b/src/custom_functions/back.rs new file mode 100644 index 00000000..3184092b --- /dev/null +++ b/src/custom_functions/back.rs @@ -0,0 +1,9 @@ +use crate::{Error, Value}; + +pub fn back(row: &[Value], columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + Ok(row[columns[0]].clone()) +} + diff --git a/src/custom_functions/is_null.rs b/src/custom_functions/is_null.rs index 90873331..205cf297 100644 --- a/src/custom_functions/is_null.rs +++ b/src/custom_functions/is_null.rs @@ -6,3 +6,18 @@ pub fn is_null(value: &Value) -> &Value { _ => value, } } +pub fn max<'a>(value1: &'a Value, value2: &'a Value) -> &'a Value { + if value1 > value2 { + value1 + } else { + value2 + } +} + +pub fn min<'a>(value1: &'a Value, value2: &'a Value) -> &'a Value { + if value1 < value2 { + value1 + } else { + value2 + } +} diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index cbd7b5bf..6b0896f9 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -2,10 +2,12 @@ pub mod triangular_moving_average; pub mod is_null; pub mod simple_moving_average; pub mod simple_cumulative_sum; +pub mod back; // Re-export the functions to the root of the crate pub use triangular_moving_average::triangular_moving_average; -pub use is_null::is_null; +pub use is_null::*; +pub use back::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; diff --git a/src/value/mod.rs b/src/value/mod.rs index 45cc97cb..c3ec6f85 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use crate::error::{EvalexprError, EvalexprResult}; mod display; @@ -20,7 +21,7 @@ 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 { @@ -38,6 +39,41 @@ pub enum Value { Empty, } + +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)) => a.partial_cmp(b), + (Value::Int(a), Value::Int(b)) => a.partial_cmp(b), + (Value::Float(a), Value::Int(b)) => (*a).partial_cmp(&(*b as FloatType)), + (Value::Int(a), Value::Float(b)) => (*a as FloatType).partial_cmp(b), + (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), + // 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)) => a == 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, + _ => false, + } + } +} + impl Default for Value { fn default() -> Self { Value::Empty From 1c7abd30629cf5437bfbea6d4e84e0000e8c43f3 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:40:39 +0000 Subject: [PATCH 034/265] Gaurd against two value --- Cargo.toml | 2 +- src/value/mod.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5d6aff71..3ed4b186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.22" +version = "7.2.23" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index c3ec6f85..2f617554 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -313,6 +313,7 @@ impl Add for &Value { fn add(self, other: Self) -> Self::Output { match (self, other) { (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, b))), From 08d314b6123ecc411e0fda7b9ec40ca35386f41a Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:53:26 +0000 Subject: [PATCH 035/265] Gaurd against two value --- Cargo.toml | 2 +- src/value/mod.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ed4b186..66a6e9f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.23" +version = "7.2.24" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index 2f617554..d3a91e4b 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -242,6 +242,7 @@ use std::ops::Mul; #[derive(Debug, Clone)] pub enum Error { + UnsupportedArithmeticBetweenTypes, UnsupportedOperation, DivisionByZero, NonNumericType, @@ -257,6 +258,7 @@ impl Error{ 1 => Error::UnsupportedOperation, 2 => Error::DivisionByZero, 3 => Error::NonNumericType, + 4 => Error::UnsupportedArithmeticBetweenTypes, _ => Error::UnsupportedOperation, } } @@ -268,6 +270,7 @@ impl ToErrorType for Error { Error::UnsupportedOperation => 1, Error::DivisionByZero => 2, Error::NonNumericType => 3, + Error::UnsupportedArithmeticBetweenTypes => 4, } } } @@ -285,7 +288,7 @@ impl Neg for Value { match self { Value::Int(a) => Ok(Value::Int(-a)), Value::Float(a) => Ok(Value::Float(-a)), - _ => Err(Error::UnsupportedOperation), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), } } } @@ -302,7 +305,7 @@ impl Rem for &Value { (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::UnsupportedOperation), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), } } } @@ -321,7 +324,7 @@ impl Add for &Value { (Value::Int(a), Value::String(b)) | (Value::String(b), Value::Int(a)) => Ok(Value::String(format!("{}{}", a, b))), (Value::Float(a), Value::String(b)) | (Value::String(b), Value::Float(a)) => Ok(Value::String(format!("{}{}", a, b))), // Add cases for other Value variants as necessary - _ => Err(Error::UnsupportedOperation), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), } } } @@ -336,7 +339,7 @@ impl Sub for &Value { (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::UnsupportedOperation), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), } } } @@ -349,7 +352,7 @@ impl Mul for &Value { (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::UnsupportedOperation), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), } } } @@ -388,7 +391,7 @@ impl Div for &Value { } }, // Add cases for other combinations as needed, returning UnsupportedOperation for non-numeric types - _ => Err(Error::UnsupportedOperation), + _ => Err(Error::UnsupportedArithmeticBetweenTypes), } } } From 5124d271381f1423e005b1618f40a09845c26ed4 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:01:00 +0000 Subject: [PATCH 036/265] Check for empty values with the arithmetic --- Cargo.toml | 2 +- src/value/mod.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 66a6e9f4..73c8a0ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.24" +version = "7.2.25" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index d3a91e4b..582d14c1 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -286,6 +286,7 @@ impl Neg for Value { 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), @@ -301,6 +302,9 @@ impl Rem for &Value { 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)), @@ -315,6 +319,9 @@ impl Add for &Value { fn add(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::Int(b)) => Ok(Value::Float(*a + *b as f64)), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), @@ -335,6 +342,9 @@ impl Sub for &Value { 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(a - b)), (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 - b)), @@ -349,6 +359,9 @@ impl Mul for &Value { 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)), @@ -362,6 +375,9 @@ impl Div for &Value { 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) From 617af98eb9514e3a221b0dfd8376a263d8b61de6 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:37:37 +0000 Subject: [PATCH 037/265] Check for empty values with the arithmetic --- Cargo.toml | 2 +- src/value/mod.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 73c8a0ae..cff80bad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.25" +version = "7.2.26" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index 582d14c1..b98525dd 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -64,6 +64,8 @@ impl PartialEq for Value { match (self, other) { (Value::String(a), Value::String(b)) => a == b, (Value::Float(a), Value::Float(b)) => a == b, + (Value::Float(a), Value::Int(b)) => *a == *b as FloatType, + (Value::Int(a), Value::Float(b)) => *a as FloatType == *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 From 4ab88495f9c287b85c6caba361c0b7d97ddab76a Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:41:00 +0000 Subject: [PATCH 038/265] Check for empty values with the arithmetic --- Cargo.toml | 2 +- src/value/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cff80bad..59674e62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.26" +version = "7.2.27" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index b98525dd..cf4f66cf 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -71,7 +71,7 @@ impl PartialEq for Value { // 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, - _ => false, + (left,right) => panic!("Cannot compare {:?} and {:?}", left, right), } } } From 75028f2e3898d66da265690cc6558247ff565189 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:59:30 +0000 Subject: [PATCH 039/265] Check for empty values with the arithmetic --- Cargo.toml | 2 +- src/custom_functions/is_null.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 59674e62..d6a74082 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.27" +version = "7.2.28" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/is_null.rs b/src/custom_functions/is_null.rs index 205cf297..a017fedc 100644 --- a/src/custom_functions/is_null.rs +++ b/src/custom_functions/is_null.rs @@ -6,6 +6,16 @@ pub fn is_null(value: &Value) -> &Value { _ => value, } } + +pub fn starts_with(message: &Value, prefix: &Value) -> Value { + if let (Value::String(message), Value::String(prefix)) = (message, prefix) { + if message.starts_with(prefix) { + return Value::Boolean(true); + } + } + return Value::Boolean(false); +} + pub fn max<'a>(value1: &'a Value, value2: &'a Value) -> &'a Value { if value1 > value2 { value1 From 46e19cbb11b423ebbe471621566906accbbcc2a1 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:08:25 +0000 Subject: [PATCH 040/265] Check for empty values with the arithmetic --- Cargo.toml | 2 +- src/value/mod.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d6a74082..0429b3bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.28" +version = "7.2.29" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index cf4f66cf..448e2c09 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -71,6 +71,8 @@ impl PartialEq for Value { // 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), } } From 69c8fa036ae2e3d61fc2f92132cbacba0d86dfdb Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:35:38 +0000 Subject: [PATCH 041/265] Added more expression functions --- Cargo.toml | 2 +- .../{is_null.rs => expression_functions.rs} | 11 ++++++++++- src/custom_functions/mod.rs | 4 ++-- src/value/mod.rs | 3 +++ 4 files changed, 16 insertions(+), 4 deletions(-) rename src/custom_functions/{is_null.rs => expression_functions.rs} (69%) diff --git a/Cargo.toml b/Cargo.toml index 0429b3bc..6f2cf7dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.29" +version = "7.2.30" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/is_null.rs b/src/custom_functions/expression_functions.rs similarity index 69% rename from src/custom_functions/is_null.rs rename to src/custom_functions/expression_functions.rs index a017fedc..82fb77bd 100644 --- a/src/custom_functions/is_null.rs +++ b/src/custom_functions/expression_functions.rs @@ -1,4 +1,4 @@ -use crate::Value; +use crate::{Error, Value}; pub fn is_null(value: &Value) -> &Value { match value { @@ -7,6 +7,15 @@ pub fn is_null(value: &Value) -> &Value { } } +pub fn abs(value: &Value) -> Result { + match value { + 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 starts_with(message: &Value, prefix: &Value) -> Value { if let (Value::String(message), Value::String(prefix)) = (message, prefix) { if message.starts_with(prefix) { diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 6b0896f9..d2075226 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -1,5 +1,5 @@ pub mod triangular_moving_average; -pub mod is_null; +pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; @@ -7,7 +7,7 @@ pub mod back; // Re-export the functions to the root of the crate pub use triangular_moving_average::triangular_moving_average; -pub use is_null::*; +pub use expression_functions::*; pub use back::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; diff --git a/src/value/mod.rs b/src/value/mod.rs index 448e2c09..f3b960ab 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -250,6 +250,7 @@ pub enum Error { UnsupportedOperation, DivisionByZero, NonNumericType, + InvalidArgumentType, } pub trait ToErrorType { @@ -263,6 +264,7 @@ impl Error{ 2 => Error::DivisionByZero, 3 => Error::NonNumericType, 4 => Error::UnsupportedArithmeticBetweenTypes, + 5 => Error::InvalidArgumentType, _ => Error::UnsupportedOperation, } } @@ -275,6 +277,7 @@ impl ToErrorType for Error { Error::DivisionByZero => 2, Error::NonNumericType => 3, Error::UnsupportedArithmeticBetweenTypes => 4, + Error::InvalidArgumentType => 5, } } } From c57c81c3736529c0ab23507c3ba6279a1a1f8b5f Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:59:13 +0000 Subject: [PATCH 042/265] Added more expression functions --- Cargo.toml | 3 +- src/custom_functions/expression_functions.rs | 37 ++++++++++++++++++++ src/value/mod.rs | 23 +++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6f2cf7dd..42bf3df4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.30" +version = "7.2.31" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] @@ -27,6 +27,7 @@ regex = { version = "1.5.5", optional = true} serde = { version = "1.0.133", optional = true} serde_derive = { version = "1.0.133", optional = true} 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"] } [features] serde_support = ["serde", "serde_derive"] diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 82fb77bd..543e8b89 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -1,4 +1,5 @@ use crate::{Error, Value}; +use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; pub fn is_null(value: &Value) -> &Value { match value { @@ -25,6 +26,42 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Value { return Value::Boolean(false); } +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 + }) +} + +fn impl_round_date_to_precision(string: &Value, precision: &Value) -> Result { + if let (Value::String(string), Value::String(precision)) = (string, precision) { + // Extract the date-time part from the input string + 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 result = format!("{}_{}", parts.iter().take(parts.len() - 1).map(|prt|prt.to_string()).collect::>().join("_"), rounded_datetime.format("%Y.%m.%d %H:%M:%S").to_string()); + + Ok(Value::String(result)) + } else { + // If arguments are not strings, return an error + Err(Error::InvalidArgumentType) + } +} + pub fn max<'a>(value1: &'a Value, value2: &'a Value) -> &'a Value { if value1 > value2 { value1 diff --git a/src/value/mod.rs b/src/value/mod.rs index f3b960ab..f5716cc1 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -251,20 +251,27 @@ pub enum Error { DivisionByZero, NonNumericType, InvalidArgumentType, + InvalidInputString, + InvalidDateFormat, + CustomError(String), } pub trait ToErrorType { fn to_error_code(&self) -> i32; + fn to_error_message(&self) -> Option; } impl Error{ - pub fn from_error_code(code: i32) -> Self { + 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, } } @@ -278,6 +285,16 @@ impl ToErrorType for Error { 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, } } } @@ -427,6 +444,8 @@ pub struct FfiResult { pub value: T, /// An integer error code. 0 indicates success, non-zero indicates an error. pub error_code: i32, + + pub error_message: String, } /// Converts a Rust `Result` to an `FfiResult`, where `T: Default`. @@ -435,10 +454,12 @@ pub fn to_ffi_result(result: Result) -> FfiRes 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())), }, } } From 54b70f4a01cee664c728a91ca63d96e741aad5b9 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:41:38 +0000 Subject: [PATCH 043/265] Added more expression functions --- Cargo.toml | 2 +- src/custom_functions/expression_functions.rs | 59 ++++++++++++++++++++ src/value/mod.rs | 13 +++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 42bf3df4..fcbb43bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.31" +version = "7.2.32" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 543e8b89..407cb1de 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -77,3 +77,62 @@ pub fn min<'a>(value1: &'a Value, value2: &'a Value) -> &'a Value { value2 } } + + +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 = impl_round_date_to_precision(&input.0, &input.1).unwrap(); + assert_eq!(result, Value::String(format!("BTCUSD_{}", expected))); + } + + #[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 = impl_round_date_to_precision(&input.0, &input.1).unwrap(); + assert_eq!(result, Value::String(format!("BTCUSD_{}", expected))); + } + + #[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 = impl_round_date_to_precision(&input.0, &input.1).unwrap(); + assert_eq!(result, Value::String(format!("BTCUSD_{}", expected))); + } + + #[test] + fn test_invalid_date_format() { + let input = ( + Value::String("BTCUSD_ThisIsNotADate".into()), + Value::String("m1".into()) + ); + let result = impl_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 = impl_round_date_to_precision(&input.0, &input.1); + assert!(result.is_err(), "Expected an error for invalid precision"); + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs index f5716cc1..8dc03ff3 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -40,6 +40,19 @@ pub enum Value { } +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 PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { From b252e8782df823fa506854aa2ff38d9dede15d8f Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:06:37 +0000 Subject: [PATCH 044/265] Added more expression functions --- src/custom_functions/expression_functions.rs | 24 +++++++++---------- .../triangular_moving_average.rs | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 407cb1de..e5b89f3f 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -1,11 +1,11 @@ use crate::{Error, Value}; use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; -pub fn is_null(value: &Value) -> &Value { - match value { +pub fn is_null(value: &Value) -> Result<&Value, Error> { + Ok(match value { Value::Empty => &Value::Int(0), _ => value, - } + }) } pub fn abs(value: &Value) -> Result { @@ -17,13 +17,13 @@ pub fn abs(value: &Value) -> Result { } } -pub fn starts_with(message: &Value, prefix: &Value) -> Value { +pub fn starts_with(message: &Value, prefix: &Value) -> Result { if let (Value::String(message), Value::String(prefix)) = (message, prefix) { if message.starts_with(prefix) { - return Value::Boolean(true); + return Ok(Value::Boolean(true)); } } - return Value::Boolean(false); + return Ok(Value::Boolean(false)); } fn round_datetime_to_precision(datetime: DateTime, precision: &str) -> Result, crate::Error> { @@ -62,20 +62,20 @@ fn impl_round_date_to_precision(string: &Value, precision: &Value) -> Result(value1: &'a Value, value2: &'a Value) -> &'a Value { - if value1 > value2 { +pub fn max<'a>(value1: &'a Value, value2: &'a Value) -> Result<&'a Value, Error> { + Ok(if value1 > value2 { value1 } else { value2 - } + }) } -pub fn min<'a>(value1: &'a Value, value2: &'a Value) -> &'a Value { - if value1 < value2 { +pub fn min<'a>(value1: &'a Value, value2: &'a Value) -> Result<&'a Value, Error> { + Ok(if value1 < value2 { value1 } else { value2 - } + }) } diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 184df630..b2dcbfb7 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -66,6 +66,7 @@ mod tests { 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"), From 4d7acdf0f212a49d9b88ee1851bf18dd39f8e461 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:07:32 +0000 Subject: [PATCH 045/265] Added more expression functions --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fcbb43bd..41c7c66d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.32" +version = "7.2.33" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From 04db843e5b287c6289ef4643936e7fc9e9bcbbb1 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:03:31 +0000 Subject: [PATCH 046/265] Added more expression functions --- Cargo.toml | 2 +- src/custom_functions/expression_functions.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41c7c66d..1576ac15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.33" +version = "7.2.34" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index e5b89f3f..4e9344ef 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -43,7 +43,7 @@ fn round_datetime_to_precision(datetime: DateTime, precision: &str) -> Resu }) } -fn impl_round_date_to_precision(string: &Value, precision: &Value) -> Result { +fn round_date_to_precision(string: &Value, precision: &Value) -> Result { if let (Value::String(string), Value::String(precision)) = (string, precision) { // Extract the date-time part from the input string let parts: Vec<&str> = string.split('_').collect(); @@ -89,7 +89,7 @@ mod test{ 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 = impl_round_date_to_precision(&input.0, &input.1).unwrap(); + let result = round_date_to_precision(&input.0, &input.1).unwrap(); assert_eq!(result, Value::String(format!("BTCUSD_{}", expected))); } @@ -100,7 +100,7 @@ mod test{ 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 = impl_round_date_to_precision(&input.0, &input.1).unwrap(); + let result = round_date_to_precision(&input.0, &input.1).unwrap(); assert_eq!(result, Value::String(format!("BTCUSD_{}", expected))); } @@ -112,7 +112,7 @@ mod test{ ); // 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 = impl_round_date_to_precision(&input.0, &input.1).unwrap(); + let result = round_date_to_precision(&input.0, &input.1).unwrap(); assert_eq!(result, Value::String(format!("BTCUSD_{}", expected))); } @@ -122,7 +122,7 @@ mod test{ Value::String("BTCUSD_ThisIsNotADate".into()), Value::String("m1".into()) ); - let result = impl_round_date_to_precision(&input.0, &input.1); + let result = round_date_to_precision(&input.0, &input.1); assert!(result.is_err()); } @@ -132,7 +132,7 @@ mod test{ Value::String("BTCUSD_2024.02.13 10:05:00".into()), Value::String("m60".into()) ); - let result = impl_round_date_to_precision(&input.0, &input.1); + let result = round_date_to_precision(&input.0, &input.1); assert!(result.is_err(), "Expected an error for invalid precision"); } } From 436e054b4955a22a705878c57d8422efea5244a8 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:18:35 +0000 Subject: [PATCH 047/265] Added more expression functions --- Cargo.toml | 2 +- src/custom_functions/expression_functions.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1576ac15..fe13c9d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.34" +version = "7.2.36" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 4e9344ef..f9d58818 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -43,7 +43,7 @@ fn round_datetime_to_precision(datetime: DateTime, precision: &str) -> Resu }) } -fn round_date_to_precision(string: &Value, precision: &Value) -> Result { +pub fn round_date_to_precision(string: &Value, precision: &Value) -> Result { if let (Value::String(string), Value::String(precision)) = (string, precision) { // Extract the date-time part from the input string let parts: Vec<&str> = string.split('_').collect(); From e3048ed24d78de339f3bfaa95c3463fb08954a10 Mon Sep 17 00:00:00 2001 From: gbemiga-viewserver <98267419+gbemiga-viewserver@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:49:44 +0000 Subject: [PATCH 048/265] Fixed triangular moving average --- Cargo.toml | 2 +- .../triangular_moving_average.rs | 123 +++++++++++++----- 2 files changed, 93 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe13c9d6..e68a20e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.36" +version = "7.2.37" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index b2dcbfb7..4130eb2c 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,51 +2,57 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { - if columns.is_empty() || columns.len() <=2 { + if columns.len() <= 2 { return Ok(Value::Empty); } - let mut sum: f64 = 0.0; // Use primitive type for intermediate summation - let mut sum_weight: usize = 0; - let length = columns.len(); - let half_length = (length / 2) + 1; - macro_rules! process_value { - ($val:expr, $weight:expr, $sum:expr, $sum_weight:expr) => { - match $val { - Value::Float(val) => { - *$sum += *val as f64 * $weight as f64; - }, - Value::Int(val) => { - *$sum += *val as f64 * $weight as f64; - }, - - _ => {} // Optionally handle other variants - } - *$sum_weight += $weight; + let mut total_sum = 0.0; + let mut total_weight = 0; + let half_length = columns.len() / 2; - }; - } - for i in 0..=half_length { - let weight = half_length + 1 - i; // Adjust weight based on distance from center + for i in half_length..columns.len() { + let mut sum: f64 = 0.0; + let mut sum_weight: usize = half_length + 1; + let mut k = half_length; - if let Some(val) = row.get(columns[i]) { - process_value!(val, weight, &mut sum, &mut sum_weight); - } + if let Some(price_i) = get_price(row, columns, i) { + sum += price_i * sum_weight as f64; // Price(i) * (HalfLength + 1) - if i != 0 && i < half_length { // Check to avoid double-counting the middle for odd lengths - if let Some(sym_val) = row.get(columns[length - 1 - i]) { - process_value!(sym_val, weight, &mut sum, &mut sum_weight); + for j in 1..=half_length { + if i + j < columns.len() { + if let Some(price) = get_price(row, columns, i + j) { + sum += price * k as f64; // Price(i + j) + sum_weight += k; + } + } + if j <= i && i >= j { + if let Some(price) = get_price(row, columns, i - j) { + sum += price * k as f64; // Price(i - j) + sum_weight += k; + } + } + k -= 1; } } + + total_sum += sum; + total_weight += sum_weight; } - if sum_weight > 0 { - Ok(Value::Float(sum / sum_weight as f64)) + if total_weight > 0 { + Ok(Value::Float(total_sum / total_weight as f64)) } else { - Ok(Value::Empty) // Handle case where no valid data was added + Ok(Value::Empty) } } +fn get_price(row: &[Value], columns: &[usize], index: usize) -> Option { + row.get(columns[index]).and_then(|value| match value { + Value::Float(val) => Some(*val), + Value::Int(val) => Some(*val as f64), + _ => None, + }) +} #[cfg(test)] mod tests { @@ -84,5 +90,60 @@ mod tests { assert_eq!(result.unwrap(), Value::Empty); // Assuming Value::Empty for empty input } + + #[test] + fn test_triangular_moving_average_empty() { + let row = vec![]; + 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 row = vec![Value::Int(10), Value::Int(20), Value::Int(30), Value::Int(40), Value::Int(50)]; + 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 row = vec![ + Value::Float(10.5), + Value::Float(20.5), + Value::Float(30.5), + Value::Float(40.5), + Value::Float(50.5) + ]; + 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 row = vec![Value::Empty, Value::Int(20), Value::Empty, Value::Int(40), Value::Empty]; + 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 } From 46d5561f111b56b31c5ca8a2cad8e5b5afacacda Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 4 Apr 2024 10:19:05 +0100 Subject: [PATCH 049/265] Changed thin trait context to allow setting of values --- src/context/mod.rs | 4 +- src/custom_functions/calc_conviction.rs | 216 ++++++++++++++++++++++++ src/custom_functions/mod.rs | 1 + 3 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 src/custom_functions/calc_conviction.rs diff --git a/src/context/mod.rs b/src/context/mod.rs index 31d8109b..c3a5fe94 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -126,11 +126,9 @@ pub trait Context { #[thin_trait_object] pub trait ThinTraitContext { - fn get_value(&self, identifier: &str) -> Option; - + fn set_value(&self, identifier: &str, value: Value) -> EvalexprResult<()>; fn set_row(&self, row: usize); - fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; } diff --git a/src/custom_functions/calc_conviction.rs b/src/custom_functions/calc_conviction.rs new file mode 100644 index 00000000..21e6cdd0 --- /dev/null +++ b/src/custom_functions/calc_conviction.rs @@ -0,0 +1,216 @@ +// use crate::{BoxedThinTraitContext, Error, FloatType, IntType, TupleType, Value}; +// +// +// struct CursorCommon{ +// position: Option, +// length: usize, +// +// } +// trait StatefulCursor<'a>{ +// fn common(&self) -> &'a CursorCommon; +// fn common_mut(&mut self) -> &'a mut CursorCommon; +// +// fn initialize(&mut self, context: Box) -> Result<(), Error>{ +// self.common_mut().length = length; +// } +// fn next(&mut self) -> Option{ +// let new_position = self.common().position.unwrap_or(0) + 1; +// if new_position < self.common().length { +// self.common_mut().position = Some(new_position); +// Some(new_position) +// } else { +// None +// } +// } +// +// fn commit(&mut self) -> Result<(), Error>{ +// loop { +// if let Some(position) = self.next() { +// continue; +// }else{ +// break; +// } +// } +// self.common_mut().position = None; +// Ok(()) +// } +// fn read(&self, field: &str, index: usize) -> Result; +// fn write(&self, field: &str, value: &Value,index: usize) -> Result<(), Error>; +// } +// +// struct TradeInitiationCursor<'a> { +// position: Option, +// prices_since_inception: &'a [Value], +// tmac_lower_band: &'a [Value], +// } +// +// impl StatefulCursor for TradeInitiationCursor{ +// fn next(&self) -> Option { +// let new_position = self.position.unwrap_or(0) + 1; +// if new_position < self.prices_since_inception.len() { +// Some(new_position) +// } else { +// None +// } +// } +// +// fn read(&self, field: &str, index: usize) -> Result { +// todo!() +// } +// +// fn write(&self, field: &str, value: &Value, index: usize) -> Result<(), Error> { +// todo!() +// } +// } +// +// +// impl ActiveTrade<'_> { +// fn new(prices_since_inception: &[f64]) -> ActiveTrade { +// ActiveTrade { +// prices_since_inception, +// } +// } +// +// fn should_close(&self) -> Result<(bool, String), Error> { +// let current_price = self.prices_since_inception.first().ok_or(Error::CustomError("Not enough values to retrieve the current price of active trade".to_owned()))?; +// let entry_price = self.prices_since_inception.last().ok_or(Error::CustomError("Not enough values to retrieve the entry price of active trade".to_owned()))?; +// let mut stop_loss = *entry_price - 0.0002; // 2 pips below entry +// let take_profit = *entry_price + 0.0003; // 3 pips above entry +// +// if *current_price <= stop_loss { +// // Close trade if price falls 2 pips below entry +// return Ok((true, format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_price, entry_price))); +// } else if *current_price >= take_profit { +// // Take profit if price rises 3 pips above entry +// return Ok((true, format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_price, entry_price))); +// } else if *current_price >= *entry_price + 0.0001 { +// // Adjust stop loss to break-even if price rises by at least 1 pip +// stop_loss = *entry_price; +// if *current_price <= stop_loss { +// // Close trade if after adjustment, current price falls to stop loss +// return Ok((true, format!("Closing trade. After adjusting stop loss to break-even, current price ({}) has reached stop loss level from entry price ({}).", current_price, entry_price))); +// } +// } +// +// // If none of the conditions to close the trade are met +// Ok((false, Default::default())) +// } +// } +// +// +// fn generate_trades<'a>(prices: &'a[f64], signals: &'a[bool]) -> Result>,Error> { +// let mut trades: Vec = Vec::new(); +// let mut active_trade: Option = None; +// let mut trade_start_index: usize = 0; +// +// for (i, &signal) in signals.iter().enumerate() { +// if signal && active_trade.is_none() && i < prices.len() { +// // Start a new trade +// trade_start_index = i; +// active_trade = Some(ActiveTrade::new(&prices[i..])); +// } +// +// if let Some(trade) = &active_trade { +// let close_result = trade.should_close()?; +// if close_result.0 || i == prices.len() - 1 { +// // Close the active trade and push to trades list +// trades.push(ActiveTrade::new(&prices[trade_start_index..=i])); +// active_trade = None; // Ensure no overlapping trades +// } +// } +// } +// +// Ok(trades) +// } +// +// +// #[cfg(test)] +// mod tests { +// use super::*; +// +// #[test] +// fn test_trade_close_conditions() { +// let prices = [1.1050, 1.1048, 1.1051, 1.1054]; +// let trade = ActiveTrade::new(&prices); +// +// let (should_close, reason) = trade.should_close().unwrap(); +// assert!(should_close, "Trade should close due to reaching take profit."); +// assert_eq!(reason, "Closing trade. Current price (1.1054) has reached or exceeded take profit level from entry price (1.1050)."); +// } +// +// #[test] +// fn test_generate_trades_with_signals() { +// let prices = [1.1050, 1.1048, 1.1051, 1.1052, 1.1053, 1.1050, 1.1048, 1.1054]; +// let signals = [true, false, false, true, false, true, false, false]; +// let trades = generate_trades(&prices, &signals).unwrap(); +// +// // Expected to create trades based on the signals, but they should not overlap +// assert_eq!(trades.len(), 3, "Expected three trades based on the given signals."); +// +// // Ensure trades are initialized with the correct prices +// assert_eq!(*trades[0].prices_since_inception.last().unwrap(), 1.1050, "First trade entry price should be 1.1050."); +// assert_eq!(*trades[1].prices_since_inception.last().unwrap(), 1.1052, "Second trade entry price should be 1.1052."); +// assert_eq!(*trades[2].prices_since_inception.last().unwrap(), 1.1050, "Third trade entry price should be 1.1050."); +// } +// +// #[test] +// fn test_trade_non_closure_on_insufficient_movement() { +// let prices = [1.1050, 1.1051]; // Not enough movement to trigger any of the close conditions +// let trade = ActiveTrade::new(&prices); +// +// let (should_close, reason) = trade.should_close().unwrap(); +// assert!(!should_close, "Trade should not close due to insufficient price movement."); +// assert_eq!(reason, "", "There should be no reason for closing the trade."); +// } +// +// #[test] +// fn test_error_on_empty_price_array() { +// let prices = []; +// let trade = ActiveTrade::new(&prices); +// +// let result = trade.should_close(); +// assert!(result.is_err(), "Should return error due to empty price array."); +// if let Err(Error::CustomError(msg)) = result { +// assert_eq!(msg, "Not enough values to retrieve the current price of active trade"); +// } else { +// panic!("Expected CustomError for empty price array."); +// } +// } +// +// #[test] +// fn generate_trades_non_overlapping() { +// let prices = [1.1050, 1.1048, 1.1052, 1.1054, 1.1051, 1.1053, 1.1050]; +// let signals = [true, false, true, false, false, true, false]; +// let trades = generate_trades(&prices, &signals).expect("Failed to generate trades"); +// +// assert_eq!(trades.len(), 3, "Should generate 3 non-overlapping trades based on signals"); +// +// // Verify that each trade has correctly identified start and potential close conditions +// // Simplifying assumption: each trade closes on the next signal or the end of the array +// // Note: In real scenarios, you'd check for specific conditions met for closing each trade +// let expected_entry_prices = [1.1050, 1.1052, 1.1053]; +// for (i, trade) in trades.iter().enumerate() { +// assert_eq!(*trade.prices_since_inception.last().unwrap(), expected_entry_prices[i], "Entry price does not match expected"); +// } +// } +// +// #[test] +// fn generate_trades_with_no_signals() { +// let prices = [1.1050, 1.1051, 1.1052, 1.1053]; +// let signals = [false, false, false, false]; // No signals to generate trades +// let trades = generate_trades(&prices, &signals).expect("Failed to generate trades"); +// +// assert!(trades.is_empty(), "Should generate no trades without any signals"); +// } +// +// #[test] +// fn generate_trades_with_all_signals() { +// let prices = [1.1050, 1.1049, 1.1051, 1.1052]; +// let signals = [true, true, true, true]; // Every point is a signal +// let trades = generate_trades(&prices, &signals).expect("Failed to generate trades"); +// +// // Expecting to generate trades only for the first signal, as subsequent signals are ignored until the current trade is closed +// // Assuming that each trade must close before another begins +// assert_eq!(trades.len(), 1, "Should generate only one trade despite signals due to non-overlapping rule"); +// } +// } diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index d2075226..14b4a634 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -3,6 +3,7 @@ pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; +mod calc_conviction; // Re-export the functions to the root of the crate From e1f139dc3fa8f7dc3f0d5f02537c139288a46abe Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 09:59:19 +0100 Subject: [PATCH 050/265] Added compiled transpose calculation templates --- src/context/mod.rs | 2 +- src/custom_functions/calc_conviction.rs | 332 ++++++------------ .../compiled_transpose_calcuation_template.rs | 20 ++ src/custom_functions/mod.rs | 1 + src/value/mod.rs | 50 +++ src/value/value_type.rs | 1 + 6 files changed, 189 insertions(+), 217 deletions(-) create mode 100644 src/custom_functions/compiled_transpose_calcuation_template.rs diff --git a/src/context/mod.rs b/src/context/mod.rs index c3a5fe94..60fcad6a 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -126,7 +126,7 @@ pub trait Context { #[thin_trait_object] pub trait ThinTraitContext { - fn get_value(&self, identifier: &str) -> Option; + fn get_value(&self, identifier: &str) -> EvalexprResult; fn set_value(&self, identifier: &str, value: Value) -> EvalexprResult<()>; fn set_row(&self, row: usize); fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; diff --git a/src/custom_functions/calc_conviction.rs b/src/custom_functions/calc_conviction.rs index 21e6cdd0..16eff317 100644 --- a/src/custom_functions/calc_conviction.rs +++ b/src/custom_functions/calc_conviction.rs @@ -1,216 +1,116 @@ -// use crate::{BoxedThinTraitContext, Error, FloatType, IntType, TupleType, Value}; -// -// -// struct CursorCommon{ -// position: Option, -// length: usize, -// -// } -// trait StatefulCursor<'a>{ -// fn common(&self) -> &'a CursorCommon; -// fn common_mut(&mut self) -> &'a mut CursorCommon; -// -// fn initialize(&mut self, context: Box) -> Result<(), Error>{ -// self.common_mut().length = length; -// } -// fn next(&mut self) -> Option{ -// let new_position = self.common().position.unwrap_or(0) + 1; -// if new_position < self.common().length { -// self.common_mut().position = Some(new_position); -// Some(new_position) -// } else { -// None -// } -// } -// -// fn commit(&mut self) -> Result<(), Error>{ -// loop { -// if let Some(position) = self.next() { -// continue; -// }else{ -// break; -// } -// } -// self.common_mut().position = None; -// Ok(()) -// } -// fn read(&self, field: &str, index: usize) -> Result; -// fn write(&self, field: &str, value: &Value,index: usize) -> Result<(), Error>; -// } -// -// struct TradeInitiationCursor<'a> { -// position: Option, -// prices_since_inception: &'a [Value], -// tmac_lower_band: &'a [Value], -// } -// -// impl StatefulCursor for TradeInitiationCursor{ -// fn next(&self) -> Option { -// let new_position = self.position.unwrap_or(0) + 1; -// if new_position < self.prices_since_inception.len() { -// Some(new_position) -// } else { -// None -// } -// } -// -// fn read(&self, field: &str, index: usize) -> Result { -// todo!() -// } -// -// fn write(&self, field: &str, value: &Value, index: usize) -> Result<(), Error> { -// todo!() -// } -// } -// -// -// impl ActiveTrade<'_> { -// fn new(prices_since_inception: &[f64]) -> ActiveTrade { -// ActiveTrade { -// prices_since_inception, -// } -// } -// -// fn should_close(&self) -> Result<(bool, String), Error> { -// let current_price = self.prices_since_inception.first().ok_or(Error::CustomError("Not enough values to retrieve the current price of active trade".to_owned()))?; -// let entry_price = self.prices_since_inception.last().ok_or(Error::CustomError("Not enough values to retrieve the entry price of active trade".to_owned()))?; -// let mut stop_loss = *entry_price - 0.0002; // 2 pips below entry -// let take_profit = *entry_price + 0.0003; // 3 pips above entry -// -// if *current_price <= stop_loss { -// // Close trade if price falls 2 pips below entry -// return Ok((true, format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_price, entry_price))); -// } else if *current_price >= take_profit { -// // Take profit if price rises 3 pips above entry -// return Ok((true, format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_price, entry_price))); -// } else if *current_price >= *entry_price + 0.0001 { -// // Adjust stop loss to break-even if price rises by at least 1 pip -// stop_loss = *entry_price; -// if *current_price <= stop_loss { -// // Close trade if after adjustment, current price falls to stop loss -// return Ok((true, format!("Closing trade. After adjusting stop loss to break-even, current price ({}) has reached stop loss level from entry price ({}).", current_price, entry_price))); -// } -// } -// -// // If none of the conditions to close the trade are met -// Ok((false, Default::default())) -// } -// } -// -// -// fn generate_trades<'a>(prices: &'a[f64], signals: &'a[bool]) -> Result>,Error> { -// let mut trades: Vec = Vec::new(); -// let mut active_trade: Option = None; -// let mut trade_start_index: usize = 0; -// -// for (i, &signal) in signals.iter().enumerate() { -// if signal && active_trade.is_none() && i < prices.len() { -// // Start a new trade -// trade_start_index = i; -// active_trade = Some(ActiveTrade::new(&prices[i..])); -// } -// -// if let Some(trade) = &active_trade { -// let close_result = trade.should_close()?; -// if close_result.0 || i == prices.len() - 1 { -// // Close the active trade and push to trades list -// trades.push(ActiveTrade::new(&prices[trade_start_index..=i])); -// active_trade = None; // Ensure no overlapping trades -// } -// } -// } -// -// Ok(trades) -// } -// -// -// #[cfg(test)] -// mod tests { -// use super::*; -// -// #[test] -// fn test_trade_close_conditions() { -// let prices = [1.1050, 1.1048, 1.1051, 1.1054]; -// let trade = ActiveTrade::new(&prices); -// -// let (should_close, reason) = trade.should_close().unwrap(); -// assert!(should_close, "Trade should close due to reaching take profit."); -// assert_eq!(reason, "Closing trade. Current price (1.1054) has reached or exceeded take profit level from entry price (1.1050)."); -// } -// -// #[test] -// fn test_generate_trades_with_signals() { -// let prices = [1.1050, 1.1048, 1.1051, 1.1052, 1.1053, 1.1050, 1.1048, 1.1054]; -// let signals = [true, false, false, true, false, true, false, false]; -// let trades = generate_trades(&prices, &signals).unwrap(); -// -// // Expected to create trades based on the signals, but they should not overlap -// assert_eq!(trades.len(), 3, "Expected three trades based on the given signals."); -// -// // Ensure trades are initialized with the correct prices -// assert_eq!(*trades[0].prices_since_inception.last().unwrap(), 1.1050, "First trade entry price should be 1.1050."); -// assert_eq!(*trades[1].prices_since_inception.last().unwrap(), 1.1052, "Second trade entry price should be 1.1052."); -// assert_eq!(*trades[2].prices_since_inception.last().unwrap(), 1.1050, "Third trade entry price should be 1.1050."); -// } -// -// #[test] -// fn test_trade_non_closure_on_insufficient_movement() { -// let prices = [1.1050, 1.1051]; // Not enough movement to trigger any of the close conditions -// let trade = ActiveTrade::new(&prices); -// -// let (should_close, reason) = trade.should_close().unwrap(); -// assert!(!should_close, "Trade should not close due to insufficient price movement."); -// assert_eq!(reason, "", "There should be no reason for closing the trade."); -// } -// -// #[test] -// fn test_error_on_empty_price_array() { -// let prices = []; -// let trade = ActiveTrade::new(&prices); -// -// let result = trade.should_close(); -// assert!(result.is_err(), "Should return error due to empty price array."); -// if let Err(Error::CustomError(msg)) = result { -// assert_eq!(msg, "Not enough values to retrieve the current price of active trade"); -// } else { -// panic!("Expected CustomError for empty price array."); -// } -// } -// -// #[test] -// fn generate_trades_non_overlapping() { -// let prices = [1.1050, 1.1048, 1.1052, 1.1054, 1.1051, 1.1053, 1.1050]; -// let signals = [true, false, true, false, false, true, false]; -// let trades = generate_trades(&prices, &signals).expect("Failed to generate trades"); -// -// assert_eq!(trades.len(), 3, "Should generate 3 non-overlapping trades based on signals"); -// -// // Verify that each trade has correctly identified start and potential close conditions -// // Simplifying assumption: each trade closes on the next signal or the end of the array -// // Note: In real scenarios, you'd check for specific conditions met for closing each trade -// let expected_entry_prices = [1.1050, 1.1052, 1.1053]; -// for (i, trade) in trades.iter().enumerate() { -// assert_eq!(*trade.prices_since_inception.last().unwrap(), expected_entry_prices[i], "Entry price does not match expected"); -// } -// } -// -// #[test] -// fn generate_trades_with_no_signals() { -// let prices = [1.1050, 1.1051, 1.1052, 1.1053]; -// let signals = [false, false, false, false]; // No signals to generate trades -// let trades = generate_trades(&prices, &signals).expect("Failed to generate trades"); -// -// assert!(trades.is_empty(), "Should generate no trades without any signals"); -// } -// -// #[test] -// fn generate_trades_with_all_signals() { -// let prices = [1.1050, 1.1049, 1.1051, 1.1052]; -// let signals = [true, true, true, true]; // Every point is a signal -// let trades = generate_trades(&prices, &signals).expect("Failed to generate trades"); -// -// // Expecting to generate trades only for the first signal, as subsequent signals are ignored until the current trade is closed -// // Assuming that each trade must close before another begins -// assert_eq!(trades.len(), 1, "Should generate only one trade despite signals due to non-overlapping rule"); -// } -// } +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedThinTraitContext, Error, FloatType, ThinTraitContext, Value, ValueType}; +use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; + +struct BasherTradeModel { + +} + + +impl CompiledTransposeCalculationTemplate for BasherTradeModel { + fn schema(&self) -> HashMap { + vec![ + ("active_trade", ValueType::Boolean), + ("reason", ValueType::String), + ("initiation_price", ValueType::Float), + ("stop_loss", ValueType::Float), + ("take_profit", ValueType::Float), + ("break_even", ValueType::Float) + ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + } + + fn dependencies(&self) -> Vec { + vec!["signal".to_string(), "close".to_string()] + } + fn commit_row(self: &mut Box, row: &mut BoxedThinTraitContext, 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 stop_loss: Option = None; + let mut take_profit: Option = None; + let mut break_even: Option = None; + let mut reason: Option = None; + + if cycle_epoch > 1 { + 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()?; + initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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 > 2 { + let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; + prev_trade_signal = row.get_value(&generate_column_name("signal", 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 current_close_value = row.get_value(&generate_column_name("close", transpose_value))?.as_float()?; + let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + let loop_active_trade = active_trade.is_some_and(|tv|tv); + + 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")?; + + if current_close_value <= loop_stop_loss { + active_trade = Some(false); + initiation_price = None; + stop_loss = None; + take_profit = None; + break_even = None; + reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); + } + else if current_close_value >= loop_take_profit { + active_trade = Some(false); + initiation_price = None; + stop_loss = None; + take_profit = None; + break_even = None; + reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); + } + else if current_close_value > loop_break_even { + stop_loss = Some(loop_initiation_price + 0.0001); + } + + }else{ + let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + if loop_trade_signal { + if !prev_trade_signal.unwrap_or_default() { + initiation_price = Some(current_close_value); + stop_loss = Some(current_close_value - 0.0002); + take_profit = Some(current_close_value + 0.0003); + break_even = Some(current_close_value + 0.0001); + active_trade = Some(true); + } + } + } + + 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs|Value::Float(rs)).unwrap_or(Value::Empty))?; + prev_trade_signal = Some(current_signal); + reason = None; + } + Ok(()) + } +} + +fn generate_column_name(field: &str, p1: &Value) -> String { + format!("{}_{}", field, p1) +} diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs new file mode 100644 index 00000000..64117d0d --- /dev/null +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -0,0 +1,20 @@ +use std::collections::HashMap; +use std::fmt::Display; +use crate::{BoxedThinTraitContext, Error, Value, ValueType}; +use crate::Error::CustomError; + +pub trait CompiledTransposeCalculationTemplate { + fn schema(&self) -> HashMap; + fn dependencies(&self) -> Vec; + fn commit_row(self: &mut Box, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; +} + +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))), + } +} \ No newline at end of file diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 14b4a634..52dbf57e 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -4,6 +4,7 @@ pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; mod calc_conviction; +mod compiled_transpose_calcuation_template; // Re-export the functions to the root of the crate diff --git a/src/value/mod.rs b/src/value/mod.rs index 8dc03ff3..ed09f9fe 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -139,6 +139,14 @@ impl Value { 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.clone())), + Value::Empty => Ok(None), + value => Err(EvalexprError::expected_string(value.clone())), + } + } /// Clones the value stored in `self` as `IntType`, or returns `Err` if `self` is not a `Value::Int`. pub fn as_int(&self) -> EvalexprResult { @@ -148,6 +156,15 @@ 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 { @@ -155,6 +172,14 @@ impl Value { 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::Empty => Ok(None), + 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` or `Value::Int`. /// Note that this method silently converts `IntType` to `FloatType`, if `self` is a `Value::Int`. @@ -166,6 +191,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 { @@ -173,6 +209,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 { @@ -269,6 +313,12 @@ pub enum Error { CustomError(String), } +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; diff --git a/src/value/value_type.rs b/src/value/value_type.rs index 4ea7395f..1fa3bf29 100644 --- a/src/value/value_type.rs +++ b/src/value/value_type.rs @@ -2,6 +2,7 @@ use crate::Value; /// The type of a `Value`. #[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[repr(C)] pub enum ValueType { /// The `Value::String` type. String, From fa57ca1a097bed0345a11b23a72bbca15307a01c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 10:28:28 +0100 Subject: [PATCH 051/265] Made compiled calcuation templates a thin trait object --- src/custom_functions/calc_conviction.rs | 3 ++- .../compiled_transpose_calcuation_template.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/calc_conviction.rs b/src/custom_functions/calc_conviction.rs index 16eff317..9620aa79 100644 --- a/src/custom_functions/calc_conviction.rs +++ b/src/custom_functions/calc_conviction.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use crate::{BoxedThinTraitContext, Error, FloatType, ThinTraitContext, Value, ValueType}; use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; + struct BasherTradeModel { } @@ -24,7 +25,7 @@ impl CompiledTransposeCalculationTemplate for BasherTradeModel { fn dependencies(&self) -> Vec { vec!["signal".to_string(), "close".to_string()] } - fn commit_row(self: &mut Box, row: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + fn commit_row(&self, row: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { let mut prev_trade_signal: Option = None; diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs index 64117d0d..21c0f471 100644 --- a/src/custom_functions/compiled_transpose_calcuation_template.rs +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -1,12 +1,15 @@ use std::collections::HashMap; use std::fmt::Display; +use thin_trait_object::thin_trait_object; use crate::{BoxedThinTraitContext, Error, Value, ValueType}; use crate::Error::CustomError; + +#[thin_trait_object] pub trait CompiledTransposeCalculationTemplate { fn schema(&self) -> HashMap; fn dependencies(&self) -> Vec; - fn commit_row(self: &mut Box, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; + fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; } pub fn context(sself : Option, context: C) -> Result From 78f11571461fe80ba547cb5a1c655b4e2a75fd39 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 10:59:33 +0100 Subject: [PATCH 052/265] Moved basher trade model --- .../{calc_conviction.rs => basher_trade_model.rs} | 0 src/custom_functions/mod.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/custom_functions/{calc_conviction.rs => basher_trade_model.rs} (100%) diff --git a/src/custom_functions/calc_conviction.rs b/src/custom_functions/basher_trade_model.rs similarity index 100% rename from src/custom_functions/calc_conviction.rs rename to src/custom_functions/basher_trade_model.rs diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 52dbf57e..c720d615 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -3,7 +3,7 @@ pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; -mod calc_conviction; +mod basher_trade_model; mod compiled_transpose_calcuation_template; From f3274589a57408057556974c510feb2122df86d1 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 11:00:08 +0100 Subject: [PATCH 053/265] Nudge versiono --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e68a20e3..074f4990 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.37" +version = "7.2.38" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From d2b7a84610028532758ef984f597f04e129a6660 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 11:03:52 +0100 Subject: [PATCH 054/265] seg --- Cargo.toml | 2 +- src/custom_functions/mod.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 074f4990..f2a7863c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.38" +version = "7.2.39" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index c720d615..803c4373 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -3,13 +3,14 @@ pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; -mod basher_trade_model; -mod compiled_transpose_calcuation_template; +pub mod basher_trade_model; +pub mod compiled_transpose_calcuation_template; // Re-export the functions to the root of the crate pub use triangular_moving_average::triangular_moving_average; pub use expression_functions::*; pub use back::*; +pub use compiled_transpose_calcuation_template::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; From f8b8c788e49dcc60bc05aab560acef0927e0cf77 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 11:27:23 +0100 Subject: [PATCH 055/265] Made basher trade model pub --- Cargo.toml | 2 +- src/custom_functions/basher_trade_model.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2a7863c..177751b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.39" +version = "7.2.40" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/basher_trade_model.rs b/src/custom_functions/basher_trade_model.rs index 9620aa79..e90186c9 100644 --- a/src/custom_functions/basher_trade_model.rs +++ b/src/custom_functions/basher_trade_model.rs @@ -5,7 +5,7 @@ use crate::{BoxedThinTraitContext, Error, FloatType, ThinTraitContext, Value, Va use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; -struct BasherTradeModel { +pub struct BasherTradeModel { } From 19c0ac34ed772342fb53baf17e3e97bbe54e507e Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 11:51:10 +0100 Subject: [PATCH 056/265] Made basher trade model pub --- src/custom_functions/basher_trade_model.rs | 4 ++++ .../compiled_transpose_calcuation_template.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/src/custom_functions/basher_trade_model.rs b/src/custom_functions/basher_trade_model.rs index e90186c9..775e3ee5 100644 --- a/src/custom_functions/basher_trade_model.rs +++ b/src/custom_functions/basher_trade_model.rs @@ -11,6 +11,10 @@ pub struct BasherTradeModel { impl CompiledTransposeCalculationTemplate for BasherTradeModel { + fn test(&self) -> usize { + 6 + } + fn schema(&self) -> HashMap { vec![ ("active_trade", ValueType::Boolean), diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs index 21c0f471..60795a02 100644 --- a/src/custom_functions/compiled_transpose_calcuation_template.rs +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -7,6 +7,7 @@ use crate::Error::CustomError; #[thin_trait_object] pub trait CompiledTransposeCalculationTemplate { + fn test(&self) -> usize; fn schema(&self) -> HashMap; fn dependencies(&self) -> Vec; fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; From badfeb53d9feadf6696e5df8427f889e2ff50adb Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 11:51:44 +0100 Subject: [PATCH 057/265] Made basher trade model pub --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 177751b3..0b62bffe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.40" +version = "7.2.41" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From ce21a422dbba79620e2cd157b8f97d3a5670ef62 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 5 Apr 2024 11:54:36 +0100 Subject: [PATCH 058/265] Made basher trade model pub --- Cargo.toml | 2 +- src/custom_functions/basher_trade_model.rs | 200 +++++++++--------- .../compiled_transpose_calcuation_template.rs | 6 +- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b62bffe..8d99233b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.41" +version = "7.2.42" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/basher_trade_model.rs b/src/custom_functions/basher_trade_model.rs index 775e3ee5..d4a2fd6c 100644 --- a/src/custom_functions/basher_trade_model.rs +++ b/src/custom_functions/basher_trade_model.rs @@ -14,106 +14,106 @@ impl CompiledTransposeCalculationTemplate for BasherTradeModel { fn test(&self) -> usize { 6 } - - fn schema(&self) -> HashMap { - vec![ - ("active_trade", ValueType::Boolean), - ("reason", ValueType::String), - ("initiation_price", ValueType::Float), - ("stop_loss", ValueType::Float), - ("take_profit", ValueType::Float), - ("break_even", ValueType::Float) - ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() - } - - fn dependencies(&self) -> Vec { - vec!["signal".to_string(), "close".to_string()] - } - fn commit_row(&self, row: &mut BoxedThinTraitContext, 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 stop_loss: Option = None; - let mut take_profit: Option = None; - let mut break_even: Option = None; - let mut reason: Option = None; - - if cycle_epoch > 1 { - 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()?; - initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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 > 2 { - let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; - prev_trade_signal = row.get_value(&generate_column_name("signal", 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 current_close_value = row.get_value(&generate_column_name("close", transpose_value))?.as_float()?; - let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); - let loop_active_trade = active_trade.is_some_and(|tv|tv); - - 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")?; - - if current_close_value <= loop_stop_loss { - active_trade = Some(false); - initiation_price = None; - stop_loss = None; - take_profit = None; - break_even = None; - reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); - } - else if current_close_value >= loop_take_profit { - active_trade = Some(false); - initiation_price = None; - stop_loss = None; - take_profit = None; - break_even = None; - reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); - } - else if current_close_value > loop_break_even { - stop_loss = Some(loop_initiation_price + 0.0001); - } - - }else{ - let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); - if loop_trade_signal { - if !prev_trade_signal.unwrap_or_default() { - initiation_price = Some(current_close_value); - stop_loss = Some(current_close_value - 0.0002); - take_profit = Some(current_close_value + 0.0003); - break_even = Some(current_close_value + 0.0001); - active_trade = Some(true); - } - } - } - - 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs|Value::Float(rs)).unwrap_or(Value::Empty))?; - prev_trade_signal = Some(current_signal); - reason = None; - } - Ok(()) - } + // + // fn schema(&self) -> HashMap { + // vec![ + // ("active_trade", ValueType::Boolean), + // ("reason", ValueType::String), + // ("initiation_price", ValueType::Float), + // ("stop_loss", ValueType::Float), + // ("take_profit", ValueType::Float), + // ("break_even", ValueType::Float) + // ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + // } + // + // fn dependencies(&self) -> Vec { + // vec!["signal".to_string(), "close".to_string()] + // } + // fn commit_row(&self, row: &mut BoxedThinTraitContext, 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 stop_loss: Option = None; + // let mut take_profit: Option = None; + // let mut break_even: Option = None; + // let mut reason: Option = None; + // + // if cycle_epoch > 1 { + // 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()?; + // initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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 > 2 { + // let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; + // prev_trade_signal = row.get_value(&generate_column_name("signal", 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 current_close_value = row.get_value(&generate_column_name("close", transpose_value))?.as_float()?; + // let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + // let loop_active_trade = active_trade.is_some_and(|tv|tv); + // + // 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")?; + // + // if current_close_value <= loop_stop_loss { + // active_trade = Some(false); + // initiation_price = None; + // stop_loss = None; + // take_profit = None; + // break_even = None; + // reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); + // } + // else if current_close_value >= loop_take_profit { + // active_trade = Some(false); + // initiation_price = None; + // stop_loss = None; + // take_profit = None; + // break_even = None; + // reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); + // } + // else if current_close_value > loop_break_even { + // stop_loss = Some(loop_initiation_price + 0.0001); + // } + // + // }else{ + // let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + // if loop_trade_signal { + // if !prev_trade_signal.unwrap_or_default() { + // initiation_price = Some(current_close_value); + // stop_loss = Some(current_close_value - 0.0002); + // take_profit = Some(current_close_value + 0.0003); + // break_even = Some(current_close_value + 0.0001); + // active_trade = Some(true); + // } + // } + // } + // + // 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs|Value::Float(rs)).unwrap_or(Value::Empty))?; + // prev_trade_signal = Some(current_signal); + // reason = None; + // } + // Ok(()) + // } } fn generate_column_name(field: &str, p1: &Value) -> String { diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs index 60795a02..567d02fe 100644 --- a/src/custom_functions/compiled_transpose_calcuation_template.rs +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -8,9 +8,9 @@ use crate::Error::CustomError; #[thin_trait_object] pub trait CompiledTransposeCalculationTemplate { fn test(&self) -> usize; - fn schema(&self) -> HashMap; - fn dependencies(&self) -> Vec; - fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; + // fn schema(&self) -> HashMap; + // fn dependencies(&self) -> Vec; + // fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; } pub fn context(sself : Option, context: C) -> Result From 8fd742bb655a00ab651881dcb5642ce987cfe09c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 7 Apr 2024 12:49:36 +0100 Subject: [PATCH 059/265] Reinstated compiled expression template --- Cargo.toml | 2 +- src/custom_functions/basher_trade_model.rs | 199 +++++++++--------- .../compiled_transpose_calcuation_template.rs | 7 +- 3 files changed, 101 insertions(+), 107 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d99233b..6bb11e61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.42" +version = "7.2.5" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/basher_trade_model.rs b/src/custom_functions/basher_trade_model.rs index d4a2fd6c..db1c2eea 100644 --- a/src/custom_functions/basher_trade_model.rs +++ b/src/custom_functions/basher_trade_model.rs @@ -11,109 +11,104 @@ pub struct BasherTradeModel { impl CompiledTransposeCalculationTemplate for BasherTradeModel { - fn test(&self) -> usize { - 6 + fn schema(&self) -> HashMap { + vec![ + ("active_trade", ValueType::Boolean), + ("reason", ValueType::String), + ("initiation_price", ValueType::Float), + ("stop_loss", ValueType::Float), + ("take_profit", ValueType::Float), + ("break_even", ValueType::Float) + ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + } + fn dependencies(&self) -> Vec { + vec!["signal".to_string(), "close".to_string()] + } + fn commit_row(&self, row: &mut BoxedThinTraitContext, 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 stop_loss: Option = None; + let mut take_profit: Option = None; + let mut break_even: Option = None; + let mut reason: Option = None; + + if cycle_epoch > 1 { + 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()?; + initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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 > 2 { + let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; + prev_trade_signal = row.get_value(&generate_column_name("signal", 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 current_close_value = row.get_value(&generate_column_name("close", transpose_value))?.as_float()?; + let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + let loop_active_trade = active_trade.is_some_and(|tv|tv); + + 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")?; + + if current_close_value <= loop_stop_loss { + active_trade = Some(false); + initiation_price = None; + stop_loss = None; + take_profit = None; + break_even = None; + reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); + } + else if current_close_value >= loop_take_profit { + active_trade = Some(false); + initiation_price = None; + stop_loss = None; + take_profit = None; + break_even = None; + reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); + } + else if current_close_value > loop_break_even { + stop_loss = Some(loop_initiation_price + 0.0001); + } + + }else{ + let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + if loop_trade_signal { + if !prev_trade_signal.unwrap_or_default() { + initiation_price = Some(current_close_value); + stop_loss = Some(current_close_value - 0.0002); + take_profit = Some(current_close_value + 0.0003); + break_even = Some(current_close_value + 0.0001); + active_trade = Some(true); + } + } + } + + 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs|Value::Float(rs)).unwrap_or(Value::Empty))?; + prev_trade_signal = Some(current_signal); + reason = None; + } + Ok(()) } - // - // fn schema(&self) -> HashMap { - // vec![ - // ("active_trade", ValueType::Boolean), - // ("reason", ValueType::String), - // ("initiation_price", ValueType::Float), - // ("stop_loss", ValueType::Float), - // ("take_profit", ValueType::Float), - // ("break_even", ValueType::Float) - // ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() - // } - // - // fn dependencies(&self) -> Vec { - // vec!["signal".to_string(), "close".to_string()] - // } - // fn commit_row(&self, row: &mut BoxedThinTraitContext, 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 stop_loss: Option = None; - // let mut take_profit: Option = None; - // let mut break_even: Option = None; - // let mut reason: Option = None; - // - // if cycle_epoch > 1 { - // 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()?; - // initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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 > 2 { - // let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; - // prev_trade_signal = row.get_value(&generate_column_name("signal", 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 current_close_value = row.get_value(&generate_column_name("close", transpose_value))?.as_float()?; - // let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); - // let loop_active_trade = active_trade.is_some_and(|tv|tv); - // - // 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")?; - // - // if current_close_value <= loop_stop_loss { - // active_trade = Some(false); - // initiation_price = None; - // stop_loss = None; - // take_profit = None; - // break_even = None; - // reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); - // } - // else if current_close_value >= loop_take_profit { - // active_trade = Some(false); - // initiation_price = None; - // stop_loss = None; - // take_profit = None; - // break_even = None; - // reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); - // } - // else if current_close_value > loop_break_even { - // stop_loss = Some(loop_initiation_price + 0.0001); - // } - // - // }else{ - // let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); - // if loop_trade_signal { - // if !prev_trade_signal.unwrap_or_default() { - // initiation_price = Some(current_close_value); - // stop_loss = Some(current_close_value - 0.0002); - // take_profit = Some(current_close_value + 0.0003); - // break_even = Some(current_close_value + 0.0001); - // active_trade = Some(true); - // } - // } - // } - // - // 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs|Value::Float(rs)).unwrap_or(Value::Empty))?; - // prev_trade_signal = Some(current_signal); - // reason = None; - // } - // Ok(()) - // } } fn generate_column_name(field: &str, p1: &Value) -> String { diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs index 567d02fe..21c0f471 100644 --- a/src/custom_functions/compiled_transpose_calcuation_template.rs +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -7,10 +7,9 @@ use crate::Error::CustomError; #[thin_trait_object] pub trait CompiledTransposeCalculationTemplate { - fn test(&self) -> usize; - // fn schema(&self) -> HashMap; - // fn dependencies(&self) -> Vec; - // fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; + fn schema(&self) -> HashMap; + fn dependencies(&self) -> Vec; + fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; } pub fn context(sself : Option, context: C) -> Result From f79818453229bcc94751347a6b7be2f9833d9baf Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 11:42:03 +0100 Subject: [PATCH 060/265] Reinstated compiled expression template --- Cargo.toml | 3 ++- src/custom_functions/compiled_transpose_calcuation_template.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6bb11e61..be38957f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.5" +version = "7.2.6" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] @@ -29,6 +29,7 @@ serde_derive = { version = "1.0.133", optional = true} 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"] } + [features] serde_support = ["serde", "serde_derive"] regex_support = ["regex"] diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs index 21c0f471..05cafa24 100644 --- a/src/custom_functions/compiled_transpose_calcuation_template.rs +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -6,7 +6,7 @@ use crate::Error::CustomError; #[thin_trait_object] -pub trait CompiledTransposeCalculationTemplate { +pub trait CompiledTransposeCalculationTemplate : Send { fn schema(&self) -> HashMap; fn dependencies(&self) -> Vec; fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; From 4b7abfd44f2c71ec5750e57884071ca2e13d27ed Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 14:29:24 +0100 Subject: [PATCH 061/265] Added trade model --- Cargo.toml | 2 +- src/custom_functions/basher_trade_model.rs | 6 ++---- src/custom_functions/mod.rs | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index be38957f..a0c78f0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.6" +version = "7.2.7" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/basher_trade_model.rs b/src/custom_functions/basher_trade_model.rs index db1c2eea..d71c0aea 100644 --- a/src/custom_functions/basher_trade_model.rs +++ b/src/custom_functions/basher_trade_model.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fmt::Display; -use crate::{BoxedThinTraitContext, Error, FloatType, ThinTraitContext, Value, ValueType}; +use crate::{BoxedThinTraitContext, Error, FloatType, generate_column_name, ThinTraitContext, Value, ValueType}; use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; @@ -111,6 +111,4 @@ impl CompiledTransposeCalculationTemplate for BasherTradeModel { } } -fn generate_column_name(field: &str, p1: &Value) -> String { - format!("{}_{}", field, p1) -} + diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 803c4373..ee645758 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -14,3 +14,19 @@ pub use back::*; pub use compiled_transpose_calcuation_template::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; +use crate::Value; + +pub fn generate_column_name(field: &str, p1: &Value) -> String { + format!("{}_{}", field, get_string(p1)) +} + +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()} + } +} From 0417abff73fc518a3a87a930717f2d0e335c7eea Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 14:40:56 +0100 Subject: [PATCH 062/265] Added trade model --- Cargo.toml | 2 +- src/custom_functions/mod.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0c78f0c..2cddf513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.7" +version = "7.2.8" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index ee645758..31d0563c 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -7,6 +7,7 @@ pub mod basher_trade_model; pub mod compiled_transpose_calcuation_template; +use std::fmt::Display; // Re-export the functions to the root of the crate pub use triangular_moving_average::triangular_moving_average; pub use expression_functions::*; @@ -17,7 +18,19 @@ pub use simple_cumulative_sum::simple_cumulative_sum; use crate::Value; pub fn generate_column_name(field: &str, p1: &Value) -> String { - format!("{}_{}", field, get_string(p1)) + 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 { From 506617612c97acd2057ac67be0ac9547b94b0b1d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 15:08:42 +0100 Subject: [PATCH 063/265] Added trade model --- Cargo.toml | 2 +- src/value/mod.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2cddf513..af36076d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.8" +version = "7.2.9" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/value/mod.rs b/src/value/mod.rs index ed09f9fe..83773208 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -169,6 +169,7 @@ impl Value { 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())), } } @@ -176,6 +177,7 @@ impl Value { 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())), } From 9411984b15836a04793e3bc23d1d3522d3697ffa Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 17:26:34 +0100 Subject: [PATCH 064/265] set value now mutates state --- Cargo.toml | 2 +- src/context/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af36076d..621a6170 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.9" +version = "7.2.10" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/context/mod.rs b/src/context/mod.rs index 60fcad6a..fd480683 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -127,7 +127,7 @@ pub trait Context { #[thin_trait_object] pub trait ThinTraitContext { fn get_value(&self, identifier: &str) -> EvalexprResult; - fn set_value(&self, identifier: &str, value: Value) -> EvalexprResult<()>; + fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()>; fn set_row(&self, row: usize); fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; } From 1970224d22472b1abf798e93cdeb16aad8a071ac Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 17:29:09 +0100 Subject: [PATCH 065/265] set value now mutates state --- src/context/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index fd480683..e836f6bd 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -130,6 +130,7 @@ pub trait ThinTraitContext { fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()>; fn set_row(&self, row: usize); fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; + fn has_changes(&self) -> bool; } /// This macro provides a convenient syntax for creating a static context. From f9d92a34df980c8d315b69dd7412057d5060c724 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 8 Apr 2024 17:29:26 +0100 Subject: [PATCH 066/265] set value now mutates state --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 621a6170..6b9e26af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.10" +version = "7.2.11" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From ba3b472f8b6060b2a8c2cf36f48b7b06c3323e62 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 10:45:10 +0100 Subject: [PATCH 067/265] Added channel monitor and changed name of Boxed thin trait --- Cargo.toml | 2 +- src/context/mod.rs | 2 +- .../adaptive_stop_loss_trade_model.rs | 124 ++++++++++ src/custom_functions/basher_trade_model.rs | 114 ---------- .../channel_monitoring_template.rs | 213 ++++++++++++++++++ .../compiled_transpose_calcuation_template.rs | 4 +- src/custom_functions/mod.rs | 4 +- src/custom_functions/test_utils.rs | 50 ++++ src/lib.rs | 2 +- 9 files changed, 395 insertions(+), 120 deletions(-) create mode 100644 src/custom_functions/adaptive_stop_loss_trade_model.rs delete mode 100644 src/custom_functions/basher_trade_model.rs create mode 100644 src/custom_functions/channel_monitoring_template.rs create mode 100644 src/custom_functions/test_utils.rs diff --git a/Cargo.toml b/Cargo.toml index 6b9e26af..1c269111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.11" +version = "7.2.12" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/context/mod.rs b/src/context/mod.rs index e836f6bd..7964fb1a 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -125,7 +125,7 @@ pub trait Context { } #[thin_trait_object] -pub trait ThinTraitContext { +pub trait OperatorRowTrait { fn get_value(&self, identifier: &str) -> EvalexprResult; fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()>; fn set_row(&self, row: usize); diff --git a/src/custom_functions/adaptive_stop_loss_trade_model.rs b/src/custom_functions/adaptive_stop_loss_trade_model.rs new file mode 100644 index 00000000..cc2c61b6 --- /dev/null +++ b/src/custom_functions/adaptive_stop_loss_trade_model.rs @@ -0,0 +1,124 @@ +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedOperatorRowTrait, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; +use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; + + +pub struct AdaptiveStopLossTradeModel { + stop_loss_threshold: FloatType, + take_profit_threshold: FloatType, + break_even_threshold: FloatType +} + + +impl AdaptiveStopLossTradeModel { + pub fn new(stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + AdaptiveStopLossTradeModel { + stop_loss_threshold, + take_profit_threshold, + break_even_threshold + } + } + } + + +impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { + fn schema(&self) -> HashMap { + vec![ + ("active_trade", ValueType::Boolean), + ("reason", ValueType::String), + ("initiation_price", ValueType::Float), + ("stop_loss", ValueType::Float), + ("take_profit", ValueType::Float), + ("break_even", ValueType::Float) + ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + } + fn dependencies(&self) -> Vec { + vec!["signal".to_string(), "close".to_string()] + } + fn commit_row(&self, row: &mut BoxedOperatorRowTrait, 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 stop_loss: Option = None; + let mut take_profit: Option = None; + let mut break_even: Option = None; + let mut reason: Option = None; + + 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()?; + initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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("signal", 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(current_close_value) = row.get_value(&generate_column_name("close", transpose_value))?.as_float_or_none()? { + let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + let loop_active_trade = active_trade.is_some_and(|tv| tv); + + 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")?; + + if current_close_value <= loop_stop_loss { + active_trade = Some(false); + initiation_price = None; + stop_loss = None; + take_profit = None; + break_even = None; + reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); + } else if current_close_value >= loop_take_profit { + active_trade = Some(false); + initiation_price = None; + stop_loss = None; + take_profit = None; + break_even = None; + reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); + } else if current_close_value > loop_break_even { + stop_loss = Some(loop_initiation_price + self.break_even_threshold); + } + } else { + let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + if loop_trade_signal { + if !prev_trade_signal.unwrap_or_default() { + initiation_price = Some(current_close_value); + stop_loss = Some(current_close_value - self.stop_loss_threshold); + take_profit = Some(current_close_value + self.take_profit_threshold); + break_even = Some(current_close_value + self.break_even_threshold); + active_trade = Some(true); + } + } + } + + 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; + prev_trade_signal = Some(current_signal); + reason = None; + } + } + Ok(()) + } +} + + diff --git a/src/custom_functions/basher_trade_model.rs b/src/custom_functions/basher_trade_model.rs deleted file mode 100644 index d71c0aea..00000000 --- a/src/custom_functions/basher_trade_model.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::collections::HashMap; -use std::fmt::Display; - -use crate::{BoxedThinTraitContext, Error, FloatType, generate_column_name, ThinTraitContext, Value, ValueType}; -use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; - - -pub struct BasherTradeModel { - -} - - -impl CompiledTransposeCalculationTemplate for BasherTradeModel { - fn schema(&self) -> HashMap { - vec![ - ("active_trade", ValueType::Boolean), - ("reason", ValueType::String), - ("initiation_price", ValueType::Float), - ("stop_loss", ValueType::Float), - ("take_profit", ValueType::Float), - ("break_even", ValueType::Float) - ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() - } - fn dependencies(&self) -> Vec { - vec!["signal".to_string(), "close".to_string()] - } - fn commit_row(&self, row: &mut BoxedThinTraitContext, 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 stop_loss: Option = None; - let mut take_profit: Option = None; - let mut break_even: Option = None; - let mut reason: Option = None; - - if cycle_epoch > 1 { - 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()?; - initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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 > 2 { - let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; - prev_trade_signal = row.get_value(&generate_column_name("signal", 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 current_close_value = row.get_value(&generate_column_name("close", transpose_value))?.as_float()?; - let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); - let loop_active_trade = active_trade.is_some_and(|tv|tv); - - 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")?; - - if current_close_value <= loop_stop_loss { - active_trade = Some(false); - initiation_price = None; - stop_loss = None; - take_profit = None; - break_even = None; - reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); - } - else if current_close_value >= loop_take_profit { - active_trade = Some(false); - initiation_price = None; - stop_loss = None; - take_profit = None; - break_even = None; - reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); - } - else if current_close_value > loop_break_even { - stop_loss = Some(loop_initiation_price + 0.0001); - } - - }else{ - let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); - if loop_trade_signal { - if !prev_trade_signal.unwrap_or_default() { - initiation_price = Some(current_close_value); - stop_loss = Some(current_close_value - 0.0002); - take_profit = Some(current_close_value + 0.0003); - break_even = Some(current_close_value + 0.0001); - active_trade = Some(true); - } - } - } - - 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)).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("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("break_even", transpose_value), break_even.clone().map(|rs|Value::Float(rs)).unwrap_or(Value::Empty))?; - prev_trade_signal = Some(current_signal); - reason = None; - } - Ok(()) - } -} - - diff --git a/src/custom_functions/channel_monitoring_template.rs b/src/custom_functions/channel_monitoring_template.rs new file mode 100644 index 00000000..6e678928 --- /dev/null +++ b/src/custom_functions/channel_monitoring_template.rs @@ -0,0 +1,213 @@ +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedOperatorRowTrait, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; +use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; + + +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: String, upper_band_field_name: String, value_field_name: String, output_field_name: String) -> 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, + upper_band_field_name, + value_field_name, + 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 + } + } +} + +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, 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::custom_functions::test_utils::MockRow; + + #[test] + fn test_commit_row_value_below_lower_band() -> Result<(), Error> { + let monitor = ChannelMonitor::new( + "lower_band".to_string(), + "upper_band".to_string(), + "value".to_string(), + "output".to_string(), + ); + + let mut row = MockRow::new(); + let cycle_epoch = 0; + let transpose_value = Value::String("02_01_2024".to_owned()); + 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); + monitor.commit_row(&mut row_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".to_string(), + "upper_band".to_string(), + "value".to_string(), + "output".to_string(), + ); + + let mut row = MockRow::new(); + let prev_transpose_value = Value::String("01_01_2024".to_owned()); + let current_transpose_value = Value::String("02_01_2024".to_owned()); + 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); + monitor.commit_row(&mut row_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".to_string(), + "upper_band".to_string(), + "value".to_string(), + "output".to_string(), + ); + + let mut row = MockRow::new(); + let prev_transpose_value = Value::String("01_01_2024".to_owned()); + let current_transpose_value = Value::String("02_01_2024".to_owned()); + 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); + monitor.commit_row(&mut row_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/custom_functions/compiled_transpose_calcuation_template.rs b/src/custom_functions/compiled_transpose_calcuation_template.rs index 05cafa24..2acbd497 100644 --- a/src/custom_functions/compiled_transpose_calcuation_template.rs +++ b/src/custom_functions/compiled_transpose_calcuation_template.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::fmt::Display; use thin_trait_object::thin_trait_object; -use crate::{BoxedThinTraitContext, Error, Value, ValueType}; +use crate::{BoxedOperatorRowTrait, Error, Value, ValueType}; use crate::Error::CustomError; @@ -9,7 +9,7 @@ use crate::Error::CustomError; pub trait CompiledTransposeCalculationTemplate : Send { fn schema(&self) -> HashMap; fn dependencies(&self) -> Vec; - fn commit_row(&self, context: &mut BoxedThinTraitContext, ordered_transpose_values: &[Value], current_position: usize) -> Result<(), Error>; + fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>; } pub fn context(sself : Option, context: C) -> Result diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 31d0563c..4a4411b8 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -3,8 +3,10 @@ pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; -pub mod basher_trade_model; +pub mod adaptive_stop_loss_trade_model; pub mod compiled_transpose_calcuation_template; +mod channel_monitoring_template; +mod test_utils; use std::fmt::Display; diff --git a/src/custom_functions/test_utils.rs b/src/custom_functions/test_utils.rs new file mode 100644 index 00000000..89568112 --- /dev/null +++ b/src/custom_functions/test_utils.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; +use crate::{context, Error, EvalexprResult, OperatorRowTrait, Value}; + +pub struct MockRow { + values: HashMap, +} + +impl MockRow { + pub fn new() -> Self { + MockRow { + values: HashMap::new(), + } + } + + pub fn insert_value(&mut self, key: String, value: Value) { + self.values.insert(key, value); + } +} + +impl OperatorRowTrait for MockRow { + // 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) -> EvalexprResult { + let option = self.values.get(column_name); + match option { + None => { Ok(Value::Empty)} + Some(val) => { + Ok(val.clone()) + } + } + } + + fn set_value(&mut self, column_name: &str, value: Value) ->EvalexprResult<()> { + self.values.insert(column_name.to_string(), value); + Ok(()) + } + + fn set_row(&self, row: usize) { + todo!() + } + + fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult { + todo!() + } + + fn has_changes(&self) -> bool { + todo!() + } + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 81cfac49..a475d129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, ThinTraitContext, BoxedThinTraitContext, + Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait, HashMapContext, }, error::{EvalexprError, EvalexprResult}, From dca7a22a818ee3f4740676bf370acf161a19cb08 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 11:25:32 +0100 Subject: [PATCH 068/265] Added channel monitor and changed name of Boxed thin trait --- Cargo.toml | 2 +- src/custom_functions/mod.rs | 5 ----- src/lib.rs | 2 ++ .../adaptive_stop_loss_trade_model.rs | 5 ++--- .../channel_monitoring_template.rs | 5 ++--- .../compiled_transpose_calcuation_template.rs | 0 src/templates/mod.rs | 12 ++++++++++++ src/{custom_functions => templates}/test_utils.rs | 0 8 files changed, 19 insertions(+), 12 deletions(-) rename src/{custom_functions => templates}/adaptive_stop_loss_trade_model.rs (96%) rename src/{custom_functions => templates}/channel_monitoring_template.rs (97%) rename src/{custom_functions => templates}/compiled_transpose_calcuation_template.rs (100%) create mode 100644 src/templates/mod.rs rename src/{custom_functions => templates}/test_utils.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 1c269111..7d746ace 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.12" +version = "7.2.13" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 4a4411b8..8086de8a 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -3,10 +3,6 @@ pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; -pub mod adaptive_stop_loss_trade_model; -pub mod compiled_transpose_calcuation_template; -mod channel_monitoring_template; -mod test_utils; use std::fmt::Display; @@ -14,7 +10,6 @@ use std::fmt::Display; pub use triangular_moving_average::triangular_moving_average; pub use expression_functions::*; pub use back::*; -pub use compiled_transpose_calcuation_template::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; use crate::Value; diff --git a/src/lib.rs b/src/lib.rs index a475d129..a7c481fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -540,6 +540,7 @@ pub use crate::{ function::Function, interface::*, custom_functions::*, + templates::*, operator::Operator, token::PartialToken, tree::Node, @@ -556,6 +557,7 @@ mod operator; mod token; mod tree; mod custom_functions; +mod templates; mod value; // Exports diff --git a/src/custom_functions/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs similarity index 96% rename from src/custom_functions/adaptive_stop_loss_trade_model.rs rename to src/templates/adaptive_stop_loss_trade_model.rs index cc2c61b6..d694acd5 100644 --- a/src/custom_functions/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -1,8 +1,7 @@ use std::collections::HashMap; use std::fmt::Display; - -use crate::{BoxedOperatorRowTrait, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; -use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; +use crate::context; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; pub struct AdaptiveStopLossTradeModel { diff --git a/src/custom_functions/channel_monitoring_template.rs b/src/templates/channel_monitoring_template.rs similarity index 97% rename from src/custom_functions/channel_monitoring_template.rs rename to src/templates/channel_monitoring_template.rs index 6e678928..bb8feb50 100644 --- a/src/custom_functions/channel_monitoring_template.rs +++ b/src/templates/channel_monitoring_template.rs @@ -1,8 +1,7 @@ use std::collections::HashMap; use std::fmt::Display; -use crate::{BoxedOperatorRowTrait, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; -use crate::custom_functions::compiled_transpose_calcuation_template::{CompiledTransposeCalculationTemplate, context}; +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; pub struct ChannelMonitor { @@ -104,7 +103,7 @@ impl CompiledTransposeCalculationTemplate for ChannelMonitor { mod commit_row_tests { use super::*; use std::collections::HashMap; - use crate::custom_functions::test_utils::MockRow; + use crate::templates::test_utils::MockRow; #[test] fn test_commit_row_value_below_lower_band() -> Result<(), Error> { diff --git a/src/custom_functions/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calcuation_template.rs similarity index 100% rename from src/custom_functions/compiled_transpose_calcuation_template.rs rename to src/templates/compiled_transpose_calcuation_template.rs diff --git a/src/templates/mod.rs b/src/templates/mod.rs new file mode 100644 index 00000000..cdab6e82 --- /dev/null +++ b/src/templates/mod.rs @@ -0,0 +1,12 @@ +pub mod adaptive_stop_loss_trade_model; +pub mod compiled_transpose_calcuation_template; +mod channel_monitoring_template; +mod test_utils; + +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 compiled_transpose_calcuation_template::*; +use crate::Value; + diff --git a/src/custom_functions/test_utils.rs b/src/templates/test_utils.rs similarity index 100% rename from src/custom_functions/test_utils.rs rename to src/templates/test_utils.rs From 8c08f5f6739131a5b0dd792ffa9e031d3b935938 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 12:00:24 +0100 Subject: [PATCH 069/265] Added channel monitor and changed name of Boxed thin trait --- Cargo.toml | 2 +- src/templates/channel_monitoring_template.rs | 34 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7d746ace..93607079 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.13" +version = "7.2.14" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/src/templates/channel_monitoring_template.rs b/src/templates/channel_monitoring_template.rs index bb8feb50..9d165d8e 100644 --- a/src/templates/channel_monitoring_template.rs +++ b/src/templates/channel_monitoring_template.rs @@ -17,7 +17,7 @@ pub struct ChannelMonitor { } impl ChannelMonitor { - pub fn new(lower_band_field_name: String, upper_band_field_name: String, value_field_name: String, output_field_name: String) -> 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); @@ -26,15 +26,15 @@ impl ChannelMonitor { let re_entered_below_field_name = format!("{}_re_entered_below", output_field_name); ChannelMonitor { - lower_band_field_name, - upper_band_field_name, - value_field_name, + 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 : output_field_name.to_owned() } } } @@ -108,10 +108,10 @@ mod commit_row_tests { #[test] fn test_commit_row_value_below_lower_band() -> Result<(), Error> { let monitor = ChannelMonitor::new( - "lower_band".to_string(), - "upper_band".to_string(), - "value".to_string(), - "output".to_string(), + "lower_band", + "upper_band", + "value", + "output", ); let mut row = MockRow::new(); @@ -136,10 +136,10 @@ mod commit_row_tests { #[test] fn test_commit_row_re_entered_above_after_being_below() -> Result<(), Error> { let monitor = ChannelMonitor::new( - "lower_band".to_string(), - "upper_band".to_string(), - "value".to_string(), - "output".to_string(), + "lower_band", + "upper_band", + "value", + "output", ); let mut row = MockRow::new(); @@ -178,10 +178,10 @@ mod commit_row_tests { #[test] fn test_commit_row_re_entered_below_after_being_above() -> Result<(), Error> { let monitor = ChannelMonitor::new( - "lower_band".to_string(), - "upper_band".to_string(), - "value".to_string(), - "output".to_string(), + "lower_band", + "upper_band", + "value", + "output", ); let mut row = MockRow::new(); From 27c44836e3d8e76acef7c95efddfc99ab5af4abb Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 14:33:20 +0100 Subject: [PATCH 070/265] configurable fields in the adaptive stop loss space --- src/templates/adaptive_stop_loss_trade_model.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index d694acd5..93af0027 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -5,6 +5,8 @@ use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, pub struct AdaptiveStopLossTradeModel { + signal_field: String, + value_field: String, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType @@ -12,8 +14,10 @@ pub struct AdaptiveStopLossTradeModel { impl AdaptiveStopLossTradeModel { - pub fn new(stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + pub fn new(signal_field_name: &str, value_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { AdaptiveStopLossTradeModel { + signal_field: signal_field_name.to_string(), + value_field: value_field_name.to_string(), stop_loss_threshold, take_profit_threshold, break_even_threshold @@ -34,7 +38,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() } fn dependencies(&self) -> Vec { - vec!["signal".to_string(), "close".to_string()] + vec![self.signal_field.to_string(), self.value_field.to_string()] } fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { From c9e6cb95cb72dc244b75fdb99bec4b3ee00b320d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 14:34:58 +0100 Subject: [PATCH 071/265] configurable fields in the adaptive stop loss space --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 93607079..c4753947 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "7.2.14" +version = "7.2.15" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] From 71ee2ab400375fb10b6c7b3126f505155932e10c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 15:07:15 +0100 Subject: [PATCH 072/265] configurable fields in the adaptive stop loss space --- src/value/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 83773208..4f451829 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::convert::TryInto; use crate::error::{EvalexprError, EvalexprResult}; mod display; @@ -97,6 +98,17 @@ impl Default for Value { } } +impl TryInto for Value{ + type Error = EvalexprError; + + fn try_into(self) -> Result { + match self { + Value::Boolean(b) => Ok(b), + value => Err(EvalexprError::expected_boolean(value)), + } + } +} + impl Value { /// Returns true if `self` is a `Value::String`. pub fn is_string(&self) -> bool { From 0f9a5d4b76b159125777db5657f6ec62e10442a7 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 15:09:15 +0100 Subject: [PATCH 073/265] configurable fields in the adaptive stop loss space --- src/value/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 4f451829..4f942daa 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -98,13 +98,13 @@ impl Default for Value { } } -impl TryInto for Value{ +impl TryInto for &Value{ type Error = EvalexprError; fn try_into(self) -> Result { match self { - Value::Boolean(b) => Ok(b), - value => Err(EvalexprError::expected_boolean(value)), + Value::Boolean(b) => Ok(b.clone()), + value => Err(EvalexprError::expected_boolean(value.clone())), } } } From 641a87e3b25c6dc11e21496ff88219a3a0c9b0ec Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 15:10:43 +0100 Subject: [PATCH 074/265] configurable fields in the adaptive stop loss space --- src/value/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 4f942daa..1b1b5345 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -99,12 +99,12 @@ impl Default for Value { } impl TryInto for &Value{ - type Error = EvalexprError; + type Error = Error; fn try_into(self) -> Result { match self { Value::Boolean(b) => Ok(b.clone()), - value => Err(EvalexprError::expected_boolean(value.clone())), + value => Err(EvalexprError::expected_boolean(value.clone()).into()), } } } From ba88de005c9f85092f0864a20844fcf52876da68 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 15:18:36 +0100 Subject: [PATCH 075/265] configurable fields in the adaptive stop loss space --- .../compiled_transpose_calcuation_template.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/templates/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calcuation_template.rs index 2acbd497..bec441ed 100644 --- a/src/templates/compiled_transpose_calcuation_template.rs +++ b/src/templates/compiled_transpose_calcuation_template.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::fmt::Display; +use std::fmt::{Debug, Display}; use thin_trait_object::thin_trait_object; use crate::{BoxedOperatorRowTrait, Error, Value, ValueType}; use crate::Error::CustomError; @@ -20,4 +20,13 @@ pub fn context(sself : Option, context: C) -> Result 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 From af9f324e1672049bd795892b00dfe37fef3a4f93 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 17:36:10 +0100 Subject: [PATCH 076/265] configurable fields in the adaptive stop loss space --- src/templates/adaptive_stop_loss_trade_model.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 93af0027..dadacbde 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -65,13 +65,13 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { if cycle_epoch > 1 { let transpose_value_before_epoch = &ordered_transpose_values[cycle_epoch - 2]; - prev_trade_signal = row.get_value(&generate_column_name("signal", transpose_value_before_epoch))?.as_boolean_or_none()?; + 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(current_close_value) = row.get_value(&generate_column_name("close", transpose_value))?.as_float_or_none()? { - let current_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + if let Some(current_close_value) = row.get_value(&generate_column_name(&self.value_field, 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); if loop_active_trade { @@ -98,7 +98,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { stop_loss = Some(loop_initiation_price + self.break_even_threshold); } } else { - let loop_trade_signal = row.get_value(&generate_column_name("signal", transpose_value))?.as_boolean_or_none()?.unwrap_or_default(); + 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_close_value); From 94526b5560765647a84482ee268f01973ccbf5c1 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 9 Apr 2024 17:49:15 +0100 Subject: [PATCH 077/265] configurable fields in the adaptive stop loss space --- src/templates/adaptive_stop_loss_trade_model.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index dadacbde..fa77a8fc 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -33,6 +33,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ("reason", ValueType::String), ("initiation_price", ValueType::Float), ("stop_loss", ValueType::Float), + ("delta", ValueType::Float), ("take_profit", ValueType::Float), ("break_even", ValueType::Float) ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() @@ -51,6 +52,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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; if cycle_epoch > 0 { @@ -80,20 +82,24 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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 delta_val = current_close_value - loop_initiation_price; if current_close_value <= loop_stop_loss { active_trade = Some(false); initiation_price = None; stop_loss = None; take_profit = None; break_even = None; - reason = Some(format!("Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).", current_close_value, loop_initiation_price)); + + delta = Some(delta_val); + reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).",delta_val, current_close_value, loop_initiation_price)); } else if current_close_value >= loop_take_profit { active_trade = Some(false); initiation_price = None; stop_loss = None; take_profit = None; break_even = None; - reason = Some(format!("Closing trade. Current price ({}) has reached or exceeded take profit level from entry price ({}).", current_close_value, loop_initiation_price)); + 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_val, current_close_value, loop_initiation_price)); } else if current_close_value > loop_break_even { stop_loss = Some(loop_initiation_price + self.break_even_threshold); } @@ -113,11 +119,13 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)).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("delta", transpose_value), delta.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("break_even", transpose_value), break_even.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; prev_trade_signal = Some(current_signal); reason = None; + delta = None; } } Ok(()) From a99e476369accfee22f6d8601b19d51f92c064ff Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 10 Apr 2024 09:07:16 +0100 Subject: [PATCH 078/265] configurable fields in the adaptive stop loss space --- src/custom_functions/expression_functions.rs | 42 +++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index f9d58818..3c93ede0 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -1,4 +1,4 @@ -use crate::{Error, Value}; +use crate::{Error, FloatType, Value}; use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; pub fn is_null(value: &Value) -> Result<&Value, Error> { @@ -16,6 +16,46 @@ pub fn abs(value: &Value) -> Result { _ => Err(Error::InvalidArgumentType), } } +pub fn safe_divide(left: &Value, right: Value) -> Result { + match (left, right) { + (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), + } +} pub fn starts_with(message: &Value, prefix: &Value) -> Result { if let (Value::String(message), Value::String(prefix)) = (message, prefix) { From 58edca3e1b48b601847a3995faebac918238e14c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 10 Apr 2024 09:08:49 +0100 Subject: [PATCH 079/265] configurable fields in the adaptive stop loss space --- src/custom_functions/expression_functions.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 3c93ede0..b2ebe80b 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -16,31 +16,31 @@ pub fn abs(value: &Value) -> Result { _ => Err(Error::InvalidArgumentType), } } -pub fn safe_divide(left: &Value, right: Value) -> Result { +pub fn safe_divide(left: &Value, right: &Value) -> Result { match (left, right) { (Value::Float(left), Value::Float(right)) => { - if right == 0.0 { + if *right == 0.0 { Ok(Value::Empty) } else { Ok(Value::Float(left / right)) } } (Value::Int(left), Value::Int(right)) => { - if right == 0 { + if *right == 0 { Ok(Value::Empty) } else { Ok(Value::Int(left / right)) } } (Value::Float(left), Value::Int(right)) => { - if right == 0 { + if *right == 0 { Ok(Value::Empty) } else { - Ok(Value::Float(left / right as FloatType)) + Ok(Value::Float(left / *right as FloatType)) } } (Value::Int(left), Value::Float(right)) => { - if right == 0.0 { + if *right == 0.0 { Ok(Value::Empty) } else { Ok(Value::Float(*left as FloatType / right)) From 04afefa03e19916fb0b1bbdbe74bf8d74384f2b1 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 10 Apr 2024 09:19:56 +0100 Subject: [PATCH 080/265] configurable fields in the adaptive stop loss space --- src/custom_functions/expression_functions.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index b2ebe80b..f7e06ce3 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -16,34 +16,34 @@ pub fn abs(value: &Value) -> Result { _ => Err(Error::InvalidArgumentType), } } -pub fn safe_divide(left: &Value, right: &Value) -> Result { - match (left, right) { +pub fn safe_divide>(left: T, right: T) -> Result { + match (left.into(), right.into()) { (Value::Float(left), Value::Float(right)) => { - if *right == 0.0 { + if right == 0.0 { Ok(Value::Empty) } else { Ok(Value::Float(left / right)) } } (Value::Int(left), Value::Int(right)) => { - if *right == 0 { + if right == 0 { Ok(Value::Empty) } else { Ok(Value::Int(left / right)) } } (Value::Float(left), Value::Int(right)) => { - if *right == 0 { + if right == 0 { Ok(Value::Empty) } else { - Ok(Value::Float(left / *right as FloatType)) + Ok(Value::Float(left / right as FloatType)) } } (Value::Int(left), Value::Float(right)) => { - if *right == 0.0 { + if right == 0.0 { Ok(Value::Empty) } else { - Ok(Value::Float(*left as FloatType / right)) + Ok(Value::Float(left as FloatType / right)) } } (_, Value::Empty) => { From fd7bb2e8b4fef8bd8c419fe09696eab792747d8c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 10 Apr 2024 09:55:26 +0100 Subject: [PATCH 081/265] configurable fields in the adaptive stop loss space --- src/custom_functions/expression_functions.rs | 34 +++++++++++--------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index f7e06ce3..7e55ce66 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -1,22 +1,22 @@ use crate::{Error, FloatType, Value}; use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; -pub fn is_null(value: &Value) -> Result<&Value, Error> { - Ok(match value { - Value::Empty => &Value::Int(0), - _ => value, +pub fn is_null>(value: T) -> Result { + Ok(match value.into() { + Value::Empty => Value::Int(0), + v => v, }) } -pub fn abs(value: &Value) -> Result { - match value { +pub fn abs>(value: T) -> Result { + match value.into() { 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>(left: T, right: T) -> Result { +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 { @@ -102,19 +102,23 @@ pub fn round_date_to_precision(string: &Value, precision: &Value) -> Result(value1: &'a Value, value2: &'a Value) -> Result<&'a Value, Error> { - Ok(if value1 > value2 { - value1 +pub fn max,TR: Into>(value1: TL, value2: TR) -> Result { + let x = value1.into(); + let x1 = value2.into(); + Ok(if x > x1 { + x } else { - value2 + x1 }) } -pub fn min<'a>(value1: &'a Value, value2: &'a Value) -> Result<&'a Value, Error> { - Ok(if value1 < value2 { - value1 +pub fn min,TR: Into>(value1: TL, value2: TR) -> Result { + let x = value1.into(); + let x1 = value2.into(); + Ok(if x < x1 { + x } else { - value2 + x1 }) } From 81a2047b3f48445c43175cd34b675207250a1e3f Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 11 Apr 2024 08:23:41 +0100 Subject: [PATCH 082/265] Capture all trade information in final record of trade --- .../adaptive_stop_loss_trade_model.rs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index fa77a8fc..34a06703 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -6,6 +6,7 @@ use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, pub struct AdaptiveStopLossTradeModel { signal_field: String, + date_field: String, value_field: String, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, @@ -14,9 +15,10 @@ pub struct AdaptiveStopLossTradeModel { impl AdaptiveStopLossTradeModel { - pub fn new(signal_field_name: &str, value_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + pub fn new(date_field: &str,signal_field_name: &str, value_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { AdaptiveStopLossTradeModel { signal_field: signal_field_name.to_string(), + date_field: date_field.to_string(), value_field: value_field_name.to_string(), stop_loss_threshold, take_profit_threshold, @@ -30,6 +32,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { fn schema(&self) -> HashMap { vec![ ("active_trade", ValueType::Boolean), + ("initiation_date", ValueType::String), ("reason", ValueType::String), ("initiation_price", ValueType::Float), ("stop_loss", ValueType::Float), @@ -49,6 +52,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { let mut active_trade: Option = None; let mut initiation_price: Option = None; + let mut initiation_date: Option = None; let mut stop_loss: Option = None; let mut take_profit: Option = None; let mut break_even: Option = None; @@ -59,6 +63,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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()?; initiation_price = row.get_value(&generate_column_name("initiation_price", transpose_value_before_epoch))?.as_float_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()?; @@ -72,10 +77,13 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { for i in cycle_epoch ..ordered_transpose_values.len() { let transpose_value = &ordered_transpose_values[i]; - if let Some(current_close_value) = row.get_value(&generate_column_name(&self.value_field, transpose_value))?.as_float_or_none()? { + if let (Some(current_close_value), Some(current_date)) = ( + row.get_value(&generate_column_name(&self.value_field, transpose_value))?.as_float_or_none()?, + row.get_value(&generate_column_name(&self.date_field, transpose_value))?.as_string_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")?; @@ -84,20 +92,11 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { let delta_val = current_close_value - loop_initiation_price; if current_close_value <= loop_stop_loss { - active_trade = Some(false); - initiation_price = None; - stop_loss = None; - take_profit = None; - break_even = None; - + loop_trade_closed = true; delta = Some(delta_val); reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).",delta_val, current_close_value, loop_initiation_price)); } else if current_close_value >= loop_take_profit { - active_trade = Some(false); - initiation_price = None; - stop_loss = None; - take_profit = None; - break_even = None; + loop_trade_closed = true; 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_val, current_close_value, loop_initiation_price)); } else if current_close_value > loop_break_even { @@ -108,6 +107,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { if loop_trade_signal { if !prev_trade_signal.unwrap_or_default() { initiation_price = Some(current_close_value); + initiation_date = Some(current_date); stop_loss = Some(current_close_value - self.stop_loss_threshold); take_profit = Some(current_close_value + self.take_profit_threshold); break_even = Some(current_close_value + self.break_even_threshold); @@ -119,6 +119,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)).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)).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("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))?; @@ -126,6 +127,14 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { prev_trade_signal = Some(current_signal); reason = None; delta = None; + if loop_trade_closed { + active_trade = Some(false); + initiation_price = None; + initiation_date = None; + stop_loss = None; + take_profit = None; + break_even = None; + } } } Ok(()) From 85a920b22dec04447b42d9852a16671059964292 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 11 Apr 2024 10:03:25 +0100 Subject: [PATCH 083/265] Fixed adaptive trade model --- src/templates/adaptive_stop_loss_trade_model.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 34a06703..38615291 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -1,12 +1,11 @@ use std::collections::HashMap; use std::fmt::Display; -use crate::context; +use crate::{context, get_string}; use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; pub struct AdaptiveStopLossTradeModel { signal_field: String, - date_field: String, value_field: String, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, @@ -15,10 +14,9 @@ pub struct AdaptiveStopLossTradeModel { impl AdaptiveStopLossTradeModel { - pub fn new(date_field: &str,signal_field_name: &str, value_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + pub fn new(signal_field_name: &str, value_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { AdaptiveStopLossTradeModel { signal_field: signal_field_name.to_string(), - date_field: date_field.to_string(), value_field: value_field_name.to_string(), stop_loss_threshold, take_profit_threshold, @@ -77,10 +75,9 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { for i in cycle_epoch ..ordered_transpose_values.len() { let transpose_value = &ordered_transpose_values[i]; - if let (Some(current_close_value), Some(current_date)) = ( - row.get_value(&generate_column_name(&self.value_field, transpose_value))?.as_float_or_none()?, - row.get_value(&generate_column_name(&self.date_field, transpose_value))?.as_string_or_none()? - ){ + if let Some(current_close_value) = + row.get_value(&generate_column_name(&self.value_field, 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; @@ -107,7 +104,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { if loop_trade_signal { if !prev_trade_signal.unwrap_or_default() { initiation_price = Some(current_close_value); - initiation_date = Some(current_date); + initiation_date = Some(get_string(&transpose_value)); stop_loss = Some(current_close_value - self.stop_loss_threshold); take_profit = Some(current_close_value + self.take_profit_threshold); break_even = Some(current_close_value + self.break_even_threshold); From 707b4682d72aeb70cf2ec8c6da1b71180b0ca8ef Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 21 May 2024 11:06:59 +0100 Subject: [PATCH 084/265] Fixed adaptive trade model --- src/templates/adaptive_stop_loss_trade_model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 38615291..ee40fd09 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -91,11 +91,11 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { if current_close_value <= loop_stop_loss { loop_trade_closed = true; delta = Some(delta_val); - reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss from entry price ({}).",delta_val, 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_val, current_close_value,loop_stop_loss, loop_initiation_price)); } else if current_close_value >= loop_take_profit { loop_trade_closed = true; 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_val, 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_val, current_close_value,loop_take_profit, loop_initiation_price)); } else if current_close_value > loop_break_even { stop_loss = Some(loop_initiation_price + self.break_even_threshold); } From 1540f96688caf950d56695408243fdae5173abe8 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 21 May 2024 11:09:50 +0100 Subject: [PATCH 085/265] Fixed adaptive trade model --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index ee40fd09..a8c1ef1a 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -91,7 +91,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { if current_close_value <= loop_stop_loss { loop_trade_closed = true; delta = Some(delta_val); - reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss {} from entry price ({}).",delta_val, current_close_value,loop_stop_loss, loop_initiation_price)); + reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss {}({}) from entry price ({}).",delta_val, 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; delta = Some(current_close_value - loop_initiation_price); From 5d6fb79854ef07979062b04acad040b6353d4106 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Wed, 29 May 2024 18:01:05 +0100 Subject: [PATCH 086/265] Fixed adaptive trade model --- src/templates/adaptive_stop_loss_trade_model.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index a8c1ef1a..ecde02e2 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -87,15 +87,13 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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 delta_val = current_close_value - loop_initiation_price; + delta = Some(current_close_value - loop_initiation_price); if current_close_value <= loop_stop_loss { loop_trade_closed = true; - delta = Some(delta_val); - reason = Some(format!("Lost {} Closing trade. Current price ({}) has fallen to or below stop loss {}({}) from entry price ({}).",delta_val, current_close_value,loop_stop_loss,self.stop_loss_threshold, 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; - 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_val, current_close_value,loop_take_profit, 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 > loop_break_even { stop_loss = Some(loop_initiation_price + self.break_even_threshold); } From a84390ac1ea2e0e6a4b25eb3835e81c5e70c4f3a Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Thu, 30 May 2024 13:40:30 +0100 Subject: [PATCH 087/265] Added exit price --- src/templates/adaptive_stop_loss_trade_model.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index ecde02e2..538baab5 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -33,6 +33,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ("initiation_date", ValueType::String), ("reason", ValueType::String), ("initiation_price", ValueType::Float), + ("exit_price", ValueType::Float), ("stop_loss", ValueType::Float), ("delta", ValueType::Float), ("take_profit", ValueType::Float), @@ -50,6 +51,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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 stop_loss: Option = None; let mut take_profit: Option = None; @@ -90,9 +92,11 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { delta = Some(current_close_value - loop_initiation_price); if current_close_value <= loop_stop_loss { loop_trade_closed = true; + exit_price = Some(current_close_value); 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); 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 > loop_break_even { stop_loss = Some(loop_initiation_price + self.break_even_threshold); @@ -116,16 +120,19 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)).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("break_even", transpose_value), break_even.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; 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; + stop_loss = None; take_profit = None; break_even = None; From 30fcc2c6b06b064d31c5ae6d30419841c232e2b5 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:03:38 +0100 Subject: [PATCH 088/265] Fixed adaptive trade model --- .../adaptive_stop_loss_trade_model.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 538baab5..4c75b6b8 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -7,6 +7,7 @@ use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, pub struct AdaptiveStopLossTradeModel { signal_field: String, value_field: String, + trading_range_field_name: String, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType @@ -14,10 +15,11 @@ pub struct AdaptiveStopLossTradeModel { impl AdaptiveStopLossTradeModel { - pub fn new(signal_field_name: &str, value_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + pub fn new(signal_field_name: &str, value_field_name: &str,trading_range_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { AdaptiveStopLossTradeModel { signal_field: signal_field_name.to_string(), value_field: value_field_name.to_string(), + trading_range_field_name: trading_range_field_name.to_string(), stop_loss_threshold, take_profit_threshold, break_even_threshold @@ -41,7 +43,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() } fn dependencies(&self) -> Vec { - vec![self.signal_field.to_string(), self.value_field.to_string()] + vec![self.signal_field.to_string(), self.value_field.to_string(), self.trading_range_field_name.to_string()] } fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { @@ -77,8 +79,11 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { for i in cycle_epoch ..ordered_transpose_values.len() { let transpose_value = &ordered_transpose_values[i]; - if let Some(current_close_value) = - row.get_value(&generate_column_name(&self.value_field, transpose_value))?.as_float_or_none()? + if let (Some(current_close_value),Some(trading_range)) = + ( + row.get_value(&generate_column_name(&self.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); @@ -99,7 +104,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { exit_price = Some(current_close_value); 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 > loop_break_even { - stop_loss = Some(loop_initiation_price + self.break_even_threshold); + stop_loss = Some(loop_initiation_price + (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(); @@ -107,9 +112,9 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { if !prev_trade_signal.unwrap_or_default() { initiation_price = Some(current_close_value); initiation_date = Some(get_string(&transpose_value)); - stop_loss = Some(current_close_value - self.stop_loss_threshold); - take_profit = Some(current_close_value + self.take_profit_threshold); - break_even = Some(current_close_value + self.break_even_threshold); + 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); } } From 5ec06dfe169a28755067c7262a902337c8cac4e9 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:22:23 +0100 Subject: [PATCH 089/265] Fixed adaptive trade model --- src/templates/adaptive_stop_loss_trade_model.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 4c75b6b8..f1acd446 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -39,6 +39,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ("stop_loss", ValueType::Float), ("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() } @@ -104,7 +105,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { exit_price = Some(current_close_value); 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 > loop_break_even { - stop_loss = Some(loop_initiation_price + (trading_range * self.break_even_threshold)); + stop_loss = Some(loop_initiation_price - (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(); @@ -129,6 +130,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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("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), trading_range.clone().map(|rs| Value::Float(rs)).unwrap_or(Value::Empty))?; prev_trade_signal = Some(current_signal); reason = None; delta = None; From 489766a36537626c8d8414e565100cce8a8648c5 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:32:38 +0100 Subject: [PATCH 090/265] Fixed adaptive trade model --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index f1acd446..a0d8d0a6 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -130,7 +130,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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("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), trading_range.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; From 9f4d4f54f79d8735ed98b0cd6aea809ab8db2547 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:19:26 +0100 Subject: [PATCH 091/265] Chanded TMA --- .../triangular_moving_average.rs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 4130eb2c..68b3f0fa 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -1,37 +1,38 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; -pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { - if columns.len() <= 2 { - return Ok(Value::Empty); +pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { + if columns.len() < 3 { + return Ok(Value::Empty); // Not enough data to calculate } let mut total_sum = 0.0; let mut total_weight = 0; let half_length = columns.len() / 2; - for i in half_length..columns.len() { + for i in 0..columns.len() { let mut sum: f64 = 0.0; - let mut sum_weight: usize = half_length + 1; - let mut k = half_length; - - if let Some(price_i) = get_price(row, columns, i) { - sum += price_i * sum_weight as f64; // Price(i) * (HalfLength + 1) - - for j in 1..=half_length { - if i + j < columns.len() { - if let Some(price) = get_price(row, columns, i + j) { - sum += price * k as f64; // Price(i + j) - sum_weight += k; - } + 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; } - if j <= i && i >= j { - if let Some(price) = get_price(row, columns, i - j) { - sum += price * k as f64; // Price(i - j) - sum_weight += k; - } + } + + // 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; } - k -= 1; } } From 6691199326f2163e8cc2537e5e024eebc650faca Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:30:45 +0100 Subject: [PATCH 092/265] Added columns len --- src/custom_functions/triangular_moving_average.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 68b3f0fa..2f3fb884 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -1,6 +1,9 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; +pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { + Ok(Value::Int(columns.len() as i64)) +} pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { if columns.len() < 3 { return Ok(Value::Empty); // Not enough data to calculate From 9a2180a5cd6ca55bd421b04463106f3a0f73656b Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 07:28:17 +0100 Subject: [PATCH 093/265] Fixed expression --- src/custom_functions/triangular_moving_average.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 2f3fb884..ea956278 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -4,7 +4,7 @@ use crate::Error::UnsupportedOperation; pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { Ok(Value::Int(columns.len() as i64)) } -pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { +pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { if columns.len() < 3 { return Ok(Value::Empty); // Not enough data to calculate } From 752f6dc8ff3a36352e367643412adad90cdb03f7 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 07:29:32 +0100 Subject: [PATCH 094/265] Fixed expression --- src/custom_functions/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 8086de8a..6cd967ce 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -8,6 +8,7 @@ pub mod back; 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 simple_moving_average::simple_moving_average; From d9d8cad8dcbea22e1ab981c4094093d7429bd366 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 07:30:17 +0100 Subject: [PATCH 095/265] Fixed expression --- src/custom_functions/triangular_moving_average.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index ea956278..238fadad 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -1,7 +1,7 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; -pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { +pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { Ok(Value::Int(columns.len() as i64)) } pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { From 61ab6dc86dc6b31aca520749df01a5e00e6df2b2 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:44:41 +0100 Subject: [PATCH 096/265] Fixed expression --- src/custom_functions/triangular_moving_average.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 238fadad..42c346cc 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,6 +2,7 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { + panic!("Not implemented"); Ok(Value::Int(columns.len() as i64)) } pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { From 52e43bd843dc863420b5386073545327cfd5de2b Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:59:23 +0100 Subject: [PATCH 097/265] Fixed expression --- src/value/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 1b1b5345..49e6d0b2 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -541,6 +541,30 @@ pub fn to_ffi_result(result: Result) -> FfiRes } } +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(err) => FfiResult { + value: T::default(), + error_code: -1, // Indicate a panic occurred + error_message: format!("A panic occurred during execution {:?}.",err), + }, + } +} + From 0baf44c6fe5609c120575330bc4b9c0e96546f1f Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:01:59 +0100 Subject: [PATCH 098/265] Fixed expression --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a7c481fb..1d98b625 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -544,7 +544,7 @@ pub use crate::{ operator::Operator, token::PartialToken, tree::Node, - value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult, to_ffi_result, Error}, + value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult, to_ffi_result, to_nested_ffi_result,Error}, }; mod context; From 3c6a8fa94cf6fc8abc35c8567fc54864b8c78268 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:17:18 +0100 Subject: [PATCH 099/265] panic now returns a custom error --- src/value/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 49e6d0b2..1ed01366 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -559,7 +559,7 @@ pub fn to_nested_ffi_result( }, Err(err) => FfiResult { value: T::default(), - error_code: -1, // Indicate a panic occurred + error_code: 8, // Indicate a panic occurred error_message: format!("A panic occurred during execution {:?}.",err), }, } From 78f56e951d9126b4fd5c1dc50b681641434b2f2c Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:23:17 +0100 Subject: [PATCH 100/265] panic now returns a custom error --- src/value/mod.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 1ed01366..d13b772a 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -557,11 +557,21 @@ pub fn to_nested_ffi_result( error_message: e.to_error_message().unwrap_or_else(|| "".to_string()), }, }, - Err(err) => FfiResult { - value: T::default(), - error_code: 8, // Indicate a panic occurred - error_message: format!("A panic occurred during execution {:?}.",err), - }, + 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, + } + } } } From 44291ce74ba24931423d434d2ce983cdf13cd297 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:25:39 +0100 Subject: [PATCH 101/265] panic now returns a custom error --- src/custom_functions/triangular_moving_average.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 42c346cc..97dc57bf 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,7 +2,7 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { - panic!("Not implemented"); + //panic!("Not implemented"); Ok(Value::Int(columns.len() as i64)) } pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { From caea8658ba68933f11d10b49f6d5c78c992315dc Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:26:46 +0100 Subject: [PATCH 102/265] panic now returns a custom error --- src/custom_functions/triangular_moving_average.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 97dc57bf..28297baa 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,12 +2,12 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { - //panic!("Not implemented"); + panic!("Not implemented"); Ok(Value::Int(columns.len() as i64)) } pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { if columns.len() < 3 { - return Ok(Value::Empty); // Not enough data to calculate + return Ok(Value::Empty); // Not engh data to calculate } let mut total_sum = 0.0; From 113980dd75d679e7b6c2bbc75f378ca7086971b9 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:45:25 +0100 Subject: [PATCH 103/265] panic now returns a custom error --- src/custom_functions/triangular_moving_average.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 28297baa..c62b9667 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,7 +2,7 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { - panic!("Not implemented"); + panic!("Not implemented 2"); Ok(Value::Int(columns.len() as i64)) } pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { From c1156678041da64a67325b465a6417ad8917eac0 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:48:16 +0100 Subject: [PATCH 104/265] panic now returns a custom error --- src/custom_functions/triangular_moving_average.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index c62b9667..8805b03b 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -2,7 +2,6 @@ use crate::{Error, Value}; use crate::Error::UnsupportedOperation; pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { - panic!("Not implemented 2"); Ok(Value::Int(columns.len() as i64)) } pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { From d00e0389ea7b6bebce1a4d851c1b25b075ce4521 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 15 Jun 2024 09:47:40 +0100 Subject: [PATCH 105/265] panic now returns a custom error --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 7964fb1a..941b3762 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -128,7 +128,7 @@ pub trait Context { pub trait OperatorRowTrait { fn get_value(&self, identifier: &str) -> EvalexprResult; fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()>; - fn set_row(&self, row: usize); + fn set_row(&mut self, row: usize); fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; fn has_changes(&self) -> bool; } From a5255975d0ebe5440767398bbc9b1b9f15b0e97a Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 15 Jun 2024 10:12:45 +0100 Subject: [PATCH 106/265] Added boxed operator schema trait --- src/context/mod.rs | 9 +++++++++ src/lib.rs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 941b3762..5e3750bc 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -128,9 +128,18 @@ pub trait Context { pub trait OperatorRowTrait { fn get_value(&self, identifier: &str) -> EvalexprResult; fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()>; + fn get_value_for_column(&self, col: usize) -> EvalexprResult; + fn set_value_for_column(&mut self, col: usize, value: Value) -> EvalexprResult<()>; fn set_row(&mut self, row: usize); fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; fn has_changes(&self) -> bool; + fn get_dirty_flags(&self) -> EvalexprResult>; +} +#[thin_trait_object] +pub trait OperatorSchemaTrait { + fn get_ordered_column_names(&self) -> EvalexprResult>; + fn get_column_name_for_column(&self, column: usize) -> EvalexprResult; + fn get_index_for_column(&self, column: &str) -> EvalexprResult; } /// This macro provides a convenient syntax for creating a static context. diff --git a/src/lib.rs b/src/lib.rs index 1d98b625..3f20452f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait, + Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait,BoxedOperatorSchemaTrait, HashMapContext, }, error::{EvalexprError, EvalexprResult}, From 435d56ae9eb8044a105ffdf56c2320d1dbfa5683 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 15 Jun 2024 11:53:30 +0100 Subject: [PATCH 107/265] Added boxed operator schema trait --- src/context/mod.rs | 23 +++++++++++++++++++++-- src/lib.rs | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 5e3750bc..a80d19fb 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -135,11 +135,30 @@ pub trait OperatorRowTrait { fn has_changes(&self) -> bool; fn get_dirty_flags(&self) -> EvalexprResult>; } + +#[thin_trait_object] +pub trait ActiveRowTrackerTrait { + fn all_active_rows(&self) -> EvalexprResult>; + fn all_changes(&self) -> EvalexprResult>; + fn set_active(&self, row: usize) -> EvalexprResult<()>; + fn is_active(&self, row: usize) -> EvalexprResult; +} + +#[repr(C)] +pub struct FFIColumn{ + pub name: String, + pub data_type: ValueType, + pub is_pk: bool, + pub meta_data: String +} + #[thin_trait_object] pub trait OperatorSchemaTrait { - fn get_ordered_column_names(&self) -> EvalexprResult>; - fn get_column_name_for_column(&self, column: usize) -> EvalexprResult; + fn get_schema(&self) -> EvalexprResult>; + fn get_column_for_index(&self, column: usize) -> EvalexprResult; fn get_index_for_column(&self, column: &str) -> EvalexprResult; + fn add_column(&mut self, column: FFIColumn) -> EvalexprResult<()>; + fn remove_column(&mut self, column_name: &str) -> EvalexprResult<()>; } /// This macro provides a convenient syntax for creating a static context. diff --git a/src/lib.rs b/src/lib.rs index 3f20452f..52c82946 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,7 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait,BoxedOperatorSchemaTrait, + Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait,BoxedOperatorSchemaTrait,BoxedActiveRowTrackerTrait,FFIColumn, HashMapContext, }, error::{EvalexprError, EvalexprResult}, From 4266cbb7c296415f94bdd73e9d9090e687927a1b Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 15 Jun 2024 12:04:51 +0100 Subject: [PATCH 108/265] Added boxed operator schema trait --- src/templates/test_utils.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 89568112..88e0e14c 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -35,7 +35,15 @@ impl OperatorRowTrait for MockRow { Ok(()) } - fn set_row(&self, row: usize) { + fn get_value_for_column(&self, col: usize) -> EvalexprResult { + todo!() + } + + fn set_value_for_column(&mut self, col: usize, value: Value) -> EvalexprResult<()> { + todo!() + } + + fn set_row(&mut self, row: usize) { todo!() } @@ -47,4 +55,7 @@ impl OperatorRowTrait for MockRow { todo!() } + fn get_dirty_flags(&self) -> EvalexprResult> { + todo!() + } } \ No newline at end of file From f43413a47f881f21a20dba7746bde6a346e0e23a Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 15 Jun 2024 13:33:43 +0100 Subject: [PATCH 109/265] Added boxed operator schema trait --- src/context/mod.rs | 18 +++++++++--------- src/templates/test_utils.rs | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index a80d19fb..9bfce531 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -126,22 +126,22 @@ pub trait Context { #[thin_trait_object] pub trait OperatorRowTrait { - fn get_value(&self, identifier: &str) -> EvalexprResult; - fn set_value(&mut self, identifier: &str, value: Value) -> EvalexprResult<()>; - fn get_value_for_column(&self, col: usize) -> EvalexprResult; - fn set_value_for_column(&mut self, col: usize, value: Value) -> EvalexprResult<()>; + fn get_value(&self, identifier: &str) -> Result; + 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_row(&mut self, row: usize); fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; fn has_changes(&self) -> bool; - fn get_dirty_flags(&self) -> EvalexprResult>; + fn get_dirty_flags(&self) -> Result,crate::Error>; } #[thin_trait_object] pub trait ActiveRowTrackerTrait { - fn all_active_rows(&self) -> EvalexprResult>; - fn all_changes(&self) -> EvalexprResult>; - fn set_active(&self, row: usize) -> EvalexprResult<()>; - fn is_active(&self, row: usize) -> EvalexprResult; + fn all_active_rows(&self) -> Result, crate::Error>; + fn all_changes(&self) -> Result, crate::Error>; + fn set_active(&self, row: usize) -> Result<(),crate::Error>; + fn is_active(&self, row: usize) -> Result; } #[repr(C)] diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 88e0e14c..fca93e78 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -20,7 +20,7 @@ impl MockRow { impl OperatorRowTrait for MockRow { // 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) -> EvalexprResult { + fn get_value(&self, column_name: &str) -> Result { let option = self.values.get(column_name); match option { None => { Ok(Value::Empty)} @@ -30,16 +30,16 @@ impl OperatorRowTrait for MockRow { } } - fn set_value(&mut self, column_name: &str, value: Value) ->EvalexprResult<()> { + fn set_value(&mut self, column_name: &str, value: Value) ->Result<(),crate::Error> { self.values.insert(column_name.to_string(), value); Ok(()) } - fn get_value_for_column(&self, col: usize) -> EvalexprResult { + fn get_value_for_column(&self, col: usize) -> Result { todo!() } - fn set_value_for_column(&mut self, col: usize, value: Value) -> EvalexprResult<()> { + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(),crate::Error> { todo!() } @@ -55,7 +55,7 @@ impl OperatorRowTrait for MockRow { todo!() } - fn get_dirty_flags(&self) -> EvalexprResult> { + fn get_dirty_flags(&self) -> Result,crate::Error> { todo!() } } \ No newline at end of file From 69512d70d2809604b22f158fadef248a77e48690 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 15 Jun 2024 16:05:36 +0100 Subject: [PATCH 110/265] Added dot net method accessors --- src/context/mod.rs | 1 + src/lib.rs | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 9bfce531..a771de8f 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -124,6 +124,7 @@ pub trait Context { fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; } + #[thin_trait_object] pub trait OperatorRowTrait { fn get_value(&self, identifier: &str) -> Result; diff --git a/src/lib.rs b/src/lib.rs index 52c82946..5e5fab86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -533,7 +533,32 @@ extern crate serde_derive; pub use crate::{ context::{ - Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait,BoxedOperatorSchemaTrait,BoxedActiveRowTrackerTrait,FFIColumn, + Context, + ContextWithMutableFunctions, + ContextWithMutableVariables, + EmptyContext, + OperatorRowTrait, + BoxedOperatorRowTrait, + BoxedOperatorSchemaTrait, + BoxedActiveRowTrackerTrait, + FFIColumn, + ActiveRowTrackerTrait_all_active_rows, + ActiveRowTrackerTrait_all_changes, + ActiveRowTrackerTrait_set_active, + 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_remove_column, HashMapContext, }, error::{EvalexprError, EvalexprResult}, From 3d8d99cb0f5ff075e713084c7e979893d43617f3 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 03:22:35 +0100 Subject: [PATCH 111/265] Added dot net method accessors --- Cargo.toml | 4 ++ src/context/mod.rs | 19 +++--- .../adaptive_stop_loss_trade_model.rs | 4 -- src/templates/test_utils.rs | 3 +- src/value/mod.rs | 2 +- src/value/value_type.rs | 1 + tests/moving_average_tests.rs | 60 +++++++++++++++++++ 7 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 tests/moving_average_tests.rs diff --git a/Cargo.toml b/Cargo.toml index c4753947..2068b5df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,17 @@ path = "src/lib.rs" [dependencies] regex = { version = "1.5.5", optional = true} serde = { version = "1.0.133", optional = true} +serde_json = "1.0.117" serde_derive = { version = "1.0.133", optional = true} +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"] } [features] serde_support = ["serde", "serde_derive"] +default = ["serde_support"] regex_support = ["regex"] [dev-dependencies] diff --git a/src/context/mod.rs b/src/context/mod.rs index a771de8f..f0202b1f 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -125,19 +125,19 @@ pub trait Context { } -#[thin_trait_object] +#[thin_trait_object(generate_dotnet_wrappers=true)] pub trait OperatorRowTrait { fn get_value(&self, identifier: &str) -> Result; 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_row(&mut self, row: usize); - fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult; + fn call_function(&self, idt: &str, argument: Value) -> Result; fn has_changes(&self) -> bool; fn get_dirty_flags(&self) -> Result,crate::Error>; } -#[thin_trait_object] +#[thin_trait_object(generate_dotnet_wrappers=true)] pub trait ActiveRowTrackerTrait { fn all_active_rows(&self) -> Result, crate::Error>; fn all_changes(&self) -> Result, crate::Error>; @@ -146,6 +146,7 @@ pub trait ActiveRowTrackerTrait { } #[repr(C)] +#[derive(Serialize, Deserialize)] pub struct FFIColumn{ pub name: String, pub data_type: ValueType, @@ -153,13 +154,13 @@ pub struct FFIColumn{ pub meta_data: String } -#[thin_trait_object] +#[thin_trait_object(generate_dotnet_wrappers=true)] pub trait OperatorSchemaTrait { - fn get_schema(&self) -> EvalexprResult>; - fn get_column_for_index(&self, column: usize) -> EvalexprResult; - fn get_index_for_column(&self, column: &str) -> EvalexprResult; - fn add_column(&mut self, column: FFIColumn) -> EvalexprResult<()>; - fn remove_column(&mut self, column_name: &str) -> EvalexprResult<()>; + fn get_schema(&self) -> Result, crate::Error>; + fn get_column_for_index(&self, column: usize) -> Result; + fn get_index_for_column(&self, column: &str) -> Result; + fn add_column(&mut self, column: FFIColumn) -> Result<(), crate::Error>; + fn remove_column(&mut self, column_name: &str) -> Result<(), crate::Error>; } /// This macro provides a convenient syntax for creating a static context. diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index a0d8d0a6..4406c375 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -47,11 +47,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { vec![self.signal_field.to_string(), self.value_field.to_string(), self.trading_range_field_name.to_string()] } fn commit_row(&self, row: &mut BoxedOperatorRowTrait, 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; diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index fca93e78..abc39a40 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -47,10 +47,11 @@ impl OperatorRowTrait for MockRow { todo!() } - fn call_function(&self, idt: &str, argument: &Value) -> EvalexprResult { + fn call_function(&self, idt: &str, argument: Value) -> Result { todo!() } + fn has_changes(&self) -> bool { todo!() } diff --git a/src/value/mod.rs b/src/value/mod.rs index d13b772a..b7116753 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -315,7 +315,7 @@ use std::ops::{Div, Rem}; use std::ops::Mul; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum Error { UnsupportedArithmeticBetweenTypes, UnsupportedOperation, diff --git a/src/value/value_type.rs b/src/value/value_type.rs index 1fa3bf29..db72087e 100644 --- a/src/value/value_type.rs +++ b/src/value/value_type.rs @@ -2,6 +2,7 @@ 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. diff --git a/tests/moving_average_tests.rs b/tests/moving_average_tests.rs new file mode 100644 index 00000000..da6c12aa --- /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")?) + } +} From 9a3e8e581744af748943bb8dd3ad50f1d0bc7217 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 03:28:08 +0100 Subject: [PATCH 112/265] Added dot net method accessors --- Cargo.toml | 4 +- tests/moving_average_tests.rs | 120 +++++++++++++++++----------------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2068b5df..ffc2df84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,8 @@ regex = { version = "1.5.5", optional = true} serde = { version = "1.0.133", optional = true} serde_json = "1.0.117" serde_derive = { version = "1.0.133", optional = true} -csv = "1.1" -anyhow = "1.0.86" +#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"] } diff --git a/tests/moving_average_tests.rs b/tests/moving_average_tests.rs index da6c12aa..4b33992c 100644 --- a/tests/moving_average_tests.rs +++ b/tests/moving_average_tests.rs @@ -1,60 +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")?) - } -} +// +// 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")?) +// } +// } From d9eb9c24c0a6d34ceb39b36584bd70177d69a71d Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 03:44:17 +0100 Subject: [PATCH 113/265] Added dot net method accessors --- Cargo.toml | 7 ++++++- src/context/mod.rs | 9 ++++++--- src/lib.rs | 28 +++++++++++++++++----------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ffc2df84..360f9c31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ path = "src/lib.rs" [dependencies] regex = { version = "1.5.5", optional = true} serde = { version = "1.0.133", optional = true} -serde_json = "1.0.117" serde_derive = { version = "1.0.133", optional = true} #csv = "1.1" #anyhow = "1.0.86" @@ -33,10 +32,16 @@ thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_ob chrono = { version = "0.4.19", default-features = false, features = ["clock", "std"] } + [features] serde_support = ["serde", "serde_derive"] default = ["serde_support"] regex_support = ["regex"] +serde_json_support = ["serde_json"] + +[dependencies.serde_json] +version = "*" +optional = true [dev-dependencies] ron = "0.7.0" diff --git a/src/context/mod.rs b/src/context/mod.rs index f0202b1f..ce998236 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -125,7 +125,8 @@ pub trait Context { } -#[thin_trait_object(generate_dotnet_wrappers=true)] +#[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 set_value(&mut self, identifier: &str, value: Value) -> Result<(),crate::Error>; @@ -137,7 +138,8 @@ pub trait OperatorRowTrait { fn get_dirty_flags(&self) -> Result,crate::Error>; } -#[thin_trait_object(generate_dotnet_wrappers=true)] +#[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>; @@ -154,7 +156,8 @@ pub struct FFIColumn{ pub meta_data: String } -#[thin_trait_object(generate_dotnet_wrappers=true)] +#[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; diff --git a/src/lib.rs b/src/lib.rs index 5e5fab86..770d77b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -542,6 +542,22 @@ pub use crate::{ BoxedOperatorSchemaTrait, BoxedActiveRowTrackerTrait, FFIColumn, + HashMapContext, + }, + 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, FfiResult, to_ffi_result, to_nested_ffi_result,Error}, +}; + +#[cfg(feature = "serde_json_support")] +pub use crate::{ + context::{ ActiveRowTrackerTrait_all_active_rows, ActiveRowTrackerTrait_all_changes, ActiveRowTrackerTrait_set_active, @@ -559,17 +575,7 @@ pub use crate::{ OperatorSchemaTrait_get_index_for_column, OperatorSchemaTrait_add_column, OperatorSchemaTrait_remove_column, - HashMapContext, - }, - 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, FfiResult, to_ffi_result, to_nested_ffi_result,Error}, + } }; mod context; From 8d34c762bb7d2bfff0782c9b24330db5d3d5f854 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 09:47:03 +0100 Subject: [PATCH 114/265] Added dot net method accessors --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 770d77b1..ffb41d48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -539,7 +539,9 @@ pub use crate::{ EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait, + OperatorSchemaTrait, BoxedOperatorSchemaTrait, + ActiveRowTrackerTrait, BoxedActiveRowTrackerTrait, FFIColumn, HashMapContext, From 33133ff2863be3d743713909203d3f7024d30aab Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:14:05 +0100 Subject: [PATCH 115/265] Added dot net method accessors --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index ce998236..c09bb0ec 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -142,7 +142,7 @@ pub trait OperatorRowTrait { #[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 all_changes(&self) -> Result, crate::Error>; fn set_active(&self, row: usize) -> Result<(),crate::Error>; fn is_active(&self, row: usize) -> Result; } From 6879f232187d410c33212dfacd289946b12402cb Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:27:56 +0100 Subject: [PATCH 116/265] Added dot net method accessors --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index c09bb0ec..ed7dfc58 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -134,7 +134,7 @@ pub trait OperatorRowTrait { fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(),crate::Error>; fn set_row(&mut self, row: usize); fn call_function(&self, idt: &str, argument: Value) -> Result; - fn has_changes(&self) -> bool; + fn has_changes(&self) -> Result; fn get_dirty_flags(&self) -> Result,crate::Error>; } From 63035941057a076ac9929ba4b5a7dcce1bbfd7f1 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:30:32 +0100 Subject: [PATCH 117/265] Added dot net method accessors --- src/templates/test_utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index abc39a40..da01bc0b 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -51,11 +51,11 @@ impl OperatorRowTrait for MockRow { todo!() } - - fn has_changes(&self) -> bool { + fn has_changes(&self) -> Result { todo!() } + fn get_dirty_flags(&self) -> Result,crate::Error> { todo!() } From dd3e37cfb9a49cf925a073375c94d586cbb06393 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:34:08 +0100 Subject: [PATCH 118/265] Added dot net method accessors --- src/value/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index b7116753..0c17fb5c 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; use std::convert::TryInto; +use std::fmt; use crate::error::{EvalexprError, EvalexprResult}; mod display; @@ -327,6 +328,27 @@ pub enum Error { 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)) From 6ce8a1df7601c0daed1249bfa0b3c15ef3ffff8b Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:29:01 +0100 Subject: [PATCH 119/265] Added dot net method accessors --- src/context/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index ed7dfc58..468d5835 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -165,6 +165,15 @@ pub trait OperatorSchemaTrait { fn add_column(&mut self, column: FFIColumn) -> Result<(), crate::Error>; fn remove_column(&mut self, column_name: &str) -> 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 OperatorStatusContainerTrait { + fn statuses(&self) -> Result, crate::Error>; + fn changes(&self) -> Result, crate::Error>; + fn add(&self, status: u8, context: &str) -> Result<(), crate::Error>; + fn remove(&self, status: u8) -> Result<(), crate::Error>; + fn contains(&mut self, status: u8) -> Result; +} /// This macro provides a convenient syntax for creating a static context. /// From d0c0d922f1f82c994efc90742aa7ad534b27cbae Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 13:32:31 +0100 Subject: [PATCH 120/265] Added dot net method accessors --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ffb41d48..8fb914e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -577,6 +577,11 @@ pub use crate::{ OperatorSchemaTrait_get_index_for_column, OperatorSchemaTrait_add_column, OperatorSchemaTrait_remove_column, + OperatorStatusContainerTrait_add, + OperatorStatusContainerTrait_remove, + OperatorStatusContainerTrait_changes, + OperatorStatusContainerTrait_contains, + OperatorStatusContainerTrait_statuses, } }; From e9a1420cce750d7fcac021e525bbbf4d26ea22af Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:03:49 +0100 Subject: [PATCH 121/265] Added dot net method accessors --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 8fb914e8..dad7fd49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -539,6 +539,7 @@ pub use crate::{ EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait, + BoxedOperatorStatusContainerTrait, OperatorSchemaTrait, BoxedOperatorSchemaTrait, ActiveRowTrackerTrait, From e5d38c131ea8105cb7deb2c5f77bd74642eb2afe Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:17:21 +0100 Subject: [PATCH 122/265] Added dot net method accessors --- src/context/mod.rs | 6 +++--- src/lib.rs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 468d5835..570bb572 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -143,7 +143,7 @@ pub trait OperatorRowTrait { pub trait ActiveRowTrackerTrait { fn all_active_rows(&self) -> Result, crate::Error>; fn all_changes(&self) -> Result, crate::Error>; - fn set_active(&self, row: usize) -> Result<(),crate::Error>; + fn set_active(&mut self, row: usize) -> Result<(),crate::Error>; fn is_active(&self, row: usize) -> Result; } @@ -170,8 +170,8 @@ pub trait OperatorSchemaTrait { pub trait OperatorStatusContainerTrait { fn statuses(&self) -> Result, crate::Error>; fn changes(&self) -> Result, crate::Error>; - fn add(&self, status: u8, context: &str) -> Result<(), crate::Error>; - fn remove(&self, status: u8) -> Result<(), crate::Error>; + fn add(&mut self, status: u8, context: &str) -> Result<(), crate::Error>; + fn remove(&mut self, status: u8) -> Result<(), crate::Error>; fn contains(&mut self, status: u8) -> Result; } diff --git a/src/lib.rs b/src/lib.rs index dad7fd49..03612371 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -539,6 +539,7 @@ pub use crate::{ EmptyContext, OperatorRowTrait, BoxedOperatorRowTrait, + OperatorStatusContainerTrait, BoxedOperatorStatusContainerTrait, OperatorSchemaTrait, BoxedOperatorSchemaTrait, From 6893aaf6c05f49e75a814c7dade53c84bd9f8eb2 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:20:22 +0100 Subject: [PATCH 123/265] Added dot net method accessors --- src/context/mod.rs | 4 +++- src/lib.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 570bb572..7b137bf8 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -143,7 +143,9 @@ pub trait OperatorRowTrait { pub trait ActiveRowTrackerTrait { fn all_active_rows(&self) -> Result, crate::Error>; fn all_changes(&self) -> Result, crate::Error>; - fn set_active(&mut self, row: usize) -> 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; } diff --git a/src/lib.rs b/src/lib.rs index 03612371..cf53ea97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -564,7 +564,9 @@ pub use crate::{ context::{ ActiveRowTrackerTrait_all_active_rows, ActiveRowTrackerTrait_all_changes, - ActiveRowTrackerTrait_set_active, + ActiveRowTrackerTrait_handle_add, + ActiveRowTrackerTrait_handle_update, + ActiveRowTrackerTrait_handle_remove, ActiveRowTrackerTrait_is_active, OperatorRowTrait_get_value, OperatorRowTrait_set_value, From 92caa07b88142ece5a79cc01f88eb50a412447da Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:21:08 +0100 Subject: [PATCH 124/265] Added dot net method accessors --- tests/close_values.csv | 3392 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3392 insertions(+) create mode 100644 tests/close_values.csv 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 From 16eda1b903e10a3608897c73664b059ede434e9c Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 14:58:44 +0100 Subject: [PATCH 125/265] Added dot net method accessors --- src/context/mod.rs | 6 ++++++ src/lib.rs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index 7b137bf8..0b4055c1 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -166,6 +166,12 @@ pub trait OperatorSchemaTrait { fn get_index_for_column(&self, column: &str) -> Result; fn add_column(&mut self, column: FFIColumn) -> Result<(), crate::Error>; fn remove_column(&mut self, column_name: &str) -> Result<(), crate::Error>; + + fn get_value(&self, identifier: &str) -> Result; + 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_row(&mut self, row: usize); } #[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))] diff --git a/src/lib.rs b/src/lib.rs index cf53ea97..15894cff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -580,6 +580,10 @@ pub use crate::{ 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, From 7dfd2a2edc8279c0ce4277297427e46c726586b4 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:30:45 +0100 Subject: [PATCH 126/265] Added dot net method accessors --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 0b4055c1..2e892038 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -150,7 +150,7 @@ pub trait ActiveRowTrackerTrait { } #[repr(C)] -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize,Debug)] pub struct FFIColumn{ pub name: String, pub data_type: ValueType, From 8602fb374af0691e03374739bec2ccebcaa1ea5e Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:14:40 +0100 Subject: [PATCH 127/265] Added dot net method accessors --- src/context/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 2e892038..f4c9387e 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -171,7 +171,6 @@ pub trait OperatorSchemaTrait { 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_row(&mut self, row: usize); } #[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))] From 1b02c034f258d7e778e2ce34f602f81903bf384e Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:15:40 +0100 Subject: [PATCH 128/265] Added dot net method accessors --- src/context/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index f4c9387e..02cb06f6 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -167,10 +167,10 @@ pub trait OperatorSchemaTrait { fn add_column(&mut self, column: FFIColumn) -> Result<(), crate::Error>; fn remove_column(&mut self, column_name: &str) -> Result<(), crate::Error>; - fn get_value(&self, identifier: &str) -> Result; - 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 get_value(&self, identifier: &str, row: usize) -> Result; + fn set_value(&mut self, identifier: &str, 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=true))] #[cfg_attr(not(feature = "serde_json_support"), thin_trait_object(generate_dotnet_wrappers=false))] From 4ac2ac1f95bbc2e491673029dd1ac10ab1275503 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 17 Jun 2024 05:07:46 +0100 Subject: [PATCH 129/265] Added dot net method accessors --- Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 360f9c31..ae7af1cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,11 +37,14 @@ chrono = { version = "0.4.19", default-features = false, features = ["clock", "s serde_support = ["serde", "serde_derive"] default = ["serde_support"] regex_support = ["regex"] -serde_json_support = ["serde_json"] +serde_json_support = ["serde_json", "log"] [dependencies.serde_json] version = "*" optional = true +[dependencies.log] +version = "*" +optional = true [dev-dependencies] ron = "0.7.0" From e237afc2b3a4caeb27d0cf0af24cc07fd1f80b98 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 17 Jun 2024 05:28:50 +0100 Subject: [PATCH 130/265] Added dot net method accessors --- src/context/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 02cb06f6..5318d7a4 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -163,12 +163,12 @@ pub struct FFIColumn{ 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: &str) -> 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: &str) -> Result<(), crate::Error>; + fn remove_column(&mut self, column_name: String) -> Result<(), crate::Error>; - fn get_value(&self, identifier: &str, row: usize) -> Result; - fn set_value(&mut self, identifier: &str, row: usize, value: Value) -> 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>; } @@ -177,7 +177,7 @@ pub trait OperatorSchemaTrait { pub trait OperatorStatusContainerTrait { fn statuses(&self) -> Result, crate::Error>; fn changes(&self) -> Result, crate::Error>; - fn add(&mut self, status: u8, context: &str) -> Result<(), crate::Error>; + fn add(&mut self, status: u8, context: String) -> Result<(), crate::Error>; fn remove(&mut self, status: u8) -> Result<(), crate::Error>; fn contains(&mut self, status: u8) -> Result; } From 93365c35ec9d6602264387b72f37e3c59ba19277 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:00:31 +0100 Subject: [PATCH 131/265] Added dot net method accessors --- src/context/mod.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 5318d7a4..ddc8dae4 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -150,12 +150,23 @@ pub trait ActiveRowTrackerTrait { } #[repr(C)] -#[derive(Serialize, Deserialize,Debug)] -pub struct FFIColumn{ +#[derive(Serialize, Deserialize, Debug)] +pub struct FFIColumn { pub name: String, pub data_type: ValueType, + #[serde(default = "default_is_pk")] pub is_pk: bool, - pub meta_data: String + #[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))] From 68d610edec8fc6baa13d6ddd10d46701275c0b73 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 18 Jun 2024 07:37:45 +0100 Subject: [PATCH 132/265] Added dot net method accessors --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index ddc8dae4..ff77c8ba 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -188,7 +188,7 @@ pub trait OperatorSchemaTrait { pub trait OperatorStatusContainerTrait { fn statuses(&self) -> Result, crate::Error>; fn changes(&self) -> Result, crate::Error>; - fn add(&mut self, status: u8, context: String) -> 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; } From f9403af2fe4ac0fbfbefa72170b075462cd9a3ce Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:44:13 +0100 Subject: [PATCH 133/265] Added dot net method accessors --- src/templates/adaptive_stop_loss_trade_model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 4406c375..5c568820 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -100,8 +100,8 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { loop_trade_closed = true; exit_price = Some(current_close_value); 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 > loop_break_even { - stop_loss = Some(loop_initiation_price - (trading_range * self.break_even_threshold)); + } else if current_close_value > (loop_stop_loss + (trading_range * self.break_even_threshold)) { + 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(); From 34164ae6a8336e80ab5134b5ee83439a7e82deb5 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:56:51 +0100 Subject: [PATCH 134/265] Added dot net method accessors --- src/templates/adaptive_stop_loss_trade_model.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 5c568820..bbd47a5d 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -54,7 +54,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { let mut initiation_date: Option = None; let mut stop_loss: Option = None; let mut take_profit: Option = None; - let mut break_even: Option = None; + //let mut break_even: Option = None; let mut delta: Option = None; let mut reason: Option = None; @@ -65,7 +65,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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()?; + //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()?; } @@ -89,8 +89,8 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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 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); delta = Some(current_close_value - loop_initiation_price); if current_close_value <= loop_stop_loss { loop_trade_closed = true; @@ -100,7 +100,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { loop_trade_closed = true; exit_price = Some(current_close_value); 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 > (loop_stop_loss + (trading_range * self.break_even_threshold)) { + } else if current_close_value > next_stop_loss_step { stop_loss = Some(loop_stop_loss + (trading_range * self.break_even_threshold)); } } else { @@ -111,7 +111,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { initiation_date = Some(get_string(&transpose_value)); 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)); + //break_even = Some(current_close_value + (trading_range *self.break_even_threshold)); active_trade = Some(true); } } @@ -125,7 +125,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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("break_even", transpose_value), break_even.clone().map(|rs| Value::Float(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; @@ -138,7 +138,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { stop_loss = None; take_profit = None; - break_even = None; + // break_even = None; } } } From f5307bebd94a2b97a9c64e55659bda4e501ce505 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:35:15 +0100 Subject: [PATCH 135/265] Added dot net method accessors --- .../adaptive_stop_loss_trade_model.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index bbd47a5d..60de0644 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use std::fmt::Display; -use crate::{context, get_string}; +use crate::{context, get_string, IntType}; use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; @@ -37,6 +37,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ("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), @@ -57,11 +58,14 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { //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()?; 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()?; @@ -92,10 +96,13 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { //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); delta = Some(current_close_value - loop_initiation_price); + trade_age = Some(context(trade_age, "Should have trade age for active trade")? + 1); if current_close_value <= loop_stop_loss { - loop_trade_closed = true; - exit_price = Some(current_close_value); - 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)); + if trade_age.unwrap_or_default() >= MIN_TRADE_DURATION { + loop_trade_closed = true; + exit_price = Some(current_close_value); + 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); @@ -113,6 +120,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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); } } } @@ -125,6 +133,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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); @@ -135,7 +144,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { active_trade = Some(false); initiation_price = None; initiation_date = None; - + trade_age = None; stop_loss = None; take_profit = None; // break_even = None; From 921cdf201699d38e80c8aa99ce5ffb72e4baaaa8 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:01:38 +0100 Subject: [PATCH 136/265] Added dot net method accessors --- src/templates/adaptive_stop_loss_trade_model.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 60de0644..474196b5 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -6,7 +6,8 @@ use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, pub struct AdaptiveStopLossTradeModel { signal_field: String, - value_field: String, + close_value_field: String, + ask_value_field: String, trading_range_field_name: String, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, @@ -15,10 +16,11 @@ pub struct AdaptiveStopLossTradeModel { impl AdaptiveStopLossTradeModel { - pub fn new(signal_field_name: &str, value_field_name: &str,trading_range_field_name: &str, stop_loss_threshold: FloatType, take_profit_threshold: FloatType, break_even_threshold: FloatType) -> AdaptiveStopLossTradeModel { + pub fn new(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 { signal_field: signal_field_name.to_string(), - value_field: value_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, @@ -45,7 +47,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() } fn dependencies(&self) -> Vec { - vec![self.signal_field.to_string(), self.value_field.to_string(), self.trading_range_field_name.to_string()] + vec![self.signal_field.to_string(), self.close_value_field.to_string(), self.trading_range_field_name.to_string()] } fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { let mut prev_trade_signal: Option = None; @@ -80,9 +82,10 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { for i in cycle_epoch ..ordered_transpose_values.len() { let transpose_value = &ordered_transpose_values[i]; - if let (Some(current_close_value),Some(trading_range)) = + if let (Some(current_close_value),Some(current_ask_value),Some(trading_range)) = ( - row.get_value(&generate_column_name(&self.value_field, transpose_value))?.as_float_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()? ) { @@ -114,7 +117,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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_close_value); + initiation_price = Some(current_ask_value); initiation_date = Some(get_string(&transpose_value)); stop_loss = Some(current_close_value - (trading_range *self.stop_loss_threshold)); take_profit = Some(current_close_value + (trading_range *self.take_profit_threshold)); From 1dea95a47082cd7b664b984285cfd3a691776826 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:22:12 +0100 Subject: [PATCH 137/265] Fixed adaptive stop loss model --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 474196b5..9b690e8b 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -47,7 +47,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() } fn dependencies(&self) -> Vec { - vec![self.signal_field.to_string(), self.close_value_field.to_string(), self.trading_range_field_name.to_string()] + vec![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, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { let mut prev_trade_signal: Option = None; From 8e4551136e57c0be89edac6f0b20fedf1a13d122 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:33:06 +0100 Subject: [PATCH 138/265] Fixed adaptive stop loss model --- src/custom_functions/expression_functions.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 7e55ce66..a52a7bbd 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -57,6 +57,20 @@ pub fn safe_divide,TR: Into>(left: TL, right: TR) -> Resu } } + +pub fn substring(message: &Value, start: usize, len: usize) -> Result { + if let Value::String(message) = message { + // Ensure start is within bounds and len does not exceed the message length + if start < message.len() { + let end = if start + len > message.len() { message.len() } else { start + len }; + let substring = &message[start..end]; + return Ok(Value::String(substring.to_string())); + } + } + return Ok(Value::String("".to_string())); +} + + pub fn starts_with(message: &Value, prefix: &Value) -> Result { if let (Value::String(message), Value::String(prefix)) = (message, prefix) { if message.starts_with(prefix) { From 58930cd91257fe0a2905ceb464e42105f8d3260b Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:41:58 +0100 Subject: [PATCH 139/265] Fixed substring --- src/custom_functions/expression_functions.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index a52a7bbd..7a4f553e 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -58,12 +58,14 @@ pub fn safe_divide,TR: Into>(left: TL, right: TR) -> Resu } -pub fn substring(message: &Value, start: usize, len: usize) -> Result { +pub fn substring(message: &Value, start: &Value, len: &Value) -> Result { if let Value::String(message) = message { // Ensure start is within bounds and len does not exceed the message length - if start < message.len() { - let end = if start + len > message.len() { message.len() } else { start + len }; - let substring = &message[start..end]; + let start_int = start.as_int()? as usize; + let len_int = len.as_int()? as usize; + 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(Value::String(substring.to_string())); } } From 5cc10a9dd6c626119400d8ce00c20f1b1695a36a Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:46:20 +0100 Subject: [PATCH 140/265] Fixed substring --- src/custom_functions/expression_functions.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 7a4f553e..b61a51ed 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -82,6 +82,18 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { return Ok(Value::Boolean(false)); } +pub fn ternary(condition: &Value, true_value: &Value, false_value: &Value) -> Result { + if let Value::Boolean(cond) = condition { + if *cond { + return Ok(true_value.clone()); + } else { + return Ok(false_value.clone()); + } + } + // 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), From 74d73f2851dc1e574e3f32eb53282487e449ba4a Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:59:17 +0100 Subject: [PATCH 141/265] Fixed substring --- src/custom_functions/expression_functions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index b61a51ed..14da4eed 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -82,12 +82,12 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { return Ok(Value::Boolean(false)); } -pub fn ternary(condition: &Value, true_value: &Value, false_value: &Value) -> Result { +pub fn ternary,TR: Into>(condition: &Value, true_value: TL, false_value: TR) -> Result { if let Value::Boolean(cond) = condition { if *cond { - return Ok(true_value.clone()); + return Ok(true_value.into()); } else { - return Ok(false_value.clone()); + return Ok(false_value.into()); } } // Return an error if the first parameter is not a boolean From cf6199385df0ee17600c1ca74632d61ef1efe254 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:20:51 +0100 Subject: [PATCH 142/265] Fixed delta --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 9b690e8b..a12d58c3 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -98,12 +98,12 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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); - delta = Some(current_close_value - loop_initiation_price); 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 { From c0ccd5951b35125ea7f34d07a4c41d32102c1b98 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:15:18 +0100 Subject: [PATCH 143/265] Fixed delta --- .../compiled_transpose_calcuation_template.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/templates/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calcuation_template.rs index bec441ed..1eb4e8fb 100644 --- a/src/templates/compiled_transpose_calcuation_template.rs +++ b/src/templates/compiled_transpose_calcuation_template.rs @@ -1,5 +1,8 @@ +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::Error::CustomError; @@ -10,6 +13,19 @@ pub trait CompiledTransposeCalculationTemplate : Send { fn schema(&self) -> HashMap; fn dependencies(&self) -> Vec; fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>; + fn commit_row_wrapped(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>{ + panic::catch_unwind(AssertUnwindSafe(|| self.commit_row(row, 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>() { + format!("Caught a panic with a string: {}", s) + } else if let Some(s) = err.downcast_ref::() { + format!("Caught a panic with a string: {}", s) + } else { + format!("Caught a panic with an unknown type.") + } } pub fn context(sself : Option, context: C) -> Result From 2c85568756d55038a1d56d248b1c2ec0057b6e14 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:27:43 +0100 Subject: [PATCH 144/265] Fixed delta --- src/templates/adaptive_stop_loss_trade_model.rs | 1 + src/templates/compiled_transpose_calcuation_template.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index a12d58c3..22a26325 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -109,6 +109,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { } 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)); diff --git a/src/templates/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calcuation_template.rs index 1eb4e8fb..9f2771f4 100644 --- a/src/templates/compiled_transpose_calcuation_template.rs +++ b/src/templates/compiled_transpose_calcuation_template.rs @@ -20,9 +20,9 @@ pub trait CompiledTransposeCalculationTemplate : Send { pub fn extract_message_from_any_error(err: Box) -> String { if let Some(s) = err.downcast_ref::<&str>() { - format!("Caught a panic with a string: {}", s) + s.to_owned() } else if let Some(s) = err.downcast_ref::() { - format!("Caught a panic with a string: {}", s) + s.to_owned() } else { format!("Caught a panic with an unknown type.") } From db223fcdf91a10819b768dc42647ccd54da45002 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:30:06 +0100 Subject: [PATCH 145/265] Fixed delta --- src/templates/compiled_transpose_calcuation_template.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calcuation_template.rs index 9f2771f4..f45bad86 100644 --- a/src/templates/compiled_transpose_calcuation_template.rs +++ b/src/templates/compiled_transpose_calcuation_template.rs @@ -20,9 +20,9 @@ pub trait CompiledTransposeCalculationTemplate : Send { pub fn extract_message_from_any_error(err: Box) -> String { if let Some(s) = err.downcast_ref::<&str>() { - s.to_owned() + s.to_string() } else if let Some(s) = err.downcast_ref::() { - s.to_owned() + s.to_string() } else { format!("Caught a panic with an unknown type.") } From 690038e6d9231d94ff72783568aa08584dfe6025 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:53:53 +0100 Subject: [PATCH 146/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 22a26325..a1149a97 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -9,6 +9,7 @@ pub struct AdaptiveStopLossTradeModel { 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 @@ -16,8 +17,9 @@ pub struct AdaptiveStopLossTradeModel { impl AdaptiveStopLossTradeModel { - pub fn new(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 { + 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(), @@ -35,6 +37,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { vec![ ("active_trade", ValueType::Boolean), ("initiation_date", ValueType::String), + ("trade_id", ValueType::String), ("reason", ValueType::String), ("initiation_price", ValueType::Float), ("exit_price", ValueType::Float), @@ -55,6 +58,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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; @@ -66,6 +70,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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()?; @@ -82,8 +87,9 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { for i in cycle_epoch ..ordered_transpose_values.len() { let transpose_value = &ordered_transpose_values[i]; - if let (Some(current_close_value),Some(current_ask_value),Some(trading_range)) = + 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()? @@ -120,6 +126,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); @@ -133,6 +140,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { row.set_value(&generate_column_name("reason", transpose_value), reason.clone().map(|rs| Value::String(rs)).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)).unwrap_or(Value::Empty))?; + row.set_value(&generate_column_name("trade_id", transpose_value), trade_id.clone().map(|rs| Value::String(rs)).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))?; @@ -148,6 +156,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { active_trade = Some(false); initiation_price = None; initiation_date = None; + trade_id = None; trade_age = None; stop_loss = None; take_profit = None; From f577f3496da530839b85d5fe50243c9b4409d70c Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:05:41 +0100 Subject: [PATCH 147/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index a1149a97..082edce5 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -50,7 +50,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() } fn dependencies(&self) -> Vec { - vec![self.signal_field.to_string(), self.close_value_field.to_string(), self.trading_range_field_name.to_string(), self.ask_value_field .to_string()] + 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, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { let mut prev_trade_signal: Option = None; From 3fcf9b31e48d18eaa3928cf9089dc3c8fc493e8a Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:17:11 +0100 Subject: [PATCH 148/265] Added trade id field --- src/custom_functions/expression_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 14da4eed..dafd754c 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -82,7 +82,7 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { return Ok(Value::Boolean(false)); } -pub fn ternary,TR: Into>(condition: &Value, true_value: TL, false_value: TR) -> Result { +pub fn ternary,TL: Into,TR: Into>(condition: TC, true_value: TL, false_value: TR) -> Result { if let Value::Boolean(cond) = condition { if *cond { return Ok(true_value.into()); From 6c2b3b64700f990015aa10103f89650e09318759 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:19:27 +0100 Subject: [PATCH 149/265] Added trade id field --- src/custom_functions/expression_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index dafd754c..6ad27284 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -84,7 +84,7 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { pub fn ternary,TL: Into,TR: Into>(condition: TC, true_value: TL, false_value: TR) -> Result { if let Value::Boolean(cond) = condition { - if *cond { + if cond { return Ok(true_value.into()); } else { return Ok(false_value.into()); From 181752811891160a3ddb4a43addf61cc2a072758 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:22:29 +0100 Subject: [PATCH 150/265] Added trade id field --- src/custom_functions/expression_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 6ad27284..4aaaf2b8 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -83,7 +83,7 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { } pub fn ternary,TL: Into,TR: Into>(condition: TC, true_value: TL, false_value: TR) -> Result { - if let Value::Boolean(cond) = condition { + if let Value::Boolean(cond) = condition.into() { if cond { return Ok(true_value.into()); } else { From 7d46a3c746e9b1f9298ff90279e29284a28659b7 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 3 Aug 2024 09:39:06 +0100 Subject: [PATCH 151/265] Added trade id field --- src/custom_functions/expression_functions.rs | 8 ++++---- tests/serde.rs | 9 ++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 4aaaf2b8..688a493c 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -58,11 +58,11 @@ pub fn safe_divide,TR: Into>(left: TL, right: TR) -> Resu } -pub fn substring(message: &Value, start: &Value, len: &Value) -> Result { - if let Value::String(message) = message { +pub fn substring,TR: Into, TC: Into>(message: TL, start: TR, len: TC) -> Result { + if let Value::String(message) = message.into() { // Ensure start is within bounds and len does not exceed the message length - let start_int = start.as_int()? as usize; - let len_int = len.as_int()? as usize; + let start_int = Into::::into(start).as_int()? as usize; + let len_int = Into::::into(len).as_int()? as usize; 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]; diff --git a/tests/serde.rs b/tests/serde.rs index 410e167e..bdff36e8 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()); + + println!("{:?}", ron::ser::to_string(&string).unwrap()); +} + #[test] fn test_serde_errors() { assert_eq!( From b0a02071e34e58333e49d04d8ce02f68b45aef32 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 3 Aug 2024 09:49:13 +0100 Subject: [PATCH 152/265] Added trade id field --- src/custom_functions/expression_functions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 688a493c..b6bc83fb 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -111,8 +111,8 @@ fn round_datetime_to_precision(datetime: DateTime, precision: &str) -> Resu }) } -pub fn round_date_to_precision(string: &Value, precision: &Value) -> Result { - if let (Value::String(string), Value::String(precision)) = (string, precision) { +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 parts: Vec<&str> = string.split('_').collect(); let datetime_str = parts.last().ok_or_else(|| Error::InvalidInputString)?; From 99ad389f3d3f9bd18a7ac7a6d72504f7f8a59081 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:08:21 +0100 Subject: [PATCH 153/265] Added trade id field --- src/custom_functions/expression_functions.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index b6bc83fb..e6f0c0d6 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -121,8 +121,11 @@ pub fn round_date_to_precision,TR: Into>(string: TL, prec .map_err(|_| Error::InvalidDateFormat)?; let datetime = Utc.from_utc_datetime(&naive_datetime); let rounded_datetime = round_datetime_to_precision(datetime, &precision.to_lowercase())?; - let result = format!("{}_{}", parts.iter().take(parts.len() - 1).map(|prt|prt.to_string()).collect::>().join("_"), rounded_datetime.format("%Y.%m.%d %H:%M:%S").to_string()); - + 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(Value::String(result)) } else { // If arguments are not strings, return an error From d4aa3396f71db5a8c7f4baf496a67c1baf3557f0 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:00:27 +0100 Subject: [PATCH 154/265] Added trade id field --- src/custom_functions/expression_functions.rs | 21 ++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index e6f0c0d6..0fb2f59f 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -1,5 +1,8 @@ +use std::convert::{TryFrom, TryInto}; +use std::fmt::Debug; use crate::{Error, FloatType, Value}; use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; +use crate::Error::CustomError; pub fn is_null>(value: T) -> Result { Ok(match value.into() { @@ -8,8 +11,11 @@ pub fn is_null>(value: T) -> Result { }) } -pub fn abs>(value: T) -> Result { - match value.into() { +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)} @@ -57,6 +63,17 @@ pub fn safe_divide,TR: Into>(left: TL, right: TR) -> Resu } } +impl TryFrom> for Value { + type Error = String; + + fn try_from(value: Result) -> Result { + match value { + Ok(num) => Ok(num), + Err(e) => Err(e), + } + } +} + pub fn substring,TR: Into, TC: Into>(message: TL, start: TR, len: TC) -> Result { if let Value::String(message) = message.into() { From d4de2a95f08799c7c0c47d8bd6b3dcaea5938980 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 6 Aug 2024 02:45:19 +0100 Subject: [PATCH 155/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 082edce5..2f02af58 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -118,7 +118,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); + //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(); From c3661bd0b06df1d8229480a34651128a71999d56 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 6 Aug 2024 02:46:17 +0100 Subject: [PATCH 156/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 2f02af58..082edce5 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -118,7 +118,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); + 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(); From e9b9d90d6b57434332e3d72b1b0d124504c2d04f Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:36:40 +0100 Subject: [PATCH 157/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 082edce5..2f02af58 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -118,7 +118,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); + //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(); From 35e6b802a4226b8a97f7b4806484f22f9e3bd942 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:03:21 +0100 Subject: [PATCH 158/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 2f02af58..082edce5 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -118,7 +118,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); + 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(); From 6a7088c12d4999fb182fa7aa6b9a0c85916272d8 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:10:12 +0100 Subject: [PATCH 159/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 082edce5..2f02af58 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -118,7 +118,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); + //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(); From 1c2b9296bf9728972638fcb49de7d83b58c7e6d3 Mon Sep 17 00:00:00 2001 From: GmeduoyeClaymore <31854059+GmeduoyeClaymore@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:40:01 +0100 Subject: [PATCH 160/265] Added trade id field --- src/templates/adaptive_stop_loss_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 2f02af58..082edce5 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -118,7 +118,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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)); + 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(); From 2347893519648b0168e2af6563be59e0aba6e2e4 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 16 Aug 2024 09:30:29 +0100 Subject: [PATCH 161/265] Another commit --- src/custom_functions/expression_functions.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 0fb2f59f..240e13d4 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -11,6 +11,13 @@ pub fn is_null>(value: T) -> Result { }) } +pub fn is_null_or,S: Into>(value: T, alternative: S) -> Result { + Ok(match value.into() { + Value::Empty => alternative.into(), + v => v, + }) +} + pub fn abs>(value: T) -> Result where >::Error: Debug { From 4bd37d95f7ab0dc47ee47f5ad2c0b08cd9b579d8 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 16 Aug 2024 10:04:38 +0100 Subject: [PATCH 162/265] Another commit --- src/custom_functions/expression_functions.rs | 20 ++++++++++++++++++++ src/value/mod.rs | 7 +++++++ 2 files changed, 27 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 240e13d4..3576bd48 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -18,6 +18,26 @@ pub fn is_null_or,S: Into>(value: T, alternative: S) -> R }) } +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 abs>(value: T) -> Result where >::Error: Debug { diff --git a/src/value/mod.rs b/src/value/mod.rs index 0c17fb5c..209a77bc 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -754,6 +754,13 @@ mod tests { // 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] From 4391edb045099874cf550f05fd2e10bbac44ae56 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 16 Aug 2024 10:28:20 +0100 Subject: [PATCH 163/265] Another commit --- src/custom_functions/expression_functions.rs | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 3576bd48..beff317f 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -36,6 +36,33 @@ pub fn clip_value_to_range, S: Into>(value: T, constant: S Ok(Value::Float(adjusted_value)) } +pub fn fallback_with_range_clipping, L: Into, C: Into, D: Into>( + p_emin: P, + rel_score_long_temp_ps: L, + rel_score_long_temp: L, + range_to_clip: C, + empty_value: D, +) -> Result { + let p_emin: Value = p_emin.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 p_emin.as_float_or_none()?.unwrap_or(0.0) <= 0.0 { + 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 From 74807a81a71c7bc379bdb24c9fac20907e04e5aa Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 16 Aug 2024 11:05:59 +0100 Subject: [PATCH 164/265] Another commit --- src/custom_functions/expression_functions.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index beff317f..c3045113 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -9,6 +9,14 @@ pub fn is_null>(value: T) -> Result { 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 { From c7af822466cac2b7f658d46daca1260a0f7fe910 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 16 Aug 2024 12:10:23 +0100 Subject: [PATCH 165/265] Another commit --- src/custom_functions/back.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/back.rs b/src/custom_functions/back.rs index 3184092b..c8bf53de 100644 --- a/src/custom_functions/back.rs +++ b/src/custom_functions/back.rs @@ -2,7 +2,7 @@ use crate::{Error, Value}; pub fn back(row: &[Value], columns: &[usize]) -> Result { if columns.is_empty() { - return Ok(Value::Empty); + return Ok(Value::Float(100f64)); } Ok(row[columns[0]].clone()) } From 7843a2c118b02843494c0de6ec53f1c5cf7d1411 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 16 Aug 2024 15:20:58 +0100 Subject: [PATCH 166/265] Another commit --- src/templates/bucket_data_template.rs | 188 ++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/templates/bucket_data_template.rs diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs new file mode 100644 index 00000000..23e35181 --- /dev/null +++ b/src/templates/bucket_data_template.rs @@ -0,0 +1,188 @@ +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; + + +pub struct BucketData { + bucket_output_field_name: String, + field_name_to_bucket: String, + no_buckets: usize +} + +impl BucketData { + pub fn new(field_to_bucket: &str, no_buckets: u8) -> BucketData { + if no_buckets == 0 { + no_buckets = 1; + } + let bucket_field_name = format!("{}_bucket", field_to_bucket); + BucketData { + bucket_output_field_name: bucket_field_name, + field_name_to_bucket: field_to_bucket, + no_buckets + } + } +} +impl CompiledTransposeCalculationTemplate for BucketData { + fn schema(&self) -> HashMap { + vec![ + (self.bucket_output_field_name.clone(), ValueType::Int), + ].iter().map(|(nm, val)| (nm.to_string(), *val)).collect() + } + fn dependencies(&self) -> Vec { + vec![ + self.field_name_to_bucket.clone() + ] + } + fn commit_row( + &self, + row: &mut BoxedOperatorRowTrait, + ordered_transpose_values: &[Value], + cycle_epoch: usize + ) -> Result<(), Error> { + // Maps transpose values to field values + let mut transpose_value_to_field_value_map: HashMap = HashMap::new(); + // Maps field values to their corresponding bucket + let mut value_to_bucket_map: HashMap = HashMap::new(); + + // Populate transpose_value_to_field_value_map + for i in 0..ordered_transpose_values.len() { + let transpose_value = &ordered_transpose_values[i]; + let field_to_bucket = row.get_value(&generate_column_name(&self.field_name_to_bucket, transpose_value))?.as_float_or_none()?; + if let Some(field_value) = field_to_bucket { + transpose_value_to_field_value_map.insert(transpose_value.clone(), field_value); + } + } + + // Calculate buckets and populate value_to_bucket_map + let num_buckets = self.no_buckets; + let mut sorted_field_values: Vec<_> = transpose_value_to_field_value_map.values().cloned().collect(); + sorted_field_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal)); + + for (i, field_value) in sorted_field_values.iter().enumerate() { + let bucket = (i * num_buckets) / sorted_field_values.len(); + value_to_bucket_map.insert(field_value.clone(), bucket); + } + + // Set bucket values in the row + for (transpose_value, field_value) in transpose_value_to_field_value_map.iter() { + if let Some(bucket) = value_to_bucket_map.get(field_value) { + row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64))?; + } + } + Ok(()) + } + +} + +#[cfg(test)] +mod commit_row_tests { + use super::*; + use std::collections::HashMap; + use crate::templates::test_utils::MockRow; + + #[test] + fn test_commit_row_value_below_lower_band() -> Result<(), Error> { + let monitor = ChannelMonitor::new( + "lower_band", + "upper_band", + "value", + "output", + ); + + let mut row = MockRow::new(); + let cycle_epoch = 0; + let transpose_value = Value::String("02_01_2024".to_owned()); + 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); + monitor.commit_row(&mut row_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 mut row = MockRow::new(); + let prev_transpose_value = Value::String("01_01_2024".to_owned()); + let current_transpose_value = Value::String("02_01_2024".to_owned()); + 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); + monitor.commit_row(&mut row_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 row = MockRow::new(); + let prev_transpose_value = Value::String("01_01_2024".to_owned()); + let current_transpose_value = Value::String("02_01_2024".to_owned()); + 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); + monitor.commit_row(&mut row_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 From e374bd2c20cef52466e46f2a78acf0c4a246635e Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 01:14:17 +0100 Subject: [PATCH 167/265] Ordered float added bucet data tests added --- Cargo.toml | 1 + src/lib.rs | 1 - src/templates/bucket_data_template.rs | 206 ++++--- src/templates/mod.rs | 5 +- src/templates/ordered_float.rs | 828 ++++++++++++++++++++++++++ src/value/mod.rs | 41 +- 6 files changed, 971 insertions(+), 111 deletions(-) create mode 100644 src/templates/ordered_float.rs diff --git a/Cargo.toml b/Cargo.toml index ae7af1cc..ad7bdfc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ 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" #csv = "1.1" #anyhow = "1.0.86" thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_object", branch = "main" } diff --git a/src/lib.rs b/src/lib.rs index 15894cff..a1804c0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -519,7 +519,6 @@ //! See [LICENSE](LICENSE) for details. //! -#![forbid(unsafe_code)] #[cfg(feature = "regex_support")] extern crate regex; diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 23e35181..81da8259 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -1,3 +1,4 @@ +use std::cmp; use std::collections::HashMap; use std::fmt::Display; @@ -7,18 +8,18 @@ use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, pub struct BucketData { bucket_output_field_name: String, field_name_to_bucket: String, - no_buckets: usize + no_buckets: u8 } impl BucketData { - pub fn new(field_to_bucket: &str, no_buckets: u8) -> BucketData { + pub fn new(field_to_bucket: &str, mut no_buckets: u8) -> BucketData { if no_buckets == 0 { no_buckets = 1; } let bucket_field_name = format!("{}_bucket", field_to_bucket); BucketData { bucket_output_field_name: bucket_field_name, - field_name_to_bucket: field_to_bucket, + field_name_to_bucket: field_to_bucket.to_owned(), no_buckets } } @@ -43,15 +44,13 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Maps transpose values to field values let mut transpose_value_to_field_value_map: HashMap = HashMap::new(); // Maps field values to their corresponding bucket - let mut value_to_bucket_map: HashMap = HashMap::new(); + let mut value_to_bucket_map: HashMap = HashMap::new(); // Populate transpose_value_to_field_value_map for i in 0..ordered_transpose_values.len() { let transpose_value = &ordered_transpose_values[i]; - let field_to_bucket = row.get_value(&generate_column_name(&self.field_name_to_bucket, transpose_value))?.as_float_or_none()?; - if let Some(field_value) = field_to_bucket { - transpose_value_to_field_value_map.insert(transpose_value.clone(), field_value); - } + let field_to_bucket = row.get_value(&generate_column_name(&self.field_name_to_bucket, transpose_value))?; + transpose_value_to_field_value_map.insert(transpose_value.clone(), field_to_bucket); } // Calculate buckets and populate value_to_bucket_map @@ -60,8 +59,8 @@ impl CompiledTransposeCalculationTemplate for BucketData { sorted_field_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal)); for (i, field_value) in sorted_field_values.iter().enumerate() { - let bucket = (i * num_buckets) / sorted_field_values.len(); - value_to_bucket_map.insert(field_value.clone(), bucket); + let bucket = (i * num_buckets as usize) / sorted_field_values.len(); + value_to_bucket_map.entry(field_value.clone()).or_insert(bucket as u8); } // Set bucket values in the row @@ -76,113 +75,112 @@ impl CompiledTransposeCalculationTemplate for BucketData { } #[cfg(test)] -mod commit_row_tests { +mod tests { use super::*; use std::collections::HashMap; use crate::templates::test_utils::MockRow; - #[test] - fn test_commit_row_value_below_lower_band() -> Result<(), Error> { - let monitor = ChannelMonitor::new( - "lower_band", - "upper_band", - "value", - "output", - ); - - let mut row = MockRow::new(); - let cycle_epoch = 0; - let transpose_value = Value::String("02_01_2024".to_owned()); - 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); - monitor.commit_row(&mut row_trait, &ordered_transpose_values, cycle_epoch)?; + // Mock implementation of BoxedOperatorRowTrait for testing purposes - 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", - ); - + fn test_commit_row_basic() { + // Create a mock row with initial values let mut row = MockRow::new(); - let prev_transpose_value = Value::String("01_01_2024".to_owned()); - let current_transpose_value = Value::String("02_01_2024".to_owned()); - 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); - monitor.commit_row(&mut row_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(()) + let field_to_bucket = "price"; + row.set_value(&generate_column_name(field_to_bucket,&Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket,&Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket,&Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + + // Instantiate BucketData with dummy values + let bucket_data = BucketData { + field_name_to_bucket: field_to_bucket.to_owned(), + bucket_output_field_name: "bucket".to_string(), + no_buckets: 3, + }; + + // Ordered transpose values (these should correspond to the field names) + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + bucket_data.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(row.get_value("bucket_date1").unwrap(), Value::Int(0)); + assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(2)); + assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(1)); } #[test] - fn test_commit_row_re_entered_below_after_being_above() -> Result<(), Error> { - let monitor = ChannelMonitor::new( - "lower_band", - "upper_band", - "value", - "output", - ); - + fn test_commit_row_with_equal_values() { + // Create a mock row with identical values let mut row = MockRow::new(); - let prev_transpose_value = Value::String("01_01_2024".to_owned()); - let current_transpose_value = Value::String("02_01_2024".to_owned()); - 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); - monitor.commit_row(&mut row_trait, &ordered_transpose_values, 0)?; + let field_to_bucket = "price"; + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + + // Instantiate BucketData with dummy values + let bucket_data = BucketData { + field_name_to_bucket: field_to_bucket.to_owned(), + bucket_output_field_name: "bucket".to_string(), + no_buckets: 3, + }; + + // Ordered transpose values (these should correspond to the field names) + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + bucket_data.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(row.get_value("bucket_date1").unwrap(), Value::Int(0)); + assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(0)); + assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(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(()) + #[test] + fn test_commit_row_with_varied_values() { + // Create a mock row with varied values + let mut row = MockRow::new(); + let field_to_bucket = "price"; + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + + // Instantiate BucketData with dummy values + let bucket_data = BucketData { + field_name_to_bucket: field_to_bucket.to_owned(), + bucket_output_field_name: "bucket".to_string(), + no_buckets: 3, + }; + + // Ordered transpose values (these should correspond to the field names) + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + bucket_data.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct bucket values have been set + assert_eq!(row.get_value("bucket_date1").unwrap(), Value::Int(0)); + assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(2)); + assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(1)); } -} \ No newline at end of file +} diff --git a/src/templates/mod.rs b/src/templates/mod.rs index cdab6e82..53bef825 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -1,12 +1,15 @@ pub mod adaptive_stop_loss_trade_model; +pub mod ordered_float; +pub mod bucket_data_template; pub mod compiled_transpose_calcuation_template; -mod channel_monitoring_template; +pub mod channel_monitoring_template; mod test_utils; 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 bucket_data_template::*; pub use compiled_transpose_calcuation_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/value/mod.rs b/src/value/mod.rs index 209a77bc..53ffb852 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -2,7 +2,7 @@ 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; @@ -42,6 +42,36 @@ pub enum Value { } +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) => { + 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<&Value> for Value { fn from(value: &Value) -> Self { match value { @@ -61,7 +91,7 @@ impl PartialOrd for Value { (Value::String(a), Value::String(b)) => a.partial_cmp(b), (Value::Float(a), Value::Float(b)) => a.partial_cmp(b), (Value::Int(a), Value::Int(b)) => a.partial_cmp(b), - (Value::Float(a), Value::Int(b)) => (*a).partial_cmp(&(*b as FloatType)), + (Value::Float(a), Value::Int(b)) => OrderedFloat::from(*a).partial_cmp(&OrderedFloat::from((*b as FloatType))), (Value::Int(a), Value::Float(b)) => (*a as FloatType).partial_cmp(b), (Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b), // For simplicity, Tuple and Empty comparisons are not implemented @@ -78,9 +108,9 @@ 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)) => a == b, - (Value::Float(a), Value::Int(b)) => *a == *b as FloatType, - (Value::Int(a), Value::Float(b)) => *a as FloatType == *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 @@ -403,6 +433,7 @@ use std::ops::Sub; use std::ops::Add; use std::ops::Neg; +use crate::ordered_float::OrderedFloat; impl Neg for Value { type Output = Result; From 5b88ddb8779ccc1ee7cab962a7aba3a9a8ac8da6 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 01:28:54 +0100 Subject: [PATCH 168/265] Ordered float added bucet data tests added --- src/custom_functions/mod.rs | 2 +- src/templates/bucket_data_template.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 6cd967ce..3e28b3c5 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -16,7 +16,7 @@ pub use simple_cumulative_sum::simple_cumulative_sum; use crate::Value; pub fn generate_column_name(field: &str, p1: &Value) -> String { - format!("{}_{}", field.to_string(), sanitize_with_char(&get_string(p1), 'x')) + format!("{}__{}", field.to_string(), sanitize_with_char(&get_string(p1), 'x')) } pub fn sanitize_with_char(value: &T, ch: char) -> String { diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 81da8259..428f6a11 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -16,7 +16,7 @@ impl BucketData { if no_buckets == 0 { no_buckets = 1; } - let bucket_field_name = format!("{}_bucket", field_to_bucket); + let bucket_field_name = format!("{}bucket", field_to_bucket); BucketData { bucket_output_field_name: bucket_field_name, field_name_to_bucket: field_to_bucket.to_owned(), From 674400d20c1b15881deef316bf0191e25dbcad98 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 03:27:01 +0100 Subject: [PATCH 169/265] Ordered float added bucet data tests added --- src/custom_functions/mod.rs | 3 +- .../simple_cumilative_returns.rs | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/custom_functions/simple_cumilative_returns.rs diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 3e28b3c5..96ab8d3a 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -3,7 +3,7 @@ pub mod expression_functions; pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; - +pub mod simple_cumilative_returns; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -13,6 +13,7 @@ pub use expression_functions::*; pub use back::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; +pub use simple_cumilative_returns::simple_cumulative_returns; use crate::Value; pub fn generate_column_name(field: &str, p1: &Value) -> String { diff --git a/src/custom_functions/simple_cumilative_returns.rs b/src/custom_functions/simple_cumilative_returns.rs new file mode 100644 index 00000000..0c970a8f --- /dev/null +++ b/src/custom_functions/simple_cumilative_returns.rs @@ -0,0 +1,70 @@ +use crate::{Error, Value}; + +pub fn simple_cumulative_returns(row: &[Value], columns: &[usize]) -> Result { + if columns.is_empty() { + return Ok(Value::Empty); + } + + let mut cumulative_return = 1.0f64; + for &col_index in columns { + if let Some(val) = row.get(col_index) { + 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), + } + } + } + + Ok(Value::Float(cumulative_return - 1.0)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_cumulative_returns_normal_operation() { + let row = vec![Value::Float(0.1), Value::Float(0.2), Value::Float(0.3)]; + 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 row = vec![Value::Float(0.1), Value::Empty, Value::Float(0.3)]; + 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 row: Vec = vec![]; + 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 row = vec![Value::Empty, Value::Empty]; + 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 row = vec![Value::Float(0.1), Value::Int(2), Value::Float(-0.1)]; + 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)); + } +} From 1fb6a7f66fd71c46a5fd4de73aa0450c2eae7d7a Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 09:08:13 +0100 Subject: [PATCH 170/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 428f6a11..d68462f8 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -66,7 +66,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Set bucket values in the row for (transpose_value, field_value) in transpose_value_to_field_value_map.iter() { if let Some(bucket) = value_to_bucket_map.get(field_value) { - row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64))?; + row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64 + 1))?; } } Ok(()) From ef5e54404a002cf179d56e19ed83c053c7f10133 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 14:51:03 +0100 Subject: [PATCH 171/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index d68462f8..519c6fc8 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -56,7 +56,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Calculate buckets and populate value_to_bucket_map let num_buckets = self.no_buckets; let mut sorted_field_values: Vec<_> = transpose_value_to_field_value_map.values().cloned().collect(); - sorted_field_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal)); + sorted_field_values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(cmp::Ordering::Equal)); for (i, field_value) in sorted_field_values.iter().enumerate() { let bucket = (i * num_buckets as usize) / sorted_field_values.len(); From 606b436b5224baaf809316d16bc00f012debf80a Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 15:53:53 +0100 Subject: [PATCH 172/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 519c6fc8..99b1db23 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -7,6 +7,7 @@ use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, pub struct BucketData { bucket_output_field_name: String, + bucket_range_output_field_name: String, field_name_to_bucket: String, no_buckets: u8 } @@ -17,8 +18,10 @@ impl BucketData { no_buckets = 1; } let bucket_field_name = format!("{}bucket", field_to_bucket); + let bucket_range_field_name = format!("{}bucketrange", field_to_bucket); BucketData { - bucket_output_field_name: bucket_field_name, + bucket_range_output_field_name: bucket_field_name, + bucket_output_field_name: bucket_range_field_name, field_name_to_bucket: field_to_bucket.to_owned(), no_buckets } @@ -27,6 +30,7 @@ impl BucketData { impl CompiledTransposeCalculationTemplate for BucketData { fn schema(&self) -> HashMap { vec![ + (self.bucket_range_output_field_name.clone(), ValueType::String), (self.bucket_output_field_name.clone(), ValueType::Int), ].iter().map(|(nm, val)| (nm.to_string(), *val)).collect() } @@ -45,6 +49,8 @@ impl CompiledTransposeCalculationTemplate for BucketData { let mut transpose_value_to_field_value_map: HashMap = HashMap::new(); // Maps field values to their corresponding bucket let mut value_to_bucket_map: HashMap = HashMap::new(); + let mut min_values_for_bucket: HashMap = HashMap::new(); + let mut max_values_for_bucket: HashMap = HashMap::new(); // Populate transpose_value_to_field_value_map for i in 0..ordered_transpose_values.len() { @@ -61,12 +67,29 @@ impl CompiledTransposeCalculationTemplate for BucketData { for (i, field_value) in sorted_field_values.iter().enumerate() { let bucket = (i * num_buckets as usize) / sorted_field_values.len(); value_to_bucket_map.entry(field_value.clone()).or_insert(bucket as u8); + if !min_values_for_bucket.contains_key(&(bucket as u8)) { + min_values_for_bucket.insert(bucket as u8, field_value.clone()); + }else{ + let min_value = min_values_for_bucket.get(&(bucket as u8)).unwrap(); + if field_value < min_value { + min_values_for_bucket.insert(bucket as u8, field_value.clone()); + } + } + if !max_values_for_bucket.contains_key(&(bucket as u8)) { + max_values_for_bucket.insert(bucket as u8, field_value.clone()); + }else{ + let max_value = max_values_for_bucket.get(&(bucket as u8)).unwrap(); + if field_value > max_value { + max_values_for_bucket.insert(bucket as u8, field_value.clone()); + } + } } // Set bucket values in the row for (transpose_value, field_value) in transpose_value_to_field_value_map.iter() { if let Some(bucket) = value_to_bucket_map.get(field_value) { row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64 + 1))?; + row.set_value(&generate_column_name(&self.bucket_range_output_field_name, transpose_value), Value::String(format!("{} to {}", min_values_for_bucket.get(bucket).unwrap_or(&Value::Empty),max_values_for_bucket.get(bucket).unwrap_or(&Value::Empty))))?; } } Ok(()) @@ -97,6 +120,7 @@ mod tests { field_name_to_bucket: field_to_bucket.to_owned(), bucket_output_field_name: "bucket".to_string(), no_buckets: 3, + bucket_range_output_field_name: "bucketRange".to_string(), }; // Ordered transpose values (these should correspond to the field names) @@ -112,8 +136,11 @@ mod tests { // Check that the correct bucket values have been set assert_eq!(row.get_value("bucket_date1").unwrap(), Value::Int(0)); + assert_eq!(row.get_value("bucketRange_date1").unwrap(), Value::String("foo".to_string())); assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(2)); + assert_eq!(row.get_value("bucketRange_date2").unwrap(), Value::String("foo".to_string())); assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(1)); + assert_eq!(row.get_value("bucketRange_date3").unwrap(), Value::String("foo".to_string())); } #[test] @@ -130,6 +157,7 @@ mod tests { field_name_to_bucket: field_to_bucket.to_owned(), bucket_output_field_name: "bucket".to_string(), no_buckets: 3, + bucket_range_output_field_name: "bucketRange".to_string(), }; // Ordered transpose values (these should correspond to the field names) @@ -164,6 +192,7 @@ mod tests { field_name_to_bucket: field_to_bucket.to_owned(), bucket_output_field_name: "bucket".to_string(), no_buckets: 3, + bucket_range_output_field_name: "bucketRange".to_string(), }; // Ordered transpose values (these should correspond to the field names) From 208c8c7faaa31f77b5ef79efa1339bfa6e3f259e Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 15:57:22 +0100 Subject: [PATCH 173/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 99b1db23..bb0b6abe 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -20,8 +20,8 @@ impl BucketData { let bucket_field_name = format!("{}bucket", field_to_bucket); let bucket_range_field_name = format!("{}bucketrange", field_to_bucket); BucketData { - bucket_range_output_field_name: bucket_field_name, - bucket_output_field_name: bucket_range_field_name, + bucket_range_output_field_name: bucket_range_field_name, + bucket_output_field_name: bucket_field_name, field_name_to_bucket: field_to_bucket.to_owned(), no_buckets } From 95b510f9717741f1ea082dcbf68857550030741d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:12:49 +0100 Subject: [PATCH 174/265] bucket indexes now start at 1 --- src/value/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 53ffb852..786b93e6 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -89,10 +89,10 @@ 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)) => 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).partial_cmp(&OrderedFloat::from((*b as FloatType))), - (Value::Int(a), Value::Float(b)) => (*a as FloatType).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 From c4961d4fece09320c6bd100448041da30e037f0f Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:22:11 +0100 Subject: [PATCH 175/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 46 +++++++++++++-------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index bb0b6abe..69d2f3d6 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -45,16 +45,13 @@ impl CompiledTransposeCalculationTemplate for BucketData { ordered_transpose_values: &[Value], cycle_epoch: usize ) -> Result<(), Error> { - // Maps transpose values to field values let mut transpose_value_to_field_value_map: HashMap = HashMap::new(); - // Maps field values to their corresponding bucket let mut value_to_bucket_map: HashMap = HashMap::new(); let mut min_values_for_bucket: HashMap = HashMap::new(); let mut max_values_for_bucket: HashMap = HashMap::new(); // Populate transpose_value_to_field_value_map - for i in 0..ordered_transpose_values.len() { - let transpose_value = &ordered_transpose_values[i]; + for transpose_value in ordered_transpose_values { let field_to_bucket = row.get_value(&generate_column_name(&self.field_name_to_bucket, transpose_value))?; transpose_value_to_field_value_map.insert(transpose_value.clone(), field_to_bucket); } @@ -62,34 +59,35 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Calculate buckets and populate value_to_bucket_map let num_buckets = self.no_buckets; let mut sorted_field_values: Vec<_> = transpose_value_to_field_value_map.values().cloned().collect(); - sorted_field_values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(cmp::Ordering::Equal)); + sorted_field_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal)); // Sort in ascending order for (i, field_value) in sorted_field_values.iter().enumerate() { let bucket = (i * num_buckets as usize) / sorted_field_values.len(); - value_to_bucket_map.entry(field_value.clone()).or_insert(bucket as u8); - if !min_values_for_bucket.contains_key(&(bucket as u8)) { - min_values_for_bucket.insert(bucket as u8, field_value.clone()); - }else{ - let min_value = min_values_for_bucket.get(&(bucket as u8)).unwrap(); - if field_value < min_value { - min_values_for_bucket.insert(bucket as u8, field_value.clone()); - } - } - if !max_values_for_bucket.contains_key(&(bucket as u8)) { - max_values_for_bucket.insert(bucket as u8, field_value.clone()); - }else{ - let max_value = max_values_for_bucket.get(&(bucket as u8)).unwrap(); - if field_value > max_value { - max_values_for_bucket.insert(bucket as u8, field_value.clone()); - } - } + let bucket_u8 = bucket as u8; + + value_to_bucket_map.entry(field_value.clone()).or_insert(bucket_u8); + + min_values_for_bucket + .entry(bucket_u8) + .and_modify(|min_value| if field_value < min_value { *min_value = field_value.clone(); }) + .or_insert(field_value.clone()); + + max_values_for_bucket + .entry(bucket_u8) + .and_modify(|max_value| if field_value > max_value { *max_value = field_value.clone(); }) + .or_insert(field_value.clone()); } // Set bucket values in the row for (transpose_value, field_value) in transpose_value_to_field_value_map.iter() { if let Some(bucket) = value_to_bucket_map.get(field_value) { - row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64 + 1))?; - row.set_value(&generate_column_name(&self.bucket_range_output_field_name, transpose_value), Value::String(format!("{} to {}", min_values_for_bucket.get(bucket).unwrap_or(&Value::Empty),max_values_for_bucket.get(bucket).unwrap_or(&Value::Empty))))?; + row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64)); + 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) + ); + row.set_value(&generate_column_name(&self.bucket_range_output_field_name, transpose_value), Value::String(bucket_range))?; } } Ok(()) From 7d3c445bb0206a82cae7d7eb2d97733a880372b5 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:26:29 +0100 Subject: [PATCH 176/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 69d2f3d6..59606b25 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -62,6 +62,11 @@ impl CompiledTransposeCalculationTemplate for BucketData { sorted_field_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal)); // Sort in ascending order for (i, field_value) in sorted_field_values.iter().enumerate() { + + if field_value == &Value::Empty { + continue; + } + let bucket = (i * num_buckets as usize) / sorted_field_values.len(); let bucket_u8 = bucket as u8; From 3db95c984a6614876bd5b9680881e86bfbb137a8 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:27:43 +0100 Subject: [PATCH 177/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 59606b25..e31716b2 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -53,6 +53,11 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Populate transpose_value_to_field_value_map for transpose_value in ordered_transpose_values { let field_to_bucket = row.get_value(&generate_column_name(&self.field_name_to_bucket, transpose_value))?; + + if field_to_bucket == Value::Empty { + continue; + } + transpose_value_to_field_value_map.insert(transpose_value.clone(), field_to_bucket); } From aaa3f659b9914f4de5678687bcc56ee40ea0aea4 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:41:13 +0100 Subject: [PATCH 178/265] bucket indexes now start at 1 --- src/value/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 786b93e6..bd739314 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -52,7 +52,7 @@ impl Hash for Value { s.hash(state); } Value::Float(f) => { - f.to_bits().hash(state); // Hash the bit representation of the float + OrderedFloat::from(*f).to_bits().hash(state); // Hash the bit representation of the float } Value::Int(i) => { i.hash(state); From 63366b5a7ef48c18d5e06953d11a77e8f8b2fbd3 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:51:24 +0100 Subject: [PATCH 179/265] bucket indexes now start at 1 --- src/templates/bucket_data_template.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index e31716b2..d234e213 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -64,7 +64,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Calculate buckets and populate value_to_bucket_map let num_buckets = self.no_buckets; let mut sorted_field_values: Vec<_> = transpose_value_to_field_value_map.values().cloned().collect(); - sorted_field_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(cmp::Ordering::Equal)); // Sort in ascending order + sorted_field_values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(cmp::Ordering::Equal)); // Sort in ascending order for (i, field_value) in sorted_field_values.iter().enumerate() { @@ -91,7 +91,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Set bucket values in the row for (transpose_value, field_value) in transpose_value_to_field_value_map.iter() { if let Some(bucket) = value_to_bucket_map.get(field_value) { - row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64)); + row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64 + 1)); let bucket_range = format!( "{} to {}", min_values_for_bucket.get(bucket).unwrap_or(&Value::Empty), From 354112f4802584271f663673b2815a4364410266 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 17 Aug 2024 16:56:04 +0100 Subject: [PATCH 180/265] bucket indexes now start at 1 --- src/value/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index bd739314..2d3ad0ad 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -500,9 +500,9 @@ impl Sub for &Value { (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)), + (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), } } From f79368e2f162fcb14695a79c775d69019dd59495 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 10:33:03 +0100 Subject: [PATCH 181/265] bucket indexes now start at 1 --- Cargo.toml | 1 + src/context/mod.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ad7bdfc4..32d81d56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ 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" #csv = "1.1" #anyhow = "1.0.86" thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_object", branch = "main" } diff --git a/src/context/mod.rs b/src/context/mod.rs index ff77c8ba..ad0cfc50 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -5,6 +5,7 @@ //! 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::{borrow::Cow, collections::HashMap}; +use indexmap::IndexMap; use thin_trait_object::thin_trait_object; use crate::{ function::Function, @@ -50,6 +51,10 @@ impl Context for EmptyContext { None } + fn get_value_by_index(&self, identifier: &usize) -> Option> { + None + } + fn call_function(&self, identifier: &str, _argument: &Value) -> EvalexprResult { Err(EvalexprError::FunctionIdentifierNotFound( identifier.to_string(), @@ -70,18 +75,54 @@ pub struct HashMapContext { functions: HashMap, } +#[derive(Clone, Debug, Default)] +pub struct IndexMapContext { + variables: IndexMap, + functions: HashMap, +} + impl HashMapContext { /// Constructs a `HashMapContext` with no mappings. pub fn new() -> Self { Default::default() } } +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> { 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 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) @@ -120,6 +161,7 @@ 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; } From 5edaa01787ffed902a6bc8bbbf2ebf5ed7324b1b Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 10:34:14 +0100 Subject: [PATCH 182/265] bucket indexes now start at 1 --- src/context/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index ad0cfc50..86c4da41 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -151,6 +151,23 @@ impl ContextWithMutableVariables for HashMapContext { } } +impl ContextWithMutableVariables for IndexMapContext { + fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> { + if let Some(existing_value) = self.variables.get_mut(&identifier) { + if ValueType::from(&existing_value) == ValueType::from(&value) { + *existing_value = value; + return Ok(()); + } else { + return Err(EvalexprError::expected_type(existing_value, value)); + } + } + + // Implicit else, because `self.variables` and `identifier` are not unborrowed in else + self.variables.insert(identifier, value); + Ok(()) + } +} + impl ContextWithMutableFunctions for HashMapContext { fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> { self.functions.insert(identifier, function); From 02875396c943e9f4b26c2065bed866352498de6b Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 10:45:29 +0100 Subject: [PATCH 183/265] bucket indexes now start at 1 --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index a1804c0e..e6319fae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -546,6 +546,7 @@ pub use crate::{ BoxedActiveRowTrackerTrait, FFIColumn, HashMapContext, + IndexMapContext }, error::{EvalexprError, EvalexprResult}, function::Function, From cd55dd21bbc83dcd3572a3fd61d19eebc039ae69 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 10:50:28 +0100 Subject: [PATCH 184/265] bucket indexes now start at 1 --- src/context/mod.rs | 1 + src/templates/test_utils.rs | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 86c4da41..fa61ed90 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -188,6 +188,7 @@ pub trait Context { #[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_value_by_index(&self, idx: &usize) -> Result; 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>; diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index da01bc0b..7f17ccf5 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -1,14 +1,15 @@ use std::collections::HashMap; +use indexmap::IndexMap; use crate::{context, Error, EvalexprResult, OperatorRowTrait, Value}; pub struct MockRow { - values: HashMap, + values: IndexMap, } impl MockRow { pub fn new() -> Self { MockRow { - values: HashMap::new(), + values: IndexMap::new(), } } @@ -30,6 +31,16 @@ impl OperatorRowTrait for MockRow { } } + fn get_value_by_index(&self, idx: &usize) -> Result { + let option = self.values.get_index(*idx); + match option { + None => { Ok(Value::Empty)} + Some(val) => { + Ok(val.1.clone()) + } + } + } + fn set_value(&mut self, column_name: &str, value: Value) ->Result<(),crate::Error> { self.values.insert(column_name.to_string(), value); Ok(()) From 145d12f8e07c9e6bbaa261d7f07f41416ddd6201 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 10:52:51 +0100 Subject: [PATCH 185/265] bucket indexes now start at 1 --- src/context/mod.rs | 2 +- src/templates/test_utils.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index fa61ed90..38866cee 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -188,7 +188,7 @@ pub trait Context { #[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_value_by_index(&self, idx: &usize) -> Result; + fn get_value_by_index(&self, idx: usize) -> Result; 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>; diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 7f17ccf5..3fa9f40f 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -31,8 +31,8 @@ impl OperatorRowTrait for MockRow { } } - fn get_value_by_index(&self, idx: &usize) -> Result { - let option = self.values.get_index(*idx); + fn get_value_by_index(&self, idx: usize) -> Result { + let option = self.values.get_index(idx); match option { None => { Ok(Value::Empty)} Some(val) => { From 0ac97c66b6a30920b3a23f1fe40420962bb5854b Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 10:55:12 +0100 Subject: [PATCH 186/265] bucket indexes now start at 1 --- src/context/mod.rs | 1 - src/templates/test_utils.rs | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 38866cee..86c4da41 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -188,7 +188,6 @@ pub trait Context { #[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_value_by_index(&self, idx: usize) -> Result; 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>; diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 3fa9f40f..21083353 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -31,8 +31,8 @@ impl OperatorRowTrait for MockRow { } } - fn get_value_by_index(&self, idx: usize) -> Result { - let option = self.values.get_index(idx); + fn get_value_for_column(&self, col: usize) -> Result { + let option = self.values.get_index(col); match option { None => { Ok(Value::Empty)} Some(val) => { @@ -46,9 +46,6 @@ impl OperatorRowTrait for MockRow { Ok(()) } - fn get_value_for_column(&self, col: usize) -> Result { - todo!() - } fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(),crate::Error> { todo!() From 83554312dad4849bfd1aabf944241db3162bd325 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 18 Aug 2024 17:44:46 +0100 Subject: [PATCH 187/265] Functions now take an operator row trait --- src/custom_functions/back.rs | 6 ++-- .../simple_cumilative_returns.rs | 19 +++++------ src/custom_functions/simple_cumulative_sum.rs | 17 +++++----- src/custom_functions/simple_moving_average.rs | 21 ++++++------ .../triangular_moving_average.rs | 32 ++++++++++--------- src/templates/mod.rs | 2 +- src/templates/test_utils.rs | 17 +++++++++- 7 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/custom_functions/back.rs b/src/custom_functions/back.rs index c8bf53de..c6256b6d 100644 --- a/src/custom_functions/back.rs +++ b/src/custom_functions/back.rs @@ -1,9 +1,9 @@ -use crate::{Error, Value}; +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; -pub fn back(row: &[Value], columns: &[usize]) -> Result { +pub fn back(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { if columns.is_empty() { return Ok(Value::Float(100f64)); } - Ok(row[columns[0]].clone()) + Ok(row.get_value_for_column(columns[0])?) } diff --git a/src/custom_functions/simple_cumilative_returns.rs b/src/custom_functions/simple_cumilative_returns.rs index 0c970a8f..acf32d86 100644 --- a/src/custom_functions/simple_cumilative_returns.rs +++ b/src/custom_functions/simple_cumilative_returns.rs @@ -1,19 +1,19 @@ -use crate::{Error, Value}; +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; -pub fn simple_cumulative_returns(row: &[Value], columns: &[usize]) -> Result { +pub fn simple_cumulative_returns(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { if columns.is_empty() { return Ok(Value::Empty); } let mut cumulative_return = 1.0f64; for &col_index in columns { - if let Some(val) = row.get(col_index) { + if let Some(val) = row.get_value_for_column(col_index).ok() { match val { Value::Float(val) => { cumulative_return *= 1.0 + val; } Value::Int(val) => { - cumulative_return *= 1.0 + (*val as f64); + cumulative_return *= 1.0 + (val as f64); } Value::Empty => {} _ => return Err(Error::NonNumericType), @@ -26,11 +26,12 @@ pub fn simple_cumulative_returns(row: &[Value], columns: &[usize]) -> Result = vec![]; + let row= MockRow::from_values(vec![]).into_boxed(); let columns: Vec = vec![]; let result = simple_cumulative_returns(&row, &columns).unwrap(); assert_eq!(result, Value::Empty); @@ -54,7 +55,7 @@ mod tests { #[test] fn test_simple_cumulative_returns_no_valid_columns() { - let row = vec![Value::Empty, Value::Empty]; + let row = MockRow::from_values(vec![Value::Empty, Value::Empty]).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 @@ -62,7 +63,7 @@ mod tests { #[test] fn test_simple_cumulative_returns_mixed_data_types() { - let row = vec![Value::Float(0.1), Value::Int(2), Value::Float(-0.1)]; + let row = MockRow::from_values(vec![Value::Float(0.1), Value::Int(2), Value::Float(-0.1)]).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 index 6131723d..eaba9bd2 100644 --- a/src/custom_functions/simple_cumulative_sum.rs +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -1,6 +1,6 @@ -use crate::{Error, Value}; +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; -pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result { +pub fn simple_cumulative_sum(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { if columns.is_empty() { return Ok(Value::Empty); } @@ -10,14 +10,14 @@ pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result { sum += val; continue; } Value::Int(val) => { - sum += *val as f64; + sum += val as f64; continue; } Value::Empty => {} @@ -33,11 +33,12 @@ pub fn simple_cumulative_sum(row: &[Value], columns: &[usize]) -> Result = vec![]; + let row = MockRow::from_values(vec![]).into_boxed(); let columns: Vec = vec![]; let result = simple_cumulative_sum(&row, &columns).unwrap(); assert_eq!(result, Value::Empty); @@ -61,7 +62,7 @@ mod tests { #[test] fn test_simple_cumulative_sum_no_valid_columns() { - let row = vec![Value::Empty, Value::Empty]; + let row = MockRow::from_values(vec![Value::Empty, Value::Empty]).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 index 9d9f26b4..9f4d5499 100644 --- a/src/custom_functions/simple_moving_average.rs +++ b/src/custom_functions/simple_moving_average.rs @@ -1,14 +1,14 @@ -use crate::{Error, Value}; +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; -pub fn simple_moving_average(row: &[Value], columns: &[usize]) -> Result { +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(col_index) { - Some(Value::Float(val)) => Some(*val), - Some(Value::Int(val)) => Some(*val as f64), + .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)); @@ -23,11 +23,12 @@ pub fn simple_moving_average(row: &[Value], columns: &[usize]) -> Result = vec![]; + let row = MockRow::from_values(vec![]).into_boxed(); let columns: Vec = vec![]; let result = simple_moving_average(&row, &columns).unwrap(); assert_eq!(result, Value::Empty); @@ -51,7 +52,7 @@ mod tests { #[test] fn test_simple_moving_average_no_valid_columns() { - let row = vec![Value::Empty, Value::Empty]; + let row = MockRow::from_values(vec![Value::Empty, Value::Empty]).into_boxed(); let columns = vec![0, 1]; let result = simple_moving_average(&row, &columns).unwrap(); assert_eq!(result, Value::Empty); @@ -61,7 +62,7 @@ mod tests { #[test] fn test_triangular_moving_average_normal_operation() { - let row = (0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(); // Simple case with enough columns + let row = MockRow::from_values((0..1111111).map(|idx| Value::Float(idx as f64)).collect::>()).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); diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 8805b03b..7c5390ee 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -1,10 +1,10 @@ -use crate::{Error, Value}; +use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; use crate::Error::UnsupportedOperation; -pub fn columns_len(row: &[Value], columns: &[usize]) -> Result { +pub fn columns_len(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { Ok(Value::Int(columns.len() as i64)) } -pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result { +pub fn triangular_moving_average(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { if columns.len() < 3 { return Ok(Value::Empty); // Not engh data to calculate } @@ -50,26 +50,28 @@ pub fn triangular_moving_average(row: &[Value], columns: &[usize]) -> Result Option { - row.get(columns[index]).and_then(|value| match value { - Value::Float(val) => Some(*val), - Value::Int(val) => Some(*val as f64), +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::MockRow; use super::*; //Time: 4.481µs #[test] fn test_triangular_moving_average_normal_operation() { - let row = (0..1111111).map(|idx| Value::Float(idx as f64)).collect::>(); // Simple case with enough columns + let row = MockRow::from_values((0..1111111).map(|idx| Value::Float(idx as f64)).collect::>()); // 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(&row, &columns); + let result = triangular_moving_average(&BoxedOperatorRowTrait::new(row), &columns); assert!(result.is_ok()); let value = result.unwrap(); match value { @@ -87,7 +89,7 @@ mod tests { #[test] fn test_triangular_moving_average_empty_input() { - let row: Vec = vec![]; + let row = MockRow::from_values(vec![]).into_boxed(); let columns: Vec = vec![]; let result = triangular_moving_average(&row, &columns); assert!(result.is_ok()); @@ -97,7 +99,7 @@ mod tests { #[test] fn test_triangular_moving_average_empty() { - let row = vec![]; + let row = MockRow::from_values(vec![]).into_boxed(); let columns = vec![]; let result = triangular_moving_average(&row, &columns).unwrap(); assert_eq!(result, Value::Empty); @@ -106,7 +108,7 @@ mod tests { #[test] fn test_triangular_moving_average_basic() { // Setup a simple scenario - let row = vec![Value::Int(10), Value::Int(20), Value::Int(30), Value::Int(40), Value::Int(50)]; + let row = MockRow::from_values(vec![Value::Int(10), Value::Int(20), Value::Int(30), Value::Int(40), Value::Int(50)]).into_boxed(); let columns = vec![0, 1, 2, 3, 4]; // Direct mapping for simplicity let result = triangular_moving_average(&row, &columns).unwrap(); @@ -120,13 +122,13 @@ mod tests { #[test] fn test_triangular_moving_average_with_floats() { // Test the function with floating point numbers - let row = vec![ + 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) - ]; + ]).into_boxed(); let columns = vec![0, 1, 2, 3, 4]; let result = triangular_moving_average(&row, &columns).unwrap(); @@ -139,7 +141,7 @@ mod tests { #[test] fn test_triangular_moving_average_invalid_values() { // Test how the function handles invalid (non-numeric) values - let row = vec![Value::Empty, Value::Int(20), Value::Empty, Value::Int(40), Value::Empty]; + let row = MockRow::from_values(vec![Value::Empty, Value::Int(20), Value::Empty, Value::Int(40), Value::Empty]).into_boxed(); let columns = vec![0, 1, 2, 3, 4]; let result = triangular_moving_average(&row, &columns).unwrap(); diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 53bef825..86b2d1be 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -3,7 +3,7 @@ pub mod ordered_float; pub mod bucket_data_template; pub mod compiled_transpose_calcuation_template; pub mod channel_monitoring_template; -mod test_utils; +pub(crate) mod test_utils; use std::fmt::Display; // Re-export the functions to the root of the crate diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 21083353..7dd35e28 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use indexmap::IndexMap; -use crate::{context, Error, EvalexprResult, OperatorRowTrait, Value}; +use crate::{context, BoxedOperatorRowTrait, Error, EvalexprResult, OperatorRowTrait, Value}; pub struct MockRow { values: IndexMap, @@ -13,6 +13,21 @@ impl MockRow { } } + pub fn from_values(row: Vec) -> Self { + let mut mock_row = MockRow::new(); + + // 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 into_boxed<'inner>(self) -> BoxedOperatorRowTrait<'inner> { + BoxedOperatorRowTrait::new(self) + } pub fn insert_value(&mut self, key: String, value: Value) { self.values.insert(key, value); } From f9e2dcee60deaf89dd7edec93c8cf6bf3d1a31ae Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 19 Aug 2024 12:25:00 +0100 Subject: [PATCH 188/265] Added change tracking to context --- src/context/mod.rs | 41 +++++++++++++++++++++++++++++++++-------- src/operator/mod.rs | 4 ++-- tests/integration.rs | 20 ++++++++++---------- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 86c4da41..b8dcd478 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -5,6 +5,7 @@ //! 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::{borrow::Cow, collections::HashMap}; +use std::collections::HashSet; use indexmap::IndexMap; use thin_trait_object::thin_trait_object; use crate::{ @@ -20,7 +21,11 @@ mod predefined; /// 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) } } @@ -73,12 +78,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 { - variables: IndexMap, - functions: HashMap, + pub variables: IndexMap, + pub changed_variables: HashSet, + pub functions: HashMap, } impl HashMapContext { @@ -135,37 +142,55 @@ impl Context for IndexMapContext { } 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) { *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 IndexMapContext { - 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) { *existing_value = value; + if track_changes { + self.changed_variables.insert(identifier); + } return Ok(()); } else { return Err(EvalexprError::expected_type(existing_value, value)); } } - - // Implicit else, because `self.variables` and `identifier` are not unborrowed in else + 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 ContextWithMutableFunctions for HashMapContext { @@ -283,7 +308,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/operator/mod.rs b/src/operator/mod.rs index 979fa40f..c57e301a 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -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/tests/integration.rs b/tests/integration.rs index 7b0d3f8a..50c307e5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -108,15 +108,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 +156,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 +237,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)))) @@ -501,7 +501,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,7 +1539,7 @@ 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(); From 8cddb489652a95dd6340fb83997e4ef712593208 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 19 Aug 2024 12:37:51 +0100 Subject: [PATCH 189/265] Added change tracking to context --- src/context/mod.rs | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index b8dcd478..5ff93eef 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -8,11 +8,7 @@ use std::{borrow::Cow, collections::HashMap}; use std::collections::HashSet; use indexmap::IndexMap; use thin_trait_object::thin_trait_object; -use crate::{ - function::Function, - value::{value_type::ValueType, Value}, - EvalexprError, EvalexprResult, -}; +use crate::{function::Function, value::{value_type::ValueType, Value}, Error, EvalexprError, EvalexprResult}; mod predefined; @@ -101,6 +97,8 @@ impl IndexMapContext { } } + + impl Context for HashMapContext { fn get_value(&self, identifier: &str) -> Option> { self.variables.get(identifier).map(Cow::Borrowed) @@ -193,6 +191,40 @@ impl ContextWithMutableVariables for IndexMapContext { } } +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 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_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 { fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> { self.functions.insert(identifier, function); From d49fe52b47eb7c705703345ba16d9218c709cd45 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 19 Aug 2024 14:02:14 +0100 Subject: [PATCH 190/265] Added change tracking to context --- src/context/mod.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index 5ff93eef..027e0c90 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -119,6 +119,26 @@ impl Context for HashMapContext { } } +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) @@ -166,6 +186,30 @@ impl ContextWithMutableVariables for HashMapContext { } } +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 = 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) { @@ -191,6 +235,40 @@ impl ContextWithMutableVariables for IndexMapContext { } } +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 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_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)) From c91cdfe16b969045bae07ed02ac377cd078997e5 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 19 Aug 2024 15:14:49 +0100 Subject: [PATCH 191/265] Added change tracking to context --- src/context/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 027e0c90..a264528c 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -162,7 +162,7 @@ impl Context for IndexMapContext { impl ContextWithMutableVariables for HashMapContext { 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); @@ -189,7 +189,7 @@ impl ContextWithMutableVariables for HashMapContext { 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) { + if ValueType::from(&existing_value) == ValueType::from(&value) || existing_value.is_empty() { *existing_value = value; if track_changes { self.changed_variables.insert(identifier); @@ -213,7 +213,7 @@ impl ContextWithMutableVariables for &mut IndexMapContext { 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) { + if ValueType::from(&existing_value) == ValueType::from(&value) || existing_value.is_empty() { *existing_value = value; if track_changes { self.changed_variables.insert(identifier); From 47e6459e6e17b8e2ffd1889643cdd05809c27ce2 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 19 Aug 2024 15:15:04 +0100 Subject: [PATCH 192/265] Added change tracking to context --- src/context/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context/mod.rs b/src/context/mod.rs index a264528c..32a2e878 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -90,6 +90,7 @@ impl HashMapContext { Default::default() } } + impl IndexMapContext { /// Constructs a `HashMapContext` with no mappings. pub fn new() -> Self { From ddd9399dae4757e6f4bec204ca0e1086ef39802c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 20 Aug 2024 11:32:47 +0100 Subject: [PATCH 193/265] Added rolling min and rolling stdev --- src/custom_functions/mod.rs | 4 + src/custom_functions/rolling_min.rs | 136 +++++++++++++++++++++++++ src/custom_functions/rolling_stdev.rs | 141 ++++++++++++++++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 src/custom_functions/rolling_min.rs create mode 100644 src/custom_functions/rolling_stdev.rs diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 96ab8d3a..33cb4604 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -4,6 +4,8 @@ pub mod simple_moving_average; pub mod simple_cumulative_sum; pub mod back; pub mod simple_cumilative_returns; +mod rolling_stdev; +mod rolling_min; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -14,6 +16,8 @@ pub use back::*; 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; use crate::Value; pub fn generate_column_name(field: &str, p1: &Value) -> String { diff --git a/src/custom_functions/rolling_min.rs b/src/custom_functions/rolling_min.rs new file mode 100644 index 00000000..c2a0d2e7 --- /dev/null +++ b/src/custom_functions/rolling_min.rs @@ -0,0 +1,136 @@ +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::MockRow; + use super::*; + + #[test] + fn test_rolling_min_basic_case() { + 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), + ]).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 row = MockRow::from_values(vec![ + Value::Float(0.1), + Value::Empty, + Value::Float(0.3), + Value::Empty, + Value::Float(0.5), + ]).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 row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + Value::Int(2), + Value::Float(5.0), + ]).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 row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + ]).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 row = MockRow::from_values(vec![ + Value::Float(2.5), + ]).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 row = MockRow::from_values(vec![ + Value::Empty, + Value::Empty, + Value::Empty, + ]).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 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), + ]).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..7d0fcd43 --- /dev/null +++ b/src/custom_functions/rolling_stdev.rs @@ -0,0 +1,141 @@ +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::MockRow; + use super::*; + + #[test] + fn test_rolling_stdev_basic_case() { + 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), + ]).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 row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + Value::Int(2), + Value::Float(5.0), + ]).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 row = MockRow::from_values(vec![ + Value::Float(0.1), + Value::Empty, + Value::Float(0.3), + Value::Empty, + Value::Float(0.5), + ]).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 row = MockRow::from_values(vec![ + Value::Float(1.0), + Value::Int(3), + Value::Float(4.0), + ]).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 row = MockRow::from_values(vec![ + Value::Float(2.5), + ]).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 row = MockRow::from_values(vec![ + Value::Empty, + Value::Empty, + Value::Empty, + ]).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"); + } +} + From f6d53f79ef873ec28712aa1de3343304ab8eb9b7 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 02:14:25 +0100 Subject: [PATCH 194/265] Added rolling min and rolling stdev --- src/templates/mod.rs | 2 + src/templates/rolling_zscore.rs | 277 ++++++++++++++++++++++++++++++++ src/templates/test_utils.rs | 2 + 3 files changed, 281 insertions(+) create mode 100644 src/templates/rolling_zscore.rs diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 86b2d1be..2afba143 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -4,12 +4,14 @@ pub mod bucket_data_template; pub mod compiled_transpose_calcuation_template; pub mod channel_monitoring_template; pub(crate) mod test_utils; +pub mod rolling_zscore; 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 bucket_data_template::*; +pub use rolling_zscore::*; pub use compiled_transpose_calcuation_template::*; use crate::Value; diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs new file mode 100644 index 00000000..80d93f79 --- /dev/null +++ b/src/templates/rolling_zscore.rs @@ -0,0 +1,277 @@ +use std::cmp; +use std::collections::HashMap; +use std::fmt::Display; + +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType, AdaptiveStopLossTradeModel}; + +pub struct RollingZScore { + avg_output_field_name: String, + stdev_output_field_name: String, + has_data_output_field_name: String, + zscore_output_field_name: String, + field_to_zscore: String, + window_size: u32, +} + +impl RollingZScore { + pub fn new(field_to_zscore: &str, output_column_prefix: &str, mut window_size: u32) -> Self { + if window_size == 0 { + window_size = 1; + } + RollingZScore { + avg_output_field_name: format!("{}avg", output_column_prefix), + stdev_output_field_name: format!("{}stdev", output_column_prefix), + has_data_output_field_name: format!("{}hasdata", output_column_prefix), + zscore_output_field_name: format!("{}zscore", output_column_prefix), + field_to_zscore: field_to_zscore.to_string(), + window_size, + } + } +} + +impl CompiledTransposeCalculationTemplate for RollingZScore { + fn schema(&self) -> HashMap { + vec![ + (self.has_data_output_field_name.clone(), ValueType::Boolean), + (self.avg_output_field_name.clone(), ValueType::Float), + (self.stdev_output_field_name.clone(), ValueType::Float), + (self.zscore_output_field_name.clone(), ValueType::Float), + ] + .into_iter() + .collect() + } + + fn dependencies(&self) -> Vec { + vec![self.field_to_zscore.clone()] + } + + fn commit_row(&self, row: &mut BoxedOperatorRowTrait, 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 mut last_avg: Option = None; + let mut last_stdev: Option = None; + let mut last_zscore: Option = None; + let mut last_value: Option = None; + + 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 = row.get_value(&generate_column_name(&self.field_to_zscore, transpose_value))?.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 the calculated values + row.set_value(&generate_column_name(&self.zscore_output_field_name, transpose_value), Value::Float(zscore))?; + row.set_value(&generate_column_name(&self.stdev_output_field_name, transpose_value), Value::Float(stdev))?; + row.set_value(&generate_column_name(&self.avg_output_field_name, transpose_value), Value::Float(avg))?; + row.set_value(&generate_column_name(&self.has_data_output_field_name, transpose_value), 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 + row.set_value( + &generate_column_name(&self.zscore_output_field_name, transpose_value), + last_zscore.map(Value::Float).unwrap_or(Value::Empty), + )?; + row.set_value( + &generate_column_name(&self.stdev_output_field_name, transpose_value), + last_stdev.map(Value::Float).unwrap_or(Value::Empty), + )?; + row.set_value( + &generate_column_name(&self.avg_output_field_name, transpose_value), + last_avg.map(Value::Float).unwrap_or(Value::Empty), + )?; + row.set_value( + &generate_column_name(&self.has_data_output_field_name, transpose_value), + Value::Boolean(true), + )?; + } + } + + Ok(()) + } + +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::time::Instant; + use crate::templates::test_utils::MockRow; + + #[test] + fn test_commit_row_basic() { + // Create a mock row with initial values + let mut row = MockRow::new(); + let field_to_zscore = "price"; + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".to_owned())), Value::Float(4.0)).unwrap(); + + // Instantiate RollingZScore with dummy values + let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); + + // Ordered transpose values (these should correspond to the field names) + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + Value::String("date4".to_string()), + ]; + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + rolling_zscore.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct zscore, avg, stdev, and has_data values have been set + assert!(row.get_value("prefixzscore__date3").unwrap() != Value::Empty); // First full window + assert!(row.get_value("prefixzscore__date4").unwrap() != Value::Empty); // Second full window + assert_eq!(row.get_value("prefixhasdata__date3").unwrap(), Value::Boolean(true)); + assert_eq!(row.get_value("prefixhasdata__date4").unwrap(), Value::Boolean(true)); + + // Test for the actual computed values + // Replace `expected_zscore_date3` with the exact value. + + assert_eq!(row.get_value("prefixzscore__date1").unwrap(), Value::Empty); + assert_eq!(row.get_value("prefixzscore__date2").unwrap(), Value::Empty); + assert_eq!(row.get_value("prefixzscore__date3").unwrap(), Value::Float(0f64)); + assert_eq!(row.get_value("prefixzscore__date4").unwrap(), Value::Float(1.2247448713915896f64)); + } + + #[test] + fn test_commit_row_with_nulls() { + // Create a mock row with initial values, including a null value + let mut row = MockRow::new(); + let field_to_zscore = "price"; + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Empty).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".to_owned())), Value::Float(3.0)).unwrap(); + + // Instantiate RollingZScore with dummy values + let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); + + // Ordered transpose values (these should correspond to the field names) + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + Value::String("date4".to_string()), + ]; + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + rolling_zscore.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + + // Check that the correct zscore, avg, stdev, and has_data values have been set + assert!(row.get_value("prefixzscore__date2").unwrap() == Value::Empty); // Should be empty because it's null + assert!(row.get_value("prefixzscore__date3").unwrap() != Value::Empty); // Should have a value + assert!(row.get_value("prefixzscore__date4").unwrap() != Value::Empty); // Should have a value + assert_eq!(row.get_value("prefixhasdata__date3").unwrap(), Value::Boolean(true)); + assert_eq!(row.get_value("prefixhasdata__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("prefixzscore__date3").unwrap(), Value::Float(1.414213562373095f64)); + } + + #[test] + fn test_commit_row_window_not_full() { + // Create a mock row with initial values + let mut row = MockRow::new(); + let field_to_zscore = "price"; + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + + // Instantiate RollingZScore with dummy values + let rolling_zscore = RollingZScore::new(field_to_zscore, "zscore_", 3); + + // Ordered transpose values (these should correspond to the field names) + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + ]; + + // Call commit_row and check the results + let mut row = BoxedOperatorRowTrait::new(row); + rolling_zscore.commit_row(&mut row, &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 mut row = MockRow::new(); + let field_to_zscore = "price"; + + // 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))), + Value::Float((i % 10) as f64 + 1.0), + ).unwrap(); + } + + // Instantiate RollingZScore with window size of 522 + let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 522); + + // Generate the ordered transpose values + let ordered_transpose_values: Vec = (1..=15000) + .map(|i| Value::String(format!("date{}", i))) + .collect(); + + // Call commit_row multiple times and measure the time + let iterations = 10; + let mut total_duration = 0; + + 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, &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/test_utils.rs b/src/templates/test_utils.rs index 7dd35e28..35cd722d 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -2,6 +2,8 @@ use std::collections::HashMap; use indexmap::IndexMap; use crate::{context, BoxedOperatorRowTrait, Error, EvalexprResult, OperatorRowTrait, Value}; + +#[derive(Debug, Clone)] pub struct MockRow { values: IndexMap, } From fc1b8359c41f488e04ade42d905468892248cafb Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 02:22:06 +0100 Subject: [PATCH 195/265] Added rolling min and rolling stdev --- src/custom_functions/expression_functions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index c3045113..54a4bf19 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -45,18 +45,18 @@ pub fn clip_value_to_range, S: Into>(value: T, constant: S } pub fn fallback_with_range_clipping, L: Into, C: Into, D: Into>( - p_emin: P, + should_use_primary: P, rel_score_long_temp_ps: L, rel_score_long_temp: L, range_to_clip: C, empty_value: D, ) -> Result { - let p_emin: Value = p_emin.into(); + 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 p_emin.as_float_or_none()?.unwrap_or(0.0) <= 0.0 { + 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 { From c5c4aa2603fc7e74957658100b576b7aa8e19d4a Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 02:24:21 +0100 Subject: [PATCH 196/265] Added rolling min and rolling stdev --- src/templates/rolling_zscore.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs index 80d93f79..0015c30e 100644 --- a/src/templates/rolling_zscore.rs +++ b/src/templates/rolling_zscore.rs @@ -201,6 +201,7 @@ mod tests { assert_eq!(row.get_value("prefixhasdata__date3").unwrap(), Value::Boolean(true)); assert_eq!(row.get_value("prefixhasdata__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("prefixzscore__date3").unwrap(), Value::Float(1.414213562373095f64)); From 9a55201f87d1d6f2d448aa58eb010e20681f15f7 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 14:24:59 +0100 Subject: [PATCH 197/265] Added column indexing to calc templates --- src/context/mod.rs | 17 ++ src/custom_functions/rolling_min.rs | 23 +- src/custom_functions/rolling_stdev.rs | 20 +- .../simple_cumilative_returns.rs | 17 +- src/custom_functions/simple_cumulative_sum.rs | 14 +- src/custom_functions/simple_moving_average.rs | 18 +- .../triangular_moving_average.rs | 21 +- .../adaptive_stop_loss_trade_model.rs | 5 +- src/templates/bucket_data_template.rs | 232 ++++++++++-------- src/templates/channel_monitoring_template.rs | 25 +- .../compiled_transpose_calcuation_template.rs | 8 +- src/templates/rolling_zscore.rs | 134 ++++++---- src/templates/test_utils.rs | 93 ++++++- src/value/mod.rs | 7 + 14 files changed, 428 insertions(+), 206 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 32a2e878..fe0dbced 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -370,6 +370,7 @@ 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>; @@ -378,6 +379,22 @@ pub trait OperatorSchemaTrait { 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=true))] +#[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=true))] +#[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>; +} + + + #[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 { diff --git a/src/custom_functions/rolling_min.rs b/src/custom_functions/rolling_min.rs index c2a0d2e7..961d7c7a 100644 --- a/src/custom_functions/rolling_min.rs +++ b/src/custom_functions/rolling_min.rs @@ -30,11 +30,12 @@ pub fn rolling_min(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result = vec![]; let result = rolling_min(&row, &columns).unwrap(); @@ -96,9 +100,10 @@ mod tests { #[test] fn test_rolling_min_single_value() { + let mock_holder = MockIndexHolder::new(); let row = MockRow::from_values(vec![ Value::Float(2.5), - ]).into_boxed(); + ],&mock_holder).into_boxed(); let columns = vec![0]; let result = rolling_min(&row, &columns).unwrap(); @@ -107,11 +112,12 @@ mod tests { #[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, - ]).into_boxed(); + ],&mock_holder).into_boxed(); let columns = vec![0, 1, 2]; let result = rolling_min(&row, &columns).unwrap(); @@ -120,13 +126,14 @@ mod tests { #[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), - ]).into_boxed(); + ],&mock_holder).into_boxed(); let columns = vec![0, 1, 2, 3, 4]; let result = rolling_min(&row, &columns).unwrap(); diff --git a/src/custom_functions/rolling_stdev.rs b/src/custom_functions/rolling_stdev.rs index 7d0fcd43..031450f7 100644 --- a/src/custom_functions/rolling_stdev.rs +++ b/src/custom_functions/rolling_stdev.rs @@ -38,11 +38,12 @@ pub fn rolling_stdev(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result = vec![]; let result = rolling_stdev(&row, &columns).unwrap(); @@ -116,9 +120,10 @@ mod tests { #[test] fn test_rolling_stdev_single_value() { + let mock_holder = MockIndexHolder::new(); let row = MockRow::from_values(vec![ Value::Float(2.5), - ]).into_boxed(); + ],&mock_holder).into_boxed(); let columns = vec![0]; let result = rolling_stdev(&row, &columns).unwrap(); @@ -127,11 +132,12 @@ mod tests { #[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, - ]).into_boxed(); + ],&mock_holder).into_boxed(); let columns = vec![0, 1, 2]; let result = rolling_stdev(&row, &columns).unwrap(); diff --git a/src/custom_functions/simple_cumilative_returns.rs b/src/custom_functions/simple_cumilative_returns.rs index acf32d86..945ebdf8 100644 --- a/src/custom_functions/simple_cumilative_returns.rs +++ b/src/custom_functions/simple_cumilative_returns.rs @@ -26,12 +26,13 @@ pub fn simple_cumulative_returns(row: &BoxedOperatorRowTrait, columns: &[usize]) #[cfg(test)] mod tests { - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; use super::*; #[test] fn test_simple_cumulative_returns_normal_operation() { - let row = MockRow::from_values(vec![Value::Float(0.1), Value::Float(0.2), Value::Float(0.3)]).into_boxed(); + 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)); @@ -39,7 +40,8 @@ mod tests { #[test] fn test_simple_cumulative_returns_partial_data() { - let row = MockRow::from_values(vec![Value::Float(0.1), Value::Empty, Value::Float(0.3)]).into_boxed(); + 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)); @@ -47,7 +49,8 @@ mod tests { #[test] fn test_simple_cumulative_returns_empty_input() { - let row= MockRow::from_values(vec![]).into_boxed(); + 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); @@ -55,7 +58,8 @@ mod tests { #[test] fn test_simple_cumulative_returns_no_valid_columns() { - let row = MockRow::from_values(vec![Value::Empty, Value::Empty]).into_boxed(); + 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 @@ -63,7 +67,8 @@ mod tests { #[test] fn test_simple_cumulative_returns_mixed_data_types() { - let row = MockRow::from_values(vec![Value::Float(0.1), Value::Int(2), Value::Float(-0.1)]).into_boxed(); + 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 index eaba9bd2..946bcbe2 100644 --- a/src/custom_functions/simple_cumulative_sum.rs +++ b/src/custom_functions/simple_cumulative_sum.rs @@ -33,12 +33,13 @@ pub fn simple_cumulative_sum(row: &BoxedOperatorRowTrait, columns: &[usize]) -> #[cfg(test)] mod tests { - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; use super::*; #[test] fn test_simple_cumulative_sum_normal_operation() { - let row = MockRow::from_values(vec![Value::Float(10.0), Value::Float(20.0), Value::Float(30.0), Value::Float(40.0)]).into_boxed(); + 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)); @@ -46,7 +47,8 @@ mod tests { #[test] fn test_simple_cumulative_sum_partial_data() { - let row = MockRow::from_values(vec![Value::Float(10.0), Value::Empty, Value::Float(30.0), Value::Empty]).into_boxed(); + 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)); @@ -54,7 +56,8 @@ mod tests { #[test] fn test_simple_cumulative_sum_empty_input() { - let row = MockRow::from_values(vec![]).into_boxed(); + 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); @@ -62,7 +65,8 @@ mod tests { #[test] fn test_simple_cumulative_sum_no_valid_columns() { - let row = MockRow::from_values(vec![Value::Empty, Value::Empty]).into_boxed(); + 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 index 9f4d5499..05c9cf75 100644 --- a/src/custom_functions/simple_moving_average.rs +++ b/src/custom_functions/simple_moving_average.rs @@ -23,12 +23,13 @@ pub fn simple_moving_average(row: &BoxedOperatorRowTrait, columns: &[usize]) -> #[cfg(test)] mod tests { - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; use super::*; #[test] fn test_simple_moving_average_normal_operation() { - let row = MockRow::from_values(vec![Value::Float(10.0), Value::Float(20.0), Value::Float(30.0), Value::Float(40.0)]).into_boxed(); + 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)); @@ -36,7 +37,8 @@ mod tests { #[test] fn test_simple_moving_average_partial_data() { - let row = MockRow::from_values(vec![Value::Float(10.0), Value::Empty, Value::Float(30.0), Value::Empty]).into_boxed(); + 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)); @@ -44,7 +46,8 @@ mod tests { #[test] fn test_simple_moving_average_empty_input() { - let row = MockRow::from_values(vec![]).into_boxed(); + 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); @@ -52,7 +55,8 @@ mod tests { #[test] fn test_simple_moving_average_no_valid_columns() { - let row = MockRow::from_values(vec![Value::Empty, Value::Empty]).into_boxed(); + 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); @@ -61,8 +65,8 @@ mod tests { //Time: 4.481µs #[test] fn test_triangular_moving_average_normal_operation() { - - let row = MockRow::from_values((0..1111111).map(|idx| Value::Float(idx as f64)).collect::>()).into_boxed(); // Simple case with enough columns + 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); diff --git a/src/custom_functions/triangular_moving_average.rs b/src/custom_functions/triangular_moving_average.rs index 7c5390ee..432c0f61 100644 --- a/src/custom_functions/triangular_moving_average.rs +++ b/src/custom_functions/triangular_moving_average.rs @@ -62,13 +62,14 @@ fn get_price(row: &BoxedOperatorRowTrait, columns: &[usize], index: usize) -> Op #[cfg(test)] mod tests { use std::process::id; - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndex, MockIndexHolder, MockRow}; use super::*; //Time: 4.481µs #[test] fn test_triangular_moving_average_normal_operation() { - let row = MockRow::from_values((0..1111111).map(|idx| Value::Float(idx as f64)).collect::>()); // Simple case with enough columns + 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); @@ -89,7 +90,8 @@ mod tests { #[test] fn test_triangular_moving_average_empty_input() { - let row = MockRow::from_values(vec![]).into_boxed(); + 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()); @@ -99,7 +101,8 @@ mod tests { #[test] fn test_triangular_moving_average_empty() { - let row = MockRow::from_values(vec![]).into_boxed(); + 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); @@ -108,7 +111,9 @@ mod tests { #[test] fn test_triangular_moving_average_basic() { // Setup a simple scenario - let row = MockRow::from_values(vec![Value::Int(10), Value::Int(20), Value::Int(30), Value::Int(40), Value::Int(50)]).into_boxed(); + + 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(); @@ -122,13 +127,14 @@ mod tests { #[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) - ]).into_boxed(); + ], &mock_index_holder).into_boxed(); let columns = vec![0, 1, 2, 3, 4]; let result = triangular_moving_average(&row, &columns).unwrap(); @@ -141,7 +147,8 @@ mod tests { #[test] fn test_triangular_moving_average_invalid_values() { // Test how the function handles invalid (non-numeric) values - let row = MockRow::from_values(vec![Value::Empty, Value::Int(20), Value::Empty, Value::Int(40), Value::Empty]).into_boxed(); + 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(); diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 082edce5..9495bc51 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -2,7 +2,7 @@ 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, @@ -33,6 +33,7 @@ impl AdaptiveStopLossTradeModel { impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { + fn schema(&self) -> HashMap { vec![ ("active_trade", ValueType::Boolean), @@ -52,7 +53,7 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { 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, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + 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; diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index d234e213..f85b448c 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -1,13 +1,14 @@ use std::cmp; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Display; -use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType}; - +use crate::{BoxedOperatorRowTrait, CompiledTransposeCalculationTemplate, Error, FloatType, generate_column_name, OperatorRowTrait, Value, ValueType, IntType}; +use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder, TransposeColumnIndex, TransposeColumnIndexHolder}; +use crate::templates::test_utils::MockIndexHolder; pub struct BucketData { - bucket_output_field_name: String, bucket_range_output_field_name: String, + bucket_output_field_name: String, field_name_to_bucket: String, no_buckets: u8 } @@ -28,6 +29,7 @@ impl BucketData { } } impl CompiledTransposeCalculationTemplate for BucketData { + fn schema(&self) -> HashMap { vec![ (self.bucket_range_output_field_name.clone(), ValueType::String), @@ -42,62 +44,88 @@ impl CompiledTransposeCalculationTemplate for BucketData { fn commit_row( &self, row: &mut BoxedOperatorRowTrait, + indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize ) -> Result<(), Error> { - let mut transpose_value_to_field_value_map: HashMap = HashMap::new(); - let mut value_to_bucket_map: HashMap = HashMap::new(); + + // Maps to hold value-to-bucket and range information + let mut transpose_value_to_field_value_map: BTreeMap = BTreeMap::new(); + 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_for_column(self.field_name_to_bucket.clone())?; + let bucket_range_index = indexes.get_index_for_column(self.bucket_range_output_field_name.clone())?; + let bucket_index = indexes.get_index_for_column(self.bucket_output_field_name.clone())?; + // Populate transpose_value_to_field_value_map - for transpose_value in ordered_transpose_values { - let field_to_bucket = row.get_value(&generate_column_name(&self.field_name_to_bucket, transpose_value))?; - - if field_to_bucket == Value::Empty { - continue; - } - - transpose_value_to_field_value_map.insert(transpose_value.clone(), field_to_bucket); + for (idx, transpose_value) in ordered_transpose_values.iter().enumerate() { + let field_to_bucket = row.get_value_for_column(field_to_bucket_index.col_idx(idx)?)?; + value_to_bucket_map.entry(field_to_bucket).or_default().push(idx); } - // Calculate buckets and populate value_to_bucket_map + // // Calculate buckets and populate value_to_bucket_map + // let num_buckets = self.no_buckets; + // for (i, (transpose_value, field_value)) in transpose_value_to_field_value_map.iter().enumerate() { + // if field_value == &Value::Empty { + // continue; + // } + // + // // Calculate bucket index + // let bucket = (i * num_buckets as usize) / transpose_value_to_field_value_map.len(); + // let bucket_u8 = bucket as u8; + // + // // Assign field_value to a bucket if not already assigned + // value_to_bucket_map.entry(field_value.clone()).or_insert(bucket_u8); + // + // // Update min and max values for each bucket + // min_values_for_bucket + // .entry(bucket_u8) + // .and_modify(|min_value| if field_value < min_value { *min_value = field_value.clone(); }) + // .or_insert(field_value.clone()); + // + // max_values_for_bucket + // .entry(bucket_u8) + // .and_modify(|max_value| if field_value > max_value { *max_value = field_value.clone(); }) + // .or_insert(field_value.clone()); + // } + // + // // Set bucket and range values in the row + // let no_buckets = value_to_bucket_map.len(); let num_buckets = self.no_buckets; - let mut sorted_field_values: Vec<_> = transpose_value_to_field_value_map.values().cloned().collect(); - sorted_field_values.sort_by(|a, b| b.partial_cmp(a).unwrap_or(cmp::Ordering::Equal)); // Sort in ascending order - - for (i, field_value) in sorted_field_values.iter().enumerate() { - - if field_value == &Value::Empty { - continue; - } - - let bucket = (i * num_buckets as usize) / sorted_field_values.len(); - let bucket_u8 = bucket as u8; + 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() { - value_to_bucket_map.entry(field_value.clone()).or_insert(bucket_u8); + let bucket = ((idx * num_buckets as usize) / no_values_in_value_to_bucket_map) as u8; + for val in transpose_columns_for_value { + let bucket_output_index = bucket_index.col_idx(*val)?; + row.set_value_for_column(bucket_output_index, Value::Int((num_buckets - bucket) as IntType))?; + } min_values_for_bucket - .entry(bucket_u8) - .and_modify(|min_value| if field_value < min_value { *min_value = field_value.clone(); }) - .or_insert(field_value.clone()); - + .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_u8) - .and_modify(|max_value| if field_value > max_value { *max_value = field_value.clone(); }) - .or_insert(field_value.clone()); + .entry(bucket) + .and_modify(|max_value| if bucket_value > max_value { *max_value = bucket_value.clone(); }) + .or_insert(bucket_value.clone()); + } - // Set bucket values in the row - for (transpose_value, field_value) in transpose_value_to_field_value_map.iter() { - if let Some(bucket) = value_to_bucket_map.get(field_value) { - row.set_value(&generate_column_name(&self.bucket_output_field_name, transpose_value), Value::Int(*bucket as i64 + 1)); - 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) - ); - row.set_value(&generate_column_name(&self.bucket_range_output_field_name, transpose_value), Value::String(bucket_range))?; + 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 { + let bucket_range_index = bucket_range_index.col_idx(*val)?; + row.set_value_for_column(bucket_range_index, Value::String(bucket_range.clone()))?; } } Ok(()) @@ -109,7 +137,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { mod tests { use super::*; use std::collections::HashMap; - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; // Mock implementation of BoxedOperatorRowTrait for testing purposes @@ -117,12 +145,14 @@ mod tests { #[test] fn test_commit_row_basic() { // Create a mock row with initial values - let mut row = MockRow::new(); + let field_to_bucket = "price"; - row.set_value(&generate_column_name(field_to_bucket,&Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket,&Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket,&Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; // Instantiate BucketData with dummy values let bucket_data = BucketData { field_name_to_bucket: field_to_bucket.to_owned(), @@ -131,35 +161,46 @@ mod tests { bucket_range_output_field_name: "bucketRange".to_string(), }; - // Ordered transpose values (these should correspond to the field names) - let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - ]; + 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,&Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket,&Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket,&Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + + // Ordered transpose values (these should correspond to the field names) + // Call commit_row and check the results - let mut row = BoxedOperatorRowTrait::new(row); - bucket_data.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + 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!(row.get_value("bucket_date1").unwrap(), Value::Int(0)); - assert_eq!(row.get_value("bucketRange_date1").unwrap(), Value::String("foo".to_string())); - assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(2)); - assert_eq!(row.get_value("bucketRange_date2").unwrap(), Value::String("foo".to_string())); - assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(1)); - assert_eq!(row.get_value("bucketRange_date3").unwrap(), Value::String("foo".to_string())); + assert_eq!(operator_row.get_value("bucket__date1").unwrap(), Value::Int(3)); + assert_eq!(operator_row.get_value("bucketRange__date1").unwrap(), Value::String("1 to 1".to_string())); + assert_eq!(operator_row.get_value("bucket__date2").unwrap(), Value::Int(1)); + assert_eq!(operator_row.get_value("bucketRange__date2").unwrap(), Value::String("3 to 3".to_string())); + assert_eq!(operator_row.get_value("bucket__date3").unwrap(), Value::Int(2)); + assert_eq!(operator_row.get_value("bucketRange__date3").unwrap(), Value::String("2 to 2".to_string())); + } + + fn create_mock_index(ordered_transpose_values: &Vec, bucket_data: &BucketData) -> MockIndexHolder { + let mut mock_index = MockIndexHolder::new(); + mock_index.register_index(bucket_data.field_name_to_bucket.clone(), &ordered_transpose_values); + mock_index.register_index(bucket_data.bucket_output_field_name.clone(), &ordered_transpose_values); + mock_index.register_index(bucket_data.bucket_range_output_field_name.clone(), &ordered_transpose_values); + mock_index } #[test] fn test_commit_row_with_equal_values() { // Create a mock row with identical values - let mut row = MockRow::new(); + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; let field_to_bucket = "price"; - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(2.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(2.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); - // Instantiate BucketData with dummy values let bucket_data = BucketData { field_name_to_bucket: field_to_bucket.to_owned(), @@ -167,33 +208,31 @@ mod tests { no_buckets: 3, bucket_range_output_field_name: "bucketRange".to_string(), }; - - // Ordered transpose values (these should correspond to the field names) - let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - ]; - + 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, &Value::String("date1".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); // Call commit_row and check the results let mut row = BoxedOperatorRowTrait::new(row); - bucket_data.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); - + 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("bucket_date1").unwrap(), Value::Int(0)); - assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(0)); - assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(0)); + assert_eq!(row.get_value("bucket__date1").unwrap(), Value::Int(3)); + assert_eq!(row.get_value("bucket__date2").unwrap(), Value::Int(3)); + assert_eq!(row.get_value("bucket__date3").unwrap(), Value::Int(3)); } #[test] fn test_commit_row_with_varied_values() { // Create a mock row with varied values - let mut row = MockRow::new(); let field_to_bucket = "price"; - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; // Instantiate BucketData with dummy values let bucket_data = BucketData { @@ -202,22 +241,23 @@ mod tests { no_buckets: 3, bucket_range_output_field_name: "bucketRange".to_string(), }; - + 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) - let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - ]; - + // Call commit_row and check the results let mut row = BoxedOperatorRowTrait::new(row); - bucket_data.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), 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("bucket_date1").unwrap(), Value::Int(0)); - assert_eq!(row.get_value("bucket_date2").unwrap(), Value::Int(2)); - assert_eq!(row.get_value("bucket_date3").unwrap(), Value::Int(1)); + assert_eq!(row.get_value("bucket__date1").unwrap(), Value::Int(3)); + assert_eq!(row.get_value("bucket__date2").unwrap(), Value::Int(1)); + assert_eq!(row.get_value("bucket__date3").unwrap(), Value::Int(2)); } } diff --git a/src/templates/channel_monitoring_template.rs b/src/templates/channel_monitoring_template.rs index 9d165d8e..d05dee47 100644 --- a/src/templates/channel_monitoring_template.rs +++ b/src/templates/channel_monitoring_template.rs @@ -2,7 +2,7 @@ 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, @@ -40,6 +40,7 @@ impl ChannelMonitor { } impl CompiledTransposeCalculationTemplate for ChannelMonitor { + fn schema(&self) -> HashMap { vec![ (self.within_bounds_field_name.clone(), ValueType::Boolean), @@ -57,7 +58,7 @@ impl CompiledTransposeCalculationTemplate for ChannelMonitor { self.value_field_name.clone() ] } - fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + 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; @@ -103,7 +104,7 @@ impl CompiledTransposeCalculationTemplate for ChannelMonitor { mod commit_row_tests { use super::*; use std::collections::HashMap; - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; #[test] fn test_commit_row_value_below_lower_band() -> Result<(), Error> { @@ -114,7 +115,8 @@ mod commit_row_tests { "output", ); - let mut row = MockRow::new(); + let holder = MockIndexHolder::new(); + let mut row = MockRow::new(&holder); let cycle_epoch = 0; let transpose_value = Value::String("02_01_2024".to_owned()); let ordered_transpose_values = vec![transpose_value.clone()]; @@ -124,7 +126,8 @@ mod commit_row_tests { row.insert_value(generate_column_name("value", &transpose_value), Value::Float(5.0)); let mut row_trait = BoxedOperatorRowTrait::new(row); - monitor.commit_row(&mut row_trait, &ordered_transpose_values, cycle_epoch)?; + 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)); @@ -142,7 +145,8 @@ mod commit_row_tests { "output", ); - let mut row = MockRow::new(); + 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".to_owned()); let current_transpose_value = Value::String("02_01_2024".to_owned()); let ordered_transpose_values = vec![prev_transpose_value.clone(), current_transpose_value.clone()]; @@ -159,7 +163,8 @@ mod commit_row_tests { row.insert_value(generate_column_name("value", ¤t_transpose_value), Value::Float(15.0)); let mut row_trait = BoxedOperatorRowTrait::new(row); - monitor.commit_row(&mut row_trait, &ordered_transpose_values, 0)?; + 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)); @@ -184,7 +189,8 @@ mod commit_row_tests { "output", ); - let mut row = MockRow::new(); + 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".to_owned()); let current_transpose_value = Value::String("02_01_2024".to_owned()); let ordered_transpose_values = vec![prev_transpose_value.clone(), current_transpose_value.clone()]; @@ -201,7 +207,8 @@ mod commit_row_tests { row.insert_value(generate_column_name("value", ¤t_transpose_value), Value::Float(15.0)); let mut row_trait = BoxedOperatorRowTrait::new(row); - monitor.commit_row(&mut row_trait, &ordered_transpose_values, 0)?; + 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)); diff --git a/src/templates/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calcuation_template.rs index f45bad86..71df8749 100644 --- a/src/templates/compiled_transpose_calcuation_template.rs +++ b/src/templates/compiled_transpose_calcuation_template.rs @@ -5,16 +5,18 @@ 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, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>; - fn commit_row_wrapped(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error>{ - panic::catch_unwind(AssertUnwindSafe(|| self.commit_row(row, ordered_transpose_values, cycle_epoch))).map_err(|err| Error::CustomError(format!("{}", extract_message_from_any_error(err))))? + 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))))? } } diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs index 0015c30e..48efa272 100644 --- a/src/templates/rolling_zscore.rs +++ b/src/templates/rolling_zscore.rs @@ -3,6 +3,7 @@ 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}; pub struct RollingZScore { avg_output_field_name: String, @@ -30,6 +31,7 @@ impl RollingZScore { } impl CompiledTransposeCalculationTemplate for RollingZScore { + fn schema(&self) -> HashMap { vec![ (self.has_data_output_field_name.clone(), ValueType::Boolean), @@ -45,7 +47,7 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { vec![self.field_to_zscore.clone()] } - fn commit_row(&self, row: &mut BoxedOperatorRowTrait, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { + 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; @@ -55,10 +57,16 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { let mut last_stdev: Option = None; let mut last_zscore: Option = None; let mut last_value: Option = None; + + let zscore_output_index = indexes.get_index_for_column(self.zscore_output_field_name.clone())?; + let stdev_index = indexes.get_index_for_column(self.stdev_output_field_name.clone())?; + let avg_index = indexes.get_index_for_column(self.avg_output_field_name.clone())?; + let has_data_index = indexes.get_index_for_column(self.has_data_output_field_name.clone())?; + let field_to_zscore_index = indexes.get_index_for_column(self.field_to_zscore.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 = row.get_value(&generate_column_name(&self.field_to_zscore, transpose_value))?.as_float_or_none()?; + let value_opt = row.get_value_for_column(field_to_zscore_index.col_idx(i)?)?.as_float_or_none()?; // Process non-null values and update the window if let Some(value) = value_opt.or(last_value) { @@ -90,27 +98,27 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { last_zscore = Some(zscore); // Set the calculated values - row.set_value(&generate_column_name(&self.zscore_output_field_name, transpose_value), Value::Float(zscore))?; - row.set_value(&generate_column_name(&self.stdev_output_field_name, transpose_value), Value::Float(stdev))?; - row.set_value(&generate_column_name(&self.avg_output_field_name, transpose_value), Value::Float(avg))?; - row.set_value(&generate_column_name(&self.has_data_output_field_name, transpose_value), Value::Boolean(true))?; + row.set_value_for_column(zscore_output_index.col_idx(i)?, Value::Float(zscore))?; + row.set_value_for_column(stdev_index.col_idx(i)?, Value::Float(stdev))?; + row.set_value_for_column(avg_index.col_idx(i)?, Value::Float(avg))?; + row.set_value_for_column(has_data_index.col_idx(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 - row.set_value( - &generate_column_name(&self.zscore_output_field_name, transpose_value), + row.set_value_for_column( + zscore_output_index.col_idx(i)?, last_zscore.map(Value::Float).unwrap_or(Value::Empty), )?; - row.set_value( - &generate_column_name(&self.stdev_output_field_name, transpose_value), + row.set_value_for_column( + stdev_index.col_idx(i)?, last_stdev.map(Value::Float).unwrap_or(Value::Empty), )?; - row.set_value( - &generate_column_name(&self.avg_output_field_name, transpose_value), + row.set_value_for_column( + avg_index.col_idx(i)?, last_avg.map(Value::Float).unwrap_or(Value::Empty), )?; - row.set_value( - &generate_column_name(&self.has_data_output_field_name, transpose_value), + row.set_value_for_column( + has_data_index.col_idx(i)?, Value::Boolean(true), )?; } @@ -127,32 +135,40 @@ mod tests { use super::*; use std::collections::HashMap; use std::time::Instant; - use crate::templates::test_utils::MockRow; + use crate::templates::test_utils::{MockIndexHolder, MockRow}; #[test] fn test_commit_row_basic() { // Create a mock row with initial values - let mut row = MockRow::new(); + + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + Value::String("date4".to_string()), + ]; + let field_to_zscore = "price"; + let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 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".to_owned())), Value::Float(1.0)).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".to_owned())), Value::Float(4.0)).unwrap(); // Instantiate RollingZScore with dummy values - let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); + // Ordered transpose values (these should correspond to the field names) - let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - Value::String("date4".to_string()), - ]; + // Call commit_row and check the results let mut row = BoxedOperatorRowTrait::new(row); - rolling_zscore.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + 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("prefixzscore__date3").unwrap() != Value::Empty); // First full window @@ -169,30 +185,44 @@ mod tests { assert_eq!(row.get_value("prefixzscore__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(); + mock_index_column_holder.register_index(rolling_zscore.field_to_zscore.to_string(), &ordered_transpose_values); + mock_index_column_holder.register_index(rolling_zscore.zscore_output_field_name.to_string(), &ordered_transpose_values); + mock_index_column_holder.register_index(rolling_zscore.avg_output_field_name.to_string(), &ordered_transpose_values); + mock_index_column_holder.register_index(rolling_zscore.stdev_output_field_name.to_string(), &ordered_transpose_values); + mock_index_column_holder.register_index(rolling_zscore.has_data_output_field_name.to_string(), &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 mut row = MockRow::new(); let field_to_zscore = "price"; + + let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + Value::String("date4".to_string()), + ]; + 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".to_owned())), Value::Float(1.0)).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Empty).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".to_owned())), Value::Float(3.0)).unwrap(); // Instantiate RollingZScore with dummy values - let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); // Ordered transpose values (these should correspond to the field names) - let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - Value::String("date4".to_string()), - ]; + // Call commit_row and check the results let mut row = BoxedOperatorRowTrait::new(row); - rolling_zscore.commit_row(&mut row, &ordered_transpose_values, 0).unwrap(); + 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("prefixzscore__date2").unwrap() == Value::Empty); // Should be empty because it's null @@ -209,24 +239,30 @@ mod tests { #[test] fn test_commit_row_window_not_full() { + // Create a mock row with initial values - let mut row = MockRow::new(); + let field_to_zscore = "price"; + let rolling_zscore = RollingZScore::new(field_to_zscore, "zscore_", 3); + let ordered_transpose_values = vec![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + ]; + + 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".to_owned())), Value::Float(1.0)).unwrap(); row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); // Instantiate RollingZScore with dummy values - let rolling_zscore = RollingZScore::new(field_to_zscore, "zscore_", 3); // Ordered transpose values (these should correspond to the field names) - let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - ]; + // Call commit_row and check the results let mut row = BoxedOperatorRowTrait::new(row); - rolling_zscore.commit_row(&mut row, &ordered_transpose_values, 2).unwrap(); + 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 @@ -236,8 +272,16 @@ mod tests { #[test] fn test_commit_row_performance() { // Create a mock row with initial values - let mut row = MockRow::new(); + + let ordered_transpose_values: Vec = (1..=15000) + .map(|i| Value::String(format!("date{}", i))) + .collect(); + + let field_to_zscore = "price"; + let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 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 { @@ -248,21 +292,19 @@ mod tests { } // Instantiate RollingZScore with window size of 522 - let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 522); // Generate the ordered transpose values - let ordered_transpose_values: Vec = (1..=15000) - .map(|i| Value::String(format!("date{}", i))) - .collect(); + // 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, &ordered_transpose_values, 0).unwrap(); + 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(); } diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 35cd722d..27c2e4d1 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -1,22 +1,92 @@ use std::collections::HashMap; use indexmap::IndexMap; -use crate::{context, BoxedOperatorRowTrait, Error, EvalexprResult, OperatorRowTrait, Value}; +use crate::{context, generate_column_name, BoxedOperatorRowTrait, Error, EvalexprResult, OperatorRowTrait, Value}; +use crate::context::{BoxedTransposeColumnIndex, TransposeColumnIndex, TransposeColumnIndexHolder}; - -#[derive(Debug, Clone)] -pub struct MockRow { +#[derive(Clone)] +pub struct MockRow<'a> { values: IndexMap, + mock_index: &'a MockIndexHolder } -impl MockRow { +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); + self.values.insert(index_name.clone(), index); + for (key,idx) in &self.values.get(&index_name).unwrap().values { + self.output_column_indexes.insert(key.clone(),*idx); + } + self.offset += transpose_values.len(); + } +} + +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("Column name not found".to_string()))} + Some(val) => { + let raw =BoxedTransposeColumnIndex::new(val).into_raw(); + let val = unsafe{BoxedTransposeColumnIndex::from_raw(raw)}; + Ok(val) + } + } + } +} + +#[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 + mock_index.values.insert(key, offset + idx); + } + 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: IndexMap::new(), + mock_index } } - pub fn from_values(row: Vec) -> Self { - let mut mock_row = MockRow::new(); + 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() { @@ -27,7 +97,7 @@ impl MockRow { mock_row } - pub fn into_boxed<'inner>(self) -> BoxedOperatorRowTrait<'inner> { + pub fn into_boxed(self) -> BoxedOperatorRowTrait<'a> { BoxedOperatorRowTrait::new(self) } pub fn insert_value(&mut self, key: String, value: Value) { @@ -35,7 +105,7 @@ impl MockRow { } } -impl OperatorRowTrait for MockRow { +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 { @@ -59,13 +129,16 @@ impl OperatorRowTrait for MockRow { } fn set_value(&mut self, column_name: &str, value: Value) ->Result<(),crate::Error> { + println!("Setting value for column {} to {:?}",column_name,value); self.values.insert(column_name.to_string(), value); Ok(()) } fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(),crate::Error> { - todo!() + 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) { diff --git a/src/value/mod.rs b/src/value/mod.rs index 2d3ad0ad..ca3f3ec0 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -85,6 +85,13 @@ impl From<&Value> for Value { } } +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).unwrap() + } +} + impl PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { From 1dab43aac25ea7555ba641a05aa082b28fb52f54 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 14:33:08 +0100 Subject: [PATCH 198/265] Fixed build --- src/context/mod.rs | 7 +++++++ ...plate.rs => compiled_transpose_calculation_template.rs} | 0 src/templates/mod.rs | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) rename src/templates/{compiled_transpose_calcuation_template.rs => compiled_transpose_calculation_template.rs} (100%) diff --git a/src/context/mod.rs b/src/context/mod.rs index fe0dbced..f83f2a0b 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -6,6 +6,7 @@ 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}; @@ -393,6 +394,12 @@ pub trait TransposeColumnIndexHolder { fn get_index_for_column(&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))] diff --git a/src/templates/compiled_transpose_calcuation_template.rs b/src/templates/compiled_transpose_calculation_template.rs similarity index 100% rename from src/templates/compiled_transpose_calcuation_template.rs rename to src/templates/compiled_transpose_calculation_template.rs diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 2afba143..bcba35d3 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -1,7 +1,7 @@ pub mod adaptive_stop_loss_trade_model; pub mod ordered_float; pub mod bucket_data_template; -pub mod compiled_transpose_calcuation_template; +pub mod compiled_transpose_calculation_template; pub mod channel_monitoring_template; pub(crate) mod test_utils; pub mod rolling_zscore; @@ -12,6 +12,6 @@ pub use channel_monitoring_template::*; pub use adaptive_stop_loss_trade_model::*; pub use bucket_data_template::*; pub use rolling_zscore::*; -pub use compiled_transpose_calcuation_template::*; +pub use compiled_transpose_calculation_template::*; use crate::Value; From acafb6de83f44922852d24ce849c67a0375fe71d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 14:34:37 +0100 Subject: [PATCH 199/265] Fixed build --- src/context/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index f83f2a0b..ae5dc9b4 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -382,13 +382,13 @@ pub trait OperatorSchemaTrait { } -#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=true))] +#[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=true))] +#[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>; From 59adb6b93d95072452f2b512314dae63fdfbadd7 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 14:39:42 +0100 Subject: [PATCH 200/265] Fixed build --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e6319fae..8a756255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -537,6 +537,10 @@ pub use crate::{ ContextWithMutableVariables, EmptyContext, OperatorRowTrait, + TransposeColumnIndexHolder, + TransposeColumnIndex, + BoxedTransposeColumnIndex, + BoxedTransposeColumnIndexHolder, BoxedOperatorRowTrait, OperatorStatusContainerTrait, BoxedOperatorStatusContainerTrait, From 4791306a0b0d4b1d5e3554e30b8ad7dff621622f Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 16:52:31 +0100 Subject: [PATCH 201/265] Fixed build --- src/value/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index ca3f3ec0..cc1cabef 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -88,7 +88,7 @@ impl From<&Value> for Value { 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).unwrap() + self.partial_cmp(other).expect(format!("Cannot compare {:?} and {:?}", self, other).as_str()) } } From 6d059e45eb3b9fd7c962d4da3780793afdec17ca Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 16:55:54 +0100 Subject: [PATCH 202/265] Fixed barfing with empty field to bucket --- src/templates/bucket_data_template.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index f85b448c..1c8d63ac 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -63,6 +63,9 @@ impl CompiledTransposeCalculationTemplate for BucketData { // Populate transpose_value_to_field_value_map for (idx, transpose_value) in ordered_transpose_values.iter().enumerate() { let field_to_bucket = row.get_value_for_column(field_to_bucket_index.col_idx(idx)?)?; + if field_to_bucket == Value::Empty { + continue; + } value_to_bucket_map.entry(field_to_bucket).or_default().push(idx); } From d9dd2a81b2449aa728c4725b6e4b7744b28f7c8e Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 21 Aug 2024 18:05:41 +0100 Subject: [PATCH 203/265] Fixed barfing with empty field to bucket --- .../simple_cumilative_returns.rs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/custom_functions/simple_cumilative_returns.rs b/src/custom_functions/simple_cumilative_returns.rs index 945ebdf8..07ad821b 100644 --- a/src/custom_functions/simple_cumilative_returns.rs +++ b/src/custom_functions/simple_cumilative_returns.rs @@ -6,8 +6,10 @@ pub fn simple_cumulative_returns(row: &BoxedOperatorRowTrait, columns: &[usize]) } 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; @@ -15,7 +17,23 @@ pub fn simple_cumulative_returns(row: &BoxedOperatorRowTrait, columns: &[usize]) Value::Int(val) => { cumulative_return *= 1.0 + (val as f64); } - Value::Empty => {} + 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), } } From 0a076da02c12dd41b94f1ef5e7d85796e89cd6f5 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 22 Aug 2024 10:22:56 +0100 Subject: [PATCH 204/265] Refactor to reduce no calls across the FFI --- src/context/mod.rs | 25 ++++++++++++ src/templates/bucket_data_template.rs | 57 ++++++++------------------- src/templates/mod.rs | 1 + src/templates/rolling_zscore.rs | 52 ++++++++++++------------ src/templates/test_utils.rs | 30 ++++++++++++++ src/templates/utils.rs | 13 ++++++ 6 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 src/templates/utils.rs diff --git a/src/context/mod.rs b/src/context/mod.rs index ae5dc9b4..e35b6ef1 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -242,6 +242,10 @@ impl OperatorRowTrait for &mut IndexMapContext { 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 set_value(&mut self, identifier: &str, value: Value) -> Result<(), Error> { Ok(ContextWithMutableVariables::set_value(self, identifier.to_string(), value, true)?) } @@ -254,6 +258,13 @@ impl OperatorRowTrait for &mut IndexMapContext { 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, values[*column].clone())?; + } + Ok(()) + } + fn set_row(&mut self, row: usize) { todo!() } @@ -276,6 +287,10 @@ impl OperatorRowTrait for IndexMapContext { 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 set_value(&mut self, identifier: &str, value: Value) -> Result<(), Error> { Ok(ContextWithMutableVariables::set_value(self, identifier.to_string(), value, true)?) } @@ -288,6 +303,13 @@ impl OperatorRowTrait for IndexMapContext { todo!() } + fn set_values_for_columns(&mut self, columns: &Vec, values: &Vec) -> Result<(), Error> { + for column in columns { + self.set_value_for_column(*column, values[*column].clone())?; + } + Ok(()) + } + fn set_row(&mut self, row: usize) { todo!() } @@ -325,9 +347,11 @@ pub trait Context { #[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 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; @@ -392,6 +416,7 @@ pub trait TransposeColumnIndex { #[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> { diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index 1c8d63ac..a3dbf250 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -1,10 +1,11 @@ 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}; pub struct BucketData { bucket_range_output_field_name: String, @@ -27,6 +28,8 @@ impl BucketData { no_buckets } } + + } impl CompiledTransposeCalculationTemplate for BucketData { @@ -50,53 +53,27 @@ impl CompiledTransposeCalculationTemplate for BucketData { ) -> Result<(), Error> { // Maps to hold value-to-bucket and range information - let mut transpose_value_to_field_value_map: BTreeMap = BTreeMap::new(); 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_for_column(self.field_name_to_bucket.clone())?; - let bucket_range_index = indexes.get_index_for_column(self.bucket_range_output_field_name.clone())?; - let bucket_index = indexes.get_index_for_column(self.bucket_output_field_name.clone())?; + let field_to_bucket_index = indexes.get_index_vec(self.field_name_to_bucket.clone())?; + let bucket_range_index = indexes.get_index_vec(self.bucket_range_output_field_name.clone())?; + let bucket_index = indexes.get_index_vec(self.bucket_output_field_name.clone())?; + let values = &row.get_values()?; + let mut output_values = vec![Value::Empty; values.len()]; + let mut modified_columns = vec![]; // Populate transpose_value_to_field_value_map for (idx, transpose_value) in ordered_transpose_values.iter().enumerate() { - let field_to_bucket = row.get_value_for_column(field_to_bucket_index.col_idx(idx)?)?; - if field_to_bucket == Value::Empty { + 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).or_default().push(idx); + value_to_bucket_map.entry(field_to_bucket.clone()).or_default().push(idx); } - // // Calculate buckets and populate value_to_bucket_map - // let num_buckets = self.no_buckets; - // for (i, (transpose_value, field_value)) in transpose_value_to_field_value_map.iter().enumerate() { - // if field_value == &Value::Empty { - // continue; - // } - // - // // Calculate bucket index - // let bucket = (i * num_buckets as usize) / transpose_value_to_field_value_map.len(); - // let bucket_u8 = bucket as u8; - // - // // Assign field_value to a bucket if not already assigned - // value_to_bucket_map.entry(field_value.clone()).or_insert(bucket_u8); - // - // // Update min and max values for each bucket - // min_values_for_bucket - // .entry(bucket_u8) - // .and_modify(|min_value| if field_value < min_value { *min_value = field_value.clone(); }) - // .or_insert(field_value.clone()); - // - // max_values_for_bucket - // .entry(bucket_u8) - // .and_modify(|max_value| if field_value > max_value { *max_value = field_value.clone(); }) - // .or_insert(field_value.clone()); - // } - // - // // Set bucket and range values in the row - // let no_buckets = value_to_bucket_map.len(); 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() { @@ -104,8 +81,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { let bucket = ((idx * num_buckets as usize) / no_values_in_value_to_bucket_map) as u8; for val in transpose_columns_for_value { - let bucket_output_index = bucket_index.col_idx(*val)?; - row.set_value_for_column(bucket_output_index, Value::Int((num_buckets - bucket) as IntType))?; + 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) @@ -127,10 +103,11 @@ impl CompiledTransposeCalculationTemplate for BucketData { max_values_for_bucket.get(&bucket).unwrap_or(&Value::Empty) ); for val in transpose_columns_for_value { - let bucket_range_index = bucket_range_index.col_idx(*val)?; - row.set_value_for_column(bucket_range_index, Value::String(bucket_range.clone()))?; + set_value_indirect(&mut output_values, &mut modified_columns, &bucket_range_index, *val, Value::String(bucket_range.clone()))?; } } + + row.set_values_for_columns(&modified_columns,&output_values)?; Ok(()) } diff --git a/src/templates/mod.rs b/src/templates/mod.rs index bcba35d3..72bb24d2 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -5,6 +5,7 @@ pub mod compiled_transpose_calculation_template; pub mod channel_monitoring_template; pub(crate) mod test_utils; pub mod rolling_zscore; +mod utils; use std::fmt::Display; // Re-export the functions to the root of the crate diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs index 48efa272..1cefe072 100644 --- a/src/templates/rolling_zscore.rs +++ b/src/templates/rolling_zscore.rs @@ -4,6 +4,7 @@ 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 { avg_output_field_name: String, @@ -58,15 +59,18 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { let mut last_zscore: Option = None; let mut last_value: Option = None; - let zscore_output_index = indexes.get_index_for_column(self.zscore_output_field_name.clone())?; - let stdev_index = indexes.get_index_for_column(self.stdev_output_field_name.clone())?; - let avg_index = indexes.get_index_for_column(self.avg_output_field_name.clone())?; - let has_data_index = indexes.get_index_for_column(self.has_data_output_field_name.clone())?; - let field_to_zscore_index = indexes.get_index_for_column(self.field_to_zscore.clone())?; + let zscore_output_index = &indexes.get_index_vec(self.zscore_output_field_name.clone())?; + let stdev_index = &indexes.get_index_vec(self.stdev_output_field_name.clone())?; + let avg_index = &indexes.get_index_vec(self.avg_output_field_name.clone())?; + let has_data_index = &indexes.get_index_vec(self.has_data_output_field_name.clone())?; + let input_field_index = &indexes.get_index_vec(self.field_to_zscore.clone())?; + let row_values = &row.get_values()?; + let mut output_values = vec![Value::Empty; row_values.len()]; + let mut modified_columns = vec![]; 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 = row.get_value_for_column(field_to_zscore_index.col_idx(i)?)?.as_float_or_none()?; + 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) { @@ -97,33 +101,26 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { last_stdev = Some(stdev); last_zscore = Some(zscore); - // Set the calculated values - row.set_value_for_column(zscore_output_index.col_idx(i)?, Value::Float(zscore))?; - row.set_value_for_column(stdev_index.col_idx(i)?, Value::Float(stdev))?; - row.set_value_for_column(avg_index.col_idx(i)?, Value::Float(avg))?; - row.set_value_for_column(has_data_index.col_idx(i)?, Value::Boolean(true))?; + + 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 - row.set_value_for_column( - zscore_output_index.col_idx(i)?, - last_zscore.map(Value::Float).unwrap_or(Value::Empty), - )?; - row.set_value_for_column( - stdev_index.col_idx(i)?, - last_stdev.map(Value::Float).unwrap_or(Value::Empty), - )?; - row.set_value_for_column( - avg_index.col_idx(i)?, - last_avg.map(Value::Float).unwrap_or(Value::Empty), - )?; - row.set_value_for_column( - has_data_index.col_idx(i)?, - Value::Boolean(true), - )?; + 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(()) } @@ -176,6 +173,7 @@ mod tests { assert_eq!(row.get_value("prefixhasdata__date3").unwrap(), Value::Boolean(true)); assert_eq!(row.get_value("prefixhasdata__date4").unwrap(), Value::Boolean(true)); + // Test for the actual computed values // Replace `expected_zscore_date3` with the exact value. diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 27c2e4d1..145d0bdd 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -45,6 +45,16 @@ impl<'a> TransposeColumnIndexHolder for &'a MockIndexHolder{ } } } + + fn get_index_vec(&self, column_name: String) -> Result, Error> { + let option = self.values.get(&column_name); + match option { + None => { Err(Error::CustomError("Column name not found".to_string()))} + Some(val) => { + Ok(val.values.values().cloned().collect()) + } + } + } } #[derive(Debug, Clone)] @@ -157,4 +167,24 @@ impl<'a> OperatorRowTrait for MockRow<'a> { fn get_dirty_flags(&self) -> Result,crate::Error> { todo!() } + + fn get_values(&self) -> Result, Error> { + let mut result = vec![]; + for (nm,val) in &self.values{ + result.push(val.clone()) + } + for (idx,value) in &self.mock_index.values { + for value in &value.values { + result.push(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, values[*column].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..c102b87e --- /dev/null +++ b/src/templates/utils.rs @@ -0,0 +1,13 @@ +use crate::{Error, 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 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; + dirty_columns.push(*column); + Ok(()) +} \ No newline at end of file From 208b4df03efba13de5e36c3fe5f3c9e3f971497d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 22 Aug 2024 10:24:49 +0100 Subject: [PATCH 205/265] Refactor to reduce no calls across the FFI --- src/context/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index e35b6ef1..574ea65d 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -343,7 +343,7 @@ pub trait Context { } -#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=true))] +#[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 OperatorRowTrait { fn get_value(&self, identifier: &str) -> Result; From 6a6f3406337c873d6371d631140dd0801657b6b6 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 22 Aug 2024 10:32:03 +0100 Subject: [PATCH 206/265] Refactor to reduce no calls across the FFI --- src/context/mod.rs | 12 ++++++------ src/templates/bucket_data_template.rs | 2 +- src/templates/rolling_zscore.rs | 2 +- src/templates/test_utils.rs | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index 574ea65d..a633b7d3 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -258,9 +258,9 @@ impl OperatorRowTrait for &mut IndexMapContext { todo!() } - fn set_values_for_columns(&mut self, colums: &Vec, mut values: &Vec) -> Result<(), Error> { + fn set_values_for_columns(&mut self, colums: Vec, mut values: Vec) -> Result<(), Error> { for column in colums { - self.set_value_for_column(*column, values[*column].clone())?; + self.set_value_for_column(column.clone(), values.remove(column))?; } Ok(()) } @@ -303,9 +303,9 @@ impl OperatorRowTrait for IndexMapContext { todo!() } - fn set_values_for_columns(&mut self, columns: &Vec, values: &Vec) -> Result<(), Error> { + 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[*column].clone())?; + self.set_value_for_column(column, values.remove(column))?; } Ok(()) } @@ -343,7 +343,7 @@ pub trait Context { } -#[cfg_attr(feature = "serde_json_support", thin_trait_object(generate_dotnet_wrappers=false))] +#[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; @@ -351,7 +351,7 @@ pub trait OperatorRowTrait { 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_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; diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index a3dbf250..fac92db7 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -107,7 +107,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { } } - row.set_values_for_columns(&modified_columns,&output_values)?; + row.set_values_for_columns(modified_columns,output_values)?; Ok(()) } diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs index 1cefe072..b1ed9e44 100644 --- a/src/templates/rolling_zscore.rs +++ b/src/templates/rolling_zscore.rs @@ -119,7 +119,7 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { } } - row.set_values_for_columns(&modified_columns,&output_values)?; + row.set_values_for_columns(modified_columns,output_values)?; Ok(()) } diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 145d0bdd..bda007c3 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -181,9 +181,9 @@ impl<'a> OperatorRowTrait for MockRow<'a> { Ok(result) } - fn set_values_for_columns(&mut self, columns: &Vec, mut values: &Vec) -> Result<(), Error> { + 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[*column].clone())?; + self.set_value_for_column(column.clone(), values.remove(column))?; } Ok(()) } From 0af9a852a80586cf8ef4281ef01c31dce8563b92 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 22 Aug 2024 12:14:42 +0100 Subject: [PATCH 207/265] Refactor to reduce no calls across the FFI --- src/templates/rolling_zscore.rs | 220 ++++++++++++++++++-------------- src/templates/test_utils.rs | 2 +- 2 files changed, 123 insertions(+), 99 deletions(-) diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs index b1ed9e44..f5413e25 100644 --- a/src/templates/rolling_zscore.rs +++ b/src/templates/rolling_zscore.rs @@ -7,45 +7,52 @@ use crate::context::{BoxedTransposeColumnIndex, BoxedTransposeColumnIndexHolder, use crate::templates::utils::{get_value_indirect, set_value_indirect}; pub struct RollingZScore { - avg_output_field_name: String, - stdev_output_field_name: String, - has_data_output_field_name: String, - zscore_output_field_name: String, - field_to_zscore: String, - window_size: u32, + fields_to_zscore: Vec, + window_size: u32 } impl RollingZScore { - pub fn new(field_to_zscore: &str, output_column_prefix: &str, mut window_size: u32) -> Self { + pub fn new(fields_to_zscore: Vec<&str>, mut window_size: u32) -> Self { if window_size == 0 { window_size = 1; } RollingZScore { - avg_output_field_name: format!("{}avg", output_column_prefix), - stdev_output_field_name: format!("{}stdev", output_column_prefix), - has_data_output_field_name: format!("{}hasdata", output_column_prefix), - zscore_output_field_name: format!("{}zscore", output_column_prefix), - field_to_zscore: field_to_zscore.to_string(), + 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 { - vec![ - (self.has_data_output_field_name.clone(), ValueType::Boolean), - (self.avg_output_field_name.clone(), ValueType::Float), - (self.stdev_output_field_name.clone(), ValueType::Float), - (self.zscore_output_field_name.clone(), ValueType::Float), - ] + 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 { - vec![self.field_to_zscore.clone()] + self.fields_to_zscore.clone() } fn commit_row(&self, row: &mut BoxedOperatorRowTrait,indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], cycle_epoch: usize) -> Result<(), Error> { @@ -54,71 +61,85 @@ impl CompiledTransposeCalculationTemplate for RollingZScore { let mut window_sum_squares = 0.0; // These variables will store the last valid values for avg, stdev, and zscore as Options - 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_index = &indexes.get_index_vec(self.zscore_output_field_name.clone())?; - let stdev_index = &indexes.get_index_vec(self.stdev_output_field_name.clone())?; - let avg_index = &indexes.get_index_vec(self.avg_output_field_name.clone())?; - let has_data_index = &indexes.get_index_vec(self.has_data_output_field_name.clone())?; - let input_field_index = &indexes.get_index_vec(self.field_to_zscore.clone())?; + let row_values = &row.get_values()?; let mut output_values = vec![Value::Empty; row_values.len()]; let mut modified_columns = vec![]; - 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; + 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))?; } - - // 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(()) @@ -146,7 +167,7 @@ mod tests { ]; let field_to_zscore = "price"; - let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); + 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); @@ -168,28 +189,31 @@ mod tests { 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("prefixzscore__date3").unwrap() != Value::Empty); // First full window - assert!(row.get_value("prefixzscore__date4").unwrap() != Value::Empty); // Second full window - assert_eq!(row.get_value("prefixhasdata__date3").unwrap(), Value::Boolean(true)); - assert_eq!(row.get_value("prefixhasdata__date4").unwrap(), Value::Boolean(true)); + 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("prefixzscore__date1").unwrap(), Value::Empty); - assert_eq!(row.get_value("prefixzscore__date2").unwrap(), Value::Empty); - assert_eq!(row.get_value("prefixzscore__date3").unwrap(), Value::Float(0f64)); - assert_eq!(row.get_value("prefixzscore__date4").unwrap(), Value::Float(1.2247448713915896f64)); + 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(); - mock_index_column_holder.register_index(rolling_zscore.field_to_zscore.to_string(), &ordered_transpose_values); - mock_index_column_holder.register_index(rolling_zscore.zscore_output_field_name.to_string(), &ordered_transpose_values); - mock_index_column_holder.register_index(rolling_zscore.avg_output_field_name.to_string(), &ordered_transpose_values); - mock_index_column_holder.register_index(rolling_zscore.stdev_output_field_name.to_string(), &ordered_transpose_values); - mock_index_column_holder.register_index(rolling_zscore.has_data_output_field_name.to_string(), &ordered_transpose_values); + 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 } @@ -198,7 +222,7 @@ mod tests { // Create a mock row with initial values, including a null value let field_to_zscore = "price"; - let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 3); + let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); let ordered_transpose_values = vec![ Value::String("date1".to_string()), Value::String("date2".to_string()), @@ -223,16 +247,16 @@ mod tests { 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("prefixzscore__date2").unwrap() == Value::Empty); // Should be empty because it's null - assert!(row.get_value("prefixzscore__date3").unwrap() != Value::Empty); // Should have a value - assert!(row.get_value("prefixzscore__date4").unwrap() != Value::Empty); // Should have a value - assert_eq!(row.get_value("prefixhasdata__date3").unwrap(), Value::Boolean(true)); - assert_eq!(row.get_value("prefixhasdata__date4").unwrap(), Value::Boolean(true)); + 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("prefixzscore__date3").unwrap(), Value::Float(1.414213562373095f64)); + assert_eq!(row.get_value("pricezscore__date3").unwrap(), Value::Float(1.414213562373095f64)); } #[test] @@ -241,7 +265,7 @@ mod tests { // Create a mock row with initial values let field_to_zscore = "price"; - let rolling_zscore = RollingZScore::new(field_to_zscore, "zscore_", 3); + let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); let ordered_transpose_values = vec![ Value::String("date1".to_string()), Value::String("date2".to_string()), @@ -277,7 +301,7 @@ mod tests { let field_to_zscore = "price"; - let rolling_zscore = RollingZScore::new(field_to_zscore, "prefix", 522); + 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); diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index bda007c3..6236be79 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -183,7 +183,7 @@ impl<'a> OperatorRowTrait for MockRow<'a> { 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.remove(column))?; + self.set_value_for_column(column.clone(), values.get(column).unwrap().clone())?; } Ok(()) } From 3e820e08ffc620f2a5f8d955b9d33775457fa96d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 23 Aug 2024 09:02:35 +0100 Subject: [PATCH 208/265] Refactor to reduce no calls across the FFI --- src/templates/bucket_data_template.rs | 191 +++++++++++++------------- 1 file changed, 93 insertions(+), 98 deletions(-) diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index fac92db7..e8d89226 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -7,110 +7,110 @@ 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 { - bucket_range_output_field_name: String, - bucket_output_field_name: String, - field_name_to_bucket: String, - no_buckets: u8 + fields_to_bucket: Vec, + no_buckets: u8, } impl BucketData { - pub fn new(field_to_bucket: &str, mut no_buckets: u8) -> BucketData { + pub fn new(fields_to_bucket: Vec<&str>, mut no_buckets: u8) -> BucketData { if no_buckets == 0 { no_buckets = 1; } - let bucket_field_name = format!("{}bucket", field_to_bucket); - let bucket_range_field_name = format!("{}bucketrange", field_to_bucket); BucketData { - bucket_range_output_field_name: bucket_range_field_name, - bucket_output_field_name: bucket_field_name, - field_name_to_bucket: field_to_bucket.to_owned(), - no_buckets + fields_to_bucket: fields_to_bucket.iter().map(|fld| fld.to_string()).collect(), + no_buckets, } } - - } impl CompiledTransposeCalculationTemplate for BucketData { - fn schema(&self) -> HashMap { - vec![ - (self.bucket_range_output_field_name.clone(), ValueType::String), - (self.bucket_output_field_name.clone(), ValueType::Int), - ].iter().map(|(nm, val)| (nm.to_string(), *val)).collect() + 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 { - vec![ - self.field_name_to_bucket.clone() - ] + self.fields_to_bucket.clone() } fn commit_row( &self, row: &mut BoxedOperatorRowTrait, indexes: &BoxedTransposeColumnIndexHolder, ordered_transpose_values: &[Value], - cycle_epoch: usize + cycle_epoch: usize, ) -> Result<(), Error> { // Maps to hold value-to-bucket and range information - 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(self.field_name_to_bucket.clone())?; - let bucket_range_index = indexes.get_index_vec(self.bucket_range_output_field_name.clone())?; - let bucket_index = indexes.get_index_vec(self.bucket_output_field_name.clone())?; let values = &row.get_values()?; let mut output_values = vec![Value::Empty; values.len()]; let mut modified_columns = vec![]; - // 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() { + 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 bucket = ((idx * num_buckets as usize) / no_values_in_value_to_bucket_map) as u8; + 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 + 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 + 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()))?; + 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()))?; + } } } - - row.set_values_for_columns(modified_columns,output_values)?; + row.set_values_for_columns(modified_columns, output_values)?; Ok(()) } - } #[cfg(test)] @@ -125,7 +125,7 @@ mod tests { #[test] fn test_commit_row_basic() { // Create a mock row with initial values - + let field_to_bucket = "price"; let ordered_transpose_values = vec![ @@ -135,40 +135,40 @@ mod tests { ]; // Instantiate BucketData with dummy values let bucket_data = BucketData { - field_name_to_bucket: field_to_bucket.to_owned(), - bucket_output_field_name: "bucket".to_string(), + fields_to_bucket: vec![field_to_bucket.to_string()], no_buckets: 3, - bucket_range_output_field_name: "bucketRange".to_string(), }; 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,&Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket,&Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket,&Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), 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(); + 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("bucket__date1").unwrap(), Value::Int(3)); - assert_eq!(operator_row.get_value("bucketRange__date1").unwrap(), Value::String("1 to 1".to_string())); - assert_eq!(operator_row.get_value("bucket__date2").unwrap(), Value::Int(1)); - assert_eq!(operator_row.get_value("bucketRange__date2").unwrap(), Value::String("3 to 3".to_string())); - assert_eq!(operator_row.get_value("bucket__date3").unwrap(), Value::Int(2)); - assert_eq!(operator_row.get_value("bucketRange__date3").unwrap(), Value::String("2 to 2".to_string())); + assert_eq!(operator_row.get_value("pricebucket__date1").unwrap(), Value::Int(3)); + assert_eq!(operator_row.get_value("pricebucketRange__date1").unwrap(), Value::String("1 to 1".to_string())); + assert_eq!(operator_row.get_value("pricebucket__date2").unwrap(), Value::Int(1)); + assert_eq!(operator_row.get_value("pricebucketRange__date2").unwrap(), Value::String("3 to 3".to_string())); + assert_eq!(operator_row.get_value("pricebucket__date3").unwrap(), Value::Int(2)); + assert_eq!(operator_row.get_value("pricebucketRange__date3").unwrap(), Value::String("2 to 2".to_string())); } fn create_mock_index(ordered_transpose_values: &Vec, bucket_data: &BucketData) -> MockIndexHolder { let mut mock_index = MockIndexHolder::new(); - mock_index.register_index(bucket_data.field_name_to_bucket.clone(), &ordered_transpose_values); - mock_index.register_index(bucket_data.bucket_output_field_name.clone(), &ordered_transpose_values); - mock_index.register_index(bucket_data.bucket_range_output_field_name.clone(), &ordered_transpose_values); + 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 } @@ -183,10 +183,8 @@ mod tests { let field_to_bucket = "price"; // Instantiate BucketData with dummy values let bucket_data = BucketData { - field_name_to_bucket: field_to_bucket.to_owned(), - bucket_output_field_name: "bucket".to_string(), + fields_to_bucket: vec![field_to_bucket.to_string()], no_buckets: 3, - bucket_range_output_field_name: "bucketRange".to_string(), }; let mock_index = create_mock_index(&ordered_transpose_values, &bucket_data); let mut row = MockRow::new(&mock_index); @@ -196,11 +194,11 @@ mod tests { // 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(); + 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("bucket__date1").unwrap(), Value::Int(3)); - assert_eq!(row.get_value("bucket__date2").unwrap(), Value::Int(3)); - assert_eq!(row.get_value("bucket__date3").unwrap(), Value::Int(3)); + 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)); } @@ -208,7 +206,7 @@ mod tests { fn test_commit_row_with_varied_values() { // Create a mock row with varied values let field_to_bucket = "price"; - let ordered_transpose_values = vec![ + let ordered_transpose_values = vec![ Value::String("date1".to_string()), Value::String("date2".to_string()), Value::String("date3".to_string()), @@ -216,15 +214,13 @@ mod tests { // Instantiate BucketData with dummy values let bucket_data = BucketData { - field_name_to_bucket: field_to_bucket.to_owned(), - bucket_output_field_name: "bucket".to_string(), + fields_to_bucket: vec![field_to_bucket.to_string()], no_buckets: 3, - bucket_range_output_field_name: "bucketRange".to_string(), }; 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, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); @@ -232,12 +228,11 @@ mod tests { row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); let mut index = BoxedTransposeColumnIndexHolder::new(&mock_index); - bucket_data.commit_row(&mut row,&index, &ordered_transpose_values, 0).unwrap(); + 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("bucket__date1").unwrap(), Value::Int(3)); - assert_eq!(row.get_value("bucket__date2").unwrap(), Value::Int(1)); - assert_eq!(row.get_value("bucket__date3").unwrap(), Value::Int(2)); + 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)); } - } From c9e048baf1218aaa5c10bfd49ec4e69bfa8a62f8 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 23 Aug 2024 10:08:26 +0100 Subject: [PATCH 209/265] Refactor to reduce no calls across the FFI --- src/value/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index cc1cabef..92d1c7f2 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -105,6 +105,8 @@ impl PartialOrd for Value { // 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, } From 8288f8a38a2de00b1bb9ce2c5f67da2e92b97c93 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 24 Aug 2024 22:22:33 +0100 Subject: [PATCH 210/265] Refactor to reduce no calls across the FFI --- src/templates/mod.rs | 2 + src/templates/simple_trade_model.rs | 477 ++++++++++++++++++++++++++++ src/templates/test_utils.rs | 189 ++++++++--- src/templates/utils.rs | 10 +- 4 files changed, 632 insertions(+), 46 deletions(-) create mode 100644 src/templates/simple_trade_model.rs diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 72bb24d2..468d931d 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -3,6 +3,7 @@ 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; @@ -11,6 +12,7 @@ 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 rolling_zscore::*; pub use compiled_transpose_calculation_template::*; diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs new file mode 100644 index 00000000..0697bd83 --- /dev/null +++ b/src/templates/simple_trade_model.rs @@ -0,0 +1,477 @@ +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, 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), + ("exit_price", ValueType::Float), + ("stop_loss", ValueType::Float), + ("trade_age", 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()] + } + 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 delta: Option = None; + let mut conviction: Option = None; + let mut reason: Option = None; + let mut trade_age: 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 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 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 all_values = row.get_values()?; + let mut dirty_columns = vec![]; + + if cycle_epoch > 0 { + let transpose_index_before_epoch = cycle_epoch - 1; + + active_trade = get_value_indirect(&all_values, &active_trade_index,transpose_index_before_epoch)?.as_boolean_or_none()?; + trade_id = get_value_indirect(&all_values, &trade_id_index,transpose_index_before_epoch)?.as_string_or_none()?; + initiation_price = get_value_indirect(&all_values, &initiation_price_index,transpose_index_before_epoch)?.as_float_or_none()?; + trade_age = get_value_indirect(&all_values, &trade_age_index,transpose_index_before_epoch)?.as_int_or_none()?; + initiation_date = get_value_indirect(&all_values, &initiation_date_index,transpose_index_before_epoch)?.as_string_or_none()?; + stop_loss = get_value_indirect(&all_values, ¤t_stop_loss_index, transpose_index_before_epoch)?.as_float_or_none()?; + take_profit = get_value_indirect(&all_values, ¤t_take_profit_index, transpose_index_before_epoch)?.as_float_or_none()?; + conviction = get_value_indirect(&all_values, &conviction_index, transpose_index_before_epoch)?.as_float_or_none()?; + reason = get_value_indirect(&all_values, &reason_index,transpose_index_before_epoch)?.as_string_or_none()?; + } + + if cycle_epoch > 1 { + prev_trade_signal = get_value_indirect(&all_values, &signal_index, cycle_epoch - 2)?.as_boolean_or_none()?; + } + + for i in cycle_epoch ..ordered_transpose_values.len() { + 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)) = + ( + get_value_indirect(&all_values, &instrument_index,i)?.as_string_or_none()?, + 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); + 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!("{} {} Closing trade. Current price ({}) has fallen to or below stop loss {} from entry price ({}).", 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!("{} {} Closing trade. Current price ({}) has reached or exceeded take profit level {} from entry price ({}).",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!("{} {} 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() { + 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(&all_values, &initial_stop_loss_index,i)?.as_float_or_none()?; + take_profit = get_value_indirect(&all_values, &initial_take_profit_index,i)?.as_float_or_none()?; + active_trade = Some(true); + trade_age = Some(0); + } + } + } + + 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(Value::String))?; + 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, &initiation_date_index, i, initiation_date.clone().map(Value::String))?; + set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &trade_id_index, i, trade_id.clone().map(Value::String))?; + 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))?; + 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; + } + } + } + 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![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; + + 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, Value::String("instrument1".to_owned()))?; + 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)?, Value::String("date1".to_owned())); + assert_eq!(row.get_value_for_transpose_index("trade_id",0)?, Value::String("date1instrument1".to_owned())); + 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![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; + + 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 initial values to initiate the trade + row.set_value_for_transpose_index("instrument",0, Value::String("instrument1".to_owned()))?; + 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("initial_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))?; + // 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, 0).unwrap(); + } + + row.set_value_for_transpose_index("instrument",1, Value::String("instrument1".to_owned()))?; + // Simulate a price drop to hit the stop loss + row.set_value_for_transpose_index("price",1, Value::Float(94.0))?; + row.set_value_for_transpose_index("instrument",2, Value::String("instrument1".to_owned()))?; + // Simulate a price drop to hit the stop loss + row.set_value_for_transpose_index("price",2, 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, 1).unwrap(); + } + + // Verify that the trade has been closed due to stop loss + assert_eq!(row.get_value_for_transpose_index("active_trade",0)?, Value::Boolean(true)); + 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(false)); + assert_eq!(row.get_value_for_transpose_index("exit_price",1)?, Value::Float(94.0)); + assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(reason) if reason.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![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + ]; + + 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 initial values to initiate the trade + row.set_value_for_transpose_index("instrument",0, Value::String("instrument1".to_owned()))?; + 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(); + } + + // Simulate a price rise to hit the take profit + row.set_value_for_transpose_index("instrument",1, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("price",1, Value::Float(111.0))?; + // Simulate a price rise to hit the take profit + row.set_value_for_transpose_index("instrument",2, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("price",2, 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, 1).unwrap(); + } + + // Verify that the trade has been closed due to take profit + 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(false)); + assert_eq!(row.get_value_for_transpose_index("exit_price",1)?, Value::Float(111.0)); + assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(reason) if reason.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![ + Value::String("date1".to_string()), + Value::String("date2".to_string()), + Value::String("date3".to_string()), + Value::String("date4".to_string()), + ]; + + 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 initial values to initiate the trade + row.set_value_for_transpose_index("instrument",0, Value::String("instrument1".to_owned()))?; + 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("initial_stop_loss",0, Value::Float(95.0))?; + row.set_value_for_transpose_index("initial_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(); + } + + // Simulate the passing of time beyond the holding period + row.set_value_for_transpose_index("instrument",1, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("price",1, 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, 1).unwrap(); + } + + row.set_value_for_transpose_index("instrument",2, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("price",2, Value::Float(103.0))?; + + row.set_value_for_transpose_index("instrument",3, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("price",3, 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, 2).unwrap(); + } + + // Verify that the trade has been closed due to holding period expiry + assert_eq!(row.get_value_for_transpose_index("active_trade",2)?, Value::Boolean(true)); + assert_eq!(row.get_value_for_transpose_index("exit_price",2)?, Value::Float(103.0)); + assert!(matches!(row.get_value_for_transpose_index("reason",2)?, Value::String(reason) if reason.contains("holding period"))); + assert_eq!(row.get_value_for_transpose_index("active_trade",3)?, 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", + "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 index 6236be79..67438602 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -5,8 +5,8 @@ use crate::context::{BoxedTransposeColumnIndex, TransposeColumnIndex, TransposeC #[derive(Clone)] pub struct MockRow<'a> { - values: IndexMap, - mock_index: &'a MockIndexHolder + values: HashMap, + mock_index: &'a MockIndexHolder, } pub struct MockIndexHolder { @@ -23,24 +23,40 @@ impl MockIndexHolder { output_column_indexes: Default::default(), } } - pub fn register_index(&mut self, index_name: String, transpose_values: &[Value]) { + 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 { - self.output_column_indexes.insert(key.clone(),*idx); + 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{ +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("Column name not found".to_string()))} + 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)}; + let raw = BoxedTransposeColumnIndex::new(val).into_raw(); + let val = unsafe { BoxedTransposeColumnIndex::from_raw(raw) }; Ok(val) } } @@ -49,9 +65,10 @@ impl<'a> TransposeColumnIndexHolder for &'a MockIndexHolder{ fn get_index_vec(&self, column_name: String) -> Result, Error> { let option = self.values.get(&column_name); match option { - None => { Err(Error::CustomError("Column name not found".to_string()))} + None => { Err(Error::CustomError(format!("Column name {column_name} not found"))) } Some(val) => { - Ok(val.values.values().cloned().collect()) + let vec = val.values.values().cloned().collect(); + Ok(vec) } } } @@ -63,39 +80,40 @@ pub struct MockIndex { } impl MockIndex { - pub fn from_transpose_values(index_name: String, transpose_values: &[Value], offset: usize) -> Self { + 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 - mock_index.values.insert(key, offset + idx); + 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{ +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)) => { + None => { Err(Error::CustomError("Transpose index not found".to_string())) } + Some((_nm, val)) => { Ok(*val) } } } } -impl<'a> MockRow<'a>{ +impl<'a> MockRow<'a> { pub fn new(mock_index: &'a MockIndexHolder) -> Self { MockRow { - values: IndexMap::new(), - mock_index + values: HashMap::new(), + mock_index, } } - pub fn from_values(row: Vec,mock_index: &'a MockIndexHolder) -> Self { + 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 @@ -106,48 +124,62 @@ impl<'a> MockRow<'a>{ 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) { - self.values.insert(key, value); + 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 option = self.values.get(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)} + None => { Ok(Value::Empty) } Some(val) => { - Ok(val.clone()) + Ok(val.clone()) } } } fn get_value_for_column(&self, col: usize) -> Result { - let option = self.values.get_index(col); + let option = self.values.get(&col); match option { - None => { Ok(Value::Empty)} + None => { Ok(Value::Empty) } Some(val) => { - Ok(val.1.clone()) + 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); - self.values.insert(column_name.to_string(), value); + 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> { - 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())?; + fn set_value_for_column(&mut self, col: usize, value: Value) -> Result<(), crate::Error> { + self.values.insert(col, value); Ok(()) } @@ -164,20 +196,87 @@ impl<'a> OperatorRowTrait for MockRow<'a> { } - fn get_dirty_flags(&self) -> Result,crate::Error> { + fn get_dirty_flags(&self) -> Result, crate::Error> { todo!() } fn get_values(&self) -> Result, Error> { - let mut result = vec![]; - for (nm,val) in &self.values{ - result.push(val.clone()) + 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(()) + } +} + + +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()) + } } - for (idx,value) in &self.mock_index.values { - for value in &value.values { - result.push(Value::Empty) + } + + 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 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) } diff --git a/src/templates/utils.rs b/src/templates/utils.rs index c102b87e..4a1362ce 100644 --- a/src/templates/utils.rs +++ b/src/templates/utils.rs @@ -4,10 +4,18 @@ pub fn get_value_indirect<'a>(values: &'a Vec, column_index: &Vec 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 set_value_indirect<'a>(values: &'a mut Vec,dirty_columns: &'a mut Vec, column_index: &Vec, idx: usize, value: Value, ) -> Result<(), Error> { +}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; 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; + dirty_columns.push(*column); + } + Ok(()) } \ No newline at end of file From 4e70fe81c236d6a367c1f8b3679d2b1ddcced0f7 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 24 Aug 2024 22:49:08 +0100 Subject: [PATCH 211/265] Refactor to reduce no calls across the FFI --- src/templates/simple_trade_model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index 0697bd83..b2a69696 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -50,7 +50,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { ].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()] + 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; From 82cacf075aa70e9d75f2f27be08d333a231a5fb6 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 09:43:54 +0100 Subject: [PATCH 212/265] Refactor to reduce no calls across the FFI --- src/context/mod.rs | 17 +++++++++++++++++ src/templates/simple_trade_model.rs | 21 ++++++++++++++++++++- src/templates/test_utils.rs | 22 ++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index a633b7d3..33262a89 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -246,6 +246,14 @@ impl OperatorRowTrait for &mut IndexMapContext { 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)?) } @@ -291,6 +299,14 @@ impl OperatorRowTrait for IndexMapContext { 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)?) } @@ -348,6 +364,7 @@ pub trait Context { 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>; diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index b2a69696..ed6e0fc5 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -85,7 +85,26 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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 all_values = row.get_values()?; + + let mut all_cols = instrument_index.clone(); + all_cols.extend(signal_index.clone()); + all_cols.extend(price_index.clone()); + all_cols.extend(conviction_index.clone()); + all_cols.extend(initial_stop_loss_index.clone()); + all_cols.extend(initial_take_profit_index.clone()); + all_cols.extend(active_trade_index.clone()); + all_cols.extend(trade_id_index.clone()); + all_cols.extend(initiation_price_index.clone()); + all_cols.extend(trade_age_index.clone()); + all_cols.extend(initiation_date_index.clone()); + all_cols.extend(current_stop_loss_index.clone()); + all_cols.extend(current_take_profit_index.clone()); + all_cols.extend(reason_index.clone()); + all_cols.extend(delta_index.clone()); + all_cols.extend(exit_price_index.clone()); + + + let mut all_values = row.get_values_for_columns(all_cols)?; let mut dirty_columns = vec![]; if cycle_epoch > 0 { diff --git a/src/templates/test_utils.rs b/src/templates/test_utils.rs index 67438602..5168e3e0 100644 --- a/src/templates/test_utils.rs +++ b/src/templates/test_utils.rs @@ -214,6 +214,17 @@ impl<'a> OperatorRowTrait for MockRow<'a> { } 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) + } } @@ -248,6 +259,17 @@ impl<'a> OperatorRowTrait for &mut MockRow<'a> { 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)))?; From 3882fd0ce364dbbc249722417179180b7fdc1dd9 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 10:47:33 +0100 Subject: [PATCH 213/265] Changed strings into CowData to avoid copying --- src/custom_functions/expression_functions.rs | 21 ++- src/error/mod.rs | 6 +- src/function/builtin.rs | 4 +- src/interface/mod.rs | 4 +- src/operator/mod.rs | 2 +- .../adaptive_stop_loss_trade_model.rs | 6 +- src/templates/bucket_data_template.rs | 44 ++--- src/templates/channel_monitoring_template.rs | 10 +- src/templates/rolling_zscore.rs | 44 ++--- src/templates/simple_trade_model.rs | 64 +++---- src/tree/mod.rs | 6 +- src/value/mod.rs | 167 +++++++++++++++++- 12 files changed, 267 insertions(+), 111 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 54a4bf19..c64a0c93 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -142,23 +142,26 @@ pub fn substring,TR: Into, TC: Into>(message: TL, // Ensure start is within bounds and len does not exceed the message length let start_int = Into::::into(start).as_int()? as usize; let len_int = Into::::into(len).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(Value::String(substring.to_string())); + return Ok(substring.to_string().into()); } } - return Ok(Value::String("".to_string())); + Ok("".to_string().into()) } pub fn starts_with(message: &Value, prefix: &Value) -> Result { if let (Value::String(message), Value::String(prefix)) = (message, prefix) { - if message.starts_with(prefix) { + let message = message.ref_into_owned(); + let prefix = prefix.ref_into_owned(); + if message.starts_with(&prefix) { return Ok(Value::Boolean(true)); } } - return Ok(Value::Boolean(false)); + Ok(Value::Boolean(false)) } pub fn ternary,TL: Into,TR: Into>(condition: TC, true_value: TL, false_value: TR) -> Result { @@ -193,6 +196,8 @@ fn round_datetime_to_precision(datetime: DateTime, precision: &str) -> Resu 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)?; @@ -205,7 +210,7 @@ pub fn round_date_to_precision,TR: Into>(string: TL, prec string1.push_str("_"); } let result = format!("{}{}", string1, rounded_datetime.format("%Y.%m.%d %H:%M:%S").to_string()); - Ok(Value::String(result)) + Ok(result.into()) } else { // If arguments are not strings, return an error Err(Error::InvalidArgumentType) @@ -244,7 +249,7 @@ mod test{ ); 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, Value::String(format!("BTCUSD_{}", expected))); + assert_eq!(result, format!("BTCUSD_{}", expected).into()); } #[test] @@ -255,7 +260,7 @@ mod test{ ); 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, Value::String(format!("BTCUSD_{}", expected))); + assert_eq!(result, format!("BTCUSD_{}", expected).into()); } #[test] @@ -267,7 +272,7 @@ mod test{ // 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, Value::String(format!("BTCUSD_{}", expected))); + assert_eq!(result, format!("BTCUSD_{}", expected).into()); } #[test] diff --git a/src/error/mod.rs b/src/error/mod.rs index a4b2d442..b84482ee 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -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/operator/mod.rs b/src/operator/mod.rs index c57e301a..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 { diff --git a/src/templates/adaptive_stop_loss_trade_model.rs b/src/templates/adaptive_stop_loss_trade_model.rs index 9495bc51..bc6712b7 100644 --- a/src/templates/adaptive_stop_loss_trade_model.rs +++ b/src/templates/adaptive_stop_loss_trade_model.rs @@ -138,10 +138,10 @@ impl CompiledTransposeCalculationTemplate for AdaptiveStopLossTradeModel { } 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)).unwrap_or(Value::Empty))?; + 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)).unwrap_or(Value::Empty))?; - row.set_value(&generate_column_name("trade_id", transpose_value), trade_id.clone().map(|rs| Value::String(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))?; diff --git a/src/templates/bucket_data_template.rs b/src/templates/bucket_data_template.rs index e8d89226..5e0c8c25 100644 --- a/src/templates/bucket_data_template.rs +++ b/src/templates/bucket_data_template.rs @@ -104,7 +104,7 @@ impl CompiledTransposeCalculationTemplate for BucketData { 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()))?; + set_value_indirect(&mut output_values, &mut modified_columns, &bucket_range_index, *val, Value::String(bucket_range.clone().into()))?; } } } @@ -129,9 +129,9 @@ mod tests { let field_to_bucket = "price"; let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), ]; // Instantiate BucketData with dummy values let bucket_data = BucketData { @@ -141,9 +141,9 @@ mod tests { 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, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + 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) @@ -155,11 +155,11 @@ mod tests { // 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(), Value::String("1 to 1".to_string())); + 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(), Value::String("3 to 3".to_string())); + 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(), Value::String("2 to 2".to_string())); + 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 { @@ -176,9 +176,9 @@ mod tests { fn test_commit_row_with_equal_values() { // Create a mock row with identical values let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), ]; let field_to_bucket = "price"; // Instantiate BucketData with dummy values @@ -188,9 +188,9 @@ mod tests { }; 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, &Value::String("date1".to_owned())), Value::Float(2.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(2.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + 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); @@ -207,9 +207,9 @@ mod tests { // Create a mock row with varied values let field_to_bucket = "price"; let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), ]; // Instantiate BucketData with dummy values @@ -223,9 +223,9 @@ mod tests { // Call commit_row and check the results let mut row = BoxedOperatorRowTrait::new(row); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date1".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); - row.set_value(&generate_column_name(field_to_bucket, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); + 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(); diff --git a/src/templates/channel_monitoring_template.rs b/src/templates/channel_monitoring_template.rs index d05dee47..4d96b9bb 100644 --- a/src/templates/channel_monitoring_template.rs +++ b/src/templates/channel_monitoring_template.rs @@ -118,7 +118,7 @@ mod commit_row_tests { let holder = MockIndexHolder::new(); let mut row = MockRow::new(&holder); let cycle_epoch = 0; - let transpose_value = Value::String("02_01_2024".to_owned()); + 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)); @@ -147,8 +147,8 @@ mod commit_row_tests { 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".to_owned()); - let current_transpose_value = Value::String("02_01_2024".to_owned()); + 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 @@ -191,8 +191,8 @@ mod commit_row_tests { 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".to_owned()); - let current_transpose_value = Value::String("02_01_2024".to_owned()); + 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 diff --git a/src/templates/rolling_zscore.rs b/src/templates/rolling_zscore.rs index f5413e25..78a9f8e1 100644 --- a/src/templates/rolling_zscore.rs +++ b/src/templates/rolling_zscore.rs @@ -160,10 +160,10 @@ mod tests { // Create a mock row with initial values let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - Value::String("date4".to_string()), + Value::String("date1".into()), + Value::String("date2".into()), + Value::String("date3".into()), + Value::String("date4".into()), ]; let field_to_zscore = "price"; @@ -172,10 +172,10 @@ mod tests { 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".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".to_owned())), Value::Float(4.0)).unwrap(); + 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 @@ -224,17 +224,17 @@ mod tests { let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - Value::String("date4".to_string()), + 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".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Empty).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date3".to_owned())), Value::Float(2.0)).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date4".to_owned())), Value::Float(3.0)).unwrap(); + 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 @@ -267,14 +267,14 @@ mod tests { let field_to_zscore = "price"; let rolling_zscore = RollingZScore::new(vec![field_to_zscore], 3); let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), + 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".to_owned())), Value::Float(1.0)).unwrap(); - row.set_value(&generate_column_name(field_to_zscore, &Value::String("date2".to_owned())), Value::Float(3.0)).unwrap(); + 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 @@ -296,7 +296,7 @@ mod tests { // Create a mock row with initial values let ordered_transpose_values: Vec = (1..=15000) - .map(|i| Value::String(format!("date{}", i))) + .map(|i| Value::String(format!("date{}", i).into())) .collect(); @@ -308,7 +308,7 @@ mod tests { // 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))), + &generate_column_name(field_to_zscore, &Value::String(format!("date{}", i).into())), Value::Float((i % 10) as f64 + 1.0), ).unwrap(); } diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index ed6e0fc5..117bbb99 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -178,10 +178,10 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { } 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(Value::String))?; + 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, &initiation_date_index, i, initiation_date.clone().map(Value::String))?; - set_value_indirect_if_some(&mut all_values, &mut dirty_columns, &trade_id_index, i, trade_id.clone().map(Value::String))?; + 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))?; @@ -229,9 +229,9 @@ mod tests { fn test_commit_row_initial_trade() -> Result<(), Error> { // Set up initial values for a new trade let ordered_transpose_values = vec![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), ]; let model = SimpleTradeModel::new( @@ -248,7 +248,7 @@ mod tests { 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, Value::String("instrument1".to_owned()))?; + 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))?; @@ -263,8 +263,8 @@ mod tests { // 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)?, Value::String("date1".to_owned())); - assert_eq!(row.get_value_for_transpose_index("trade_id",0)?, Value::String("date1instrument1".to_owned())); + 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)); @@ -280,9 +280,9 @@ mod tests { 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![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), ]; let model = SimpleTradeModel::new( @@ -300,7 +300,7 @@ mod tests { let mut row = MockRow::new(&mock_index); // Set initial values to initiate the trade - row.set_value_for_transpose_index("instrument",0, Value::String("instrument1".to_owned()))?; + 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("initial_stop_loss",0, Value::Float(95.0))?; @@ -314,10 +314,10 @@ mod tests { model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 0).unwrap(); } - row.set_value_for_transpose_index("instrument",1, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; // Simulate a price drop to hit the stop loss row.set_value_for_transpose_index("price",1, Value::Float(94.0))?; - row.set_value_for_transpose_index("instrument",2, Value::String("instrument1".to_owned()))?; + 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))?; @@ -332,7 +332,7 @@ mod tests { 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(false)); assert_eq!(row.get_value_for_transpose_index("exit_price",1)?, Value::Float(94.0)); - assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(reason) if reason.contains("stop loss"))); + assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(ref reason) if reason.ref_into_owned().contains("stop loss"))); Ok(()) } @@ -341,9 +341,9 @@ mod tests { 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![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), ]; let model = SimpleTradeModel::new( @@ -361,7 +361,7 @@ mod tests { let mut row = MockRow::new(&mock_index); // Set initial values to initiate the trade - row.set_value_for_transpose_index("instrument",0, Value::String("instrument1".to_owned()))?; + 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))?; @@ -375,10 +375,10 @@ mod tests { } // Simulate a price rise to hit the take profit - row.set_value_for_transpose_index("instrument",1, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; row.set_value_for_transpose_index("price",1, Value::Float(111.0))?; // Simulate a price rise to hit the take profit - row.set_value_for_transpose_index("instrument",2, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; row.set_value_for_transpose_index("price",2, Value::Float(111.0))?; { @@ -391,7 +391,7 @@ mod tests { 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(false)); assert_eq!(row.get_value_for_transpose_index("exit_price",1)?, Value::Float(111.0)); - assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(reason) if reason.contains("take profit"))); + assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(ref reason) if reason.ref_into_owned().contains("take profit"))); Ok(()) } @@ -400,10 +400,10 @@ mod tests { 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![ - Value::String("date1".to_string()), - Value::String("date2".to_string()), - Value::String("date3".to_string()), - Value::String("date4".to_string()), + "date1".to_string().into(), + "date2".to_string().into(), + "date3".to_string().into(), + "date4".to_string().into(), ]; let model = SimpleTradeModel::new( @@ -421,7 +421,7 @@ mod tests { let mut row = MockRow::new(&mock_index); // Set initial values to initiate the trade - row.set_value_for_transpose_index("instrument",0, Value::String("instrument1".to_owned()))?; + 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("initial_stop_loss",0, Value::Float(95.0))?; @@ -435,7 +435,7 @@ mod tests { } // Simulate the passing of time beyond the holding period - row.set_value_for_transpose_index("instrument",1, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; row.set_value_for_transpose_index("price",1, Value::Float(102.0))?; { let mut operator_row = BoxedOperatorRowTrait::new(&mut row); @@ -443,10 +443,10 @@ mod tests { model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 1).unwrap(); } - row.set_value_for_transpose_index("instrument",2, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; row.set_value_for_transpose_index("price",2, Value::Float(103.0))?; - row.set_value_for_transpose_index("instrument",3, Value::String("instrument1".to_owned()))?; + row.set_value_for_transpose_index("instrument",3, "instrument1".to_owned().into())?; row.set_value_for_transpose_index("price",3, Value::Float(103.0))?; { let mut operator_row = BoxedOperatorRowTrait::new(&mut row); @@ -457,7 +457,7 @@ mod tests { // Verify that the trade has been closed due to holding period expiry assert_eq!(row.get_value_for_transpose_index("active_trade",2)?, Value::Boolean(true)); assert_eq!(row.get_value_for_transpose_index("exit_price",2)?, Value::Float(103.0)); - assert!(matches!(row.get_value_for_transpose_index("reason",2)?, Value::String(reason) if reason.contains("holding period"))); + assert!(matches!(row.get_value_for_transpose_index("reason",2)?, Value::String(ref reason) if reason.ref_into_owned().contains("holding period"))); assert_eq!(row.get_value_for_transpose_index("active_trade",3)?, Value::Boolean(false)); Ok(()) diff --git a/src/tree/mod.rs b/src/tree/mod.rs index bb7f4941..51357b8e 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), } @@ -629,7 +629,7 @@ pub fn tokens_to_operator_tree(tokens: Vec) -> EvalexprResult { Token::Float(float) => 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 92d1c7f2..331e9f82 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -28,7 +28,7 @@ pub const EMPTY_VALUE: () = (); #[repr(C)] pub enum Value { /// A string value. - String(String), + String(CowData), /// A float value. Float(FloatType), /// An integer value. @@ -42,6 +42,124 @@ pub enum Value { } + +/// A helper enum for handling owned data or references with raw pointers. +#[derive(Clone)] +pub enum CowData { + /// Owned data. + Owned(T), + /// Borrowed data as a raw pointer. + Borrowed(*const T), +} + +impl Hash for CowData +where + T: Hash, +{ + fn hash(&self, state: &mut H) { + match self { + CowData::Owned(ref data) => data.hash(state), + CowData::Borrowed(ptr) => unsafe { (**ptr).hash(state) }, + } + } +} + + + +impl fmt::Display for CowData +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CowData::Owned(ref data) => write!(f, "{}", data), + CowData::Borrowed(ptr) => unsafe { write!(f, "{:?}", &*ptr) }, + } + } +} + +impl CowData { + /// Access the data, either as a reference or as a mutable reference if it's owned. + pub unsafe fn as_ref(&self) -> &T { + match self { + CowData::Owned(ref data) => data, + CowData::Borrowed(ptr) => &**ptr, + } + } + + /// Convert to an owned version, cloning the data if it was borrowed. + pub fn into_owned(self) -> T + where + T: Clone, + { + match self { + CowData::Owned(data) => data, + CowData::Borrowed(ptr) => unsafe { (*ptr).clone() }, + } + } pub fn ref_into_owned(&self) -> T + where + T: Clone, + { + match self { + CowData::Owned(data) => data.clone(), + CowData::Borrowed(ptr) => unsafe { (**ptr).clone() }, + } + } +} + + +impl fmt::Debug for CowData +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CowData::Owned(ref data) => { + f.debug_tuple("Owned") + .field(data) + .finish() + }, + CowData::Borrowed(ptr) => { + // Attempt to safely print the borrowed data + unsafe { + f.debug_tuple("Borrowed") + .field(&*ptr) + .finish() + } + }, + } + } +} + +impl Serialize for CowData +where + T: Serialize + Clone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + CowData::Owned(ref data) => data.serialize(serializer), + CowData::Borrowed(ptr) => unsafe { (**ptr).serialize(serializer) }, + } + } +} + +impl<'de, T> Deserialize<'de> for CowData +where + T: Deserialize<'de> + Clone, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let owned_data = T::deserialize(deserializer)?; + Ok(CowData::Owned(owned_data)) + } +} + + impl Eq for Value {} // Implement Hash for Value @@ -72,6 +190,19 @@ impl Hash for Value { } +impl From for CowData { + fn from(s: String) -> Self { + CowData::Owned(s) + } +} + +impl From<&str> for CowData { + fn from(s: &str) -> Self { + CowData::Owned(s.to_string()) + } +} + + impl From<&Value> for Value { fn from(value: &Value) -> Self { match value { @@ -92,6 +223,25 @@ impl Ord for Value { } } + +impl PartialOrd for CowData +where + T: PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + unsafe { self.as_ref().partial_cmp(other.as_ref()) } + } +} + +impl PartialEq for CowData +where + T: PartialEq, +{ + 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) { @@ -187,14 +337,14 @@ 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.clone())), + Value::String(string) => Ok(Some(string.ref_into_owned())), Value::Empty => Ok(None), value => Err(EvalexprError::expected_string(value.clone())), } @@ -305,13 +455,13 @@ impl Value { impl From for Value { fn from(string: String) -> Self { - Value::String(string) + Value::String(CowData::Owned(string)) } } impl From<&str> for Value { fn from(string: &str) -> Self { - Value::String(string.to_string()) + Value::String(CowData::Owned(string.to_string())) } } @@ -442,6 +592,7 @@ use std::ops::Sub; use std::ops::Add; use std::ops::Neg; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::ordered_float::OrderedFloat; impl Neg for Value { @@ -489,10 +640,10 @@ impl Add for &Value { (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, b))), + (Value::String(a), Value::String(b)) => Ok(Value::String(format!("{}{}", a, b).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))), - (Value::Float(a), Value::String(b)) | (Value::String(b), Value::Float(a)) => Ok(Value::String(format!("{}{}", a, b))), + (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), } From 8b034c583ca19c071a1ce00e7205afce746709c3 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 10:52:56 +0100 Subject: [PATCH 214/265] Changed strings into CowData to avoid copying --- src/value/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 331e9f82..6ee239ba 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -52,6 +52,8 @@ pub enum CowData { Borrowed(*const T), } +unsafe impl Send for CowData where T: Send {} + impl Hash for CowData where T: Hash, From 99fb835cffbaa85f3ed1428bdddad1449cc57685 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 10:56:20 +0100 Subject: [PATCH 215/265] Changed strings into CowData to avoid copying --- src/value/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 6ee239ba..e750e2cb 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -53,6 +53,7 @@ pub enum CowData { } unsafe impl Send for CowData where T: Send {} +unsafe impl Sync for CowData where T: Sync {} impl Hash for CowData where From bbae589a131c5436bac45c01714b3ffb38d3fe11 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 10:59:06 +0100 Subject: [PATCH 216/265] export value --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8a756255..d40c7680 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -608,6 +608,6 @@ mod token; mod tree; mod custom_functions; mod templates; -mod value; +pub mod value; // Exports From c4c9e1c54640f244165be0d3edac9451ef6da6ee Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 11:14:55 +0100 Subject: [PATCH 217/265] export value --- src/value/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index e750e2cb..413f9f31 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -42,6 +42,21 @@ pub enum Value { } +// 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 + } +} + + /// A helper enum for handling owned data or references with raw pointers. #[derive(Clone)] From d88cdbb69df786b2f582f2569fb2a88bf933deb0 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 11:37:02 +0100 Subject: [PATCH 218/265] export value --- src/value/mod.rs | 114 ++++++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index 413f9f31..6d1110c2 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -28,7 +28,7 @@ pub const EMPTY_VALUE: () = (); #[repr(C)] pub enum Value { /// A string value. - String(CowData), + String(CowData), /// A float value. Float(FloatType), /// An integer value. @@ -43,15 +43,15 @@ pub enum Value { // Implement PartialEq for CowData and &str -impl PartialEq for CowData { +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 { +impl PartialEq for str { + fn eq(&self, other: &CowData) -> bool { other == self } } @@ -60,75 +60,84 @@ impl PartialEq> for str { /// A helper enum for handling owned data or references with raw pointers. #[derive(Clone)] -pub enum CowData { - /// Owned data. - Owned(T), - /// Borrowed data as a raw pointer. - Borrowed(*const T), +pub enum CowData{ + Owned(String), + Borrowed { + data: *const u8, + length: usize, + }, } +unsafe impl Send for CowData{} +unsafe impl Sync for CowData{} -unsafe impl Send for CowData where T: Send {} -unsafe impl Sync for CowData where T: Sync {} - -impl Hash for CowData -where - T: Hash, +impl Hash for CowData { fn hash(&self, state: &mut H) { match self { CowData::Owned(ref data) => data.hash(state), - CowData::Borrowed(ptr) => unsafe { (**ptr).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 -where - T: fmt::Display, +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(ptr) => unsafe { write!(f, "{:?}", &*ptr) }, + CowData::Borrowed{data,length} => unsafe { + let slice = std::slice::from_raw_parts(*data, *length); + write!(f, "{:?}", std::str::from_utf8_unchecked(slice)) + + }, } } } -impl CowData { +impl CowData { /// Access the data, either as a reference or as a mutable reference if it's owned. - pub unsafe fn as_ref(&self) -> &T { + pub unsafe fn as_ref(&self) -> &str { match self { CowData::Owned(ref data) => data, - CowData::Borrowed(ptr) => &**ptr, + CowData::Borrowed{data,length} => { + let slice = std::slice::from_raw_parts(*data, *length); + std::str::from_utf8_unchecked(slice) + } } } /// Convert to an owned version, cloning the data if it was borrowed. - pub fn into_owned(self) -> T - where - T: Clone, + pub fn into_owned(self) -> String { match self { CowData::Owned(data) => data, - CowData::Borrowed(ptr) => unsafe { (*ptr).clone() }, + 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) -> T - where - T: Clone, + } pub fn ref_into_owned(&self) -> String + { match self { CowData::Owned(data) => data.clone(), - CowData::Borrowed(ptr) => unsafe { (**ptr).clone() }, + 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 -where - T: fmt::Debug, +impl fmt::Debug for CowData + { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -137,11 +146,14 @@ where .field(data) .finish() }, - CowData::Borrowed(ptr) => { + 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(&*ptr) + .field(&value) .finish() } }, @@ -149,9 +161,7 @@ where } } -impl Serialize for CowData -where - T: Serialize + Clone, +impl Serialize for CowData { fn serialize(&self, serializer: S) -> Result where @@ -159,20 +169,23 @@ where { match self { CowData::Owned(ref data) => data.serialize(serializer), - CowData::Borrowed(ptr) => unsafe { (**ptr).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, T> Deserialize<'de> for CowData -where - T: Deserialize<'de> + Clone, +impl<'de> Deserialize<'de> for CowData { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { - let owned_data = T::deserialize(deserializer)?; + let owned_data = String::deserialize(deserializer)?; Ok(CowData::Owned(owned_data)) } } @@ -208,13 +221,13 @@ impl Hash for Value { } -impl From for CowData { +impl From for CowData { fn from(s: String) -> Self { CowData::Owned(s) } } -impl From<&str> for CowData { +impl From<&str> for CowData { fn from(s: &str) -> Self { CowData::Owned(s.to_string()) } @@ -242,18 +255,14 @@ impl Ord for Value { } -impl PartialOrd for CowData -where - T: PartialOrd, +impl PartialOrd for CowData { fn partial_cmp(&self, other: &Self) -> Option { unsafe { self.as_ref().partial_cmp(other.as_ref()) } } } -impl PartialEq for CowData -where - T: PartialEq, +impl PartialEq for CowData { fn eq(&self, other: &Self) -> bool { unsafe { self.as_ref() == other.as_ref() } @@ -612,6 +621,7 @@ use std::ops::Add; use std::ops::Neg; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::ordered_float::OrderedFloat; +use crate::value; impl Neg for Value { type Output = Result; From 10eacc747ed1474e688ed8c3be9c9247bfcd8173 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 14:05:46 +0100 Subject: [PATCH 219/265] export value --- src/value/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 6d1110c2..7179cd56 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -262,6 +262,8 @@ impl PartialOrd for CowData } } +impl Eq for CowData {} + impl PartialEq for CowData { fn eq(&self, other: &Self) -> bool { From cbc7b781adf1cdc05d54f1148e917623d150fcc7 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 14:14:45 +0100 Subject: [PATCH 220/265] export value --- Cargo.toml | 1 + src/value/mod.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 32d81d56..0e1cd064 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ 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" #csv = "1.1" #anyhow = "1.0.86" thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_object", branch = "main" } diff --git a/src/value/mod.rs b/src/value/mod.rs index 7179cd56..545d0f89 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -67,6 +67,15 @@ pub enum CowData{ length: usize, }, } + +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, + } + } +} unsafe impl Send for CowData{} unsafe impl Sync for CowData{} @@ -621,6 +630,7 @@ use std::ops::Sub; use std::ops::Add; use std::ops::Neg; +use deepsize::{Context, DeepSizeOf}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::ordered_float::OrderedFloat; use crate::value; From a352716a636dc9c5e70c0d5237bf1254ca64218d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 14:20:49 +0100 Subject: [PATCH 221/265] export value --- src/value/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 545d0f89..52979038 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -76,6 +76,14 @@ impl DeepSizeOf for CowData{ } } } + +impl FromStr for CowData{ + type Err = EvalexprError; + + fn from_str(s: &str) -> Result { + Ok(CowData::Owned(s.to_string())) + } +} unsafe impl Send for CowData{} unsafe impl Sync for CowData{} @@ -630,6 +638,7 @@ use std::ops::Sub; use std::ops::Add; use std::ops::Neg; +use std::str::FromStr; use deepsize::{Context, DeepSizeOf}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::ordered_float::OrderedFloat; From e4822abb9a033acecdc9a450f6b49fd680315249 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 14:26:06 +0100 Subject: [PATCH 222/265] export value --- src/value/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 52979038..00c923ce 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -272,6 +272,12 @@ impl Ord for Value { } +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 { From ad40dd449f7ebef3c685b095599adb55de0a210c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 14:30:22 +0100 Subject: [PATCH 223/265] export value --- src/value/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 00c923ce..f807d1bd 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -56,6 +56,13 @@ impl PartialEq for str { } } +#[cfg(feature = "serde_json_support")] +impl From for serde_json::Value { + fn from(value: CowData) -> Self { + Self::String(value.into_owned()) + } +} + /// A helper enum for handling owned data or references with raw pointers. From 4e5678ad25fcac79cde71e85ca708c056034cc0b Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 14:43:01 +0100 Subject: [PATCH 224/265] export value --- src/value/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index f807d1bd..fbc4194c 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -75,6 +75,22 @@ pub enum CowData{ }, } + +impl From for String { + fn from(cow_data: CowData) -> String { + match cow_data { + CowData::Owned(s) => 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 { From 80b163c566ca8a9513381f849c3cf90b4e92ff48 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 15:11:38 +0100 Subject: [PATCH 225/265] export value --- src/value/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index fbc4194c..d6323ed2 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -151,6 +151,13 @@ impl CowData { } } } + + 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 From 2010304e1525442bfe7da8444b15ca8808e4e811 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 25 Aug 2024 16:09:02 +0100 Subject: [PATCH 226/265] export value --- src/templates/simple_trade_model.rs | 47 +++++++++++------------------ src/templates/utils.rs | 11 +++++-- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index 117bbb99..6b903598 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -3,7 +3,7 @@ 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, set_value_indirect, set_value_indirect_if_some}; +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, @@ -84,24 +84,10 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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 = instrument_index.clone(); - all_cols.extend(signal_index.clone()); + let mut all_cols = signal_index.clone(); all_cols.extend(price_index.clone()); - all_cols.extend(conviction_index.clone()); - all_cols.extend(initial_stop_loss_index.clone()); - all_cols.extend(initial_take_profit_index.clone()); - all_cols.extend(active_trade_index.clone()); - all_cols.extend(trade_id_index.clone()); - all_cols.extend(initiation_price_index.clone()); - all_cols.extend(trade_age_index.clone()); - all_cols.extend(initiation_date_index.clone()); - all_cols.extend(current_stop_loss_index.clone()); - all_cols.extend(current_take_profit_index.clone()); - all_cols.extend(reason_index.clone()); - all_cols.extend(delta_index.clone()); - all_cols.extend(exit_price_index.clone()); let mut all_values = row.get_values_for_columns(all_cols)?; @@ -110,15 +96,15 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { if cycle_epoch > 0 { let transpose_index_before_epoch = cycle_epoch - 1; - active_trade = get_value_indirect(&all_values, &active_trade_index,transpose_index_before_epoch)?.as_boolean_or_none()?; - trade_id = get_value_indirect(&all_values, &trade_id_index,transpose_index_before_epoch)?.as_string_or_none()?; - initiation_price = get_value_indirect(&all_values, &initiation_price_index,transpose_index_before_epoch)?.as_float_or_none()?; - trade_age = get_value_indirect(&all_values, &trade_age_index,transpose_index_before_epoch)?.as_int_or_none()?; - initiation_date = get_value_indirect(&all_values, &initiation_date_index,transpose_index_before_epoch)?.as_string_or_none()?; - stop_loss = get_value_indirect(&all_values, ¤t_stop_loss_index, transpose_index_before_epoch)?.as_float_or_none()?; - take_profit = get_value_indirect(&all_values, ¤t_take_profit_index, transpose_index_before_epoch)?.as_float_or_none()?; - conviction = get_value_indirect(&all_values, &conviction_index, transpose_index_before_epoch)?.as_float_or_none()?; - reason = get_value_indirect(&all_values, &reason_index,transpose_index_before_epoch)?.as_string_or_none()?; + 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()?; + 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()?; } if cycle_epoch > 1 { @@ -126,12 +112,15 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { } 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)) = ( - get_value_indirect(&all_values, &instrument_index,i)?.as_string_or_none()?, + instrument_name.as_ref(), get_value_indirect(&all_values, &price_index,i)?.as_float_or_none()? ) { @@ -169,8 +158,8 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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(&all_values, &initial_stop_loss_index,i)?.as_float_or_none()?; - take_profit = get_value_indirect(&all_values, &initial_take_profit_index,i)?.as_float_or_none()?; + 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); } diff --git a/src/templates/utils.rs b/src/templates/utils.rs index 4a1362ce..dfe010f4 100644 --- a/src/templates/utils.rs +++ b/src/templates/utils.rs @@ -1,10 +1,17 @@ -use crate::{Error, Value}; +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 set_value_indirect<'a>(values: &'a mut Vec,dirty_columns: &'a mut Vec, column_index: &Vec, idx: usize, value: Value) -> Result<(), Error> { +} + +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; From b1142a5831a09aa8d6a1ca09da628fb93e9abda3 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 26 Aug 2024 00:38:29 +0100 Subject: [PATCH 227/265] export value --- src/custom_functions/mod.rs | 4 ++ src/custom_functions/pow.rs | 115 +++++++++++++++++++++++++++++++++++ src/custom_functions/sqrt.rs | 95 +++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/custom_functions/pow.rs create mode 100644 src/custom_functions/sqrt.rs diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 33cb4604..88f0d02e 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -6,6 +6,8 @@ pub mod back; pub mod simple_cumilative_returns; mod rolling_stdev; mod rolling_min; +pub mod pow; +mod sqrt; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -13,6 +15,8 @@ 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 simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; pub use simple_cumilative_returns::simple_cumulative_returns; diff --git a/src/custom_functions/pow.rs b/src/custom_functions/pow.rs new file mode 100644 index 00000000..b99ce553 --- /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::InvalidArgumentType), + } +} + +#[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/sqrt.rs b/src/custom_functions/sqrt.rs new file mode 100644 index 00000000..af77bad1 --- /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::InvalidArgumentType) + } else { + Ok(Value::Float(fl.sqrt())) + } + } + Value::Int(nn) => { + if nn < 0 { + Err(Error::InvalidArgumentType) + } else { + Ok(Value::Float((nn as f64).sqrt())) // Convert to float for square root + } + } + Value::Empty => Ok(Value::Empty), + _ => Err(Error::InvalidArgumentType), + } +} + +#[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()); + } +} + From c6bda47cbe81f16520035981e4f487c2b89190fc Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 26 Aug 2024 01:02:33 +0100 Subject: [PATCH 228/265] export value --- src/custom_functions/pow.rs | 2 +- src/custom_functions/sqrt.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/custom_functions/pow.rs b/src/custom_functions/pow.rs index b99ce553..0fc7a076 100644 --- a/src/custom_functions/pow.rs +++ b/src/custom_functions/pow.rs @@ -26,7 +26,7 @@ where (Value::Int(bi), Value::Float(ef)) => { Ok(Value::Float((bi as f64).powf(ef))) } - _ => Err(Error::InvalidArgumentType), + _ => Err(Error::CustomError("Invalidat artument type passed to pow function".to_string())), } } diff --git a/src/custom_functions/sqrt.rs b/src/custom_functions/sqrt.rs index af77bad1..937612ae 100644 --- a/src/custom_functions/sqrt.rs +++ b/src/custom_functions/sqrt.rs @@ -11,20 +11,20 @@ where match value.try_into().map_err(|err| CustomError(format!("{err:?}")))? { Value::Float(fl) => { if fl < 0.0 { - Err(Error::InvalidArgumentType) + 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::InvalidArgumentType) + 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::InvalidArgumentType), + _ => Err(Error::CustomError("Invalid argument type passed to sqrt function".to_string())), } } From 5b6d110216fe2d25f8fb844dfe53e28f5e178260 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 26 Aug 2024 02:52:35 +0100 Subject: [PATCH 229/265] export value --- src/custom_functions/expression_functions.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index c64a0c93..0341c459 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -164,6 +164,17 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { Ok(Value::Boolean(false)) } +pub fn ends_with(message: &Value, suffix: &Value) -> Result { + if let (Value::String(message), Value::String(prefix)) = (message, suffix) { + 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 ternary,TL: Into,TR: Into>(condition: TC, true_value: TL, false_value: TR) -> Result { if let Value::Boolean(cond) = condition.into() { if cond { From 5f7d5c09120ad03c5c77985b265efcc5360fa6d9 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 26 Aug 2024 02:54:48 +0100 Subject: [PATCH 230/265] export value --- src/custom_functions/expression_functions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 0341c459..9484e0cf 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -153,8 +153,8 @@ pub fn substring,TR: Into, TC: Into>(message: TL, } -pub fn starts_with(message: &Value, prefix: &Value) -> Result { - if let (Value::String(message), Value::String(prefix)) = (message, prefix) { +pub fn starts_with,TR: Into, TC: Into>(message: TL, prefix: TR) -> Result { + if let (Value::String(message), Value::String(prefix)) = (message.into(), prefix.into()) { let message = message.ref_into_owned(); let prefix = prefix.ref_into_owned(); if message.starts_with(&prefix) { @@ -164,8 +164,8 @@ pub fn starts_with(message: &Value, prefix: &Value) -> Result { Ok(Value::Boolean(false)) } -pub fn ends_with(message: &Value, suffix: &Value) -> Result { - if let (Value::String(message), Value::String(prefix)) = (message, suffix) { +pub fn ends_with,TR: Into, TC: Into>(message: TL, suffix: TR) -> Result { + if let (Value::String(message), Value::String(prefix)) = (message.into(), suffix.into()) { let message = message.ref_into_owned(); let prefix = prefix.ref_into_owned(); if message.ends_with(&prefix) { From 5c654dc7d8bb34e9651549a85cd94be7772c91ef Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 26 Aug 2024 02:57:23 +0100 Subject: [PATCH 231/265] export value --- src/custom_functions/expression_functions.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 9484e0cf..ce290e62 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -136,6 +136,16 @@ impl TryFrom> for Value { } } +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: Into, TC: Into>(message: TL, start: TR, len: TC) -> Result { if let Value::String(message) = message.into() { From c499660b4b4eea6af8be557f72114b0b0cd11ad9 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 26 Aug 2024 03:07:34 +0100 Subject: [PATCH 232/265] export value --- src/custom_functions/expression_functions.rs | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index ce290e62..fa45c91d 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -147,11 +147,13 @@ impl TryFrom for bool { } } -pub fn substring,TR: Into, TC: Into>(message: TL, start: TR, len: TC) -> Result { - if let Value::String(message) = message.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 = Into::::into(start).as_int()? as usize; - let len_int = Into::::into(len).as_int()? as usize; + 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 }; @@ -163,8 +165,10 @@ pub fn substring,TR: Into, TC: Into>(message: TL, } -pub fn starts_with,TR: Into, TC: Into>(message: TL, prefix: TR) -> Result { - if let (Value::String(message), Value::String(prefix)) = (message.into(), prefix.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) { @@ -174,8 +178,10 @@ pub fn starts_with,TR: Into, TC: Into>(message: TL Ok(Value::Boolean(false)) } -pub fn ends_with,TR: Into, TC: Into>(message: TL, suffix: TR) -> Result { - if let (Value::String(message), Value::String(prefix)) = (message.into(), suffix.into()) { +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) { From ebd29dcc303c14e0017907e1f39af5d70f986869 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Tue, 27 Aug 2024 11:21:10 +0100 Subject: [PATCH 233/265] Model now respects days since last trade --- src/templates/simple_trade_model.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index 6b903598..bb800ad1 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -45,6 +45,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { ("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() @@ -65,11 +66,13 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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())?; @@ -104,7 +107,8 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { stop_loss = get_value_indirect_from_row(row,¤t_stop_loss_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()?; + 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 { @@ -154,7 +158,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { } } else { if current_signal { - if !prev_trade_signal.unwrap_or_default() { + 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); @@ -162,7 +166,10 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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); } } @@ -176,12 +183,14 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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); reason = None; delta = None; exit_price = None; if loop_trade_closed { active_trade = Some(false); + days_since_last_trade = Some(0); initiation_price = None; initiation_date = None; trade_id = None; @@ -464,6 +473,7 @@ mod tests { &model.initial_take_profit_field_name, "active_trade", "initiation_price", + "days_since_last_trade", "exit_price", "initiation_date", "trade_id", From 22c78aa1ddf610a77d1528756e2cfa390a51a959 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 28 Aug 2024 01:03:24 +0100 Subject: [PATCH 234/265] Model now respects days since last trade --- Cargo.toml | 2 +- src/custom_functions/bucket_functions.rs | 130 ++++++++++++ src/custom_functions/mod.rs | 1 + src/templates/bucket_data_discrete.rs | 252 +++++++++++++++++++++++ src/templates/mod.rs | 2 + 5 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 src/custom_functions/bucket_functions.rs create mode 100644 src/templates/bucket_data_discrete.rs diff --git a/Cargo.toml b/Cargo.toml index 0e1cd064..472c7602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ deepsize = "0.2.0" #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" [features] diff --git a/src/custom_functions/bucket_functions.rs b/src/custom_functions/bucket_functions.rs new file mode 100644 index 00000000..5689062e --- /dev/null +++ b/src/custom_functions/bucket_functions.rs @@ -0,0 +1,130 @@ +use crate::Value; +use crate::Error; +use crate::IntType; +use crate::Error::CustomError; +macro_rules! generate_bucket_functions { + ($($name:ident($($stop:ident),+)),+) => { + $( + pub fn $name(value_to_bucket: Value, $($stop: Value),+) -> Result { + let stops = vec![$($stop),+]; + + // Ensure stops are in ascending order + for i in 0..stops.len() - 1 { + if stops[i] > stops[i + 1] { + return Err(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)) + } + + paste::paste! { + pub fn [<$name _desc>](value_to_bucket: Value, $($stop: Value),+) -> Result { + let stops = vec![$($stop),+]; + + // Ensure stops are in ascending order + for i in 0..stops.len() - 1 { + if stops[i] > stops[i + 1] { + return Err(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_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/mod.rs b/src/custom_functions/mod.rs index 88f0d02e..17cfb582 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -8,6 +8,7 @@ mod rolling_stdev; mod rolling_min; pub mod pow; mod sqrt; +mod bucket_functions; use std::fmt::Display; // Re-export the functions to the root of the crate 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/mod.rs b/src/templates/mod.rs index 468d931d..c16930b2 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -7,6 +7,7 @@ pub mod simple_trade_model; pub(crate) mod test_utils; pub mod rolling_zscore; mod utils; +pub mod bucket_data_discrete; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -14,6 +15,7 @@ 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 rolling_zscore::*; pub use compiled_transpose_calculation_template::*; use crate::Value; From 6e24a1cfe0b7311ada946309cc951985b2ef6f2d Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 28 Aug 2024 01:49:00 +0100 Subject: [PATCH 235/265] Exported bucket functions --- src/custom_functions/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 17cfb582..4f7fbe78 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -8,7 +8,7 @@ mod rolling_stdev; mod rolling_min; pub mod pow; mod sqrt; -mod bucket_functions; +pub mod bucket_functions; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -18,6 +18,7 @@ pub use expression_functions::*; pub use back::*; pub use pow::*; pub use sqrt::*; +pub use bucket_functions::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; pub use simple_cumilative_returns::simple_cumulative_returns; From 1e291b3859fb35256ec574b1303c0bef5f1c2522 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 28 Aug 2024 01:58:31 +0100 Subject: [PATCH 236/265] Exported bucket functions --- src/custom_functions/bucket_functions.rs | 28 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/custom_functions/bucket_functions.rs b/src/custom_functions/bucket_functions.rs index 5689062e..8afe049d 100644 --- a/src/custom_functions/bucket_functions.rs +++ b/src/custom_functions/bucket_functions.rs @@ -5,13 +5,20 @@ use crate::Error::CustomError; macro_rules! generate_bucket_functions { ($($name:ident($($stop:ident),+)),+) => { $( - pub fn $name(value_to_bucket: Value, $($stop: Value),+) -> Result { - let stops = vec![$($stop),+]; + pub fn $name(value_to_bucket: T, $($stop: E),+) -> Result + where + T: std::convert::TryInto, + E: std::convert::TryInto, + >::Error: core::fmt::Debug, + >::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(CustomError("Stops must be in ascending order".to_string())); + return Err(Error::CustomError("Stops must be in ascending order".to_string())); } } @@ -25,14 +32,21 @@ macro_rules! generate_bucket_functions { Ok(Value::Int(stops.len() as IntType)) } - paste::paste! { - pub fn [<$name _desc>](value_to_bucket: Value, $($stop: Value),+) -> Result { - let stops = vec![$($stop),+]; + paste::paste! { + pub fn [<$name _desc>] (value_to_bucket: T, $($stop: E),+) -> Result + where + T: std::convert::TryInto, + E: std::convert::TryInto, + >::Error: core::fmt::Debug, + >::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(CustomError("Stops must be in ascending order".to_string())); + return Err(Error::CustomError("Stops must be in ascending order".to_string())); } } From 63a32c9f79cb2d15a0ae8cb7e34b2b0ffe5681ae Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 28 Aug 2024 09:52:36 +0100 Subject: [PATCH 237/265] new generic type for each bucket type param --- src/custom_functions/bucket_functions.rs | 85 ++++++++++++++++-------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/src/custom_functions/bucket_functions.rs b/src/custom_functions/bucket_functions.rs index 8afe049d..614efa8a 100644 --- a/src/custom_functions/bucket_functions.rs +++ b/src/custom_functions/bucket_functions.rs @@ -5,40 +5,38 @@ use crate::Error::CustomError; macro_rules! generate_bucket_functions { ($($name:ident($($stop:ident),+)),+) => { $( - pub fn $name(value_to_bucket: T, $($stop: E),+) -> Result - where - T: std::convert::TryInto, - E: std::convert::TryInto, - >::Error: core::fmt::Debug, - >::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())); + 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)); + // 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)) - } + Ok(Value::Int(stops.len() as IntType)) + } - paste::paste! { - pub fn [<$name _desc>] (value_to_bucket: T, $($stop: E),+) -> Result + pub fn [<$name _desc>] ]),+>(value_to_bucket: T, $($stop: []),+) -> Result where T: std::convert::TryInto, - E: std::convert::TryInto, >::Error: core::fmt::Debug, - >::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()))?),+]; @@ -130,6 +128,41 @@ mod tests { 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); From 7adf63ed8cc17861ef9f98cdbf85f55922617157 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 29 Aug 2024 03:44:33 +0100 Subject: [PATCH 238/265] Added correlation analysis --- .../correnlation_analysis_template.rs | 53 +++++++++++++++++++ src/templates/mod.rs | 2 + 2 files changed, 55 insertions(+) create mode 100644 src/templates/correnlation_analysis_template.rs diff --git a/src/templates/correnlation_analysis_template.rs b/src/templates/correnlation_analysis_template.rs new file mode 100644 index 00000000..e22039f0 --- /dev/null +++ b/src/templates/correnlation_analysis_template.rs @@ -0,0 +1,53 @@ +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}; + + +struct CorrelationAnalysis{ + independent_variables: Vec, + dependent_variable: String, +} + +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 { + vec![ + + ("coefficient", ValueType::Float), + ("pvalue", ValueType::Float), + ("rsquared", ValueType::Float), + ("advjustedrsquared", ValueType::Float) + ].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![]; + row.set_values_for_columns(modified_columns, output_values)?; + Ok(()) + } +} diff --git a/src/templates/mod.rs b/src/templates/mod.rs index c16930b2..42cfb4ce 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod test_utils; pub mod rolling_zscore; mod utils; pub mod bucket_data_discrete; +pub mod correnlation_analysis_template; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -16,6 +17,7 @@ pub use adaptive_stop_loss_trade_model::*; pub use simple_trade_model::*; pub use bucket_data_template::*; pub use bucket_data_discrete::*; +pub use correnlation_analysis_template::*; pub use rolling_zscore::*; pub use compiled_transpose_calculation_template::*; use crate::Value; From 71a9214125ee4d8a69dcb52dfac9a7dbc95fd4c3 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 29 Aug 2024 03:45:53 +0100 Subject: [PATCH 239/265] Added correlation analysis --- src/templates/correnlation_analysis_template.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/correnlation_analysis_template.rs b/src/templates/correnlation_analysis_template.rs index e22039f0..1a436e92 100644 --- a/src/templates/correnlation_analysis_template.rs +++ b/src/templates/correnlation_analysis_template.rs @@ -8,7 +8,7 @@ use crate::templates::test_utils::MockIndexHolder; use crate::templates::utils::{get_value_indirect, set_value_indirect}; -struct CorrelationAnalysis{ +pub struct CorrelationAnalysis{ independent_variables: Vec, dependent_variable: String, } From 688b1a663d91b38c115e0fe6451aae6f12386a11 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 29 Aug 2024 03:50:38 +0100 Subject: [PATCH 240/265] Added correlation analysis --- .../correnlation_analysis_template.rs | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/templates/correnlation_analysis_template.rs b/src/templates/correnlation_analysis_template.rs index 1a436e92..e00db128 100644 --- a/src/templates/correnlation_analysis_template.rs +++ b/src/templates/correnlation_analysis_template.rs @@ -13,6 +13,19 @@ pub struct CorrelationAnalysis{ 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 { @@ -23,13 +36,14 @@ impl CorrelationAnalysis { } impl CompiledTransposeCalculationTemplate for CorrelationAnalysis { fn schema(&self) -> HashMap { - vec![ - - ("coefficient", ValueType::Float), - ("pvalue", ValueType::Float), - ("rsquared", ValueType::Float), - ("advjustedrsquared", ValueType::Float) - ].iter().map(|(nm, val)|(nm.to_string(),*val)).collect() + 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(); From 54f4e6de7ea876b4a3262d4ac969c5a501a7b936 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 29 Aug 2024 17:33:13 +0100 Subject: [PATCH 241/265] Added correlation analysis --- Cargo.toml | 3 + .../correlation_analysis_template.rs | 186 ++++++++++++++++++ .../correnlation_analysis_template.rs | 67 ------- src/templates/mod.rs | 4 +- src/templates/simple_trade_model.rs | 4 +- 5 files changed, 193 insertions(+), 71 deletions(-) create mode 100644 src/templates/correlation_analysis_template.rs delete mode 100644 src/templates/correnlation_analysis_template.rs diff --git a/Cargo.toml b/Cargo.toml index 472c7602..2b6b0f0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,9 @@ 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" #csv = "1.1" #anyhow = "1.0.86" thin_trait_object = { git = "https://github.com/gbemiga-viewserver/thin_trait_object", branch = "main" } 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/correnlation_analysis_template.rs b/src/templates/correnlation_analysis_template.rs deleted file mode 100644 index e00db128..00000000 --- a/src/templates/correnlation_analysis_template.rs +++ /dev/null @@ -1,67 +0,0 @@ -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}; - - -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![]; - row.set_values_for_columns(modified_columns, output_values)?; - Ok(()) - } -} diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 42cfb4ce..2316b208 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -8,7 +8,7 @@ pub(crate) mod test_utils; pub mod rolling_zscore; mod utils; pub mod bucket_data_discrete; -pub mod correnlation_analysis_template; +pub mod correlation_analysis_template; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -17,7 +17,7 @@ pub use adaptive_stop_loss_trade_model::*; pub use simple_trade_model::*; pub use bucket_data_template::*; pub use bucket_data_discrete::*; -pub use correnlation_analysis_template::*; +pub use correlation_analysis_template::*; pub use rolling_zscore::*; pub use compiled_transpose_calculation_template::*; use crate::Value; diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index bb800ad1..71c0485f 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -142,13 +142,13 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { exit_price = Some(current_close_value); let delta_value = (current_close_value - loop_initiation_price) * loop_conviction; delta = Some(delta_value); - reason = Some(format!("{} {} Closing trade. Current price ({}) has fallen to or below stop loss {} from entry price ({}).", get_delta_label(delta_value), delta_value, current_close_value, loop_stop_loss.as_ref().unwrap(), loop_initiation_price)); + reason = Some(format!("{} {} 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!("{} {} Closing trade. Current price ({}) has reached or exceeded take profit level {} from entry price ({}).",get_delta_label(delta_value),delta.unwrap(), current_close_value,loop_take_profit.as_ref().unwrap(), loop_initiation_price)); + reason = Some(format!("{} {} 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); From 25827fcbf52bac43dc55edca0cb90731f0f0cc19 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 30 Aug 2024 12:27:09 +0100 Subject: [PATCH 242/265] Added correlation analysis --- src/templates/simple_trade_model.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index 71c0485f..ab0ee10b 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -142,19 +142,19 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { exit_price = Some(current_close_value); let delta_value = (current_close_value - loop_initiation_price) * loop_conviction; delta = Some(delta_value); - reason = Some(format!("{} {} 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)); + 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!("{} {} 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)); + 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!("{} {} Closing trade. Trade has reached holding period of ({}).",get_delta_label(delta_value),delta.unwrap(),trade_age.as_ref().unwrap())); + 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 { From 7fa487835e6ac88d4c854a4dfda45f8417c8e0ea Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Fri, 30 Aug 2024 12:29:47 +0100 Subject: [PATCH 243/265] dot infront of lowest bucket to make it appear at top of list when sorted --- src/custom_functions/bucket_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/bucket_functions.rs b/src/custom_functions/bucket_functions.rs index 614efa8a..d6b5f716 100644 --- a/src/custom_functions/bucket_functions.rs +++ b/src/custom_functions/bucket_functions.rs @@ -52,7 +52,7 @@ macro_rules! generate_bucket_functions { for (i, stop) in stops.iter().enumerate() { if value_to_bucket <= *stop { if i == 0 { - return Ok(Value::String(format!("<= {}", stop).into())); + return Ok(Value::String(format!(".<= {}", stop).into())); } else { return Ok(Value::String(format!("{} - {}", stops[i - 1], stop).into())); } From d061a43025780ea2ac83ebe5a79a4e148786d535 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 31 Aug 2024 03:24:47 +0100 Subject: [PATCH 244/265] dot infront of lowest bucket to make it appear at top of list when sorted --- src/templates/simple_trade_model.rs | 132 ++++++++++++++++++---------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/src/templates/simple_trade_model.rs b/src/templates/simple_trade_model.rs index ab0ee10b..f6177b99 100644 --- a/src/templates/simple_trade_model.rs +++ b/src/templates/simple_trade_model.rs @@ -42,6 +42,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { ("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), @@ -55,8 +56,10 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { } 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; @@ -78,6 +81,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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())?; @@ -105,6 +109,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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()?; @@ -113,6 +118,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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() { @@ -136,6 +142,8 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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; @@ -176,6 +184,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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))?; @@ -185,6 +194,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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; @@ -192,6 +202,7 @@ impl CompiledTransposeCalculationTemplate for SimpleTradeModel { 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; @@ -278,6 +289,7 @@ mod tests { 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(), @@ -297,40 +309,47 @@ mod tests { let mock_index = create_mock_index(&ordered_transpose_values, &model); let mut row = MockRow::new(&mock_index); - // Set initial values to initiate the trade + // 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(true))?; - row.set_value_for_transpose_index("price",0, Value::Float(100.0))?; - row.set_value_for_transpose_index("initial_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))?; + 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, 0).unwrap(); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 1).unwrap(); } - row.set_value_for_transpose_index("instrument",1, "instrument1".to_owned().into())?; - // Simulate a price drop to hit the stop loss - row.set_value_for_transpose_index("price",1, Value::Float(94.0))?; 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, 1).unwrap(); + 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",0)?, Value::Boolean(true)); 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(false)); - assert_eq!(row.get_value_for_transpose_index("exit_price",1)?, Value::Float(94.0)); - assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(ref reason) if reason.ref_into_owned().contains("stop loss"))); + 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(()) } @@ -339,6 +358,7 @@ mod tests { 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(), @@ -358,38 +378,47 @@ mod tests { let mock_index = create_mock_index(&ordered_transpose_values, &model); let mut row = MockRow::new(&mock_index); - // Set initial values to initiate the trade + + // 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(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))?; + 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, 0).unwrap(); + 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",1, "instrument1".to_owned().into())?; - row.set_value_for_transpose_index("price",1, 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",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, 1).unwrap(); + 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",1)?, Value::Boolean(true)); - assert_eq!(row.get_value_for_transpose_index("active_trade",2)?, Value::Boolean(false)); - assert_eq!(row.get_value_for_transpose_index("exit_price",1)?, Value::Float(111.0)); - assert!(matches!(row.get_value_for_transpose_index("reason",1)?, Value::String(ref reason) if reason.ref_into_owned().contains("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(()) } @@ -398,6 +427,7 @@ mod tests { 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(), @@ -418,45 +448,50 @@ mod tests { let mock_index = create_mock_index(&ordered_transpose_values, &model); let mut row = MockRow::new(&mock_index); - // Set initial values to initiate the trade + // 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(true))?; - row.set_value_for_transpose_index("price",0, Value::Float(100.0))?; - row.set_value_for_transpose_index("initial_stop_loss",0, Value::Float(95.0))?; - row.set_value_for_transpose_index("initial_take_profit",0, Value::Float(110.0))?; - row.set_value_for_transpose_index("conviction",0, Value::Float(1.0))?; + 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, 0).unwrap(); + 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",1, "instrument1".to_owned().into())?; - row.set_value_for_transpose_index("price",1, Value::Float(102.0))?; + 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, 1).unwrap(); + model.commit_row(&mut operator_row, &mock_index_holder, &ordered_transpose_values, 2).unwrap(); } - row.set_value_for_transpose_index("instrument",2, "instrument1".to_owned().into())?; - row.set_value_for_transpose_index("price",2, Value::Float(103.0))?; - 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, 2).unwrap(); + 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",2)?, Value::Boolean(true)); - assert_eq!(row.get_value_for_transpose_index("exit_price",2)?, Value::Float(103.0)); - assert!(matches!(row.get_value_for_transpose_index("reason",2)?, Value::String(ref reason) if reason.ref_into_owned().contains("holding period"))); - assert_eq!(row.get_value_for_transpose_index("active_trade",3)?, Value::Boolean(false)); + 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(()) } @@ -473,6 +508,7 @@ mod tests { &model.initial_take_profit_field_name, "active_trade", "initiation_price", + "trade_daily_return", "days_since_last_trade", "exit_price", "initiation_date", From 1190963209ee54b86a55f38e6321766bff64b0fb Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 1 Sep 2024 14:28:43 +0100 Subject: [PATCH 245/265] Added some report functions --- src/custom_functions/mod.rs | 2 + src/custom_functions/report_uri_functions.rs | 87 ++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/custom_functions/report_uri_functions.rs diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 4f7fbe78..c8d0b81f 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -9,6 +9,7 @@ mod rolling_min; pub mod pow; mod sqrt; pub mod bucket_functions; +pub mod report_uri_functions; use std::fmt::Display; // Re-export the functions to the root of the crate @@ -24,6 +25,7 @@ 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 { diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs new file mode 100644 index 00000000..aa9f4740 --- /dev/null +++ b/src/custom_functions/report_uri_functions.rs @@ -0,0 +1,87 @@ +use std::collections::HashMap; +use std::convert::TryInto; + +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>(uri: TL, output_property_name: TR) -> Result +where >::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 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": report_key, + }]); + Ok(Value::String(serde_json::to_string(&result).map_err(|err| CustomError(format!("{err}")))?.into())) +} +pub fn extract_report_key_from_operator_uri,TR: TryInto>(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(operator_uri.into())) +} + +fn extract_parameters(input_string: &str) -> HashMap { + // 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, value); + } + } + + return params_dict; + } + + // Return an empty HashMap if 'params' is not found + 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 From 9cc5bff21c5d0bdc927b03c8353e0416272d95d5 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 1 Sep 2024 14:43:47 +0100 Subject: [PATCH 246/265] Added some report functions --- src/custom_functions/report_uri_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs index aa9f4740..1e969c70 100644 --- a/src/custom_functions/report_uri_functions.rs +++ b/src/custom_functions/report_uri_functions.rs @@ -25,7 +25,7 @@ where >::Error: std::fmt::Display,,TR: TryInto>(uri: TL) -> Result +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()?; From f59e07ce7834e8d4793f10b9c1582894cd19efa5 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 1 Sep 2024 14:45:20 +0100 Subject: [PATCH 247/265] Added some report functions --- src/custom_functions/report_uri_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs index 1e969c70..2f6b31a3 100644 --- a/src/custom_functions/report_uri_functions.rs +++ b/src/custom_functions/report_uri_functions.rs @@ -29,7 +29,7 @@ pub fn extract_report_key_from_operator_uri>(uri: TL) -> Res where >::Error: std::fmt::Display { let operator_uri = uri.try_into().map_err(|err| CustomError(format!("{}",err)))?.as_string()?; - Ok(Value::String(operator_uri.into())) + Ok(Value::String(extract_report_key(&operator_uri).into())) } fn extract_parameters(input_string: &str) -> HashMap { From 63d9f7175cea76f14ab86e2536c807be2c342a36 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 1 Sep 2024 14:48:40 +0100 Subject: [PATCH 248/265] Added some report functions --- src/custom_functions/report_uri_functions.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs index 2f6b31a3..026277d5 100644 --- a/src/custom_functions/report_uri_functions.rs +++ b/src/custom_functions/report_uri_functions.rs @@ -8,11 +8,12 @@ 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>(uri: TL, output_property_name: TR) -> Result -where >::Error: std::fmt::Display,>::Error: std::fmt::Display +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!([{ @@ -21,7 +22,7 @@ where >::Error: std::fmt::Display, Date: Tue, 3 Sep 2024 11:18:03 +0100 Subject: [PATCH 249/265] Added or and and functions --- src/custom_functions/expression_functions.rs | 34 ++++++++++++ src/error/display.rs | 2 +- src/error/mod.rs | 2 +- src/tree/mod.rs | 4 +- tests/integration.rs | 54 +++++++++++--------- tests/regex.rs | 13 +++-- tests/serde.rs | 2 +- 7 files changed, 80 insertions(+), 31 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index fa45c91d..e8179335 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -191,6 +191,40 @@ where >::Error: Debug,>::Error: Debug 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 { 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 b84482ee..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. diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 51357b8e..c5d187ae 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -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 diff --git a/tests/integration.rs b/tests/integration.rs index 50c307e5..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))); @@ -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()) }) ); } @@ -1676,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); @@ -1686,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); @@ -1701,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()); @@ -1716,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)); @@ -1748,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!( @@ -1780,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!( @@ -1815,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!( @@ -1847,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/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 bdff36e8..990cdd8e 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -16,7 +16,7 @@ fn test_serde() { #[test] fn test_string_serialization() { - let string = Value::String("Item1".to_owned()); + let string = Value::String("Item1".to_owned().into()); println!("{:?}", ron::ser::to_string(&string).unwrap()); } From de88812ebf808eb63f8ae09a415ea40563f2c768 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 4 Sep 2024 11:13:21 +0100 Subject: [PATCH 250/265] Added or and and functions --- src/custom_functions/back.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/back.rs b/src/custom_functions/back.rs index c6256b6d..f86325c8 100644 --- a/src/custom_functions/back.rs +++ b/src/custom_functions/back.rs @@ -2,7 +2,7 @@ use crate::{BoxedOperatorRowTrait, Error, OperatorRowTrait, Value}; pub fn back(row: &BoxedOperatorRowTrait, columns: &[usize]) -> Result { if columns.is_empty() { - return Ok(Value::Float(100f64)); + return Ok(Value::Empty); } Ok(row.get_value_for_column(columns[0])?) } From 448d8335634716b57c6cb5997503a350842e9e4b Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 4 Sep 2024 16:37:46 +0100 Subject: [PATCH 251/265] Added ATan --- src/custom_functions/expression_functions.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index e8179335..38b7111b 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -2,6 +2,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt::Debug; use crate::{Error, FloatType, Value}; use chrono::{NaiveDateTime,Timelike,Utc, DateTime, Duration, Datelike, TimeZone}; +use num_traits::real::Real; use crate::Error::CustomError; pub fn is_null>(value: T) -> Result { @@ -288,6 +289,15 @@ pub fn max,TR: Into>(value1: TL, value2: TR) -> Result>(value: T) -> Result { + let x = value.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(); From 1eb672515f22e58b30cce0adee786c3091a81f72 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Wed, 4 Sep 2024 17:52:12 +0100 Subject: [PATCH 252/265] Added ATan --- src/custom_functions/expression_functions.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 38b7111b..4b870466 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -288,15 +288,16 @@ pub fn max,TR: Into>(value1: TL, value2: TR) -> Result>(value: T) -> Result { - let x = value.into(); - - // Apply atan to the value - let atan_x = x. atan(); - - Ok(atan_x) -} +// 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(); From d0a10ce3bad77aabb85f603e5f6b13771145a13a Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 7 Sep 2024 17:40:16 +0100 Subject: [PATCH 253/265] Added some report functions --- src/value/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index d6323ed2..44714ddd 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -68,7 +68,7 @@ impl From for serde_json::Value { /// A helper enum for handling owned data or references with raw pointers. #[derive(Clone)] pub enum CowData{ - Owned(String), + Owned(Box>), Borrowed { data: *const u8, length: usize, @@ -79,7 +79,7 @@ pub enum CowData{ impl From for String { fn from(cow_data: CowData) -> String { match cow_data { - CowData::Owned(s) => s, + CowData::Owned(s) => Pin::into_inner(*s), CowData::Borrowed { data, length, .. } => { // Safely convert the borrowed data to a String unsafe { @@ -104,7 +104,7 @@ impl FromStr for CowData{ type Err = EvalexprError; fn from_str(s: &str) -> Result { - Ok(CowData::Owned(s.to_string())) + Ok(CowData::Owned(Box::new(Pin::new(s.to_string())))) } } unsafe impl Send for CowData{} @@ -163,7 +163,7 @@ impl CowData { pub fn into_owned(self) -> String { match self { - CowData::Owned(data) => data, + 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() @@ -173,7 +173,7 @@ impl CowData { { match self { - CowData::Owned(data) => data.clone(), + 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() @@ -233,7 +233,7 @@ impl<'de> Deserialize<'de> for CowData D: Deserializer<'de>, { let owned_data = String::deserialize(deserializer)?; - Ok(CowData::Owned(owned_data)) + Ok(CowData::Owned(Box::new(Pin::new(owned_data)))) } } @@ -270,13 +270,13 @@ impl Hash for Value { impl From for CowData { fn from(s: String) -> Self { - CowData::Owned(s) + CowData::Owned(Box::new(Pin::new(s))) } } impl From<&str> for CowData { fn from(s: &str) -> Self { - CowData::Owned(s.to_string()) + CowData::Owned(Box::new(Pin::new(s.to_string()))) } } @@ -537,13 +537,13 @@ impl Value { impl From for Value { fn from(string: String) -> Self { - Value::String(CowData::Owned(string)) + Value::String(CowData::Owned(Box::new(Pin::new(string)))) } } impl From<&str> for Value { fn from(string: &str) -> Self { - Value::String(CowData::Owned(string.to_string())) + Value::String(CowData::Owned(Box::new(Pin::new(string.to_string())))) } } @@ -674,6 +674,7 @@ 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}; From df99927b562f7df03760f3ce9badb6c9c05b4b62 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 7 Sep 2024 21:34:16 +0100 Subject: [PATCH 254/265] Added some report functions --- src/templates/utils.rs | 2 +- src/value/mod.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/templates/utils.rs b/src/templates/utils.rs index dfe010f4..76a5da92 100644 --- a/src/templates/utils.rs +++ b/src/templates/utils.rs @@ -21,7 +21,7 @@ pub fn set_value_indirect<'a>(values: &'a mut Vec,dirty_columns: &'a mu 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; + *result = value.into_owned(); dirty_columns.push(*column); } Ok(()) diff --git a/src/value/mod.rs b/src/value/mod.rs index 44714ddd..c98ba714 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -63,6 +63,14 @@ impl From for serde_json::Value { } } +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. From 5a7c410c238d3e7ee1b841e28da0f3de669cf872 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 7 Sep 2024 21:49:56 +0100 Subject: [PATCH 255/265] Added some report functions --- src/templates/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/utils.rs b/src/templates/utils.rs index 76a5da92..155797b6 100644 --- a/src/templates/utils.rs +++ b/src/templates/utils.rs @@ -14,7 +14,7 @@ pub fn get_value_indirect_from_row<'a>(row: &BoxedOperatorRowTrait, column_inde 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; + *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> { From 02ebc3a399ea19ef7a36cee44f4725689ad5ff85 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sat, 7 Sep 2024 22:13:17 +0100 Subject: [PATCH 256/265] Added some report functions --- src/custom_functions/expression_functions.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/custom_functions/expression_functions.rs b/src/custom_functions/expression_functions.rs index 4b870466..d078af0f 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -165,6 +165,21 @@ where >::Error: Debug,>::Error: Debug, Ok("".to_string().into()) } +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 From ca9c83272bb99b12da0bf83db5249e4d71843ab9 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 8 Sep 2024 03:29:20 +0100 Subject: [PATCH 257/265] Added some report functions --- src/value/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/value/mod.rs b/src/value/mod.rs index c98ba714..95e31cb3 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -728,13 +728,15 @@ impl Add for &Value { 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, b).into())), + (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())), From eef4715d12989ae69b0863d197b1287fe2a5e110 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 8 Sep 2024 04:17:58 +0100 Subject: [PATCH 258/265] Added some report functions --- Cargo.toml | 1 + src/custom_functions/report_uri_functions.rs | 28 ++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b6b0f0e..e5143f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ linregress = "0.5" 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" [features] diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs index 026277d5..6b5151c7 100644 --- a/src/custom_functions/report_uri_functions.rs +++ b/src/custom_functions/report_uri_functions.rs @@ -1,6 +1,6 @@ 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")] @@ -33,7 +33,25 @@ where >::Error: std::fmt::Display Ok(Value::String(extract_report_key(&operator_uri).into())) } -fn extract_parameters(input_string: &str) -> HashMap { +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' @@ -52,15 +70,15 @@ fn extract_parameters(input_string: &str) -> HashMap { 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, value); + params_dict.insert(key, url_decode(value)?); } } - return params_dict; + return Ok(params_dict); } // Return an empty HashMap if 'params' is not found - HashMap::new() + Ok(HashMap::new()) } From 7691bd198195619b81b968c4bb953210adbec370 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Sun, 8 Sep 2024 04:55:15 +0100 Subject: [PATCH 259/265] Added some report functions --- src/custom_functions/report_uri_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/report_uri_functions.rs b/src/custom_functions/report_uri_functions.rs index 6b5151c7..21250389 100644 --- a/src/custom_functions/report_uri_functions.rs +++ b/src/custom_functions/report_uri_functions.rs @@ -14,7 +14,7 @@ where >::Error: std::fmt::Display, Date: Mon, 9 Sep 2024 11:33:16 +0100 Subject: [PATCH 260/265] Added some report functions --- src/value/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/value/mod.rs b/src/value/mod.rs index 95e31cb3..deb5224b 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -834,6 +834,21 @@ pub struct FfiResult { 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 { From 72ed12d38fad857e09df8c0dec552e5d0d1e14e2 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Mon, 9 Sep 2024 11:38:18 +0100 Subject: [PATCH 261/265] Added some report functions --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d40c7680..1be4e94b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -560,7 +560,7 @@ pub use crate::{ operator::Operator, token::PartialToken, tree::Node, - value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE, FfiResult, to_ffi_result, to_nested_ffi_result,Error}, + 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")] From 2bbeb9fb2a957faea45e7a747b8688af9cde72cb Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 24 Oct 2024 09:12:01 +0100 Subject: [PATCH 262/265] Added atan --- src/custom_functions/atan.rs | 78 ++++++++++++++++++++++++++++++++++++ src/custom_functions/mod.rs | 2 + 2 files changed, 80 insertions(+) create mode 100644 src/custom_functions/atan.rs 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/mod.rs b/src/custom_functions/mod.rs index c8d0b81f..0b317946 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -8,6 +8,7 @@ mod rolling_stdev; mod rolling_min; pub mod pow; mod sqrt; +mod atan; pub mod bucket_functions; pub mod report_uri_functions; @@ -19,6 +20,7 @@ pub use expression_functions::*; pub use back::*; pub use pow::*; pub use sqrt::*; +pub use atan::*; pub use bucket_functions::*; pub use simple_moving_average::simple_moving_average; pub use simple_cumulative_sum::simple_cumulative_sum; From dd256d5f95943b7217111e9b5601f30830c98f19 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 24 Oct 2024 12:20:49 +0100 Subject: [PATCH 263/265] Added atan --- Cargo.toml | 2 + src/custom_functions/date_utils.rs | 81 ++++++++++++++++++++ src/custom_functions/expression_functions.rs | 5 ++ src/custom_functions/mod.rs | 2 + 4 files changed, 90 insertions(+) create mode 100644 src/custom_functions/date_utils.rs diff --git a/Cargo.toml b/Cargo.toml index e5143f40..350fda94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,14 @@ 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] diff --git a/src/custom_functions/date_utils.rs b/src/custom_functions/date_utils.rs new file mode 100644 index 00000000..33fbd218 --- /dev/null +++ b/src/custom_functions/date_utils.rs @@ -0,0 +1,81 @@ +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 start_date_str = start_date.try_into().map_err(|err| CustomError(format!("{err:?}")))?.as_string()?; + let end_date_str = end_date.try_into().map_err(|err| CustomError(format!("{err:?}")))?.as_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)) +} + + +#[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 index d078af0f..610bcda4 100644 --- a/src/custom_functions/expression_functions.rs +++ b/src/custom_functions/expression_functions.rs @@ -2,6 +2,7 @@ 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; @@ -165,6 +166,10 @@ where >::Error: Debug,>::Error: Debug, Ok("".to_string().into()) } +use chrono::NaiveDate; + + + pub fn concat, TR: TryInto>(left: TL, right: TR) -> Result where >::Error: Debug, diff --git a/src/custom_functions/mod.rs b/src/custom_functions/mod.rs index 0b317946..1dc592b8 100644 --- a/src/custom_functions/mod.rs +++ b/src/custom_functions/mod.rs @@ -11,6 +11,7 @@ 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 @@ -22,6 +23,7 @@ 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; From 9274ec0294934ba7d0218f538a1cb5968547f44c Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 24 Oct 2024 12:45:57 +0100 Subject: [PATCH 264/265] added days between --- src/custom_functions/date_utils.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/custom_functions/date_utils.rs b/src/custom_functions/date_utils.rs index 33fbd218..47331977 100644 --- a/src/custom_functions/date_utils.rs +++ b/src/custom_functions/date_utils.rs @@ -15,8 +15,16 @@ where >::Error: Debug, { // Convert inputs to strings and then to NaiveDate - let start_date_str = start_date.try_into().map_err(|err| CustomError(format!("{err:?}")))?.as_string()?; - let end_date_str = end_date.try_into().map_err(|err| CustomError(format!("{err:?}")))?.as_string()?; + 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") From e75f1e374fe6e77838c3b2f71116a8ab87160129 Mon Sep 17 00:00:00 2001 From: Gbemiga Meduoye Date: Thu, 24 Oct 2024 12:56:28 +0100 Subject: [PATCH 265/265] added days between --- src/custom_functions/date_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom_functions/date_utils.rs b/src/custom_functions/date_utils.rs index 47331977..f03fad08 100644 --- a/src/custom_functions/date_utils.rs +++ b/src/custom_functions/date_utils.rs @@ -38,7 +38,7 @@ where } let days = BUSINESS_DAY_CALENDAR.work_days_between(start_date, end_date); // Return the result as an integer value - Ok(Value::Int(days)) + Ok(Value::Int(days - 1)) }