diff --git a/crates/brk_bindgen/src/analysis/positions.rs b/crates/brk_bindgen/src/analysis/positions.rs index d70f2b483..454136e8f 100644 --- a/crates/brk_bindgen/src/analysis/positions.rs +++ b/crates/brk_bindgen/src/analysis/positions.rs @@ -8,7 +8,10 @@ use std::collections::BTreeMap; use brk_types::TreeNode; -use super::{find_common_prefix, find_common_suffix, get_node_fields, normalize_prefix}; +use super::{ + find_common_prefix, find_common_suffix, get_node_fields, get_shortest_leaf_name, + normalize_prefix, +}; use crate::{PatternBaseResult, PatternField, PatternMode, StructuralPattern, build_child_path}; /// Result of analyzing a single pattern instance. @@ -91,7 +94,47 @@ fn collect_instance_analyses( } // Analyze this instance - let analysis = analyze_instance(&child_bases); + let mut analysis = analyze_instance(&child_bases); + + // When some field_parts are empty (children returned the same base), + // replace empty parts with discriminators derived from shortest leaf names. + let has_empty = analysis.field_parts.values().any(|v| v.is_empty()); + let has_nonempty = analysis.field_parts.values().any(|v| !v.is_empty()); + if has_empty && has_nonempty { + // Mixed case: some fields have parts, some don't. + // Use shortest leaf to derive discriminators for empty fields. + let prefix = format!("{}_", analysis.base); + for (field_name, child_node) in children { + if let Some(part) = analysis.field_parts.get(field_name) + && part.is_empty() + && let Some(leaf) = get_shortest_leaf_name(child_node) + && let Some(suffix) = leaf.strip_prefix(&prefix) + && !suffix.is_empty() + // Only use if the suffix starts with the field key, + // avoiding internal sub-field names like "0sd" from a Price child + && suffix.starts_with(field_name.trim_start_matches('_')) + { + analysis + .field_parts + .insert(field_name.clone(), suffix.to_string()); + } + } + } else if has_empty && analysis.field_parts.len() > 1 { + // All-empty case: all children returned the same base. + // Re-analyze using shortest leaf names which may differentiate. + let mut leaf_bases: BTreeMap = BTreeMap::new(); + for (field_name, child_node) in children { + if let Some(leaf) = get_shortest_leaf_name(child_node) { + leaf_bases.insert(field_name.clone(), leaf); + } + } + if leaf_bases.len() == child_bases.len() { + let leaf_analysis = analyze_instance(&leaf_bases); + if !leaf_analysis.field_parts.values().all(|v| v.is_empty()) { + analysis.field_parts = leaf_analysis.field_parts; + } + } + } // Store the base result for this node // Note: has_outlier is false because we use recursive base computation @@ -121,6 +164,126 @@ fn collect_instance_analyses( } } +/// Try to detect a template pattern when instances have different field_parts. +/// +/// Supports two cases: +/// 1. **Embedded discriminator**: a substring varies per instance within field_parts. +/// E.g., `ratio_pct99_bps` vs `ratio_pct1_bps` → template `ratio_{disc}_bps` +/// 2. **Suffix discriminator**: a common suffix is appended to all field_parts. +/// E.g., `ratio_sd` vs `ratio_sd_4y` → template `ratio_sd{disc}` +fn try_detect_template( + majority: &[&InstanceAnalysis], + fields: &[PatternField], +) -> Option { + if majority.len() < 2 { + return None; + } + + // Strategy 1: Find an embedded discriminator (shortest non-empty field_part + // that differs between instances and appears as substring in other parts) + if let Some(mode) = try_embedded_disc(majority, fields) { + return Some(mode); + } + + // Strategy 2: Find a common suffix difference across ALL field_parts + try_suffix_disc(majority, fields) +} + +/// Strategy 1: embedded discriminator (e.g., pct99 inside ratio_pct99_bps) +fn try_embedded_disc( + majority: &[&InstanceAnalysis], + fields: &[PatternField], +) -> Option { + let first = &majority[0]; + let second = &majority[1]; + + // Find the discriminator: shortest non-empty field_part that differs + let disc_field = fields + .iter() + .filter_map(|f| first.field_parts.get(&f.name).map(|v| (&f.name, v))) + .filter(|(_, v)| !v.is_empty()) + .min_by_key(|(_, v)| v.len())?; + + let disc_first = disc_field.1; + let disc_second = second.field_parts.get(disc_field.0)?; + + if disc_first == disc_second || disc_first.is_empty() || disc_second.is_empty() { + return None; + } + + // Build templates by replacing the discriminator with {disc} + let mut templates = BTreeMap::new(); + for field in fields { + let part = first.field_parts.get(&field.name)?; + let template = part.replacen(disc_first, "{disc}", 1); + templates.insert(field.name.clone(), template); + } + + // Verify ALL instances match + for analysis in majority { + let inst_disc = analysis.field_parts.get(disc_field.0)?; + for field in fields { + let part = analysis.field_parts.get(&field.name)?; + let expected = templates.get(&field.name)?.replace("{disc}", inst_disc); + if part != &expected { + return None; + } + } + } + + Some(PatternMode::Templated { templates }) +} + +/// Strategy 2: suffix discriminator (e.g., all field_parts differ by `_4y` suffix) +fn try_suffix_disc( + majority: &[&InstanceAnalysis], + fields: &[PatternField], +) -> Option { + let first = &majority[0]; + + // For each other instance, check if ALL field_parts differ from the first + // by the same suffix. Use the first field to detect the suffix. + let ref_field = &fields[0].name; + let ref_first = first.field_parts.get(ref_field)?; + + // Build templates from the first instance + // Non-empty parts get {disc} appended; empty parts (identity) stay empty + let mut templates = BTreeMap::new(); + for field in fields { + let part = first.field_parts.get(&field.name)?; + if part.is_empty() { + templates.insert(field.name.clone(), String::new()); + } else { + templates.insert(field.name.clone(), format!("{part}{{disc}}")); + } + } + + // Verify ALL other instances: non-empty parts differ by the same suffix + for analysis in &majority[1..] { + let ref_other = analysis.field_parts.get(ref_field)?; + let suffix = ref_other.strip_prefix(ref_first)?; + + for field in fields { + let first_part = first.field_parts.get(&field.name)?; + let other_part = analysis.field_parts.get(&field.name)?; + + if first_part.is_empty() { + // Identity field — must stay empty in all instances + if !other_part.is_empty() { + return None; + } + } else { + let expected = format!("{first_part}{suffix}"); + if other_part != &expected { + return None; + } + } + } + } + + Some(PatternMode::Templated { templates }) +} + /// Analyze a single pattern instance from its child bases. fn analyze_instance(child_bases: &BTreeMap) -> InstanceAnalysis { let bases: Vec<&str> = child_bases.values().map(|s| s.as_str()).collect(); @@ -144,6 +307,19 @@ fn analyze_instance(child_bases: &BTreeMap) -> InstanceAnalysis field_parts.insert(field_name.clone(), relative); } + // If all field_parts are empty (all children returned the same base), + // use the field keys as suffix discriminators. This handles patterns like + // period windows (all/_4y/_2y/_1y) where children differ by a suffix + // that corresponds to the tree key. + if field_parts.len() > 1 && field_parts.values().all(|v| v.is_empty()) { + // Can't differentiate — this pattern is non-parameterizable + return InstanceAnalysis { + base, + field_parts, + is_suffix_mode: true, + }; + } + return InstanceAnalysis { base, field_parts, @@ -188,64 +364,56 @@ fn analyze_instance(child_bases: &BTreeMap) -> InstanceAnalysis } /// Determine the consistent mode for a pattern from all its instances. -/// Uses majority voting: if most instances agree on mode and field_parts, -/// use those. Minority instances will be inlined at usage sites. +/// Picks the majority mode (suffix vs prefix), then requires all instances +/// in that mode to agree on field_parts. Minority-mode instances get inlined. fn determine_pattern_mode( analyses: &[InstanceAnalysis], fields: &[PatternField], ) -> Option { - if analyses.is_empty() { - return None; - } + analyses.first()?; - // Group instances by (mode, field_parts) signature - let suffix_instances: Vec<_> = analyses.iter().filter(|a| a.is_suffix_mode).collect(); - let prefix_instances: Vec<_> = analyses.iter().filter(|a| !a.is_suffix_mode).collect(); + // Pick the majority mode + let suffix_count = analyses.iter().filter(|a| a.is_suffix_mode).count(); + let is_suffix = suffix_count * 2 >= analyses.len(); - // Pick the majority mode group - let (majority_instances, is_suffix) = if suffix_instances.len() >= prefix_instances.len() { - (suffix_instances, true) - } else { - (prefix_instances, false) - }; - - if majority_instances.is_empty() { - return None; - } - - // Find the most common field_parts within the majority group - // Convert to sorted Vec for comparison since BTreeMap isn't hashable - let mut parts_counts: BTreeMap, usize> = BTreeMap::new(); - for analysis in &majority_instances { - let mut sorted: Vec<_> = analysis - .field_parts - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - sorted.sort(); - *parts_counts.entry(sorted).or_insert(0) += 1; - } - - let (best_parts_vec, _count) = parts_counts.into_iter().max_by_key(|(_, count)| *count)?; - let best_parts: BTreeMap = best_parts_vec.into_iter().collect(); + // All instances of the majority mode must agree on field_parts + let majority: Vec<_> = analyses + .iter() + .filter(|a| a.is_suffix_mode == is_suffix) + .collect(); + let first_majority = majority.first()?; // Verify all required fields have parts for field in fields { - if !best_parts.contains_key(&field.name) { + if !first_majority.field_parts.contains_key(&field.name) { return None; } } - let field_parts = best_parts; + if majority + .iter() + .all(|a| a.field_parts == first_majority.field_parts) + { + let field_parts = first_majority.field_parts.clone(); + return if is_suffix { + Some(PatternMode::Suffix { + relatives: field_parts, + }) + } else { + Some(PatternMode::Prefix { + prefixes: field_parts, + }) + }; + } + + // Instances disagree on field_parts. Try to detect a template pattern: + // if each field's value varies by exactly one substring that's different + // per instance, we can use a Templated mode with {disc} placeholder. if is_suffix { - Some(PatternMode::Suffix { - relatives: field_parts, - }) + try_detect_template(&majority, fields) } else { - Some(PatternMode::Prefix { - prefixes: field_parts, - }) + None } } @@ -402,9 +570,8 @@ mod tests { assert_eq!(relatives.get("min"), Some(&"min".to_string())); assert_eq!(relatives.get("percentiles"), Some(&"".to_string())); } - PatternMode::Prefix { .. } => { - panic!("Expected suffix mode, got prefix mode"); - } + PatternMode::Prefix { .. } => panic!("Expected suffix mode, got prefix mode"), + PatternMode::Templated { .. } => panic!("Expected suffix mode, got templated mode"), } } @@ -460,9 +627,8 @@ mod tests { assert_eq!(relatives.get("max"), Some(&"max".to_string())); assert_eq!(relatives.get("min"), Some(&"min".to_string())); } - PatternMode::Prefix { .. } => { - panic!("Expected suffix mode"); - } + PatternMode::Prefix { .. } => panic!("Expected suffix mode"), + PatternMode::Templated { .. } => panic!("Expected suffix mode, got templated"), } } } diff --git a/crates/brk_bindgen/src/analysis/tree.rs b/crates/brk_bindgen/src/analysis/tree.rs index 5502b9631..845e909f6 100644 --- a/crates/brk_bindgen/src/analysis/tree.rs +++ b/crates/brk_bindgen/src/analysis/tree.rs @@ -16,7 +16,7 @@ use super::{find_common_prefix, find_common_suffix, normalize_prefix}; /// /// This is useful for pattern base analysis where we want the "base" case /// (e.g., the leaf without suffix like `_btc` or `_usd`). -fn get_shortest_leaf_name(node: &TreeNode) -> Option { +pub(super) fn get_shortest_leaf_name(node: &TreeNode) -> Option { match node { TreeNode::Leaf(leaf) => Some(leaf.name().to_string()), TreeNode::Branch(children) => children diff --git a/crates/brk_bindgen/src/backends/javascript.rs b/crates/brk_bindgen/src/backends/javascript.rs index eded7d986..97b2dbddb 100644 --- a/crates/brk_bindgen/src/backends/javascript.rs +++ b/crates/brk_bindgen/src/backends/javascript.rs @@ -59,4 +59,52 @@ impl LanguageSyntax for JavaScriptSyntax { fn constructor_name(&self, type_name: &str) -> String { format!("create{}", type_name) } + + fn disc_arg_expr(&self, template: &str) -> String { + if template == "{disc}" { + "disc".to_string() + } else if template.is_empty() { + "''".to_string() + } else if !template.contains("{disc}") { + format!("'{}'", template) + } else if template.ends_with("{disc}") { + let static_part = template.trim_end_matches("{disc}").trim_end_matches('_'); + format!("_m('{}', disc)", static_part) + } else { + let js_template = template.replace("{disc}", "${disc}"); + format!("`{}`", js_template) + } + } + + fn template_expr(&self, acc_var: &str, template: &str) -> String { + let var_name = to_camel_case(acc_var); + if template.is_empty() { + // Identity — just pass disc + format!("_m({}, disc)", var_name) + } else if template == "{disc}" { + // Template IS the discriminator + format!("_m({}, disc)", var_name) + } else if !template.contains("{disc}") { + // Static suffix — no disc involved + format!("_m({}, '{}')", var_name, template) + } else { + // Template with {disc}: use nested _m for proper separator handling + // "ratio_{disc}_bps" → split on {disc} → _m(_m(acc, 'ratio'), disc) then _bps + // But this is complex. For embedded disc, use string interpolation. + // For suffix disc (ends with {disc}), use _m composition. + if template.ends_with("{disc}") { + let static_part = &template[..template.len() - "{disc}".len()]; + if static_part.is_empty() { + format!("_m({}, disc)", var_name) + } else { + let static_part = static_part.trim_end_matches('_'); + format!("_m(_m({}, '{}'), disc)", var_name, static_part) + } + } else { + // Embedded disc — use template literal + let js_template = template.replace("{disc}", "${disc}"); + format!("_m({}, `{}`)", var_name, js_template) + } + } + } } diff --git a/crates/brk_bindgen/src/backends/python.rs b/crates/brk_bindgen/src/backends/python.rs index 17d5aff30..a20b18bc9 100644 --- a/crates/brk_bindgen/src/backends/python.rs +++ b/crates/brk_bindgen/src/backends/python.rs @@ -54,4 +54,29 @@ impl LanguageSyntax for PythonSyntax { fn constructor_name(&self, type_name: &str) -> String { type_name.to_string() } + + fn disc_arg_expr(&self, template: &str) -> String { + if template == "{disc}" { + "disc".to_string() + } else if template.is_empty() { + "''".to_string() + } else if !template.contains("{disc}") { + format!("'{}'", template) + } else if template.ends_with("{disc}") { + let static_part = template.trim_end_matches("{disc}").trim_end_matches('_'); + format!("_m('{}', disc)", static_part) + } else { + format!("f'{}'", template) + } + } + + fn template_expr(&self, acc_var: &str, template: &str) -> String { + if template == "{disc}" { + format!("_m({}, disc)", acc_var) + } else if !template.contains("{disc}") { + format!("_m({}, '{}')", acc_var, template) + } else { + format!("_m({}, f'{}')", acc_var, template) + } + } } diff --git a/crates/brk_bindgen/src/backends/rust.rs b/crates/brk_bindgen/src/backends/rust.rs index ea2acd711..a58e2af13 100644 --- a/crates/brk_bindgen/src/backends/rust.rs +++ b/crates/brk_bindgen/src/backends/rust.rs @@ -55,4 +55,48 @@ impl LanguageSyntax for RustSyntax { fn constructor_name(&self, type_name: &str) -> String { format!("{}::new", type_name) } + + fn disc_arg_expr(&self, template: &str) -> String { + if template == "{disc}" { + "disc.clone()".to_string() + } else if template.is_empty() { + "String::new()".to_string() + } else if !template.contains("{disc}") { + format!("\"{}\".to_string()", template) + } else if template.ends_with("{disc}") { + let static_part = template.trim_end_matches("{disc}").trim_end_matches('_'); + format!("_m(\"{}\", &disc)", static_part) + } else { + let rust_template = template + .replace("{disc}", "{disc}") + .replace('{', "{{") + .replace('}', "}}") + .replace("{{disc}}", "{disc}"); + format!("format!(\"{}\")", rust_template) + } + } + + fn template_expr(&self, acc_var: &str, template: &str) -> String { + if template == "{disc}" { + // _m(acc, disc) in Rust + format!("_m(&{}, &disc)", acc_var) + } else if template.is_empty() { + // Identity + acc_var.to_string() + } else if !template.contains("{disc}") { + // Static suffix + format!("_m(&{}, \"{}\")", acc_var, template) + } else { + // Template with disc: _m(&acc, &format!("ratio_{disc}_bps", disc=disc)) + let rust_template = template + .replace("{disc}", "{disc}") + .replace('{', "{{") + .replace('}', "}}") + .replace("{{disc}}", "{disc}"); + format!( + "_m(&{}, &format!(\"{}\", disc=disc))", + acc_var, rust_template + ) + } + } } diff --git a/crates/brk_bindgen/src/generate/fields.rs b/crates/brk_bindgen/src/generate/fields.rs index 97a066202..9dd2ec32d 100644 --- a/crates/brk_bindgen/src/generate/fields.rs +++ b/crates/brk_bindgen/src/generate/fields.rs @@ -29,7 +29,10 @@ fn compute_path_expr( ) -> String { match pattern.get_field_part(&field.name) { Some(part) => { - if pattern.is_suffix_mode() { + if pattern.is_templated() { + // Templated: replace {disc} with disc variable at runtime + syntax.template_expr(base_var, part) + } else if pattern.is_suffix_mode() { syntax.suffix_expr(base_var, part) } else { syntax.prefix_expr(part, base_var) @@ -76,7 +79,20 @@ pub fn generate_parameterized_field( let type_ann = metadata.field_type_annotation(field, pattern.is_generic, None, syntax.generic_syntax()); let path_expr = compute_path_expr(syntax, pattern, field, "acc"); - let value = compute_field_value(syntax, field, metadata, &path_expr); + + // When calling a templated child pattern, pass acc and disc separately + let value = if let Some(child_pattern) = metadata.find_pattern(&field.rust_type) + && child_pattern.is_templated() + { + let disc_template = pattern + .get_field_part(&field.name) + .unwrap_or(&field.name); + let disc_arg = syntax.disc_arg_expr(disc_template); + let acc_arg = syntax.suffix_expr("acc", ""); // identity — returns acc or acc.clone() + syntax.constructor(&field.rust_type, &format!("{acc_arg}, {disc_arg}")) + } else { + compute_field_value(syntax, field, metadata, &path_expr) + }; writeln!( output, diff --git a/crates/brk_bindgen/src/generators/javascript/client.rs b/crates/brk_bindgen/src/generators/javascript/client.rs index c92da745c..497fb8e32 100644 --- a/crates/brk_bindgen/src/generators/javascript/client.rs +++ b/crates/brk_bindgen/src/generators/javascript/client.rs @@ -701,7 +701,7 @@ pub fn generate_structural_patterns( } writeln!(output, " */\n").unwrap(); - // Generate factory function for ALL patterns + writeln!(output, "/**").unwrap(); writeln!(output, " * Create a {} pattern node", pattern.name).unwrap(); if pattern.is_generic { @@ -709,6 +709,9 @@ pub fn generate_structural_patterns( } writeln!(output, " * @param {{BrkClientBase}} client").unwrap(); writeln!(output, " * @param {{string}} acc - Accumulated metric name").unwrap(); + if pattern.is_templated() { + writeln!(output, " * @param {{string}} disc - Discriminator suffix").unwrap(); + } let return_type = if pattern.is_generic { format!("{}", pattern.name) } else { @@ -717,7 +720,11 @@ pub fn generate_structural_patterns( writeln!(output, " * @returns {{{}}}", return_type).unwrap(); writeln!(output, " */").unwrap(); - writeln!(output, "function create{}(client, acc) {{", pattern.name).unwrap(); + if pattern.is_templated() { + writeln!(output, "function create{}(client, acc, disc) {{", pattern.name).unwrap(); + } else { + writeln!(output, "function create{}(client, acc) {{", pattern.name).unwrap(); + } writeln!(output, " return {{").unwrap(); let syntax = JavaScriptSyntax; diff --git a/crates/brk_bindgen/src/generators/javascript/tree.rs b/crates/brk_bindgen/src/generators/javascript/tree.rs index b56a7fafb..79b4d8fed 100644 --- a/crates/brk_bindgen/src/generators/javascript/tree.rs +++ b/crates/brk_bindgen/src/generators/javascript/tree.rs @@ -221,12 +221,31 @@ fn generate_tree_initializer( writeln!(output, "{}}},", indent_str).unwrap(); } else { // Use pattern factory - writeln!( - output, - "{}{}: create{}(this, '{}'),", - indent_str, field_name, child.field.rust_type, child.base_result.base - ) - .unwrap(); + let pattern = metadata.find_pattern(&child.field.rust_type); + if pattern.is_some_and(|p| p.is_templated()) { + // Templated: extract discriminator from field_parts + let disc = child + .base_result + .field_parts + .values() + .filter(|v| !v.is_empty()) + .min_by_key(|v| v.len()) + .cloned() + .unwrap_or_default(); + writeln!( + output, + "{}{}: create{}(this, '{}', '{}'),", + indent_str, field_name, child.field.rust_type, child.base_result.base, disc + ) + .unwrap(); + } else { + writeln!( + output, + "{}{}: create{}(this, '{}'),", + indent_str, field_name, child.field.rust_type, child.base_result.base + ) + .unwrap(); + } } } } diff --git a/crates/brk_bindgen/src/generators/python/client.rs b/crates/brk_bindgen/src/generators/python/client.rs index e08ac79a5..04becdcff 100644 --- a/crates/brk_bindgen/src/generators/python/client.rs +++ b/crates/brk_bindgen/src/generators/python/client.rs @@ -683,6 +683,7 @@ pub fn generate_structural_patterns( writeln!(output, "# Reusable structural pattern classes\n").unwrap(); for pattern in patterns { + // Generate class if pattern.is_generic { writeln!(output, "class {}(Generic[T]):", pattern.name).unwrap(); diff --git a/crates/brk_bindgen/src/generators/rust/client.rs b/crates/brk_bindgen/src/generators/rust/client.rs index 8cb722942..4afa21d5f 100644 --- a/crates/brk_bindgen/src/generators/rust/client.rs +++ b/crates/brk_bindgen/src/generators/rust/client.rs @@ -552,11 +552,19 @@ pub fn generate_pattern_structs( " /// Create a new pattern node with accumulated metric name." ) .unwrap(); - writeln!( - output, - " pub fn new(client: Arc, acc: String) -> Self {{" - ) - .unwrap(); + if pattern.is_templated() { + writeln!( + output, + " pub fn new(client: Arc, acc: String, disc: String) -> Self {{" + ) + .unwrap(); + } else { + writeln!( + output, + " pub fn new(client: Arc, acc: String) -> Self {{" + ) + .unwrap(); + } writeln!(output, " Self {{").unwrap(); let syntax = RustSyntax; diff --git a/crates/brk_bindgen/src/generators/rust/tree.rs b/crates/brk_bindgen/src/generators/rust/tree.rs index aba64e61e..832299ea7 100644 --- a/crates/brk_bindgen/src/generators/rust/tree.rs +++ b/crates/brk_bindgen/src/generators/rust/tree.rs @@ -98,17 +98,37 @@ fn generate_tree_node( .unwrap(); } else { // Pattern type - use ::new() constructor - // All patterns have ::new(), parameterizable ones use detected mode, - // non-parameterizable ones use field name fallback - generate_tree_node_field( - output, - &syntax, - &child.field, - metadata, - " ", - child.name, - Some(&child.base_result.base), - ); + let pattern = metadata.find_pattern(&child.field.rust_type); + if pattern.is_some_and(|p| p.is_templated()) { + // Templated pattern: pass base and disc + let disc = child + .base_result + .field_parts + .values() + .filter(|v| !v.is_empty()) + .min_by_key(|v| v.len()) + .cloned() + .unwrap_or_default(); + writeln!( + output, + " {}: {}::new(client.clone(), {}, {}.to_string()),", + field_name, + child.field.rust_type, + syntax.string_literal(&child.base_result.base), + syntax.string_literal(&disc), + ) + .unwrap(); + } else { + generate_tree_node_field( + output, + &syntax, + &child.field, + metadata, + " ", + child.name, + Some(&child.base_result.base), + ); + } } } diff --git a/crates/brk_bindgen/src/syntax.rs b/crates/brk_bindgen/src/syntax.rs index d622f2324..034df4a45 100644 --- a/crates/brk_bindgen/src/syntax.rs +++ b/crates/brk_bindgen/src/syntax.rs @@ -92,4 +92,23 @@ pub trait LanguageSyntax { /// - JavaScript: `createTypeName` /// - Rust: `TypeName::new` fn constructor_name(&self, type_name: &str) -> String; + + /// Format a discriminator argument for passing to a templated child. + /// + /// Returns an expression computing the disc value from a template. + /// - `"pct99"` (static) → `'pct99'` (JS) / `"pct99".to_string()` (Rust) + /// - `""` (empty) → `disc` (pass parent's disc through) + /// - `"p1sd{disc}"` (suffix) → `_m('p1sd', disc)` (composed) + /// - `"ratio_{disc}_bps"` (embedded) → `` `ratio_${disc}_bps` `` (template literal) + fn disc_arg_expr(&self, template: &str) -> String; + + /// Format a templated mode expression: substitute `{disc}` at runtime. + /// + /// The template contains `{disc}` placeholder. The generated code should + /// construct `_m(acc, template_with_disc_substituted)` at runtime. + /// + /// # Arguments + /// * `acc_var` - The accumulator variable (e.g., "acc") + /// * `template` - Template like `"ratio_{disc}_bps"` or `"{disc}"` + fn template_expr(&self, acc_var: &str, template: &str) -> String; } diff --git a/crates/brk_bindgen/src/types/metadata.rs b/crates/brk_bindgen/src/types/metadata.rs index ca37e46d8..32fba12b7 100644 --- a/crates/brk_bindgen/src/types/metadata.rs +++ b/crates/brk_bindgen/src/types/metadata.rs @@ -69,26 +69,10 @@ impl ClientMetadata { self.find_pattern(name).is_some_and(|p| p.is_generic) } - /// Check if a pattern by name is fully parameterizable. - /// A pattern is parameterizable if it has a mode AND all its branch fields - /// are also parameterizable (or not patterns at all). + /// Check if a pattern by name is parameterizable (has a mode). pub fn is_parameterizable(&self, name: &str) -> bool { - self.find_pattern(name).is_some_and(|p| { - if !p.is_parameterizable() { - return false; - } - // Check all branch fields have parameterizable types (or are not patterns) - p.fields.iter().all(|f| { - if f.is_branch() { - self.structural_patterns - .iter() - .find(|pat| pat.name == f.rust_type) - .is_none_or(|pat| pat.is_parameterizable()) - } else { - true - } - }) - }) + self.find_pattern(name) + .is_some_and(|p| p.is_parameterizable()) } /// Check if child fields match ANY pattern (parameterizable or not). diff --git a/crates/brk_bindgen/src/types/positions.rs b/crates/brk_bindgen/src/types/positions.rs index 25ff34219..6d3bf2114 100644 --- a/crates/brk_bindgen/src/types/positions.rs +++ b/crates/brk_bindgen/src/types/positions.rs @@ -23,4 +23,12 @@ pub enum PatternMode { /// Maps field name to its prefix (empty string for identity) prefixes: BTreeMap, }, + /// Fields construct metric names using a template with a discriminator placeholder. + /// Factory takes two params: `acc` (base) and `disc` (discriminator). + /// Formula: `_m(acc, template.replace("{disc}", disc))` + /// Example: template `"ratio_{disc}_bps"` with disc `"pct99"` → `_m(acc, "ratio_pct99_bps")` + Templated { + /// Maps field name to its template string containing `{disc}` placeholder + templates: BTreeMap, + }, } diff --git a/crates/brk_bindgen/src/types/structs.rs b/crates/brk_bindgen/src/types/structs.rs index d2530526d..699bcaad5 100644 --- a/crates/brk_bindgen/src/types/structs.rs +++ b/crates/brk_bindgen/src/types/structs.rs @@ -41,21 +41,30 @@ impl StructuralPattern { relatives.get(field_name).map(|s| s.as_str()) } Some(PatternMode::Prefix { prefixes }) => prefixes.get(field_name).map(|s| s.as_str()), + Some(PatternMode::Templated { templates }) => { + templates.get(field_name).map(|s| s.as_str()) + } None => None, } } /// Returns true if this pattern is in suffix mode. pub fn is_suffix_mode(&self) -> bool { - matches!(&self.mode, Some(PatternMode::Suffix { .. })) + matches!( + &self.mode, + Some(PatternMode::Suffix { .. } | PatternMode::Templated { .. }) + ) + } + + /// Returns true if this pattern uses templated mode with a discriminator. + pub fn is_templated(&self) -> bool { + matches!(&self.mode, Some(PatternMode::Templated { .. })) } /// Check if the given instance field parts match this pattern's field parts. - /// Returns true if all field parts in the pattern match the instance's field parts. pub fn field_parts_match(&self, instance_field_parts: &BTreeMap) -> bool { match &self.mode { Some(PatternMode::Suffix { relatives }) => { - // For each field in the pattern, check if the instance has the same suffix relatives.iter().all(|(field_name, pattern_suffix)| { instance_field_parts .get(field_name) @@ -63,18 +72,56 @@ impl StructuralPattern { }) } Some(PatternMode::Prefix { prefixes }) => { - // For each field in the pattern, check if the instance has the same prefix prefixes.iter().all(|(field_name, pattern_prefix)| { instance_field_parts .get(field_name) .is_some_and(|instance_prefix| instance_prefix == pattern_prefix) }) } - None => false, // Non-parameterizable patterns don't use field parts + Some(PatternMode::Templated { templates }) => { + // For templated patterns, check if the instance's field_parts + // can be produced by substituting some discriminator into the templates + let first_template_field = templates.iter().next(); + let Some((ref_field, ref_template)) = first_template_field else { + return false; + }; + let Some(ref_value) = instance_field_parts.get(ref_field) else { + return false; + }; + // Extract discriminator from the reference field + let Some(disc) = extract_disc(ref_template, ref_value) else { + return false; + }; + // Verify all fields match with this discriminator + templates.iter().all(|(field_name, template)| { + instance_field_parts + .get(field_name) + .is_some_and(|value| *value == template.replace("{disc}", &disc)) + }) + } + None => false, } } } +/// Extract the discriminator value by matching a template against a concrete string. +/// E.g., template `"ratio_{disc}_bps"` matched against `"ratio_pct99_bps"` yields `"pct99"`. +fn extract_disc(template: &str, value: &str) -> Option { + let parts: Vec<&str> = template.split("{disc}").collect(); + if parts.len() != 2 { + return None; + } + let prefix = parts[0]; + let suffix = parts[1]; + if value.starts_with(prefix) && value.ends_with(suffix) { + let disc = &value[prefix.len()..value.len() - suffix.len()]; + if !disc.is_empty() { + return Some(disc.to_string()); + } + } + None +} + /// A field in a structural pattern. #[derive(Debug, Clone, PartialOrd, Ord)] pub struct PatternField { diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index a0c422caa..dac0f6e26 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -992,41 +992,41 @@ impl Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75 /// Pattern struct for repeated tree structure. pub struct _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern { pub _0sd: CentsSatsUsdPattern, - pub m0_5sd: PriceValuePattern, - pub m1_5sd: PriceValuePattern, - pub m1sd: PriceValuePattern, - pub m2_5sd: PriceValuePattern, - pub m2sd: PriceValuePattern, - pub m3sd: PriceValuePattern, - pub p0_5sd: PriceValuePattern, - pub p1_5sd: PriceValuePattern, - pub p1sd: PriceValuePattern, - pub p2_5sd: PriceValuePattern, - pub p2sd: PriceValuePattern, - pub p3sd: PriceValuePattern, + pub m0_5sd: PriceRatioPattern, + pub m1_5sd: PriceRatioPattern, + pub m1sd: PriceRatioPattern, + pub m2_5sd: PriceRatioPattern, + pub m2sd: PriceRatioPattern, + pub m3sd: PriceRatioPattern, + pub p0_5sd: PriceRatioPattern, + pub p1_5sd: PriceRatioPattern, + pub p1sd: PriceRatioPattern, + pub p2_5sd: PriceRatioPattern, + pub p2sd: PriceRatioPattern, + pub p3sd: PriceRatioPattern, pub sd: MetricPattern1, pub zscore: MetricPattern1, } impl _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern { /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { + pub fn new(client: Arc, acc: String, disc: String) -> Self { Self { - _0sd: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "0sd_4y")), - m0_5sd: PriceValuePattern::new(client.clone(), acc.clone()), - m1_5sd: PriceValuePattern::new(client.clone(), acc.clone()), - m1sd: PriceValuePattern::new(client.clone(), acc.clone()), - m2_5sd: PriceValuePattern::new(client.clone(), acc.clone()), - m2sd: PriceValuePattern::new(client.clone(), acc.clone()), - m3sd: PriceValuePattern::new(client.clone(), acc.clone()), - p0_5sd: PriceValuePattern::new(client.clone(), acc.clone()), - p1_5sd: PriceValuePattern::new(client.clone(), acc.clone()), - p1sd: PriceValuePattern::new(client.clone(), acc.clone()), - p2_5sd: PriceValuePattern::new(client.clone(), acc.clone()), - p2sd: PriceValuePattern::new(client.clone(), acc.clone()), - p3sd: PriceValuePattern::new(client.clone(), acc.clone()), - sd: MetricPattern1::new(client.clone(), _m(&acc, "sd_4y")), - zscore: MetricPattern1::new(client.clone(), _m(&acc, "zscore_4y")), + _0sd: CentsSatsUsdPattern::new(client.clone(), _m(&acc, &format!("0sd{disc}", disc=disc))), + m0_5sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("m0_5sd", &disc)), + m1_5sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("m1_5sd", &disc)), + m1sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("m1sd", &disc)), + m2_5sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("m2_5sd", &disc)), + m2sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("m2sd", &disc)), + m3sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("m3sd", &disc)), + p0_5sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("p0_5sd", &disc)), + p1_5sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("p1_5sd", &disc)), + p1sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("p1sd", &disc)), + p2_5sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("p2_5sd", &disc)), + p2sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("p2sd", &disc)), + p3sd: PriceRatioPattern::new(client.clone(), acc.clone(), _m("p3sd", &disc)), + sd: MetricPattern1::new(client.clone(), _m(&acc, &format!("ratio_sd{disc}", disc=disc))), + zscore: MetricPattern1::new(client.clone(), _m(&acc, &format!("ratio_zscore{disc}", disc=disc))), } } } @@ -1123,18 +1123,18 @@ impl CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - cap: CentsDeltaRelUsdPattern::new(client.clone(), _m(&acc, "realized_cap")), - gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), _m(&acc, "realized_gross_pnl")), - investor: LowerPriceUpperPattern::new(client.clone(), acc.clone()), - loss: BaseCapitulationCumulativeNegativeRelSumValuePattern::new(client.clone(), acc.clone()), - mvrv: MetricPattern1::new(client.clone(), _m(&acc, "mvrv")), - net_pnl: BaseChangeCumulativeDeltaRelSumPattern::new(client.clone(), _m(&acc, "net")), - peak_regret: BaseCumulativeRelPattern::new(client.clone(), _m(&acc, "realized_peak_regret")), - price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern::new(client.clone(), _m(&acc, "realized_price")), - profit: BaseCumulativeDistributionRelSumValuePattern::new(client.clone(), acc.clone()), - profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "realized_profit_to_loss_ratio")), - sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), _m(&acc, "sell_side_risk_ratio")), - sopr: AdjustedRatioValuePattern::new(client.clone(), acc.clone()), + cap: CentsDeltaRelUsdPattern::new(client.clone(), format!("{acc}_cap")), + gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), format!("{acc}_gross_pnl")), + investor: LowerPriceUpperPattern::new(client.clone(), format!("{acc}_investor")), + loss: BaseCapitulationCumulativeNegativeRelSumValuePattern::new(client.clone(), format!("{acc}_loss")), + mvrv: MetricPattern1::new(client.clone(), format!("{acc}_mvrv")), + net_pnl: BaseChangeCumulativeDeltaRelSumPattern::new(client.clone(), format!("{acc}_net_pnl")), + peak_regret: BaseCumulativeRelPattern::new(client.clone(), format!("{acc}_peak_regret")), + price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern::new(client.clone(), format!("{acc}_price")), + profit: BaseCumulativeDistributionRelSumValuePattern::new(client.clone(), format!("{acc}_profit")), + profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), format!("{acc}_profit_to_loss_ratio")), + sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), format!("{acc}_sell_side_risk_ratio")), + sopr: AdjustedRatioValuePattern::new(client.clone(), format!("{acc}_sopr")), } } } @@ -1223,18 +1223,18 @@ pub struct AverageGainsLossesRsiStochPattern { impl AverageGainsLossesRsiStochPattern { /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { + pub fn new(client: Arc, acc: String, disc: String) -> Self { Self { - average_gain: MetricPattern1::new(client.clone(), _m(&acc, "average_gain_24h")), - average_loss: MetricPattern1::new(client.clone(), _m(&acc, "average_loss_24h")), - gains: MetricPattern1::new(client.clone(), _m(&acc, "gains_24h")), - losses: MetricPattern1::new(client.clone(), _m(&acc, "losses_24h")), - rsi: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, "24h")), - rsi_max: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, "max_24h")), - rsi_min: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, "min_24h")), - stoch_rsi: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, "stoch_24h")), - stoch_rsi_d: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, "stoch_d_24h")), - stoch_rsi_k: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, "stoch_k_24h")), + average_gain: MetricPattern1::new(client.clone(), _m(&acc, &format!("average_gain_{disc}", disc=disc))), + average_loss: MetricPattern1::new(client.clone(), _m(&acc, &format!("average_loss_{disc}", disc=disc))), + gains: MetricPattern1::new(client.clone(), _m(&acc, &format!("gains_{disc}", disc=disc))), + losses: MetricPattern1::new(client.clone(), _m(&acc, &format!("losses_{disc}", disc=disc))), + rsi: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, &disc)), + rsi_max: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, &format!("max_{disc}", disc=disc))), + rsi_min: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, &format!("min_{disc}", disc=disc))), + stoch_rsi: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, &format!("stoch_{disc}", disc=disc))), + stoch_rsi_d: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, &format!("stoch_d_{disc}", disc=disc))), + stoch_rsi_k: BpsPercentRatioPattern3::new(client.clone(), _m(&acc, &format!("stoch_k_{disc}", disc=disc))), } } } @@ -1377,7 +1377,7 @@ impl BpsCentsPercentilesRatioSatsSmaStdUsdPattern { ratio: MetricPattern1::new(client.clone(), _m(&acc, "ratio")), sats: MetricPattern1::new(client.clone(), _m(&acc, "sats")), sma: _1m1w1y2y4yAllPattern::new(client.clone(), _m(&acc, "ratio_sma")), - std_dev: _1y2y4yAllPattern::new(client.clone(), _m(&acc, "ratio")), + std_dev: _1y2y4yAllPattern::new(client.clone(), acc.clone()), usd: MetricPattern1::new(client.clone(), acc.clone()), } } @@ -1531,12 +1531,12 @@ impl CapLossMvrvNetPriceProfitSoprPattern { pub fn new(client: Arc, acc: String) -> Self { Self { cap: CentsDeltaUsdPattern::new(client.clone(), _m(&acc, "realized_cap")), - loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone()), + loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone(), String::new()), mvrv: MetricPattern1::new(client.clone(), _m(&acc, "mvrv")), net_pnl: BaseCumulativeDeltaSumPattern::new(client.clone(), _m(&acc, "net_realized_pnl")), price: BpsCentsRatioSatsUsdPattern::new(client.clone(), _m(&acc, "realized_price")), profit: BaseCumulativeSumPattern3::new(client.clone(), _m(&acc, "realized_profit")), - sopr: RatioValuePattern::new(client.clone(), acc.clone()), + sopr: RatioValuePattern::new(client.clone(), _m(&acc, "sopr_24h")), } } } @@ -1773,12 +1773,12 @@ impl Pct1Pct2Pct5Pct95Pct98Pct99Pattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - pct1: BpsPriceRatioPattern::new(client.clone(), acc.clone()), - pct2: BpsPriceRatioPattern::new(client.clone(), acc.clone()), - pct5: BpsPriceRatioPattern::new(client.clone(), acc.clone()), - pct95: BpsPriceRatioPattern::new(client.clone(), acc.clone()), - pct98: BpsPriceRatioPattern::new(client.clone(), acc.clone()), - pct99: BpsPriceRatioPattern::new(client.clone(), acc.clone()), + pct1: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct1".to_string()), + pct2: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct2".to_string()), + pct5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct5".to_string()), + pct95: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct95".to_string()), + pct98: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct98".to_string()), + pct99: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99".to_string()), } } } @@ -1972,11 +1972,11 @@ impl EmaHistogramLineSignalPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - ema_fast: MetricPattern1::new(client.clone(), _m(&acc, "ema_fast_24h")), - ema_slow: MetricPattern1::new(client.clone(), _m(&acc, "ema_slow_24h")), - histogram: MetricPattern1::new(client.clone(), _m(&acc, "histogram_24h")), - line: MetricPattern1::new(client.clone(), _m(&acc, "line_24h")), - signal: MetricPattern1::new(client.clone(), _m(&acc, "signal_24h")), + ema_fast: MetricPattern1::new(client.clone(), format!("{acc}_ema_fast")), + ema_slow: MetricPattern1::new(client.clone(), format!("{acc}_ema_slow")), + histogram: MetricPattern1::new(client.clone(), format!("{acc}_histogram")), + line: MetricPattern1::new(client.clone(), format!("{acc}_line")), + signal: MetricPattern1::new(client.clone(), format!("{acc}_signal")), } } } @@ -2018,9 +2018,9 @@ impl MvrvNuplRealizedSupplyPattern { Self { mvrv: MetricPattern1::new(client.clone(), _m(&acc, "mvrv")), nupl: BpsRatioPattern::new(client.clone(), _m(&acc, "nupl")), - realized_cap: AllSthPattern::new(client.clone(), acc.clone()), + realized_cap: AllSthPattern::new(client.clone(), _m(&acc, "realized_cap")), realized_price: BpsCentsRatioSatsUsdPattern::new(client.clone(), _m(&acc, "realized_price")), - supply: AllSthPattern2::new(client.clone(), acc.clone()), + supply: AllSthPattern2::new(client.clone(), _m(&acc, "supply")), } } } @@ -2201,10 +2201,10 @@ impl _1y2y4yAllPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - _1y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone()), - _2y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone()), - _4y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone()), - all: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone()), + _1y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone(), "1y".to_string()), + _2y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone(), "2y".to_string()), + _4y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone(), "4y".to_string()), + all: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern::new(client.clone(), acc.clone(), String::new()), } } } @@ -2259,12 +2259,12 @@ pub struct BaseCumulativeNegativeSumPattern { impl BaseCumulativeNegativeSumPattern { /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { + pub fn new(client: Arc, acc: String, disc: String) -> Self { Self { - base: CentsUsdPattern2::new(client.clone(), _m(&acc, "unrealized_loss")), - cumulative: CentsUsdPattern2::new(client.clone(), _m(&acc, "unrealized_loss_cumulative")), - negative: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss")), - sum: _1m1w1y24hPattern4::new(client.clone(), _m(&acc, "unrealized_loss_sum")), + base: CentsUsdPattern2::new(client.clone(), _m(&acc, &disc)), + cumulative: CentsUsdPattern2::new(client.clone(), _m(&acc, &format!("{disc}_cumulative", disc=disc))), + negative: MetricPattern1::new(client.clone(), _m(&acc, &format!("neg_{disc}", disc=disc))), + sum: _1m1w1y24hPattern4::new(client.clone(), _m(&acc, &format!("{disc}_sum", disc=disc))), } } } @@ -2381,7 +2381,7 @@ impl LossNetNuplProfitPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone()), + loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone(), String::new()), net_pnl: CentsUsdPattern::new(client.clone(), _m(&acc, "net_unrealized_pnl")), nupl: BpsRatioPattern::new(client.clone(), _m(&acc, "nupl")), profit: BaseCumulativeSumPattern3::new(client.clone(), _m(&acc, "unrealized_profit")), @@ -2584,11 +2584,11 @@ pub struct BpsPriceRatioPattern { impl BpsPriceRatioPattern { /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { + pub fn new(client: Arc, acc: String, disc: String) -> Self { Self { - bps: MetricPattern1::new(client.clone(), _m(&acc, "ratio_pct99_bps")), - price: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct99")), - ratio: MetricPattern1::new(client.clone(), _m(&acc, "ratio_pct99")), + bps: MetricPattern1::new(client.clone(), _m(&acc, &format!("ratio_{disc}_bps", disc=disc))), + price: CentsSatsUsdPattern::new(client.clone(), _m(&acc, &disc)), + ratio: MetricPattern1::new(client.clone(), _m(&acc, &format!("ratio_{disc}", disc=disc))), } } } @@ -2730,7 +2730,7 @@ impl LossNuplProfitPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone()), + loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone(), String::new()), nupl: BpsRatioPattern::new(client.clone(), _m(&acc, "nupl")), profit: BaseCumulativeSumPattern3::new(client.clone(), _m(&acc, "unrealized_profit")), } @@ -2766,9 +2766,9 @@ impl RatioValuePattern2 { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "asopr")), - value_created: BaseCumulativeSumPattern::new(client.clone(), _m(&acc, "adj_value_created")), - value_destroyed: BaseCumulativeSumPattern::new(client.clone(), _m(&acc, "adj_value_destroyed")), + ratio: _1m1w1y24hPattern::new(client.clone(), format!("{acc}_ratio")), + value_created: BaseCumulativeSumPattern::new(client.clone(), format!("{acc}_value_created")), + value_destroyed: BaseCumulativeSumPattern::new(client.clone(), format!("{acc}_value_destroyed")), } } } @@ -2838,7 +2838,7 @@ impl AbsoluteRatePattern { pub fn new(client: Arc, acc: String) -> Self { Self { absolute: _1m1w1y24hPattern::new(client.clone(), acc.clone()), - rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()), + rate: _1m1w1y24hPattern2::new(client.clone(), _m(&acc, "rate")), } } } @@ -2854,7 +2854,7 @@ impl AbsoluteRatePattern2 { pub fn new(client: Arc, acc: String) -> Self { Self { absolute: _1m1w1y24hPattern3::new(client.clone(), acc.clone()), - rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()), + rate: _1m1w1y24hPattern2::new(client.clone(), _m(&acc, "rate")), } } } @@ -3020,17 +3020,17 @@ impl InPattern { } /// Pattern struct for repeated tree structure. -pub struct PriceValuePattern { +pub struct PriceRatioPattern { pub price: CentsSatsUsdPattern, - pub value: MetricPattern1, + pub ratio: MetricPattern1, } -impl PriceValuePattern { +impl PriceRatioPattern { /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { + pub fn new(client: Arc, acc: String, disc: String) -> Self { Self { - price: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "p3sd_4y")), - value: MetricPattern1::new(client.clone(), _m(&acc, "ratio_p3sd_4y")), + price: CentsSatsUsdPattern::new(client.clone(), _m(&acc, &disc)), + ratio: MetricPattern1::new(client.clone(), _m(&acc, &format!("ratio_{disc}", disc=disc))), } } } @@ -3061,8 +3061,8 @@ impl SdSmaPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - sd: MetricPattern1::new(client.clone(), _m(&acc, "sd_1y")), - sma: MetricPattern1::new(client.clone(), _m(&acc, "sma_1y")), + sd: MetricPattern1::new(client.clone(), format!("{acc}_sd")), + sma: MetricPattern1::new(client.clone(), format!("{acc}_sma")), } } } @@ -5338,7 +5338,7 @@ impl MetricsTree_Market_Returns_Periods { pub struct MetricsTree_Market_Returns_Sd24h { pub _1w: MetricsTree_Market_Returns_Sd24h_1w, pub _1m: MetricsTree_Market_Returns_Sd24h_1m, - pub _1y: SdSmaPattern, + pub _1y: MetricsTree_Market_Returns_Sd24h_1y, } impl MetricsTree_Market_Returns_Sd24h { @@ -5346,7 +5346,7 @@ impl MetricsTree_Market_Returns_Sd24h { Self { _1w: MetricsTree_Market_Returns_Sd24h_1w::new(client.clone(), format!("{base_path}_1w")), _1m: MetricsTree_Market_Returns_Sd24h_1m::new(client.clone(), format!("{base_path}_1m")), - _1y: SdSmaPattern::new(client.clone(), "price_return_24h".to_string()), + _1y: MetricsTree_Market_Returns_Sd24h_1y::new(client.clone(), format!("{base_path}_1y")), } } } @@ -5381,6 +5381,21 @@ impl MetricsTree_Market_Returns_Sd24h_1m { } } +/// Metrics tree node. +pub struct MetricsTree_Market_Returns_Sd24h_1y { + pub sma: MetricPattern1, + pub sd: MetricPattern1, +} + +impl MetricsTree_Market_Returns_Sd24h_1y { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + sma: MetricPattern1::new(client.clone(), "price_return_24h_sma_1y".to_string()), + sd: MetricPattern1::new(client.clone(), "price_return_24h_sd_1y".to_string()), + } + } +} + /// Metrics tree node. pub struct MetricsTree_Market_Volatility { pub _1w: MetricPattern1, @@ -5484,8 +5499,8 @@ pub struct MetricsTree_Market_MovingAverage_Sma_200d { pub sats: MetricPattern1, pub bps: MetricPattern1, pub ratio: MetricPattern1, - pub x2_4: MetricsTree_Market_MovingAverage_Sma_200d_X24, - pub x0_8: MetricsTree_Market_MovingAverage_Sma_200d_X08, + pub x2_4: CentsSatsUsdPattern, + pub x0_8: CentsSatsUsdPattern, } impl MetricsTree_Market_MovingAverage_Sma_200d { @@ -5496,42 +5511,8 @@ impl MetricsTree_Market_MovingAverage_Sma_200d { sats: MetricPattern1::new(client.clone(), "price_sma_200d_sats".to_string()), bps: MetricPattern1::new(client.clone(), "price_sma_200d_ratio_bps".to_string()), ratio: MetricPattern1::new(client.clone(), "price_sma_200d_ratio".to_string()), - x2_4: MetricsTree_Market_MovingAverage_Sma_200d_X24::new(client.clone(), format!("{base_path}_x2_4")), - x0_8: MetricsTree_Market_MovingAverage_Sma_200d_X08::new(client.clone(), format!("{base_path}_x0_8")), - } - } -} - -/// Metrics tree node. -pub struct MetricsTree_Market_MovingAverage_Sma_200d_X24 { - pub usd: MetricPattern1, - pub cents: MetricPattern1, - pub sats: MetricPattern1, -} - -impl MetricsTree_Market_MovingAverage_Sma_200d_X24 { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - usd: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_usd".to_string()), - cents: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_cents".to_string()), - sats: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_sats".to_string()), - } - } -} - -/// Metrics tree node. -pub struct MetricsTree_Market_MovingAverage_Sma_200d_X08 { - pub usd: MetricPattern1, - pub cents: MetricPattern1, - pub sats: MetricPattern1, -} - -impl MetricsTree_Market_MovingAverage_Sma_200d_X08 { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - usd: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_usd".to_string()), - cents: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_cents".to_string()), - sats: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_sats".to_string()), + x2_4: CentsSatsUsdPattern::new(client.clone(), "price_sma_200d_x2_4".to_string()), + x0_8: CentsSatsUsdPattern::new(client.clone(), "price_sma_200d_x0_8".to_string()), } } } @@ -5543,7 +5524,7 @@ pub struct MetricsTree_Market_MovingAverage_Sma_350d { pub sats: MetricPattern1, pub bps: MetricPattern1, pub ratio: MetricPattern1, - pub x2: MetricsTree_Market_MovingAverage_Sma_350d_X2, + pub x2: CentsSatsUsdPattern, } impl MetricsTree_Market_MovingAverage_Sma_350d { @@ -5554,24 +5535,7 @@ impl MetricsTree_Market_MovingAverage_Sma_350d { sats: MetricPattern1::new(client.clone(), "price_sma_350d_sats".to_string()), bps: MetricPattern1::new(client.clone(), "price_sma_350d_ratio_bps".to_string()), ratio: MetricPattern1::new(client.clone(), "price_sma_350d_ratio".to_string()), - x2: MetricsTree_Market_MovingAverage_Sma_350d_X2::new(client.clone(), format!("{base_path}_x2")), - } - } -} - -/// Metrics tree node. -pub struct MetricsTree_Market_MovingAverage_Sma_350d_X2 { - pub usd: MetricPattern1, - pub cents: MetricPattern1, - pub sats: MetricPattern1, -} - -impl MetricsTree_Market_MovingAverage_Sma_350d_X2 { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - usd: MetricPattern1::new(client.clone(), "price_sma_350d_x2_usd".to_string()), - cents: MetricPattern1::new(client.clone(), "price_sma_350d_x2_cents".to_string()), - sats: MetricPattern1::new(client.clone(), "price_sma_350d_x2_sats".to_string()), + x2: CentsSatsUsdPattern::new(client.clone(), "price_sma_350d_x2".to_string()), } } } @@ -5840,118 +5804,25 @@ impl MetricsTree_Market_Technical { /// Metrics tree node. pub struct MetricsTree_Market_Technical_Rsi { pub _24h: AverageGainsLossesRsiStochPattern, - pub _1w: MetricsTree_Market_Technical_Rsi_1w, - pub _1m: MetricsTree_Market_Technical_Rsi_1m, - pub _1y: MetricsTree_Market_Technical_Rsi_1y, + pub _1w: AverageGainsLossesRsiStochPattern, + pub _1m: AverageGainsLossesRsiStochPattern, + pub _1y: AverageGainsLossesRsiStochPattern, } impl MetricsTree_Market_Technical_Rsi { pub fn new(client: Arc, base_path: String) -> Self { Self { - _24h: AverageGainsLossesRsiStochPattern::new(client.clone(), "rsi".to_string()), - _1w: MetricsTree_Market_Technical_Rsi_1w::new(client.clone(), format!("{base_path}_1w")), - _1m: MetricsTree_Market_Technical_Rsi_1m::new(client.clone(), format!("{base_path}_1m")), - _1y: MetricsTree_Market_Technical_Rsi_1y::new(client.clone(), format!("{base_path}_1y")), - } - } -} - -/// Metrics tree node. -pub struct MetricsTree_Market_Technical_Rsi_1w { - pub gains: MetricPattern1, - pub losses: MetricPattern1, - pub average_gain: MetricPattern1, - pub average_loss: MetricPattern1, - pub rsi: BpsPercentRatioPattern3, - pub rsi_min: BpsPercentRatioPattern3, - pub rsi_max: BpsPercentRatioPattern3, - pub stoch_rsi: BpsPercentRatioPattern3, - pub stoch_rsi_k: BpsPercentRatioPattern3, - pub stoch_rsi_d: BpsPercentRatioPattern3, -} - -impl MetricsTree_Market_Technical_Rsi_1w { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - gains: MetricPattern1::new(client.clone(), "rsi_gains_1w".to_string()), - losses: MetricPattern1::new(client.clone(), "rsi_losses_1w".to_string()), - average_gain: MetricPattern1::new(client.clone(), "rsi_average_gain_1w".to_string()), - average_loss: MetricPattern1::new(client.clone(), "rsi_average_loss_1w".to_string()), - rsi: BpsPercentRatioPattern3::new(client.clone(), "rsi_1w".to_string()), - rsi_min: BpsPercentRatioPattern3::new(client.clone(), "rsi_min_1w".to_string()), - rsi_max: BpsPercentRatioPattern3::new(client.clone(), "rsi_max_1w".to_string()), - stoch_rsi: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_1w".to_string()), - stoch_rsi_k: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_k_1w".to_string()), - stoch_rsi_d: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_d_1w".to_string()), - } - } -} - -/// Metrics tree node. -pub struct MetricsTree_Market_Technical_Rsi_1m { - pub gains: MetricPattern1, - pub losses: MetricPattern1, - pub average_gain: MetricPattern1, - pub average_loss: MetricPattern1, - pub rsi: BpsPercentRatioPattern3, - pub rsi_min: BpsPercentRatioPattern3, - pub rsi_max: BpsPercentRatioPattern3, - pub stoch_rsi: BpsPercentRatioPattern3, - pub stoch_rsi_k: BpsPercentRatioPattern3, - pub stoch_rsi_d: BpsPercentRatioPattern3, -} - -impl MetricsTree_Market_Technical_Rsi_1m { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - gains: MetricPattern1::new(client.clone(), "rsi_gains_1m".to_string()), - losses: MetricPattern1::new(client.clone(), "rsi_losses_1m".to_string()), - average_gain: MetricPattern1::new(client.clone(), "rsi_average_gain_1m".to_string()), - average_loss: MetricPattern1::new(client.clone(), "rsi_average_loss_1m".to_string()), - rsi: BpsPercentRatioPattern3::new(client.clone(), "rsi_1m".to_string()), - rsi_min: BpsPercentRatioPattern3::new(client.clone(), "rsi_min_1m".to_string()), - rsi_max: BpsPercentRatioPattern3::new(client.clone(), "rsi_max_1m".to_string()), - stoch_rsi: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_1m".to_string()), - stoch_rsi_k: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_k_1m".to_string()), - stoch_rsi_d: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_d_1m".to_string()), - } - } -} - -/// Metrics tree node. -pub struct MetricsTree_Market_Technical_Rsi_1y { - pub gains: MetricPattern1, - pub losses: MetricPattern1, - pub average_gain: MetricPattern1, - pub average_loss: MetricPattern1, - pub rsi: BpsPercentRatioPattern3, - pub rsi_min: BpsPercentRatioPattern3, - pub rsi_max: BpsPercentRatioPattern3, - pub stoch_rsi: BpsPercentRatioPattern3, - pub stoch_rsi_k: BpsPercentRatioPattern3, - pub stoch_rsi_d: BpsPercentRatioPattern3, -} - -impl MetricsTree_Market_Technical_Rsi_1y { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - gains: MetricPattern1::new(client.clone(), "rsi_gains_1y".to_string()), - losses: MetricPattern1::new(client.clone(), "rsi_losses_1y".to_string()), - average_gain: MetricPattern1::new(client.clone(), "rsi_average_gain_1y".to_string()), - average_loss: MetricPattern1::new(client.clone(), "rsi_average_loss_1y".to_string()), - rsi: BpsPercentRatioPattern3::new(client.clone(), "rsi_1y".to_string()), - rsi_min: BpsPercentRatioPattern3::new(client.clone(), "rsi_min_1y".to_string()), - rsi_max: BpsPercentRatioPattern3::new(client.clone(), "rsi_max_1y".to_string()), - stoch_rsi: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_1y".to_string()), - stoch_rsi_k: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_k_1y".to_string()), - stoch_rsi_d: BpsPercentRatioPattern3::new(client.clone(), "rsi_stoch_d_1y".to_string()), + _24h: AverageGainsLossesRsiStochPattern::new(client.clone(), "rsi".to_string(), "24h".to_string().to_string()), + _1w: AverageGainsLossesRsiStochPattern::new(client.clone(), "rsi".to_string(), "1w".to_string().to_string()), + _1m: AverageGainsLossesRsiStochPattern::new(client.clone(), "rsi".to_string(), "1m".to_string().to_string()), + _1y: AverageGainsLossesRsiStochPattern::new(client.clone(), "rsi".to_string(), "1y".to_string().to_string()), } } } /// Metrics tree node. pub struct MetricsTree_Market_Technical_Macd { - pub _24h: EmaHistogramLineSignalPattern, + pub _24h: MetricsTree_Market_Technical_Macd_24h, pub _1w: MetricsTree_Market_Technical_Macd_1w, pub _1m: MetricsTree_Market_Technical_Macd_1m, pub _1y: MetricsTree_Market_Technical_Macd_1y, @@ -5960,7 +5831,7 @@ pub struct MetricsTree_Market_Technical_Macd { impl MetricsTree_Market_Technical_Macd { pub fn new(client: Arc, base_path: String) -> Self { Self { - _24h: EmaHistogramLineSignalPattern::new(client.clone(), "macd".to_string()), + _24h: MetricsTree_Market_Technical_Macd_24h::new(client.clone(), format!("{base_path}_24h")), _1w: MetricsTree_Market_Technical_Macd_1w::new(client.clone(), format!("{base_path}_1w")), _1m: MetricsTree_Market_Technical_Macd_1m::new(client.clone(), format!("{base_path}_1m")), _1y: MetricsTree_Market_Technical_Macd_1y::new(client.clone(), format!("{base_path}_1y")), @@ -5968,6 +5839,27 @@ impl MetricsTree_Market_Technical_Macd { } } +/// Metrics tree node. +pub struct MetricsTree_Market_Technical_Macd_24h { + pub ema_fast: MetricPattern1, + pub ema_slow: MetricPattern1, + pub line: MetricPattern1, + pub signal: MetricPattern1, + pub histogram: MetricPattern1, +} + +impl MetricsTree_Market_Technical_Macd_24h { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + ema_fast: MetricPattern1::new(client.clone(), "macd_ema_fast_24h".to_string()), + ema_slow: MetricPattern1::new(client.clone(), "macd_ema_slow_24h".to_string()), + line: MetricPattern1::new(client.clone(), "macd_line_24h".to_string()), + signal: MetricPattern1::new(client.clone(), "macd_signal_24h".to_string()), + histogram: MetricPattern1::new(client.clone(), "macd_histogram_24h".to_string()), + } + } +} + /// Metrics tree node. pub struct MetricsTree_Market_Technical_Macd_1w { pub ema_fast: MetricPattern1, @@ -6578,7 +6470,7 @@ pub struct MetricsTree_Cohorts_Utxo_All { pub supply: MetricsTree_Cohorts_Utxo_All_Supply, pub outputs: UnspentPattern, pub activity: CoindaysCoinyearsDormancySentPattern, - pub realized: CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern, + pub realized: MetricsTree_Cohorts_Utxo_All_Realized, pub cost_basis: InvestedMaxMinPercentilesSupplyPattern, pub unrealized: MetricsTree_Cohorts_Utxo_All_Unrealized, } @@ -6589,7 +6481,7 @@ impl MetricsTree_Cohorts_Utxo_All { supply: MetricsTree_Cohorts_Utxo_All_Supply::new(client.clone(), format!("{base_path}_supply")), outputs: UnspentPattern::new(client.clone(), "utxo_count".to_string()), activity: CoindaysCoinyearsDormancySentPattern::new(client.clone(), "".to_string()), - realized: CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern::new(client.clone(), "".to_string()), + realized: MetricsTree_Cohorts_Utxo_All_Realized::new(client.clone(), format!("{base_path}_realized")), cost_basis: InvestedMaxMinPercentilesSupplyPattern::new(client.clone(), "".to_string()), unrealized: MetricsTree_Cohorts_Utxo_All_Unrealized::new(client.clone(), format!("{base_path}_unrealized")), } @@ -6617,6 +6509,41 @@ impl MetricsTree_Cohorts_Utxo_All_Supply { } } +/// Metrics tree node. +pub struct MetricsTree_Cohorts_Utxo_All_Realized { + pub cap: CentsDeltaRelUsdPattern, + pub profit: BaseCumulativeDistributionRelSumValuePattern, + pub loss: BaseCapitulationCumulativeNegativeRelSumValuePattern, + pub price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern, + pub mvrv: MetricPattern1, + pub sopr: AdjustedRatioValuePattern, + pub net_pnl: BaseChangeCumulativeDeltaRelSumPattern, + pub gross_pnl: BaseCumulativeSumPattern3, + pub sell_side_risk_ratio: _1m1w1y24hPattern6, + pub peak_regret: BaseCumulativeRelPattern, + pub investor: LowerPriceUpperPattern, + pub profit_to_loss_ratio: _1m1w1y24hPattern, +} + +impl MetricsTree_Cohorts_Utxo_All_Realized { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + cap: CentsDeltaRelUsdPattern::new(client.clone(), "realized_cap".to_string()), + profit: BaseCumulativeDistributionRelSumValuePattern::new(client.clone(), "".to_string()), + loss: BaseCapitulationCumulativeNegativeRelSumValuePattern::new(client.clone(), "".to_string()), + price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern::new(client.clone(), "realized_price".to_string()), + mvrv: MetricPattern1::new(client.clone(), "mvrv".to_string()), + sopr: AdjustedRatioValuePattern::new(client.clone(), "".to_string()), + net_pnl: BaseChangeCumulativeDeltaRelSumPattern::new(client.clone(), "net".to_string()), + gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), "realized_gross_pnl".to_string()), + sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), "sell_side_risk_ratio".to_string()), + peak_regret: BaseCumulativeRelPattern::new(client.clone(), "realized_peak_regret".to_string()), + investor: LowerPriceUpperPattern::new(client.clone(), "".to_string()), + profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "realized_profit_to_loss_ratio".to_string()), + } + } +} + /// Metrics tree node. pub struct MetricsTree_Cohorts_Utxo_All_Unrealized { pub nupl: BpsRatioPattern, @@ -6708,7 +6635,7 @@ pub struct MetricsTree_Cohorts_Utxo_Sth { pub supply: DeltaHalfInRelTotalPattern2, pub outputs: UnspentPattern, pub activity: CoindaysCoinyearsDormancySentPattern, - pub realized: CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern, + pub realized: MetricsTree_Cohorts_Utxo_Sth_Realized, pub cost_basis: InvestedMaxMinPercentilesSupplyPattern, pub unrealized: GrossInvestedLossNetNuplProfitSentimentPattern2, } @@ -6719,13 +6646,48 @@ impl MetricsTree_Cohorts_Utxo_Sth { supply: DeltaHalfInRelTotalPattern2::new(client.clone(), "sth_supply".to_string()), outputs: UnspentPattern::new(client.clone(), "sth_utxo_count".to_string()), activity: CoindaysCoinyearsDormancySentPattern::new(client.clone(), "sth".to_string()), - realized: CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern::new(client.clone(), "sth".to_string()), + realized: MetricsTree_Cohorts_Utxo_Sth_Realized::new(client.clone(), format!("{base_path}_realized")), cost_basis: InvestedMaxMinPercentilesSupplyPattern::new(client.clone(), "sth".to_string()), unrealized: GrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "sth".to_string()), } } } +/// Metrics tree node. +pub struct MetricsTree_Cohorts_Utxo_Sth_Realized { + pub cap: CentsDeltaRelUsdPattern, + pub profit: BaseCumulativeDistributionRelSumValuePattern, + pub loss: BaseCapitulationCumulativeNegativeRelSumValuePattern, + pub price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern, + pub mvrv: MetricPattern1, + pub sopr: AdjustedRatioValuePattern, + pub net_pnl: BaseChangeCumulativeDeltaRelSumPattern, + pub gross_pnl: BaseCumulativeSumPattern3, + pub sell_side_risk_ratio: _1m1w1y24hPattern6, + pub peak_regret: BaseCumulativeRelPattern, + pub investor: LowerPriceUpperPattern, + pub profit_to_loss_ratio: _1m1w1y24hPattern, +} + +impl MetricsTree_Cohorts_Utxo_Sth_Realized { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + cap: CentsDeltaRelUsdPattern::new(client.clone(), "sth_realized_cap".to_string()), + profit: BaseCumulativeDistributionRelSumValuePattern::new(client.clone(), "sth".to_string()), + loss: BaseCapitulationCumulativeNegativeRelSumValuePattern::new(client.clone(), "sth".to_string()), + price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern::new(client.clone(), "sth_realized_price".to_string()), + mvrv: MetricPattern1::new(client.clone(), "sth_mvrv".to_string()), + sopr: AdjustedRatioValuePattern::new(client.clone(), "sth".to_string()), + net_pnl: BaseChangeCumulativeDeltaRelSumPattern::new(client.clone(), "sth_net".to_string()), + gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), "sth_realized_gross_pnl".to_string()), + sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), "sth_sell_side_risk_ratio".to_string()), + peak_regret: BaseCumulativeRelPattern::new(client.clone(), "sth_realized_peak_regret".to_string()), + investor: LowerPriceUpperPattern::new(client.clone(), "sth".to_string()), + profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "sth_realized_profit_to_loss_ratio".to_string()), + } + } +} + /// Metrics tree node. pub struct MetricsTree_Cohorts_Utxo_Lth { pub supply: DeltaHalfInRelTotalPattern2, diff --git a/crates/brk_computer/src/internal/per_block/price.rs b/crates/brk_computer/src/internal/per_block/price.rs index d3a854a64..aa03cc6db 100644 --- a/crates/brk_computer/src/internal/per_block/price.rs +++ b/crates/brk_computer/src/internal/per_block/price.rs @@ -10,7 +10,7 @@ use brk_types::{Cents, Dollars, SatsFract, Version}; use schemars::JsonSchema; use vecdb::{Database, ReadableCloneableVec, UnaryTransform}; -use super::{PerBlock, LazyPerBlock}; +use super::{LazyPerBlock, PerBlock}; use crate::{ indexes, internal::{CentsUnsignedToDollars, ComputedVecValue, DollarsToSatsFract, NumericValue}, @@ -32,8 +32,7 @@ impl Price> { version: Version, indexes: &indexes::Vecs, ) -> Result { - let cents = - PerBlock::forced_import(db, &format!("{name}_cents"), version, indexes)?; + let cents = PerBlock::forced_import(db, &format!("{name}_cents"), version, indexes)?; let usd = LazyPerBlock::from_computed::( name, version, @@ -65,11 +64,7 @@ where source.height.read_only_boxed_clone(), source, ); - let usd = LazyPerBlock::from_lazy::( - &format!("{name}_usd"), - version, - ¢s, - ); + let usd = LazyPerBlock::from_lazy::(name, version, ¢s); let sats = LazyPerBlock::from_lazy::( &format!("{name}_sats"), version, diff --git a/crates/brk_computer/src/internal/per_block/ratio/std_dev_bands.rs b/crates/brk_computer/src/internal/per_block/ratio/std_dev_bands.rs index cce22bdd6..db626f0f1 100644 --- a/crates/brk_computer/src/internal/per_block/ratio/std_dev_bands.rs +++ b/crates/brk_computer/src/internal/per_block/ratio/std_dev_bands.rs @@ -27,10 +27,10 @@ impl RatioPerBlockStdDevBands { let v = version + VERSION; macro_rules! import_sd { - ($suffix:expr, $period:expr, $days:expr) => { + ($period:expr, $days:expr) => { StdDevPerBlockExtended::forced_import( db, - &format!("{name}_{}", $suffix), + name, $period, $days, v, @@ -40,10 +40,10 @@ impl RatioPerBlockStdDevBands { } Ok(Self { - all: import_sd!("ratio", "", usize::MAX), - _1y: import_sd!("ratio", "1y", 365), - _2y: import_sd!("ratio", "2y", 2 * 365), - _4y: import_sd!("ratio", "4y", 4 * 365), + all: import_sd!("", usize::MAX), + _1y: import_sd!("1y", 365), + _2y: import_sd!("2y", 2 * 365), + _4y: import_sd!("4y", 4 * 365), }) } diff --git a/crates/brk_computer/src/internal/per_block/stddev/extended.rs b/crates/brk_computer/src/internal/per_block/stddev/extended.rs index 21426dc4a..942c89821 100644 --- a/crates/brk_computer/src/internal/per_block/stddev/extended.rs +++ b/crates/brk_computer/src/internal/per_block/stddev/extended.rs @@ -8,13 +8,13 @@ use vecdb::{ use crate::{ blocks, indexes, - internal::{PerBlock, Price, PriceTimesRatioCents}, + internal::{PerBlock, Price, PriceTimesRatioCents, per_block::stddev::period_suffix}, }; #[derive(Traversable)] pub struct StdDevBand { #[traversable(flatten)] - pub value: PerBlock, + pub ratio: PerBlock, pub price: Price>, } @@ -49,16 +49,11 @@ impl StdDevPerBlockExtended { indexes: &indexes::Vecs, ) -> Result { let version = parent_version + Version::TWO; - let p = super::period_suffix(period); + let p = period_suffix(period); macro_rules! import { ($suffix:expr) => { - PerBlock::forced_import( - db, - &format!("{name}_{}{p}", $suffix), - version, - indexes, - )? + PerBlock::forced_import(db, &format!("{name}_{}{p}", $suffix), version, indexes)? }; } @@ -69,18 +64,18 @@ impl StdDevPerBlockExtended { } macro_rules! import_band { - ($suffix:expr) => { + ($suffix:expr) => {{ StdDevBand { - value: import!(concat!("ratio_", $suffix)), + ratio: import!(concat!("ratio_", $suffix)), price: import_price!($suffix), } - }; + }}; } Ok(Self { days, - sd: import!("sd"), - zscore: import!("zscore"), + sd: import!("ratio_sd"), + zscore: import!("ratio_zscore"), _0sd: import_price!("0sd"), p0_5sd: import_band!("p0_5sd"), p1sd: import_band!("p1sd"), @@ -106,12 +101,9 @@ impl StdDevPerBlockExtended { sma: &impl ReadableVec, ) -> Result<()> { if self.days == usize::MAX { - self.sd.height.compute_expanding_sd( - starting_indexes.height, - source, - sma, - exit, - )?; + self.sd + .height + .compute_expanding_sd(starting_indexes.height, source, sma, exit)?; } else { let window_starts = blocks.lookback.start_vec(self.days); self.sd.height.compute_rolling_sd( @@ -154,10 +146,7 @@ impl StdDevPerBlockExtended { let source_data = source.collect_range_at(start, source_len); let sma_data = sma.collect_range_at(start, sma.len()); - let sd_data = self - .sd - .height - .collect_range_at(start, self.sd.height.len()); + let sd_data = self.sd.height.collect_range_at(start, self.sd.height.len()); const MULTIPLIERS: [f32; 12] = [ 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0, @@ -208,18 +197,18 @@ impl StdDevPerBlockExtended { } compute_band_price!(&mut self._0sd, sma); - compute_band_price!(&mut self.p0_5sd.price, &self.p0_5sd.value.height); - compute_band_price!(&mut self.p1sd.price, &self.p1sd.value.height); - compute_band_price!(&mut self.p1_5sd.price, &self.p1_5sd.value.height); - compute_band_price!(&mut self.p2sd.price, &self.p2sd.value.height); - compute_band_price!(&mut self.p2_5sd.price, &self.p2_5sd.value.height); - compute_band_price!(&mut self.p3sd.price, &self.p3sd.value.height); - compute_band_price!(&mut self.m0_5sd.price, &self.m0_5sd.value.height); - compute_band_price!(&mut self.m1sd.price, &self.m1sd.value.height); - compute_band_price!(&mut self.m1_5sd.price, &self.m1_5sd.value.height); - compute_band_price!(&mut self.m2sd.price, &self.m2sd.value.height); - compute_band_price!(&mut self.m2_5sd.price, &self.m2_5sd.value.height); - compute_band_price!(&mut self.m3sd.price, &self.m3sd.value.height); + compute_band_price!(&mut self.p0_5sd.price, &self.p0_5sd.ratio.height); + compute_band_price!(&mut self.p1sd.price, &self.p1sd.ratio.height); + compute_band_price!(&mut self.p1_5sd.price, &self.p1_5sd.ratio.height); + compute_band_price!(&mut self.p2sd.price, &self.p2sd.ratio.height); + compute_band_price!(&mut self.p2_5sd.price, &self.p2_5sd.ratio.height); + compute_band_price!(&mut self.p3sd.price, &self.p3sd.ratio.height); + compute_band_price!(&mut self.m0_5sd.price, &self.m0_5sd.ratio.height); + compute_band_price!(&mut self.m1sd.price, &self.m1sd.ratio.height); + compute_band_price!(&mut self.m1_5sd.price, &self.m1_5sd.ratio.height); + compute_band_price!(&mut self.m2sd.price, &self.m2sd.ratio.height); + compute_band_price!(&mut self.m2_5sd.price, &self.m2_5sd.ratio.height); + compute_band_price!(&mut self.m3sd.price, &self.m3sd.ratio.height); Ok(()) } @@ -228,18 +217,18 @@ impl StdDevPerBlockExtended { &mut self, ) -> impl Iterator>> { [ - &mut self.p0_5sd.value.height, - &mut self.p1sd.value.height, - &mut self.p1_5sd.value.height, - &mut self.p2sd.value.height, - &mut self.p2_5sd.value.height, - &mut self.p3sd.value.height, - &mut self.m0_5sd.value.height, - &mut self.m1sd.value.height, - &mut self.m1_5sd.value.height, - &mut self.m2sd.value.height, - &mut self.m2_5sd.value.height, - &mut self.m3sd.value.height, + &mut self.p0_5sd.ratio.height, + &mut self.p1sd.ratio.height, + &mut self.p1_5sd.ratio.height, + &mut self.p2sd.ratio.height, + &mut self.p2_5sd.ratio.height, + &mut self.p3sd.ratio.height, + &mut self.m0_5sd.ratio.height, + &mut self.m1sd.ratio.height, + &mut self.m1_5sd.ratio.height, + &mut self.m2sd.ratio.height, + &mut self.m2_5sd.ratio.height, + &mut self.m3sd.ratio.height, ] .into_iter() } diff --git a/crates/brk_server/examples/bindgen.rs b/crates/brk_server/examples/bindgen.rs index 79f713c55..286b208ca 100644 --- a/crates/brk_server/examples/bindgen.rs +++ b/crates/brk_server/examples/bindgen.rs @@ -25,6 +25,7 @@ pub fn main() -> color_eyre::Result<()> { .to_path_buf(); let output_paths = brk_bindgen::ClientOutputPaths::new() + .rust(workspace_root.join("crates/brk_client/src/lib.rs")) .javascript(workspace_root.join("website/scripts/modules/brk-client/index.js")); generate_bindings(&vecs, &openapi, &output_paths)?; diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 8116fa2d8..6ec219943 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1656,18 +1656,18 @@ function createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65 /** * @typedef {Object} _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern * @property {CentsSatsUsdPattern} _0sd - * @property {PriceValuePattern} m05sd - * @property {PriceValuePattern} m15sd - * @property {PriceValuePattern} m1sd - * @property {PriceValuePattern} m25sd - * @property {PriceValuePattern} m2sd - * @property {PriceValuePattern} m3sd - * @property {PriceValuePattern} p05sd - * @property {PriceValuePattern} p15sd - * @property {PriceValuePattern} p1sd - * @property {PriceValuePattern} p25sd - * @property {PriceValuePattern} p2sd - * @property {PriceValuePattern} p3sd + * @property {PriceRatioPattern} m05sd + * @property {PriceRatioPattern} m15sd + * @property {PriceRatioPattern} m1sd + * @property {PriceRatioPattern} m25sd + * @property {PriceRatioPattern} m2sd + * @property {PriceRatioPattern} m3sd + * @property {PriceRatioPattern} p05sd + * @property {PriceRatioPattern} p15sd + * @property {PriceRatioPattern} p1sd + * @property {PriceRatioPattern} p25sd + * @property {PriceRatioPattern} p2sd + * @property {PriceRatioPattern} p3sd * @property {MetricPattern1} sd * @property {MetricPattern1} zscore */ @@ -1676,25 +1676,26 @@ function createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65 * Create a _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name + * @param {string} disc - Discriminator suffix * @returns {_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern} */ -function create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc) { +function create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, disc) { return { - _0sd: createCentsSatsUsdPattern(client, _m(acc, '0sd_4y')), - m05sd: createPriceValuePattern(client, acc), - m15sd: createPriceValuePattern(client, acc), - m1sd: createPriceValuePattern(client, acc), - m25sd: createPriceValuePattern(client, acc), - m2sd: createPriceValuePattern(client, acc), - m3sd: createPriceValuePattern(client, acc), - p05sd: createPriceValuePattern(client, acc), - p15sd: createPriceValuePattern(client, acc), - p1sd: createPriceValuePattern(client, acc), - p25sd: createPriceValuePattern(client, acc), - p2sd: createPriceValuePattern(client, acc), - p3sd: createPriceValuePattern(client, acc), - sd: createMetricPattern1(client, _m(acc, 'sd_4y')), - zscore: createMetricPattern1(client, _m(acc, 'zscore_4y')), + _0sd: createCentsSatsUsdPattern(client, _m(_m(acc, '0sd'), disc)), + m05sd: createPriceRatioPattern(client, acc, _m('m0_5sd', disc)), + m15sd: createPriceRatioPattern(client, acc, _m('m1_5sd', disc)), + m1sd: createPriceRatioPattern(client, acc, _m('m1sd', disc)), + m25sd: createPriceRatioPattern(client, acc, _m('m2_5sd', disc)), + m2sd: createPriceRatioPattern(client, acc, _m('m2sd', disc)), + m3sd: createPriceRatioPattern(client, acc, _m('m3sd', disc)), + p05sd: createPriceRatioPattern(client, acc, _m('p0_5sd', disc)), + p15sd: createPriceRatioPattern(client, acc, _m('p1_5sd', disc)), + p1sd: createPriceRatioPattern(client, acc, _m('p1sd', disc)), + p25sd: createPriceRatioPattern(client, acc, _m('p2_5sd', disc)), + p2sd: createPriceRatioPattern(client, acc, _m('p2sd', disc)), + p3sd: createPriceRatioPattern(client, acc, _m('p3sd', disc)), + sd: createMetricPattern1(client, _m(_m(acc, 'ratio_sd'), disc)), + zscore: createMetricPattern1(client, _m(_m(acc, 'ratio_zscore'), disc)), }; } @@ -1907,20 +1908,21 @@ function createAverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern(c * Create a AverageGainsLossesRsiStochPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name + * @param {string} disc - Discriminator suffix * @returns {AverageGainsLossesRsiStochPattern} */ -function createAverageGainsLossesRsiStochPattern(client, acc) { +function createAverageGainsLossesRsiStochPattern(client, acc, disc) { return { - averageGain: createMetricPattern1(client, _m(acc, 'average_gain_24h')), - averageLoss: createMetricPattern1(client, _m(acc, 'average_loss_24h')), - gains: createMetricPattern1(client, _m(acc, 'gains_24h')), - losses: createMetricPattern1(client, _m(acc, 'losses_24h')), - rsi: createBpsPercentRatioPattern3(client, _m(acc, '24h')), - rsiMax: createBpsPercentRatioPattern3(client, _m(acc, 'max_24h')), - rsiMin: createBpsPercentRatioPattern3(client, _m(acc, 'min_24h')), - stochRsi: createBpsPercentRatioPattern3(client, _m(acc, 'stoch_24h')), - stochRsiD: createBpsPercentRatioPattern3(client, _m(acc, 'stoch_d_24h')), - stochRsiK: createBpsPercentRatioPattern3(client, _m(acc, 'stoch_k_24h')), + averageGain: createMetricPattern1(client, _m(_m(acc, 'average_gain'), disc)), + averageLoss: createMetricPattern1(client, _m(_m(acc, 'average_loss'), disc)), + gains: createMetricPattern1(client, _m(_m(acc, 'gains'), disc)), + losses: createMetricPattern1(client, _m(_m(acc, 'losses'), disc)), + rsi: createBpsPercentRatioPattern3(client, _m(acc, disc)), + rsiMax: createBpsPercentRatioPattern3(client, _m(_m(acc, 'max'), disc)), + rsiMin: createBpsPercentRatioPattern3(client, _m(_m(acc, 'min'), disc)), + stochRsi: createBpsPercentRatioPattern3(client, _m(_m(acc, 'stoch'), disc)), + stochRsiD: createBpsPercentRatioPattern3(client, _m(_m(acc, 'stoch_d'), disc)), + stochRsiK: createBpsPercentRatioPattern3(client, _m(_m(acc, 'stoch_k'), disc)), }; } @@ -2078,7 +2080,7 @@ function createBpsCentsPercentilesRatioSatsSmaStdUsdPattern(client, acc) { ratio: createMetricPattern1(client, _m(acc, 'ratio')), sats: createMetricPattern1(client, _m(acc, 'sats')), sma: create_1m1w1y2y4yAllPattern(client, _m(acc, 'ratio_sma')), - stdDev: create_1y2y4yAllPattern(client, _m(acc, 'ratio')), + stdDev: create_1y2y4yAllPattern(client, acc), usd: createMetricPattern1(client, acc), }; } @@ -2252,7 +2254,7 @@ function createBaseCumulativeNegativeRelSumPattern2(client, acc) { function createCapLossMvrvNetPriceProfitSoprPattern(client, acc) { return { cap: createCentsDeltaUsdPattern(client, _m(acc, 'realized_cap')), - loss: createBaseCumulativeNegativeSumPattern(client, acc), + loss: createBaseCumulativeNegativeSumPattern(client, acc, ''), mvrv: createMetricPattern1(client, _m(acc, 'mvrv')), netPnl: createBaseCumulativeDeltaSumPattern(client, _m(acc, 'net_realized_pnl')), price: createBpsCentsRatioSatsUsdPattern(client, _m(acc, 'realized_price')), @@ -2524,12 +2526,12 @@ function createDeltaHalfInRelTotalPattern2(client, acc) { */ function createPct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc) { return { - pct1: createBpsPriceRatioPattern(client, acc), - pct2: createBpsPriceRatioPattern(client, acc), - pct5: createBpsPriceRatioPattern(client, acc), - pct95: createBpsPriceRatioPattern(client, acc), - pct98: createBpsPriceRatioPattern(client, acc), - pct99: createBpsPriceRatioPattern(client, acc), + pct1: createBpsPriceRatioPattern(client, acc, 'pct1'), + pct2: createBpsPriceRatioPattern(client, acc, 'pct2'), + pct5: createBpsPriceRatioPattern(client, acc, 'pct5'), + pct95: createBpsPriceRatioPattern(client, acc, 'pct95'), + pct98: createBpsPriceRatioPattern(client, acc, 'pct98'), + pct99: createBpsPriceRatioPattern(client, acc, 'pct99'), }; } @@ -2750,11 +2752,11 @@ function createDeltaHalfInTotalPattern2(client, acc) { */ function createEmaHistogramLineSignalPattern(client, acc) { return { - emaFast: createMetricPattern1(client, _m(acc, 'ema_fast_24h')), - emaSlow: createMetricPattern1(client, _m(acc, 'ema_slow_24h')), - histogram: createMetricPattern1(client, _m(acc, 'histogram_24h')), - line: createMetricPattern1(client, _m(acc, 'line_24h')), - signal: createMetricPattern1(client, _m(acc, 'signal_24h')), + emaFast: createMetricPattern1(client, `${acc}_ema_fast`), + emaSlow: createMetricPattern1(client, `${acc}_ema_slow`), + histogram: createMetricPattern1(client, `${acc}_histogram`), + line: createMetricPattern1(client, `${acc}_line`), + signal: createMetricPattern1(client, `${acc}_signal`), }; } @@ -3014,10 +3016,10 @@ function create_1m1w1y24hPattern4(client, acc) { */ function create_1y2y4yAllPattern(client, acc) { return { - _1y: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc), - _2y: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc), - _4y: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc), - all: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc), + _1y: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, ''), + _2y: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, ''), + _4y: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, ''), + all: create_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, ''), }; } @@ -3079,14 +3081,15 @@ function createBaseCumulativeDeltaSumPattern(client, acc) { * Create a BaseCumulativeNegativeSumPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name + * @param {string} disc - Discriminator suffix * @returns {BaseCumulativeNegativeSumPattern} */ -function createBaseCumulativeNegativeSumPattern(client, acc) { +function createBaseCumulativeNegativeSumPattern(client, acc, disc) { return { - base: createCentsUsdPattern2(client, _m(acc, 'unrealized_loss')), - cumulative: createCentsUsdPattern2(client, _m(acc, 'unrealized_loss_cumulative')), - negative: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss')), - sum: create_1m1w1y24hPattern4(client, _m(acc, 'unrealized_loss_sum')), + base: createCentsUsdPattern2(client, _m(acc, disc)), + cumulative: createCentsUsdPattern2(client, _m(acc, `${disc}_cumulative`)), + negative: createMetricPattern1(client, _m(_m(acc, 'neg'), disc)), + sum: create_1m1w1y24hPattern4(client, _m(acc, `${disc}_sum`)), }; } @@ -3221,7 +3224,7 @@ function createCoindaysCoinyearsDormancySentPattern(client, acc) { */ function createLossNetNuplProfitPattern(client, acc) { return { - loss: createBaseCumulativeNegativeSumPattern(client, acc), + loss: createBaseCumulativeNegativeSumPattern(client, acc, ''), netPnl: createCentsUsdPattern(client, _m(acc, 'net_unrealized_pnl')), nupl: createBpsRatioPattern(client, _m(acc, 'nupl')), profit: createBaseCumulativeSumPattern3(client, _m(acc, 'unrealized_profit')), @@ -3457,13 +3460,14 @@ function createBpsPercentRatioPattern4(client, acc) { * Create a BpsPriceRatioPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name + * @param {string} disc - Discriminator suffix * @returns {BpsPriceRatioPattern} */ -function createBpsPriceRatioPattern(client, acc) { +function createBpsPriceRatioPattern(client, acc, disc) { return { - bps: createMetricPattern1(client, _m(acc, 'ratio_pct99_bps')), - price: createCentsSatsUsdPattern(client, _m(acc, 'pct99')), - ratio: createMetricPattern1(client, _m(acc, 'ratio_pct99')), + bps: createMetricPattern1(client, _m(acc, `ratio_${disc}_bps`)), + price: createCentsSatsUsdPattern(client, _m(acc, disc)), + ratio: createMetricPattern1(client, _m(_m(acc, 'ratio'), disc)), }; } @@ -3629,7 +3633,7 @@ function createGreedNetPainPattern(client, acc) { */ function createLossNuplProfitPattern(client, acc) { return { - loss: createBaseCumulativeNegativeSumPattern(client, acc), + loss: createBaseCumulativeNegativeSumPattern(client, acc, ''), nupl: createBpsRatioPattern(client, _m(acc, 'nupl')), profit: createBaseCumulativeSumPattern3(client, _m(acc, 'unrealized_profit')), }; @@ -3671,9 +3675,9 @@ function createLowerPriceUpperPattern(client, acc) { */ function createRatioValuePattern2(client, acc) { return { - ratio: create_1m1w1y24hPattern(client, _m(acc, 'asopr')), - valueCreated: createBaseCumulativeSumPattern(client, _m(acc, 'adj_value_created')), - valueDestroyed: createBaseCumulativeSumPattern(client, _m(acc, 'adj_value_destroyed')), + ratio: create_1m1w1y24hPattern(client, `${acc}_ratio`), + valueCreated: createBaseCumulativeSumPattern(client, `${acc}_value_created`), + valueDestroyed: createBaseCumulativeSumPattern(client, `${acc}_value_destroyed`), }; } @@ -3973,21 +3977,22 @@ function createInPattern(client, acc) { } /** - * @typedef {Object} PriceValuePattern + * @typedef {Object} PriceRatioPattern * @property {CentsSatsUsdPattern} price - * @property {MetricPattern1} value + * @property {MetricPattern1} ratio */ /** - * Create a PriceValuePattern pattern node + * Create a PriceRatioPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {PriceValuePattern} + * @param {string} disc - Discriminator suffix + * @returns {PriceRatioPattern} */ -function createPriceValuePattern(client, acc) { +function createPriceRatioPattern(client, acc, disc) { return { - price: createCentsSatsUsdPattern(client, _m(acc, 'p3sd_4y')), - value: createMetricPattern1(client, _m(acc, 'ratio_p3sd_4y')), + price: createCentsSatsUsdPattern(client, _m(acc, disc)), + ratio: createMetricPattern1(client, _m(_m(acc, 'ratio'), disc)), }; } @@ -4024,8 +4029,8 @@ function createRelPattern(client, acc) { */ function createSdSmaPattern(client, acc) { return { - sd: createMetricPattern1(client, _m(acc, 'sd_1y')), - sma: createMetricPattern1(client, _m(acc, 'sma_1y')), + sd: createMetricPattern1(client, `${acc}_sd`), + sma: createMetricPattern1(client, `${acc}_sma`), }; } @@ -5051,7 +5056,7 @@ function createUnspentPattern(client, acc) { * @typedef {Object} MetricsTree_Market_Returns_Sd24h * @property {MetricsTree_Market_Returns_Sd24h_1w} _1w * @property {MetricsTree_Market_Returns_Sd24h_1m} _1m - * @property {SdSmaPattern} _1y + * @property {MetricsTree_Market_Returns_Sd24h_1y} _1y */ /** @@ -5066,6 +5071,12 @@ function createUnspentPattern(client, acc) { * @property {MetricPattern1} sd */ +/** + * @typedef {Object} MetricsTree_Market_Returns_Sd24h_1y + * @property {MetricPattern1} sma + * @property {MetricPattern1} sd + */ + /** * @typedef {Object} MetricsTree_Market_Volatility * @property {MetricPattern1} _1w @@ -5115,22 +5126,8 @@ function createUnspentPattern(client, acc) { * @property {MetricPattern1} sats * @property {MetricPattern1} bps * @property {MetricPattern1} ratio - * @property {MetricsTree_Market_MovingAverage_Sma_200d_X24} x24 - * @property {MetricsTree_Market_MovingAverage_Sma_200d_X08} x08 - */ - -/** - * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_200d_X24 - * @property {MetricPattern1} usd - * @property {MetricPattern1} cents - * @property {MetricPattern1} sats - */ - -/** - * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_200d_X08 - * @property {MetricPattern1} usd - * @property {MetricPattern1} cents - * @property {MetricPattern1} sats + * @property {CentsSatsUsdPattern} x24 + * @property {CentsSatsUsdPattern} x08 */ /** @@ -5140,14 +5137,7 @@ function createUnspentPattern(client, acc) { * @property {MetricPattern1} sats * @property {MetricPattern1} bps * @property {MetricPattern1} ratio - * @property {MetricsTree_Market_MovingAverage_Sma_350d_X2} x2 - */ - -/** - * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_350d_X2 - * @property {MetricPattern1} usd - * @property {MetricPattern1} cents - * @property {MetricPattern1} sats + * @property {CentsSatsUsdPattern} x2 */ /** @@ -5270,61 +5260,28 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Market_Technical_Rsi * @property {AverageGainsLossesRsiStochPattern} _24h - * @property {MetricsTree_Market_Technical_Rsi_1w} _1w - * @property {MetricsTree_Market_Technical_Rsi_1m} _1m - * @property {MetricsTree_Market_Technical_Rsi_1y} _1y - */ - -/** - * @typedef {Object} MetricsTree_Market_Technical_Rsi_1w - * @property {MetricPattern1} gains - * @property {MetricPattern1} losses - * @property {MetricPattern1} averageGain - * @property {MetricPattern1} averageLoss - * @property {BpsPercentRatioPattern3} rsi - * @property {BpsPercentRatioPattern3} rsiMin - * @property {BpsPercentRatioPattern3} rsiMax - * @property {BpsPercentRatioPattern3} stochRsi - * @property {BpsPercentRatioPattern3} stochRsiK - * @property {BpsPercentRatioPattern3} stochRsiD - */ - -/** - * @typedef {Object} MetricsTree_Market_Technical_Rsi_1m - * @property {MetricPattern1} gains - * @property {MetricPattern1} losses - * @property {MetricPattern1} averageGain - * @property {MetricPattern1} averageLoss - * @property {BpsPercentRatioPattern3} rsi - * @property {BpsPercentRatioPattern3} rsiMin - * @property {BpsPercentRatioPattern3} rsiMax - * @property {BpsPercentRatioPattern3} stochRsi - * @property {BpsPercentRatioPattern3} stochRsiK - * @property {BpsPercentRatioPattern3} stochRsiD - */ - -/** - * @typedef {Object} MetricsTree_Market_Technical_Rsi_1y - * @property {MetricPattern1} gains - * @property {MetricPattern1} losses - * @property {MetricPattern1} averageGain - * @property {MetricPattern1} averageLoss - * @property {BpsPercentRatioPattern3} rsi - * @property {BpsPercentRatioPattern3} rsiMin - * @property {BpsPercentRatioPattern3} rsiMax - * @property {BpsPercentRatioPattern3} stochRsi - * @property {BpsPercentRatioPattern3} stochRsiK - * @property {BpsPercentRatioPattern3} stochRsiD + * @property {AverageGainsLossesRsiStochPattern} _1w + * @property {AverageGainsLossesRsiStochPattern} _1m + * @property {AverageGainsLossesRsiStochPattern} _1y */ /** * @typedef {Object} MetricsTree_Market_Technical_Macd - * @property {EmaHistogramLineSignalPattern} _24h + * @property {MetricsTree_Market_Technical_Macd_24h} _24h * @property {MetricsTree_Market_Technical_Macd_1w} _1w * @property {MetricsTree_Market_Technical_Macd_1m} _1m * @property {MetricsTree_Market_Technical_Macd_1y} _1y */ +/** + * @typedef {Object} MetricsTree_Market_Technical_Macd_24h + * @property {MetricPattern1} emaFast + * @property {MetricPattern1} emaSlow + * @property {MetricPattern1} line + * @property {MetricPattern1} signal + * @property {MetricPattern1} histogram + */ + /** * @typedef {Object} MetricsTree_Market_Technical_Macd_1w * @property {MetricPattern1} emaFast @@ -7845,7 +7802,10 @@ class BrkClient extends BrkClientBase { sma: createMetricPattern1(this, 'price_return_24h_sma_1m'), sd: createMetricPattern1(this, 'price_return_24h_sd_1m'), }, - _1y: createSdSmaPattern(this, 'price_return_24h'), + _1y: { + sma: createMetricPattern1(this, 'price_return_24h_sma_1y'), + sd: createMetricPattern1(this, 'price_return_24h_sd_1y'), + }, }, }, volatility: { @@ -7878,16 +7838,8 @@ class BrkClient extends BrkClientBase { sats: createMetricPattern1(this, 'price_sma_200d_sats'), bps: createMetricPattern1(this, 'price_sma_200d_ratio_bps'), ratio: createMetricPattern1(this, 'price_sma_200d_ratio'), - x24: { - usd: createMetricPattern1(this, 'price_sma_200d_x2_4_usd'), - cents: createMetricPattern1(this, 'price_sma_200d_x2_4_cents'), - sats: createMetricPattern1(this, 'price_sma_200d_x2_4_sats'), - }, - x08: { - usd: createMetricPattern1(this, 'price_sma_200d_x0_8_usd'), - cents: createMetricPattern1(this, 'price_sma_200d_x0_8_cents'), - sats: createMetricPattern1(this, 'price_sma_200d_x0_8_sats'), - }, + x24: createCentsSatsUsdPattern(this, 'price_sma_200d_x2_4'), + x08: createCentsSatsUsdPattern(this, 'price_sma_200d_x0_8'), }, _350d: { usd: createMetricPattern1(this, 'price_sma_350d'), @@ -7895,11 +7847,7 @@ class BrkClient extends BrkClientBase { sats: createMetricPattern1(this, 'price_sma_350d_sats'), bps: createMetricPattern1(this, 'price_sma_350d_ratio_bps'), ratio: createMetricPattern1(this, 'price_sma_350d_ratio'), - x2: { - usd: createMetricPattern1(this, 'price_sma_350d_x2_usd'), - cents: createMetricPattern1(this, 'price_sma_350d_x2_cents'), - sats: createMetricPattern1(this, 'price_sma_350d_x2_sats'), - }, + x2: createCentsSatsUsdPattern(this, 'price_sma_350d_x2'), }, _1y: createBpsCentsRatioSatsUsdPattern(this, 'price_sma_1y'), _2y: createBpsCentsRatioSatsUsdPattern(this, 'price_sma_2y'), @@ -7995,49 +7943,22 @@ class BrkClient extends BrkClientBase { }, technical: { rsi: { - _24h: createAverageGainsLossesRsiStochPattern(this, 'rsi'), - _1w: { - gains: createMetricPattern1(this, 'rsi_gains_1w'), - losses: createMetricPattern1(this, 'rsi_losses_1w'), - averageGain: createMetricPattern1(this, 'rsi_average_gain_1w'), - averageLoss: createMetricPattern1(this, 'rsi_average_loss_1w'), - rsi: createBpsPercentRatioPattern3(this, 'rsi_1w'), - rsiMin: createBpsPercentRatioPattern3(this, 'rsi_min_1w'), - rsiMax: createBpsPercentRatioPattern3(this, 'rsi_max_1w'), - stochRsi: createBpsPercentRatioPattern3(this, 'rsi_stoch_1w'), - stochRsiK: createBpsPercentRatioPattern3(this, 'rsi_stoch_k_1w'), - stochRsiD: createBpsPercentRatioPattern3(this, 'rsi_stoch_d_1w'), - }, - _1m: { - gains: createMetricPattern1(this, 'rsi_gains_1m'), - losses: createMetricPattern1(this, 'rsi_losses_1m'), - averageGain: createMetricPattern1(this, 'rsi_average_gain_1m'), - averageLoss: createMetricPattern1(this, 'rsi_average_loss_1m'), - rsi: createBpsPercentRatioPattern3(this, 'rsi_1m'), - rsiMin: createBpsPercentRatioPattern3(this, 'rsi_min_1m'), - rsiMax: createBpsPercentRatioPattern3(this, 'rsi_max_1m'), - stochRsi: createBpsPercentRatioPattern3(this, 'rsi_stoch_1m'), - stochRsiK: createBpsPercentRatioPattern3(this, 'rsi_stoch_k_1m'), - stochRsiD: createBpsPercentRatioPattern3(this, 'rsi_stoch_d_1m'), - }, - _1y: { - gains: createMetricPattern1(this, 'rsi_gains_1y'), - losses: createMetricPattern1(this, 'rsi_losses_1y'), - averageGain: createMetricPattern1(this, 'rsi_average_gain_1y'), - averageLoss: createMetricPattern1(this, 'rsi_average_loss_1y'), - rsi: createBpsPercentRatioPattern3(this, 'rsi_1y'), - rsiMin: createBpsPercentRatioPattern3(this, 'rsi_min_1y'), - rsiMax: createBpsPercentRatioPattern3(this, 'rsi_max_1y'), - stochRsi: createBpsPercentRatioPattern3(this, 'rsi_stoch_1y'), - stochRsiK: createBpsPercentRatioPattern3(this, 'rsi_stoch_k_1y'), - stochRsiD: createBpsPercentRatioPattern3(this, 'rsi_stoch_d_1y'), - }, + _24h: createAverageGainsLossesRsiStochPattern(this, 'rsi', '24h'), + _1w: createAverageGainsLossesRsiStochPattern(this, 'rsi', '1w'), + _1m: createAverageGainsLossesRsiStochPattern(this, 'rsi', '1m'), + _1y: createAverageGainsLossesRsiStochPattern(this, 'rsi', '1y'), }, stochK: createBpsPercentRatioPattern3(this, 'stoch_k'), stochD: createBpsPercentRatioPattern3(this, 'stoch_d'), piCycle: createBpsRatioPattern2(this, 'pi_cycle'), macd: { - _24h: createEmaHistogramLineSignalPattern(this, 'macd'), + _24h: { + emaFast: createMetricPattern1(this, 'macd_ema_fast_24h'), + emaSlow: createMetricPattern1(this, 'macd_ema_slow_24h'), + line: createMetricPattern1(this, 'macd_line_24h'), + signal: createMetricPattern1(this, 'macd_signal_24h'), + histogram: createMetricPattern1(this, 'macd_histogram_24h'), + }, _1w: { emaFast: createMetricPattern1(this, 'macd_ema_fast_1w'), emaSlow: createMetricPattern1(this, 'macd_ema_slow_1w'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index e18cb1c51..fa83ba5fa 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -2149,21 +2149,21 @@ class _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, '0sd_4y')) - self.m0_5sd: PriceValuePattern = PriceValuePattern(client, acc) - self.m1_5sd: PriceValuePattern = PriceValuePattern(client, acc) - self.m1sd: PriceValuePattern = PriceValuePattern(client, acc) - self.m2_5sd: PriceValuePattern = PriceValuePattern(client, acc) - self.m2sd: PriceValuePattern = PriceValuePattern(client, acc) - self.m3sd: PriceValuePattern = PriceValuePattern(client, acc) - self.p0_5sd: PriceValuePattern = PriceValuePattern(client, acc) - self.p1_5sd: PriceValuePattern = PriceValuePattern(client, acc) - self.p1sd: PriceValuePattern = PriceValuePattern(client, acc) - self.p2_5sd: PriceValuePattern = PriceValuePattern(client, acc) - self.p2sd: PriceValuePattern = PriceValuePattern(client, acc) - self.p3sd: PriceValuePattern = PriceValuePattern(client, acc) - self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'sd_4y')) - self.zscore: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'zscore_4y')) + self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, f'0sd{disc}')) + self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('m0_5sd', disc)) + self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('m1_5sd', disc)) + self.m1sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('m1sd', disc)) + self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('m2_5sd', disc)) + self.m2sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('m2sd', disc)) + self.m3sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('m3sd', disc)) + self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('p0_5sd', disc)) + self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('p1_5sd', disc)) + self.p1sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('p1sd', disc)) + self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('p2_5sd', disc)) + self.p2sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('p2sd', disc)) + self.p3sd: PriceRatioPattern = PriceRatioPattern(client, acc, _m('p3sd', disc)) + self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'ratio_sd{disc}')) + self.zscore: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'ratio_zscore{disc}')) class _10y1m1w1y2y3m3y4y5y6m6y8yPattern2: """Pattern struct for repeated tree structure.""" @@ -2206,18 +2206,18 @@ class CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.cap: CentsDeltaRelUsdPattern = CentsDeltaRelUsdPattern(client, _m(acc, 'realized_cap')) - self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, _m(acc, 'realized_gross_pnl')) - self.investor: LowerPriceUpperPattern = LowerPriceUpperPattern(client, acc) - self.loss: BaseCapitulationCumulativeNegativeRelSumValuePattern = BaseCapitulationCumulativeNegativeRelSumValuePattern(client, acc) - self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'mvrv')) - self.net_pnl: BaseChangeCumulativeDeltaRelSumPattern = BaseChangeCumulativeDeltaRelSumPattern(client, _m(acc, 'net')) - self.peak_regret: BaseCumulativeRelPattern = BaseCumulativeRelPattern(client, _m(acc, 'realized_peak_regret')) - self.price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern = BpsCentsPercentilesRatioSatsSmaStdUsdPattern(client, _m(acc, 'realized_price')) - self.profit: BaseCumulativeDistributionRelSumValuePattern = BaseCumulativeDistributionRelSumValuePattern(client, acc) - self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, _m(acc, 'realized_profit_to_loss_ratio')) - self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, _m(acc, 'sell_side_risk_ratio')) - self.sopr: AdjustedRatioValuePattern = AdjustedRatioValuePattern(client, acc) + self.cap: CentsDeltaRelUsdPattern = CentsDeltaRelUsdPattern(client, f'{acc}_cap') + self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, f'{acc}_gross_pnl') + self.investor: LowerPriceUpperPattern = LowerPriceUpperPattern(client, f'{acc}_investor') + self.loss: BaseCapitulationCumulativeNegativeRelSumValuePattern = BaseCapitulationCumulativeNegativeRelSumValuePattern(client, f'{acc}_loss') + self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_mvrv') + self.net_pnl: BaseChangeCumulativeDeltaRelSumPattern = BaseChangeCumulativeDeltaRelSumPattern(client, f'{acc}_net_pnl') + self.peak_regret: BaseCumulativeRelPattern = BaseCumulativeRelPattern(client, f'{acc}_peak_regret') + self.price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern = BpsCentsPercentilesRatioSatsSmaStdUsdPattern(client, f'{acc}_price') + self.profit: BaseCumulativeDistributionRelSumValuePattern = BaseCumulativeDistributionRelSumValuePattern(client, f'{acc}_profit') + self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, f'{acc}_profit_to_loss_ratio') + self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, f'{acc}_sell_side_risk_ratio') + self.sopr: AdjustedRatioValuePattern = AdjustedRatioValuePattern(client, f'{acc}_sopr') class AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern: """Pattern struct for repeated tree structure.""" @@ -2258,16 +2258,16 @@ class AverageGainsLossesRsiStochPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.average_gain: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'average_gain_24h')) - self.average_loss: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'average_loss_24h')) - self.gains: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'gains_24h')) - self.losses: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'losses_24h')) - self.rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, '24h')) - self.rsi_max: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'max_24h')) - self.rsi_min: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'min_24h')) - self.stoch_rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'stoch_24h')) - self.stoch_rsi_d: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'stoch_d_24h')) - self.stoch_rsi_k: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'stoch_k_24h')) + self.average_gain: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'average_gain_{disc}')) + self.average_loss: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'average_loss_{disc}')) + self.gains: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'gains_{disc}')) + self.losses: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'losses_{disc}')) + self.rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, disc)) + self.rsi_max: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, f'max_{disc}')) + self.rsi_min: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, f'min_{disc}')) + self.stoch_rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, f'stoch_{disc}')) + self.stoch_rsi_d: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, f'stoch_d_{disc}')) + self.stoch_rsi_k: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, f'stoch_k_{disc}')) class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern3: """Pattern struct for repeated tree structure.""" @@ -2338,7 +2338,7 @@ class BpsCentsPercentilesRatioSatsSmaStdUsdPattern: self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio')) self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, _m(acc, 'sats')) self.sma: _1m1w1y2y4yAllPattern = _1m1w1y2y4yAllPattern(client, _m(acc, 'ratio_sma')) - self.std_dev: _1y2y4yAllPattern = _1y2y4yAllPattern(client, _m(acc, 'ratio')) + self.std_dev: _1y2y4yAllPattern = _1y2y4yAllPattern(client, acc) self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc) class AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(Generic[T]): @@ -2413,12 +2413,12 @@ class CapLossMvrvNetPriceProfitSoprPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" self.cap: CentsDeltaUsdPattern = CentsDeltaUsdPattern(client, _m(acc, 'realized_cap')) - self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc) + self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc, '') self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'mvrv')) self.net_pnl: BaseCumulativeDeltaSumPattern = BaseCumulativeDeltaSumPattern(client, _m(acc, 'net_realized_pnl')) self.price: BpsCentsRatioSatsUsdPattern = BpsCentsRatioSatsUsdPattern(client, _m(acc, 'realized_price')) self.profit: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, _m(acc, 'realized_profit')) - self.sopr: RatioValuePattern = RatioValuePattern(client, acc) + self.sopr: RatioValuePattern = RatioValuePattern(client, _m(acc, 'sopr_24h')) class GrossInvestedLossNetNuplProfitSentimentPattern2: """Pattern struct for repeated tree structure.""" @@ -2534,12 +2534,12 @@ class Pct1Pct2Pct5Pct95Pct98Pct99Pattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.pct1: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc) - self.pct2: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc) - self.pct5: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc) - self.pct95: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc) - self.pct98: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc) - self.pct99: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc) + self.pct1: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct1') + self.pct2: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct2') + self.pct5: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct5') + self.pct95: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct95') + self.pct98: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct98') + self.pct99: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct99') class ActivityOutputsRealizedSupplyUnrealizedPattern: """Pattern struct for repeated tree structure.""" @@ -2634,11 +2634,11 @@ class EmaHistogramLineSignalPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.ema_fast: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ema_fast_24h')) - self.ema_slow: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ema_slow_24h')) - self.histogram: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'histogram_24h')) - self.line: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'line_24h')) - self.signal: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'signal_24h')) + self.ema_fast: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_ema_fast') + self.ema_slow: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_ema_slow') + self.histogram: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_histogram') + self.line: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_line') + self.signal: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_signal') class InvestedMaxMinPercentilesSupplyPattern: """Pattern struct for repeated tree structure.""" @@ -2658,9 +2658,9 @@ class MvrvNuplRealizedSupplyPattern: """Create pattern node with accumulated metric name.""" self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'mvrv')) self.nupl: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'nupl')) - self.realized_cap: AllSthPattern = AllSthPattern(client, acc) + self.realized_cap: AllSthPattern = AllSthPattern(client, _m(acc, 'realized_cap')) self.realized_price: BpsCentsRatioSatsUsdPattern = BpsCentsRatioSatsUsdPattern(client, _m(acc, 'realized_price')) - self.supply: AllSthPattern2 = AllSthPattern2(client, acc) + self.supply: AllSthPattern2 = AllSthPattern2(client, _m(acc, 'supply')) class PhsReboundThsPattern: """Pattern struct for repeated tree structure.""" @@ -2749,10 +2749,10 @@ class _1y2y4yAllPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self._1y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc) - self._2y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc) - self._4y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc) - self.all: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc) + self._1y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, '1y') + self._2y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, '2y') + self._4y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, '4y') + self.all: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern(client, acc, '') class AdjustedRatioValuePattern: """Pattern struct for repeated tree structure.""" @@ -2779,10 +2779,10 @@ class BaseCumulativeNegativeSumPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.base: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'unrealized_loss')) - self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'unrealized_loss_cumulative')) - self.negative: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss')) - self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, _m(acc, 'unrealized_loss_sum')) + self.base: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, disc)) + self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, f'{disc}_cumulative')) + self.negative: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, f'neg_{disc}')) + self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, _m(acc, f'{disc}_sum')) class BothReactivatedReceivingSendingPattern: """Pattern struct for repeated tree structure.""" @@ -2839,7 +2839,7 @@ class LossNetNuplProfitPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc) + self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc, '') self.net_pnl: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'net_unrealized_pnl')) self.nupl: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'nupl')) self.profit: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, _m(acc, 'unrealized_profit')) @@ -2942,9 +2942,9 @@ class BpsPriceRatioPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, _m(acc, 'ratio_pct99_bps')) - self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct99')) - self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio_pct99')) + self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, _m(acc, f'ratio_{disc}_bps')) + self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, disc)) + self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'ratio_{disc}')) class BpsPercentRatioPattern5: """Pattern struct for repeated tree structure.""" @@ -3014,7 +3014,7 @@ class LossNuplProfitPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc) + self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc, '') self.nupl: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'nupl')) self.profit: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, _m(acc, 'unrealized_profit')) @@ -3032,9 +3032,9 @@ class RatioValuePattern2: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, _m(acc, 'asopr')) - self.value_created: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, _m(acc, 'adj_value_created')) - self.value_destroyed: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, _m(acc, 'adj_value_destroyed')) + self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, f'{acc}_ratio') + self.value_created: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, f'{acc}_value_created') + self.value_destroyed: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, f'{acc}_value_destroyed') class RatioValuePattern: """Pattern struct for repeated tree structure.""" @@ -3069,7 +3069,7 @@ class AbsoluteRatePattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" self.absolute: _1m1w1y24hPattern[StoredI64] = _1m1w1y24hPattern(client, acc) - self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc) + self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, _m(acc, 'rate')) class AbsoluteRatePattern2: """Pattern struct for repeated tree structure.""" @@ -3077,7 +3077,7 @@ class AbsoluteRatePattern2: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" self.absolute: _1m1w1y24hPattern3 = _1m1w1y24hPattern3(client, acc) - self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc) + self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, _m(acc, 'rate')) class AllSthPattern2: """Pattern struct for repeated tree structure.""" @@ -3159,13 +3159,13 @@ class InPattern: self.in_loss: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'loss')) self.in_profit: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'profit')) -class PriceValuePattern: +class PriceRatioPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'p3sd_4y')) - self.value: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio_p3sd_4y')) + self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, disc)) + self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, f'ratio_{disc}')) class RelPattern: """Pattern struct for repeated tree structure.""" @@ -3180,8 +3180,8 @@ class SdSmaPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'sd_1y')) - self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'sma_1y')) + self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_sd') + self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, f'{acc}_sma') class ValuePattern: """Pattern struct for repeated tree structure.""" @@ -4237,13 +4237,20 @@ class MetricsTree_Market_Returns_Sd24h_1m: self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_return_24h_sma_1m') self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_return_24h_sd_1m') +class MetricsTree_Market_Returns_Sd24h_1y: + """Metrics tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_return_24h_sma_1y') + self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_return_24h_sd_1y') + class MetricsTree_Market_Returns_Sd24h: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): self._1w: MetricsTree_Market_Returns_Sd24h_1w = MetricsTree_Market_Returns_Sd24h_1w(client) self._1m: MetricsTree_Market_Returns_Sd24h_1m = MetricsTree_Market_Returns_Sd24h_1m(client) - self._1y: SdSmaPattern = SdSmaPattern(client, 'price_return_24h') + self._1y: MetricsTree_Market_Returns_Sd24h_1y = MetricsTree_Market_Returns_Sd24h_1y(client) class MetricsTree_Market_Returns: """Metrics tree node.""" @@ -4271,22 +4278,6 @@ class MetricsTree_Market_Range: self.true_range_sum_2w: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_true_range_sum_2w') self.choppiness_index_2w: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'price_choppiness_index_2w') -class MetricsTree_Market_MovingAverage_Sma_200d_X24: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_200d_x2_4_usd') - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_x2_4_cents') - self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_200d_x2_4_sats') - -class MetricsTree_Market_MovingAverage_Sma_200d_X08: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_200d_x0_8_usd') - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_x0_8_cents') - self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_200d_x0_8_sats') - class MetricsTree_Market_MovingAverage_Sma_200d: """Metrics tree node.""" @@ -4296,16 +4287,8 @@ class MetricsTree_Market_MovingAverage_Sma_200d: self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_200d_sats') self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, 'price_sma_200d_ratio_bps') self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sma_200d_ratio') - self.x2_4: MetricsTree_Market_MovingAverage_Sma_200d_X24 = MetricsTree_Market_MovingAverage_Sma_200d_X24(client) - self.x0_8: MetricsTree_Market_MovingAverage_Sma_200d_X08 = MetricsTree_Market_MovingAverage_Sma_200d_X08(client) - -class MetricsTree_Market_MovingAverage_Sma_350d_X2: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_350d_x2_usd') - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_350d_x2_cents') - self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_350d_x2_sats') + self.x2_4: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'price_sma_200d_x2_4') + self.x0_8: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'price_sma_200d_x0_8') class MetricsTree_Market_MovingAverage_Sma_350d: """Metrics tree node.""" @@ -4316,7 +4299,7 @@ class MetricsTree_Market_MovingAverage_Sma_350d: self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_350d_sats') self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, 'price_sma_350d_ratio_bps') self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sma_350d_ratio') - self.x2: MetricsTree_Market_MovingAverage_Sma_350d_X2 = MetricsTree_Market_MovingAverage_Sma_350d_X2(client) + self.x2: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'price_sma_350d_x2') class MetricsTree_Market_MovingAverage_Sma: """Metrics tree node.""" @@ -4462,59 +4445,24 @@ class MetricsTree_Market_Dca: self.period: MetricsTree_Market_Dca_Period = MetricsTree_Market_Dca_Period(client) self.class_: MetricsTree_Market_Dca_Class = MetricsTree_Market_Dca_Class(client) -class MetricsTree_Market_Technical_Rsi_1w: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.gains: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_gains_1w') - self.losses: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_losses_1w') - self.average_gain: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_average_gain_1w') - self.average_loss: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_average_loss_1w') - self.rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_1w') - self.rsi_min: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_min_1w') - self.rsi_max: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_max_1w') - self.stoch_rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_1w') - self.stoch_rsi_k: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_k_1w') - self.stoch_rsi_d: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_d_1w') - -class MetricsTree_Market_Technical_Rsi_1m: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.gains: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_gains_1m') - self.losses: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_losses_1m') - self.average_gain: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_average_gain_1m') - self.average_loss: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_average_loss_1m') - self.rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_1m') - self.rsi_min: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_min_1m') - self.rsi_max: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_max_1m') - self.stoch_rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_1m') - self.stoch_rsi_k: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_k_1m') - self.stoch_rsi_d: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_d_1m') - -class MetricsTree_Market_Technical_Rsi_1y: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.gains: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_gains_1y') - self.losses: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_losses_1y') - self.average_gain: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_average_gain_1y') - self.average_loss: MetricPattern1[StoredF32] = MetricPattern1(client, 'rsi_average_loss_1y') - self.rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_1y') - self.rsi_min: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_min_1y') - self.rsi_max: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_max_1y') - self.stoch_rsi: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_1y') - self.stoch_rsi_k: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_k_1y') - self.stoch_rsi_d: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'rsi_stoch_d_1y') - class MetricsTree_Market_Technical_Rsi: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): self._24h: AverageGainsLossesRsiStochPattern = AverageGainsLossesRsiStochPattern(client, 'rsi') - self._1w: MetricsTree_Market_Technical_Rsi_1w = MetricsTree_Market_Technical_Rsi_1w(client) - self._1m: MetricsTree_Market_Technical_Rsi_1m = MetricsTree_Market_Technical_Rsi_1m(client) - self._1y: MetricsTree_Market_Technical_Rsi_1y = MetricsTree_Market_Technical_Rsi_1y(client) + self._1w: AverageGainsLossesRsiStochPattern = AverageGainsLossesRsiStochPattern(client, 'rsi') + self._1m: AverageGainsLossesRsiStochPattern = AverageGainsLossesRsiStochPattern(client, 'rsi') + self._1y: AverageGainsLossesRsiStochPattern = AverageGainsLossesRsiStochPattern(client, 'rsi') + +class MetricsTree_Market_Technical_Macd_24h: + """Metrics tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.ema_fast: MetricPattern1[StoredF32] = MetricPattern1(client, 'macd_ema_fast_24h') + self.ema_slow: MetricPattern1[StoredF32] = MetricPattern1(client, 'macd_ema_slow_24h') + self.line: MetricPattern1[StoredF32] = MetricPattern1(client, 'macd_line_24h') + self.signal: MetricPattern1[StoredF32] = MetricPattern1(client, 'macd_signal_24h') + self.histogram: MetricPattern1[StoredF32] = MetricPattern1(client, 'macd_histogram_24h') class MetricsTree_Market_Technical_Macd_1w: """Metrics tree node.""" @@ -4550,7 +4498,7 @@ class MetricsTree_Market_Technical_Macd: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self._24h: EmaHistogramLineSignalPattern = EmaHistogramLineSignalPattern(client, 'macd') + self._24h: MetricsTree_Market_Technical_Macd_24h = MetricsTree_Market_Technical_Macd_24h(client) self._1w: MetricsTree_Market_Technical_Macd_1w = MetricsTree_Market_Technical_Macd_1w(client) self._1m: MetricsTree_Market_Technical_Macd_1m = MetricsTree_Market_Technical_Macd_1m(client) self._1y: MetricsTree_Market_Technical_Macd_1y = MetricsTree_Market_Technical_Macd_1y(client) @@ -4827,6 +4775,23 @@ class MetricsTree_Cohorts_Utxo_All_Supply: self.in_profit: BtcCentsRelSatsUsdPattern2 = BtcCentsRelSatsUsdPattern2(client, 'supply_in_profit') self.in_loss: BtcCentsRelSatsUsdPattern2 = BtcCentsRelSatsUsdPattern2(client, 'supply_in_loss') +class MetricsTree_Cohorts_Utxo_All_Realized: + """Metrics tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.cap: CentsDeltaRelUsdPattern = CentsDeltaRelUsdPattern(client, 'realized_cap') + self.profit: BaseCumulativeDistributionRelSumValuePattern = BaseCumulativeDistributionRelSumValuePattern(client, '') + self.loss: BaseCapitulationCumulativeNegativeRelSumValuePattern = BaseCapitulationCumulativeNegativeRelSumValuePattern(client, '') + self.price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern = BpsCentsPercentilesRatioSatsSmaStdUsdPattern(client, 'realized_price') + self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, 'mvrv') + self.sopr: AdjustedRatioValuePattern = AdjustedRatioValuePattern(client, '') + self.net_pnl: BaseChangeCumulativeDeltaRelSumPattern = BaseChangeCumulativeDeltaRelSumPattern(client, 'net') + self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, 'realized_gross_pnl') + self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, 'sell_side_risk_ratio') + self.peak_regret: BaseCumulativeRelPattern = BaseCumulativeRelPattern(client, 'realized_peak_regret') + self.investor: LowerPriceUpperPattern = LowerPriceUpperPattern(client, '') + self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'realized_profit_to_loss_ratio') + class MetricsTree_Cohorts_Utxo_All_Unrealized_Profit: """Metrics tree node.""" @@ -4875,10 +4840,27 @@ class MetricsTree_Cohorts_Utxo_All: self.supply: MetricsTree_Cohorts_Utxo_All_Supply = MetricsTree_Cohorts_Utxo_All_Supply(client) self.outputs: UnspentPattern = UnspentPattern(client, 'utxo_count') self.activity: CoindaysCoinyearsDormancySentPattern = CoindaysCoinyearsDormancySentPattern(client, '') - self.realized: CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern = CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern(client, '') + self.realized: MetricsTree_Cohorts_Utxo_All_Realized = MetricsTree_Cohorts_Utxo_All_Realized(client) self.cost_basis: InvestedMaxMinPercentilesSupplyPattern = InvestedMaxMinPercentilesSupplyPattern(client, '') self.unrealized: MetricsTree_Cohorts_Utxo_All_Unrealized = MetricsTree_Cohorts_Utxo_All_Unrealized(client) +class MetricsTree_Cohorts_Utxo_Sth_Realized: + """Metrics tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.cap: CentsDeltaRelUsdPattern = CentsDeltaRelUsdPattern(client, 'sth_realized_cap') + self.profit: BaseCumulativeDistributionRelSumValuePattern = BaseCumulativeDistributionRelSumValuePattern(client, 'sth') + self.loss: BaseCapitulationCumulativeNegativeRelSumValuePattern = BaseCapitulationCumulativeNegativeRelSumValuePattern(client, 'sth') + self.price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern = BpsCentsPercentilesRatioSatsSmaStdUsdPattern(client, 'sth_realized_price') + self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, 'sth_mvrv') + self.sopr: AdjustedRatioValuePattern = AdjustedRatioValuePattern(client, 'sth') + self.net_pnl: BaseChangeCumulativeDeltaRelSumPattern = BaseChangeCumulativeDeltaRelSumPattern(client, 'sth_net') + self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, 'sth_realized_gross_pnl') + self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, 'sth_sell_side_risk_ratio') + self.peak_regret: BaseCumulativeRelPattern = BaseCumulativeRelPattern(client, 'sth_realized_peak_regret') + self.investor: LowerPriceUpperPattern = LowerPriceUpperPattern(client, 'sth') + self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'sth_realized_profit_to_loss_ratio') + class MetricsTree_Cohorts_Utxo_Sth: """Metrics tree node.""" @@ -4886,7 +4868,7 @@ class MetricsTree_Cohorts_Utxo_Sth: self.supply: DeltaHalfInRelTotalPattern2 = DeltaHalfInRelTotalPattern2(client, 'sth_supply') self.outputs: UnspentPattern = UnspentPattern(client, 'sth_utxo_count') self.activity: CoindaysCoinyearsDormancySentPattern = CoindaysCoinyearsDormancySentPattern(client, 'sth') - self.realized: CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern = CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern(client, 'sth') + self.realized: MetricsTree_Cohorts_Utxo_Sth_Realized = MetricsTree_Cohorts_Utxo_Sth_Realized(client) self.cost_basis: InvestedMaxMinPercentilesSupplyPattern = InvestedMaxMinPercentilesSupplyPattern(client, 'sth') self.unrealized: GrossInvestedLossNetNuplProfitSentimentPattern2 = GrossInvestedLossNetNuplProfitSentimentPattern2(client, 'sth') diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index ebb796ebf..0940bcfa7 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -46,7 +46,10 @@ export function flatMapCohorts(list, fn) { * @returns {R[]} */ export function mapCohortsWithAll(list, all, fn) { - return [...list.map(fn), { ...fn({ ...all, name: "All" }), defaultActive: false }]; + return [ + ...list.map(fn), + { ...fn({ ...all, name: "All" }), defaultActive: false }, + ]; } /** @@ -60,7 +63,10 @@ export function mapCohortsWithAll(list, all, fn) { * @returns {R[]} */ export function flatMapCohortsWithAll(list, all, fn) { - return [...list.flatMap(fn), ...fn({ ...all, name: "All" }).map((s) => ({ ...s, defaultActive: false }))]; + return [ + ...list.flatMap(fn), + ...fn({ ...all, name: "All" }).map((s) => ({ ...s, defaultActive: false })), + ]; } /** @@ -257,20 +263,32 @@ export function satsBtcUsdFullTree({ pattern, name, title, color }) { name: "Compare", title: `${title} Rolling Sum`, bottom: ROLLING_WINDOWS.flatMap((w) => - satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name, color: w.color }), + satsBtcUsd({ + pattern: pattern.sum[w.key], + name: w.name, + color: w.color, + }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${title} ${w.name} Rolling Sum`, - bottom: satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name, color: w.color }), + bottom: satsBtcUsd({ + pattern: pattern.sum[w.key], + name: w.name, + color: w.color, + }), })), ], }, { name: "Cumulative", title: `${title} (Total)`, - bottom: satsBtcUsd({ pattern: pattern.cumulative, name: "all-time", color }), + bottom: satsBtcUsd({ + pattern: pattern.cumulative, + name: "all-time", + color, + }), }, ]; } @@ -296,7 +314,12 @@ export function simplePriceRatioTree({ pattern, title, legend, color }) { title: `${title} Ratio`, top: [price({ metric: pattern, name: legend, color })], bottom: [ - baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }), + baseline({ + metric: pattern.ratio, + name: "Ratio", + unit: Unit.ratio, + base: 1, + }), ], }, ]; @@ -312,7 +335,13 @@ export function simplePriceRatioTree({ pattern, title, legend, color }) { * @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences] * @returns {PartialOptionsTree} */ -export function priceRatioPercentilesTree({ pattern, title, legend, color, priceReferences }) { +export function priceRatioPercentilesTree({ + pattern, + title, + legend, + color, + priceReferences, +}) { const p = pattern.percentiles; const pctUsd = [ { name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 }, @@ -338,7 +367,13 @@ export function priceRatioPercentilesTree({ pattern, title, legend, color, price price({ metric: pattern, name: legend, color }), ...(priceReferences ?? []), ...pctUsd.map(({ name, prop, color }) => - price({ metric: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }), + price({ + metric: prop, + name, + color, + defaultActive: false, + options: { lineStyle: 1 }, + }), ), ], }, @@ -348,13 +383,31 @@ export function priceRatioPercentilesTree({ pattern, title, legend, color, price top: [ price({ metric: pattern, name: legend, color }), ...pctUsd.map(({ name, prop, color }) => - price({ metric: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }), + price({ + metric: prop, + name, + color, + defaultActive: false, + options: { lineStyle: 1 }, + }), ), ], bottom: [ - baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }), + baseline({ + metric: pattern.ratio, + name: "Ratio", + unit: Unit.ratio, + base: 1, + }), ...pctRatio.map(({ name, prop, color }) => - line({ metric: prop, name, color, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }), + line({ + metric: prop, + name, + color, + defaultActive: false, + unit: Unit.ratio, + options: { lineStyle: 1 }, + }), ), ], }, @@ -381,7 +434,13 @@ export function groupedSimplePriceRatioTree({ list, title }) { name: "Ratio", title: `${title} Ratio`, bottom: list.map(({ name, color, pattern }) => - baseline({ metric: pattern.ratio, name, color, unit: Unit.ratio, base: 1 }), + baseline({ + metric: pattern.ratio, + name, + color, + unit: Unit.ratio, + base: 1, + }), ), }, ]; @@ -453,10 +512,30 @@ export function percentileMap(ratio) { */ export function sdPatterns(ratio) { return /** @type {const} */ ([ - { nameAddon: "All Time", titleAddon: "", sd: ratio.stdDev.all, smaRatio: ratio.sma.all.ratio }, - { nameAddon: "4y", titleAddon: "4y", sd: ratio.stdDev._4y, smaRatio: ratio.sma._4y.ratio }, - { nameAddon: "2y", titleAddon: "2y", sd: ratio.stdDev._2y, smaRatio: ratio.sma._2y.ratio }, - { nameAddon: "1y", titleAddon: "1y", sd: ratio.stdDev._1y, smaRatio: ratio.sma._1y.ratio }, + { + nameAddon: "All Time", + titleAddon: "", + sd: ratio.stdDev.all, + smaRatio: ratio.sma.all.ratio, + }, + { + nameAddon: "4y", + titleAddon: "4y", + sd: ratio.stdDev._4y, + smaRatio: ratio.sma._4y.ratio, + }, + { + nameAddon: "2y", + titleAddon: "2y", + sd: ratio.stdDev._2y, + smaRatio: ratio.sma._2y.ratio, + }, + { + nameAddon: "1y", + titleAddon: "1y", + sd: ratio.stdDev._1y, + smaRatio: ratio.sma._1y.ratio, + }, ]); } @@ -490,18 +569,18 @@ export function sdBandsUsd(sd) { export function sdBandsRatio(sd, smaRatio) { return /** @type {const} */ ([ { name: "0σ", prop: smaRatio, color: colors.sd._0 }, - { name: "+0.5σ", prop: sd.p05sd.value, color: colors.sd.p05 }, - { name: "−0.5σ", prop: sd.m05sd.value, color: colors.sd.m05 }, - { name: "+1σ", prop: sd.p1sd.value, color: colors.sd.p1 }, - { name: "−1σ", prop: sd.m1sd.value, color: colors.sd.m1 }, - { name: "+1.5σ", prop: sd.p15sd.value, color: colors.sd.p15 }, - { name: "−1.5σ", prop: sd.m15sd.value, color: colors.sd.m15 }, - { name: "+2σ", prop: sd.p2sd.value, color: colors.sd.p2 }, - { name: "−2σ", prop: sd.m2sd.value, color: colors.sd.m2 }, - { name: "+2.5σ", prop: sd.p25sd.value, color: colors.sd.p25 }, - { name: "−2.5σ", prop: sd.m25sd.value, color: colors.sd.m25 }, - { name: "+3σ", prop: sd.p3sd.value, color: colors.sd.p3 }, - { name: "−3σ", prop: sd.m3sd.value, color: colors.sd.m3 }, + { name: "+0.5σ", prop: sd.p05sd.ratio, color: colors.sd.p05 }, + { name: "−0.5σ", prop: sd.m05sd.ratio, color: colors.sd.m05 }, + { name: "+1σ", prop: sd.p1sd.ratio, color: colors.sd.p1 }, + { name: "−1σ", prop: sd.m1sd.ratio, color: colors.sd.m1 }, + { name: "+1.5σ", prop: sd.p15sd.ratio, color: colors.sd.p15 }, + { name: "−1.5σ", prop: sd.m15sd.ratio, color: colors.sd.m15 }, + { name: "+2σ", prop: sd.p2sd.ratio, color: colors.sd.p2 }, + { name: "−2σ", prop: sd.m2sd.ratio, color: colors.sd.m2 }, + { name: "+2.5σ", prop: sd.p25sd.ratio, color: colors.sd.p25 }, + { name: "−2.5σ", prop: sd.m25sd.ratio, color: colors.sd.m25 }, + { name: "+3σ", prop: sd.p3sd.ratio, color: colors.sd.p3 }, + { name: "−3σ", prop: sd.m3sd.ratio, color: colors.sd.m3 }, ]); }