mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-21 07:58:11 -07:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 227a31f64e | |||
| 00984112d8 | |||
| 3af673132a | |||
| 0fc61d7932 | |||
| 5161ae2012 | |||
| 10c16335ed | |||
| 29ad37803e | |||
| 8d43f340a0 | |||
| e279116bff | |||
| 5c376acb9f | |||
| a43b00c12c | |||
| 15fbdc168f | |||
| 046c624abf | |||
| 4d7212fb63 | |||
| 2207ec1b7e | |||
| 3f2a48f248 | |||
| 3f9edb211e | |||
| eee1a10d2a | |||
| 0bf2cd77dc | |||
| a99c06013b | |||
| 10ef95497f | |||
| e0a618837e | |||
| 5bd1f0b625 | |||
| 4d3e8bbcaf | |||
| 153fcdf4e0 | |||
| 5d83ee4d70 | |||
| 10d29aa2ea | |||
| 528880bd04 | |||
| 5937fbe724 | |||
| 7a718293c0 | |||
| 75f37b6695 | |||
| f9688629ca | |||
| c9e704bdf4 | |||
| 1e0a7d6d0f | |||
| 565251f12f | |||
| 9879a986aa | |||
| 8d7ef8da61 | |||
| 17478a4ac4 | |||
| b3031b3375 | |||
| 2e401379a0 | |||
| 45ab6ebf71 | |||
| 00f7d69ea6 | |||
| 408d83c350 | |||
| 43df9e098c | |||
| 0c7861071d | |||
| 6f430bdb8c | |||
| 4b415b215d | |||
| 8614e9eded | |||
| c85da92cbc | |||
| 297fc3b855 | |||
| c9d5a62fcb | |||
| 90b3b51c48 | |||
| 5966ab05e4 | |||
| c3506339cd | |||
| e54843291e | |||
| b0b261fe9f | |||
| 6786be296d | |||
| e5068bbbf3 | |||
| 36cfe49b20 | |||
| 33cc13708a | |||
| 2389632812 | |||
| e0bcdb8105 | |||
| 45e83c98b9 | |||
| 753bbf3e7e | |||
| 54cc0cb446 | |||
| d64dcb75a9 | |||
| f599115f6c | |||
| 9fc45625ad | |||
| c68d1d1fda | |||
| 6cbe09af23 | |||
| 96d35d1d29 | |||
| e23554811b | |||
| 041c542046 | |||
| 66dc7cd8f5 | |||
| b00692249c | |||
| ff2c04a100 | |||
| 7cee0e2c5a | |||
| 744032f1f1 | |||
| 99b171bad6 | |||
| 37e2b6eae2 | |||
| a967fe8f35 | |||
| a3f3c54675 | |||
| f41874f438 | |||
| 98bbfec525 | |||
| 1bcf3235b6 | |||
| 07734b8bab | |||
| a2fd1e03ad | |||
| 90e8741fb7 | |||
| 5f5563fece | |||
| c7edfce481 | |||
| 7b3dd83b93 |
@@ -18,6 +18,7 @@ _*
|
|||||||
/*.py
|
/*.py
|
||||||
/*.json
|
/*.json
|
||||||
/*.html
|
/*.html
|
||||||
|
!/btc-cycle-sim.html
|
||||||
/research
|
/research
|
||||||
/filter_*
|
/filter_*
|
||||||
/heatmaps*
|
/heatmaps*
|
||||||
@@ -25,6 +26,7 @@ _*
|
|||||||
/playground
|
/playground
|
||||||
/*.txt
|
/*.txt
|
||||||
/*.csv
|
/*.csv
|
||||||
|
/tmp
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log*
|
*.log*
|
||||||
|
|||||||
Generated
+343
-465
File diff suppressed because it is too large
Load Diff
+38
-36
@@ -4,7 +4,7 @@ members = ["crates/*"]
|
|||||||
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
|
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
|
||||||
package.license = "MIT"
|
package.license = "MIT"
|
||||||
package.edition = "2024"
|
package.edition = "2024"
|
||||||
package.version = "0.3.1"
|
package.version = "0.3.6"
|
||||||
package.homepage = "https://bitcoinresearchkit.org"
|
package.homepage = "https://bitcoinresearchkit.org"
|
||||||
package.repository = "https://github.com/bitcoinresearchkit/brk"
|
package.repository = "https://github.com/bitcoinresearchkit/brk"
|
||||||
package.readme = "README.md"
|
package.readme = "README.md"
|
||||||
@@ -35,57 +35,59 @@ debug = true
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
aide = { version = "0.16.0-alpha.4", features = ["axum-json", "axum-query"] }
|
aide = { version = "0.16.0-alpha.4", features = ["axum-json", "axum-query"] }
|
||||||
axum = { version = "0.8.9", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] }
|
axum = { version = "0.8.9", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] }
|
||||||
bitcoin = { version = "0.32.100", features = ["serde"] }
|
bitcoin = { version = "0.32.102", features = ["serde"] }
|
||||||
brk_alloc = { version = "0.3.1", path = "crates/brk_alloc" }
|
brk_alloc = { version = "0.3.6", path = "crates/brk_alloc" }
|
||||||
brk_bencher = { version = "0.3.1", path = "crates/brk_bencher" }
|
brk_bencher = { version = "0.3.6", path = "crates/brk_bencher" }
|
||||||
brk_bindgen = { version = "0.3.1", path = "crates/brk_bindgen" }
|
brk_bindgen = { version = "0.3.6", path = "crates/brk_bindgen" }
|
||||||
brk_cli = { version = "0.3.1", path = "crates/brk_cli" }
|
brk_cli = { version = "0.3.6", path = "crates/brk_cli" }
|
||||||
brk_client = { version = "0.3.1", path = "crates/brk_client" }
|
brk_client = { version = "0.3.6", path = "crates/brk_client" }
|
||||||
brk_cohort = { version = "0.3.1", path = "crates/brk_cohort" }
|
brk_cohort = { version = "0.3.6", path = "crates/brk_cohort" }
|
||||||
brk_computer = { version = "0.3.1", path = "crates/brk_computer" }
|
brk_computer = { version = "0.3.6", path = "crates/brk_computer" }
|
||||||
brk_error = { version = "0.3.1", path = "crates/brk_error" }
|
brk_error = { version = "0.3.6", path = "crates/brk_error" }
|
||||||
brk_fetcher = { version = "0.3.1", path = "crates/brk_fetcher" }
|
brk_fetcher = { version = "0.3.6", path = "crates/brk_fetcher" }
|
||||||
brk_indexer = { version = "0.3.1", path = "crates/brk_indexer" }
|
brk_indexer = { version = "0.3.6", path = "crates/brk_indexer" }
|
||||||
brk_iterator = { version = "0.3.1", path = "crates/brk_iterator" }
|
brk_iterator = { version = "0.3.6", path = "crates/brk_iterator" }
|
||||||
brk_logger = { version = "0.3.1", path = "crates/brk_logger" }
|
brk_logger = { version = "0.3.6", path = "crates/brk_logger" }
|
||||||
brk_mempool = { version = "0.3.1", path = "crates/brk_mempool" }
|
brk_mempool = { version = "0.3.6", path = "crates/brk_mempool" }
|
||||||
brk_oracle = { version = "0.3.1", path = "crates/brk_oracle" }
|
brk_oracle = { version = "0.3.6", path = "crates/brk_oracle" }
|
||||||
brk_query = { version = "0.3.1", path = "crates/brk_query", features = ["tokio"] }
|
brk_query = { version = "0.3.6", path = "crates/brk_query", features = ["tokio"] }
|
||||||
brk_reader = { version = "0.3.1", path = "crates/brk_reader" }
|
brk_reader = { version = "0.3.6", path = "crates/brk_reader" }
|
||||||
brk_rpc = { version = "0.3.1", path = "crates/brk_rpc" }
|
brk_rpc = { version = "0.3.6", path = "crates/brk_rpc" }
|
||||||
brk_server = { version = "0.3.1", path = "crates/brk_server" }
|
brk_server = { version = "0.3.6", path = "crates/brk_server" }
|
||||||
brk_store = { version = "0.3.1", path = "crates/brk_store" }
|
brk_store = { version = "0.3.6", path = "crates/brk_store" }
|
||||||
brk_traversable = { version = "0.3.1", path = "crates/brk_traversable", features = ["pco", "derive"] }
|
brk_traversable = { version = "0.3.6", path = "crates/brk_traversable", features = ["pco", "derive"] }
|
||||||
brk_traversable_derive = { version = "0.3.1", path = "crates/brk_traversable_derive" }
|
brk_traversable_derive = { version = "0.3.6", path = "crates/brk_traversable_derive" }
|
||||||
brk_types = { version = "0.3.1", path = "crates/brk_types" }
|
brk_types = { version = "0.3.6", path = "crates/brk_types" }
|
||||||
brk_website = { version = "0.3.1", path = "crates/brk_website" }
|
brk_website = { version = "0.3.6", path = "crates/brk_website" }
|
||||||
byteview = "0.10.1"
|
byteview = "0.10.1"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
corepc-jsonrpc = { package = "jsonrpc", version = "0.19.0", features = ["simple_http"], default-features = false }
|
corepc-jsonrpc = { package = "jsonrpc", version = "0.19.0", features = ["simple_http"], default-features = false }
|
||||||
corepc-types = { version = "0.14.0", features = ["std"], default-features = false }
|
corepc-types = { version = "0.15.0", features = ["std"], default-features = false }
|
||||||
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
||||||
fjall = "3.1.4"
|
fjall = "3.1.8"
|
||||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||||
jiff = { version = "0.2.28", features = ["perf-inline", "tz-system"], default-features = false }
|
jiff = { version = "0.2.34", features = ["perf-inline", "tz-system"], default-features = false }
|
||||||
owo-colors = "4.3.0"
|
owo-colors = "4.3.0"
|
||||||
parking_lot = "0.12.5"
|
parking_lot = "0.12.5"
|
||||||
pco = "1.0.2"
|
pco = "1.0.2"
|
||||||
rayon = "1.12.0"
|
rayon = "1.12.0"
|
||||||
rustc-hash = "2.1.2"
|
rapidhash = "4.5.1"
|
||||||
|
rustc-hash = "2.1.3"
|
||||||
schemars = { version = "1.2.1", features = ["indexmap2"] }
|
schemars = { version = "1.2.1", features = ["indexmap2"] }
|
||||||
serde = "1.0.228"
|
serde = "1.0.229"
|
||||||
serde_bytes = "0.11.19"
|
serde_bytes = "0.11.19"
|
||||||
serde_derive = "1.0.228"
|
serde_derive = "1.0.229"
|
||||||
serde_json = { version = "1.0.150", features = ["float_roundtrip", "preserve_order"] }
|
serde_json = { version = "1.0.151", features = ["float_roundtrip", "preserve_order"] }
|
||||||
smallvec = "1.15.1"
|
smallvec = "1.15.2"
|
||||||
tokio = { version = "1.52.3", features = ["rt-multi-thread"] }
|
tokio = { version = "1.53.0", features = ["rt-multi-thread"] }
|
||||||
tower-http = { version = "0.6.11", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
|
tower-http = { version = "0.7.0", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
|
||||||
tower-layer = "0.3"
|
tower-layer = "0.3"
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
ureq = { version = "3.3.0", features = ["json"] }
|
ureq = { version = "3.3.0", features = ["json"] }
|
||||||
vecdb = { version = "0.10.3", features = ["derive", "serde_json", "pco", "schemars"] }
|
vecdb = { version = "0.10.4", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||||
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
|
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||||
|
|
||||||
|
|
||||||
[workspace.metadata.release]
|
[workspace.metadata.release]
|
||||||
shared-version = true
|
shared-version = true
|
||||||
tag-name = "v{{version}}"
|
tag-name = "v{{version}}"
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ fn collect_instance_analyses(
|
|||||||
///
|
///
|
||||||
/// Supports two cases:
|
/// Supports two cases:
|
||||||
/// 1. **Embedded discriminator**: a substring varies per instance within field_parts.
|
/// 1. **Embedded discriminator**: a substring varies per instance within field_parts.
|
||||||
/// E.g., `ratio_pct99_bps` vs `ratio_pct1_bps` → template `ratio_{disc}_bps`
|
/// E.g., `ratio_pct99_ppm` vs `ratio_pct1_ppm` → template `ratio_{disc}_ppm`
|
||||||
/// 2. **Suffix discriminator**: a common suffix is appended to all field_parts.
|
/// 2. **Suffix discriminator**: a common suffix is appended to all field_parts.
|
||||||
/// E.g., `ratio_sd` vs `ratio_sd_4y` → template `ratio_sd{disc}`
|
/// E.g., `ratio_sd` vs `ratio_sd_4y` → template `ratio_sd{disc}`
|
||||||
fn try_detect_template(
|
fn try_detect_template(
|
||||||
@@ -307,11 +307,11 @@ fn try_detect_template(
|
|||||||
return Some(mode);
|
return Some(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 2: embedded discriminator (e.g., ratio_pct99_bps vs ratio_pct1_bps)
|
// Strategy 2: embedded discriminator (e.g., ratio_pct99_ppm vs ratio_pct1_ppm)
|
||||||
try_embedded_disc(majority, fields)
|
try_embedded_disc(majority, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strategy 1: embedded discriminator (e.g., pct99 inside ratio_pct99_bps)
|
/// Strategy 1: embedded discriminator (e.g., pct99 inside ratio_pct99_ppm)
|
||||||
fn try_embedded_disc(
|
fn try_embedded_disc(
|
||||||
majority: &[&InstanceAnalysis],
|
majority: &[&InstanceAnalysis],
|
||||||
fields: &[PatternField],
|
fields: &[PatternField],
|
||||||
@@ -772,7 +772,7 @@ mod tests {
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
let fields = vec![
|
let fields = vec![
|
||||||
PatternField {
|
PatternField {
|
||||||
name: "bps".into(),
|
name: "ppm".into(),
|
||||||
rust_type: "T".into(),
|
rust_type: "T".into(),
|
||||||
json_type: "n".into(),
|
json_type: "n".into(),
|
||||||
indexes: BTreeSet::new(),
|
indexes: BTreeSet::new(),
|
||||||
@@ -796,7 +796,7 @@ mod tests {
|
|||||||
let pct99 = InstanceAnalysis {
|
let pct99 = InstanceAnalysis {
|
||||||
base: "realized_price".into(),
|
base: "realized_price".into(),
|
||||||
field_parts: [
|
field_parts: [
|
||||||
("bps".into(), "ratio_pct99_bps".into()),
|
("ppm".into(), "ratio_pct99_ppm".into()),
|
||||||
("price".into(), "pct99".into()),
|
("price".into(), "pct99".into()),
|
||||||
("ratio".into(), "ratio_pct99".into()),
|
("ratio".into(), "ratio_pct99".into()),
|
||||||
]
|
]
|
||||||
@@ -808,7 +808,7 @@ mod tests {
|
|||||||
let pct1 = InstanceAnalysis {
|
let pct1 = InstanceAnalysis {
|
||||||
base: "realized_price".into(),
|
base: "realized_price".into(),
|
||||||
field_parts: [
|
field_parts: [
|
||||||
("bps".into(), "ratio_pct1_bps".into()),
|
("ppm".into(), "ratio_pct1_ppm".into()),
|
||||||
("price".into(), "pct1".into()),
|
("price".into(), "pct1".into()),
|
||||||
("ratio".into(), "ratio_pct1".into()),
|
("ratio".into(), "ratio_pct1".into()),
|
||||||
]
|
]
|
||||||
@@ -821,7 +821,7 @@ mod tests {
|
|||||||
assert!(mode.is_some());
|
assert!(mode.is_some());
|
||||||
match mode.unwrap() {
|
match mode.unwrap() {
|
||||||
PatternMode::Templated { templates } => {
|
PatternMode::Templated { templates } => {
|
||||||
assert_eq!(templates.get("bps").unwrap(), "ratio_{disc}_bps");
|
assert_eq!(templates.get("ppm").unwrap(), "ratio_{disc}_ppm");
|
||||||
assert_eq!(templates.get("price").unwrap(), "{disc}");
|
assert_eq!(templates.get("price").unwrap(), "{disc}");
|
||||||
assert_eq!(templates.get("ratio").unwrap(), "ratio_{disc}");
|
assert_eq!(templates.get("ratio").unwrap(), "ratio_{disc}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ impl LanguageSyntax for JavaScriptSyntax {
|
|||||||
format!("_m({}, '{}')", var_name, template)
|
format!("_m({}, '{}')", var_name, template)
|
||||||
} else {
|
} else {
|
||||||
// Template with {disc}: use nested _m for proper separator handling
|
// Template with {disc}: use nested _m for proper separator handling
|
||||||
// "ratio_{disc}_bps" → split on {disc} → _m(_m(acc, 'ratio'), disc) then _bps
|
// "ratio_{disc}_ppm" → split on {disc} → _m(_m(acc, 'ratio'), disc) then _ppm
|
||||||
// But this is complex. For embedded disc, use string interpolation.
|
// But this is complex. For embedded disc, use string interpolation.
|
||||||
// For suffix disc (ends with {disc}), use _m composition.
|
// For suffix disc (ends with {disc}), use _m composition.
|
||||||
if let Some(static_part) = template.strip_suffix("{disc}") {
|
if let Some(static_part) = template.strip_suffix("{disc}") {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use brk_cohort::{
|
use brk_cohort::{
|
||||||
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, EPOCH_NAMES, LOSS_NAMES, OVER_AGE_NAMES,
|
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, ENTRY_NAMES, EPOCH_NAMES, LOSS_NAMES,
|
||||||
OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES,
|
OVER_AGE_NAMES, OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES,
|
||||||
UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
|
SPENDABLE_TYPE_NAMES, TERM_NAMES, UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
|
||||||
};
|
};
|
||||||
use brk_types::{Index, pools};
|
use brk_types::{Index, pools};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -59,6 +59,7 @@ impl CohortConstants {
|
|||||||
("TERM_NAMES", to_value(&TERM_NAMES)),
|
("TERM_NAMES", to_value(&TERM_NAMES)),
|
||||||
("EPOCH_NAMES", to_value(&EPOCH_NAMES)),
|
("EPOCH_NAMES", to_value(&EPOCH_NAMES)),
|
||||||
("CLASS_NAMES", to_value(&CLASS_NAMES)),
|
("CLASS_NAMES", to_value(&CLASS_NAMES)),
|
||||||
|
("ENTRY_NAMES", to_value(&ENTRY_NAMES)),
|
||||||
("SPENDABLE_TYPE_NAMES", to_value(&SPENDABLE_TYPE_NAMES)),
|
("SPENDABLE_TYPE_NAMES", to_value(&SPENDABLE_TYPE_NAMES)),
|
||||||
("AGE_RANGE_NAMES", to_value(&AGE_RANGE_NAMES)),
|
("AGE_RANGE_NAMES", to_value(&AGE_RANGE_NAMES)),
|
||||||
("UNDER_AGE_NAMES", to_value(&UNDER_AGE_NAMES)),
|
("UNDER_AGE_NAMES", to_value(&UNDER_AGE_NAMES)),
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
|||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void, cache?: boolean }}}} [options]",
|
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void, cache?: boolean, memCache?: boolean }}}} [options]",
|
||||||
return_type
|
return_type
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -60,22 +60,22 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
|||||||
|
|
||||||
let params = build_method_params(endpoint);
|
let params = build_method_params(endpoint);
|
||||||
let params_with_opts = if params.is_empty() {
|
let params_with_opts = if params.is_empty() {
|
||||||
"{ signal, onValue, cache } = {}".to_string()
|
"{ signal, onValue, cache, memCache } = {}".to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{}, {{ signal, onValue, cache }} = {{}}", params)
|
format!("{}, {{ signal, onValue, cache, memCache }} = {{}}", params)
|
||||||
};
|
};
|
||||||
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
|
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
|
||||||
|
|
||||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||||
|
|
||||||
let fetch_call: String = if endpoint.returns_binary() {
|
let fetch_call: String = if endpoint.returns_binary() {
|
||||||
"this.getBytes(path, { signal, onValue, cache })".to_string()
|
"this.getBytes(path, { signal, onValue, cache, memCache })".to_string()
|
||||||
} else if endpoint.returns_json() {
|
} else if endpoint.returns_json() {
|
||||||
"this.getJson(path, { signal, onValue, cache })".to_string()
|
"this.getJson(path, { signal, onValue, cache, memCache })".to_string()
|
||||||
} else if endpoint.response_kind.text_is_numeric() {
|
} else if endpoint.response_kind.text_is_numeric() {
|
||||||
"Number(await this.getText(path, { signal, cache, onValue: onValue ? (v) => onValue(Number(v)) : undefined }))".to_string()
|
"Number(await this.getText(path, { signal, cache, memCache, onValue: onValue ? (v) => onValue(Number(v)) : undefined }))".to_string()
|
||||||
} else {
|
} else {
|
||||||
"this.getText(path, { signal, onValue, cache })".to_string()
|
"this.getText(path, { signal, onValue, cache, memCache })".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
write_path_assignment(output, endpoint, &path);
|
write_path_assignment(output, endpoint, &path);
|
||||||
@@ -83,7 +83,7 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
|||||||
if endpoint.supports_csv {
|
if endpoint.supports_csv {
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
" if (format === 'csv') return this.getText(path, {{ signal, onValue, cache }});"
|
" if (format === 'csv') return this.getText(path, {{ signal, onValue, cache, memCache }});"
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ const _openBrowserCache = (option) => {{
|
|||||||
return caches.open(name).catch(() => null);
|
return caches.open(name).catch(() => null);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{string}} url
|
||||||
|
* @returns {{URL}}
|
||||||
|
*/
|
||||||
|
const _parseBaseUrl = (url) => new URL(url, typeof location === 'undefined' ? undefined : location.href);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom error class for BRK client errors
|
* Custom error class for BRK client errors
|
||||||
*/
|
*/
|
||||||
@@ -237,8 +243,8 @@ function _wrapSeriesData(raw) {{
|
|||||||
* @property {{(n: number) => RangeBuilder<T>}} first - Get first n items
|
* @property {{(n: number) => RangeBuilder<T>}} first - Get first n items
|
||||||
* @property {{(n: number) => RangeBuilder<T>}} last - Get last n items
|
* @property {{(n: number) => RangeBuilder<T>}} last - Get last n items
|
||||||
* @property {{(n: number) => SkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
* @property {{(n: number) => SkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
||||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch all data
|
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch all data
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||||
* @property {{() => Promise<number>}} len - Get total number of data points
|
* @property {{() => Promise<number>}} len - Get total number of data points
|
||||||
* @property {{() => Promise<Version>}} version - Get the current version of the series
|
* @property {{() => Promise<Version>}} version - Get the current version of the series
|
||||||
* @property {{Thenable<T>}} then - Thenable (await endpoint)
|
* @property {{Thenable<T>}} then - Thenable (await endpoint)
|
||||||
@@ -253,8 +259,8 @@ function _wrapSeriesData(raw) {{
|
|||||||
* @property {{(n: number) => DateRangeBuilder<T>}} first - Get first n items
|
* @property {{(n: number) => DateRangeBuilder<T>}} first - Get first n items
|
||||||
* @property {{(n: number) => DateRangeBuilder<T>}} last - Get last n items
|
* @property {{(n: number) => DateRangeBuilder<T>}} last - Get last n items
|
||||||
* @property {{(n: number) => DateSkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
* @property {{(n: number) => DateSkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
||||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch all data
|
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch all data
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||||
* @property {{() => Promise<number>}} len - Get total number of data points
|
* @property {{() => Promise<number>}} len - Get total number of data points
|
||||||
* @property {{() => Promise<Version>}} version - Get the current version of the series
|
* @property {{() => Promise<Version>}} version - Get the current version of the series
|
||||||
* @property {{DateThenable<T>}} then - Thenable (await endpoint)
|
* @property {{DateThenable<T>}} then - Thenable (await endpoint)
|
||||||
@@ -263,41 +269,53 @@ function _wrapSeriesData(raw) {{
|
|||||||
|
|
||||||
/** @typedef {{SeriesEndpoint<any>}} AnySeriesEndpoint */
|
/** @typedef {{SeriesEndpoint<any>}} AnySeriesEndpoint */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {{Object}} ClientFetchOptions
|
||||||
|
* @property {{AbortSignal}} [signal] - Abort this request
|
||||||
|
* @property {{boolean}} [cache] - Use HTTP/browser/client caches. Set false for a no-store network fetch.
|
||||||
|
* @property {{boolean}} [memCache] - Use the parsed in-memory response cache. Set false for large one-shot reads.
|
||||||
|
* @property {{(value: T) => void}} [onValue] - Receive stale/fresh values as they arrive
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @template T @typedef {{ClientFetchOptions<SeriesData<T>> | ((value: SeriesData<T>) => void)}} SeriesFetchArg */
|
||||||
|
/** @template T @typedef {{ClientFetchOptions<DateSeriesData<T>> | ((value: DateSeriesData<T>) => void)}} DateSeriesFetchArg */
|
||||||
|
|
||||||
/** @template T @typedef {{Object}} SingleItemBuilder
|
/** @template T @typedef {{Object}} SingleItemBuilder
|
||||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch the item
|
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch the item
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||||
* @property {{Thenable<T>}} then - Thenable
|
* @property {{Thenable<T>}} then - Thenable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @template T @typedef {{Object}} DateSingleItemBuilder
|
/** @template T @typedef {{Object}} DateSingleItemBuilder
|
||||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch the item
|
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch the item
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||||
* @property {{DateThenable<T>}} then - Thenable
|
* @property {{DateThenable<T>}} then - Thenable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @template T @typedef {{Object}} SkippedBuilder
|
/** @template T @typedef {{Object}} SkippedBuilder
|
||||||
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items after skipped position
|
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items after skipped position
|
||||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch from skipped position to end
|
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch from skipped position to end
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||||
* @property {{Thenable<T>}} then - Thenable
|
* @property {{Thenable<T>}} then - Thenable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @template T @typedef {{Object}} DateSkippedBuilder
|
/** @template T @typedef {{Object}} DateSkippedBuilder
|
||||||
* @property {{(n: number) => DateRangeBuilder<T>}} take - Take n items after skipped position
|
* @property {{(n: number) => DateRangeBuilder<T>}} take - Take n items after skipped position
|
||||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch from skipped position to end
|
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch from skipped position to end
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||||
* @property {{DateThenable<T>}} then - Thenable
|
* @property {{DateThenable<T>}} then - Thenable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @template T @typedef {{Object}} RangeBuilder
|
/** @template T @typedef {{Object}} RangeBuilder
|
||||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch the range
|
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch the range
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||||
* @property {{Thenable<T>}} then - Thenable
|
* @property {{Thenable<T>}} then - Thenable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @template T @typedef {{Object}} DateRangeBuilder
|
/** @template T @typedef {{Object}} DateRangeBuilder
|
||||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch the range
|
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch the range
|
||||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||||
* @property {{DateThenable<T>}} then - Thenable
|
* @property {{DateThenable<T>}} then - Thenable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -344,8 +362,8 @@ function _endpoint(client, name, index) {{
|
|||||||
* @returns {{DateRangeBuilder<T>}}
|
* @returns {{DateRangeBuilder<T>}}
|
||||||
*/
|
*/
|
||||||
const rangeBuilder = (start, end) => ({{
|
const rangeBuilder = (start, end) => ({{
|
||||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(start, end), onValue); }},
|
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(start, end), arg, options); }},
|
||||||
fetchCsv() {{ return client.getText(buildPath(start, end, 'csv')); }},
|
fetchCsv(options) {{ return client.getText(buildPath(start, end, 'csv'), options); }},
|
||||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -354,8 +372,8 @@ function _endpoint(client, name, index) {{
|
|||||||
* @returns {{DateSingleItemBuilder<T>}}
|
* @returns {{DateSingleItemBuilder<T>}}
|
||||||
*/
|
*/
|
||||||
const singleItemBuilder = (idx) => ({{
|
const singleItemBuilder = (idx) => ({{
|
||||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(idx, idx + 1), onValue); }},
|
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(idx, idx + 1), arg, options); }},
|
||||||
fetchCsv() {{ return client.getText(buildPath(idx, idx + 1, 'csv')); }},
|
fetchCsv(options) {{ return client.getText(buildPath(idx, idx + 1, 'csv'), options); }},
|
||||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -365,8 +383,8 @@ function _endpoint(client, name, index) {{
|
|||||||
*/
|
*/
|
||||||
const skippedBuilder = (start) => ({{
|
const skippedBuilder = (start) => ({{
|
||||||
take(n) {{ return rangeBuilder(start, start + n); }},
|
take(n) {{ return rangeBuilder(start, start + n); }},
|
||||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(start, undefined), onValue); }},
|
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(start, undefined), arg, options); }},
|
||||||
fetchCsv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
|
fetchCsv(options) {{ return client.getText(buildPath(start, undefined, 'csv'), options); }},
|
||||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -381,8 +399,8 @@ function _endpoint(client, name, index) {{
|
|||||||
first(n) {{ return rangeBuilder(undefined, n); }},
|
first(n) {{ return rangeBuilder(undefined, n); }},
|
||||||
last(n) {{ return n === 0 ? rangeBuilder(undefined, 0) : rangeBuilder(-n, undefined); }},
|
last(n) {{ return n === 0 ? rangeBuilder(undefined, 0) : rangeBuilder(-n, undefined); }},
|
||||||
skip(n) {{ return skippedBuilder(n); }},
|
skip(n) {{ return skippedBuilder(n); }},
|
||||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(), onValue); }},
|
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(), arg, options); }},
|
||||||
fetchCsv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
|
fetchCsv(options) {{ return client.getText(buildPath(undefined, undefined, 'csv'), options); }},
|
||||||
len() {{ return client.getSeriesLen(name, index); }},
|
len() {{ return client.getSeriesLen(name, index); }},
|
||||||
version() {{ return client.getSeriesVersion(name, index); }},
|
version() {{ return client.getSeriesVersion(name, index); }},
|
||||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||||
@@ -403,6 +421,9 @@ class BrkClientBase {{
|
|||||||
const isString = typeof options === 'string';
|
const isString = typeof options === 'string';
|
||||||
const rawUrl = isString ? options : options.baseUrl;
|
const rawUrl = isString ? options : options.baseUrl;
|
||||||
this.baseUrl = rawUrl.endsWith('/') ? rawUrl.slice(0, -1) : rawUrl;
|
this.baseUrl = rawUrl.endsWith('/') ? rawUrl.slice(0, -1) : rawUrl;
|
||||||
|
const url = _parseBaseUrl(this.baseUrl);
|
||||||
|
this.url = url.href.endsWith('/') ? url.href.slice(0, -1) : url.href;
|
||||||
|
this.domain = url.hostname;
|
||||||
this.timeout = isString ? 5000 : (options.timeout ?? 5000);
|
this.timeout = isString ? 5000 : (options.timeout ?? 5000);
|
||||||
/** @type {{Promise<Cache | null>}} */
|
/** @type {{Promise<Cache | null>}} */
|
||||||
this._browserCachePromise = _openBrowserCache(isString ? undefined : options.browserCache);
|
this._browserCachePromise = _openBrowserCache(isString ? undefined : options.browserCache);
|
||||||
@@ -478,10 +499,10 @@ class BrkClientBase {{
|
|||||||
* @template T
|
* @template T
|
||||||
* @param {{string}} path
|
* @param {{string}} path
|
||||||
* @param {{(res: Response) => Promise<T>}} parse - Response body reader
|
* @param {{(res: Response) => Promise<T>}} parse - Response body reader
|
||||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
* @param {{ClientFetchOptions<T>}} [options]
|
||||||
* @returns {{Promise<T>}}
|
* @returns {{Promise<T>}}
|
||||||
*/
|
*/
|
||||||
async _getCached(path, parse, {{ onValue, signal, cache = true }} = {{}}) {{
|
async _getCached(path, parse, {{ onValue, signal, cache = true, memCache = true }} = {{}}) {{
|
||||||
if (!cache) {{
|
if (!cache) {{
|
||||||
const res = await this.get(path, {{ signal, cache }});
|
const res = await this.get(path, {{ signal, cache }});
|
||||||
const value = await parse(res);
|
const value = await parse(res);
|
||||||
@@ -490,8 +511,9 @@ class BrkClientBase {{
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
const url = `${{this.baseUrl}}${{path}}`;
|
const url = `${{this.baseUrl}}${{path}}`;
|
||||||
|
const useMemCache = memCache !== false;
|
||||||
/** @type {{_MemEntry<T> | undefined}} */
|
/** @type {{_MemEntry<T> | undefined}} */
|
||||||
const memHit = this._memGet(url);
|
const memHit = useMemCache ? this._memGet(url) : undefined;
|
||||||
const browserCache = this._browserCache;
|
const browserCache = this._browserCache;
|
||||||
|
|
||||||
// L1 fast path: deliver from memCache, revalidate via network.
|
// L1 fast path: deliver from memCache, revalidate via network.
|
||||||
@@ -504,7 +526,7 @@ class BrkClientBase {{
|
|||||||
if (netEtag && netEtag === memHit.etag) return memHit.value;
|
if (netEtag && netEtag === memHit.etag) return memHit.value;
|
||||||
const cloned = browserCache ? res.clone() : null;
|
const cloned = browserCache ? res.clone() : null;
|
||||||
const value = await parse(res);
|
const value = await parse(res);
|
||||||
this._memSet(url, netEtag, value);
|
if (useMemCache) this._memSet(url, netEtag, value);
|
||||||
if (onValue) onValue(value);
|
if (onValue) onValue(value);
|
||||||
if (cloned && browserCache) {{
|
if (cloned && browserCache) {{
|
||||||
const cacheStore = browserCache;
|
const cacheStore = browserCache;
|
||||||
@@ -523,7 +545,7 @@ class BrkClientBase {{
|
|||||||
if (!res || networkSettled) return null;
|
if (!res || networkSettled) return null;
|
||||||
const value = await parse(res);
|
const value = await parse(res);
|
||||||
if (networkSettled) return value;
|
if (networkSettled) return value;
|
||||||
this._memSet(url, res.headers.get('ETag'), value);
|
if (useMemCache) this._memSet(url, res.headers.get('ETag'), value);
|
||||||
onValue(value);
|
onValue(value);
|
||||||
return value;
|
return value;
|
||||||
}}).catch(() => null)
|
}}).catch(() => null)
|
||||||
@@ -534,11 +556,11 @@ class BrkClientBase {{
|
|||||||
networkSettled = true;
|
networkSettled = true;
|
||||||
const netEtag = res.headers.get('ETag');
|
const netEtag = res.headers.get('ETag');
|
||||||
// Stale won and populated memCache with matching ETag → reuse, skip parse + second onValue.
|
// Stale won and populated memCache with matching ETag → reuse, skip parse + second onValue.
|
||||||
const populated = /** @type {{_MemEntry<T> | undefined}} */ (this._memGet(url));
|
const populated = useMemCache ? /** @type {{_MemEntry<T> | undefined}} */ (this._memGet(url)) : undefined;
|
||||||
if (populated && netEtag && netEtag === populated.etag) return populated.value;
|
if (populated && netEtag && netEtag === populated.etag) return populated.value;
|
||||||
const cloned = browserCache ? res.clone() : null;
|
const cloned = browserCache ? res.clone() : null;
|
||||||
const value = await parse(res);
|
const value = await parse(res);
|
||||||
this._memSet(url, netEtag, value);
|
if (useMemCache) this._memSet(url, netEtag, value);
|
||||||
if (onValue) onValue(value);
|
if (onValue) onValue(value);
|
||||||
if (cloned && browserCache) {{
|
if (cloned && browserCache) {{
|
||||||
const cacheStore = browserCache;
|
const cacheStore = browserCache;
|
||||||
@@ -556,7 +578,7 @@ class BrkClientBase {{
|
|||||||
* Make a GET request expecting a JSON response. Cached and supports `onValue`.
|
* Make a GET request expecting a JSON response. Cached and supports `onValue`.
|
||||||
* @template T
|
* @template T
|
||||||
* @param {{string}} path
|
* @param {{string}} path
|
||||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
* @param {{ClientFetchOptions<T>}} [options]
|
||||||
* @returns {{Promise<T>}}
|
* @returns {{Promise<T>}}
|
||||||
*/
|
*/
|
||||||
getJson(path, options) {{
|
getJson(path, options) {{
|
||||||
@@ -567,7 +589,7 @@ class BrkClientBase {{
|
|||||||
* Make a GET request expecting a text response (text/plain, text/csv, ...).
|
* Make a GET request expecting a text response (text/plain, text/csv, ...).
|
||||||
* Cached and supports `onValue`, same as `getJson`.
|
* Cached and supports `onValue`, same as `getJson`.
|
||||||
* @param {{string}} path
|
* @param {{string}} path
|
||||||
* @param {{{{ onValue?: (value: string) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
* @param {{ClientFetchOptions<string>}} [options]
|
||||||
* @returns {{Promise<string>}}
|
* @returns {{Promise<string>}}
|
||||||
*/
|
*/
|
||||||
getText(path, options) {{
|
getText(path, options) {{
|
||||||
@@ -578,7 +600,7 @@ class BrkClientBase {{
|
|||||||
* Make a GET request expecting binary data (application/octet-stream).
|
* Make a GET request expecting binary data (application/octet-stream).
|
||||||
* Cached and supports `onValue`, same as `getJson`.
|
* Cached and supports `onValue`, same as `getJson`.
|
||||||
* @param {{string}} path
|
* @param {{string}} path
|
||||||
* @param {{{{ onValue?: (value: Uint8Array) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
* @param {{ClientFetchOptions<Uint8Array>}} [options]
|
||||||
* @returns {{Promise<Uint8Array>}}
|
* @returns {{Promise<Uint8Array>}}
|
||||||
*/
|
*/
|
||||||
getBytes(path, options) {{
|
getBytes(path, options) {{
|
||||||
@@ -650,12 +672,17 @@ class BrkClientBase {{
|
|||||||
* Fetch series data and wrap with helper methods (internal)
|
* Fetch series data and wrap with helper methods (internal)
|
||||||
* @template T
|
* @template T
|
||||||
* @param {{string}} path
|
* @param {{string}} path
|
||||||
* @param {{(value: DateSeriesData<T>) => void}} [onValue]
|
* @param {{DateSeriesFetchArg<T>}} [arg]
|
||||||
|
* @param {{ClientFetchOptions<DateSeriesData<T>>}} [options]
|
||||||
* @returns {{Promise<DateSeriesData<T>>}}
|
* @returns {{Promise<DateSeriesData<T>>}}
|
||||||
*/
|
*/
|
||||||
async _fetchSeriesData(path, onValue) {{
|
async _fetchSeriesData(path, arg, options) {{
|
||||||
|
const requestOptions = typeof arg === 'function'
|
||||||
|
? {{ ...(options ?? {{}}), onValue: arg }}
|
||||||
|
: {{ ...(arg ?? {{}}), ...(options ?? {{}}) }};
|
||||||
|
const onValue = requestOptions.onValue;
|
||||||
const wrappedOnValue = onValue ? (/** @type {{SeriesData<T>}} */ raw) => onValue(_wrapSeriesData(raw)) : undefined;
|
const wrappedOnValue = onValue ? (/** @type {{SeriesData<T>}} */ raw) => onValue(_wrapSeriesData(raw)) : undefined;
|
||||||
const raw = await this.getJson(path, {{ onValue: wrappedOnValue }});
|
const raw = await this.getJson(path, {{ ...requestOptions, onValue: wrappedOnValue }});
|
||||||
return _wrapSeriesData(raw);
|
return _wrapSeriesData(raw);
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
@@ -679,6 +706,160 @@ const _p = (prefix, acc) => acc ? `${{prefix}}_${{acc}}` : prefix;
|
|||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
output.push_str(r##"
|
||||||
|
const _MASK_64 = 0xffffffffffffffffn;
|
||||||
|
const _RAPIDHASH_SECRETS = /** @type {const} */ ([
|
||||||
|
0x2d358dccaa6c78a5n,
|
||||||
|
0x8bb84b93962eacc9n,
|
||||||
|
0x4b33a62ed433d4a3n,
|
||||||
|
0x4d5a2da51de1aa47n,
|
||||||
|
0xa0761d6478bd642fn,
|
||||||
|
0xe7037ed1a0b428dbn,
|
||||||
|
0x90ed1765281c388cn,
|
||||||
|
]);
|
||||||
|
const _RAPIDHASH_SEED = _rapidHashSeed(0n);
|
||||||
|
|
||||||
|
/** @param {bigint} value */
|
||||||
|
function _u64(value) {
|
||||||
|
return value & _MASK_64;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {bigint} left @param {bigint} right */
|
||||||
|
function _rapidMix(left, right) {
|
||||||
|
const result = _u64(left) * _u64(right);
|
||||||
|
return _u64(result) ^ _u64(result >> 64n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {bigint} left @param {bigint} right @returns {[bigint, bigint]} */
|
||||||
|
function _rapidMum(left, right) {
|
||||||
|
const result = _u64(left) * _u64(right);
|
||||||
|
return [_u64(result), _u64(result >> 64n)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {bigint} seed */
|
||||||
|
function _rapidHashSeed(seed) {
|
||||||
|
return _u64(seed ^ _rapidMix(seed ^ _RAPIDHASH_SECRETS[2], _RAPIDHASH_SECRETS[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Uint8Array} bytes @param {number} offset */
|
||||||
|
function _readU32(bytes, offset) {
|
||||||
|
return (
|
||||||
|
BigInt(bytes[offset]) |
|
||||||
|
(BigInt(bytes[offset + 1]) << 8n) |
|
||||||
|
(BigInt(bytes[offset + 2]) << 16n) |
|
||||||
|
(BigInt(bytes[offset + 3]) << 24n)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Uint8Array} bytes @param {number} offset */
|
||||||
|
function _readU64(bytes, offset) {
|
||||||
|
return _readU32(bytes, offset) | (_readU32(bytes, offset + 4) << 32n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload */
|
||||||
|
function _asUint8Array(payload) {
|
||||||
|
if (payload instanceof Uint8Array) return payload;
|
||||||
|
if (payload instanceof ArrayBuffer) return new Uint8Array(payload);
|
||||||
|
if (ArrayBuffer.isView(payload)) return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
|
||||||
|
if (Array.isArray(payload)) return new Uint8Array(payload);
|
||||||
|
throw new Error("Expected address payload bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload */
|
||||||
|
function _rapidHashV3(payload) {
|
||||||
|
const bytes = _asUint8Array(payload);
|
||||||
|
const length = bytes.length;
|
||||||
|
if (length === 0) throw new Error("Expected a non-empty address payload");
|
||||||
|
if (length > 65) throw new Error("Expected at most 65 address payload bytes");
|
||||||
|
|
||||||
|
let seed = _RAPIDHASH_SEED;
|
||||||
|
let a = 0n;
|
||||||
|
let b = 0n;
|
||||||
|
let remainder;
|
||||||
|
|
||||||
|
if (length <= 16) {
|
||||||
|
if (length >= 4) {
|
||||||
|
seed ^= BigInt(length);
|
||||||
|
if (length >= 8) {
|
||||||
|
a ^= _readU64(bytes, 0);
|
||||||
|
b ^= _readU64(bytes, length - 8);
|
||||||
|
} else {
|
||||||
|
a ^= _readU32(bytes, 0);
|
||||||
|
b ^= _readU32(bytes, length - 4);
|
||||||
|
}
|
||||||
|
} else if (length > 0) {
|
||||||
|
a ^= (BigInt(bytes[0]) << 45n) | BigInt(bytes[length - 1]);
|
||||||
|
b ^= BigInt(bytes[length >> 1]);
|
||||||
|
}
|
||||||
|
remainder = BigInt(length);
|
||||||
|
} else {
|
||||||
|
seed = _rapidMix(_readU64(bytes, 0) ^ _RAPIDHASH_SECRETS[2], _readU64(bytes, 8) ^ seed);
|
||||||
|
if (length > 32) {
|
||||||
|
seed = _rapidMix(_readU64(bytes, 16) ^ _RAPIDHASH_SECRETS[2], _readU64(bytes, 24) ^ seed);
|
||||||
|
if (length > 48) {
|
||||||
|
seed = _rapidMix(_readU64(bytes, 32) ^ _RAPIDHASH_SECRETS[1], _readU64(bytes, 40) ^ seed);
|
||||||
|
if (length > 64) {
|
||||||
|
seed = _rapidMix(_readU64(bytes, 48) ^ _RAPIDHASH_SECRETS[1], _readU64(bytes, 56) ^ seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remainder = BigInt(length);
|
||||||
|
a ^= _readU64(bytes, length - 16) ^ remainder;
|
||||||
|
b ^= _readU64(bytes, length - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
a ^= _RAPIDHASH_SECRETS[1];
|
||||||
|
b ^= seed;
|
||||||
|
[a, b] = _rapidMum(a, b);
|
||||||
|
return _rapidMix(a ^ 0xaaaaaaaaaaaaaaaan, b ^ _RAPIDHASH_SECRETS[1] ^ remainder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {number} nibbles */
|
||||||
|
function _validateHashPrefixNibbles(nibbles) {
|
||||||
|
if (!Number.isInteger(nibbles) || nibbles < 1 || nibbles > 16) {
|
||||||
|
throw new Error("Expected hash-prefix length from 1 to 16 hex nibbles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {OutputType} addrType @returns {number[]} */
|
||||||
|
function _addressPayloadLengths(addrType) {
|
||||||
|
switch (addrType) {
|
||||||
|
case "p2a": return [2];
|
||||||
|
case "p2pk": return [33, 65];
|
||||||
|
case "p2pkh":
|
||||||
|
case "p2sh":
|
||||||
|
case "v0_p2wpkh": return [20];
|
||||||
|
case "v0_p2wsh":
|
||||||
|
case "v1_p2tr": return [32];
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported address type for address payload hash-prefix: ${addrType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {OutputType} addrType
|
||||||
|
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload
|
||||||
|
*/
|
||||||
|
function _validateAddressPayloadForType(addrType, payload) {
|
||||||
|
const length = _asUint8Array(payload).length;
|
||||||
|
const expected = _addressPayloadLengths(addrType);
|
||||||
|
if (!expected.includes(length)) {
|
||||||
|
throw new Error(`Expected ${addrType} address payload length ${expected.join(" or ")} bytes`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`.
|
||||||
|
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload - Raw address payload bytes
|
||||||
|
* @param {number} nibbles - Prefix length from 1 to 16 hex nibbles
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function addressPayloadHashPrefix(payload, nibbles) {
|
||||||
|
_validateHashPrefixNibbles(nibbles);
|
||||||
|
return _rapidHashV3(payload).toString(16).padStart(16, "0").slice(0, nibbles);
|
||||||
|
}
|
||||||
|
|
||||||
|
"##);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate static constants for the BrkClient class.
|
/// Generate static constants for the BrkClient class.
|
||||||
|
|||||||
@@ -111,6 +111,32 @@ pub fn generate_main_client(
|
|||||||
writeln!(output, " this.series = this._buildTree();").unwrap();
|
writeln!(output, " this.series = this._buildTree();").unwrap();
|
||||||
writeln!(output, " }}\n").unwrap();
|
writeln!(output, " }}\n").unwrap();
|
||||||
|
|
||||||
|
output.push_str(r##" /**
|
||||||
|
* Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||||
|
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload
|
||||||
|
* @param {number} nibbles
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static addressPayloadHashPrefix(payload, nibbles) {
|
||||||
|
return addressPayloadHashPrefix(payload, nibbles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch address hash-prefix matches from raw address payload bytes.
|
||||||
|
* @param {OutputType} addrType
|
||||||
|
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload - Raw payload bytes matching addrType length
|
||||||
|
* @param {number} nibbles
|
||||||
|
* @param {{ signal?: AbortSignal, onValue?: (value: AddrHashPrefixMatches) => void, cache?: boolean, memCache?: boolean }} [options]
|
||||||
|
* @returns {Promise<AddrHashPrefixMatches>}
|
||||||
|
*/
|
||||||
|
getAddressPayloadHashPrefixMatches(addrType, payload, nibbles, options = {}) {
|
||||||
|
_validateAddressPayloadForType(addrType, payload);
|
||||||
|
const prefix = addressPayloadHashPrefix(payload, nibbles);
|
||||||
|
return this.getAddressHashPrefixMatches(addrType, prefix, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
"##);
|
||||||
|
|
||||||
writeln!(output, " /**").unwrap();
|
writeln!(output, " /**").unwrap();
|
||||||
writeln!(output, " * @private").unwrap();
|
writeln!(output, " * @private").unwrap();
|
||||||
writeln!(output, " * @returns {{SeriesTree}}").unwrap();
|
writeln!(output, " * @returns {{SeriesTree}}").unwrap();
|
||||||
@@ -161,7 +187,11 @@ pub fn generate_main_client(
|
|||||||
|
|
||||||
writeln!(output, "}}\n").unwrap();
|
writeln!(output, "}}\n").unwrap();
|
||||||
|
|
||||||
writeln!(output, "export {{ BrkClient, BrkError }};").unwrap();
|
writeln!(
|
||||||
|
output,
|
||||||
|
"export {{ BrkClient, BrkError, addressPayloadHashPrefix }};"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|||||||
@@ -84,6 +84,23 @@ pub fn generate_main_client(output: &mut String, endpoints: &[Endpoint]) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
writeln!(output, " return _date_to_index(index, d)").unwrap();
|
writeln!(output, " return _date_to_index(index, d)").unwrap();
|
||||||
writeln!(output).unwrap();
|
writeln!(output).unwrap();
|
||||||
|
|
||||||
|
output.push_str(r#" @staticmethod
|
||||||
|
def address_payload_hash_prefix(payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str:
|
||||||
|
"""Compute the RapidHash v3 hash-prefix for raw address payload bytes."""
|
||||||
|
return address_payload_hash_prefix(payload, nibbles)
|
||||||
|
|
||||||
|
def get_address_payload_hash_prefix_matches(
|
||||||
|
self,
|
||||||
|
addr_type: OutputType,
|
||||||
|
payload: Union[bytes, bytearray, memoryview],
|
||||||
|
nibbles: int,
|
||||||
|
) -> AddrHashPrefixMatches:
|
||||||
|
"""Fetch address hash-prefix matches from raw payload bytes matching addr_type length."""
|
||||||
|
_validate_address_payload_for_type(addr_type, payload)
|
||||||
|
return self.get_address_hash_prefix_matches(addr_type, address_payload_hash_prefix(payload, nibbles))
|
||||||
|
|
||||||
|
"#);
|
||||||
// Generate API methods
|
// Generate API methods
|
||||||
generate_api_methods(output, endpoints);
|
generate_api_methods(output, endpoints);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,125 @@ def _p(prefix: str, acc: str) -> str:
|
|||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
output.push_str(r#"
|
||||||
|
_MASK_64 = (1 << 64) - 1
|
||||||
|
_RAPIDHASH_SECRETS = (
|
||||||
|
0x2d358dccaa6c78a5,
|
||||||
|
0x8bb84b93962eacc9,
|
||||||
|
0x4b33a62ed433d4a3,
|
||||||
|
0x4d5a2da51de1aa47,
|
||||||
|
0xa0761d6478bd642f,
|
||||||
|
0xe7037ed1a0b428db,
|
||||||
|
0x90ed1765281c388c,
|
||||||
|
)
|
||||||
|
_RAPIDHASH_SEED = 0
|
||||||
|
|
||||||
|
|
||||||
|
def _u64(value: int) -> int:
|
||||||
|
return value & _MASK_64
|
||||||
|
|
||||||
|
|
||||||
|
def _rapid_mix(left: int, right: int) -> int:
|
||||||
|
result = _u64(left) * _u64(right)
|
||||||
|
return _u64(result) ^ _u64(result >> 64)
|
||||||
|
|
||||||
|
|
||||||
|
def _rapid_mum(left: int, right: int) -> Tuple[int, int]:
|
||||||
|
result = _u64(left) * _u64(right)
|
||||||
|
return _u64(result), _u64(result >> 64)
|
||||||
|
|
||||||
|
|
||||||
|
def _rapid_hash_seed(seed: int) -> int:
|
||||||
|
return _u64(seed ^ _rapid_mix(seed ^ _RAPIDHASH_SECRETS[2], _RAPIDHASH_SECRETS[1]))
|
||||||
|
|
||||||
|
|
||||||
|
_RAPIDHASH_SEED = _rapid_hash_seed(0)
|
||||||
|
|
||||||
|
|
||||||
|
def _read_u32(data: bytes, offset: int) -> int:
|
||||||
|
return int.from_bytes(data[offset:offset + 4], "little")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_u64(data: bytes, offset: int) -> int:
|
||||||
|
return int.from_bytes(data[offset:offset + 8], "little")
|
||||||
|
|
||||||
|
|
||||||
|
def _rapid_hash_v3(payload: Union[bytes, bytearray, memoryview]) -> int:
|
||||||
|
data = bytes(payload)
|
||||||
|
length = len(data)
|
||||||
|
if length == 0:
|
||||||
|
raise ValueError("Expected a non-empty address payload")
|
||||||
|
if length > 65:
|
||||||
|
raise ValueError("Expected at most 65 address payload bytes")
|
||||||
|
|
||||||
|
seed = _RAPIDHASH_SEED
|
||||||
|
a = 0
|
||||||
|
b = 0
|
||||||
|
|
||||||
|
if length <= 16:
|
||||||
|
if length >= 4:
|
||||||
|
seed ^= length
|
||||||
|
if length >= 8:
|
||||||
|
a ^= _read_u64(data, 0)
|
||||||
|
b ^= _read_u64(data, length - 8)
|
||||||
|
else:
|
||||||
|
a ^= _read_u32(data, 0)
|
||||||
|
b ^= _read_u32(data, length - 4)
|
||||||
|
elif length > 0:
|
||||||
|
a ^= (data[0] << 45) | data[length - 1]
|
||||||
|
b ^= data[length >> 1]
|
||||||
|
remainder = length
|
||||||
|
else:
|
||||||
|
if length > 16:
|
||||||
|
seed = _rapid_mix(_read_u64(data, 0) ^ _RAPIDHASH_SECRETS[2], _read_u64(data, 8) ^ seed)
|
||||||
|
if length > 32:
|
||||||
|
seed = _rapid_mix(_read_u64(data, 16) ^ _RAPIDHASH_SECRETS[2], _read_u64(data, 24) ^ seed)
|
||||||
|
if length > 48:
|
||||||
|
seed = _rapid_mix(_read_u64(data, 32) ^ _RAPIDHASH_SECRETS[1], _read_u64(data, 40) ^ seed)
|
||||||
|
if length > 64:
|
||||||
|
seed = _rapid_mix(_read_u64(data, 48) ^ _RAPIDHASH_SECRETS[1], _read_u64(data, 56) ^ seed)
|
||||||
|
remainder = length
|
||||||
|
a ^= _read_u64(data, length - 16) ^ remainder
|
||||||
|
b ^= _read_u64(data, length - 8)
|
||||||
|
|
||||||
|
a ^= _RAPIDHASH_SECRETS[1]
|
||||||
|
b ^= seed
|
||||||
|
a, b = _rapid_mum(a, b)
|
||||||
|
return _rapid_mix(a ^ 0xaaaaaaaaaaaaaaaa, b ^ _RAPIDHASH_SECRETS[1] ^ remainder)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_hash_prefix_nibbles(nibbles: int) -> None:
|
||||||
|
if isinstance(nibbles, bool) or not isinstance(nibbles, int) or nibbles < 1 or nibbles > 16:
|
||||||
|
raise ValueError("Expected hash-prefix length from 1 to 16 hex nibbles")
|
||||||
|
|
||||||
|
|
||||||
|
def _address_payload_lengths(addr_type: OutputType) -> Tuple[int, ...]:
|
||||||
|
if addr_type == "p2a":
|
||||||
|
return (2,)
|
||||||
|
if addr_type == "p2pk":
|
||||||
|
return (33, 65)
|
||||||
|
if addr_type in ("p2pkh", "p2sh", "v0_p2wpkh"):
|
||||||
|
return (20,)
|
||||||
|
if addr_type in ("v0_p2wsh", "v1_p2tr"):
|
||||||
|
return (32,)
|
||||||
|
raise ValueError(f"Unsupported address type for address payload hash-prefix: {addr_type}")
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_address_payload_for_type(addr_type: OutputType, payload: Union[bytes, bytearray, memoryview]) -> None:
|
||||||
|
length = len(bytes(payload))
|
||||||
|
expected = _address_payload_lengths(addr_type)
|
||||||
|
if length not in expected:
|
||||||
|
joined = " or ".join(str(value) for value in expected)
|
||||||
|
raise ValueError(f"Expected {addr_type} address payload length {joined} bytes")
|
||||||
|
|
||||||
|
|
||||||
|
def address_payload_hash_prefix(payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str:
|
||||||
|
"""Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`."""
|
||||||
|
_validate_hash_prefix_nibbles(nibbles)
|
||||||
|
return f"{_rapid_hash_v3(payload):016x}"[:nibbles]
|
||||||
|
|
||||||
|
|
||||||
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the SeriesData and SeriesEndpoint classes
|
/// Generate the SeriesData and SeriesEndpoint classes
|
||||||
|
|||||||
@@ -76,6 +76,36 @@ impl BrkClient {{
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
output.push_str(r#" /// Decode a mainnet Bitcoin address into the BRK address type and raw payload bytes.
|
||||||
|
pub fn decode_address_payload(address: &str) -> Result<AddressPayload> {
|
||||||
|
decode_address_payload(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||||
|
pub fn address_payload_hash_prefix(payload: &[u8], nibbles: usize) -> Result<String> {
|
||||||
|
address_payload_hash_prefix(payload, nibbles)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a mainnet Bitcoin address and compute its hash prefix.
|
||||||
|
pub fn address_hash_prefix(address: &str, nibbles: usize) -> Result<AddressHashPrefix> {
|
||||||
|
address_hash_prefix(address, nibbles)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch address hash-prefix matches from raw payload bytes matching `addr_type` length.
|
||||||
|
pub fn get_address_payload_hash_prefix_matches(&self, addr_type: OutputType, payload: &[u8], nibbles: usize) -> Result<AddrHashPrefixMatches> {
|
||||||
|
validate_address_payload_for_type(addr_type, payload)?;
|
||||||
|
let prefix = address_payload_hash_prefix(payload, nibbles)?;
|
||||||
|
self.get_address_hash_prefix_matches(addr_type, &prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch address hash-prefix matches for a mainnet Bitcoin address.
|
||||||
|
pub fn get_address_hash_prefix_matches_for_address(&self, address: &str, nibbles: usize) -> Result<AddrHashPrefixMatches> {
|
||||||
|
let hashed = address_hash_prefix(address, nibbles)?;
|
||||||
|
self.get_address_hash_prefix_matches(hashed.addr_type, &hashed.prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
"#);
|
||||||
|
|
||||||
generate_api_methods(output, endpoints);
|
generate_api_methods(output, endpoints);
|
||||||
|
|
||||||
writeln!(output, "}}").unwrap();
|
writeln!(output, "}}").unwrap();
|
||||||
@@ -118,6 +148,23 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
|||||||
"get_text"
|
"get_text"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if endpoint.path == "/api/address/hash-prefix/{addr_type}/{prefix}" {
|
||||||
|
writeln!(
|
||||||
|
output,
|
||||||
|
" let addr_type = address_payload_type_path(addr_type)?;"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(
|
||||||
|
output,
|
||||||
|
" self.base.{}(&format!(\"{}\"{}))",
|
||||||
|
fetch_method, path, index_arg
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
writeln!(output, " }}").unwrap();
|
||||||
|
writeln!(output).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if endpoint.query_params.is_empty() {
|
if endpoint.query_params.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ use crate::{
|
|||||||
pub fn generate_imports(output: &mut String) {
|
pub fn generate_imports(output: &mut String) {
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
r#"use std::sync::Arc;
|
r#"use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::ops::{{Bound, RangeBounds}};
|
use std::ops::{{Bound, RangeBounds}};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
pub use brk_cohort::*;
|
pub use brk_cohort::*;
|
||||||
@@ -43,6 +44,97 @@ impl std::error::Error for BrkError {{}}
|
|||||||
/// Result type for BRK client operations.
|
/// Result type for BRK client operations.
|
||||||
pub type Result<T> = std::result::Result<T, BrkError>;
|
pub type Result<T> = std::result::Result<T, BrkError>;
|
||||||
|
|
||||||
|
/// BRK address type and raw payload bytes used by the hash-prefix index.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AddressPayload {{
|
||||||
|
pub addr_type: OutputType,
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}}
|
||||||
|
|
||||||
|
/// BRK address type and leading hex nibbles of the address-payload hash.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AddressHashPrefix {{
|
||||||
|
pub addr_type: OutputType,
|
||||||
|
pub prefix: String,
|
||||||
|
}}
|
||||||
|
|
||||||
|
/// Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{{addr_type}}/{{prefix}}`.
|
||||||
|
pub fn address_payload_hash_prefix(payload: &[u8], nibbles: usize) -> Result<String> {{
|
||||||
|
if payload.is_empty() {{
|
||||||
|
return Err(BrkError {{ message: "Expected a non-empty address payload".to_string() }});
|
||||||
|
}}
|
||||||
|
if payload.len() > 65 {{
|
||||||
|
return Err(BrkError {{ message: "Expected at most 65 address payload bytes".to_string() }});
|
||||||
|
}}
|
||||||
|
if !(1..=16).contains(&nibbles) {{
|
||||||
|
return Err(BrkError {{ message: "Expected hash-prefix length from 1 to 16 hex nibbles".to_string() }});
|
||||||
|
}}
|
||||||
|
Ok(format!("{{:016x}}", rapidhash::v3::rapidhash_v3(payload))[..nibbles].to_string())
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> Result<()> {{
|
||||||
|
let expected: &[usize] = match addr_type {{
|
||||||
|
OutputType::P2A => &[2],
|
||||||
|
OutputType::P2PK33 => &[33],
|
||||||
|
OutputType::P2PK65 => &[65],
|
||||||
|
OutputType::P2PKH | OutputType::P2SH | OutputType::P2WPKH => &[20],
|
||||||
|
OutputType::P2WSH | OutputType::P2TR => &[32],
|
||||||
|
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {{
|
||||||
|
return Err(BrkError {{ message: format!("Unsupported address type for address payload hash-prefix: {{addr_type:?}}") }});
|
||||||
|
}},
|
||||||
|
}};
|
||||||
|
let addr_type = address_payload_type_path(addr_type)?;
|
||||||
|
|
||||||
|
if !expected.contains(&payload.len()) {{
|
||||||
|
let joined = expected
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" or ");
|
||||||
|
return Err(BrkError {{ message: format!("Expected {{addr_type}} address payload length {{joined}} bytes") }});
|
||||||
|
}}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}}
|
||||||
|
|
||||||
|
fn address_payload_type_path(addr_type: OutputType) -> Result<&'static str> {{
|
||||||
|
match addr_type {{
|
||||||
|
OutputType::P2A => Ok("p2a"),
|
||||||
|
OutputType::P2PK33 | OutputType::P2PK65 => Ok("p2pk"),
|
||||||
|
OutputType::P2PKH => Ok("p2pkh"),
|
||||||
|
OutputType::P2SH => Ok("p2sh"),
|
||||||
|
OutputType::P2WPKH => Ok("v0_p2wpkh"),
|
||||||
|
OutputType::P2WSH => Ok("v0_p2wsh"),
|
||||||
|
OutputType::P2TR => Ok("v1_p2tr"),
|
||||||
|
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {{
|
||||||
|
Err(BrkError {{ message: format!("Unsupported address type for address payload hash-prefix: {{addr_type:?}}") }})
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
/// Decode a mainnet Bitcoin address into the BRK address type and raw payload bytes.
|
||||||
|
pub fn decode_address_payload(address: &str) -> Result<AddressPayload> {{
|
||||||
|
if address.is_empty() {{
|
||||||
|
return Err(BrkError {{ message: "Expected an address string".to_string() }});
|
||||||
|
}}
|
||||||
|
let addr_bytes = AddrBytes::from_str(address).map_err(|e| BrkError {{ message: e.to_string() }})?;
|
||||||
|
let addr_type = OutputType::from(&addr_bytes);
|
||||||
|
|
||||||
|
Ok(AddressPayload {{
|
||||||
|
addr_type,
|
||||||
|
payload: addr_bytes.as_slice().to_vec(),
|
||||||
|
}})
|
||||||
|
}}
|
||||||
|
|
||||||
|
/// Decode a mainnet Bitcoin address and compute its hash prefix.
|
||||||
|
pub fn address_hash_prefix(address: &str, nibbles: usize) -> Result<AddressHashPrefix> {{
|
||||||
|
let decoded = decode_address_payload(address)?;
|
||||||
|
Ok(AddressHashPrefix {{
|
||||||
|
addr_type: decoded.addr_type,
|
||||||
|
prefix: address_payload_hash_prefix(&decoded.payload, nibbles)?,
|
||||||
|
}})
|
||||||
|
}}
|
||||||
|
|
||||||
/// Options for configuring the BRK client.
|
/// Options for configuring the BRK client.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BrkClientOptions {{
|
pub struct BrkClientOptions {{
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ pub trait LanguageSyntax {
|
|||||||
/// - `"pct99"` (static) → `'pct99'` (JS) / `"pct99".to_string()` (Rust)
|
/// - `"pct99"` (static) → `'pct99'` (JS) / `"pct99".to_string()` (Rust)
|
||||||
/// - `""` (empty) → `disc` (pass parent's disc through)
|
/// - `""` (empty) → `disc` (pass parent's disc through)
|
||||||
/// - `"p1sd{disc}"` (suffix) → `_m('p1sd', disc)` (composed)
|
/// - `"p1sd{disc}"` (suffix) → `_m('p1sd', disc)` (composed)
|
||||||
/// - `"ratio_{disc}_bps"` (embedded) → `` `ratio_${disc}_bps` `` (template literal)
|
/// - `"ratio_{disc}_ppm"` (embedded) → `` `ratio_${disc}_ppm` `` (template literal)
|
||||||
fn disc_arg_expr(&self, template: &str) -> String;
|
fn disc_arg_expr(&self, template: &str) -> String;
|
||||||
|
|
||||||
/// Format a templated mode expression: substitute `{disc}` at runtime.
|
/// Format a templated mode expression: substitute `{disc}` at runtime.
|
||||||
@@ -117,6 +117,6 @@ pub trait LanguageSyntax {
|
|||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `acc_var` - The accumulator variable (e.g., "acc")
|
/// * `acc_var` - The accumulator variable (e.g., "acc")
|
||||||
/// * `template` - Template like `"ratio_{disc}_bps"` or `"{disc}"`
|
/// * `template` - Template like `"ratio_{disc}_ppm"` or `"{disc}"`
|
||||||
fn template_expr(&self, acc_var: &str, template: &str) -> String;
|
fn template_expr(&self, acc_var: &str, template: &str) -> String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub enum PatternMode {
|
|||||||
/// Fields construct series names using a template with a discriminator placeholder.
|
/// Fields construct series names using a template with a discriminator placeholder.
|
||||||
/// Factory takes two params: `acc` (base) and `disc` (discriminator).
|
/// Factory takes two params: `acc` (base) and `disc` (discriminator).
|
||||||
/// Formula: `_m(acc, template.replace("{disc}", disc))`
|
/// Formula: `_m(acc, template.replace("{disc}", disc))`
|
||||||
/// Example: template `"ratio_{disc}_bps"` with disc `"pct99"` → `_m(acc, "ratio_pct99_bps")`
|
/// Example: template `"ratio_{disc}_ppm"` with disc `"pct99"` → `_m(acc, "ratio_pct99_ppm")`
|
||||||
Templated {
|
Templated {
|
||||||
/// Maps field name to its template string containing `{disc}` placeholder
|
/// Maps field name to its template string containing `{disc}` placeholder
|
||||||
templates: BTreeMap<String, String>,
|
templates: BTreeMap<String, String>,
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl StructuralPattern {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the discriminator value by matching a template against a concrete string.
|
/// 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"`.
|
/// E.g., template `"ratio_{disc}_ppm"` matched against `"ratio_pct99_ppm"` yields `"pct99"`.
|
||||||
fn extract_disc(template: &str, value: &str) -> Option<String> {
|
fn extract_disc(template: &str, value: &str) -> Option<String> {
|
||||||
let (prefix, suffix) = template.split_once("{disc}")?;
|
let (prefix, suffix) = template.split_once("{disc}")?;
|
||||||
if value.starts_with(prefix) && value.ends_with(suffix) {
|
if value.starts_with(prefix) && value.ends_with(suffix) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ owo-colors = { workspace = true }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
toml = "1.1.2"
|
toml = "1.1.3"
|
||||||
vecdb = { workspace = true }
|
vecdb = { workspace = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ exclude = ["examples/"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
brk_cohort = { workspace = true }
|
brk_cohort = { workspace = true }
|
||||||
brk_types = { workspace = true }
|
brk_types = { workspace = true }
|
||||||
|
rapidhash = { workspace = true }
|
||||||
ureq = { workspace = true }
|
ureq = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ fn main() -> brk_client::Result<()> {
|
|||||||
// day1() returns DateMetricEndpointBuilder, so fetch() returns DateMetricData
|
// day1() returns DateMetricEndpointBuilder, so fetch() returns DateMetricData
|
||||||
let price_close = client
|
let price_close = client
|
||||||
.series()
|
.series()
|
||||||
.prices
|
.price
|
||||||
.split
|
.split
|
||||||
.close
|
.close
|
||||||
.usd
|
.usd
|
||||||
|
|||||||
+1822
-746
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
|||||||
|
use brk_client::{
|
||||||
|
BrkClient, OutputType, address_hash_prefix, address_payload_hash_prefix, decode_address_payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn address_payload_hash_prefix_vectors() {
|
||||||
|
let vectors = [
|
||||||
|
(vec![0x4e, 0x73], "58101afa51a1ecfd"),
|
||||||
|
((0_u8..20).collect::<Vec<_>>(), "c3327ecb8ae1ff23"),
|
||||||
|
((0_u8..32).collect::<Vec<_>>(), "c0186990f026b180"),
|
||||||
|
((0_u8..65).collect::<Vec<_>>(), "0d4b77027ae7d700"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (payload, expected) in vectors.iter() {
|
||||||
|
assert_eq!(address_payload_hash_prefix(payload, 16).unwrap(), *expected);
|
||||||
|
assert_eq!(
|
||||||
|
BrkClient::address_payload_hash_prefix(payload, 8).unwrap(),
|
||||||
|
&expected[..8]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn address_payload_hash_prefix_validation() {
|
||||||
|
assert!(address_payload_hash_prefix(&[], 16).is_err());
|
||||||
|
assert!(address_payload_hash_prefix(&[0; 66], 16).is_err());
|
||||||
|
assert!(address_payload_hash_prefix(&[1, 2], 0).is_err());
|
||||||
|
assert!(address_payload_hash_prefix(&[1, 2], 17).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn address_hash_prefix_uses_brk_address_parser() {
|
||||||
|
let address = "1BoatSLRHtKNngkdXEeobR76b53LETtpyT";
|
||||||
|
let decoded = decode_address_payload(address).unwrap();
|
||||||
|
assert_eq!(decoded.addr_type, OutputType::P2PKH);
|
||||||
|
assert_eq!(decoded.payload.len(), 20);
|
||||||
|
|
||||||
|
let hashed = address_hash_prefix(address, 8).unwrap();
|
||||||
|
assert_eq!(hashed.addr_type, OutputType::P2PKH);
|
||||||
|
assert_eq!(
|
||||||
|
hashed.prefix,
|
||||||
|
address_payload_hash_prefix(&decoded.payload, 8).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
use brk_traversable::Traversable;
|
||||||
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::{CohortName, Filter};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum EntryPrice {
|
||||||
|
Discount,
|
||||||
|
Premium,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntryPrice {
|
||||||
|
#[inline]
|
||||||
|
pub const fn from_is_discount(is_discount: bool) -> Self {
|
||||||
|
if is_discount {
|
||||||
|
Self::Discount
|
||||||
|
} else {
|
||||||
|
Self::Premium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub const fn is_discount(self) -> bool {
|
||||||
|
matches!(self, Self::Discount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ENTRY_FILTERS: ByEntry<Filter> = ByEntry {
|
||||||
|
discount: Filter::Entry(EntryPrice::Discount),
|
||||||
|
premium: Filter::Entry(EntryPrice::Premium),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ENTRY_NAMES: ByEntry<CohortName> = ByEntry {
|
||||||
|
discount: CohortName::new("veteran", "Veteran", "Veteran Coins"),
|
||||||
|
premium: CohortName::new("rookie", "Rookie", "Rookie Coins"),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Traversable, Serialize)]
|
||||||
|
pub struct ByEntry<T> {
|
||||||
|
pub discount: T,
|
||||||
|
pub premium: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ByEntry<CohortName> {
|
||||||
|
pub const fn names() -> &'static Self {
|
||||||
|
&ENTRY_NAMES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ByEntry<T> {
|
||||||
|
pub fn new<F>(mut create: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(Filter, &'static str) -> T,
|
||||||
|
{
|
||||||
|
let f = ENTRY_FILTERS;
|
||||||
|
let n = ENTRY_NAMES;
|
||||||
|
Self {
|
||||||
|
discount: create(f.discount, n.discount.id),
|
||||||
|
premium: create(f.premium, n.premium.id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||||
|
where
|
||||||
|
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||||
|
{
|
||||||
|
let f = ENTRY_FILTERS;
|
||||||
|
let n = ENTRY_NAMES;
|
||||||
|
Ok(Self {
|
||||||
|
discount: create(f.discount, n.discount.id)?,
|
||||||
|
premium: create(f.premium, n.premium.id)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, entry: EntryPrice) -> &T {
|
||||||
|
match entry {
|
||||||
|
EntryPrice::Discount => &self.discount,
|
||||||
|
EntryPrice::Premium => &self.premium,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, entry: EntryPrice) -> &mut T {
|
||||||
|
match entry {
|
||||||
|
EntryPrice::Discount => &mut self.discount,
|
||||||
|
EntryPrice::Premium => &mut self.premium,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||||
|
[&self.discount, &self.premium].into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||||
|
[&mut self.discount, &mut self.premium].into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut T>
|
||||||
|
where
|
||||||
|
T: Send + Sync,
|
||||||
|
{
|
||||||
|
[&mut self.discount, &mut self.premium].into_par_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ impl CohortContext {
|
|||||||
/// Build full name for a filter, adding prefix only for Time/Amount filters.
|
/// Build full name for a filter, adding prefix only for Time/Amount filters.
|
||||||
///
|
///
|
||||||
/// Prefix rules:
|
/// Prefix rules:
|
||||||
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Type`
|
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Entry`, `Type`
|
||||||
/// - Context prefix: `Time`, `Amount`
|
/// - Context prefix: `Time`, `Amount`
|
||||||
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
|
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
|
||||||
match filter {
|
match filter {
|
||||||
@@ -32,6 +32,7 @@ impl CohortContext {
|
|||||||
| Filter::Term(_)
|
| Filter::Term(_)
|
||||||
| Filter::Epoch(_)
|
| Filter::Epoch(_)
|
||||||
| Filter::Class(_)
|
| Filter::Class(_)
|
||||||
|
| Filter::Entry(_)
|
||||||
| Filter::Type(_) => name.to_string(),
|
| Filter::Type(_) => name.to_string(),
|
||||||
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
|
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_types::{Halving, OutputType, Sats, Year};
|
use brk_types::{Halving, OutputType, Sats, Year};
|
||||||
|
|
||||||
use super::{AmountFilter, CohortContext, Term, TimeFilter};
|
use super::{AmountFilter, CohortContext, EntryPrice, Term, TimeFilter};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Filter {
|
pub enum Filter {
|
||||||
@@ -10,6 +10,7 @@ pub enum Filter {
|
|||||||
Amount(AmountFilter),
|
Amount(AmountFilter),
|
||||||
Epoch(Halving),
|
Epoch(Halving),
|
||||||
Class(Year),
|
Class(Year),
|
||||||
|
Entry(EntryPrice),
|
||||||
Type(OutputType),
|
Type(OutputType),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +69,8 @@ impl Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to compute extended metrics (realized cap ratios, profit/loss ratios, percentiles)
|
/// Whether to compute extended metrics (realized cap ratios, profit/loss ratios, percentiles)
|
||||||
/// For UTXO context: true only for age range cohorts (Range) and aggregate cohorts (All, Term)
|
/// For UTXO context: true for age range cohorts (Range), aggregate cohorts (All, Term),
|
||||||
|
/// and immutable entry valuation cohorts.
|
||||||
/// For address context: always false
|
/// For address context: always false
|
||||||
pub fn is_extended(&self, context: CohortContext) -> bool {
|
pub fn is_extended(&self, context: CohortContext) -> bool {
|
||||||
match context {
|
match context {
|
||||||
@@ -76,7 +78,10 @@ impl Filter {
|
|||||||
CohortContext::Utxo => {
|
CohortContext::Utxo => {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
Filter::All | Filter::Term(_) | Filter::Time(TimeFilter::Range(_))
|
Filter::All
|
||||||
|
| Filter::Term(_)
|
||||||
|
| Filter::Time(TimeFilter::Range(_))
|
||||||
|
| Filter::Entry(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ mod amount_range;
|
|||||||
mod by_addr_type;
|
mod by_addr_type;
|
||||||
mod by_any_addr;
|
mod by_any_addr;
|
||||||
mod by_epoch;
|
mod by_epoch;
|
||||||
|
mod by_entry;
|
||||||
mod by_term;
|
mod by_term;
|
||||||
mod by_type;
|
mod by_type;
|
||||||
mod class;
|
mod class;
|
||||||
@@ -36,6 +37,7 @@ pub use amount_range::*;
|
|||||||
pub use by_addr_type::*;
|
pub use by_addr_type::*;
|
||||||
pub use by_any_addr::*;
|
pub use by_any_addr::*;
|
||||||
pub use by_epoch::*;
|
pub use by_epoch::*;
|
||||||
|
pub use by_entry::*;
|
||||||
pub use by_term::*;
|
pub use by_term::*;
|
||||||
pub use by_type::*;
|
pub use by_type::*;
|
||||||
pub use class::*;
|
pub use class::*;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use brk_traversable::Traversable;
|
|||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AgeRange, AmountRange, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount, SpendableType,
|
AgeRange, AmountRange, ByEntry, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount,
|
||||||
UnderAge, UnderAmount,
|
SpendableType, UnderAge, UnderAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Clone, Traversable)]
|
#[derive(Default, Clone, Traversable)]
|
||||||
@@ -12,6 +12,7 @@ pub struct UTXOGroups<T> {
|
|||||||
pub age_range: AgeRange<T>,
|
pub age_range: AgeRange<T>,
|
||||||
pub epoch: ByEpoch<T>,
|
pub epoch: ByEpoch<T>,
|
||||||
pub class: Class<T>,
|
pub class: Class<T>,
|
||||||
|
pub entry: ByEntry<T>,
|
||||||
pub over_age: OverAge<T>,
|
pub over_age: OverAge<T>,
|
||||||
pub over_amount: OverAmount<T>,
|
pub over_amount: OverAmount<T>,
|
||||||
pub amount_range: AmountRange<T>,
|
pub amount_range: AmountRange<T>,
|
||||||
@@ -31,6 +32,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
age_range: AgeRange::new(&mut create),
|
age_range: AgeRange::new(&mut create),
|
||||||
epoch: ByEpoch::new(&mut create),
|
epoch: ByEpoch::new(&mut create),
|
||||||
class: Class::new(&mut create),
|
class: Class::new(&mut create),
|
||||||
|
entry: ByEntry::new(&mut create),
|
||||||
over_age: OverAge::new(&mut create),
|
over_age: OverAge::new(&mut create),
|
||||||
over_amount: OverAmount::new(&mut create),
|
over_amount: OverAmount::new(&mut create),
|
||||||
amount_range: AmountRange::new(&mut create),
|
amount_range: AmountRange::new(&mut create),
|
||||||
@@ -51,6 +53,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
.chain(self.age_range.iter())
|
.chain(self.age_range.iter())
|
||||||
.chain(self.epoch.iter())
|
.chain(self.epoch.iter())
|
||||||
.chain(self.class.iter())
|
.chain(self.class.iter())
|
||||||
|
.chain(self.entry.iter())
|
||||||
.chain(self.amount_range.iter())
|
.chain(self.amount_range.iter())
|
||||||
.chain(self.under_amount.iter())
|
.chain(self.under_amount.iter())
|
||||||
.chain(self.type_.iter())
|
.chain(self.type_.iter())
|
||||||
@@ -66,6 +69,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
.chain(self.age_range.iter_mut())
|
.chain(self.age_range.iter_mut())
|
||||||
.chain(self.epoch.iter_mut())
|
.chain(self.epoch.iter_mut())
|
||||||
.chain(self.class.iter_mut())
|
.chain(self.class.iter_mut())
|
||||||
|
.chain(self.entry.iter_mut())
|
||||||
.chain(self.amount_range.iter_mut())
|
.chain(self.amount_range.iter_mut())
|
||||||
.chain(self.under_amount.iter_mut())
|
.chain(self.under_amount.iter_mut())
|
||||||
.chain(self.type_.iter_mut())
|
.chain(self.type_.iter_mut())
|
||||||
@@ -84,6 +88,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
.chain(self.age_range.par_iter_mut())
|
.chain(self.age_range.par_iter_mut())
|
||||||
.chain(self.epoch.par_iter_mut())
|
.chain(self.epoch.par_iter_mut())
|
||||||
.chain(self.class.par_iter_mut())
|
.chain(self.class.par_iter_mut())
|
||||||
|
.chain(self.entry.par_iter_mut())
|
||||||
.chain(self.amount_range.par_iter_mut())
|
.chain(self.amount_range.par_iter_mut())
|
||||||
.chain(self.under_amount.par_iter_mut())
|
.chain(self.under_amount.par_iter_mut())
|
||||||
.chain(self.type_.par_iter_mut())
|
.chain(self.type_.par_iter_mut())
|
||||||
@@ -94,6 +99,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
.iter()
|
.iter()
|
||||||
.chain(self.epoch.iter())
|
.chain(self.epoch.iter())
|
||||||
.chain(self.class.iter())
|
.chain(self.class.iter())
|
||||||
|
.chain(self.entry.iter())
|
||||||
.chain(self.amount_range.iter())
|
.chain(self.amount_range.iter())
|
||||||
.chain(self.type_.iter())
|
.chain(self.type_.iter())
|
||||||
}
|
}
|
||||||
@@ -103,6 +109,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.chain(self.epoch.iter_mut())
|
.chain(self.epoch.iter_mut())
|
||||||
.chain(self.class.iter_mut())
|
.chain(self.class.iter_mut())
|
||||||
|
.chain(self.entry.iter_mut())
|
||||||
.chain(self.amount_range.iter_mut())
|
.chain(self.amount_range.iter_mut())
|
||||||
.chain(self.type_.iter_mut())
|
.chain(self.type_.iter_mut())
|
||||||
}
|
}
|
||||||
@@ -115,6 +122,7 @@ impl<T> UTXOGroups<T> {
|
|||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.chain(self.epoch.par_iter_mut())
|
.chain(self.epoch.par_iter_mut())
|
||||||
.chain(self.class.par_iter_mut())
|
.chain(self.class.par_iter_mut())
|
||||||
|
.chain(self.entry.par_iter_mut())
|
||||||
.chain(self.amount_range.par_iter_mut())
|
.chain(self.amount_range.par_iter_mut())
|
||||||
.chain(self.type_.par_iter_mut())
|
.chain(self.type_.par_iter_mut())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ impl Vecs {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let starting_height = indexer.safe_lengths().height;
|
let starting_height = indexer.safe_lengths().height;
|
||||||
self.adjustment.bps.height.compute_ratio_change(
|
self.adjustment.raw.height.compute_ratio_change(
|
||||||
starting_height,
|
starting_height,
|
||||||
&indexer.vecs.blocks.difficulty,
|
&indexer.vecs.blocks.difficulty,
|
||||||
2016,
|
2016,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, Epoch, StoredF32, StoredF64, StoredU32};
|
use brk_types::{Epoch, PartsPerMillionSigned32, StoredF32, StoredF64, StoredU32};
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Resolutions};
|
use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Resolutions};
|
||||||
@@ -7,7 +7,7 @@ use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Resolutions};
|
|||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
pub value: Resolutions<StoredF64>,
|
pub value: Resolutions<StoredF64>,
|
||||||
pub hashrate: LazyPerBlock<StoredF64>,
|
pub hashrate: LazyPerBlock<StoredF64>,
|
||||||
pub adjustment: PercentPerBlock<BasisPointsSigned32, M>,
|
pub adjustment: PercentPerBlock<PartsPerMillionSigned32, M>,
|
||||||
pub epoch: PerBlock<Epoch, M>,
|
pub epoch: PerBlock<Epoch, M>,
|
||||||
pub blocks_to_retarget: PerBlock<StoredU32, M>,
|
pub blocks_to_retarget: PerBlock<StoredU32, M>,
|
||||||
pub days_to_retarget: LazyPerBlock<StoredF32, StoredU32>,
|
pub days_to_retarget: LazyPerBlock<StoredF32, StoredU32>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
use brk_types::BasisPoints16;
|
use brk_types::PartsPerMillion32;
|
||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
@@ -8,10 +8,10 @@ use super::Vecs;
|
|||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
|
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
|
||||||
let starting_height = indexer.safe_lengths().height;
|
let starting_height = indexer.safe_lengths().height;
|
||||||
self.fullness.bps.compute_transform(
|
self.fullness.raw.compute_transform(
|
||||||
starting_height,
|
starting_height,
|
||||||
&indexer.vecs.blocks.weight,
|
&indexer.vecs.blocks.weight,
|
||||||
|(h, weight, ..)| (h, BasisPoints16::from(weight.fullness())),
|
|(h, weight, ..)| (h, PartsPerMillion32::from(weight.fullness())),
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, StoredU64, Weight};
|
use brk_types::{PartsPerMillion32, StoredU64, Weight};
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{LazyPerBlockRolling, PercentVec};
|
use crate::internal::{LazyPerBlockRolling, PercentVec};
|
||||||
@@ -7,5 +7,5 @@ use crate::internal::{LazyPerBlockRolling, PercentVec};
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
pub weight: LazyPerBlockRolling<Weight, StoredU64>,
|
pub weight: LazyPerBlockRolling<Weight, StoredU64>,
|
||||||
pub fullness: PercentVec<BasisPoints16, M>,
|
pub fullness: PercentVec<PartsPerMillion32, M>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
use brk_types::BasisPointsSigned32;
|
use brk_types::PartsPerMillionSigned64;
|
||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::super::activity;
|
use super::super::activity;
|
||||||
@@ -17,14 +17,14 @@ impl Vecs {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let starting_height = indexer.safe_lengths().height;
|
let starting_height = indexer.safe_lengths().height;
|
||||||
|
|
||||||
self.inflation_rate.bps.height.compute_transform2(
|
self.inflation_rate.raw.height.compute_transform2(
|
||||||
starting_height,
|
starting_height,
|
||||||
&activity.ratio.height,
|
&activity.ratio.height,
|
||||||
&supply.inflation_rate.bps.height,
|
&supply.inflation_rate.raw.height,
|
||||||
|(h, a2vr, inflation, ..)| {
|
|(h, a2vr, inflation, ..)| {
|
||||||
(
|
(
|
||||||
h,
|
h,
|
||||||
BasisPointsSigned32::from(f64::from(a2vr) * f64::from(inflation)),
|
PartsPerMillionSigned64::from(f64::from(a2vr) * f64::from(inflation)),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
exit,
|
exit,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ impl Vecs {
|
|||||||
inflation_rate: PercentPerBlock::forced_import(
|
inflation_rate: PercentPerBlock::forced_import(
|
||||||
db,
|
db,
|
||||||
"cointime_adj_inflation_rate",
|
"cointime_adj_inflation_rate",
|
||||||
version + Version::ONE,
|
version + Version::TWO,
|
||||||
indexes,
|
indexes,
|
||||||
)?,
|
)?,
|
||||||
tx_velocity_native: PerBlock::forced_import(
|
tx_velocity_native: PerBlock::forced_import(
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, StoredF64};
|
use brk_types::{PartsPerMillionSigned64, StoredF64};
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{PerBlock, PercentPerBlock};
|
use crate::internal::{PerBlock, PercentPerBlock};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
pub inflation_rate: PercentPerBlock<BasisPointsSigned32, M>,
|
pub inflation_rate: PercentPerBlock<PartsPerMillionSigned64, M>,
|
||||||
pub tx_velocity_native: PerBlock<StoredF64, M>,
|
pub tx_velocity_native: PerBlock<StoredF64, M>,
|
||||||
pub tx_velocity_fiat: PerBlock<StoredF64, M>,
|
pub tx_velocity_fiat: PerBlock<StoredF64, M>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints32, Cents};
|
use brk_types::{Cents, PartsPerMillion32};
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{FiatPerBlock, RatioPerBlock};
|
use crate::internal::{FiatPerBlock, RatioPerBlock};
|
||||||
@@ -11,5 +11,5 @@ pub struct Vecs<M: StorageMode = Rw> {
|
|||||||
pub vaulted: FiatPerBlock<Cents, M>,
|
pub vaulted: FiatPerBlock<Cents, M>,
|
||||||
pub active: FiatPerBlock<Cents, M>,
|
pub active: FiatPerBlock<Cents, M>,
|
||||||
pub cointime: FiatPerBlock<Cents, M>,
|
pub cointime: FiatPerBlock<Cents, M>,
|
||||||
pub aviv: RatioPerBlock<BasisPoints32, M>,
|
pub aviv: RatioPerBlock<PartsPerMillion32, M>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ use brk_indexer::Indexer;
|
|||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{blocks, distribution, mining, prices, supply};
|
use crate::{blocks, distribution, mining, price, supply};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
mining: &mining::Vecs,
|
mining: &mining::Vecs,
|
||||||
supply_vecs: &supply::Vecs,
|
supply_vecs: &supply::Vecs,
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use vecdb::Exit;
|
|||||||
|
|
||||||
use super::super::{activity, cap, supply};
|
use super::super::{activity, cap, supply};
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{distribution, prices};
|
use crate::{distribution, price};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
activity: &activity::Vecs,
|
activity: &activity::Vecs,
|
||||||
supply: &supply::Vecs,
|
supply: &supply::Vecs,
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ use brk_types::StoredF64;
|
|||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::{super::value, Vecs};
|
use super::{super::value, Vecs};
|
||||||
use crate::{blocks, internal::algo::ComputeRollingMedianFromStarts, prices};
|
use crate::{blocks, internal::algo::ComputeRollingMedianFromStarts, price};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
value: &value::Vecs,
|
value: &value::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use vecdb::Exit;
|
|||||||
|
|
||||||
use super::super::activity;
|
use super::super::activity;
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{distribution, prices};
|
use crate::{distribution, price};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
activity: &activity::Vecs,
|
activity: &activity::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ use vecdb::Exit;
|
|||||||
|
|
||||||
use super::super::activity;
|
use super::super::activity;
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{distribution, prices};
|
use crate::{distribution, price};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
activity: &activity::Vecs,
|
activity: &activity::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, StoredI64, StoredU64, Version};
|
use brk_types::{PartsPerMillionSigned64, StoredI64, StoredU64, Version};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::AddrCountsVecs;
|
use super::AddrCountsVecs;
|
||||||
|
|
||||||
type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, BasisPointsSigned32>;
|
type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, PartsPerMillionSigned64>;
|
||||||
|
|
||||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||||
pub struct DeltaVecs(#[traversable(flatten)] pub WithAddrTypes<AddrDelta>);
|
pub struct DeltaVecs(#[traversable(flatten)] pub WithAddrTypes<AddrDelta>);
|
||||||
@@ -21,7 +21,7 @@ impl DeltaVecs {
|
|||||||
cached_starts: &Windows<&WindowStartVec>,
|
cached_starts: &Windows<&WindowStartVec>,
|
||||||
indexes: &indexes::Vecs,
|
indexes: &indexes::Vecs,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let version = version + Version::TWO;
|
let version = version + Version::new(3);
|
||||||
|
|
||||||
let all = LazyRollingDeltasFromHeight::new(
|
let all = LazyRollingDeltasFromHeight::new(
|
||||||
"addr_count",
|
"addr_count",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ use super::{
|
|||||||
count::AddrCountFundedTotalVecs,
|
count::AddrCountFundedTotalVecs,
|
||||||
supply::{AddrSupplyShareVecs, AddrSupplyVecs},
|
supply::{AddrSupplyShareVecs, AddrSupplyVecs},
|
||||||
};
|
};
|
||||||
use crate::{indexes, prices};
|
use crate::{indexes, price};
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ impl ExposedAddrVecs {
|
|||||||
pub(crate) fn compute_rest(
|
pub(crate) fn compute_rest(
|
||||||
&mut self,
|
&mut self,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use brk_cohort::ByAddrType;
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, OutputType, StoredF32, StoredU32, StoredU64, Version};
|
use brk_types::{OutputType, PartsPerMillion32, StoredF32, StoredU32, StoredU64, Version};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
@@ -67,11 +67,11 @@ use super::state::AddrTypeToAddrEventCount;
|
|||||||
pub struct AddrEventsVecs<M: StorageMode = Rw> {
|
pub struct AddrEventsVecs<M: StorageMode = Rw> {
|
||||||
pub output_to_reused_addr_count:
|
pub output_to_reused_addr_count:
|
||||||
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||||
pub output_to_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
|
pub output_to_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32, M>>,
|
||||||
pub spendable_output_to_reused_addr_share: PercentCumulativeRolling<BasisPoints16, M>,
|
pub spendable_output_to_reused_addr_share: PercentCumulativeRolling<PartsPerMillion32, M>,
|
||||||
pub input_from_reused_addr_count:
|
pub input_from_reused_addr_count:
|
||||||
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||||
pub input_from_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
|
pub input_from_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32, M>>,
|
||||||
pub active_reused_addr_count: PerBlockRollingAverage<StoredU32, StoredU64, M>,
|
pub active_reused_addr_count: PerBlockRollingAverage<StoredU32, StoredU64, M>,
|
||||||
pub active_reused_addr_share: PerBlockRollingAverage<StoredF32, StoredF32, M>,
|
pub active_reused_addr_share: PerBlockRollingAverage<StoredF32, StoredF32, M>,
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ impl AddrEventsVecs {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
let import_percent =
|
let import_percent =
|
||||||
|name: &str| -> Result<WithAddrTypes<PercentCumulativeRolling<BasisPoints16>>> {
|
|name: &str| -> Result<WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32>>> {
|
||||||
Ok(WithAddrTypes {
|
Ok(WithAddrTypes {
|
||||||
all: PercentCumulativeRolling::forced_import(db, name, version, indexes)?,
|
all: PercentCumulativeRolling::forced_import(db, name, version, indexes)?,
|
||||||
by_addr_type: ByAddrType::new_with_name(|type_name| {
|
by_addr_type: ByAddrType::new_with_name(|type_name| {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ use super::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
indexes, inputs,
|
indexes, inputs,
|
||||||
internal::{WindowStartVec, Windows},
|
internal::{WindowStartVec, Windows},
|
||||||
outputs, prices,
|
outputs, price,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
@@ -112,7 +112,7 @@ impl ReusedAddrVecs {
|
|||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
outputs_by_type: &outputs::ByTypeVecs,
|
outputs_by_type: &outputs::ByTypeVecs,
|
||||||
inputs_by_type: &inputs::ByTypeVecs,
|
inputs_by_type: &inputs::ByTypeVecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use brk_cohort::ByAddrType;
|
use brk_cohort::ByAddrType;
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, Height, Sats, Version};
|
use brk_types::{Height, PartsPerMillion32, Sats, Version};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
indexes,
|
indexes,
|
||||||
internal::{PercentPerBlock, RatioSatsBp16, WithAddrTypes},
|
internal::{PercentPerBlock, RatioSats, WithAddrTypes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::vecs::AddrSupplyVecs;
|
use super::vecs::AddrSupplyVecs;
|
||||||
@@ -18,7 +18,7 @@ use super::vecs::AddrSupplyVecs;
|
|||||||
/// - Per-type: type's category supply / type's total supply
|
/// - Per-type: type's category supply / type's total supply
|
||||||
#[derive(Deref, DerefMut, Traversable)]
|
#[derive(Deref, DerefMut, Traversable)]
|
||||||
pub struct AddrSupplyShareVecs<M: StorageMode = Rw>(
|
pub struct AddrSupplyShareVecs<M: StorageMode = Rw>(
|
||||||
#[traversable(flatten)] pub WithAddrTypes<PercentPerBlock<BasisPoints16, M>>,
|
#[traversable(flatten)] pub WithAddrTypes<PercentPerBlock<PartsPerMillion32, M>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl AddrSupplyShareVecs {
|
impl AddrSupplyShareVecs {
|
||||||
@@ -29,7 +29,7 @@ impl AddrSupplyShareVecs {
|
|||||||
indexes: &indexes::Vecs,
|
indexes: &indexes::Vecs,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(Self(
|
Ok(Self(
|
||||||
WithAddrTypes::<PercentPerBlock<BasisPoints16>>::forced_import(
|
WithAddrTypes::<PercentPerBlock<PartsPerMillion32>>::forced_import(
|
||||||
db,
|
db,
|
||||||
&format!("{name}_addr_supply_share"),
|
&format!("{name}_addr_supply_share"),
|
||||||
version,
|
version,
|
||||||
@@ -46,7 +46,8 @@ impl AddrSupplyShareVecs {
|
|||||||
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.all.compute_binary::<Sats, Sats, RatioSatsBp16>(
|
self.all
|
||||||
|
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&supply.all.sats.height,
|
&supply.all.sats.height,
|
||||||
all_supply_sats,
|
all_supply_sats,
|
||||||
@@ -57,7 +58,7 @@ impl AddrSupplyShareVecs {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(supply.by_addr_type.iter().zip(type_supply_sats.iter()))
|
.zip(supply.by_addr_type.iter().zip(type_supply_sats.iter()))
|
||||||
{
|
{
|
||||||
share.compute_binary::<Sats, Sats, RatioSatsBp16>(
|
share.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&cat.sats.height,
|
&cat.sats.height,
|
||||||
*denom,
|
*denom,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
distribution::DynCohortVecs,
|
distribution::DynCohortVecs,
|
||||||
indexes,
|
indexes,
|
||||||
internal::{WindowStartVec, Windows},
|
internal::{WindowStartVec, Windows},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{super::traits::CohortVecs, vecs::AddrCohortVecs};
|
use super::{super::traits::CohortVecs, vecs::AddrCohortVecs};
|
||||||
@@ -95,7 +95,7 @@ impl AddrCohorts {
|
|||||||
/// First phase of post-processing: compute index transforms.
|
/// First phase of post-processing: compute index transforms.
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -108,7 +108,7 @@ impl AddrCohorts {
|
|||||||
/// Second phase of post-processing: compute relative metrics.
|
/// Second phase of post-processing: compute relative metrics.
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use brk_cohort::{CohortContext, Filter, Filtered};
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, Cents, Height, Sats, StoredI64, StoredU64, Version};
|
use brk_types::{Cents, Height, PartsPerMillionSigned64, Sats, StoredI64, StoredU64, Version};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
distribution::state::{AddrCohortState, MinimalRealizedState},
|
distribution::state::{AddrCohortState, MinimalRealizedState},
|
||||||
indexes,
|
indexes,
|
||||||
internal::{PerBlockWithDeltas, WindowStartVec, Windows},
|
internal::{PerBlockWithDeltas, WindowStartVec, Windows},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::distribution::metrics::{ImportConfig, MinimalCohortMetrics};
|
use crate::distribution::metrics::{ImportConfig, MinimalCohortMetrics};
|
||||||
@@ -28,7 +28,7 @@ pub struct AddrCohortVecs<M: StorageMode = Rw> {
|
|||||||
#[traversable(flatten)]
|
#[traversable(flatten)]
|
||||||
pub metrics: MinimalCohortMetrics<M>,
|
pub metrics: MinimalCohortMetrics<M>,
|
||||||
|
|
||||||
pub addr_count: PerBlockWithDeltas<StoredU64, StoredI64, BasisPointsSigned32, M>,
|
pub addr_count: PerBlockWithDeltas<StoredU64, StoredI64, PartsPerMillionSigned64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddrCohortVecs {
|
impl AddrCohortVecs {
|
||||||
@@ -56,7 +56,7 @@ impl AddrCohortVecs {
|
|||||||
db,
|
db,
|
||||||
&cfg.name("addr_count"),
|
&cfg.name("addr_count"),
|
||||||
version,
|
version,
|
||||||
Version::ONE,
|
Version::TWO,
|
||||||
indexes,
|
indexes,
|
||||||
cached_starts,
|
cached_starts,
|
||||||
)?;
|
)?;
|
||||||
@@ -174,7 +174,7 @@ impl DynCohortVecs for AddrCohortVecs {
|
|||||||
|
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -229,7 +229,7 @@ impl CohortVecs for AddrCohortVecs {
|
|||||||
|
|
||||||
fn compute_rest_part2(
|
fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use brk_indexer::Lengths;
|
|||||||
use brk_types::{Cents, Height, Sats, StoredU64, Version};
|
use brk_types::{Cents, Height, Sats, StoredU64, Version};
|
||||||
use vecdb::{Exit, ReadableVec};
|
use vecdb::{Exit, ReadableVec};
|
||||||
|
|
||||||
use crate::prices;
|
use crate::price;
|
||||||
|
|
||||||
/// Dynamic dispatch trait for cohort vectors.
|
/// Dynamic dispatch trait for cohort vectors.
|
||||||
///
|
///
|
||||||
@@ -31,7 +31,7 @@ pub trait DynCohortVecs: Send + Sync {
|
|||||||
/// First phase of post-processing computations.
|
/// First phase of post-processing computations.
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
@@ -61,7 +61,7 @@ pub trait CohortVecs: DynCohortVecs {
|
|||||||
/// Second phase of post-processing computations.
|
/// Second phase of post-processing computations.
|
||||||
fn compute_rest_part2(
|
fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_cohort::{Filter, PROFITABILITY_RANGE_COUNT, compute_profitability_boundaries};
|
use brk_cohort::{Filter, PROFITABILITY_RANGE_COUNT, compute_profitability_boundaries};
|
||||||
use brk_types::{Cents, CentsCompact, Sats};
|
use brk_types::{Cents, CentsCompact, PartsPerMillion32, Sats};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::state::PendingDelta,
|
distribution::state::PendingDelta,
|
||||||
@@ -30,18 +30,34 @@ const TREE_SIZE: usize = TIER0_COUNT + TIER1_COUNT + OVERFLOW; // 190,001
|
|||||||
pub(super) struct CostBasisNode {
|
pub(super) struct CostBasisNode {
|
||||||
all_sats: i64,
|
all_sats: i64,
|
||||||
sth_sats: i64,
|
sth_sats: i64,
|
||||||
|
discount_sats: i64,
|
||||||
all_usd: i128,
|
all_usd: i128,
|
||||||
sth_usd: i128,
|
sth_usd: i128,
|
||||||
|
discount_usd: i128,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CostBasisNode {
|
impl CostBasisNode {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
|
fn new_supply(sats: i64, usd: i128, is_sth: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
all_sats: sats,
|
all_sats: sats,
|
||||||
sth_sats: if is_sth { sats } else { 0 },
|
sth_sats: if is_sth { sats } else { 0 },
|
||||||
|
discount_sats: 0,
|
||||||
all_usd: usd,
|
all_usd: usd,
|
||||||
sth_usd: if is_sth { usd } else { 0 },
|
sth_usd: if is_sth { usd } else { 0 },
|
||||||
|
discount_usd: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn new_discount(sats: i64, usd: i128) -> Self {
|
||||||
|
Self {
|
||||||
|
all_sats: 0,
|
||||||
|
sth_sats: 0,
|
||||||
|
discount_sats: sats,
|
||||||
|
all_usd: 0,
|
||||||
|
sth_usd: 0,
|
||||||
|
discount_usd: usd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +67,10 @@ impl FenwickNode for CostBasisNode {
|
|||||||
fn add_assign(&mut self, other: &Self) {
|
fn add_assign(&mut self, other: &Self) {
|
||||||
self.all_sats += other.all_sats;
|
self.all_sats += other.all_sats;
|
||||||
self.sth_sats += other.sth_sats;
|
self.sth_sats += other.sth_sats;
|
||||||
|
self.discount_sats += other.discount_sats;
|
||||||
self.all_usd += other.all_usd;
|
self.all_usd += other.all_usd;
|
||||||
self.sth_usd += other.sth_usd;
|
self.sth_usd += other.sth_usd;
|
||||||
|
self.discount_usd += other.discount_usd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,16 +169,34 @@ impl CostBasisFenwick {
|
|||||||
}
|
}
|
||||||
let bucket = price_to_bucket(price);
|
let bucket = price_to_bucket(price);
|
||||||
let delta =
|
let delta =
|
||||||
CostBasisNode::new(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
|
CostBasisNode::new_supply(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
|
||||||
self.tree.add(bucket, &delta);
|
self.tree.add(bucket, &delta);
|
||||||
self.totals.add_assign(&delta);
|
self.totals.add_assign(&delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bulk-initialize from BTreeMaps (one per age-range cohort).
|
/// Apply a net delta from the discount-entry cohort.
|
||||||
/// Call after state import when all pending maps have been drained.
|
///
|
||||||
pub(super) fn bulk_init<'a>(
|
/// Supply totals are maintained from the age-range cohorts; this updates
|
||||||
|
/// only the discount-entry partition so premium can be derived as all - discount.
|
||||||
|
pub(super) fn apply_discount_delta(&mut self, price: CentsCompact, pending: &PendingDelta) {
|
||||||
|
let net_sats = u64::from(pending.inc) as i64 - u64::from(pending.dec) as i64;
|
||||||
|
if net_sats == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let bucket = price_to_bucket(price);
|
||||||
|
let delta =
|
||||||
|
CostBasisNode::new_discount(net_sats, price.as_u128() as i128 * net_sats as i128);
|
||||||
|
self.tree.add(bucket, &delta);
|
||||||
|
self.totals.add_assign(&delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bulk-initialize from age-range maps plus the discount-entry map.
|
||||||
|
/// Age-range maps maintain all/STH/LTH totals; the discount-entry map
|
||||||
|
/// maintains only the discount partition used to derive premium.
|
||||||
|
pub(super) fn bulk_init_with_discount<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
maps: impl Iterator<Item = (&'a std::collections::BTreeMap<CentsCompact, Sats>, bool)>,
|
maps: impl Iterator<Item = (&'a std::collections::BTreeMap<CentsCompact, Sats>, bool)>,
|
||||||
|
discount_maps: impl Iterator<Item = &'a std::collections::BTreeMap<CentsCompact, Sats>>,
|
||||||
) {
|
) {
|
||||||
self.tree.reset();
|
self.tree.reset();
|
||||||
self.totals = CostBasisNode::default();
|
self.totals = CostBasisNode::default();
|
||||||
@@ -169,7 +205,18 @@ impl CostBasisFenwick {
|
|||||||
for (&price, &sats) in map.iter() {
|
for (&price, &sats) in map.iter() {
|
||||||
let bucket = price_to_bucket(price);
|
let bucket = price_to_bucket(price);
|
||||||
let s = u64::from(sats) as i64;
|
let s = u64::from(sats) as i64;
|
||||||
let node = CostBasisNode::new(s, price.as_u128() as i128 * s as i128, is_sth);
|
let node =
|
||||||
|
CostBasisNode::new_supply(s, price.as_u128() as i128 * s as i128, is_sth);
|
||||||
|
self.tree.add_raw(bucket, &node);
|
||||||
|
self.totals.add_assign(&node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for map in discount_maps {
|
||||||
|
for (&price, &sats) in map.iter() {
|
||||||
|
let bucket = price_to_bucket(price);
|
||||||
|
let s = u64::from(sats) as i64;
|
||||||
|
let node = CostBasisNode::new_discount(s, price.as_u128() as i128 * s as i128);
|
||||||
self.tree.add_raw(bucket, &node);
|
self.tree.add_raw(bucket, &node);
|
||||||
self.totals.add_assign(&node);
|
self.totals.add_assign(&node);
|
||||||
}
|
}
|
||||||
@@ -212,6 +259,26 @@ impl CostBasisFenwick {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute percentile prices for discount-entry cohort.
|
||||||
|
pub(super) fn percentiles_discount_entry(&self) -> PercentileResult {
|
||||||
|
self.compute_percentiles(
|
||||||
|
self.totals.discount_sats,
|
||||||
|
self.totals.discount_usd,
|
||||||
|
|n| n.discount_sats,
|
||||||
|
|n| n.discount_usd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute percentile prices for premium-entry cohort (all - discount).
|
||||||
|
pub(super) fn percentiles_premium_entry(&self) -> PercentileResult {
|
||||||
|
self.compute_percentiles(
|
||||||
|
self.totals.all_sats - self.totals.discount_sats,
|
||||||
|
self.totals.all_usd - self.totals.discount_usd,
|
||||||
|
|n| n.all_sats - n.discount_sats,
|
||||||
|
|n| n.all_usd - n.discount_usd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_percentiles(
|
fn compute_percentiles(
|
||||||
&self,
|
&self,
|
||||||
total_sats: i64,
|
total_sats: i64,
|
||||||
@@ -265,12 +332,52 @@ impl CostBasisFenwick {
|
|||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
/// Compute supply density: % of supply with cost basis within ±5% of spot.
|
/// Compute supply density: % of supply with cost basis within ±5% of spot.
|
||||||
/// Returns (all_bps, sth_bps, lth_bps) as basis points (0-10000).
|
pub(super) fn density(
|
||||||
pub(super) fn density(&self, spot_price: Cents) -> (u16, u16, u16) {
|
&self,
|
||||||
|
spot_price: Cents,
|
||||||
|
) -> (PartsPerMillion32, PartsPerMillion32, PartsPerMillion32) {
|
||||||
if self.totals.all_sats <= 0 {
|
if self.totals.all_sats <= 0 {
|
||||||
return (0, 0, 0);
|
return (
|
||||||
|
PartsPerMillion32::ZERO,
|
||||||
|
PartsPerMillion32::ZERO,
|
||||||
|
PartsPerMillion32::ZERO,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let range = self.density_range(spot_price);
|
||||||
|
let all_range = range.all_sats.max(0);
|
||||||
|
let sth_range = range.sth_sats.max(0);
|
||||||
|
let lth_range = all_range - sth_range;
|
||||||
|
|
||||||
|
let lth_total = self.totals.all_sats - self.totals.sth_sats;
|
||||||
|
(
|
||||||
|
Self::to_ppm(all_range, self.totals.all_sats),
|
||||||
|
Self::to_ppm(sth_range, self.totals.sth_sats),
|
||||||
|
Self::to_ppm(lth_range, lth_total),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute supply density for entry cohorts: (discount, premium).
|
||||||
|
pub(super) fn entry_density(
|
||||||
|
&self,
|
||||||
|
spot_price: Cents,
|
||||||
|
) -> (PartsPerMillion32, PartsPerMillion32) {
|
||||||
|
if self.totals.all_sats <= 0 {
|
||||||
|
return (PartsPerMillion32::ZERO, PartsPerMillion32::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = self.density_range(spot_price);
|
||||||
|
let discount_range = range.discount_sats.max(0);
|
||||||
|
let premium_range = range.all_sats.max(0) - discount_range;
|
||||||
|
let premium_total = self.totals.all_sats - self.totals.discount_sats;
|
||||||
|
|
||||||
|
(
|
||||||
|
Self::to_ppm(discount_range, self.totals.discount_sats),
|
||||||
|
Self::to_ppm(premium_range, premium_total),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn density_range(&self, spot_price: Cents) -> CostBasisNode {
|
||||||
let spot_f64 = u64::from(spot_price) as f64;
|
let spot_f64 = u64::from(spot_price) as f64;
|
||||||
let low = Cents::from((spot_f64 * 0.95) as u64);
|
let low = Cents::from((spot_f64 * 0.95) as u64);
|
||||||
let high = Cents::from((spot_f64 * 1.05) as u64);
|
let high = Cents::from((spot_f64 * 1.05) as u64);
|
||||||
@@ -285,24 +392,23 @@ impl CostBasisFenwick {
|
|||||||
CostBasisNode::default()
|
CostBasisNode::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let all_range = (cum_high.all_sats - cum_low.all_sats).max(0);
|
CostBasisNode {
|
||||||
let sth_range = (cum_high.sth_sats - cum_low.sth_sats).max(0);
|
all_sats: cum_high.all_sats - cum_low.all_sats,
|
||||||
let lth_range = all_range - sth_range;
|
sth_sats: cum_high.sth_sats - cum_low.sth_sats,
|
||||||
|
discount_sats: cum_high.discount_sats - cum_low.discount_sats,
|
||||||
let to_bps = |range: i64, total: i64| -> u16 {
|
all_usd: cum_high.all_usd - cum_low.all_usd,
|
||||||
if total <= 0 {
|
sth_usd: cum_high.sth_usd - cum_low.sth_usd,
|
||||||
0
|
discount_usd: cum_high.discount_usd - cum_low.discount_usd,
|
||||||
} else {
|
}
|
||||||
(range as f64 / total as f64 * 10000.0).round() as u16
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let lth_total = self.totals.all_sats - self.totals.sth_sats;
|
#[inline(always)]
|
||||||
(
|
fn to_ppm(range: i64, total: i64) -> PartsPerMillion32 {
|
||||||
to_bps(all_range, self.totals.all_sats),
|
if total <= 0 {
|
||||||
to_bps(sth_range, self.totals.sth_sats),
|
PartsPerMillion32::ZERO
|
||||||
to_bps(lth_range, lth_total),
|
} else {
|
||||||
)
|
PartsPerMillion32::from(range as f64 / total as f64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use brk_cohort::{
|
use brk_cohort::{
|
||||||
AgeRange, AmountRange, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge, OverAmount,
|
AgeRange, AmountRange, ByEntry, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge,
|
||||||
SpendableType, Term, UnderAge, UnderAmount,
|
OverAmount, SpendableType, Term, UnderAge, UnderAmount,
|
||||||
};
|
};
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
@@ -27,7 +27,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
indexes,
|
indexes,
|
||||||
internal::{ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
|
internal::{ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{fenwick::CostBasisFenwick, vecs::UTXOCohortVecs};
|
use super::{fenwick::CostBasisFenwick, vecs::UTXOCohortVecs};
|
||||||
@@ -45,6 +45,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
|
|||||||
pub over_age: OverAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
pub over_age: OverAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||||
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||||
pub class: Class<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
pub class: Class<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||||
|
pub entry: ByEntry<UTXOCohortVecs<ExtendedCohortMetrics<M>>>,
|
||||||
pub over_amount: OverAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
pub over_amount: OverAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||||
pub amount_range: AmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
pub amount_range: AmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||||
pub under_amount: UnderAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
pub under_amount: UnderAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||||
@@ -67,8 +68,10 @@ pub(crate) struct UTXOCohortsTransientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UTXOCohorts<Rw> {
|
impl UTXOCohorts<Rw> {
|
||||||
/// ~71 separate cohorts (21 age + 5 epoch + 18 class + 15 amount + 12 type)
|
/// Separate cohorts currently total 72:
|
||||||
const SEPARATE_COHORT_CAPACITY: usize = 80;
|
/// 21 age + 5 epoch + 18 class + 2 entry + 15 amount + 11 spendable type.
|
||||||
|
/// Keep small headroom because this is only Vec allocation capacity.
|
||||||
|
const SEPARATE_COHORT_CAPACITY: usize = 82;
|
||||||
|
|
||||||
/// Import all UTXO cohorts from database.
|
/// Import all UTXO cohorts from database.
|
||||||
pub(crate) fn forced_import(
|
pub(crate) fn forced_import(
|
||||||
@@ -136,6 +139,26 @@ impl UTXOCohorts<Rw> {
|
|||||||
let epoch = ByEpoch::try_new(&core_separate)?;
|
let epoch = ByEpoch::try_new(&core_separate)?;
|
||||||
let class = Class::try_new(&core_separate)?;
|
let class = Class::try_new(&core_separate)?;
|
||||||
|
|
||||||
|
let extended_separate =
|
||||||
|
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<ExtendedCohortMetrics>> {
|
||||||
|
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||||
|
let cfg = ImportConfig {
|
||||||
|
db,
|
||||||
|
filter: &f,
|
||||||
|
full_name: &full_name,
|
||||||
|
version: v,
|
||||||
|
indexes,
|
||||||
|
cached_starts,
|
||||||
|
};
|
||||||
|
let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name)));
|
||||||
|
Ok(UTXOCohortVecs::new(
|
||||||
|
state,
|
||||||
|
ExtendedCohortMetrics::forced_import(&cfg)?,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry = ByEntry::try_new(&extended_separate)?;
|
||||||
|
|
||||||
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
|
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
|
||||||
let minimal_separate =
|
let minimal_separate =
|
||||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
|
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
|
||||||
@@ -281,6 +304,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
lth,
|
lth,
|
||||||
epoch,
|
epoch,
|
||||||
class,
|
class,
|
||||||
|
entry,
|
||||||
type_,
|
type_,
|
||||||
under_age,
|
under_age,
|
||||||
over_age,
|
over_age,
|
||||||
@@ -309,6 +333,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
sth,
|
sth,
|
||||||
caches,
|
caches,
|
||||||
age_range,
|
age_range,
|
||||||
|
entry,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
caches
|
caches
|
||||||
@@ -327,7 +352,15 @@ impl UTXOCohorts<Rw> {
|
|||||||
Some((map, caches.fenwick.is_sth_at(i)))
|
Some((map, caches.fenwick.is_sth_at(i)))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
caches.fenwick.bulk_init(maps.into_iter());
|
let discount_maps = entry
|
||||||
|
.discount
|
||||||
|
.state
|
||||||
|
.as_ref()
|
||||||
|
.map(|state| state.cost_basis_map())
|
||||||
|
.into_iter();
|
||||||
|
caches
|
||||||
|
.fenwick
|
||||||
|
.bulk_init_with_discount(maps.into_iter(), discount_maps);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply pending deltas from all age-range cohorts to the Fenwick tree.
|
/// Apply pending deltas from all age-range cohorts to the Fenwick tree.
|
||||||
@@ -338,7 +371,10 @@ impl UTXOCohorts<Rw> {
|
|||||||
}
|
}
|
||||||
// Destructure to get separate borrows on caches and age_range
|
// Destructure to get separate borrows on caches and age_range
|
||||||
let Self {
|
let Self {
|
||||||
caches, age_range, ..
|
caches,
|
||||||
|
age_range,
|
||||||
|
entry,
|
||||||
|
..
|
||||||
} = self;
|
} = self;
|
||||||
for (i, sub) in age_range.iter().enumerate() {
|
for (i, sub) in age_range.iter().enumerate() {
|
||||||
if let Some(state) = sub.state.as_ref() {
|
if let Some(state) = sub.state.as_ref() {
|
||||||
@@ -348,6 +384,11 @@ impl UTXOCohorts<Rw> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(state) = entry.discount.state.as_ref() {
|
||||||
|
state.for_each_cost_basis_pending(|&price, delta| {
|
||||||
|
caches.fenwick.apply_discount_delta(price, delta);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push maturation sats to the matured vecs for the given height.
|
/// Push maturation sats to the matured vecs for the given height.
|
||||||
@@ -365,6 +406,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
age_range,
|
age_range,
|
||||||
epoch,
|
epoch,
|
||||||
class,
|
class,
|
||||||
|
entry,
|
||||||
amount_range,
|
amount_range,
|
||||||
type_,
|
type_,
|
||||||
..
|
..
|
||||||
@@ -374,6 +416,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
.map(|x| x as &mut dyn DynCohortVecs)
|
.map(|x| x as &mut dyn DynCohortVecs)
|
||||||
.chain(epoch.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
.chain(epoch.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
.chain(class.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
.chain(class.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
|
.chain(entry.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
.chain(
|
.chain(
|
||||||
amount_range
|
amount_range
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
@@ -389,6 +432,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
age_range,
|
age_range,
|
||||||
epoch,
|
epoch,
|
||||||
class,
|
class,
|
||||||
|
entry,
|
||||||
amount_range,
|
amount_range,
|
||||||
type_,
|
type_,
|
||||||
..
|
..
|
||||||
@@ -398,6 +442,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
.map(|x| x as &mut dyn DynCohortVecs)
|
.map(|x| x as &mut dyn DynCohortVecs)
|
||||||
.chain(epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
.chain(epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
.chain(class.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
.chain(class.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
|
.chain(entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
.chain(amount_range.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
.chain(amount_range.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
.chain(type_.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
.chain(type_.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||||
}
|
}
|
||||||
@@ -409,6 +454,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
.map(|x| x as &dyn DynCohortVecs)
|
.map(|x| x as &dyn DynCohortVecs)
|
||||||
.chain(self.epoch.iter().map(|x| x as &dyn DynCohortVecs))
|
.chain(self.epoch.iter().map(|x| x as &dyn DynCohortVecs))
|
||||||
.chain(self.class.iter().map(|x| x as &dyn DynCohortVecs))
|
.chain(self.class.iter().map(|x| x as &dyn DynCohortVecs))
|
||||||
|
.chain(self.entry.iter().map(|x| x as &dyn DynCohortVecs))
|
||||||
.chain(self.amount_range.iter().map(|x| x as &dyn DynCohortVecs))
|
.chain(self.amount_range.iter().map(|x| x as &dyn DynCohortVecs))
|
||||||
.chain(self.type_.iter().map(|x| x as &dyn DynCohortVecs))
|
.chain(self.type_.iter().map(|x| x as &dyn DynCohortVecs))
|
||||||
}
|
}
|
||||||
@@ -483,7 +529,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
/// First phase of post-processing: compute index transforms.
|
/// First phase of post-processing: compute index transforms.
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -516,6 +562,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
);
|
);
|
||||||
all.extend(self.epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
all.extend(self.epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
||||||
all.extend(self.class.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
all.extend(self.class.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
||||||
|
all.extend(self.entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
||||||
all.extend(
|
all.extend(
|
||||||
self.amount_range
|
self.amount_range
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@@ -546,7 +593,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
@@ -604,6 +651,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
under_amount,
|
under_amount,
|
||||||
epoch,
|
epoch,
|
||||||
class,
|
class,
|
||||||
|
entry,
|
||||||
type_,
|
type_,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
@@ -676,6 +724,19 @@ impl UTXOCohorts<Rw> {
|
|||||||
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
|
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
Box::new(|| {
|
||||||
|
entry.par_iter_mut().try_for_each(|v| {
|
||||||
|
v.metrics.compute_rest_part2(
|
||||||
|
blocks,
|
||||||
|
prices,
|
||||||
|
starting_lengths,
|
||||||
|
height_to_market_cap,
|
||||||
|
ss,
|
||||||
|
au,
|
||||||
|
exit,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}),
|
||||||
Box::new(|| {
|
Box::new(|| {
|
||||||
amount_range.par_iter_mut().try_for_each(|v| {
|
amount_range.par_iter_mut().try_for_each(|v| {
|
||||||
v.metrics
|
v.metrics
|
||||||
@@ -730,6 +791,9 @@ impl UTXOCohorts<Rw> {
|
|||||||
for v in self.class.iter_mut() {
|
for v in self.class.iter_mut() {
|
||||||
vecs.extend(v.metrics.collect_all_vecs_mut());
|
vecs.extend(v.metrics.collect_all_vecs_mut());
|
||||||
}
|
}
|
||||||
|
for v in self.entry.iter_mut() {
|
||||||
|
vecs.extend(v.metrics.collect_all_vecs_mut());
|
||||||
|
}
|
||||||
for v in self.amount_range.iter_mut() {
|
for v in self.amount_range.iter_mut() {
|
||||||
vecs.extend(v.metrics.collect_all_vecs_mut());
|
vecs.extend(v.metrics.collect_all_vecs_mut());
|
||||||
}
|
}
|
||||||
@@ -813,7 +877,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
|
|
||||||
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
|
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
|
||||||
/// Called during the block loop after separate cohorts' push_state but before reset.
|
/// Called during the block loop after separate cohorts' push_state but before reset.
|
||||||
pub(crate) fn push_overlapping(&mut self, height_price: Cents) {
|
pub(crate) fn push_overlapping(&mut self, height_price: Cents) -> Cents {
|
||||||
let Self {
|
let Self {
|
||||||
all,
|
all,
|
||||||
sth,
|
sth,
|
||||||
@@ -852,7 +916,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
all.metrics.realized.push_accum(&all_acc);
|
let all_capitalized_price = all.metrics.realized.push_accum(&all_acc);
|
||||||
sth.metrics.realized.push_accum(&sth_acc);
|
sth.metrics.realized.push_accum(&sth_acc);
|
||||||
lth.metrics.realized.push_accum(<h_acc);
|
lth.metrics.realized.push_accum(<h_acc);
|
||||||
|
|
||||||
@@ -880,6 +944,8 @@ impl UTXOCohorts<Rw> {
|
|||||||
.unrealized
|
.unrealized
|
||||||
.capitalized_cap_in_loss_raw
|
.capitalized_cap_in_loss_raw
|
||||||
.push(CentsSquaredSats::new(lth_ccap.1));
|
.push(CentsSquaredSats::new(lth_ccap.1));
|
||||||
|
|
||||||
|
all_capitalized_price
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
|
|||||||
|
|
||||||
use brk_cohort::{AGE_RANGE_NAMES, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES};
|
use brk_cohort::{AGE_RANGE_NAMES, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES};
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{BasisPoints16, Cents, CentsCompact, Date, Dollars, Sats, UrpdRaw};
|
use brk_types::{Cents, CentsCompact, Date, Dollars, PartsPerMillion32, Sats, UrpdRaw};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
|
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
|
||||||
@@ -50,6 +50,22 @@ impl UTXOCohorts {
|
|||||||
let lth = self.caches.fenwick.percentiles_lth();
|
let lth = self.caches.fenwick.percentiles_lth();
|
||||||
push_cost_basis(<h, lth_d, &mut self.lth.metrics.cost_basis);
|
push_cost_basis(<h, lth_d, &mut self.lth.metrics.cost_basis);
|
||||||
|
|
||||||
|
let (discount_d, premium_d) = self.caches.fenwick.entry_density(spot_price);
|
||||||
|
|
||||||
|
let discount = self.caches.fenwick.percentiles_discount_entry();
|
||||||
|
push_cost_basis(
|
||||||
|
&discount,
|
||||||
|
discount_d,
|
||||||
|
&mut self.entry.discount.metrics.cost_basis,
|
||||||
|
);
|
||||||
|
|
||||||
|
let premium = self.caches.fenwick.percentiles_premium_entry();
|
||||||
|
push_cost_basis(
|
||||||
|
&premium,
|
||||||
|
premium_d,
|
||||||
|
&mut self.entry.premium.metrics.cost_basis,
|
||||||
|
);
|
||||||
|
|
||||||
let prof = self.caches.fenwick.profitability(spot_price);
|
let prof = self.caches.fenwick.profitability(spot_price);
|
||||||
push_profitability(&prof, &mut self.profitability);
|
push_profitability(&prof, &mut self.profitability);
|
||||||
}
|
}
|
||||||
@@ -123,10 +139,14 @@ impl UTXOCohorts {
|
|||||||
|
|
||||||
/// Push percentiles + density to cost basis vecs.
|
/// Push percentiles + density to cost basis vecs.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn push_cost_basis(percentiles: &PercentileResult, density_bps: u16, cost_basis: &mut CostBasis) {
|
fn push_cost_basis(
|
||||||
|
percentiles: &PercentileResult,
|
||||||
|
density: PartsPerMillion32,
|
||||||
|
cost_basis: &mut CostBasis,
|
||||||
|
) {
|
||||||
cost_basis.push_minmax(percentiles.min_price, percentiles.max_price);
|
cost_basis.push_minmax(percentiles.min_price, percentiles.max_price);
|
||||||
cost_basis.push_percentiles(&percentiles.sat_prices, &percentiles.usd_prices);
|
cost_basis.push_percentiles(&percentiles.sat_prices, &percentiles.usd_prices);
|
||||||
cost_basis.push_density(BasisPoints16::from(density_bps));
|
cost_basis.push_density(density);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use brk_cohort::EntryPrice;
|
||||||
use brk_types::{Cents, CostBasisSnapshot, Height, Timestamp};
|
use brk_types::{Cents, CostBasisSnapshot, Height, Timestamp};
|
||||||
use vecdb::Rw;
|
use vecdb::Rw;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
/// - The "under_1h" age cohort (all new UTXOs start at 0 hours old)
|
/// - The "under_1h" age cohort (all new UTXOs start at 0 hours old)
|
||||||
/// - The appropriate epoch cohort based on block height
|
/// - The appropriate epoch cohort based on block height
|
||||||
/// - The appropriate class cohort based on block timestamp
|
/// - The appropriate class cohort based on block timestamp
|
||||||
|
/// - The immutable entry valuation cohort based on creation price versus anchor
|
||||||
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
|
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
|
||||||
/// - The appropriate amount range cohort based on value
|
/// - The appropriate amount range cohort based on value
|
||||||
pub(crate) fn receive(
|
pub(crate) fn receive(
|
||||||
@@ -20,13 +22,14 @@ impl UTXOCohorts<Rw> {
|
|||||||
height: Height,
|
height: Height,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
price: Cents,
|
price: Cents,
|
||||||
|
entry: EntryPrice,
|
||||||
) {
|
) {
|
||||||
let supply_state = received.spendable_supply;
|
let supply_state = received.spendable_supply;
|
||||||
|
|
||||||
// Pre-compute snapshot once for the 3 cohorts sharing the same supply_state
|
// Pre-compute snapshot once for cohorts sharing the block-level supply_state
|
||||||
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
|
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
|
||||||
|
|
||||||
// New UTXOs go into under_1h, current epoch, and current class
|
// New UTXOs go into under_1h plus immutable creation cohorts
|
||||||
self.age_range
|
self.age_range
|
||||||
.under_1h
|
.under_1h
|
||||||
.state
|
.state
|
||||||
@@ -45,6 +48,12 @@ impl UTXOCohorts<Rw> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.receive_utxo_snapshot(&supply_state, &snapshot);
|
.receive_utxo_snapshot(&supply_state, &snapshot);
|
||||||
}
|
}
|
||||||
|
self.entry
|
||||||
|
.get_mut(entry)
|
||||||
|
.state
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.receive_utxo_snapshot(&supply_state, &snapshot);
|
||||||
|
|
||||||
// Update output type cohorts (skip types with no outputs this block)
|
// Update output type cohorts (skip types with no outputs this block)
|
||||||
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
|
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl UTXOCohorts<Rw> {
|
|||||||
// This is the max price between receive and send heights
|
// This is the max price between receive and send heights
|
||||||
let peak_price = price_range_max.max_between(receive_height, send_height);
|
let peak_price = price_range_max.max_between(receive_height, send_height);
|
||||||
|
|
||||||
// Pre-compute once for age_range, epoch, year (all share sent.spendable_supply)
|
// Pre-compute once for cohorts sharing the sent supply.
|
||||||
if let Some(pre) = SendPrecomputed::new(
|
if let Some(pre) = SendPrecomputed::new(
|
||||||
&sent.spendable_supply,
|
&sent.spendable_supply,
|
||||||
current_price,
|
current_price,
|
||||||
@@ -75,6 +75,12 @@ impl UTXOCohorts<Rw> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.send_utxo_precomputed(&sent.spendable_supply, &pre);
|
.send_utxo_precomputed(&sent.spendable_supply, &pre);
|
||||||
}
|
}
|
||||||
|
self.entry
|
||||||
|
.get_mut(block_state.entry)
|
||||||
|
.state
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.send_utxo_precomputed(&sent.spendable_supply, &pre);
|
||||||
} else if sent.spendable_supply.utxo_count > 0 {
|
} else if sent.spendable_supply.utxo_count > 0 {
|
||||||
// Zero-value UTXOs: just subtract supply
|
// Zero-value UTXOs: just subtract supply
|
||||||
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
|
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
|
||||||
@@ -85,6 +91,12 @@ impl UTXOCohorts<Rw> {
|
|||||||
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
|
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
|
||||||
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
|
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
|
||||||
}
|
}
|
||||||
|
self.entry
|
||||||
|
.get_mut(block_state.entry)
|
||||||
|
.state
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.supply -= &sent.spendable_supply;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update output type cohorts (skip zero-supply entries)
|
// Update output type cohorts (skip zero-supply entries)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use vecdb::{Exit, ReadableVec};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::{cohorts::traits::DynCohortVecs, metrics::CoreCohortMetrics},
|
distribution::{cohorts::traits::DynCohortVecs, metrics::CoreCohortMetrics},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::UTXOCohortVecs;
|
use super::UTXOCohortVecs;
|
||||||
@@ -56,7 +56,7 @@ impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
|
|||||||
|
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use vecdb::{Exit, ReadableVec};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::{cohorts::traits::DynCohortVecs, metrics::MinimalCohortMetrics},
|
distribution::{cohorts::traits::DynCohortVecs, metrics::MinimalCohortMetrics},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::UTXOCohortVecs;
|
use super::UTXOCohortVecs;
|
||||||
@@ -49,7 +49,7 @@ impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
|
|||||||
|
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ use crate::{
|
|||||||
metrics::{CohortMetricsBase, CohortMetricsState},
|
metrics::{CohortMetricsBase, CohortMetricsState},
|
||||||
state::UTXOCohortState,
|
state::UTXOCohortState,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
@@ -186,7 +186,7 @@ impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
|
|||||||
|
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use brk_types::{Cents, Height, Version};
|
|||||||
use vecdb::{Exit, ReadableVec};
|
use vecdb::{Exit, ReadableVec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::cohorts::traits::DynCohortVecs, distribution::metrics::TypeCohortMetrics, prices,
|
distribution::cohorts::traits::DynCohortVecs, distribution::metrics::TypeCohortMetrics, price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::UTXOCohortVecs;
|
use super::UTXOCohortVecs;
|
||||||
@@ -55,7 +55,7 @@ impl DynCohortVecs for UTXOCohortVecs<TypeCohortMetrics> {
|
|||||||
|
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use brk_cohort::ByAddrType;
|
use brk_cohort::{ByAddrType, EntryPrice};
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
@@ -46,6 +46,7 @@ pub(crate) fn process_blocks(
|
|||||||
last_height: Height,
|
last_height: Height,
|
||||||
chain_state: &mut Vec<BlockState>,
|
chain_state: &mut Vec<BlockState>,
|
||||||
tx_index_to_height: &mut RangeMap<TxIndex, Height>,
|
tx_index_to_height: &mut RangeMap<TxIndex, Height>,
|
||||||
|
mut entry_anchor: Cents,
|
||||||
cached_prices: &[Cents],
|
cached_prices: &[Cents],
|
||||||
cached_timestamps: &[Timestamp],
|
cached_timestamps: &[Timestamp],
|
||||||
cached_price_range_max: &PriceRangeMax,
|
cached_price_range_max: &PriceRangeMax,
|
||||||
@@ -370,9 +371,14 @@ pub(crate) fn process_blocks(
|
|||||||
.iterate(Sats::FIFTY_BTC, OutputType::P2PK65);
|
.iterate(Sats::FIFTY_BTC, OutputType::P2PK65);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let entry = EntryPrice::from_is_discount(
|
||||||
|
entry_anchor == Cents::ZERO || block_price <= entry_anchor,
|
||||||
|
);
|
||||||
|
|
||||||
// Push current block state before processing cohort updates
|
// Push current block state before processing cohort updates
|
||||||
chain_state.push(BlockState {
|
chain_state.push(BlockState {
|
||||||
supply: transacted.spendable_supply,
|
supply: transacted.spendable_supply,
|
||||||
|
entry,
|
||||||
price: block_price,
|
price: block_price,
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
@@ -411,7 +417,7 @@ pub(crate) fn process_blocks(
|
|||||||
|| {
|
|| {
|
||||||
// UTXO cohorts receive/send
|
// UTXO cohorts receive/send
|
||||||
vecs.utxo_cohorts
|
vecs.utxo_cohorts
|
||||||
.receive(transacted, height, timestamp, block_price);
|
.receive(transacted, height, timestamp, block_price, entry);
|
||||||
if let Some(min_h) =
|
if let Some(min_h) =
|
||||||
vecs.utxo_cohorts
|
vecs.utxo_cohorts
|
||||||
.send(height_to_sent, chain_state, ctx.price_range_max)
|
.send(height_to_sent, chain_state, ctx.price_range_max)
|
||||||
@@ -460,7 +466,7 @@ pub(crate) fn process_blocks(
|
|||||||
let is_last_of_day = is_last_of_day[offset];
|
let is_last_of_day = is_last_of_day[offset];
|
||||||
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
|
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
|
||||||
|
|
||||||
push_cohort_states(
|
entry_anchor = push_cohort_states(
|
||||||
&mut vecs.utxo_cohorts,
|
&mut vecs.utxo_cohorts,
|
||||||
&mut vecs.addr_cohorts,
|
&mut vecs.addr_cohorts,
|
||||||
height,
|
height,
|
||||||
@@ -527,7 +533,7 @@ fn push_cohort_states(
|
|||||||
addr_cohorts: &mut AddrCohorts,
|
addr_cohorts: &mut AddrCohorts,
|
||||||
height: Height,
|
height: Height,
|
||||||
height_price: Cents,
|
height_price: Cents,
|
||||||
) {
|
) -> Cents {
|
||||||
// Phase 1: push + unrealized (no reset yet, states still needed for aggregation)
|
// Phase 1: push + unrealized (no reset yet, states still needed for aggregation)
|
||||||
rayon::join(
|
rayon::join(
|
||||||
|| {
|
|| {
|
||||||
@@ -545,7 +551,7 @@ fn push_cohort_states(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Phase 2: aggregate age_range states → push to overlapping cohorts
|
// Phase 2: aggregate age_range states → push to overlapping cohorts
|
||||||
utxo_cohorts.push_overlapping(height_price);
|
let all_capitalized_price = utxo_cohorts.push_overlapping(height_price);
|
||||||
|
|
||||||
// Phase 3: reset per-block values
|
// Phase 3: reset per-block values
|
||||||
utxo_cohorts
|
utxo_cohorts
|
||||||
@@ -554,4 +560,6 @@ fn push_cohort_states(
|
|||||||
addr_cohorts
|
addr_cohorts
|
||||||
.iter_separate_mut()
|
.iter_separate_mut()
|
||||||
.for_each(|v| v.reset_single_iteration_values());
|
.for_each(|v| v.reset_single_iteration_values());
|
||||||
|
|
||||||
|
all_capitalized_price
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
state::{CohortState, CostBasisOps, RealizedOps},
|
state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
},
|
},
|
||||||
internal::{PerBlockCumulativeRolling, ValuePerBlockCumulativeRolling},
|
internal::{PerBlockCumulativeRolling, ValuePerBlockCumulativeRolling},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ActivityMinimal;
|
use super::ActivityMinimal;
|
||||||
@@ -98,7 +98,7 @@ impl ActivityCore {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
metrics::ImportConfig,
|
metrics::ImportConfig,
|
||||||
state::{CohortState, CostBasisOps, RealizedOps},
|
state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ActivityCore;
|
use super::ActivityCore;
|
||||||
@@ -89,7 +89,7 @@ impl ActivityFull {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
state::{CohortState, CostBasisOps, RealizedOps},
|
state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
},
|
},
|
||||||
internal::ValuePerBlockCumulativeRolling,
|
internal::ValuePerBlockCumulativeRolling,
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
@@ -63,7 +63,7 @@ impl ActivityMinimal {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use vecdb::Exit;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait ActivityLike: Send + Sync {
|
pub trait ActivityLike: Send + Sync {
|
||||||
@@ -30,7 +30,7 @@ pub trait ActivityLike: Send + Sync {
|
|||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
@@ -62,7 +62,7 @@ impl ActivityLike for ActivityCore {
|
|||||||
}
|
}
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -96,7 +96,7 @@ impl ActivityLike for ActivityFull {
|
|||||||
}
|
}
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
ActivityFull, AdjustedSopr, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase,
|
ActivityFull, AdjustedSopr, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase,
|
||||||
RealizedFull, RelativeForAll, SupplyCore, UnrealizedFull,
|
RealizedFull, RelativeForAll, SupplyCore, UnrealizedFull,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// All-cohort metrics: extended realized + adjusted (as composable add-on),
|
/// All-cohort metrics: extended realized + adjusted (as composable add-on),
|
||||||
@@ -100,7 +100,7 @@ impl AllCohortMetrics {
|
|||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||||
under_1h_value_created: &impl ReadableVec<Height, Cents>,
|
under_1h_value_created: &impl ReadableVec<Height, Cents>,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
|
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
|
||||||
UnrealizedCore,
|
UnrealizedCore,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Basic cohort metrics: no extensions, used by age_range cohorts.
|
/// Basic cohort metrics: no extensions, used by age_range cohorts.
|
||||||
@@ -61,7 +61,7 @@ impl BasicCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
|
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
|
||||||
UnrealizedCore,
|
UnrealizedCore,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
@@ -102,7 +102,7 @@ impl CoreCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -122,7 +122,7 @@ impl CoreCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase, RealizedFull,
|
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase, RealizedFull,
|
||||||
RelativeWithExtended, SupplyCore, UnrealizedFull,
|
RelativeWithExtended, SupplyCore, UnrealizedFull,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Cohort metrics with extended realized + extended cost basis (no adjusted).
|
/// Cohort metrics with extended realized + extended cost basis (no adjusted).
|
||||||
@@ -90,7 +90,7 @@ impl ExtendedCohortMetrics {
|
|||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
distribution::metrics::{
|
distribution::metrics::{
|
||||||
ActivityFull, AdjustedSopr, CohortMetricsBase, ImportConfig, RealizedFull, UnrealizedFull,
|
ActivityFull, AdjustedSopr, CohortMetricsBase, ImportConfig, RealizedFull, UnrealizedFull,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ExtendedCohortMetrics;
|
use super::ExtendedCohortMetrics;
|
||||||
@@ -62,7 +62,7 @@ impl ExtendedAdjustedCohortMetrics {
|
|||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||||
under_1h_value_created: &impl ReadableVec<Height, Cents>,
|
under_1h_value_created: &impl ReadableVec<Height, Cents>,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
distribution::metrics::{
|
distribution::metrics::{
|
||||||
ActivityMinimal, ImportConfig, OutputsBase, RealizedMinimal, SupplyBase, UnrealizedMinimal,
|
ActivityMinimal, ImportConfig, OutputsBase, RealizedMinimal, SupplyBase, UnrealizedMinimal,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// MinimalCohortMetrics: supply, outputs, realized cap/price/mvrv/profit/loss + value_created/destroyed.
|
/// MinimalCohortMetrics: supply, outputs, realized cap/price/mvrv/profit/loss + value_created/destroyed.
|
||||||
@@ -97,7 +97,7 @@ impl MinimalCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -111,7 +111,7 @@ impl MinimalCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
distribution::metrics::{
|
distribution::metrics::{
|
||||||
ActivityMinimal, ImportConfig, OutputsBase, RealizedMinimal, SupplyCore, UnrealizedBasic,
|
ActivityMinimal, ImportConfig, OutputsBase, RealizedMinimal, SupplyCore, UnrealizedBasic,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// TypeCohortMetrics: supply(core), outputs(base), realized(minimal), unrealized(basic).
|
/// TypeCohortMetrics: supply(core), outputs(base), realized(minimal), unrealized(basic).
|
||||||
@@ -59,7 +59,7 @@ impl TypeCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -73,7 +73,7 @@ impl TypeCohortMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use brk_cohort::Filter;
|
use brk_cohort::Filter;
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, Height, Version};
|
use brk_types::{
|
||||||
|
Cents, Height, PartsPerMillion32, PartsPerMillionSigned32, PartsPerMillionSigned64, Version,
|
||||||
|
};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
|
use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
|
||||||
|
|
||||||
@@ -39,12 +41,11 @@ impl_config_import!(
|
|||||||
ValuePerBlockCumulative,
|
ValuePerBlockCumulative,
|
||||||
PriceWithRatioPerBlock,
|
PriceWithRatioPerBlock,
|
||||||
PriceWithRatioExtendedPerBlock,
|
PriceWithRatioExtendedPerBlock,
|
||||||
RatioPerBlock<BasisPoints32>,
|
RatioPerBlock<PartsPerMillionSigned32>,
|
||||||
RatioPerBlock<BasisPointsSigned32>,
|
PercentPerBlock<PartsPerMillion32>,
|
||||||
PercentPerBlock<BasisPoints16>,
|
PercentPerBlock<PartsPerMillionSigned32>,
|
||||||
PercentPerBlock<BasisPoints32>,
|
PercentPerBlock<PartsPerMillionSigned64>,
|
||||||
PercentPerBlock<BasisPointsSigned32>,
|
PercentRollingWindows<PartsPerMillion32>,
|
||||||
PercentRollingWindows<BasisPoints32>,
|
|
||||||
Price<PerBlock<Cents>>,
|
Price<PerBlock<Cents>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use brk_error::Result;
|
|||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::CentsSquaredSats;
|
use brk_types::CentsSquaredSats;
|
||||||
use brk_types::{BasisPoints16, Cents, Height, Sats, Version};
|
use brk_types::{Cents, Height, PartsPerMillion32, Sats, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::internal::{PERCENTILES_LEN, PerBlock, PercentPerBlock, PercentilesVecs, Price};
|
use crate::internal::{PERCENTILES_LEN, PerBlock, PercentPerBlock, PercentilesVecs, Price};
|
||||||
@@ -25,7 +25,7 @@ pub struct CostBasis<M: StorageMode = Rw> {
|
|||||||
pub max: Price<PerBlock<Cents, M>>,
|
pub max: Price<PerBlock<Cents, M>>,
|
||||||
pub per_coin: PercentilesVecs<M>,
|
pub per_coin: PercentilesVecs<M>,
|
||||||
pub per_dollar: PercentilesVecs<M>,
|
pub per_dollar: PercentilesVecs<M>,
|
||||||
pub supply_density: PercentPerBlock<BasisPoints16, M>,
|
pub supply_density: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CostBasis {
|
impl CostBasis {
|
||||||
@@ -88,7 +88,7 @@ impl CostBasis {
|
|||||||
.height
|
.height
|
||||||
.len()
|
.len()
|
||||||
.min(self.max.cents.height.len())
|
.min(self.max.cents.height.len())
|
||||||
.min(self.supply_density.bps.height.len())
|
.min(self.supply_density.raw.height.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -108,8 +108,8 @@ impl CostBasis {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn push_density(&mut self, density_bps: BasisPoints16) {
|
pub(crate) fn push_density(&mut self, density: PartsPerMillion32) {
|
||||||
self.supply_density.bps.height.push(density_bps);
|
self.supply_density.raw.height.push(density);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||||
@@ -128,7 +128,7 @@ impl CostBasis {
|
|||||||
&mut self.in_loss.per_dollar.cents.height,
|
&mut self.in_loss.per_dollar.cents.height,
|
||||||
&mut self.min.cents.height,
|
&mut self.min.cents.height,
|
||||||
&mut self.max.cents.height,
|
&mut self.max.cents.height,
|
||||||
&mut self.supply_density.bps.height,
|
&mut self.supply_density.raw.height,
|
||||||
];
|
];
|
||||||
vecs.extend(
|
vecs.extend(
|
||||||
self.per_coin
|
self.per_coin
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ use crate::{
|
|||||||
CohortState, CoreRealizedState, CostBasisData, CostBasisOps, CostBasisRaw,
|
CohortState, CoreRealizedState, CostBasisData, CostBasisOps, CostBasisRaw,
|
||||||
MinimalRealizedState, RealizedOps, RealizedState, WithCapital, WithoutCapital,
|
MinimalRealizedState, RealizedOps, RealizedState, WithCapital, WithoutCapital,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait CohortMetricsState {
|
pub trait CohortMetricsState {
|
||||||
@@ -270,7 +270,7 @@ pub trait CohortMetricsBase:
|
|||||||
/// First phase of computed metrics (indexes from height).
|
/// First phase of computed metrics (indexes from height).
|
||||||
fn compute_rest_part1(
|
fn compute_rest_part1(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, Height, StoredF32, StoredI64, StoredU32, StoredU64, Version};
|
use brk_types::{
|
||||||
|
Height, PartsPerMillionSigned64, StoredF32, StoredI64, StoredU32, StoredU64, Version,
|
||||||
|
};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -15,7 +17,7 @@ use crate::{
|
|||||||
/// Base output metrics: utxo_count + delta.
|
/// Base output metrics: utxo_count + delta.
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct OutputsBase<M: StorageMode = Rw> {
|
pub struct OutputsBase<M: StorageMode = Rw> {
|
||||||
pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, BasisPointsSigned32, M>,
|
pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, PartsPerMillionSigned64, M>,
|
||||||
pub spent_count: PerBlockCumulativeRolling<StoredU32, StoredU64, M>,
|
pub spent_count: PerBlockCumulativeRolling<StoredU32, StoredU64, M>,
|
||||||
pub spending_rate: PerBlock<StoredF32, M>,
|
pub spending_rate: PerBlock<StoredF32, M>,
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,7 @@ impl OutputsBase {
|
|||||||
cfg.db,
|
cfg.db,
|
||||||
&cfg.name("utxo_count"),
|
&cfg.name("utxo_count"),
|
||||||
cfg.version,
|
cfg.version,
|
||||||
v1,
|
Version::TWO,
|
||||||
cfg.indexes,
|
cfg.indexes,
|
||||||
cfg.cached_starts,
|
cfg.cached_starts,
|
||||||
)?,
|
)?,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use brk_cohort::{Loss, Profit, ProfitabilityRange};
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, Bitcoin, Cents, Dollars, Sats, Version};
|
use brk_types::{Bitcoin, Cents, Dollars, PartsPerMillionSigned32, Sats, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
internal::{
|
internal::{
|
||||||
PerBlock, RatioPerBlock, ValuePerBlock, ValuePerBlockWithDeltas, WindowStartVec, Windows,
|
PerBlock, RatioPerBlock, ValuePerBlock, ValuePerBlockWithDeltas, WindowStartVec, Windows,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
@@ -24,7 +24,7 @@ pub struct ProfitabilityBucket<M: StorageMode = Rw> {
|
|||||||
pub supply: WithSth<ValuePerBlockWithDeltas<M>, ValuePerBlock<M>>,
|
pub supply: WithSth<ValuePerBlockWithDeltas<M>, ValuePerBlock<M>>,
|
||||||
pub realized_cap: WithSth<PerBlock<Dollars, M>>,
|
pub realized_cap: WithSth<PerBlock<Dollars, M>>,
|
||||||
pub unrealized_pnl: WithSth<PerBlock<Dollars, M>>,
|
pub unrealized_pnl: WithSth<PerBlock<Dollars, M>>,
|
||||||
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
|
pub nupl: RatioPerBlock<PartsPerMillionSigned32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: StorageMode> ProfitabilityBucket<M> {
|
impl<M: StorageMode> ProfitabilityBucket<M> {
|
||||||
@@ -115,7 +115,7 @@ impl ProfitabilityBucket {
|
|||||||
|
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
is_profit: bool,
|
is_profit: bool,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
@@ -152,7 +152,7 @@ impl ProfitabilityBucket {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.nupl.bps.height.compute_transform3(
|
self.nupl.raw.height.compute_transform3(
|
||||||
max_from,
|
max_from,
|
||||||
&prices.spot.cents.height,
|
&prices.spot.cents.height,
|
||||||
&self.realized_cap.all.height,
|
&self.realized_cap.all.height,
|
||||||
@@ -161,11 +161,11 @@ impl ProfitabilityBucket {
|
|||||||
let p = spot.as_u128();
|
let p = spot.as_u128();
|
||||||
let supply = supply_sats.as_u128();
|
let supply = supply_sats.as_u128();
|
||||||
if p == 0 || supply == 0 {
|
if p == 0 || supply == 0 {
|
||||||
(i, BasisPointsSigned32::ZERO)
|
(i, PartsPerMillionSigned32::ZERO)
|
||||||
} else {
|
} else {
|
||||||
let rp = Cents::from(cap_dollars).as_u128() * Sats::ONE_BTC_U128 / supply;
|
let rp = Cents::from(cap_dollars).as_u128() * Sats::ONE_BTC_U128 / supply;
|
||||||
let bps = ((p as i128 - rp as i128) * 10000) / p as i128;
|
let ratio = (p as f64 - rp as f64) / p as f64;
|
||||||
(i, BasisPointsSigned32::from(bps as i32))
|
(i, PartsPerMillionSigned32::from(ratio))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exit,
|
exit,
|
||||||
@@ -176,7 +176,7 @@ impl ProfitabilityBucket {
|
|||||||
|
|
||||||
pub(crate) fn compute_from_ranges(
|
pub(crate) fn compute_from_ranges(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
is_profit: bool,
|
is_profit: bool,
|
||||||
sources: &[&ProfitabilityBucket],
|
sources: &[&ProfitabilityBucket],
|
||||||
@@ -230,7 +230,7 @@ impl ProfitabilityBucket {
|
|||||||
&mut self.realized_cap.sth.height,
|
&mut self.realized_cap.sth.height,
|
||||||
&mut self.unrealized_pnl.all.height,
|
&mut self.unrealized_pnl.all.height,
|
||||||
&mut self.unrealized_pnl.sth.height,
|
&mut self.unrealized_pnl.sth.height,
|
||||||
&mut self.nupl.bps.height,
|
&mut self.nupl.raw.height,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ impl ProfitabilityMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use brk_error::Result;
|
|||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Dollars, Height, StoredF64, Version,
|
Bitcoin, Cents, CentsSigned, Dollars, Height, PartsPerMillionSigned64, StoredF64, Version,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{
|
use vecdb::{
|
||||||
@@ -16,7 +16,7 @@ use crate::{
|
|||||||
FiatPerBlockCumulativeWithSumsAndDeltas, LazyPerBlock, NegCentsUnsignedToDollars,
|
FiatPerBlockCumulativeWithSumsAndDeltas, LazyPerBlock, NegCentsUnsignedToDollars,
|
||||||
PerBlockCumulativeRolling, RatioCents64, RollingWindow24hPerBlock, Windows,
|
PerBlockCumulativeRolling, RatioCents64, RollingWindow24hPerBlock, Windows,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::distribution::metrics::ImportConfig;
|
use crate::distribution::metrics::ImportConfig;
|
||||||
@@ -45,8 +45,12 @@ pub struct RealizedCore<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[traversable(wrap = "loss", rename = "negative")]
|
#[traversable(wrap = "loss", rename = "negative")]
|
||||||
pub neg_loss: NegRealizedLoss,
|
pub neg_loss: NegRealizedLoss,
|
||||||
pub net_pnl:
|
pub net_pnl: FiatPerBlockCumulativeWithSumsAndDeltas<
|
||||||
FiatPerBlockCumulativeWithSumsAndDeltas<CentsSigned, CentsSigned, BasisPointsSigned32, M>,
|
CentsSigned,
|
||||||
|
CentsSigned,
|
||||||
|
PartsPerMillionSigned64,
|
||||||
|
M,
|
||||||
|
>,
|
||||||
pub sopr: RealizedSoprCore<M>,
|
pub sopr: RealizedSoprCore<M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +84,7 @@ impl RealizedCore {
|
|||||||
cfg.db,
|
cfg.db,
|
||||||
&cfg.name("net_realized_pnl"),
|
&cfg.name("net_realized_pnl"),
|
||||||
cfg.version + v1,
|
cfg.version + v1,
|
||||||
Version::new(4),
|
Version::new(5),
|
||||||
cfg.indexes,
|
cfg.indexes,
|
||||||
cfg.cached_starts,
|
cfg.cached_starts,
|
||||||
)?;
|
)?;
|
||||||
@@ -166,7 +170,7 @@ impl RealizedCore {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||||
transfer_volume_sum_24h_cents: &impl ReadableVec<Height, Cents>,
|
transfer_volume_sum_24h_cents: &impl ReadableVec<Height, Cents>,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use brk_error::Result;
|
|||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats,
|
Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats, Dollars, Height, PartsPerMillion32,
|
||||||
Dollars, Height, StoredF64, Version,
|
PartsPerMillionSigned64, StoredF64, Version,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
@@ -13,12 +13,11 @@ use crate::{
|
|||||||
distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital},
|
distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital},
|
||||||
internal::{
|
internal::{
|
||||||
FiatPerBlockCumulativeWithSums, PercentPerBlock, PercentRollingWindows,
|
FiatPerBlockCumulativeWithSums, PercentPerBlock, PercentRollingWindows,
|
||||||
PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32,
|
PriceWithRatioExtendedPerBlock, RatioCents, RatioCents64, RatioCentsSignedCents,
|
||||||
RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlockPercentiles,
|
RatioCentsSignedDollars, RatioDollars, RatioPerBlockPercentiles, RatioPerBlockStdDevBands,
|
||||||
RatioPerBlockStdDevBands, RatioSma, RollingWindows, RollingWindowsFrom1w,
|
RatioSma, RollingWindows, RollingWindowsFrom1w, ValuePerBlockCumulativeRolling,
|
||||||
ValuePerBlockCumulativeRolling,
|
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::distribution::metrics::ImportConfig;
|
use crate::distribution::metrics::ImportConfig;
|
||||||
@@ -28,9 +27,9 @@ use super::RealizedCore;
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RealizedNetPnl<M: StorageMode = Rw> {
|
pub struct RealizedNetPnl<M: StorageMode = Rw> {
|
||||||
#[traversable(wrap = "change_1m", rename = "to_rcap")]
|
#[traversable(wrap = "change_1m", rename = "to_rcap")]
|
||||||
pub change_1m_to_rcap: PercentPerBlock<BasisPointsSigned32, M>,
|
pub change_1m_to_rcap: PercentPerBlock<PartsPerMillionSigned64, M>,
|
||||||
#[traversable(wrap = "change_1m", rename = "to_mcap")]
|
#[traversable(wrap = "change_1m", rename = "to_mcap")]
|
||||||
pub change_1m_to_mcap: PercentPerBlock<BasisPointsSigned32, M>,
|
pub change_1m_to_mcap: PercentPerBlock<PartsPerMillionSigned64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
@@ -60,7 +59,7 @@ pub struct RealizedFull<M: StorageMode = Rw> {
|
|||||||
pub core: RealizedCore<M>,
|
pub core: RealizedCore<M>,
|
||||||
|
|
||||||
pub gross_pnl: FiatPerBlockCumulativeWithSums<Cents, M>,
|
pub gross_pnl: FiatPerBlockCumulativeWithSums<Cents, M>,
|
||||||
pub sell_side_risk_ratio: PercentRollingWindows<BasisPoints32, M>,
|
pub sell_side_risk_ratio: PercentRollingWindows<PartsPerMillion32, M>,
|
||||||
pub net_pnl: RealizedNetPnl<M>,
|
pub net_pnl: RealizedNetPnl<M>,
|
||||||
pub sopr: RealizedSopr<M>,
|
pub sopr: RealizedSopr<M>,
|
||||||
pub peak_regret: RealizedPeakRegret<M>,
|
pub peak_regret: RealizedPeakRegret<M>,
|
||||||
@@ -71,7 +70,7 @@ pub struct RealizedFull<M: StorageMode = Rw> {
|
|||||||
#[traversable(hidden)]
|
#[traversable(hidden)]
|
||||||
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
|
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
|
||||||
#[traversable(wrap = "cap", rename = "to_own_mcap")]
|
#[traversable(wrap = "cap", rename = "to_own_mcap")]
|
||||||
pub cap_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
|
pub cap_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
|
|
||||||
#[traversable(wrap = "price", rename = "percentiles")]
|
#[traversable(wrap = "price", rename = "percentiles")]
|
||||||
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
|
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
|
||||||
@@ -95,8 +94,8 @@ impl RealizedFull {
|
|||||||
|
|
||||||
// Net PnL
|
// Net PnL
|
||||||
let net_pnl = RealizedNetPnl {
|
let net_pnl = RealizedNetPnl {
|
||||||
change_1m_to_rcap: cfg.import("net_pnl_change_1m_to_rcap", Version::new(4))?,
|
change_1m_to_rcap: cfg.import("net_pnl_change_1m_to_rcap", Version::new(5))?,
|
||||||
change_1m_to_mcap: cfg.import("net_pnl_change_1m_to_mcap", Version::new(4))?,
|
change_1m_to_mcap: cfg.import("net_pnl_change_1m_to_mcap", Version::new(5))?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// SOPR
|
// SOPR
|
||||||
@@ -206,7 +205,7 @@ impl RealizedFull {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) {
|
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) -> Cents {
|
||||||
self.cap_raw.push(accum.cap_raw);
|
self.cap_raw.push(accum.cap_raw);
|
||||||
self.capitalized.cap_raw.push(accum.capitalized_cap_raw);
|
self.capitalized.cap_raw.push(accum.capitalized_cap_raw);
|
||||||
|
|
||||||
@@ -221,6 +220,8 @@ impl RealizedFull {
|
|||||||
self.capitalized.price.cents.height.push(capitalized_price);
|
self.capitalized.price.cents.height.push(capitalized_price);
|
||||||
|
|
||||||
self.peak_regret.value.block.cents.push(accum.peak_regret());
|
self.peak_regret.value.block.cents.push(accum.peak_regret());
|
||||||
|
|
||||||
|
capitalized_price
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_rest_part1(
|
pub(crate) fn compute_rest_part1(
|
||||||
@@ -240,7 +241,7 @@ impl RealizedFull {
|
|||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||||
@@ -284,7 +285,7 @@ impl RealizedFull {
|
|||||||
// Net PnL 1m change relative to rcap and mcap
|
// Net PnL 1m change relative to rcap and mcap
|
||||||
self.net_pnl
|
self.net_pnl
|
||||||
.change_1m_to_rcap
|
.change_1m_to_rcap
|
||||||
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps32>(
|
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCents<PartsPerMillionSigned64>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
&self.core.net_pnl.delta.absolute._1m.cents.height,
|
&self.core.net_pnl.delta.absolute._1m.cents.height,
|
||||||
&self.core.minimal.cap.cents.height,
|
&self.core.minimal.cap.cents.height,
|
||||||
@@ -292,7 +293,11 @@ impl RealizedFull {
|
|||||||
)?;
|
)?;
|
||||||
self.net_pnl
|
self.net_pnl
|
||||||
.change_1m_to_mcap
|
.change_1m_to_mcap
|
||||||
.compute_binary::<CentsSigned, Dollars, RatioCentsSignedDollarsBps32>(
|
.compute_binary::<
|
||||||
|
CentsSigned,
|
||||||
|
Dollars,
|
||||||
|
RatioCentsSignedDollars<PartsPerMillionSigned64>,
|
||||||
|
>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
&self.core.net_pnl.delta.absolute._1m.cents.height,
|
&self.core.net_pnl.delta.absolute._1m.cents.height,
|
||||||
height_to_market_cap,
|
height_to_market_cap,
|
||||||
@@ -311,7 +316,7 @@ impl RealizedFull {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(self.gross_pnl.sum.as_array())
|
.zip(self.gross_pnl.sum.as_array())
|
||||||
{
|
{
|
||||||
ssrr.compute_binary::<Cents, Cents, RatioCentsBp32>(
|
ssrr.compute_binary::<Cents, Cents, RatioCents<PartsPerMillion32>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
&rv.cents.height,
|
&rv.cents.height,
|
||||||
&self.core.minimal.cap.cents.height,
|
&self.core.minimal.cap.cents.height,
|
||||||
@@ -321,7 +326,7 @@ impl RealizedFull {
|
|||||||
|
|
||||||
// Realized cap relative to own market cap
|
// Realized cap relative to own market cap
|
||||||
self.cap_to_own_mcap
|
self.cap_to_own_mcap
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
&self.core.minimal.cap.usd.height,
|
&self.core.minimal.cap.usd.height,
|
||||||
height_to_market_cap,
|
height_to_market_cap,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use brk_error::Result;
|
|||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Height, Sats, StoredF32,
|
Bitcoin, Cents, CentsSigned, Height, PartsPerMillion64, PartsPerMillionSigned64, Sats,
|
||||||
Version,
|
StoredF32, Version,
|
||||||
};
|
};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
@@ -13,14 +13,14 @@ use crate::{
|
|||||||
FiatPerBlockCumulativeWithSums, FiatPerBlockWithDeltas, Identity, LazyPerBlock,
|
FiatPerBlockCumulativeWithSums, FiatPerBlockWithDeltas, Identity, LazyPerBlock,
|
||||||
PriceWithRatioPerBlock,
|
PriceWithRatioPerBlock,
|
||||||
},
|
},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::distribution::metrics::ImportConfig;
|
use crate::distribution::metrics::ImportConfig;
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RealizedMinimal<M: StorageMode = Rw> {
|
pub struct RealizedMinimal<M: StorageMode = Rw> {
|
||||||
pub cap: FiatPerBlockWithDeltas<Cents, CentsSigned, BasisPointsSigned32, M>,
|
pub cap: FiatPerBlockWithDeltas<Cents, CentsSigned, PartsPerMillionSigned64, M>,
|
||||||
pub profit: FiatPerBlockCumulativeWithSums<Cents, M>,
|
pub profit: FiatPerBlockCumulativeWithSums<Cents, M>,
|
||||||
pub loss: FiatPerBlockCumulativeWithSums<Cents, M>,
|
pub loss: FiatPerBlockCumulativeWithSums<Cents, M>,
|
||||||
pub price: PriceWithRatioPerBlock<M>,
|
pub price: PriceWithRatioPerBlock<M>,
|
||||||
@@ -35,13 +35,13 @@ impl RealizedMinimal {
|
|||||||
cfg.db,
|
cfg.db,
|
||||||
&cfg.name("realized_cap"),
|
&cfg.name("realized_cap"),
|
||||||
cfg.version,
|
cfg.version,
|
||||||
v1,
|
Version::TWO,
|
||||||
cfg.indexes,
|
cfg.indexes,
|
||||||
cfg.cached_starts,
|
cfg.cached_starts,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let price: PriceWithRatioPerBlock = cfg.import("realized_price", v1)?;
|
let price: PriceWithRatioPerBlock = cfg.import("realized_price", v1)?;
|
||||||
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
|
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, PartsPerMillion64>(
|
||||||
&cfg.name("mvrv"),
|
&cfg.name("mvrv"),
|
||||||
cfg.version,
|
cfg.version,
|
||||||
&price.ratio,
|
&price.ratio,
|
||||||
@@ -104,7 +104,7 @@ impl RealizedMinimal {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_part2(
|
pub(crate) fn compute_rest_part2(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, BasisPoints32, BasisPointsSigned32, Dollars, Height, Version};
|
use brk_types::{Dollars, Height, PartsPerMillion32, PartsPerMillionSigned32, Version};
|
||||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{PercentPerBlock, RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32};
|
use crate::internal::{PercentPerBlock, RatioDollars};
|
||||||
|
|
||||||
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
|
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
|
||||||
|
|
||||||
@@ -11,11 +11,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
|
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
|
||||||
#[traversable(wrap = "unrealized/profit", rename = "to_own_mcap")]
|
#[traversable(wrap = "unrealized/profit", rename = "to_own_mcap")]
|
||||||
pub unrealized_profit_to_own_mcap: PercentPerBlock<BasisPoints16, M>,
|
pub unrealized_profit_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "unrealized/loss", rename = "to_own_mcap")]
|
#[traversable(wrap = "unrealized/loss", rename = "to_own_mcap")]
|
||||||
pub unrealized_loss_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
|
pub unrealized_loss_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_mcap")]
|
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_mcap")]
|
||||||
pub net_unrealized_pnl_to_own_mcap: PercentPerBlock<BasisPointsSigned32, M>,
|
pub net_unrealized_pnl_to_own_mcap: PercentPerBlock<PartsPerMillionSigned32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelativeExtendedOwnMarketCap {
|
impl RelativeExtendedOwnMarketCap {
|
||||||
@@ -39,21 +39,21 @@ impl RelativeExtendedOwnMarketCap {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.unrealized_profit_to_own_mcap
|
self.unrealized_profit_to_own_mcap
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.profit.usd.height,
|
&unrealized.profit.usd.height,
|
||||||
own_market_cap,
|
own_market_cap,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.unrealized_loss_to_own_mcap
|
self.unrealized_loss_to_own_mcap
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.loss.usd.height,
|
&unrealized.loss.usd.height,
|
||||||
own_market_cap,
|
own_market_cap,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.net_unrealized_pnl_to_own_mcap
|
self.net_unrealized_pnl_to_own_mcap
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBps32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillionSigned32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.net_pnl.usd.height,
|
&unrealized.net_pnl.usd.height,
|
||||||
own_market_cap,
|
own_market_cap,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, BasisPointsSigned32, Dollars, Height, Version};
|
use brk_types::{Dollars, Height, PartsPerMillion32, PartsPerMillionSigned32, Version};
|
||||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{PercentPerBlock, RatioDollarsBp16, RatioDollarsBps32};
|
use crate::internal::{PercentPerBlock, RatioDollars};
|
||||||
|
|
||||||
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
|
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
|
||||||
|
|
||||||
@@ -11,11 +11,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> {
|
pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> {
|
||||||
#[traversable(wrap = "unrealized/profit", rename = "to_own_gross_pnl")]
|
#[traversable(wrap = "unrealized/profit", rename = "to_own_gross_pnl")]
|
||||||
pub unrealized_profit_to_own_gross_pnl: PercentPerBlock<BasisPoints16, M>,
|
pub unrealized_profit_to_own_gross_pnl: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "unrealized/loss", rename = "to_own_gross_pnl")]
|
#[traversable(wrap = "unrealized/loss", rename = "to_own_gross_pnl")]
|
||||||
pub unrealized_loss_to_own_gross_pnl: PercentPerBlock<BasisPoints16, M>,
|
pub unrealized_loss_to_own_gross_pnl: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_gross_pnl")]
|
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_gross_pnl")]
|
||||||
pub net_unrealized_pnl_to_own_gross_pnl: PercentPerBlock<BasisPointsSigned32, M>,
|
pub net_unrealized_pnl_to_own_gross_pnl: PercentPerBlock<PartsPerMillionSigned32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelativeExtendedOwnPnl {
|
impl RelativeExtendedOwnPnl {
|
||||||
@@ -39,21 +39,21 @@ impl RelativeExtendedOwnPnl {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.unrealized_profit_to_own_gross_pnl
|
self.unrealized_profit_to_own_gross_pnl
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.profit.usd.height,
|
&unrealized.profit.usd.height,
|
||||||
gross_pnl_usd,
|
gross_pnl_usd,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.unrealized_loss_to_own_gross_pnl
|
self.unrealized_loss_to_own_gross_pnl
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.loss.usd.height,
|
&unrealized.loss.usd.height,
|
||||||
gross_pnl_usd,
|
gross_pnl_usd,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.net_unrealized_pnl_to_own_gross_pnl
|
self.net_unrealized_pnl_to_own_gross_pnl
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBps32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillionSigned32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.net_pnl.usd.height,
|
&unrealized.net_pnl.usd.height,
|
||||||
gross_pnl_usd,
|
gross_pnl_usd,
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, Dollars, Height, Sats, Version};
|
use brk_types::{Dollars, Height, PartsPerMillion32, Sats, Version};
|
||||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::metrics::{ImportConfig, SupplyCore, UnrealizedBasic},
|
distribution::metrics::{ImportConfig, SupplyCore, UnrealizedBasic},
|
||||||
internal::{PercentPerBlock, RatioDollarsBp16, RatioSatsBp16},
|
internal::{PercentPerBlock, RatioDollars, RatioSats},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Full relative metrics (sth/lth/all tier).
|
/// Full relative metrics (sth/lth/all tier).
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RelativeFull<M: StorageMode = Rw> {
|
pub struct RelativeFull<M: StorageMode = Rw> {
|
||||||
#[traversable(wrap = "supply/in_profit", rename = "share")]
|
#[traversable(wrap = "supply/in_profit", rename = "share")]
|
||||||
pub supply_in_profit_share: PercentPerBlock<BasisPoints16, M>,
|
pub supply_in_profit_share: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "supply/in_loss", rename = "share")]
|
#[traversable(wrap = "supply/in_loss", rename = "share")]
|
||||||
pub supply_in_loss_share: PercentPerBlock<BasisPoints16, M>,
|
pub supply_in_loss_share: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
|
|
||||||
#[traversable(wrap = "unrealized/profit", rename = "to_mcap")]
|
#[traversable(wrap = "unrealized/profit", rename = "to_mcap")]
|
||||||
pub unrealized_profit_to_mcap: PercentPerBlock<BasisPoints16, M>,
|
pub unrealized_profit_to_mcap: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "unrealized/loss", rename = "to_mcap")]
|
#[traversable(wrap = "unrealized/loss", rename = "to_mcap")]
|
||||||
pub unrealized_loss_to_mcap: PercentPerBlock<BasisPoints16, M>,
|
pub unrealized_loss_to_mcap: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelativeFull {
|
impl RelativeFull {
|
||||||
@@ -44,14 +44,14 @@ impl RelativeFull {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.supply_in_profit_share
|
self.supply_in_profit_share
|
||||||
.compute_binary::<Sats, Sats, RatioSatsBp16>(
|
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&supply.in_profit.sats.height,
|
&supply.in_profit.sats.height,
|
||||||
&supply.total.sats.height,
|
&supply.total.sats.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.supply_in_loss_share
|
self.supply_in_loss_share
|
||||||
.compute_binary::<Sats, Sats, RatioSatsBp16>(
|
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&supply.in_loss.sats.height,
|
&supply.in_loss.sats.height,
|
||||||
&supply.total.sats.height,
|
&supply.total.sats.height,
|
||||||
@@ -59,14 +59,14 @@ impl RelativeFull {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.unrealized_profit_to_mcap
|
self.unrealized_profit_to_mcap
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.profit.usd.height,
|
&unrealized.profit.usd.height,
|
||||||
market_cap,
|
market_cap,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.unrealized_loss_to_mcap
|
self.unrealized_loss_to_mcap
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.loss.usd.height,
|
&unrealized.loss.usd.height,
|
||||||
market_cap,
|
market_cap,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, Cents, Height, Version};
|
use brk_types::{Cents, Height, PartsPerMillion32, Version};
|
||||||
use vecdb::{Exit, Rw, StorageMode};
|
use vecdb::{Exit, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{PercentPerBlock, RatioCentsBp16};
|
use crate::internal::{PercentPerBlock, RatioCents};
|
||||||
|
|
||||||
use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
|
use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
|
||||||
|
|
||||||
@@ -12,9 +12,9 @@ use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RelativeInvestedCapital<M: StorageMode = Rw> {
|
pub struct RelativeInvestedCapital<M: StorageMode = Rw> {
|
||||||
#[traversable(wrap = "invested_capital/in_profit", rename = "share")]
|
#[traversable(wrap = "invested_capital/in_profit", rename = "share")]
|
||||||
pub in_profit_share: PercentPerBlock<BasisPoints16, M>,
|
pub in_profit_share: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
#[traversable(wrap = "invested_capital/in_loss", rename = "share")]
|
#[traversable(wrap = "invested_capital/in_loss", rename = "share")]
|
||||||
pub in_loss_share: PercentPerBlock<BasisPoints16, M>,
|
pub in_loss_share: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RelativeInvestedCapital {
|
impl RelativeInvestedCapital {
|
||||||
@@ -35,14 +35,14 @@ impl RelativeInvestedCapital {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let realized_cap = &realized.core.minimal.cap.cents.height;
|
let realized_cap = &realized.core.minimal.cap.cents.height;
|
||||||
self.in_profit_share
|
self.in_profit_share
|
||||||
.compute_binary::<Cents, Cents, RatioCentsBp16>(
|
.compute_binary::<Cents, Cents, RatioCents<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.invested_capital.in_profit.cents.height,
|
&unrealized.invested_capital.in_profit.cents.height,
|
||||||
realized_cap,
|
realized_cap,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
self.in_loss_share
|
self.in_loss_share
|
||||||
.compute_binary::<Cents, Cents, RatioCentsBp16>(
|
.compute_binary::<Cents, Cents, RatioCents<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&unrealized.invested_capital.in_loss.cents.height,
|
&unrealized.invested_capital.in_loss.cents.height,
|
||||||
realized_cap,
|
realized_cap,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use brk_traversable::Traversable;
|
|||||||
use brk_types::{Height, Sats, StoredU64, Version};
|
use brk_types::{Height, Sats, StoredU64, Version};
|
||||||
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{indexes, internal::ValuePerBlock, prices};
|
use crate::{indexes, internal::ValuePerBlock, price};
|
||||||
|
|
||||||
/// Average amount held per UTXO and per funded address.
|
/// Average amount held per UTXO and per funded address.
|
||||||
///
|
///
|
||||||
@@ -53,7 +53,7 @@ impl AvgAmountMetrics {
|
|||||||
|
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
supply_sats: &impl ReadableVec<Height, Sats>,
|
supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
utxo_count: &impl ReadableVec<Height, StoredU64>,
|
utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
funded_addr_count: &impl ReadableVec<Height, StoredU64>,
|
funded_addr_count: &impl ReadableVec<Height, StoredU64>,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Lengths;
|
use brk_indexer::Lengths;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, BasisPointsSigned32, Height, Sats, SatsSigned, Version};
|
use brk_types::{Height, PartsPerMillion32, PartsPerMillionSigned64, Sats, SatsSigned, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
prices,
|
price,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::internal::{
|
use crate::internal::{
|
||||||
LazyRollingDeltasAmountFromHeight, PercentPerBlock, RatioSatsBp16, ValuePerBlock,
|
LazyRollingDeltasAmountFromHeight, PercentPerBlock, RatioSats, ValuePerBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::distribution::metrics::ImportConfig;
|
use crate::distribution::metrics::ImportConfig;
|
||||||
@@ -19,9 +19,9 @@ use crate::distribution::metrics::ImportConfig;
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct SupplyBase<M: StorageMode = Rw> {
|
pub struct SupplyBase<M: StorageMode = Rw> {
|
||||||
pub total: ValuePerBlock<M>,
|
pub total: ValuePerBlock<M>,
|
||||||
pub delta: LazyRollingDeltasAmountFromHeight<Sats, SatsSigned, BasisPointsSigned32>,
|
pub delta: LazyRollingDeltasAmountFromHeight<Sats, SatsSigned, PartsPerMillionSigned64>,
|
||||||
#[traversable(rename = "dominance")]
|
#[traversable(rename = "dominance")]
|
||||||
pub dominance: PercentPerBlock<BasisPoints16, M>,
|
pub dominance: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SupplyBase {
|
impl SupplyBase {
|
||||||
@@ -30,7 +30,7 @@ impl SupplyBase {
|
|||||||
|
|
||||||
let delta = LazyRollingDeltasAmountFromHeight::new(
|
let delta = LazyRollingDeltasAmountFromHeight::new(
|
||||||
&cfg.name("supply_delta"),
|
&cfg.name("supply_delta"),
|
||||||
cfg.version + Version::ONE,
|
cfg.version + Version::TWO,
|
||||||
&supply.sats.height,
|
&supply.sats.height,
|
||||||
cfg.cached_starts,
|
cfg.cached_starts,
|
||||||
cfg.indexes,
|
cfg.indexes,
|
||||||
@@ -58,13 +58,13 @@ impl SupplyBase {
|
|||||||
vec![
|
vec![
|
||||||
&mut self.total.sats.height as &mut dyn AnyStoredVec,
|
&mut self.total.sats.height as &mut dyn AnyStoredVec,
|
||||||
&mut self.total.cents.height,
|
&mut self.total.cents.height,
|
||||||
&mut self.dominance.bps.height,
|
&mut self.dominance.raw.height,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
max_from: Height,
|
max_from: Height,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -77,7 +77,8 @@ impl SupplyBase {
|
|||||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.dominance.compute_binary::<Sats, Sats, RatioSatsBp16>(
|
self.dominance
|
||||||
|
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.total.sats.height,
|
&self.total.sats.height,
|
||||||
all_supply_sats,
|
all_supply_sats,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use brk_types::{Height, Version};
|
|||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{distribution::state::UnrealizedState, prices};
|
use crate::{distribution::state::UnrealizedState, price};
|
||||||
|
|
||||||
use crate::internal::{
|
use crate::internal::{
|
||||||
HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValuePerBlock, ValuePerBlock,
|
HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValuePerBlock, ValuePerBlock,
|
||||||
@@ -72,7 +72,7 @@ impl SupplyCore {
|
|||||||
|
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
max_from: Height,
|
max_from: Height,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode,
|
|||||||
|
|
||||||
use crate::distribution::state::UnrealizedState;
|
use crate::distribution::state::UnrealizedState;
|
||||||
use crate::internal::{CentsSubtractToCentsSigned, FiatPerBlock};
|
use crate::internal::{CentsSubtractToCentsSigned, FiatPerBlock};
|
||||||
use crate::{distribution::metrics::ImportConfig, prices};
|
use crate::{distribution::metrics::ImportConfig, price};
|
||||||
|
|
||||||
use super::UnrealizedCore;
|
use super::UnrealizedCore;
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ impl UnrealizedFull {
|
|||||||
|
|
||||||
pub(crate) fn compute_rest_all(
|
pub(crate) fn compute_rest_all(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, Cents, Height, Version};
|
use brk_types::{Cents, Height, PartsPerMillionSigned32, Version};
|
||||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::RatioPerBlock;
|
use crate::internal::RatioPerBlock;
|
||||||
@@ -9,7 +9,7 @@ use crate::distribution::metrics::ImportConfig;
|
|||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct UnrealizedMinimal<M: StorageMode = Rw> {
|
pub struct UnrealizedMinimal<M: StorageMode = Rw> {
|
||||||
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
|
pub nupl: RatioPerBlock<PartsPerMillionSigned32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnrealizedMinimal {
|
impl UnrealizedMinimal {
|
||||||
@@ -31,18 +31,18 @@ impl UnrealizedMinimal {
|
|||||||
realized_price: &impl ReadableVec<Height, Cents>,
|
realized_price: &impl ReadableVec<Height, Cents>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.nupl.bps.height.compute_transform2(
|
self.nupl.raw.height.compute_transform2(
|
||||||
max_from,
|
max_from,
|
||||||
spot_price,
|
spot_price,
|
||||||
realized_price,
|
realized_price,
|
||||||
|(i, price, realized_price, ..)| {
|
|(i, price, realized_price, ..)| {
|
||||||
let p = price.as_u128();
|
let p = price.as_u128();
|
||||||
if p == 0 {
|
if p == 0 {
|
||||||
(i, BasisPointsSigned32::ZERO)
|
(i, PartsPerMillionSigned32::ZERO)
|
||||||
} else {
|
} else {
|
||||||
let rp = realized_price.as_u128();
|
let rp = realized_price.as_u128();
|
||||||
let nupl_bps = ((p as i128 - rp as i128) * 10000) / p as i128;
|
let ratio = (p as f64 - rp as f64) / p as f64;
|
||||||
(i, BasisPointsSigned32::from(nupl_bps as i32))
|
(i, PartsPerMillionSigned32::from(ratio))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exit,
|
exit,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use brk_indexer::Lengths;
|
|||||||
use brk_types::{Height, Sats};
|
use brk_types::{Height, Sats};
|
||||||
use vecdb::{Exit, ReadableVec};
|
use vecdb::{Exit, ReadableVec};
|
||||||
|
|
||||||
use crate::{distribution::state::UnrealizedState, prices};
|
use crate::{distribution::state::UnrealizedState, price};
|
||||||
|
|
||||||
pub trait UnrealizedLike: Send + Sync {
|
pub trait UnrealizedLike: Send + Sync {
|
||||||
fn as_core(&self) -> &UnrealizedCore;
|
fn as_core(&self) -> &UnrealizedCore;
|
||||||
@@ -22,7 +22,7 @@ pub trait UnrealizedLike: Send + Sync {
|
|||||||
fn push_state(&mut self, state: &UnrealizedState);
|
fn push_state(&mut self, state: &UnrealizedState);
|
||||||
fn compute_rest(
|
fn compute_rest(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
@@ -46,7 +46,7 @@ impl UnrealizedLike for UnrealizedCore {
|
|||||||
}
|
}
|
||||||
fn compute_rest(
|
fn compute_rest(
|
||||||
&mut self,
|
&mut self,
|
||||||
_prices: &prices::Vecs,
|
_prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
_supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
_supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
_supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
_supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
@@ -72,7 +72,7 @@ impl UnrealizedLike for UnrealizedFull {
|
|||||||
}
|
}
|
||||||
fn compute_rest(
|
fn compute_rest(
|
||||||
&mut self,
|
&mut self,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
starting_lengths: &Lengths,
|
starting_lengths: &Lengths,
|
||||||
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::ops::{Add, AddAssign, SubAssign};
|
use std::ops::{Add, AddAssign, SubAssign};
|
||||||
|
|
||||||
|
use brk_cohort::EntryPrice;
|
||||||
use brk_types::{Cents, SupplyState, Timestamp};
|
use brk_types::{Cents, SupplyState, Timestamp};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@@ -8,6 +9,8 @@ pub struct BlockState {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub supply: SupplyState,
|
pub supply: SupplyState,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
pub entry: EntryPrice,
|
||||||
|
#[serde(skip)]
|
||||||
pub price: Cents,
|
pub price: Cents,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: Timestamp,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use brk_cohort::{ByAddrType, Filter};
|
use brk_cohort::{ByAddrType, EntryPrice, Filter};
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
@@ -29,7 +29,7 @@ use crate::{
|
|||||||
PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes,
|
PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes,
|
||||||
db_utils::{finalize_db, open_db},
|
db_utils::{finalize_db, open_db},
|
||||||
},
|
},
|
||||||
outputs, prices, transactions,
|
outputs, price, transactions,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -316,7 +316,7 @@ impl Vecs {
|
|||||||
outputs: &outputs::Vecs,
|
outputs: &outputs::Vecs,
|
||||||
transactions: &transactions::Vecs,
|
transactions: &transactions::Vecs,
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.db.sync_bg_tasks()?;
|
self.db.sync_bg_tasks()?;
|
||||||
@@ -436,13 +436,34 @@ impl Vecs {
|
|||||||
let end = usize::from(recovered_height);
|
let end = usize::from(recovered_height);
|
||||||
debug!("building supply_state vec for {} heights", recovered_height);
|
debug!("building supply_state vec for {} heights", recovered_height);
|
||||||
let supply_state_data: Vec<_> = self.supply_state.collect_range_at(0, end);
|
let supply_state_data: Vec<_> = self.supply_state.collect_range_at(0, end);
|
||||||
|
let capitalized_price_data: Vec<_> = self
|
||||||
|
.utxo_cohorts
|
||||||
|
.all
|
||||||
|
.metrics
|
||||||
|
.realized
|
||||||
|
.capitalized
|
||||||
|
.price
|
||||||
|
.cents
|
||||||
|
.height
|
||||||
|
.collect_range_at(0, end);
|
||||||
|
|
||||||
|
let mut entry_anchor = Cents::ZERO;
|
||||||
chain_state = supply_state_data
|
chain_state = supply_state_data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(h, supply)| BlockState {
|
.map(|(h, supply)| {
|
||||||
|
let price = self.caches.prices[h];
|
||||||
|
let entry = EntryPrice::from_is_discount(
|
||||||
|
entry_anchor == Cents::ZERO || price <= entry_anchor,
|
||||||
|
);
|
||||||
|
entry_anchor = capitalized_price_data[h];
|
||||||
|
|
||||||
|
BlockState {
|
||||||
supply,
|
supply,
|
||||||
price: self.caches.prices[h],
|
entry,
|
||||||
|
price,
|
||||||
timestamp: self.caches.timestamps[h],
|
timestamp: self.caches.timestamps[h],
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
debug!("chain_state rebuilt");
|
debug!("chain_state rebuilt");
|
||||||
@@ -474,6 +495,20 @@ impl Vecs {
|
|||||||
let prices = std::mem::take(&mut self.caches.prices);
|
let prices = std::mem::take(&mut self.caches.prices);
|
||||||
let timestamps = std::mem::take(&mut self.caches.timestamps);
|
let timestamps = std::mem::take(&mut self.caches.timestamps);
|
||||||
let price_range_max = std::mem::take(&mut self.caches.price_range_max);
|
let price_range_max = std::mem::take(&mut self.caches.price_range_max);
|
||||||
|
let entry_anchor = starting_height
|
||||||
|
.decremented()
|
||||||
|
.and_then(|height| {
|
||||||
|
self.utxo_cohorts
|
||||||
|
.all
|
||||||
|
.metrics
|
||||||
|
.realized
|
||||||
|
.capitalized
|
||||||
|
.price
|
||||||
|
.cents
|
||||||
|
.height
|
||||||
|
.collect_one(height)
|
||||||
|
})
|
||||||
|
.unwrap_or(Cents::ZERO);
|
||||||
|
|
||||||
process_blocks(
|
process_blocks(
|
||||||
self,
|
self,
|
||||||
@@ -486,6 +521,7 @@ impl Vecs {
|
|||||||
last_height,
|
last_height,
|
||||||
&mut chain_state,
|
&mut chain_state,
|
||||||
&mut tx_index_to_height,
|
&mut tx_index_to_height,
|
||||||
|
entry_anchor,
|
||||||
&prices,
|
&prices,
|
||||||
×tamps,
|
×tamps,
|
||||||
&price_range_max,
|
&price_range_max,
|
||||||
|
|||||||
@@ -250,12 +250,7 @@ impl Vecs {
|
|||||||
identity: LazyVecFrom1::init(
|
identity: LazyVecFrom1::init(
|
||||||
"op_return_index",
|
"op_return_index",
|
||||||
version,
|
version,
|
||||||
indexer
|
indexer.vecs.op_return.to_tx_index.read_only_boxed_clone(),
|
||||||
.vecs
|
|
||||||
.scripts
|
|
||||||
.op_return
|
|
||||||
.to_tx_index
|
|
||||||
.read_only_boxed_clone(),
|
|
||||||
|index, _| index,
|
|index, _| index,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
use brk_types::{Bitcoin, Dollars, StoredF32};
|
use brk_types::{Bitcoin, Dollars, PartsPerMillion64, StoredF32};
|
||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::{Vecs, gini};
|
use super::{Vecs, gini};
|
||||||
use crate::{distribution, internal::RatioDollarsBp32, market, mining, transactions};
|
use crate::{distribution, internal::RatioDollars, market, mining, transactions};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -23,8 +23,8 @@ impl Vecs {
|
|||||||
|
|
||||||
// Puell Multiple: daily_subsidy_usd / sma_365d_subsidy_usd
|
// Puell Multiple: daily_subsidy_usd / sma_365d_subsidy_usd
|
||||||
self.puell_multiple
|
self.puell_multiple
|
||||||
.bps
|
.raw
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
&mining.rewards.subsidy.block.usd,
|
&mining.rewards.subsidy.block.usd,
|
||||||
&mining.rewards.subsidy.average._1y.usd.height,
|
&mining.rewards.subsidy.average._1y.usd.height,
|
||||||
@@ -36,8 +36,8 @@ impl Vecs {
|
|||||||
|
|
||||||
// RHODL Ratio: 1d-1w realized cap / 1y-2y realized cap
|
// RHODL Ratio: 1d-1w realized cap / 1y-2y realized cap
|
||||||
self.rhodl_ratio
|
self.rhodl_ratio
|
||||||
.bps
|
.raw
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
&distribution
|
&distribution
|
||||||
.utxo_cohorts
|
.utxo_cohorts
|
||||||
@@ -70,8 +70,8 @@ impl Vecs {
|
|||||||
.usd
|
.usd
|
||||||
.height;
|
.height;
|
||||||
self.nvt
|
self.nvt
|
||||||
.bps
|
.raw
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
market_cap,
|
market_cap,
|
||||||
&transactions.volume.transfer_volume.sum._24h.usd.height,
|
&transactions.volume.transfer_volume.sum._24h.usd.height,
|
||||||
@@ -80,8 +80,8 @@ impl Vecs {
|
|||||||
|
|
||||||
// Thermocap Multiple: market_cap / thermo_cap
|
// Thermocap Multiple: market_cap / thermo_cap
|
||||||
self.thermo_cap_multiple
|
self.thermo_cap_multiple
|
||||||
.bps
|
.raw
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
|
||||||
starting_lengths.height,
|
starting_lengths.height,
|
||||||
market_cap,
|
market_cap,
|
||||||
&mining.rewards.subsidy.cumulative.usd.height,
|
&mining.rewards.subsidy.cumulative.usd.height,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_indexer::Indexer;
|
use brk_indexer::Indexer;
|
||||||
use brk_types::{BasisPoints16, Sats, StoredU64, Version};
|
use brk_types::{PartsPerMillion32, Sats, StoredU64, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
|
||||||
|
|
||||||
use crate::{distribution, internal::PercentPerBlock};
|
use crate::{distribution, internal::PercentPerBlock};
|
||||||
|
|
||||||
pub(super) fn compute(
|
pub(super) fn compute(
|
||||||
gini: &mut PercentPerBlock<BasisPoints16>,
|
gini: &mut PercentPerBlock<PartsPerMillion32>,
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
@@ -34,13 +34,13 @@ pub(super) fn compute(
|
|||||||
.iter()
|
.iter()
|
||||||
.fold(Version::ZERO, |acc, v| acc + v.version());
|
.fold(Version::ZERO, |acc, v| acc + v.version());
|
||||||
|
|
||||||
gini.bps
|
gini.raw
|
||||||
.height
|
.height
|
||||||
.validate_computed_version_or_reset(source_version)?;
|
.validate_computed_version_or_reset(source_version)?;
|
||||||
|
|
||||||
let min_len = gini.bps.height.len().min(starting_height.to_usize());
|
let min_len = gini.raw.height.len().min(starting_height.to_usize());
|
||||||
|
|
||||||
gini.bps.height.truncate_if_needed_at(min_len)?;
|
gini.raw.height.truncate_if_needed_at(min_len)?;
|
||||||
|
|
||||||
let total_heights = supply_vecs
|
let total_heights = supply_vecs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -49,7 +49,7 @@ pub(super) fn compute(
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
.min(count_vecs.iter().map(|v| v.len()).min().unwrap_or(0));
|
.min(count_vecs.iter().map(|v| v.len()).min().unwrap_or(0));
|
||||||
|
|
||||||
let start_height = gini.bps.height.len();
|
let start_height = gini.raw.height.len();
|
||||||
if start_height >= total_heights {
|
if start_height >= total_heights {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -73,23 +73,23 @@ pub(super) fn compute(
|
|||||||
let count: u64 = count_data[c][offset].into();
|
let count: u64 = count_data[c][offset].into();
|
||||||
buckets.push((count, supply));
|
buckets.push((count, supply));
|
||||||
}
|
}
|
||||||
gini.bps.height.push(gini_from_lorenz(&buckets));
|
gini.raw.height.push(gini_from_lorenz(&buckets));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let _lock = exit.lock();
|
let _lock = exit.lock();
|
||||||
gini.bps.height.write()?;
|
gini.raw.height.write()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gini_from_lorenz(buckets: &[(u64, u64)]) -> BasisPoints16 {
|
fn gini_from_lorenz(buckets: &[(u64, u64)]) -> PartsPerMillion32 {
|
||||||
let total_count: u64 = buckets.iter().map(|(c, _)| c).sum();
|
let total_count: u64 = buckets.iter().map(|(c, _)| c).sum();
|
||||||
let total_supply: u64 = buckets.iter().map(|(_, s)| s).sum();
|
let total_supply: u64 = buckets.iter().map(|(_, s)| s).sum();
|
||||||
|
|
||||||
if total_count == 0 || total_supply == 0 {
|
if total_count == 0 || total_supply == 0 {
|
||||||
return BasisPoints16::ZERO;
|
return PartsPerMillion32::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut cumulative_count, mut cumulative_supply, mut area) = (0u64, 0u64, 0.0f64);
|
let (mut cumulative_count, mut cumulative_supply, mut area) = (0u64, 0u64, 0.0f64);
|
||||||
@@ -103,5 +103,5 @@ fn gini_from_lorenz(buckets: &[(u64, u64)]) -> BasisPoints16 {
|
|||||||
area += (p1 - p0) * (w0 + w1) / 2.0;
|
area += (p1 - p0) * (w0 + w1) / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasisPoints16::from(1.0 - 2.0 * area)
|
PartsPerMillion32::from(1.0 - 2.0 * area)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use brk_traversable::Traversable;
|
|||||||
use brk_types::Version;
|
use brk_types::Version;
|
||||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::{distribution, indexes, prices};
|
use crate::{distribution, indexes, price};
|
||||||
|
|
||||||
pub use inner::RarityMeterInner;
|
pub use inner::RarityMeterInner;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ impl RarityMeter {
|
|||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
prices: &prices::Vecs,
|
prices: &price::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let realized = &distribution.utxo_cohorts.all.metrics.realized;
|
let realized = &distribution.utxo_cohorts.all.metrics.realized;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
|
use brk_types::{PartsPerMillion32, PartsPerMillion64, StoredF32};
|
||||||
use vecdb::{Database, Rw, StorageMode};
|
use vecdb::{Database, Rw, StorageMode};
|
||||||
|
|
||||||
use super::rarity_meter::RarityMeter;
|
use super::rarity_meter::RarityMeter;
|
||||||
@@ -15,11 +15,11 @@ pub struct DormancyVecs<M: StorageMode = Rw> {
|
|||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
#[traversable(skip)]
|
#[traversable(skip)]
|
||||||
pub(crate) db: Database,
|
pub(crate) db: Database,
|
||||||
pub puell_multiple: RatioPerBlock<BasisPoints32, M>,
|
pub puell_multiple: RatioPerBlock<PartsPerMillion64, M>,
|
||||||
pub nvt: RatioPerBlock<BasisPoints32, M>,
|
pub nvt: RatioPerBlock<PartsPerMillion64, M>,
|
||||||
pub gini: PercentPerBlock<BasisPoints16, M>,
|
pub gini: PercentPerBlock<PartsPerMillion32, M>,
|
||||||
pub rhodl_ratio: RatioPerBlock<BasisPoints32, M>,
|
pub rhodl_ratio: RatioPerBlock<PartsPerMillion64, M>,
|
||||||
pub thermo_cap_multiple: RatioPerBlock<BasisPoints32, M>,
|
pub thermo_cap_multiple: RatioPerBlock<PartsPerMillion64, M>,
|
||||||
pub coindays_destroyed_supply_adj: PerBlock<StoredF32, M>,
|
pub coindays_destroyed_supply_adj: PerBlock<StoredF32, M>,
|
||||||
pub coinyears_destroyed_supply_adj: PerBlock<StoredF32, M>,
|
pub coinyears_destroyed_supply_adj: PerBlock<StoredF32, M>,
|
||||||
pub dormancy: DormancyVecs<M>,
|
pub dormancy: DormancyVecs<M>,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ use vecdb::{AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
|
|||||||
use super::{Vecs, WithInputTypes};
|
use super::{Vecs, WithInputTypes};
|
||||||
use crate::internal::{CoinbasePolicy, PerBlockCumulativeRolling, walk_blocks};
|
use crate::internal::{CoinbasePolicy, PerBlockCumulativeRolling, walk_blocks};
|
||||||
|
|
||||||
|
const WRITE_INTERVAL: usize = 10_000;
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
|
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
|
||||||
let starting_lengths = indexer.safe_lengths();
|
let starting_lengths = indexer.safe_lengths();
|
||||||
@@ -37,6 +39,7 @@ impl Vecs {
|
|||||||
|
|
||||||
let mut itype_cursor = indexer.vecs.inputs.output_type.cursor();
|
let mut itype_cursor = indexer.vecs.inputs.output_type.cursor();
|
||||||
let mut fi_in_cursor = indexer.vecs.transactions.first_txin_index.cursor();
|
let mut fi_in_cursor = indexer.vecs.transactions.first_txin_index.cursor();
|
||||||
|
let mut height = skip;
|
||||||
|
|
||||||
walk_blocks(
|
walk_blocks(
|
||||||
&fi_batch,
|
&fi_batch,
|
||||||
@@ -65,7 +68,8 @@ impl Vecs {
|
|||||||
);
|
);
|
||||||
push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type);
|
push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type);
|
||||||
|
|
||||||
if self.input_count.all.block.batch_limit_reached() {
|
height += 1;
|
||||||
|
if height.is_multiple_of(WRITE_INTERVAL) {
|
||||||
let _lock = exit.lock();
|
let _lock = exit.lock();
|
||||||
self.input_count.write()?;
|
self.input_count.write()?;
|
||||||
self.tx_count.write()?;
|
self.tx_count.write()?;
|
||||||
@@ -79,11 +83,11 @@ impl Vecs {
|
|||||||
self.input_count.write()?;
|
self.input_count.write()?;
|
||||||
self.tx_count.write()?;
|
self.tx_count.write()?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.input_count
|
self.input_count
|
||||||
.compute_rest(starting_lengths.height, exit)?;
|
.compute_rest(starting_lengths.height, exit)?;
|
||||||
self.tx_count.compute_rest(starting_lengths.height, exit)?;
|
self.tx_count.compute_rest(starting_lengths.height, exit)?;
|
||||||
}
|
|
||||||
|
|
||||||
for (otype, source) in self.input_count.by_type.iter_typed() {
|
for (otype, source) in self.input_count.by_type.iter_typed() {
|
||||||
self.input_share.get_mut(otype).compute_count_ratio(
|
self.input_share.get_mut(otype).compute_count_ratio(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_cohort::SpendableType;
|
use brk_cohort::SpendableType;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints16, StoredU64};
|
use brk_types::{PartsPerMillion32, StoredU64};
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use super::WithInputTypes;
|
use super::WithInputTypes;
|
||||||
@@ -9,7 +9,7 @@ use crate::internal::{PerBlockCumulativeRolling, PercentCumulativeRolling};
|
|||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
pub input_count: WithInputTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
pub input_count: WithInputTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||||
pub input_share: SpendableType<PercentCumulativeRolling<BasisPoints16, M>>,
|
pub input_share: SpendableType<PercentCumulativeRolling<PartsPerMillion32, M>>,
|
||||||
pub tx_count: WithInputTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
pub tx_count: WithInputTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||||
pub tx_share: SpendableType<PercentCumulativeRolling<BasisPoints16, M>>,
|
pub tx_share: SpendableType<PercentCumulativeRolling<PartsPerMillion32, M>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ use brk_indexer::Indexer;
|
|||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{blocks, indexes};
|
use crate::blocks;
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
indexes: &indexes::Vecs,
|
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -18,7 +17,7 @@ impl Vecs {
|
|||||||
let starting_lengths = indexer.safe_lengths();
|
let starting_lengths = indexer.safe_lengths();
|
||||||
|
|
||||||
self.spent.compute(indexer, exit)?;
|
self.spent.compute(indexer, exit)?;
|
||||||
self.count.compute(indexer, indexes, blocks, exit)?;
|
self.count.compute(indexer, blocks, exit)?;
|
||||||
self.per_sec.compute(&self.count, &starting_lengths, exit)?;
|
self.per_sec.compute(&self.count, &starting_lengths, exit)?;
|
||||||
self.by_type.compute(indexer, exit)?;
|
self.by_type.compute(indexer, exit)?;
|
||||||
|
|
||||||
|
|||||||
@@ -3,27 +3,25 @@ use brk_indexer::Indexer;
|
|||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{blocks, indexes};
|
use crate::blocks;
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
indexer: &Indexer,
|
indexer: &Indexer,
|
||||||
indexes: &indexes::Vecs,
|
|
||||||
blocks: &blocks::Vecs,
|
blocks: &blocks::Vecs,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let starting_height = indexer.safe_lengths().height;
|
let starting_height = indexer.safe_lengths().height;
|
||||||
let window_starts = blocks.lookback.window_starts();
|
let window_starts = blocks.lookback.window_starts();
|
||||||
self.0.compute(
|
|
||||||
|
self.sum.compute_count_from_indexes(
|
||||||
starting_height,
|
starting_height,
|
||||||
&indexes.tx_index.input_count,
|
&indexer.vecs.inputs.first_txin_index,
|
||||||
&indexer.vecs.transactions.first_tx_index,
|
&indexer.vecs.inputs.outpoint,
|
||||||
&indexes.height.tx_index_count,
|
|
||||||
&window_starts,
|
|
||||||
exit,
|
exit,
|
||||||
0,
|
|
||||||
)?;
|
)?;
|
||||||
|
self.compute_rest(starting_height, &window_starts, exit)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::BasisPointsSigned16;
|
use brk_types::PartsPerMillionSigned32;
|
||||||
use vecdb::{EagerVec, Exit, PcoVec, ReadableVec, VecIndex, VecValue};
|
use vecdb::{EagerVec, Exit, PcoVec, ReadableVec, VecIndex, VecValue};
|
||||||
|
|
||||||
pub trait ComputeDrawdown<I: VecIndex> {
|
pub trait ComputeDrawdown<I: VecIndex> {
|
||||||
@@ -16,7 +16,7 @@ pub trait ComputeDrawdown<I: VecIndex> {
|
|||||||
f64: From<C> + From<A>;
|
f64: From<C> + From<A>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> ComputeDrawdown<I> for EagerVec<PcoVec<I, BasisPointsSigned16>>
|
impl<I> ComputeDrawdown<I> for EagerVec<PcoVec<I, PartsPerMillionSigned32>>
|
||||||
where
|
where
|
||||||
I: VecIndex,
|
I: VecIndex,
|
||||||
{
|
{
|
||||||
@@ -39,9 +39,9 @@ where
|
|||||||
|(i, current, ath, _)| {
|
|(i, current, ath, _)| {
|
||||||
let ath_f64 = f64::from(ath);
|
let ath_f64 = f64::from(ath);
|
||||||
let drawdown = if ath_f64 == 0.0 {
|
let drawdown = if ath_f64 == 0.0 {
|
||||||
BasisPointsSigned16::default()
|
PartsPerMillionSigned32::default()
|
||||||
} else {
|
} else {
|
||||||
BasisPointsSigned16::from((f64::from(current) - ath_f64) / ath_f64)
|
PartsPerMillionSigned32::from((f64::from(current) - ath_f64) / ath_f64)
|
||||||
};
|
};
|
||||||
(i, drawdown)
|
(i, drawdown)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use super::fenwick::FenwickTree;
|
|||||||
|
|
||||||
/// Fast expanding percentile tracker using a Fenwick tree (Binary Indexed Tree).
|
/// Fast expanding percentile tracker using a Fenwick tree (Binary Indexed Tree).
|
||||||
///
|
///
|
||||||
/// Values are discretized to 10 BPS (0.1%) resolution and tracked in
|
/// Values are discretized to 0.001 ratio resolution and tracked in
|
||||||
/// a fixed-size frequency array with Fenwick prefix sums. This gives:
|
/// a fixed-size frequency array with Fenwick prefix sums. This gives:
|
||||||
/// - O(log N) insert (N = tree size, ~16 ops for 43k buckets)
|
/// - O(log N) insert (N = tree size, ~16 ops for 43k buckets)
|
||||||
/// - O(log N) percentile query via prefix-sum walk
|
/// - O(log N) percentile query via prefix-sum walk
|
||||||
@@ -15,11 +15,9 @@ pub(crate) struct ExpandingPercentiles {
|
|||||||
count: u32,
|
count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bucket granularity in BPS. 10 BPS = 0.1% = 0.001 ratio.
|
const BUCKET_WIDTH: f64 = 0.001;
|
||||||
const BUCKET_BPS: i32 = 10;
|
const MAX_RATIO: f64 = 43.0;
|
||||||
/// Max ratio supported: 43.0 = 430,000 BPS.
|
const TREE_SIZE: usize = (MAX_RATIO / BUCKET_WIDTH) as usize + 1;
|
||||||
const MAX_BPS: i32 = 430_000;
|
|
||||||
const TREE_SIZE: usize = (MAX_BPS / BUCKET_BPS) as usize + 1;
|
|
||||||
|
|
||||||
impl Default for ExpandingPercentiles {
|
impl Default for ExpandingPercentiles {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@@ -43,8 +41,9 @@ impl ExpandingPercentiles {
|
|||||||
/// Convert f32 ratio to 0-indexed bucket.
|
/// Convert f32 ratio to 0-indexed bucket.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_bucket(value: f32) -> usize {
|
fn to_bucket(value: f32) -> usize {
|
||||||
let bps = (value as f64 * 10000.0).round() as i32;
|
(value as f64 / BUCKET_WIDTH)
|
||||||
(bps / BUCKET_BPS).clamp(0, TREE_SIZE as i32 - 1) as usize
|
.round()
|
||||||
|
.clamp(0.0, (TREE_SIZE - 1) as f64) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bulk-load values in O(n + N) instead of O(n log N).
|
/// Bulk-load values in O(n + N) instead of O(n log N).
|
||||||
@@ -73,10 +72,10 @@ impl ExpandingPercentiles {
|
|||||||
|
|
||||||
/// Compute 8 percentiles in one call via kth. O(8 × log N) but with
|
/// Compute 8 percentiles in one call via kth. O(8 × log N) but with
|
||||||
/// shared tree traversal across all 8 targets for better cache locality.
|
/// shared tree traversal across all 8 targets for better cache locality.
|
||||||
/// Quantiles q must be sorted ascending in (0, 1). Output is in BPS.
|
/// Quantiles q must be sorted ascending in (0, 1). Output values are ratios.
|
||||||
pub fn quantiles(&self, qs: &[f64; 8], out: &mut [u32; 8]) {
|
pub fn quantiles(&self, qs: &[f64; 8], out: &mut [f64; 8]) {
|
||||||
if self.count == 0 {
|
if self.count == 0 {
|
||||||
out.iter_mut().for_each(|o| *o = 0);
|
out.fill(0.0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut targets = [0u32; 8];
|
let mut targets = [0u32; 8];
|
||||||
@@ -87,7 +86,7 @@ impl ExpandingPercentiles {
|
|||||||
let mut buckets = [0usize; 8];
|
let mut buckets = [0usize; 8];
|
||||||
self.tree.kth(&targets, &|n: &u32| *n, &mut buckets);
|
self.tree.kth(&targets, &|n: &u32| *n, &mut buckets);
|
||||||
for (i, bucket) in buckets.iter().enumerate() {
|
for (i, bucket) in buckets.iter().enumerate() {
|
||||||
out[i] = *bucket as u32 * BUCKET_BPS as u32;
|
out[i] = *bucket as f64 * BUCKET_WIDTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,8 +95,8 @@ impl ExpandingPercentiles {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn quantile(ep: &ExpandingPercentiles, q: f64) -> u32 {
|
fn quantile(ep: &ExpandingPercentiles, q: f64) -> f64 {
|
||||||
let mut out = [0u32; 8];
|
let mut out = [0.0; 8];
|
||||||
ep.quantiles(&[q, q, q, q, q, q, q, q], &mut out);
|
ep.quantiles(&[q, q, q, q, q, q, q, q], &mut out);
|
||||||
out[0]
|
out[0]
|
||||||
}
|
}
|
||||||
@@ -111,20 +110,20 @@ mod tests {
|
|||||||
assert_eq!(ep.count(), 1000);
|
assert_eq!(ep.count(), 1000);
|
||||||
|
|
||||||
let median = quantile(&ep, 0.5);
|
let median = quantile(&ep, 0.5);
|
||||||
assert!((median as i32 - 5000).abs() < 100, "median was {median}");
|
assert!((median - 0.5).abs() < 0.01, "median was {median}");
|
||||||
|
|
||||||
let p99 = quantile(&ep, 0.99);
|
let p99 = quantile(&ep, 0.99);
|
||||||
assert!((p99 as i32 - 9900).abs() < 100, "p99 was {p99}");
|
assert!((p99 - 0.99).abs() < 0.01, "p99 was {p99}");
|
||||||
|
|
||||||
let p01 = quantile(&ep, 0.01);
|
let p01 = quantile(&ep, 0.01);
|
||||||
assert!((p01 as i32 - 100).abs() < 100, "p01 was {p01}");
|
assert!((p01 - 0.01).abs() < 0.01, "p01 was {p01}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty() {
|
fn empty() {
|
||||||
let ep = ExpandingPercentiles::default();
|
let ep = ExpandingPercentiles::default();
|
||||||
assert_eq!(ep.count(), 0);
|
assert_eq!(ep.count(), 0);
|
||||||
assert_eq!(quantile(&ep, 0.5), 0);
|
assert_eq!(quantile(&ep, 0.5), 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -132,7 +131,7 @@ mod tests {
|
|||||||
let mut ep = ExpandingPercentiles::default();
|
let mut ep = ExpandingPercentiles::default();
|
||||||
ep.add(0.42);
|
ep.add(0.42);
|
||||||
let v = quantile(&ep, 0.5);
|
let v = quantile(&ep, 0.5);
|
||||||
assert!((v as i32 - 4200).abs() <= BUCKET_BPS, "got {v}");
|
assert!((v - 0.42).abs() <= BUCKET_WIDTH, "got {v}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -144,6 +143,6 @@ mod tests {
|
|||||||
assert_eq!(ep.count(), 100);
|
assert_eq!(ep.count(), 100);
|
||||||
ep.reset();
|
ep.reset();
|
||||||
assert_eq!(ep.count(), 0);
|
assert_eq!(ep.count(), 0);
|
||||||
assert_eq!(quantile(&ep, 0.5), 0);
|
assert_eq!(quantile(&ep, 0.5), 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use brk_traversable::Traversable;
|
|||||||
|
|
||||||
#[derive(Clone, Traversable)]
|
#[derive(Clone, Traversable)]
|
||||||
pub struct Percent<A, B = A, C = B> {
|
pub struct Percent<A, B = A, C = B> {
|
||||||
pub bps: A,
|
pub raw: A,
|
||||||
pub ratio: B,
|
pub ratio: B,
|
||||||
pub percent: C,
|
pub percent: C,
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user