From 4abbc87ce535fff22f90d74cc7587a79adaf832e Mon Sep 17 00:00:00 2001 From: nym21 Date: Mon, 10 Aug 2026 22:44:28 +0200 Subject: [PATCH] global: massive columnar rework part 1 --- Cargo.lock | 73 +- Cargo.toml | 2 +- crates/brk_client/src/lib.rs | 2787 +++++++++++++---- crates/brk_cohort/Cargo.toml | 1 + crates/brk_cohort/src/age_range.rs | 392 ++- crates/brk_cohort/src/by_addr_type.rs | 226 +- crates/brk_cohort/src/by_type.rs | 141 + crates/brk_cohort/src/lib.rs | 2 + crates/brk_cohort/src/profitability.rs | 479 +++ crates/brk_cohort/src/spendable_type.rs | 137 + .../src/distribution/addr/activity.rs | 424 ++- .../src/distribution/addr/count/all_vecs.rs | 38 +- .../addr/count/funded_total_vecs.rs | 9 +- .../src/distribution/addr/count/state.rs | 11 +- .../src/distribution/addr/exposed/mod.rs | 10 +- .../src/distribution/addr/new_addr_count.rs | 4 +- .../distribution/addr/reused/events/state.rs | 9 +- .../distribution/addr/reused/events/vecs.rs | 100 +- .../src/distribution/addr/reused/mod.rs | 11 +- .../src/distribution/addr/supply/share.rs | 62 +- .../src/distribution/addr/supply/state.rs | 8 +- .../src/distribution/addr/supply/vecs.rs | 47 +- .../src/distribution/addr/total_addr_count.rs | 41 +- .../src/distribution/cohorts/utxo/fenwick.rs | 22 +- .../src/distribution/cohorts/utxo/groups.rs | 218 +- .../distribution/cohorts/utxo/percentiles.rs | 56 +- .../distribution/cohorts/utxo/tick_tock.rs | 32 +- .../src/distribution/compute/block_loop.rs | 4 +- .../src/distribution/metrics/activity/full.rs | 63 +- .../src/distribution/metrics/cohort/basic.rs | 7 +- .../src/distribution/metrics/cohort/core.rs | 10 +- .../distribution/metrics/cohort/extended.rs | 7 +- .../metrics/cohort/extended_adjusted.rs | 6 +- .../src/distribution/metrics/config.rs | 18 +- .../distribution/metrics/cost_basis/mod.rs | 16 +- .../src/distribution/metrics/profitability.rs | 526 ++-- .../distribution/metrics/realized/adjusted.rs | 29 +- .../src/distribution/metrics/realized/full.rs | 89 +- .../distribution/metrics/supply/age_range.rs | 176 ++ .../distribution/metrics/supply/avg_amount.rs | 158 +- .../src/distribution/metrics/supply/base.rs | 83 +- .../src/distribution/metrics/supply/cache.rs | 33 +- .../src/distribution/metrics/supply/core.rs | 142 +- .../src/distribution/metrics/supply/mod.rs | 2 + crates/brk_computer/src/distribution/vecs.rs | 54 +- .../src/frameworks/coinflow/compute.rs | 543 ++-- .../src/frameworks/coinflow/import.rs | 137 +- .../src/frameworks/coinflow/mod.rs | 46 +- .../src/frameworks/coinflow/vecs.rs | 44 +- .../src/frameworks/cointime/activity/mod.rs | 1 - .../frameworks/cointime/age_range/compute.rs | 456 +-- .../frameworks/cointime/age_range/import.rs | 224 +- .../src/frameworks/cointime/age_range/mod.rs | 2 +- .../src/frameworks/cointime/age_range/vecs.rs | 64 +- .../frameworks/cointime/aggregate/compute.rs | 140 +- crates/brk_computer/src/frameworks/mod.rs | 37 +- crates/brk_computer/src/indexes/timestamp.rs | 1 - .../src/inputs/by_type/compute.rs | 20 +- .../brk_computer/src/inputs/by_type/import.rs | 61 +- .../brk_computer/src/inputs/by_type/vecs.rs | 21 +- .../src/inputs/by_type/with_input_types.rs | 167 +- .../brk_computer/src/internal/block_walker.rs | 16 +- .../src/internal/containers/windows.rs | 110 + .../internal/containers/windows_from_1w.rs | 114 + .../brk_computer/src/internal/daily_metric.rs | 85 +- .../brk_computer/src/internal/indexes/lazy.rs | 1 - .../cached_count_cumulative_rolling.rs | 217 -- .../per_block/computed/columnar/base.rs | 363 +++ .../computed/columnar/cumulative_rolling.rs | 277 ++ .../per_block/computed/columnar/lazy.rs | 47 + .../columnar/lazy_count_cumulative_rolling.rs | 79 + .../columnar/lazy_cumulative_rolling.rs | 57 + .../per_block/computed/columnar/mod.rs | 11 + .../per_block/computed/cumulative_rolling.rs | 16 - .../computed/lazy_cumulative_average.rs | 69 + .../computed/lazy_cumulative_rolling.rs | 30 +- .../src/internal/per_block/computed/mod.rs | 6 +- .../per_block/computed/resolutions.rs | 1 - .../lazy/cached_block_count_reader.rs | 4 + .../per_block/lazy/derived_resolutions.rs | 1 - .../internal/per_block/percent/columnar.rs | 52 + .../internal/per_block/percent/lazy_vec.rs | 1 - .../src/internal/per_block/percent/mod.rs | 2 + .../src/internal/per_block/percent/windows.rs | 30 +- .../src/internal/per_block/percentiles.rs | 139 +- .../src/internal/per_block/price.rs | 38 +- .../src/internal/per_block/ratio/columnar.rs | 48 + .../per_block/ratio/columnar_price.rs | 60 + .../src/internal/per_block/ratio/mod.rs | 4 + .../src/internal/per_block/rolling/windows.rs | 70 +- .../src/internal/per_block/value/columnar.rs | 239 ++ .../src/internal/per_block/value/mod.rs | 4 +- .../per_block/value/spot_with_deltas.rs | 44 - .../src/internal/transform/mod.rs | 2 +- .../src/internal/transform/ratio.rs | 18 +- .../src/internal/with_addr_types.rs | 218 -- .../src/internal/with_addr_types/columnar.rs | 143 + .../src/internal/with_addr_types/mod.rs | 12 + crates/brk_computer/src/lib.rs | 1 + .../src/market/moving_average/compute.rs | 32 +- .../src/market/moving_average/import.rs | 47 +- .../src/market/moving_average/vecs.rs | 228 +- .../src/models/bedrock/compute.rs | 736 ++--- .../brk_computer/src/models/bedrock/import.rs | 121 +- crates/brk_computer/src/models/bedrock/mod.rs | 35 +- .../brk_computer/src/models/bedrock/price.rs | 39 +- .../brk_computer/src/models/bedrock/vecs.rs | 729 ++++- .../src/models/rarity_meter/components.rs | 259 +- .../src/models/rarity_meter/extremes.rs | 179 +- .../src/models/rarity_meter/inner.rs | 542 ++-- .../src/models/rarity_meter/mod.rs | 1 + .../src/models/rarity_meter/percentiles.rs | 181 +- .../brk_computer/src/op_return/breakdown.rs | 346 ++ crates/brk_computer/src/op_return/by_kind.rs | 29 +- crates/brk_computer/src/op_return/compute.rs | 196 +- crates/brk_computer/src/op_return/import.rs | 24 +- crates/brk_computer/src/op_return/mod.rs | 6 +- crates/brk_computer/src/op_return/policy.rs | 30 + crates/brk_computer/src/op_return/total.rs | 169 + crates/brk_computer/src/op_return/vecs.rs | 526 +--- .../src/outputs/by_type/compute.rs | 21 +- .../src/outputs/by_type/import.rs | 63 +- .../brk_computer/src/outputs/by_type/vecs.rs | 21 +- .../src/outputs/by_type/with_output_types.rs | 165 +- crates/brk_computer/src/price/ohlcs.rs | 2 - .../src/transactions/features/compute.rs | 33 +- .../src/transactions/features/import.rs | 47 +- .../src/transactions/features/mod.rs | 2 +- .../src/transactions/features/vecs.rs | 123 +- .../src/transactions/fees/compute.rs | 59 +- .../src/transactions/fees/import.rs | 72 +- .../brk_computer/src/transactions/fees/mod.rs | 2 +- .../src/transactions/fees/vecs.rs | 115 +- .../src/transactions/patterns/compute.rs | 72 +- .../src/transactions/patterns/import.rs | 89 +- .../src/transactions/patterns/mod.rs | 2 +- .../src/transactions/patterns/vecs.rs | 121 +- .../src/transactions/versions/compute.rs | 22 +- .../src/transactions/versions/import.rs | 60 +- .../src/transactions/versions/mod.rs | 2 +- .../src/transactions/versions/vecs.rs | 105 +- crates/brk_mcp/Cargo.toml | 2 +- crates/brk_traversable/src/lib.rs | 84 +- crates/brk_types/src/byte_count.rs | 144 + crates/brk_types/src/lib.rs | 4 + crates/brk_types/src/op_return_kind.rs | 87 +- crates/brk_types/src/op_return_policy_id.rs | 76 + crates/brk_types/src/output_type.rs | 2 + crates/brk_types/src/percentile.rs | 224 ++ crates/rawdb/Cargo.toml | 2 +- crates/vecdb/src/traits/formattable.rs | 46 + crates/vecdb/src/traits/printable.rs | 21 + crates/vecdb/src/variants/cached/cloneable.rs | 5 + crates/vecdb/src/variants/columnar/mod.rs | 27 +- crates/vecdb/src/variants/columnar/read.rs | 34 +- crates/vecdb/src/variants/columnar/schema.rs | 24 +- crates/vecdb/src/variants/columnar/sum.rs | 40 +- crates/vecdb/src/variants/columnar/traits.rs | 15 +- .../src/variants/eager/compute/statistics.rs | 204 ++ .../src/variants/eager/compute/transforms.rs | 93 +- crates/vecdb/src/variants/eager/mod.rs | 10 +- crates/vecdb/tests/columnar.rs | 121 +- crates/vecdb/tests/initial_capacity.rs | 7 +- modules/brk-client/index.js | 2624 ++++++++++++---- packages/brk_client/brk_client/__init__.py | 1340 ++++++-- website/llms-full.txt | 2 +- website/llms.txt | 2 +- .../scripts/options/frameworks/coinflow.js | 10 +- .../options/frameworks/cointime/index.js | 14 +- website_next/llms-full.txt | 2 +- website_next/llms.txt | 2 +- 171 files changed, 15733 insertions(+), 6546 deletions(-) create mode 100644 crates/brk_cohort/src/profitability.rs create mode 100644 crates/brk_computer/src/distribution/metrics/supply/age_range.rs delete mode 100644 crates/brk_computer/src/internal/per_block/computed/cached_count_cumulative_rolling.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/columnar/base.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/columnar/cumulative_rolling.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/columnar/lazy.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/columnar/lazy_count_cumulative_rolling.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/columnar/lazy_cumulative_rolling.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/columnar/mod.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_average.rs create mode 100644 crates/brk_computer/src/internal/per_block/percent/columnar.rs create mode 100644 crates/brk_computer/src/internal/per_block/ratio/columnar.rs create mode 100644 crates/brk_computer/src/internal/per_block/ratio/columnar_price.rs create mode 100644 crates/brk_computer/src/internal/per_block/value/columnar.rs delete mode 100644 crates/brk_computer/src/internal/per_block/value/spot_with_deltas.rs delete mode 100644 crates/brk_computer/src/internal/with_addr_types.rs create mode 100644 crates/brk_computer/src/internal/with_addr_types/columnar.rs create mode 100644 crates/brk_computer/src/internal/with_addr_types/mod.rs create mode 100644 crates/brk_computer/src/op_return/breakdown.rs create mode 100644 crates/brk_computer/src/op_return/policy.rs create mode 100644 crates/brk_computer/src/op_return/total.rs create mode 100644 crates/brk_types/src/byte_count.rs create mode 100644 crates/brk_types/src/op_return_policy_id.rs diff --git a/Cargo.lock b/Cargo.lock index 4e2937414..6698427ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,9 +184,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -544,6 +544,7 @@ dependencies = [ "brk_traversable", "brk_types", "rayon", + "schemars", "serde", "vecdb", ] @@ -963,9 +964,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "jobserver", @@ -1158,9 +1159,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" dependencies = [ "percent-encoding", "time", @@ -1630,9 +1631,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "flate2" @@ -2321,9 +2322,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -2796,9 +2797,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" @@ -3142,9 +3143,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "3.1.1" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "094c075f6698deef5a657cf4df6b684dff65157d255978b92b552ec22503f17a" +checksum = "c8dddc5b1924b9a59fba420166160ca2c4663a4e01803e52eda33070f56d63c8" dependencies = [ "async-trait", "base64 0.23.1", @@ -3403,9 +3404,9 @@ dependencies = [ [[package]] name = "serde_qs" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67d525c8ff68aa99e5818302259bdd02d86d0303710616f39c0f44846ff6d332" +checksum = "475b9310e272827d34113bf8679b6dfb7fa0023ee049764ba003f8f984e8b5d1" dependencies = [ "axum", "itoa", @@ -3711,18 +3712,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -4051,11 +4052,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "cookie_store", "flate2", "log", @@ -4071,11 +4072,11 @@ dependencies = [ [[package]] name = "ureq-proto" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "http", "httparse", "log", @@ -4202,9 +4203,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -4215,9 +4216,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4225,9 +4226,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -4238,18 +4239,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 154208d0e..c4b53a30c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ tokio = { version = "1.53.1", features = ["rt-multi-thread"] } tower-http = { version = "0.7.0", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] } tower-layer = "0.3" tracing = { version = "0.1", default-features = false, features = ["std"] } -ureq = { version = "3.3.0", features = ["json"] } +ureq = { version = "3.4.0", features = ["json"] } vecdb = { path = "crates/vecdb", version = "0.11.2", features = ["derive", "serde_json", "pco", "schemars"] } vecdb_derive = { path = "crates/vecdb_derive", version = "0.11.2" } diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index df97c2911..04df17320 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1070,7 +1070,119 @@ impl SeriesPattern for SeriesPattern35 { fn get(&self // Reusable pattern structs /// Pattern struct for repeated tree structure. -pub struct IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern { +pub struct _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mCumulativeOverUnderPattern { + pub _10y_to_12y: AverageBlockCumulativeSumPattern, + pub _12y_to_15y: AverageBlockCumulativeSumPattern, + pub _18m_to_2y: AverageBlockCumulativeSumPattern, + pub _1d_to_1w: AverageBlockCumulativeSumPattern, + pub _1h_to_1d: AverageBlockCumulativeSumPattern, + pub _1m_to_2m: AverageBlockCumulativeSumPattern, + pub _1w_to_1m: AverageBlockCumulativeSumPattern, + pub _1y_to_18m: AverageBlockCumulativeSumPattern, + pub _2m_to_3m: AverageBlockCumulativeSumPattern, + pub _2y_to_3y: AverageBlockCumulativeSumPattern, + pub _3m_to_4m: AverageBlockCumulativeSumPattern, + pub _3y_to_4y: AverageBlockCumulativeSumPattern, + pub _4m_to_5m: AverageBlockCumulativeSumPattern, + pub _4y_to_5y: AverageBlockCumulativeSumPattern, + pub _5m_to_6m: AverageBlockCumulativeSumPattern, + pub _5y_to_6y: AverageBlockCumulativeSumPattern, + pub _6m_to_9m: AverageBlockCumulativeSumPattern, + pub _6y_to_7y: AverageBlockCumulativeSumPattern, + pub _7y_to_8y: AverageBlockCumulativeSumPattern, + pub _8y_to_10y: AverageBlockCumulativeSumPattern, + pub _9m_to_1y: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredF64; 23]>, + pub over_15y: AverageBlockCumulativeSumPattern, + pub under_1h: AverageBlockCumulativeSumPattern, +} + +/// Pattern struct for repeated tree structure. +pub struct _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mHeightOverUnderPattern2 { + pub _10y_to_12y: BtcCentsSatsUsdPattern, + pub _12y_to_15y: BtcCentsSatsUsdPattern, + pub _18m_to_2y: BtcCentsSatsUsdPattern, + pub _1d_to_1w: BtcCentsSatsUsdPattern, + pub _1h_to_1d: BtcCentsSatsUsdPattern, + pub _1m_to_2m: BtcCentsSatsUsdPattern, + pub _1w_to_1m: BtcCentsSatsUsdPattern, + pub _1y_to_18m: BtcCentsSatsUsdPattern, + pub _2m_to_3m: BtcCentsSatsUsdPattern, + pub _2y_to_3y: BtcCentsSatsUsdPattern, + pub _3m_to_4m: BtcCentsSatsUsdPattern, + pub _3y_to_4y: BtcCentsSatsUsdPattern, + pub _4m_to_5m: BtcCentsSatsUsdPattern, + pub _4y_to_5y: BtcCentsSatsUsdPattern, + pub _5m_to_6m: BtcCentsSatsUsdPattern, + pub _5y_to_6y: BtcCentsSatsUsdPattern, + pub _6m_to_9m: BtcCentsSatsUsdPattern, + pub _6y_to_7y: BtcCentsSatsUsdPattern, + pub _7y_to_8y: BtcCentsSatsUsdPattern, + pub _8y_to_10y: BtcCentsSatsUsdPattern, + pub _9m_to_1y: BtcCentsSatsUsdPattern, + pub height: SeriesPattern18<[Sats; 23]>, + pub over_15y: BtcCentsSatsUsdPattern, + pub under_1h: BtcCentsSatsUsdPattern, +} + +/// Pattern struct for repeated tree structure. +pub struct AscribeBareBitproofBlockstackCoinColuCumulativeDocproofEmptyEpobcEternityFactomKomodoMemoOmniOpenPoetRunesStacksStamperyTextUnknownVeriPattern3 { + pub ascribe: AverageBlockCumulativeSumPattern, + pub bare_hash: AverageBlockCumulativeSumPattern, + pub bitproof: AverageBlockCumulativeSumPattern, + pub blockstack: AverageBlockCumulativeSumPattern, + pub coin_spark: AverageBlockCumulativeSumPattern, + pub colu: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18, + pub docproof: AverageBlockCumulativeSumPattern, + pub empty: AverageBlockCumulativeSumPattern, + pub epobc: AverageBlockCumulativeSumPattern, + pub eternity_wall: AverageBlockCumulativeSumPattern, + pub factom: AverageBlockCumulativeSumPattern, + pub komodo: AverageBlockCumulativeSumPattern, + pub memo: AverageBlockCumulativeSumPattern, + pub omni: AverageBlockCumulativeSumPattern, + pub open_assets: AverageBlockCumulativeSumPattern, + pub open_timestamps: AverageBlockCumulativeSumPattern, + pub poet: AverageBlockCumulativeSumPattern, + pub runes: AverageBlockCumulativeSumPattern, + pub stacks: AverageBlockCumulativeSumPattern, + pub stampery: AverageBlockCumulativeSumPattern, + pub text: AverageBlockCumulativeSumPattern, + pub unknown: AverageBlockCumulativeSumPattern, + pub veri_block: AverageBlockCumulativeSumPattern, +} + +/// Pattern struct for repeated tree structure. +pub struct _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mOverUnderPattern3 { + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub over_15y: SeriesPattern1, + pub under_1h: SeriesPattern1, +} + +/// Pattern struct for repeated tree structure. +pub struct HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern { + pub height: SeriesPattern18<[Cents; 19]>, pub index: SeriesPattern1, pub pct0_1: CentsSatsUsdPattern, pub pct0_5: CentsSatsUsdPattern, @@ -1094,10 +1206,11 @@ pub struct IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct pub score: SeriesPattern1, } -impl IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern { +impl HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { + height: SeriesPattern18::new(client.clone(), _m(&acc, "percentiles_cents")), index: SeriesPattern1::new(client.clone(), _m(&acc, "index")), pct0_1: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct0_1")), pct0_5: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct0_5")), @@ -1124,7 +1237,8 @@ impl IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct9 } /// Pattern struct for repeated tree structure. -pub struct Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern { +pub struct HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern { + pub height: SeriesPattern18<[Cents; 19]>, pub pct05: CentsSatsUsdPattern, pub pct10: CentsSatsUsdPattern, pub pct15: CentsSatsUsdPattern, @@ -1146,10 +1260,11 @@ pub struct Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct7 pub pct95: CentsSatsUsdPattern, } -impl Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern { +impl HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { + height: SeriesPattern18::new(client.clone(), _m(&acc, "cents")), pct05: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct05")), pct10: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct10")), pct15: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct15")), @@ -1174,7 +1289,7 @@ impl Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75 } /// Pattern struct for repeated tree structure. -pub struct Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern { +pub struct Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern { pub pct0_1: PpmPriceRatioPattern, pub pct0_5: PpmPriceRatioPattern, pub pct1: PpmPriceRatioPattern, @@ -1194,9 +1309,10 @@ pub struct Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct pub pct99: PpmPriceRatioPattern, pub pct99_5: PpmPriceRatioPattern, pub pct99_9: PpmPriceRatioPattern, + pub ratios: SeriesPattern18<[PartsPerMillion32; 19]>, } -impl Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern { +impl Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { @@ -1219,44 +1335,7 @@ impl Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct9 pct99: PpmPriceRatioPattern::new(client.clone(), acc.clone(), "pct99".to_string()), pct99_5: PpmPriceRatioPattern::new(client.clone(), acc.clone(), "pct99_5".to_string()), pct99_9: PpmPriceRatioPattern::new(client.clone(), acc.clone(), "pct99_9".to_string()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern { - pub all: AverageBlockCumulativeSumPattern, - pub empty: AverageBlockCumulativeSumPattern, - pub op_return: AverageBlockCumulativeSumPattern, - pub p2a: AverageBlockCumulativeSumPattern, - pub p2ms: AverageBlockCumulativeSumPattern, - pub p2pk33: AverageBlockCumulativeSumPattern, - pub p2pk65: AverageBlockCumulativeSumPattern, - pub p2pkh: AverageBlockCumulativeSumPattern, - pub p2sh: AverageBlockCumulativeSumPattern, - pub p2tr: AverageBlockCumulativeSumPattern, - pub p2wpkh: AverageBlockCumulativeSumPattern, - pub p2wsh: AverageBlockCumulativeSumPattern, - pub unknown: AverageBlockCumulativeSumPattern, -} - -impl AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - all: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "bis")), - empty: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_empty_outputs_output")), - op_return: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_op_return_output")), - p2a: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2a_output")), - p2ms: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2ms_output")), - p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2pk33_output")), - p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2pk65_output")), - p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2pkh_output")), - p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2sh_output")), - p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2tr_output")), - p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2wpkh_output")), - p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_p2wsh_output")), - unknown: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "with_unknown_outputs_output")), + ratios: SeriesPattern18::new(client.clone(), _m(&acc, "ratios_ppm")), } } } @@ -1333,22 +1412,6 @@ impl _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 { } } -/// Pattern struct for repeated tree structure. -pub struct AllEmptyP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern { - pub all: AverageBlockCumulativeSumPattern, - pub empty: AverageBlockCumulativeSumPattern, - pub p2a: AverageBlockCumulativeSumPattern, - pub p2ms: AverageBlockCumulativeSumPattern, - pub p2pk33: AverageBlockCumulativeSumPattern, - pub p2pk65: AverageBlockCumulativeSumPattern, - pub p2pkh: AverageBlockCumulativeSumPattern, - pub p2sh: AverageBlockCumulativeSumPattern, - pub p2tr: AverageBlockCumulativeSumPattern, - pub p2wpkh: AverageBlockCumulativeSumPattern, - pub p2wsh: AverageBlockCumulativeSumPattern, - pub unknown: AverageBlockCumulativeSumPattern, -} - /// Pattern struct for repeated tree structure. pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern { pub cap: CentsDeltaToUsdPattern, @@ -1360,8 +1423,8 @@ pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern { pub peak_regret: BlockCumulativeSumPattern, pub price: CentsPpmRatioSatsUsdPattern, pub profit: BlockCumulativeSumPattern, - pub profit_to_loss_ratio: _1m1w1y24hPattern, - pub sell_side_risk_ratio: _1m1w1y24hPattern8, + pub profit_to_loss_ratio: _1m1w1y24hHeightPattern2, + pub sell_side_risk_ratio: _1m1w1y24hHeightPattern3, pub sopr: AdjustedRatioValuePattern, } @@ -1378,8 +1441,8 @@ impl CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern { peak_regret: BlockCumulativeSumPattern::new(client.clone(), _m(&acc, "realized_peak_regret")), price: CentsPpmRatioSatsUsdPattern::new(client.clone(), _m(&acc, "realized_price")), profit: BlockCumulativeSumPattern::new(client.clone(), _m(&acc, "realized_profit")), - profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "realized_profit_to_loss_ratio")), - sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), _m(&acc, "sell_side_risk_ratio")), + profit_to_loss_ratio: _1m1w1y24hHeightPattern2::new(client.clone(), _m(&acc, "realized_profit_to_loss_ratio")), + sell_side_risk_ratio: _1m1w1y24hHeightPattern3::new(client.clone(), _m(&acc, "sell_side_risk_ratio")), sopr: AdjustedRatioValuePattern::new(client.clone(), acc.clone()), } } @@ -1421,6 +1484,21 @@ impl EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2 { } } +/// Pattern struct for repeated tree structure. +pub struct AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern { + pub all: BtcCentsSatsUsdPattern, + pub height: SeriesPattern18<[Sats; 8]>, + pub p2a: BtcCentsSatsUsdPattern, + pub p2pk33: BtcCentsSatsUsdPattern, + pub p2pk65: BtcCentsSatsUsdPattern, + pub p2pkh: BtcCentsSatsUsdPattern, + pub p2sh: BtcCentsSatsUsdPattern, + pub p2tr: BtcCentsSatsUsdPattern, + pub p2wpkh: BtcCentsSatsUsdPattern, + pub p2wsh: BtcCentsSatsUsdPattern, + pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, +} + /// Pattern struct for repeated tree structure. pub struct AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern { pub average: _1m1w1y24hPattern, @@ -1524,22 +1602,9 @@ impl AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90S } /// Pattern struct for repeated tree structure. -pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern { - pub all: BtcCentsSatsUsdPattern, - pub p2a: BtcCentsSatsUsdPattern, - pub p2pk33: BtcCentsSatsUsdPattern, - pub p2pk65: BtcCentsSatsUsdPattern, - pub p2pkh: BtcCentsSatsUsdPattern, - pub p2sh: BtcCentsSatsUsdPattern, - pub p2tr: BtcCentsSatsUsdPattern, - pub p2wpkh: BtcCentsSatsUsdPattern, - pub p2wsh: BtcCentsSatsUsdPattern, - pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5, -} - -/// Pattern struct for repeated tree structure. -pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 { +pub struct AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern { pub all: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 8]>, pub p2a: AverageBlockCumulativeSumPattern, pub p2pk33: AverageBlockCumulativeSumPattern, pub p2pk65: AverageBlockCumulativeSumPattern, @@ -1550,25 +1615,22 @@ pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 { pub p2wsh: AverageBlockCumulativeSumPattern, } -impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - all: AverageBlockCumulativeSumPattern::new(client.clone(), acc.clone()), - p2a: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2a", &acc)), - p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2pk33", &acc)), - p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2pk65", &acc)), - p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2pkh", &acc)), - p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2sh", &acc)), - p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2tr", &acc)), - p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2wpkh", &acc)), - p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), _p("p2wsh", &acc)), - } - } +/// Pattern struct for repeated tree structure. +pub struct AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern { + pub all: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, + pub p2a: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, } /// Pattern struct for repeated tree structure. -pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 { +pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 { pub all: PercentPpmRatioPattern2, pub p2a: PercentPpmRatioPattern2, pub p2pk33: PercentPpmRatioPattern2, @@ -1580,7 +1642,7 @@ pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 { pub p2wsh: PercentPpmRatioPattern2, } -impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 { +impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { @@ -1598,37 +1660,7 @@ impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 { } /// Pattern struct for repeated tree structure. -pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 { - pub all: SeriesPattern1, - pub p2a: SeriesPattern1, - pub p2pk33: SeriesPattern1, - pub p2pk65: SeriesPattern1, - pub p2pkh: SeriesPattern1, - pub p2sh: SeriesPattern1, - pub p2tr: SeriesPattern1, - pub p2wpkh: SeriesPattern1, - pub p2wsh: SeriesPattern1, -} - -impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - all: SeriesPattern1::new(client.clone(), acc.clone()), - p2a: SeriesPattern1::new(client.clone(), _p("p2a", &acc)), - p2pk33: SeriesPattern1::new(client.clone(), _p("p2pk33", &acc)), - p2pk65: SeriesPattern1::new(client.clone(), _p("p2pk65", &acc)), - p2pkh: SeriesPattern1::new(client.clone(), _p("p2pkh", &acc)), - p2sh: SeriesPattern1::new(client.clone(), _p("p2sh", &acc)), - p2tr: SeriesPattern1::new(client.clone(), _p("p2tr", &acc)), - p2wpkh: SeriesPattern1::new(client.clone(), _p("p2wpkh", &acc)), - p2wsh: SeriesPattern1::new(client.clone(), _p("p2wsh", &acc)), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 { +pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 { pub all: _1m1w1y24hPercentPpmRatioPattern, pub p2a: _1m1w1y24hPercentPpmRatioPattern, pub p2pk33: _1m1w1y24hPercentPpmRatioPattern, @@ -1640,7 +1672,7 @@ pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 { pub p2wsh: _1m1w1y24hPercentPpmRatioPattern, } -impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 { +impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { @@ -1747,34 +1779,6 @@ impl Pct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern { } } -/// Pattern struct for repeated tree structure. -pub struct ChainDataFeeFeesOutputTxPattern { - pub chain_share: PercentPpmRatioPattern2, - pub data_bytes: AverageBlockCumulativeSumPattern, - pub data_share: PercentPpmRatioPattern2, - pub fee_share: _1m1w1y24hPercentPpmRatioPattern, - pub fees: AverageBlockCumulativeSumPattern, - pub output_count: AverageBlockCumulativeSumPattern, - pub tx_count: AverageBlockCumulativeSumPattern, - pub tx_vsize: AverageBlockCumulativeSumPattern, -} - -impl ChainDataFeeFeesOutputTxPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - chain_share: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "chain_share")), - data_bytes: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "data_bytes")), - data_share: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "data_share")), - fee_share: _1m1w1y24hPercentPpmRatioPattern::new(client.clone(), _m(&acc, "fee_share")), - fees: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "fees")), - output_count: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "output_count")), - tx_count: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "tx_count")), - tx_vsize: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "tx_vsize")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct _10y2y3y4y5y6y8yPattern { pub _10y: PercentPpmRatioPattern, @@ -1857,10 +1861,10 @@ impl _1m1y2y3m4y6m8yPattern { pub struct ActiveInputOutputSpendablePattern { pub active_reused_addr_count: _1m1w1y24hBlockPattern, pub active_reused_addr_share: _1m1w1y24hBlockPattern2, - pub input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, - pub input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7, - pub output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, - pub output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7, + pub input_from_reused_addr_count: AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, + pub input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, + pub output_to_reused_addr_count: AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, + pub output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, pub spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern, } @@ -1890,40 +1894,14 @@ impl CapLossMvrvNetPriceProfitSoprPattern { } } -/// Pattern struct for repeated tree structure. -pub struct CoindaysDormancySupplyWakefulnessPattern { - pub coindays_consumed: AverageBlockCumulativeSumPattern, - pub coindays_created: AverageBlockCumulativeSumPattern, - pub coindays_stored: AverageBlockCumulativeSumPattern, - pub dormancy: SeriesPattern1, - pub supply: AwakeDormantPattern, - pub wakefulness: SeriesPattern1, - pub wakefulness_to_dormancy: SeriesPattern1, -} - -impl CoindaysDormancySupplyWakefulnessPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - coindays_consumed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "coindays_consumed")), - coindays_created: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "coindays_created")), - coindays_stored: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "coindays_stored")), - dormancy: SeriesPattern1::new(client.clone(), _m(&acc, "dormancy")), - supply: AwakeDormantPattern::new(client.clone(), acc.clone()), - wakefulness: SeriesPattern1::new(client.clone(), _m(&acc, "wakefulness")), - wakefulness_to_dormancy: SeriesPattern1::new(client.clone(), _m(&acc, "wakefulness_to_dormancy")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct InMaxMinPerSupplyPattern { pub in_loss: PerPattern, pub in_profit: PerPattern, pub max: CentsSatsUsdPattern, pub min: CentsSatsUsdPattern, - pub per_coin: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, - pub per_dollar: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, + pub per_coin: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, + pub per_dollar: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, pub supply_density: PercentPpmRatioPattern2, } @@ -1935,8 +1913,8 @@ impl InMaxMinPerSupplyPattern { in_profit: PerPattern::new(client.clone(), _m(&acc, "cost_basis_in_profit_per")), max: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "cost_basis_max")), min: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "cost_basis_min")), - per_coin: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), _m(&acc, "cost_basis_per_coin")), - per_dollar: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), _m(&acc, "cost_basis_per_dollar")), + per_coin: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), _m(&acc, "cost_basis_per_coin")), + per_dollar: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), _m(&acc, "cost_basis_per_dollar")), supply_density: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "supply_density")), } } @@ -1994,6 +1972,30 @@ impl MaxMedianMinPct10Pct25Pct75Pct90Pattern { } } +/// Pattern struct for repeated tree structure. +pub struct AverageBlockChainCumulativeDataSumPattern { + pub average: _1m1w1y24hPattern, + pub block: SeriesPattern18, + pub chain_share: PercentPpmRatioPattern2, + pub cumulative: SeriesPattern1, + pub data_share: PercentPpmRatioPattern2, + pub sum: _1m1w1y24hPattern, +} + +impl AverageBlockChainCumulativeDataSumPattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + average: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "data_bytes_average")), + block: SeriesPattern18::new(client.clone(), _m(&acc, "data_bytes")), + chain_share: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "chain_share")), + cumulative: SeriesPattern1::new(client.clone(), _m(&acc, "data_bytes_cumulative")), + data_share: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "data_share")), + sum: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "data_bytes_sum")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct AverageBlockCumulativeInSumPattern { pub average: _1m1w1y24hPattern3, @@ -2090,6 +2092,52 @@ impl DeltaDominanceHalfInTotalPattern { } } +/// Pattern struct for repeated tree structure. +pub struct HeightRankTailThresholdPattern { + pub height: SeriesPattern18<[Dollars; 3]>, + pub rank: SeriesPattern1, + pub tail: PercentPpmRatioPattern2, + pub threshold_pct0_025: SeriesPattern1, + pub threshold_pct0_05: SeriesPattern1, + pub threshold_pct0_1: SeriesPattern1, +} + +impl HeightRankTailThresholdPattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + height: SeriesPattern18::new(client.clone(), _m(&acc, "thresholds")), + rank: SeriesPattern1::new(client.clone(), _m(&acc, "rank")), + tail: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "tail")), + threshold_pct0_025: SeriesPattern1::new(client.clone(), _m(&acc, "threshold")), + threshold_pct0_05: SeriesPattern1::new(client.clone(), _m(&acc, "threshold_pct0_05")), + threshold_pct0_1: SeriesPattern1::new(client.clone(), _m(&acc, "threshold_pct0_1")), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct _1m1w1y24hHeightPattern3 { + pub _1m: PercentPpmRatioPattern2, + pub _1w: PercentPpmRatioPattern2, + pub _1y: PercentPpmRatioPattern2, + pub _24h: PercentPpmRatioPattern2, + pub height: SeriesPattern18<[PartsPerMillion32; 4]>, +} + +impl _1m1w1y24hHeightPattern3 { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + _1m: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "1m")), + _1w: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "1w")), + _1y: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "1y")), + _24h: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "24h")), + height: SeriesPattern18::new(client.clone(), _m(&acc, "ppm")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct _1m1w1y24hBlockPattern2 { pub _1m: SeriesPattern1, @@ -2134,6 +2182,72 @@ impl _1m1w1y24hBlockPattern { } } +/// Pattern struct for repeated tree structure. +pub struct _1m1w1y24hHeightPattern { + pub _1m: SeriesPattern1, + pub _1w: SeriesPattern1, + pub _1y: SeriesPattern1, + pub _24h: SeriesPattern1, + pub height: SeriesPattern18<[StoredF32; 4]>, +} + +impl _1m1w1y24hHeightPattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + _1m: SeriesPattern1::new(client.clone(), _m(&acc, "1m")), + _1w: SeriesPattern1::new(client.clone(), _m(&acc, "1w")), + _1y: SeriesPattern1::new(client.clone(), _m(&acc, "1y")), + _24h: SeriesPattern1::new(client.clone(), _m(&acc, "24h")), + height: SeriesPattern18::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct _1m1w1y24hHeightPattern4 { + pub _1m: SeriesPattern1, + pub _1w: SeriesPattern1, + pub _1y: SeriesPattern1, + pub _24h: SeriesPattern1, + pub height: SeriesPattern18<[StoredF64; 3]>, +} + +impl _1m1w1y24hHeightPattern4 { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + _1m: SeriesPattern1::new(client.clone(), _m(&acc, "1m")), + _1w: SeriesPattern1::new(client.clone(), _m(&acc, "1w")), + _1y: SeriesPattern1::new(client.clone(), _m(&acc, "1y")), + _24h: SeriesPattern1::new(client.clone(), _m(&acc, "24h")), + height: SeriesPattern18::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct _1m1w1y24hHeightPattern2 { + pub _1m: SeriesPattern1, + pub _1w: SeriesPattern1, + pub _1y: SeriesPattern1, + pub _24h: SeriesPattern1, + pub height: SeriesPattern18<[StoredF64; 4]>, +} + +impl _1m1w1y24hHeightPattern2 { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + _1m: SeriesPattern1::new(client.clone(), _m(&acc, "1m")), + _1w: SeriesPattern1::new(client.clone(), _m(&acc, "1w")), + _1y: SeriesPattern1::new(client.clone(), _m(&acc, "1y")), + _24h: SeriesPattern1::new(client.clone(), _m(&acc, "24h")), + height: SeriesPattern18::new(client.clone(), acc.clone()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct ActiveBidirectionalReactivatedReceivingSendingPattern { pub active: _1m1w1y24hBlockPattern, @@ -2244,6 +2358,28 @@ impl ActivityOutputsRealizedSupplyUnrealizedPattern2 { } } +/// Pattern struct for repeated tree structure. +pub struct AverageBlockCumulativeFeeSumPattern { + pub average: _1m1w1y24hPattern, + pub block: SeriesPattern18, + pub cumulative: SeriesPattern1, + pub fee_share: _1m1w1y24hPercentPpmRatioPattern, + pub sum: _1m1w1y24hPattern, +} + +impl AverageBlockCumulativeFeeSumPattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + average: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "fees_average")), + block: SeriesPattern18::new(client.clone(), _m(&acc, "fees")), + cumulative: SeriesPattern1::new(client.clone(), _m(&acc, "fees_cumulative")), + fee_share: _1m1w1y24hPercentPpmRatioPattern::new(client.clone(), _m(&acc, "fee_share")), + sum: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "fees_sum")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct BlockChangeCumulativeDeltaSumPattern { pub block: CentsUsdPattern4, @@ -2461,25 +2597,12 @@ impl PhsReboundThsPattern { } /// Pattern struct for repeated tree structure. -pub struct RankTailThresholdPattern { - pub rank: SeriesPattern1, - pub tail: PercentPpmRatioPattern2, - pub threshold_pct0_025: SeriesPattern1, - pub threshold_pct0_05: SeriesPattern1, - pub threshold_pct0_1: SeriesPattern1, -} - -impl RankTailThresholdPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - rank: SeriesPattern1::new(client.clone(), _m(&acc, "rank")), - tail: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "tail")), - threshold_pct0_025: SeriesPattern1::new(client.clone(), _m(&acc, "threshold")), - threshold_pct0_05: SeriesPattern1::new(client.clone(), _m(&acc, "threshold_pct0_05")), - threshold_pct0_1: SeriesPattern1::new(client.clone(), _m(&acc, "threshold_pct0_1")), - } - } +pub struct CumulativeMultipleOversizedPrePattern3 { + pub cumulative: SeriesPattern18, + pub multiple: AverageBlockCumulativeSumPattern, + pub oversized: AverageBlockCumulativeSumPattern, + pub pre_v30_nonstandard: AverageBlockCumulativeSumPattern, + pub pre_v30_standard: AverageBlockCumulativeSumPattern, } /// Pattern struct for repeated tree structure. @@ -2622,26 +2745,6 @@ impl _1m1w1y24hPattern2 { } } -/// Pattern struct for repeated tree structure. -pub struct _1m1w1y24hPattern8 { - pub _1m: PercentPpmRatioPattern2, - pub _1w: PercentPpmRatioPattern2, - pub _1y: PercentPpmRatioPattern2, - pub _24h: PercentPpmRatioPattern2, -} - -impl _1m1w1y24hPattern8 { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - _1m: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "1m")), - _1w: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "1w")), - _1y: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "1y")), - _24h: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "24h")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct AverageBlockCumulativeSumPattern2 { pub average: _1m1w1y24hPattern3, @@ -2814,7 +2917,7 @@ impl CentsToUsdPattern3 { pub struct CoindaysCoinyearsDormancyTransferPattern { pub coindays_destroyed: AverageBlockCumulativeSumPattern, pub coinyears_destroyed: SeriesPattern1, - pub dormancy: _1m1w1y24hPattern, + pub dormancy: _1m1w1y24hHeightPattern, pub transfer_volume: AverageBlockCumulativeInSumPattern, } @@ -2824,7 +2927,7 @@ impl CoindaysCoinyearsDormancyTransferPattern { Self { coindays_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "coindays_destroyed")), coinyears_destroyed: SeriesPattern1::new(client.clone(), _m(&acc, "coinyears_destroyed")), - dormancy: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "dormancy")), + dormancy: _1m1w1y24hHeightPattern::new(client.clone(), _m(&acc, "dormancy")), transfer_volume: AverageBlockCumulativeInSumPattern::new(client.clone(), _m(&acc, "transfer_volume")), } } @@ -2850,26 +2953,6 @@ impl LossNetNuplProfitPattern { } } -/// Pattern struct for repeated tree structure. -pub struct MobilitySpendingSupplyPattern { - pub mobility: SeriesPattern1, - pub spending_exposure: SeriesPattern1, - pub spending_rate: SeriesPattern1, - pub supply: ImmobileMobilePattern, -} - -impl MobilitySpendingSupplyPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - mobility: SeriesPattern1::new(client.clone(), _m(&acc, "mobility")), - spending_exposure: SeriesPattern1::new(client.clone(), _m(&acc, "spending_exposure")), - spending_rate: SeriesPattern1::new(client.clone(), _m(&acc, "spending_rate")), - supply: ImmobileMobilePattern::new(client.clone(), acc.clone()), - } - } -} - /// Pattern struct for repeated tree structure. pub struct NuplRealizedSupplyUnrealizedPattern { pub nupl: PpmRatioPattern, @@ -2933,7 +3016,7 @@ impl AverageBlockCumulativeSumPattern { /// Pattern struct for repeated tree structure. pub struct AdjustedRatioValuePattern { pub adjusted: RatioTransferValuePattern, - pub ratio: _1m1w1y24hPattern, + pub ratio: _1m1w1y24hHeightPattern4, pub value_destroyed: AverageBlockCumulativeSumPattern, } @@ -2942,7 +3025,7 @@ impl AdjustedRatioValuePattern { pub fn new(client: Arc, acc: String) -> Self { Self { adjusted: RatioTransferValuePattern::new(client.clone(), acc.clone()), - ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "sopr")), + ratio: _1m1w1y24hHeightPattern4::new(client.clone(), _m(&acc, "sopr")), value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "value_destroyed")), } } @@ -3103,7 +3186,7 @@ impl CentsSatsUsdPattern { pub struct CountEventsSupplyPattern { pub count: FundedTotalPattern, pub events: ActiveInputOutputSpendablePattern, - pub supply: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern, + pub supply: AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern, } /// Pattern struct for repeated tree structure. @@ -3288,7 +3371,7 @@ impl PpmPriceRatioPattern { /// Pattern struct for repeated tree structure. pub struct RatioTransferValuePattern { - pub ratio: _1m1w1y24hPattern, + pub ratio: _1m1w1y24hHeightPattern2, pub transfer_volume: AverageBlockCumulativeSumPattern, pub value_destroyed: AverageBlockCumulativeSumPattern, } @@ -3297,7 +3380,7 @@ impl RatioTransferValuePattern { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { - ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "asopr")), + ratio: _1m1w1y24hHeightPattern2::new(client.clone(), _m(&acc, "asopr")), transfer_volume: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "adj_value_created")), value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "adj_value_destroyed")), } @@ -3436,22 +3519,6 @@ impl AllSthPattern { } } -/// Pattern struct for repeated tree structure. -pub struct AwakeDormantPattern { - pub awake: BtcCentsSatsUsdPattern, - pub dormant: BtcCentsSatsUsdPattern, -} - -impl AwakeDormantPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - awake: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "awake_supply")), - dormant: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "dormant_supply")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct AwakeDormantPattern2 { pub awake: CapPriceSupplyPattern, @@ -3620,18 +3687,8 @@ impl CoindaysTransferPattern { /// Pattern struct for repeated tree structure. pub struct FundedTotalPattern { - pub funded: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, - pub total: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, -} - -impl FundedTotalPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - funded: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), acc.clone()), - total: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), _p("total", &acc)), - } - } + pub funded: AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, + pub total: AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, } /// Pattern struct for repeated tree structure. @@ -3640,22 +3697,6 @@ pub struct ImmobileMobilePattern2 { pub mobile: BtcCentsInSatsUsdPattern, } -/// Pattern struct for repeated tree structure. -pub struct ImmobileMobilePattern { - pub immobile: BtcCentsSatsUsdPattern, - pub mobile: BtcCentsSatsUsdPattern, -} - -impl ImmobileMobilePattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - immobile: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "immobile_supply")), - mobile: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "mobile_supply")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct InPattern2 { pub in_loss: CentsUsdPattern3, @@ -4750,6 +4791,7 @@ pub struct SeriesTree_Inputs_ByType_InputCount { pub p2a: AverageBlockCumulativeSumPattern, pub unknown: AverageBlockCumulativeSumPattern, pub empty: AverageBlockCumulativeSumPattern, + pub height: SeriesPattern18<[StoredU16; 11]>, } impl SeriesTree_Inputs_ByType_InputCount { @@ -4767,6 +4809,7 @@ impl SeriesTree_Inputs_ByType_InputCount { p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "p2a_prevout_count".to_string()), unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "unknown_outputs_prevout_count".to_string()), empty: AverageBlockCumulativeSumPattern::new(client.clone(), "empty_outputs_prevout_count".to_string()), + height: SeriesPattern18::new(client.clone(), "prevout_count_by_type".to_string()), } } } @@ -4818,6 +4861,7 @@ pub struct SeriesTree_Inputs_ByType_TxCount { pub p2a: AverageBlockCumulativeSumPattern, pub unknown: AverageBlockCumulativeSumPattern, pub empty: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 11]>, } impl SeriesTree_Inputs_ByType_TxCount { @@ -4835,6 +4879,7 @@ impl SeriesTree_Inputs_ByType_TxCount { p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2a_prevout".to_string()), unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_unknown_outputs_prevout".to_string()), empty: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_empty_outputs_prevout".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "tx_count_with_prevout_by_type_cumulative".to_string()), } } } @@ -4927,7 +4972,7 @@ pub struct SeriesTree_Outputs_ByType { pub output_count: SeriesTree_Outputs_ByType_OutputCount, pub spendable_output_count: AverageBlockCumulativeSumPattern, pub output_share: SeriesTree_Outputs_ByType_OutputShare, - pub tx_count: AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern, + pub tx_count: SeriesTree_Outputs_ByType_TxCount, pub tx_share: EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2, } @@ -4937,7 +4982,7 @@ impl SeriesTree_Outputs_ByType { output_count: SeriesTree_Outputs_ByType_OutputCount::new(client.clone(), format!("{base_path}_output_count")), spendable_output_count: AverageBlockCumulativeSumPattern::new(client.clone(), "spendable_output_count".to_string()), output_share: SeriesTree_Outputs_ByType_OutputShare::new(client.clone(), format!("{base_path}_output_share")), - tx_count: AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern::new(client.clone(), "tx_count".to_string()), + tx_count: SeriesTree_Outputs_ByType_TxCount::new(client.clone(), format!("{base_path}_tx_count")), tx_share: EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2::new(client.clone(), "tx_share_with".to_string()), } } @@ -4958,6 +5003,7 @@ pub struct SeriesTree_Outputs_ByType_OutputCount { pub unknown: AverageBlockCumulativeSumPattern, pub empty: AverageBlockCumulativeSumPattern, pub op_return: AverageBlockCumulativeSumPattern, + pub height: SeriesPattern18<[StoredU16; 12]>, } impl SeriesTree_Outputs_ByType_OutputCount { @@ -4976,6 +5022,7 @@ impl SeriesTree_Outputs_ByType_OutputCount { unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "unknown_outputs_output_count".to_string()), empty: AverageBlockCumulativeSumPattern::new(client.clone(), "empty_outputs_output_count".to_string()), op_return: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_output_count".to_string()), + height: SeriesPattern18::new(client.clone(), "output_count_by_type".to_string()), } } } @@ -5015,6 +5062,45 @@ impl SeriesTree_Outputs_ByType_OutputShare { } } +/// Series tree node. +pub struct SeriesTree_Outputs_ByType_TxCount { + pub all: AverageBlockCumulativeSumPattern, + pub p2pk65: AverageBlockCumulativeSumPattern, + pub p2pk33: AverageBlockCumulativeSumPattern, + pub p2pkh: AverageBlockCumulativeSumPattern, + pub p2ms: AverageBlockCumulativeSumPattern, + pub p2sh: AverageBlockCumulativeSumPattern, + pub p2wpkh: AverageBlockCumulativeSumPattern, + pub p2wsh: AverageBlockCumulativeSumPattern, + pub p2tr: AverageBlockCumulativeSumPattern, + pub p2a: AverageBlockCumulativeSumPattern, + pub unknown: AverageBlockCumulativeSumPattern, + pub empty: AverageBlockCumulativeSumPattern, + pub op_return: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 12]>, +} + +impl SeriesTree_Outputs_ByType_TxCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_bis".to_string()), + p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2pk65_output".to_string()), + p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2pk33_output".to_string()), + p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2pkh_output".to_string()), + p2ms: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2ms_output".to_string()), + p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2sh_output".to_string()), + p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2wpkh_output".to_string()), + p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2wsh_output".to_string()), + p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2tr_output".to_string()), + p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_p2a_output".to_string()), + unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_unknown_outputs_output".to_string()), + empty: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_empty_outputs_output".to_string()), + op_return: AverageBlockCumulativeSumPattern::new(client.clone(), "tx_count_with_op_return_output".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "tx_count_with_output_by_type_cumulative".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Outputs_Value { pub op_return: BlockCumulativePattern, @@ -5033,11 +5119,11 @@ pub struct SeriesTree_Addrs { pub raw: SeriesTree_Addrs_Raw, pub indexes: SeriesTree_Addrs_Indexes, pub data: SeriesTree_Addrs_Data, - pub funded: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, - pub empty: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, + pub funded: SeriesTree_Addrs_Funded, + pub empty: SeriesTree_Addrs_Empty, pub activity: SeriesTree_Addrs_Activity, - pub total: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, - pub new: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, + pub total: SeriesTree_Addrs_Total, + pub new: SeriesTree_Addrs_New, pub reused: SeriesTree_Addrs_Reused, pub respent: SeriesTree_Addrs_Respent, pub exposed: SeriesTree_Addrs_Exposed, @@ -5051,11 +5137,11 @@ impl SeriesTree_Addrs { raw: SeriesTree_Addrs_Raw::new(client.clone(), format!("{base_path}_raw")), indexes: SeriesTree_Addrs_Indexes::new(client.clone(), format!("{base_path}_indexes")), data: SeriesTree_Addrs_Data::new(client.clone(), format!("{base_path}_data")), - funded: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), "addr_count".to_string()), - empty: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), "empty_addr_count".to_string()), + funded: SeriesTree_Addrs_Funded::new(client.clone(), format!("{base_path}_funded")), + empty: SeriesTree_Addrs_Empty::new(client.clone(), format!("{base_path}_empty")), activity: SeriesTree_Addrs_Activity::new(client.clone(), format!("{base_path}_activity")), - total: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), "total_addr_count".to_string()), - new: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "new_addr_count".to_string()), + total: SeriesTree_Addrs_Total::new(client.clone(), format!("{base_path}_total")), + new: SeriesTree_Addrs_New::new(client.clone(), format!("{base_path}_new")), reused: SeriesTree_Addrs_Reused::new(client.clone(), format!("{base_path}_reused")), respent: SeriesTree_Addrs_Respent::new(client.clone(), format!("{base_path}_respent")), exposed: SeriesTree_Addrs_Exposed::new(client.clone(), format!("{base_path}_exposed")), @@ -5258,6 +5344,68 @@ impl SeriesTree_Addrs_Data { } } +/// Series tree node. +pub struct SeriesTree_Addrs_Funded { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Funded { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "addr_count_by_type".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Empty { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Empty { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "empty_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_empty_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_empty_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_empty_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_empty_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_empty_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_empty_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_empty_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_empty_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "empty_addr_count_by_type".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Activity { pub all: SeriesTree_Addrs_Activity_All, @@ -5308,9 +5456,69 @@ impl SeriesTree_Addrs_Activity_All { } } +/// Series tree node. +pub struct SeriesTree_Addrs_Total { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Total { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "total_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_total_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_total_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_total_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_total_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_total_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_total_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_total_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_total_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "total_addr_count_by_type".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_New { + pub all: AverageBlockCumulativeSumPattern, + pub p2pk65: AverageBlockCumulativeSumPattern, + pub p2pk33: AverageBlockCumulativeSumPattern, + pub p2pkh: AverageBlockCumulativeSumPattern, + pub p2sh: AverageBlockCumulativeSumPattern, + pub p2wpkh: AverageBlockCumulativeSumPattern, + pub p2wsh: AverageBlockCumulativeSumPattern, + pub p2tr: AverageBlockCumulativeSumPattern, + pub p2a: AverageBlockCumulativeSumPattern, +} + +impl SeriesTree_Addrs_New { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: AverageBlockCumulativeSumPattern::new(client.clone(), "new_addr_count".to_string()), + p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk65_new_addr_count".to_string()), + p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk33_new_addr_count".to_string()), + p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pkh_new_addr_count".to_string()), + p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2sh_new_addr_count".to_string()), + p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wpkh_new_addr_count".to_string()), + p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wsh_new_addr_count".to_string()), + p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), "p2tr_new_addr_count".to_string()), + p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "p2a_new_addr_count".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Reused { - pub count: FundedTotalPattern, + pub count: SeriesTree_Addrs_Reused_Count, pub events: SeriesTree_Addrs_Reused_Events, pub supply: SeriesTree_Addrs_Reused_Supply, } @@ -5318,20 +5526,97 @@ pub struct SeriesTree_Addrs_Reused { impl SeriesTree_Addrs_Reused { pub fn new(client: Arc, base_path: String) -> Self { Self { - count: FundedTotalPattern::new(client.clone(), "reused_addr_count".to_string()), + count: SeriesTree_Addrs_Reused_Count::new(client.clone(), format!("{base_path}_count")), events: SeriesTree_Addrs_Reused_Events::new(client.clone(), format!("{base_path}_events")), supply: SeriesTree_Addrs_Reused_Supply::new(client.clone(), format!("{base_path}_supply")), } } } +/// Series tree node. +pub struct SeriesTree_Addrs_Reused_Count { + pub funded: SeriesTree_Addrs_Reused_Count_Funded, + pub total: SeriesTree_Addrs_Reused_Count_Total, +} + +impl SeriesTree_Addrs_Reused_Count { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + funded: SeriesTree_Addrs_Reused_Count_Funded::new(client.clone(), format!("{base_path}_funded")), + total: SeriesTree_Addrs_Reused_Count_Total::new(client.clone(), format!("{base_path}_total")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Reused_Count_Funded { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Reused_Count_Funded { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "reused_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_reused_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_reused_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_reused_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_reused_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_reused_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_reused_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_reused_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_reused_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "reused_addr_count_by_type".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Reused_Count_Total { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Reused_Count_Total { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "total_reused_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_total_reused_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_total_reused_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_total_reused_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_total_reused_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_total_reused_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_total_reused_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_total_reused_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_total_reused_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "total_reused_addr_count_by_type".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Reused_Events { - pub output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, - pub output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7, + pub output_to_reused_addr_count: SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount, + pub output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, pub spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern, - pub input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, - pub input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7, + pub input_from_reused_addr_count: SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount, + pub input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, pub active_reused_addr_count: _1m1w1y24hBlockPattern, pub active_reused_addr_share: _1m1w1y24hBlockPattern2, } @@ -5339,17 +5624,79 @@ pub struct SeriesTree_Addrs_Reused_Events { impl SeriesTree_Addrs_Reused_Events { pub fn new(client: Arc, base_path: String) -> Self { Self { - output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "output_to_reused_addr_count".to_string()), - output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7::new(client.clone(), "output_to_reused_addr_share".to_string()), + output_to_reused_addr_count: SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount::new(client.clone(), format!("{base_path}_output_to_reused_addr_count")), + output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "output_to_reused_addr_share".to_string()), spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern::new(client.clone(), "spendable_output_to_reused_addr_share".to_string()), - input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "input_from_reused_addr_count".to_string()), - input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7::new(client.clone(), "input_from_reused_addr_share".to_string()), + input_from_reused_addr_count: SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount::new(client.clone(), format!("{base_path}_input_from_reused_addr_count")), + input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "input_from_reused_addr_share".to_string()), active_reused_addr_count: _1m1w1y24hBlockPattern::new(client.clone(), "active_reused_addr_count".to_string()), active_reused_addr_share: _1m1w1y24hBlockPattern2::new(client.clone(), "active_reused_addr_share".to_string()), } } } +/// Series tree node. +pub struct SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount { + pub all: AverageBlockCumulativeSumPattern, + pub p2pk65: AverageBlockCumulativeSumPattern, + pub p2pk33: AverageBlockCumulativeSumPattern, + pub p2pkh: AverageBlockCumulativeSumPattern, + pub p2sh: AverageBlockCumulativeSumPattern, + pub p2wpkh: AverageBlockCumulativeSumPattern, + pub p2wsh: AverageBlockCumulativeSumPattern, + pub p2tr: AverageBlockCumulativeSumPattern, + pub p2a: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: AverageBlockCumulativeSumPattern::new(client.clone(), "output_to_reused_addr_count".to_string()), + p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk65_output_to_reused_addr_count".to_string()), + p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk33_output_to_reused_addr_count".to_string()), + p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pkh_output_to_reused_addr_count".to_string()), + p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2sh_output_to_reused_addr_count".to_string()), + p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wpkh_output_to_reused_addr_count".to_string()), + p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wsh_output_to_reused_addr_count".to_string()), + p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), "p2tr_output_to_reused_addr_count".to_string()), + p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "p2a_output_to_reused_addr_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "output_to_reused_addr_count_by_type_cumulative".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount { + pub all: AverageBlockCumulativeSumPattern, + pub p2pk65: AverageBlockCumulativeSumPattern, + pub p2pk33: AverageBlockCumulativeSumPattern, + pub p2pkh: AverageBlockCumulativeSumPattern, + pub p2sh: AverageBlockCumulativeSumPattern, + pub p2wpkh: AverageBlockCumulativeSumPattern, + pub p2wsh: AverageBlockCumulativeSumPattern, + pub p2tr: AverageBlockCumulativeSumPattern, + pub p2a: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: AverageBlockCumulativeSumPattern::new(client.clone(), "input_from_reused_addr_count".to_string()), + p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk65_input_from_reused_addr_count".to_string()), + p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk33_input_from_reused_addr_count".to_string()), + p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pkh_input_from_reused_addr_count".to_string()), + p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2sh_input_from_reused_addr_count".to_string()), + p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wpkh_input_from_reused_addr_count".to_string()), + p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wsh_input_from_reused_addr_count".to_string()), + p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), "p2tr_input_from_reused_addr_count".to_string()), + p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "p2a_input_from_reused_addr_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "input_from_reused_addr_count_by_type_cumulative".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Reused_Supply { pub all: BtcCentsSatsUsdPattern, @@ -5361,7 +5708,8 @@ pub struct SeriesTree_Addrs_Reused_Supply { pub p2wsh: BtcCentsSatsUsdPattern, pub p2tr: BtcCentsSatsUsdPattern, pub p2a: BtcCentsSatsUsdPattern, - pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5, + pub height: SeriesPattern18<[Sats; 8]>, + pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, } impl SeriesTree_Addrs_Reused_Supply { @@ -5376,14 +5724,15 @@ impl SeriesTree_Addrs_Reused_Supply { p2wsh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wsh_reused_addr_supply".to_string()), p2tr: BtcCentsSatsUsdPattern::new(client.clone(), "p2tr_reused_addr_supply".to_string()), p2a: BtcCentsSatsUsdPattern::new(client.clone(), "p2a_reused_addr_supply".to_string()), - share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5::new(client.clone(), "reused_addr_supply_share".to_string()), + height: SeriesPattern18::new(client.clone(), "reused_addr_supply_sats_by_type".to_string()), + share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), "reused_addr_supply_share".to_string()), } } } /// Series tree node. pub struct SeriesTree_Addrs_Respent { - pub count: FundedTotalPattern, + pub count: SeriesTree_Addrs_Respent_Count, pub events: SeriesTree_Addrs_Respent_Events, pub supply: SeriesTree_Addrs_Respent_Supply, } @@ -5391,20 +5740,97 @@ pub struct SeriesTree_Addrs_Respent { impl SeriesTree_Addrs_Respent { pub fn new(client: Arc, base_path: String) -> Self { Self { - count: FundedTotalPattern::new(client.clone(), "respent_addr_count".to_string()), + count: SeriesTree_Addrs_Respent_Count::new(client.clone(), format!("{base_path}_count")), events: SeriesTree_Addrs_Respent_Events::new(client.clone(), format!("{base_path}_events")), supply: SeriesTree_Addrs_Respent_Supply::new(client.clone(), format!("{base_path}_supply")), } } } +/// Series tree node. +pub struct SeriesTree_Addrs_Respent_Count { + pub funded: SeriesTree_Addrs_Respent_Count_Funded, + pub total: SeriesTree_Addrs_Respent_Count_Total, +} + +impl SeriesTree_Addrs_Respent_Count { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + funded: SeriesTree_Addrs_Respent_Count_Funded::new(client.clone(), format!("{base_path}_funded")), + total: SeriesTree_Addrs_Respent_Count_Total::new(client.clone(), format!("{base_path}_total")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Respent_Count_Funded { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Respent_Count_Funded { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "respent_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_respent_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_respent_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_respent_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_respent_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_respent_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_respent_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_respent_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_respent_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "respent_addr_count_by_type".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Respent_Count_Total { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Respent_Count_Total { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "total_respent_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_total_respent_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_total_respent_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_total_respent_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_total_respent_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_total_respent_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_total_respent_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_total_respent_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_total_respent_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "total_respent_addr_count_by_type".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Respent_Events { - pub output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, - pub output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7, + pub output_to_reused_addr_count: SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount, + pub output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, pub spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern, - pub input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, - pub input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7, + pub input_from_reused_addr_count: SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount, + pub input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6, pub active_reused_addr_count: _1m1w1y24hBlockPattern, pub active_reused_addr_share: _1m1w1y24hBlockPattern2, } @@ -5412,17 +5838,79 @@ pub struct SeriesTree_Addrs_Respent_Events { impl SeriesTree_Addrs_Respent_Events { pub fn new(client: Arc, base_path: String) -> Self { Self { - output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "output_to_respent_addr_count".to_string()), - output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7::new(client.clone(), "output_to_respent_addr_share".to_string()), + output_to_reused_addr_count: SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount::new(client.clone(), format!("{base_path}_output_to_reused_addr_count")), + output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "output_to_respent_addr_share".to_string()), spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern::new(client.clone(), "spendable_output_to_respent_addr_share".to_string()), - input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "input_from_respent_addr_count".to_string()), - input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7::new(client.clone(), "input_from_respent_addr_share".to_string()), + input_from_reused_addr_count: SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount::new(client.clone(), format!("{base_path}_input_from_reused_addr_count")), + input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6::new(client.clone(), "input_from_respent_addr_share".to_string()), active_reused_addr_count: _1m1w1y24hBlockPattern::new(client.clone(), "active_respent_addr_count".to_string()), active_reused_addr_share: _1m1w1y24hBlockPattern2::new(client.clone(), "active_respent_addr_share".to_string()), } } } +/// Series tree node. +pub struct SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount { + pub all: AverageBlockCumulativeSumPattern, + pub p2pk65: AverageBlockCumulativeSumPattern, + pub p2pk33: AverageBlockCumulativeSumPattern, + pub p2pkh: AverageBlockCumulativeSumPattern, + pub p2sh: AverageBlockCumulativeSumPattern, + pub p2wpkh: AverageBlockCumulativeSumPattern, + pub p2wsh: AverageBlockCumulativeSumPattern, + pub p2tr: AverageBlockCumulativeSumPattern, + pub p2a: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: AverageBlockCumulativeSumPattern::new(client.clone(), "output_to_respent_addr_count".to_string()), + p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk65_output_to_respent_addr_count".to_string()), + p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk33_output_to_respent_addr_count".to_string()), + p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pkh_output_to_respent_addr_count".to_string()), + p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2sh_output_to_respent_addr_count".to_string()), + p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wpkh_output_to_respent_addr_count".to_string()), + p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wsh_output_to_respent_addr_count".to_string()), + p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), "p2tr_output_to_respent_addr_count".to_string()), + p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "p2a_output_to_respent_addr_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "output_to_respent_addr_count_by_type_cumulative".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount { + pub all: AverageBlockCumulativeSumPattern, + pub p2pk65: AverageBlockCumulativeSumPattern, + pub p2pk33: AverageBlockCumulativeSumPattern, + pub p2pkh: AverageBlockCumulativeSumPattern, + pub p2sh: AverageBlockCumulativeSumPattern, + pub p2wpkh: AverageBlockCumulativeSumPattern, + pub p2wsh: AverageBlockCumulativeSumPattern, + pub p2tr: AverageBlockCumulativeSumPattern, + pub p2a: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: AverageBlockCumulativeSumPattern::new(client.clone(), "input_from_respent_addr_count".to_string()), + p2pk65: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk65_input_from_respent_addr_count".to_string()), + p2pk33: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pk33_input_from_respent_addr_count".to_string()), + p2pkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2pkh_input_from_respent_addr_count".to_string()), + p2sh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2sh_input_from_respent_addr_count".to_string()), + p2wpkh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wpkh_input_from_respent_addr_count".to_string()), + p2wsh: AverageBlockCumulativeSumPattern::new(client.clone(), "p2wsh_input_from_respent_addr_count".to_string()), + p2tr: AverageBlockCumulativeSumPattern::new(client.clone(), "p2tr_input_from_respent_addr_count".to_string()), + p2a: AverageBlockCumulativeSumPattern::new(client.clone(), "p2a_input_from_respent_addr_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "input_from_respent_addr_count_by_type_cumulative".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Respent_Supply { pub all: BtcCentsSatsUsdPattern, @@ -5434,7 +5922,8 @@ pub struct SeriesTree_Addrs_Respent_Supply { pub p2wsh: BtcCentsSatsUsdPattern, pub p2tr: BtcCentsSatsUsdPattern, pub p2a: BtcCentsSatsUsdPattern, - pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5, + pub height: SeriesPattern18<[Sats; 8]>, + pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, } impl SeriesTree_Addrs_Respent_Supply { @@ -5449,26 +5938,104 @@ impl SeriesTree_Addrs_Respent_Supply { p2wsh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wsh_respent_addr_supply".to_string()), p2tr: BtcCentsSatsUsdPattern::new(client.clone(), "p2tr_respent_addr_supply".to_string()), p2a: BtcCentsSatsUsdPattern::new(client.clone(), "p2a_respent_addr_supply".to_string()), - share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5::new(client.clone(), "respent_addr_supply_share".to_string()), + height: SeriesPattern18::new(client.clone(), "respent_addr_supply_sats_by_type".to_string()), + share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), "respent_addr_supply_share".to_string()), } } } /// Series tree node. pub struct SeriesTree_Addrs_Exposed { - pub count: FundedTotalPattern, + pub count: SeriesTree_Addrs_Exposed_Count, pub supply: SeriesTree_Addrs_Exposed_Supply, } impl SeriesTree_Addrs_Exposed { pub fn new(client: Arc, base_path: String) -> Self { Self { - count: FundedTotalPattern::new(client.clone(), "exposed_addr_count".to_string()), + count: SeriesTree_Addrs_Exposed_Count::new(client.clone(), format!("{base_path}_count")), supply: SeriesTree_Addrs_Exposed_Supply::new(client.clone(), format!("{base_path}_supply")), } } } +/// Series tree node. +pub struct SeriesTree_Addrs_Exposed_Count { + pub funded: SeriesTree_Addrs_Exposed_Count_Funded, + pub total: SeriesTree_Addrs_Exposed_Count_Total, +} + +impl SeriesTree_Addrs_Exposed_Count { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + funded: SeriesTree_Addrs_Exposed_Count_Funded::new(client.clone(), format!("{base_path}_funded")), + total: SeriesTree_Addrs_Exposed_Count_Total::new(client.clone(), format!("{base_path}_total")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Exposed_Count_Funded { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Exposed_Count_Funded { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "exposed_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_exposed_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_exposed_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_exposed_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_exposed_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_exposed_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_exposed_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_exposed_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_exposed_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "exposed_addr_count_by_type".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Exposed_Count_Total { + pub all: SeriesPattern1, + pub p2pk65: SeriesPattern1, + pub p2pk33: SeriesPattern1, + pub p2pkh: SeriesPattern1, + pub p2sh: SeriesPattern1, + pub p2wpkh: SeriesPattern1, + pub p2wsh: SeriesPattern1, + pub p2tr: SeriesPattern1, + pub p2a: SeriesPattern1, + pub height: SeriesPattern18<[StoredU64; 8]>, +} + +impl SeriesTree_Addrs_Exposed_Count_Total { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: SeriesPattern1::new(client.clone(), "total_exposed_addr_count".to_string()), + p2pk65: SeriesPattern1::new(client.clone(), "p2pk65_total_exposed_addr_count".to_string()), + p2pk33: SeriesPattern1::new(client.clone(), "p2pk33_total_exposed_addr_count".to_string()), + p2pkh: SeriesPattern1::new(client.clone(), "p2pkh_total_exposed_addr_count".to_string()), + p2sh: SeriesPattern1::new(client.clone(), "p2sh_total_exposed_addr_count".to_string()), + p2wpkh: SeriesPattern1::new(client.clone(), "p2wpkh_total_exposed_addr_count".to_string()), + p2wsh: SeriesPattern1::new(client.clone(), "p2wsh_total_exposed_addr_count".to_string()), + p2tr: SeriesPattern1::new(client.clone(), "p2tr_total_exposed_addr_count".to_string()), + p2a: SeriesPattern1::new(client.clone(), "p2a_total_exposed_addr_count".to_string()), + height: SeriesPattern18::new(client.clone(), "total_exposed_addr_count_by_type".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Addrs_Exposed_Supply { pub all: BtcCentsSatsUsdPattern, @@ -5480,7 +6047,8 @@ pub struct SeriesTree_Addrs_Exposed_Supply { pub p2wsh: BtcCentsSatsUsdPattern, pub p2tr: BtcCentsSatsUsdPattern, pub p2a: BtcCentsSatsUsdPattern, - pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5, + pub height: SeriesPattern18<[Sats; 8]>, + pub share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4, } impl SeriesTree_Addrs_Exposed_Supply { @@ -5495,7 +6063,8 @@ impl SeriesTree_Addrs_Exposed_Supply { p2wsh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wsh_exposed_addr_supply".to_string()), p2tr: BtcCentsSatsUsdPattern::new(client.clone(), "p2tr_exposed_addr_supply".to_string()), p2a: BtcCentsSatsUsdPattern::new(client.clone(), "p2a_exposed_addr_supply".to_string()), - share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5::new(client.clone(), "exposed_addr_supply_share".to_string()), + height: SeriesPattern18::new(client.clone(), "exposed_addr_supply_sats_by_type".to_string()), + share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4::new(client.clone(), "exposed_addr_supply_share".to_string()), } } } @@ -5677,7 +6246,7 @@ impl SeriesTree_OpReturn_Raw { /// Series tree node. pub struct SeriesTree_OpReturn_Total { - pub data_bytes: AverageBlockCumulativeSumPattern, + pub data_bytes: AverageBlockCumulativeSumPattern, pub tx_count: AverageBlockCumulativeSumPattern, pub tx_vsize: AverageBlockCumulativeSumPattern, pub fees: AverageBlockCumulativeSumPattern, @@ -5700,76 +6269,442 @@ impl SeriesTree_OpReturn_Total { /// Series tree node. pub struct SeriesTree_OpReturn_ByKind { - pub runes: ChainDataFeeFeesOutputTxPattern, - pub veri_block: ChainDataFeeFeesOutputTxPattern, - pub omni: ChainDataFeeFeesOutputTxPattern, - pub stacks: ChainDataFeeFeesOutputTxPattern, - pub blockstack: ChainDataFeeFeesOutputTxPattern, - pub colu: ChainDataFeeFeesOutputTxPattern, - pub open_assets: ChainDataFeeFeesOutputTxPattern, - pub komodo: ChainDataFeeFeesOutputTxPattern, - pub coin_spark: ChainDataFeeFeesOutputTxPattern, - pub poet: ChainDataFeeFeesOutputTxPattern, - pub docproof: ChainDataFeeFeesOutputTxPattern, - pub open_timestamps: ChainDataFeeFeesOutputTxPattern, - pub factom: ChainDataFeeFeesOutputTxPattern, - pub eternity_wall: ChainDataFeeFeesOutputTxPattern, - pub memo: ChainDataFeeFeesOutputTxPattern, - pub bitproof: ChainDataFeeFeesOutputTxPattern, - pub ascribe: ChainDataFeeFeesOutputTxPattern, - pub stampery: ChainDataFeeFeesOutputTxPattern, - pub epobc: ChainDataFeeFeesOutputTxPattern, - pub bare_hash: ChainDataFeeFeesOutputTxPattern, - pub text: ChainDataFeeFeesOutputTxPattern, - pub empty: ChainDataFeeFeesOutputTxPattern, - pub unknown: ChainDataFeeFeesOutputTxPattern, + pub output_count: SeriesTree_OpReturn_ByKind_OutputCount, + pub data_bytes: SeriesTree_OpReturn_ByKind_DataBytes, + pub tx_count: SeriesTree_OpReturn_ByKind_TxCount, + pub tx_vsize: SeriesTree_OpReturn_ByKind_TxVsize, + pub fees: SeriesTree_OpReturn_ByKind_Fees, } impl SeriesTree_OpReturn_ByKind { pub fn new(client: Arc, base_path: String) -> Self { Self { - runes: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_runes".to_string()), - veri_block: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_veri_block".to_string()), - omni: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_omni".to_string()), - stacks: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_stacks".to_string()), - blockstack: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_blockstack".to_string()), - colu: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_colu".to_string()), - open_assets: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_open_assets".to_string()), - komodo: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_komodo".to_string()), - coin_spark: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_coin_spark".to_string()), - poet: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_poet".to_string()), - docproof: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_docproof".to_string()), - open_timestamps: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_open_timestamps".to_string()), - factom: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_factom".to_string()), - eternity_wall: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_eternity_wall".to_string()), - memo: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_memo".to_string()), - bitproof: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_bitproof".to_string()), - ascribe: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_ascribe".to_string()), - stampery: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_stampery".to_string()), - epobc: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_epobc".to_string()), - bare_hash: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_bare_hash".to_string()), - text: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_text".to_string()), - empty: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_empty".to_string()), - unknown: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_unknown".to_string()), + output_count: SeriesTree_OpReturn_ByKind_OutputCount::new(client.clone(), format!("{base_path}_output_count")), + data_bytes: SeriesTree_OpReturn_ByKind_DataBytes::new(client.clone(), format!("{base_path}_data_bytes")), + tx_count: SeriesTree_OpReturn_ByKind_TxCount::new(client.clone(), format!("{base_path}_tx_count")), + tx_vsize: SeriesTree_OpReturn_ByKind_TxVsize::new(client.clone(), format!("{base_path}_tx_vsize")), + fees: SeriesTree_OpReturn_ByKind_Fees::new(client.clone(), format!("{base_path}_fees")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_ByKind_OutputCount { + pub runes: AverageBlockCumulativeSumPattern, + pub veri_block: AverageBlockCumulativeSumPattern, + pub omni: AverageBlockCumulativeSumPattern, + pub stacks: AverageBlockCumulativeSumPattern, + pub blockstack: AverageBlockCumulativeSumPattern, + pub colu: AverageBlockCumulativeSumPattern, + pub open_assets: AverageBlockCumulativeSumPattern, + pub komodo: AverageBlockCumulativeSumPattern, + pub coin_spark: AverageBlockCumulativeSumPattern, + pub poet: AverageBlockCumulativeSumPattern, + pub docproof: AverageBlockCumulativeSumPattern, + pub open_timestamps: AverageBlockCumulativeSumPattern, + pub factom: AverageBlockCumulativeSumPattern, + pub eternity_wall: AverageBlockCumulativeSumPattern, + pub memo: AverageBlockCumulativeSumPattern, + pub bitproof: AverageBlockCumulativeSumPattern, + pub ascribe: AverageBlockCumulativeSumPattern, + pub stampery: AverageBlockCumulativeSumPattern, + pub epobc: AverageBlockCumulativeSumPattern, + pub bare_hash: AverageBlockCumulativeSumPattern, + pub text: AverageBlockCumulativeSumPattern, + pub empty: AverageBlockCumulativeSumPattern, + pub unknown: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 23]>, +} + +impl SeriesTree_OpReturn_ByKind_OutputCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + runes: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_runes_output_count".to_string()), + veri_block: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_veri_block_output_count".to_string()), + omni: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_omni_output_count".to_string()), + stacks: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_stacks_output_count".to_string()), + blockstack: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_blockstack_output_count".to_string()), + colu: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_colu_output_count".to_string()), + open_assets: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_open_assets_output_count".to_string()), + komodo: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_komodo_output_count".to_string()), + coin_spark: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_coin_spark_output_count".to_string()), + poet: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_poet_output_count".to_string()), + docproof: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_docproof_output_count".to_string()), + open_timestamps: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_open_timestamps_output_count".to_string()), + factom: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_factom_output_count".to_string()), + eternity_wall: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_eternity_wall_output_count".to_string()), + memo: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_memo_output_count".to_string()), + bitproof: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_bitproof_output_count".to_string()), + ascribe: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_ascribe_output_count".to_string()), + stampery: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_stampery_output_count".to_string()), + epobc: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_epobc_output_count".to_string()), + bare_hash: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_bare_hash_output_count".to_string()), + text: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_text_output_count".to_string()), + empty: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_empty_output_count".to_string()), + unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_unknown_output_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_by_kind_output_count".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_ByKind_DataBytes { + pub runes: AverageBlockChainCumulativeDataSumPattern, + pub veri_block: AverageBlockChainCumulativeDataSumPattern, + pub omni: AverageBlockChainCumulativeDataSumPattern, + pub stacks: AverageBlockChainCumulativeDataSumPattern, + pub blockstack: AverageBlockChainCumulativeDataSumPattern, + pub colu: AverageBlockChainCumulativeDataSumPattern, + pub open_assets: AverageBlockChainCumulativeDataSumPattern, + pub komodo: AverageBlockChainCumulativeDataSumPattern, + pub coin_spark: AverageBlockChainCumulativeDataSumPattern, + pub poet: AverageBlockChainCumulativeDataSumPattern, + pub docproof: AverageBlockChainCumulativeDataSumPattern, + pub open_timestamps: AverageBlockChainCumulativeDataSumPattern, + pub factom: AverageBlockChainCumulativeDataSumPattern, + pub eternity_wall: AverageBlockChainCumulativeDataSumPattern, + pub memo: AverageBlockChainCumulativeDataSumPattern, + pub bitproof: AverageBlockChainCumulativeDataSumPattern, + pub ascribe: AverageBlockChainCumulativeDataSumPattern, + pub stampery: AverageBlockChainCumulativeDataSumPattern, + pub epobc: AverageBlockChainCumulativeDataSumPattern, + pub bare_hash: AverageBlockChainCumulativeDataSumPattern, + pub text: AverageBlockChainCumulativeDataSumPattern, + pub empty: AverageBlockChainCumulativeDataSumPattern, + pub unknown: AverageBlockChainCumulativeDataSumPattern, + pub cumulative: SeriesPattern18<[Bytes; 23]>, +} + +impl SeriesTree_OpReturn_ByKind_DataBytes { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + runes: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_runes".to_string()), + veri_block: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_veri_block".to_string()), + omni: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_omni".to_string()), + stacks: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_stacks".to_string()), + blockstack: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_blockstack".to_string()), + colu: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_colu".to_string()), + open_assets: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_open_assets".to_string()), + komodo: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_komodo".to_string()), + coin_spark: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_coin_spark".to_string()), + poet: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_poet".to_string()), + docproof: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_docproof".to_string()), + open_timestamps: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_open_timestamps".to_string()), + factom: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_factom".to_string()), + eternity_wall: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_eternity_wall".to_string()), + memo: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_memo".to_string()), + bitproof: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_bitproof".to_string()), + ascribe: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_ascribe".to_string()), + stampery: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_stampery".to_string()), + epobc: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_epobc".to_string()), + bare_hash: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_bare_hash".to_string()), + text: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_text".to_string()), + empty: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_empty".to_string()), + unknown: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_unknown".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_by_kind_data_bytes".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_ByKind_TxCount { + pub runes: AverageBlockCumulativeSumPattern, + pub veri_block: AverageBlockCumulativeSumPattern, + pub omni: AverageBlockCumulativeSumPattern, + pub stacks: AverageBlockCumulativeSumPattern, + pub blockstack: AverageBlockCumulativeSumPattern, + pub colu: AverageBlockCumulativeSumPattern, + pub open_assets: AverageBlockCumulativeSumPattern, + pub komodo: AverageBlockCumulativeSumPattern, + pub coin_spark: AverageBlockCumulativeSumPattern, + pub poet: AverageBlockCumulativeSumPattern, + pub docproof: AverageBlockCumulativeSumPattern, + pub open_timestamps: AverageBlockCumulativeSumPattern, + pub factom: AverageBlockCumulativeSumPattern, + pub eternity_wall: AverageBlockCumulativeSumPattern, + pub memo: AverageBlockCumulativeSumPattern, + pub bitproof: AverageBlockCumulativeSumPattern, + pub ascribe: AverageBlockCumulativeSumPattern, + pub stampery: AverageBlockCumulativeSumPattern, + pub epobc: AverageBlockCumulativeSumPattern, + pub bare_hash: AverageBlockCumulativeSumPattern, + pub text: AverageBlockCumulativeSumPattern, + pub empty: AverageBlockCumulativeSumPattern, + pub unknown: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 23]>, +} + +impl SeriesTree_OpReturn_ByKind_TxCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + runes: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_runes_tx_count".to_string()), + veri_block: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_veri_block_tx_count".to_string()), + omni: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_omni_tx_count".to_string()), + stacks: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_stacks_tx_count".to_string()), + blockstack: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_blockstack_tx_count".to_string()), + colu: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_colu_tx_count".to_string()), + open_assets: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_open_assets_tx_count".to_string()), + komodo: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_komodo_tx_count".to_string()), + coin_spark: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_coin_spark_tx_count".to_string()), + poet: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_poet_tx_count".to_string()), + docproof: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_docproof_tx_count".to_string()), + open_timestamps: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_open_timestamps_tx_count".to_string()), + factom: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_factom_tx_count".to_string()), + eternity_wall: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_eternity_wall_tx_count".to_string()), + memo: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_memo_tx_count".to_string()), + bitproof: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_bitproof_tx_count".to_string()), + ascribe: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_ascribe_tx_count".to_string()), + stampery: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_stampery_tx_count".to_string()), + epobc: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_epobc_tx_count".to_string()), + bare_hash: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_bare_hash_tx_count".to_string()), + text: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_text_tx_count".to_string()), + empty: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_empty_tx_count".to_string()), + unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_unknown_tx_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_by_kind_tx_count".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_ByKind_TxVsize { + pub runes: AverageBlockCumulativeSumPattern, + pub veri_block: AverageBlockCumulativeSumPattern, + pub omni: AverageBlockCumulativeSumPattern, + pub stacks: AverageBlockCumulativeSumPattern, + pub blockstack: AverageBlockCumulativeSumPattern, + pub colu: AverageBlockCumulativeSumPattern, + pub open_assets: AverageBlockCumulativeSumPattern, + pub komodo: AverageBlockCumulativeSumPattern, + pub coin_spark: AverageBlockCumulativeSumPattern, + pub poet: AverageBlockCumulativeSumPattern, + pub docproof: AverageBlockCumulativeSumPattern, + pub open_timestamps: AverageBlockCumulativeSumPattern, + pub factom: AverageBlockCumulativeSumPattern, + pub eternity_wall: AverageBlockCumulativeSumPattern, + pub memo: AverageBlockCumulativeSumPattern, + pub bitproof: AverageBlockCumulativeSumPattern, + pub ascribe: AverageBlockCumulativeSumPattern, + pub stampery: AverageBlockCumulativeSumPattern, + pub epobc: AverageBlockCumulativeSumPattern, + pub bare_hash: AverageBlockCumulativeSumPattern, + pub text: AverageBlockCumulativeSumPattern, + pub empty: AverageBlockCumulativeSumPattern, + pub unknown: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[VSize; 23]>, +} + +impl SeriesTree_OpReturn_ByKind_TxVsize { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + runes: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_runes_tx_vsize".to_string()), + veri_block: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_veri_block_tx_vsize".to_string()), + omni: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_omni_tx_vsize".to_string()), + stacks: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_stacks_tx_vsize".to_string()), + blockstack: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_blockstack_tx_vsize".to_string()), + colu: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_colu_tx_vsize".to_string()), + open_assets: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_open_assets_tx_vsize".to_string()), + komodo: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_komodo_tx_vsize".to_string()), + coin_spark: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_coin_spark_tx_vsize".to_string()), + poet: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_poet_tx_vsize".to_string()), + docproof: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_docproof_tx_vsize".to_string()), + open_timestamps: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_open_timestamps_tx_vsize".to_string()), + factom: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_factom_tx_vsize".to_string()), + eternity_wall: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_eternity_wall_tx_vsize".to_string()), + memo: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_memo_tx_vsize".to_string()), + bitproof: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_bitproof_tx_vsize".to_string()), + ascribe: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_ascribe_tx_vsize".to_string()), + stampery: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_stampery_tx_vsize".to_string()), + epobc: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_epobc_tx_vsize".to_string()), + bare_hash: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_bare_hash_tx_vsize".to_string()), + text: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_text_tx_vsize".to_string()), + empty: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_empty_tx_vsize".to_string()), + unknown: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_unknown_tx_vsize".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_by_kind_tx_vsize".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_ByKind_Fees { + pub runes: AverageBlockCumulativeFeeSumPattern, + pub veri_block: AverageBlockCumulativeFeeSumPattern, + pub omni: AverageBlockCumulativeFeeSumPattern, + pub stacks: AverageBlockCumulativeFeeSumPattern, + pub blockstack: AverageBlockCumulativeFeeSumPattern, + pub colu: AverageBlockCumulativeFeeSumPattern, + pub open_assets: AverageBlockCumulativeFeeSumPattern, + pub komodo: AverageBlockCumulativeFeeSumPattern, + pub coin_spark: AverageBlockCumulativeFeeSumPattern, + pub poet: AverageBlockCumulativeFeeSumPattern, + pub docproof: AverageBlockCumulativeFeeSumPattern, + pub open_timestamps: AverageBlockCumulativeFeeSumPattern, + pub factom: AverageBlockCumulativeFeeSumPattern, + pub eternity_wall: AverageBlockCumulativeFeeSumPattern, + pub memo: AverageBlockCumulativeFeeSumPattern, + pub bitproof: AverageBlockCumulativeFeeSumPattern, + pub ascribe: AverageBlockCumulativeFeeSumPattern, + pub stampery: AverageBlockCumulativeFeeSumPattern, + pub epobc: AverageBlockCumulativeFeeSumPattern, + pub bare_hash: AverageBlockCumulativeFeeSumPattern, + pub text: AverageBlockCumulativeFeeSumPattern, + pub empty: AverageBlockCumulativeFeeSumPattern, + pub unknown: AverageBlockCumulativeFeeSumPattern, + pub cumulative: SeriesPattern18<[Sats; 23]>, +} + +impl SeriesTree_OpReturn_ByKind_Fees { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + runes: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_runes".to_string()), + veri_block: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_veri_block".to_string()), + omni: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_omni".to_string()), + stacks: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_stacks".to_string()), + blockstack: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_blockstack".to_string()), + colu: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_colu".to_string()), + open_assets: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_open_assets".to_string()), + komodo: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_komodo".to_string()), + coin_spark: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_coin_spark".to_string()), + poet: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_poet".to_string()), + docproof: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_docproof".to_string()), + open_timestamps: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_open_timestamps".to_string()), + factom: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_factom".to_string()), + eternity_wall: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_eternity_wall".to_string()), + memo: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_memo".to_string()), + bitproof: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_bitproof".to_string()), + ascribe: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_ascribe".to_string()), + stampery: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_stampery".to_string()), + epobc: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_epobc".to_string()), + bare_hash: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_bare_hash".to_string()), + text: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_text".to_string()), + empty: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_empty".to_string()), + unknown: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_unknown".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_by_kind_fees".to_string()), } } } /// Series tree node. pub struct SeriesTree_OpReturn_Policy { - pub pre_v30_standard: ChainDataFeeFeesOutputTxPattern, - pub pre_v30_nonstandard: ChainDataFeeFeesOutputTxPattern, - pub oversized: ChainDataFeeFeesOutputTxPattern, - pub multiple: ChainDataFeeFeesOutputTxPattern, + pub output_count: SeriesTree_OpReturn_Policy_OutputCount, + pub data_bytes: SeriesTree_OpReturn_Policy_DataBytes, + pub tx_count: SeriesTree_OpReturn_Policy_TxCount, + pub tx_vsize: SeriesTree_OpReturn_Policy_TxVsize, + pub fees: SeriesTree_OpReturn_Policy_Fees, } impl SeriesTree_OpReturn_Policy { pub fn new(client: Arc, base_path: String) -> Self { Self { - pre_v30_standard: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_policy_pre_v30_standard".to_string()), - pre_v30_nonstandard: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard".to_string()), - oversized: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_policy_oversized".to_string()), - multiple: ChainDataFeeFeesOutputTxPattern::new(client.clone(), "op_return_policy_multiple".to_string()), + output_count: SeriesTree_OpReturn_Policy_OutputCount::new(client.clone(), format!("{base_path}_output_count")), + data_bytes: SeriesTree_OpReturn_Policy_DataBytes::new(client.clone(), format!("{base_path}_data_bytes")), + tx_count: SeriesTree_OpReturn_Policy_TxCount::new(client.clone(), format!("{base_path}_tx_count")), + tx_vsize: SeriesTree_OpReturn_Policy_TxVsize::new(client.clone(), format!("{base_path}_tx_vsize")), + fees: SeriesTree_OpReturn_Policy_Fees::new(client.clone(), format!("{base_path}_fees")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_Policy_OutputCount { + pub pre_v30_standard: AverageBlockCumulativeSumPattern, + pub pre_v30_nonstandard: AverageBlockCumulativeSumPattern, + pub oversized: AverageBlockCumulativeSumPattern, + pub multiple: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 4]>, +} + +impl SeriesTree_OpReturn_Policy_OutputCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + pre_v30_standard: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_pre_v30_standard_output_count".to_string()), + pre_v30_nonstandard: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard_output_count".to_string()), + oversized: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_oversized_output_count".to_string()), + multiple: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_multiple_output_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_policy_output_count".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_Policy_DataBytes { + pub pre_v30_standard: AverageBlockChainCumulativeDataSumPattern, + pub pre_v30_nonstandard: AverageBlockChainCumulativeDataSumPattern, + pub oversized: AverageBlockChainCumulativeDataSumPattern, + pub multiple: AverageBlockChainCumulativeDataSumPattern, + pub cumulative: SeriesPattern18<[Bytes; 4]>, +} + +impl SeriesTree_OpReturn_Policy_DataBytes { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + pre_v30_standard: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_policy_pre_v30_standard".to_string()), + pre_v30_nonstandard: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard".to_string()), + oversized: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_policy_oversized".to_string()), + multiple: AverageBlockChainCumulativeDataSumPattern::new(client.clone(), "op_return_policy_multiple".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_policy_data_bytes".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_Policy_TxCount { + pub pre_v30_standard: AverageBlockCumulativeSumPattern, + pub pre_v30_nonstandard: AverageBlockCumulativeSumPattern, + pub oversized: AverageBlockCumulativeSumPattern, + pub multiple: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredU64; 4]>, +} + +impl SeriesTree_OpReturn_Policy_TxCount { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + pre_v30_standard: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_pre_v30_standard_tx_count".to_string()), + pre_v30_nonstandard: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard_tx_count".to_string()), + oversized: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_oversized_tx_count".to_string()), + multiple: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_multiple_tx_count".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_policy_tx_count".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_Policy_TxVsize { + pub pre_v30_standard: AverageBlockCumulativeSumPattern, + pub pre_v30_nonstandard: AverageBlockCumulativeSumPattern, + pub oversized: AverageBlockCumulativeSumPattern, + pub multiple: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[VSize; 4]>, +} + +impl SeriesTree_OpReturn_Policy_TxVsize { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + pre_v30_standard: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_pre_v30_standard_tx_vsize".to_string()), + pre_v30_nonstandard: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard_tx_vsize".to_string()), + oversized: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_oversized_tx_vsize".to_string()), + multiple: AverageBlockCumulativeSumPattern::new(client.clone(), "op_return_policy_multiple_tx_vsize".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_policy_tx_vsize".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_OpReturn_Policy_Fees { + pub pre_v30_standard: AverageBlockCumulativeFeeSumPattern, + pub pre_v30_nonstandard: AverageBlockCumulativeFeeSumPattern, + pub oversized: AverageBlockCumulativeFeeSumPattern, + pub multiple: AverageBlockCumulativeFeeSumPattern, + pub cumulative: SeriesPattern18<[Sats; 4]>, +} + +impl SeriesTree_OpReturn_Policy_Fees { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + pre_v30_standard: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_policy_pre_v30_standard".to_string()), + pre_v30_nonstandard: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_policy_pre_v30_nonstandard".to_string()), + oversized: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_policy_oversized".to_string()), + multiple: AverageBlockCumulativeFeeSumPattern::new(client.clone(), "op_return_policy_multiple".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "op_return_cumulative_policy_fees".to_string()), } } } @@ -6015,57 +6950,521 @@ impl SeriesTree_Frameworks_Cointime_Activity { /// Series tree node. pub struct SeriesTree_Frameworks_Cointime_AgeRange { - pub under_1h: CoindaysDormancySupplyWakefulnessPattern, - pub _1h_to_1d: CoindaysDormancySupplyWakefulnessPattern, - pub _1d_to_1w: CoindaysDormancySupplyWakefulnessPattern, - pub _1w_to_1m: CoindaysDormancySupplyWakefulnessPattern, - pub _1m_to_2m: CoindaysDormancySupplyWakefulnessPattern, - pub _2m_to_3m: CoindaysDormancySupplyWakefulnessPattern, - pub _3m_to_4m: CoindaysDormancySupplyWakefulnessPattern, - pub _4m_to_5m: CoindaysDormancySupplyWakefulnessPattern, - pub _5m_to_6m: CoindaysDormancySupplyWakefulnessPattern, - pub _6m_to_9m: CoindaysDormancySupplyWakefulnessPattern, - pub _9m_to_1y: CoindaysDormancySupplyWakefulnessPattern, - pub _1y_to_18m: CoindaysDormancySupplyWakefulnessPattern, - pub _18m_to_2y: CoindaysDormancySupplyWakefulnessPattern, - pub _2y_to_3y: CoindaysDormancySupplyWakefulnessPattern, - pub _3y_to_4y: CoindaysDormancySupplyWakefulnessPattern, - pub _4y_to_5y: CoindaysDormancySupplyWakefulnessPattern, - pub _5y_to_6y: CoindaysDormancySupplyWakefulnessPattern, - pub _6y_to_7y: CoindaysDormancySupplyWakefulnessPattern, - pub _7y_to_8y: CoindaysDormancySupplyWakefulnessPattern, - pub _8y_to_10y: CoindaysDormancySupplyWakefulnessPattern, - pub _10y_to_12y: CoindaysDormancySupplyWakefulnessPattern, - pub _12y_to_15y: CoindaysDormancySupplyWakefulnessPattern, - pub over_15y: CoindaysDormancySupplyWakefulnessPattern, + pub coindays_created: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated, + pub coindays_consumed: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed, + pub coindays_stored: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored, + pub activity: SeriesTree_Frameworks_Cointime_AgeRange_Activity, + pub supply: SeriesTree_Frameworks_Cointime_AgeRange_Supply, } impl SeriesTree_Frameworks_Cointime_AgeRange { pub fn new(client: Arc, base_path: String) -> Self { Self { - under_1h: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_under_1h_old".to_string()), - _1h_to_1d: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_1h_to_1d_old".to_string()), - _1d_to_1w: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_1d_to_1w_old".to_string()), - _1w_to_1m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_1w_to_1m_old".to_string()), - _1m_to_2m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_1m_to_2m_old".to_string()), - _2m_to_3m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_2m_to_3m_old".to_string()), - _3m_to_4m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_3m_to_4m_old".to_string()), - _4m_to_5m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_4m_to_5m_old".to_string()), - _5m_to_6m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_5m_to_6m_old".to_string()), - _6m_to_9m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_6m_to_9m_old".to_string()), - _9m_to_1y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_9m_to_1y_old".to_string()), - _1y_to_18m: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_1y_to_18m_old".to_string()), - _18m_to_2y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_18m_to_2y_old".to_string()), - _2y_to_3y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_2y_to_3y_old".to_string()), - _3y_to_4y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_3y_to_4y_old".to_string()), - _4y_to_5y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_4y_to_5y_old".to_string()), - _5y_to_6y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_5y_to_6y_old".to_string()), - _6y_to_7y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_6y_to_7y_old".to_string()), - _7y_to_8y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_7y_to_8y_old".to_string()), - _8y_to_10y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_8y_to_10y_old".to_string()), - _10y_to_12y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_10y_to_12y_old".to_string()), - _12y_to_15y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_12y_to_15y_old".to_string()), - over_15y: CoindaysDormancySupplyWakefulnessPattern::new(client.clone(), "utxos_over_15y_old".to_string()), + coindays_created: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated::new(client.clone(), format!("{base_path}_coindays_created")), + coindays_consumed: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed::new(client.clone(), format!("{base_path}_coindays_consumed")), + coindays_stored: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored::new(client.clone(), format!("{base_path}_coindays_stored")), + activity: SeriesTree_Frameworks_Cointime_AgeRange_Activity::new(client.clone(), format!("{base_path}_activity")), + supply: SeriesTree_Frameworks_Cointime_AgeRange_Supply::new(client.clone(), format!("{base_path}_supply")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated { + pub under_1h: AverageBlockCumulativeSumPattern, + pub _1h_to_1d: AverageBlockCumulativeSumPattern, + pub _1d_to_1w: AverageBlockCumulativeSumPattern, + pub _1w_to_1m: AverageBlockCumulativeSumPattern, + pub _1m_to_2m: AverageBlockCumulativeSumPattern, + pub _2m_to_3m: AverageBlockCumulativeSumPattern, + pub _3m_to_4m: AverageBlockCumulativeSumPattern, + pub _4m_to_5m: AverageBlockCumulativeSumPattern, + pub _5m_to_6m: AverageBlockCumulativeSumPattern, + pub _6m_to_9m: AverageBlockCumulativeSumPattern, + pub _9m_to_1y: AverageBlockCumulativeSumPattern, + pub _1y_to_18m: AverageBlockCumulativeSumPattern, + pub _18m_to_2y: AverageBlockCumulativeSumPattern, + pub _2y_to_3y: AverageBlockCumulativeSumPattern, + pub _3y_to_4y: AverageBlockCumulativeSumPattern, + pub _4y_to_5y: AverageBlockCumulativeSumPattern, + pub _5y_to_6y: AverageBlockCumulativeSumPattern, + pub _6y_to_7y: AverageBlockCumulativeSumPattern, + pub _7y_to_8y: AverageBlockCumulativeSumPattern, + pub _8y_to_10y: AverageBlockCumulativeSumPattern, + pub _10y_to_12y: AverageBlockCumulativeSumPattern, + pub _12y_to_15y: AverageBlockCumulativeSumPattern, + pub over_15y: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredF64; 23]>, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_under_1h_old_coindays_created".to_string()), + _1h_to_1d: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1h_to_1d_old_coindays_created".to_string()), + _1d_to_1w: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1d_to_1w_old_coindays_created".to_string()), + _1w_to_1m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1w_to_1m_old_coindays_created".to_string()), + _1m_to_2m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1m_to_2m_old_coindays_created".to_string()), + _2m_to_3m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_2m_to_3m_old_coindays_created".to_string()), + _3m_to_4m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_3m_to_4m_old_coindays_created".to_string()), + _4m_to_5m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_4m_to_5m_old_coindays_created".to_string()), + _5m_to_6m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_5m_to_6m_old_coindays_created".to_string()), + _6m_to_9m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_6m_to_9m_old_coindays_created".to_string()), + _9m_to_1y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_9m_to_1y_old_coindays_created".to_string()), + _1y_to_18m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1y_to_18m_old_coindays_created".to_string()), + _18m_to_2y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_18m_to_2y_old_coindays_created".to_string()), + _2y_to_3y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_2y_to_3y_old_coindays_created".to_string()), + _3y_to_4y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_3y_to_4y_old_coindays_created".to_string()), + _4y_to_5y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_4y_to_5y_old_coindays_created".to_string()), + _5y_to_6y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_5y_to_6y_old_coindays_created".to_string()), + _6y_to_7y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_6y_to_7y_old_coindays_created".to_string()), + _7y_to_8y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_7y_to_8y_old_coindays_created".to_string()), + _8y_to_10y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_8y_to_10y_old_coindays_created".to_string()), + _10y_to_12y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_10y_to_12y_old_coindays_created".to_string()), + _12y_to_15y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_12y_to_15y_old_coindays_created".to_string()), + over_15y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_over_15y_old_coindays_created".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "utxos_age_range_coindays_created_cumulative".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed { + pub under_1h: AverageBlockCumulativeSumPattern, + pub _1h_to_1d: AverageBlockCumulativeSumPattern, + pub _1d_to_1w: AverageBlockCumulativeSumPattern, + pub _1w_to_1m: AverageBlockCumulativeSumPattern, + pub _1m_to_2m: AverageBlockCumulativeSumPattern, + pub _2m_to_3m: AverageBlockCumulativeSumPattern, + pub _3m_to_4m: AverageBlockCumulativeSumPattern, + pub _4m_to_5m: AverageBlockCumulativeSumPattern, + pub _5m_to_6m: AverageBlockCumulativeSumPattern, + pub _6m_to_9m: AverageBlockCumulativeSumPattern, + pub _9m_to_1y: AverageBlockCumulativeSumPattern, + pub _1y_to_18m: AverageBlockCumulativeSumPattern, + pub _18m_to_2y: AverageBlockCumulativeSumPattern, + pub _2y_to_3y: AverageBlockCumulativeSumPattern, + pub _3y_to_4y: AverageBlockCumulativeSumPattern, + pub _4y_to_5y: AverageBlockCumulativeSumPattern, + pub _5y_to_6y: AverageBlockCumulativeSumPattern, + pub _6y_to_7y: AverageBlockCumulativeSumPattern, + pub _7y_to_8y: AverageBlockCumulativeSumPattern, + pub _8y_to_10y: AverageBlockCumulativeSumPattern, + pub _10y_to_12y: AverageBlockCumulativeSumPattern, + pub _12y_to_15y: AverageBlockCumulativeSumPattern, + pub over_15y: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredF64; 23]>, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_under_1h_old_coindays_consumed".to_string()), + _1h_to_1d: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1h_to_1d_old_coindays_consumed".to_string()), + _1d_to_1w: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1d_to_1w_old_coindays_consumed".to_string()), + _1w_to_1m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1w_to_1m_old_coindays_consumed".to_string()), + _1m_to_2m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1m_to_2m_old_coindays_consumed".to_string()), + _2m_to_3m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_2m_to_3m_old_coindays_consumed".to_string()), + _3m_to_4m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_3m_to_4m_old_coindays_consumed".to_string()), + _4m_to_5m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_4m_to_5m_old_coindays_consumed".to_string()), + _5m_to_6m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_5m_to_6m_old_coindays_consumed".to_string()), + _6m_to_9m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_6m_to_9m_old_coindays_consumed".to_string()), + _9m_to_1y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_9m_to_1y_old_coindays_consumed".to_string()), + _1y_to_18m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1y_to_18m_old_coindays_consumed".to_string()), + _18m_to_2y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_18m_to_2y_old_coindays_consumed".to_string()), + _2y_to_3y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_2y_to_3y_old_coindays_consumed".to_string()), + _3y_to_4y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_3y_to_4y_old_coindays_consumed".to_string()), + _4y_to_5y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_4y_to_5y_old_coindays_consumed".to_string()), + _5y_to_6y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_5y_to_6y_old_coindays_consumed".to_string()), + _6y_to_7y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_6y_to_7y_old_coindays_consumed".to_string()), + _7y_to_8y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_7y_to_8y_old_coindays_consumed".to_string()), + _8y_to_10y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_8y_to_10y_old_coindays_consumed".to_string()), + _10y_to_12y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_10y_to_12y_old_coindays_consumed".to_string()), + _12y_to_15y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_12y_to_15y_old_coindays_consumed".to_string()), + over_15y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_over_15y_old_coindays_consumed".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "utxos_age_range_coindays_consumed_cumulative".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored { + pub under_1h: AverageBlockCumulativeSumPattern, + pub _1h_to_1d: AverageBlockCumulativeSumPattern, + pub _1d_to_1w: AverageBlockCumulativeSumPattern, + pub _1w_to_1m: AverageBlockCumulativeSumPattern, + pub _1m_to_2m: AverageBlockCumulativeSumPattern, + pub _2m_to_3m: AverageBlockCumulativeSumPattern, + pub _3m_to_4m: AverageBlockCumulativeSumPattern, + pub _4m_to_5m: AverageBlockCumulativeSumPattern, + pub _5m_to_6m: AverageBlockCumulativeSumPattern, + pub _6m_to_9m: AverageBlockCumulativeSumPattern, + pub _9m_to_1y: AverageBlockCumulativeSumPattern, + pub _1y_to_18m: AverageBlockCumulativeSumPattern, + pub _18m_to_2y: AverageBlockCumulativeSumPattern, + pub _2y_to_3y: AverageBlockCumulativeSumPattern, + pub _3y_to_4y: AverageBlockCumulativeSumPattern, + pub _4y_to_5y: AverageBlockCumulativeSumPattern, + pub _5y_to_6y: AverageBlockCumulativeSumPattern, + pub _6y_to_7y: AverageBlockCumulativeSumPattern, + pub _7y_to_8y: AverageBlockCumulativeSumPattern, + pub _8y_to_10y: AverageBlockCumulativeSumPattern, + pub _10y_to_12y: AverageBlockCumulativeSumPattern, + pub _12y_to_15y: AverageBlockCumulativeSumPattern, + pub over_15y: AverageBlockCumulativeSumPattern, + pub cumulative: SeriesPattern18<[StoredF64; 23]>, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_under_1h_old_coindays_stored".to_string()), + _1h_to_1d: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1h_to_1d_old_coindays_stored".to_string()), + _1d_to_1w: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1d_to_1w_old_coindays_stored".to_string()), + _1w_to_1m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1w_to_1m_old_coindays_stored".to_string()), + _1m_to_2m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1m_to_2m_old_coindays_stored".to_string()), + _2m_to_3m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_2m_to_3m_old_coindays_stored".to_string()), + _3m_to_4m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_3m_to_4m_old_coindays_stored".to_string()), + _4m_to_5m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_4m_to_5m_old_coindays_stored".to_string()), + _5m_to_6m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_5m_to_6m_old_coindays_stored".to_string()), + _6m_to_9m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_6m_to_9m_old_coindays_stored".to_string()), + _9m_to_1y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_9m_to_1y_old_coindays_stored".to_string()), + _1y_to_18m: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_1y_to_18m_old_coindays_stored".to_string()), + _18m_to_2y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_18m_to_2y_old_coindays_stored".to_string()), + _2y_to_3y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_2y_to_3y_old_coindays_stored".to_string()), + _3y_to_4y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_3y_to_4y_old_coindays_stored".to_string()), + _4y_to_5y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_4y_to_5y_old_coindays_stored".to_string()), + _5y_to_6y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_5y_to_6y_old_coindays_stored".to_string()), + _6y_to_7y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_6y_to_7y_old_coindays_stored".to_string()), + _7y_to_8y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_7y_to_8y_old_coindays_stored".to_string()), + _8y_to_10y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_8y_to_10y_old_coindays_stored".to_string()), + _10y_to_12y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_10y_to_12y_old_coindays_stored".to_string()), + _12y_to_15y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_12y_to_15y_old_coindays_stored".to_string()), + over_15y: AverageBlockCumulativeSumPattern::new(client.clone(), "utxos_over_15y_old_coindays_stored".to_string()), + cumulative: SeriesPattern18::new(client.clone(), "utxos_age_range_coindays_stored_cumulative".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Activity { + pub wakefulness: SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness, + pub dormancy: SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy, + pub wakefulness_to_dormancy: SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy, + pub height: SeriesPattern18<[StoredF64; 23]>, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Activity { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + wakefulness: SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness::new(client.clone(), format!("{base_path}_wakefulness")), + dormancy: SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy::new(client.clone(), format!("{base_path}_dormancy")), + wakefulness_to_dormancy: SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy::new(client.clone(), format!("{base_path}_wakefulness_to_dormancy")), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_wakefulness".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness { + pub under_1h: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub over_15y: SeriesPattern1, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: SeriesPattern1::new(client.clone(), "utxos_under_1h_old_wakefulness".to_string()), + _1h_to_1d: SeriesPattern1::new(client.clone(), "utxos_1h_to_1d_old_wakefulness".to_string()), + _1d_to_1w: SeriesPattern1::new(client.clone(), "utxos_1d_to_1w_old_wakefulness".to_string()), + _1w_to_1m: SeriesPattern1::new(client.clone(), "utxos_1w_to_1m_old_wakefulness".to_string()), + _1m_to_2m: SeriesPattern1::new(client.clone(), "utxos_1m_to_2m_old_wakefulness".to_string()), + _2m_to_3m: SeriesPattern1::new(client.clone(), "utxos_2m_to_3m_old_wakefulness".to_string()), + _3m_to_4m: SeriesPattern1::new(client.clone(), "utxos_3m_to_4m_old_wakefulness".to_string()), + _4m_to_5m: SeriesPattern1::new(client.clone(), "utxos_4m_to_5m_old_wakefulness".to_string()), + _5m_to_6m: SeriesPattern1::new(client.clone(), "utxos_5m_to_6m_old_wakefulness".to_string()), + _6m_to_9m: SeriesPattern1::new(client.clone(), "utxos_6m_to_9m_old_wakefulness".to_string()), + _9m_to_1y: SeriesPattern1::new(client.clone(), "utxos_9m_to_1y_old_wakefulness".to_string()), + _1y_to_18m: SeriesPattern1::new(client.clone(), "utxos_1y_to_18m_old_wakefulness".to_string()), + _18m_to_2y: SeriesPattern1::new(client.clone(), "utxos_18m_to_2y_old_wakefulness".to_string()), + _2y_to_3y: SeriesPattern1::new(client.clone(), "utxos_2y_to_3y_old_wakefulness".to_string()), + _3y_to_4y: SeriesPattern1::new(client.clone(), "utxos_3y_to_4y_old_wakefulness".to_string()), + _4y_to_5y: SeriesPattern1::new(client.clone(), "utxos_4y_to_5y_old_wakefulness".to_string()), + _5y_to_6y: SeriesPattern1::new(client.clone(), "utxos_5y_to_6y_old_wakefulness".to_string()), + _6y_to_7y: SeriesPattern1::new(client.clone(), "utxos_6y_to_7y_old_wakefulness".to_string()), + _7y_to_8y: SeriesPattern1::new(client.clone(), "utxos_7y_to_8y_old_wakefulness".to_string()), + _8y_to_10y: SeriesPattern1::new(client.clone(), "utxos_8y_to_10y_old_wakefulness".to_string()), + _10y_to_12y: SeriesPattern1::new(client.clone(), "utxos_10y_to_12y_old_wakefulness".to_string()), + _12y_to_15y: SeriesPattern1::new(client.clone(), "utxos_12y_to_15y_old_wakefulness".to_string()), + over_15y: SeriesPattern1::new(client.clone(), "utxos_over_15y_old_wakefulness".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy { + pub under_1h: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub over_15y: SeriesPattern1, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: SeriesPattern1::new(client.clone(), "utxos_under_1h_old_dormancy".to_string()), + _1h_to_1d: SeriesPattern1::new(client.clone(), "utxos_1h_to_1d_old_dormancy".to_string()), + _1d_to_1w: SeriesPattern1::new(client.clone(), "utxos_1d_to_1w_old_dormancy".to_string()), + _1w_to_1m: SeriesPattern1::new(client.clone(), "utxos_1w_to_1m_old_dormancy".to_string()), + _1m_to_2m: SeriesPattern1::new(client.clone(), "utxos_1m_to_2m_old_dormancy".to_string()), + _2m_to_3m: SeriesPattern1::new(client.clone(), "utxos_2m_to_3m_old_dormancy".to_string()), + _3m_to_4m: SeriesPattern1::new(client.clone(), "utxos_3m_to_4m_old_dormancy".to_string()), + _4m_to_5m: SeriesPattern1::new(client.clone(), "utxos_4m_to_5m_old_dormancy".to_string()), + _5m_to_6m: SeriesPattern1::new(client.clone(), "utxos_5m_to_6m_old_dormancy".to_string()), + _6m_to_9m: SeriesPattern1::new(client.clone(), "utxos_6m_to_9m_old_dormancy".to_string()), + _9m_to_1y: SeriesPattern1::new(client.clone(), "utxos_9m_to_1y_old_dormancy".to_string()), + _1y_to_18m: SeriesPattern1::new(client.clone(), "utxos_1y_to_18m_old_dormancy".to_string()), + _18m_to_2y: SeriesPattern1::new(client.clone(), "utxos_18m_to_2y_old_dormancy".to_string()), + _2y_to_3y: SeriesPattern1::new(client.clone(), "utxos_2y_to_3y_old_dormancy".to_string()), + _3y_to_4y: SeriesPattern1::new(client.clone(), "utxos_3y_to_4y_old_dormancy".to_string()), + _4y_to_5y: SeriesPattern1::new(client.clone(), "utxos_4y_to_5y_old_dormancy".to_string()), + _5y_to_6y: SeriesPattern1::new(client.clone(), "utxos_5y_to_6y_old_dormancy".to_string()), + _6y_to_7y: SeriesPattern1::new(client.clone(), "utxos_6y_to_7y_old_dormancy".to_string()), + _7y_to_8y: SeriesPattern1::new(client.clone(), "utxos_7y_to_8y_old_dormancy".to_string()), + _8y_to_10y: SeriesPattern1::new(client.clone(), "utxos_8y_to_10y_old_dormancy".to_string()), + _10y_to_12y: SeriesPattern1::new(client.clone(), "utxos_10y_to_12y_old_dormancy".to_string()), + _12y_to_15y: SeriesPattern1::new(client.clone(), "utxos_12y_to_15y_old_dormancy".to_string()), + over_15y: SeriesPattern1::new(client.clone(), "utxos_over_15y_old_dormancy".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy { + pub under_1h: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub over_15y: SeriesPattern1, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: SeriesPattern1::new(client.clone(), "utxos_under_1h_old_wakefulness_to_dormancy".to_string()), + _1h_to_1d: SeriesPattern1::new(client.clone(), "utxos_1h_to_1d_old_wakefulness_to_dormancy".to_string()), + _1d_to_1w: SeriesPattern1::new(client.clone(), "utxos_1d_to_1w_old_wakefulness_to_dormancy".to_string()), + _1w_to_1m: SeriesPattern1::new(client.clone(), "utxos_1w_to_1m_old_wakefulness_to_dormancy".to_string()), + _1m_to_2m: SeriesPattern1::new(client.clone(), "utxos_1m_to_2m_old_wakefulness_to_dormancy".to_string()), + _2m_to_3m: SeriesPattern1::new(client.clone(), "utxos_2m_to_3m_old_wakefulness_to_dormancy".to_string()), + _3m_to_4m: SeriesPattern1::new(client.clone(), "utxos_3m_to_4m_old_wakefulness_to_dormancy".to_string()), + _4m_to_5m: SeriesPattern1::new(client.clone(), "utxos_4m_to_5m_old_wakefulness_to_dormancy".to_string()), + _5m_to_6m: SeriesPattern1::new(client.clone(), "utxos_5m_to_6m_old_wakefulness_to_dormancy".to_string()), + _6m_to_9m: SeriesPattern1::new(client.clone(), "utxos_6m_to_9m_old_wakefulness_to_dormancy".to_string()), + _9m_to_1y: SeriesPattern1::new(client.clone(), "utxos_9m_to_1y_old_wakefulness_to_dormancy".to_string()), + _1y_to_18m: SeriesPattern1::new(client.clone(), "utxos_1y_to_18m_old_wakefulness_to_dormancy".to_string()), + _18m_to_2y: SeriesPattern1::new(client.clone(), "utxos_18m_to_2y_old_wakefulness_to_dormancy".to_string()), + _2y_to_3y: SeriesPattern1::new(client.clone(), "utxos_2y_to_3y_old_wakefulness_to_dormancy".to_string()), + _3y_to_4y: SeriesPattern1::new(client.clone(), "utxos_3y_to_4y_old_wakefulness_to_dormancy".to_string()), + _4y_to_5y: SeriesPattern1::new(client.clone(), "utxos_4y_to_5y_old_wakefulness_to_dormancy".to_string()), + _5y_to_6y: SeriesPattern1::new(client.clone(), "utxos_5y_to_6y_old_wakefulness_to_dormancy".to_string()), + _6y_to_7y: SeriesPattern1::new(client.clone(), "utxos_6y_to_7y_old_wakefulness_to_dormancy".to_string()), + _7y_to_8y: SeriesPattern1::new(client.clone(), "utxos_7y_to_8y_old_wakefulness_to_dormancy".to_string()), + _8y_to_10y: SeriesPattern1::new(client.clone(), "utxos_8y_to_10y_old_wakefulness_to_dormancy".to_string()), + _10y_to_12y: SeriesPattern1::new(client.clone(), "utxos_10y_to_12y_old_wakefulness_to_dormancy".to_string()), + _12y_to_15y: SeriesPattern1::new(client.clone(), "utxos_12y_to_15y_old_wakefulness_to_dormancy".to_string()), + over_15y: SeriesPattern1::new(client.clone(), "utxos_over_15y_old_wakefulness_to_dormancy".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Supply { + pub awake: SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake, + pub dormant: SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Supply { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + awake: SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake::new(client.clone(), format!("{base_path}_awake")), + dormant: SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant::new(client.clone(), format!("{base_path}_dormant")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake { + pub under_1h: BtcCentsSatsUsdPattern, + pub _1h_to_1d: BtcCentsSatsUsdPattern, + pub _1d_to_1w: BtcCentsSatsUsdPattern, + pub _1w_to_1m: BtcCentsSatsUsdPattern, + pub _1m_to_2m: BtcCentsSatsUsdPattern, + pub _2m_to_3m: BtcCentsSatsUsdPattern, + pub _3m_to_4m: BtcCentsSatsUsdPattern, + pub _4m_to_5m: BtcCentsSatsUsdPattern, + pub _5m_to_6m: BtcCentsSatsUsdPattern, + pub _6m_to_9m: BtcCentsSatsUsdPattern, + pub _9m_to_1y: BtcCentsSatsUsdPattern, + pub _1y_to_18m: BtcCentsSatsUsdPattern, + pub _18m_to_2y: BtcCentsSatsUsdPattern, + pub _2y_to_3y: BtcCentsSatsUsdPattern, + pub _3y_to_4y: BtcCentsSatsUsdPattern, + pub _4y_to_5y: BtcCentsSatsUsdPattern, + pub _5y_to_6y: BtcCentsSatsUsdPattern, + pub _6y_to_7y: BtcCentsSatsUsdPattern, + pub _7y_to_8y: BtcCentsSatsUsdPattern, + pub _8y_to_10y: BtcCentsSatsUsdPattern, + pub _10y_to_12y: BtcCentsSatsUsdPattern, + pub _12y_to_15y: BtcCentsSatsUsdPattern, + pub over_15y: BtcCentsSatsUsdPattern, + pub height: SeriesPattern18<[Sats; 23]>, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_under_1h_old_awake_supply".to_string()), + _1h_to_1d: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1h_to_1d_old_awake_supply".to_string()), + _1d_to_1w: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1d_to_1w_old_awake_supply".to_string()), + _1w_to_1m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1w_to_1m_old_awake_supply".to_string()), + _1m_to_2m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1m_to_2m_old_awake_supply".to_string()), + _2m_to_3m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2m_to_3m_old_awake_supply".to_string()), + _3m_to_4m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3m_to_4m_old_awake_supply".to_string()), + _4m_to_5m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4m_to_5m_old_awake_supply".to_string()), + _5m_to_6m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5m_to_6m_old_awake_supply".to_string()), + _6m_to_9m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6m_to_9m_old_awake_supply".to_string()), + _9m_to_1y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_9m_to_1y_old_awake_supply".to_string()), + _1y_to_18m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1y_to_18m_old_awake_supply".to_string()), + _18m_to_2y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_18m_to_2y_old_awake_supply".to_string()), + _2y_to_3y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2y_to_3y_old_awake_supply".to_string()), + _3y_to_4y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3y_to_4y_old_awake_supply".to_string()), + _4y_to_5y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4y_to_5y_old_awake_supply".to_string()), + _5y_to_6y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5y_to_6y_old_awake_supply".to_string()), + _6y_to_7y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6y_to_7y_old_awake_supply".to_string()), + _7y_to_8y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_7y_to_8y_old_awake_supply".to_string()), + _8y_to_10y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_8y_to_10y_old_awake_supply".to_string()), + _10y_to_12y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_10y_to_12y_old_awake_supply".to_string()), + _12y_to_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_12y_to_15y_old_awake_supply".to_string()), + over_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_over_15y_old_awake_supply".to_string()), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_awake_supply_sats".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant { + pub under_1h: BtcCentsSatsUsdPattern, + pub _1h_to_1d: BtcCentsSatsUsdPattern, + pub _1d_to_1w: BtcCentsSatsUsdPattern, + pub _1w_to_1m: BtcCentsSatsUsdPattern, + pub _1m_to_2m: BtcCentsSatsUsdPattern, + pub _2m_to_3m: BtcCentsSatsUsdPattern, + pub _3m_to_4m: BtcCentsSatsUsdPattern, + pub _4m_to_5m: BtcCentsSatsUsdPattern, + pub _5m_to_6m: BtcCentsSatsUsdPattern, + pub _6m_to_9m: BtcCentsSatsUsdPattern, + pub _9m_to_1y: BtcCentsSatsUsdPattern, + pub _1y_to_18m: BtcCentsSatsUsdPattern, + pub _18m_to_2y: BtcCentsSatsUsdPattern, + pub _2y_to_3y: BtcCentsSatsUsdPattern, + pub _3y_to_4y: BtcCentsSatsUsdPattern, + pub _4y_to_5y: BtcCentsSatsUsdPattern, + pub _5y_to_6y: BtcCentsSatsUsdPattern, + pub _6y_to_7y: BtcCentsSatsUsdPattern, + pub _7y_to_8y: BtcCentsSatsUsdPattern, + pub _8y_to_10y: BtcCentsSatsUsdPattern, + pub _10y_to_12y: BtcCentsSatsUsdPattern, + pub _12y_to_15y: BtcCentsSatsUsdPattern, + pub over_15y: BtcCentsSatsUsdPattern, + pub height: SeriesPattern18<[Sats; 23]>, +} + +impl SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_under_1h_old_dormant_supply".to_string()), + _1h_to_1d: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1h_to_1d_old_dormant_supply".to_string()), + _1d_to_1w: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1d_to_1w_old_dormant_supply".to_string()), + _1w_to_1m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1w_to_1m_old_dormant_supply".to_string()), + _1m_to_2m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1m_to_2m_old_dormant_supply".to_string()), + _2m_to_3m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2m_to_3m_old_dormant_supply".to_string()), + _3m_to_4m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3m_to_4m_old_dormant_supply".to_string()), + _4m_to_5m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4m_to_5m_old_dormant_supply".to_string()), + _5m_to_6m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5m_to_6m_old_dormant_supply".to_string()), + _6m_to_9m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6m_to_9m_old_dormant_supply".to_string()), + _9m_to_1y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_9m_to_1y_old_dormant_supply".to_string()), + _1y_to_18m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1y_to_18m_old_dormant_supply".to_string()), + _18m_to_2y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_18m_to_2y_old_dormant_supply".to_string()), + _2y_to_3y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2y_to_3y_old_dormant_supply".to_string()), + _3y_to_4y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3y_to_4y_old_dormant_supply".to_string()), + _4y_to_5y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4y_to_5y_old_dormant_supply".to_string()), + _5y_to_6y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5y_to_6y_old_dormant_supply".to_string()), + _6y_to_7y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6y_to_7y_old_dormant_supply".to_string()), + _7y_to_8y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_7y_to_8y_old_dormant_supply".to_string()), + _8y_to_10y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_8y_to_10y_old_dormant_supply".to_string()), + _10y_to_12y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_10y_to_12y_old_dormant_supply".to_string()), + _12y_to_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_12y_to_15y_old_dormant_supply".to_string()), + over_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_over_15y_old_dormant_supply".to_string()), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_dormant_supply_sats".to_string()), } } } @@ -6372,57 +7771,327 @@ impl SeriesTree_Frameworks_Coinflow { /// Series tree node. pub struct SeriesTree_Frameworks_Coinflow_AgeRange { - pub under_1h: MobilitySpendingSupplyPattern, - pub _1h_to_1d: MobilitySpendingSupplyPattern, - pub _1d_to_1w: MobilitySpendingSupplyPattern, - pub _1w_to_1m: MobilitySpendingSupplyPattern, - pub _1m_to_2m: MobilitySpendingSupplyPattern, - pub _2m_to_3m: MobilitySpendingSupplyPattern, - pub _3m_to_4m: MobilitySpendingSupplyPattern, - pub _4m_to_5m: MobilitySpendingSupplyPattern, - pub _5m_to_6m: MobilitySpendingSupplyPattern, - pub _6m_to_9m: MobilitySpendingSupplyPattern, - pub _9m_to_1y: MobilitySpendingSupplyPattern, - pub _1y_to_18m: MobilitySpendingSupplyPattern, - pub _18m_to_2y: MobilitySpendingSupplyPattern, - pub _2y_to_3y: MobilitySpendingSupplyPattern, - pub _3y_to_4y: MobilitySpendingSupplyPattern, - pub _4y_to_5y: MobilitySpendingSupplyPattern, - pub _5y_to_6y: MobilitySpendingSupplyPattern, - pub _6y_to_7y: MobilitySpendingSupplyPattern, - pub _7y_to_8y: MobilitySpendingSupplyPattern, - pub _8y_to_10y: MobilitySpendingSupplyPattern, - pub _10y_to_12y: MobilitySpendingSupplyPattern, - pub _12y_to_15y: MobilitySpendingSupplyPattern, - pub over_15y: MobilitySpendingSupplyPattern, + pub spending_rate: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate, + pub spending_exposure: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure, + pub supply: SeriesTree_Frameworks_Coinflow_AgeRange_Supply, } impl SeriesTree_Frameworks_Coinflow_AgeRange { pub fn new(client: Arc, base_path: String) -> Self { Self { - under_1h: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_under_1h_old".to_string()), - _1h_to_1d: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_1h_to_1d_old".to_string()), - _1d_to_1w: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_1d_to_1w_old".to_string()), - _1w_to_1m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_1w_to_1m_old".to_string()), - _1m_to_2m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_1m_to_2m_old".to_string()), - _2m_to_3m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_2m_to_3m_old".to_string()), - _3m_to_4m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_3m_to_4m_old".to_string()), - _4m_to_5m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_4m_to_5m_old".to_string()), - _5m_to_6m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_5m_to_6m_old".to_string()), - _6m_to_9m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_6m_to_9m_old".to_string()), - _9m_to_1y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_9m_to_1y_old".to_string()), - _1y_to_18m: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_1y_to_18m_old".to_string()), - _18m_to_2y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_18m_to_2y_old".to_string()), - _2y_to_3y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_2y_to_3y_old".to_string()), - _3y_to_4y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_3y_to_4y_old".to_string()), - _4y_to_5y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_4y_to_5y_old".to_string()), - _5y_to_6y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_5y_to_6y_old".to_string()), - _6y_to_7y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_6y_to_7y_old".to_string()), - _7y_to_8y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_7y_to_8y_old".to_string()), - _8y_to_10y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_8y_to_10y_old".to_string()), - _10y_to_12y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_10y_to_12y_old".to_string()), - _12y_to_15y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_12y_to_15y_old".to_string()), - over_15y: MobilitySpendingSupplyPattern::new(client.clone(), "utxos_over_15y_old".to_string()), + spending_rate: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate::new(client.clone(), format!("{base_path}_spending_rate")), + spending_exposure: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure::new(client.clone(), format!("{base_path}_spending_exposure")), + supply: SeriesTree_Frameworks_Coinflow_AgeRange_Supply::new(client.clone(), format!("{base_path}_supply")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate { + pub under_1h: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub over_15y: SeriesPattern1, + pub height: SeriesPattern18<[StoredF64; 23]>, +} + +impl SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: SeriesPattern1::new(client.clone(), "utxos_under_1h_old_spending_rate".to_string()), + _1h_to_1d: SeriesPattern1::new(client.clone(), "utxos_1h_to_1d_old_spending_rate".to_string()), + _1d_to_1w: SeriesPattern1::new(client.clone(), "utxos_1d_to_1w_old_spending_rate".to_string()), + _1w_to_1m: SeriesPattern1::new(client.clone(), "utxos_1w_to_1m_old_spending_rate".to_string()), + _1m_to_2m: SeriesPattern1::new(client.clone(), "utxos_1m_to_2m_old_spending_rate".to_string()), + _2m_to_3m: SeriesPattern1::new(client.clone(), "utxos_2m_to_3m_old_spending_rate".to_string()), + _3m_to_4m: SeriesPattern1::new(client.clone(), "utxos_3m_to_4m_old_spending_rate".to_string()), + _4m_to_5m: SeriesPattern1::new(client.clone(), "utxos_4m_to_5m_old_spending_rate".to_string()), + _5m_to_6m: SeriesPattern1::new(client.clone(), "utxos_5m_to_6m_old_spending_rate".to_string()), + _6m_to_9m: SeriesPattern1::new(client.clone(), "utxos_6m_to_9m_old_spending_rate".to_string()), + _9m_to_1y: SeriesPattern1::new(client.clone(), "utxos_9m_to_1y_old_spending_rate".to_string()), + _1y_to_18m: SeriesPattern1::new(client.clone(), "utxos_1y_to_18m_old_spending_rate".to_string()), + _18m_to_2y: SeriesPattern1::new(client.clone(), "utxos_18m_to_2y_old_spending_rate".to_string()), + _2y_to_3y: SeriesPattern1::new(client.clone(), "utxos_2y_to_3y_old_spending_rate".to_string()), + _3y_to_4y: SeriesPattern1::new(client.clone(), "utxos_3y_to_4y_old_spending_rate".to_string()), + _4y_to_5y: SeriesPattern1::new(client.clone(), "utxos_4y_to_5y_old_spending_rate".to_string()), + _5y_to_6y: SeriesPattern1::new(client.clone(), "utxos_5y_to_6y_old_spending_rate".to_string()), + _6y_to_7y: SeriesPattern1::new(client.clone(), "utxos_6y_to_7y_old_spending_rate".to_string()), + _7y_to_8y: SeriesPattern1::new(client.clone(), "utxos_7y_to_8y_old_spending_rate".to_string()), + _8y_to_10y: SeriesPattern1::new(client.clone(), "utxos_8y_to_10y_old_spending_rate".to_string()), + _10y_to_12y: SeriesPattern1::new(client.clone(), "utxos_10y_to_12y_old_spending_rate".to_string()), + _12y_to_15y: SeriesPattern1::new(client.clone(), "utxos_12y_to_15y_old_spending_rate".to_string()), + over_15y: SeriesPattern1::new(client.clone(), "utxos_over_15y_old_spending_rate".to_string()), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_spending_rate".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure { + pub under_1h: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub over_15y: SeriesPattern1, + pub mobility: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility, + pub height: SeriesPattern18<[StoredF64; 23]>, +} + +impl SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: SeriesPattern1::new(client.clone(), "utxos_under_1h_old_spending_exposure".to_string()), + _1h_to_1d: SeriesPattern1::new(client.clone(), "utxos_1h_to_1d_old_spending_exposure".to_string()), + _1d_to_1w: SeriesPattern1::new(client.clone(), "utxos_1d_to_1w_old_spending_exposure".to_string()), + _1w_to_1m: SeriesPattern1::new(client.clone(), "utxos_1w_to_1m_old_spending_exposure".to_string()), + _1m_to_2m: SeriesPattern1::new(client.clone(), "utxos_1m_to_2m_old_spending_exposure".to_string()), + _2m_to_3m: SeriesPattern1::new(client.clone(), "utxos_2m_to_3m_old_spending_exposure".to_string()), + _3m_to_4m: SeriesPattern1::new(client.clone(), "utxos_3m_to_4m_old_spending_exposure".to_string()), + _4m_to_5m: SeriesPattern1::new(client.clone(), "utxos_4m_to_5m_old_spending_exposure".to_string()), + _5m_to_6m: SeriesPattern1::new(client.clone(), "utxos_5m_to_6m_old_spending_exposure".to_string()), + _6m_to_9m: SeriesPattern1::new(client.clone(), "utxos_6m_to_9m_old_spending_exposure".to_string()), + _9m_to_1y: SeriesPattern1::new(client.clone(), "utxos_9m_to_1y_old_spending_exposure".to_string()), + _1y_to_18m: SeriesPattern1::new(client.clone(), "utxos_1y_to_18m_old_spending_exposure".to_string()), + _18m_to_2y: SeriesPattern1::new(client.clone(), "utxos_18m_to_2y_old_spending_exposure".to_string()), + _2y_to_3y: SeriesPattern1::new(client.clone(), "utxos_2y_to_3y_old_spending_exposure".to_string()), + _3y_to_4y: SeriesPattern1::new(client.clone(), "utxos_3y_to_4y_old_spending_exposure".to_string()), + _4y_to_5y: SeriesPattern1::new(client.clone(), "utxos_4y_to_5y_old_spending_exposure".to_string()), + _5y_to_6y: SeriesPattern1::new(client.clone(), "utxos_5y_to_6y_old_spending_exposure".to_string()), + _6y_to_7y: SeriesPattern1::new(client.clone(), "utxos_6y_to_7y_old_spending_exposure".to_string()), + _7y_to_8y: SeriesPattern1::new(client.clone(), "utxos_7y_to_8y_old_spending_exposure".to_string()), + _8y_to_10y: SeriesPattern1::new(client.clone(), "utxos_8y_to_10y_old_spending_exposure".to_string()), + _10y_to_12y: SeriesPattern1::new(client.clone(), "utxos_10y_to_12y_old_spending_exposure".to_string()), + _12y_to_15y: SeriesPattern1::new(client.clone(), "utxos_12y_to_15y_old_spending_exposure".to_string()), + over_15y: SeriesPattern1::new(client.clone(), "utxos_over_15y_old_spending_exposure".to_string()), + mobility: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility::new(client.clone(), format!("{base_path}_mobility")), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_spending_exposure".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility { + pub under_1h: SeriesPattern1, + pub _1h_to_1d: SeriesPattern1, + pub _1d_to_1w: SeriesPattern1, + pub _1w_to_1m: SeriesPattern1, + pub _1m_to_2m: SeriesPattern1, + pub _2m_to_3m: SeriesPattern1, + pub _3m_to_4m: SeriesPattern1, + pub _4m_to_5m: SeriesPattern1, + pub _5m_to_6m: SeriesPattern1, + pub _6m_to_9m: SeriesPattern1, + pub _9m_to_1y: SeriesPattern1, + pub _1y_to_18m: SeriesPattern1, + pub _18m_to_2y: SeriesPattern1, + pub _2y_to_3y: SeriesPattern1, + pub _3y_to_4y: SeriesPattern1, + pub _4y_to_5y: SeriesPattern1, + pub _5y_to_6y: SeriesPattern1, + pub _6y_to_7y: SeriesPattern1, + pub _7y_to_8y: SeriesPattern1, + pub _8y_to_10y: SeriesPattern1, + pub _10y_to_12y: SeriesPattern1, + pub _12y_to_15y: SeriesPattern1, + pub over_15y: SeriesPattern1, +} + +impl SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: SeriesPattern1::new(client.clone(), "utxos_under_1h_old_mobility".to_string()), + _1h_to_1d: SeriesPattern1::new(client.clone(), "utxos_1h_to_1d_old_mobility".to_string()), + _1d_to_1w: SeriesPattern1::new(client.clone(), "utxos_1d_to_1w_old_mobility".to_string()), + _1w_to_1m: SeriesPattern1::new(client.clone(), "utxos_1w_to_1m_old_mobility".to_string()), + _1m_to_2m: SeriesPattern1::new(client.clone(), "utxos_1m_to_2m_old_mobility".to_string()), + _2m_to_3m: SeriesPattern1::new(client.clone(), "utxos_2m_to_3m_old_mobility".to_string()), + _3m_to_4m: SeriesPattern1::new(client.clone(), "utxos_3m_to_4m_old_mobility".to_string()), + _4m_to_5m: SeriesPattern1::new(client.clone(), "utxos_4m_to_5m_old_mobility".to_string()), + _5m_to_6m: SeriesPattern1::new(client.clone(), "utxos_5m_to_6m_old_mobility".to_string()), + _6m_to_9m: SeriesPattern1::new(client.clone(), "utxos_6m_to_9m_old_mobility".to_string()), + _9m_to_1y: SeriesPattern1::new(client.clone(), "utxos_9m_to_1y_old_mobility".to_string()), + _1y_to_18m: SeriesPattern1::new(client.clone(), "utxos_1y_to_18m_old_mobility".to_string()), + _18m_to_2y: SeriesPattern1::new(client.clone(), "utxos_18m_to_2y_old_mobility".to_string()), + _2y_to_3y: SeriesPattern1::new(client.clone(), "utxos_2y_to_3y_old_mobility".to_string()), + _3y_to_4y: SeriesPattern1::new(client.clone(), "utxos_3y_to_4y_old_mobility".to_string()), + _4y_to_5y: SeriesPattern1::new(client.clone(), "utxos_4y_to_5y_old_mobility".to_string()), + _5y_to_6y: SeriesPattern1::new(client.clone(), "utxos_5y_to_6y_old_mobility".to_string()), + _6y_to_7y: SeriesPattern1::new(client.clone(), "utxos_6y_to_7y_old_mobility".to_string()), + _7y_to_8y: SeriesPattern1::new(client.clone(), "utxos_7y_to_8y_old_mobility".to_string()), + _8y_to_10y: SeriesPattern1::new(client.clone(), "utxos_8y_to_10y_old_mobility".to_string()), + _10y_to_12y: SeriesPattern1::new(client.clone(), "utxos_10y_to_12y_old_mobility".to_string()), + _12y_to_15y: SeriesPattern1::new(client.clone(), "utxos_12y_to_15y_old_mobility".to_string()), + over_15y: SeriesPattern1::new(client.clone(), "utxos_over_15y_old_mobility".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Coinflow_AgeRange_Supply { + pub mobile: SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile, + pub immobile: SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile, +} + +impl SeriesTree_Frameworks_Coinflow_AgeRange_Supply { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + mobile: SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile::new(client.clone(), format!("{base_path}_mobile")), + immobile: SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile::new(client.clone(), format!("{base_path}_immobile")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile { + pub under_1h: BtcCentsSatsUsdPattern, + pub _1h_to_1d: BtcCentsSatsUsdPattern, + pub _1d_to_1w: BtcCentsSatsUsdPattern, + pub _1w_to_1m: BtcCentsSatsUsdPattern, + pub _1m_to_2m: BtcCentsSatsUsdPattern, + pub _2m_to_3m: BtcCentsSatsUsdPattern, + pub _3m_to_4m: BtcCentsSatsUsdPattern, + pub _4m_to_5m: BtcCentsSatsUsdPattern, + pub _5m_to_6m: BtcCentsSatsUsdPattern, + pub _6m_to_9m: BtcCentsSatsUsdPattern, + pub _9m_to_1y: BtcCentsSatsUsdPattern, + pub _1y_to_18m: BtcCentsSatsUsdPattern, + pub _18m_to_2y: BtcCentsSatsUsdPattern, + pub _2y_to_3y: BtcCentsSatsUsdPattern, + pub _3y_to_4y: BtcCentsSatsUsdPattern, + pub _4y_to_5y: BtcCentsSatsUsdPattern, + pub _5y_to_6y: BtcCentsSatsUsdPattern, + pub _6y_to_7y: BtcCentsSatsUsdPattern, + pub _7y_to_8y: BtcCentsSatsUsdPattern, + pub _8y_to_10y: BtcCentsSatsUsdPattern, + pub _10y_to_12y: BtcCentsSatsUsdPattern, + pub _12y_to_15y: BtcCentsSatsUsdPattern, + pub over_15y: BtcCentsSatsUsdPattern, + pub height: SeriesPattern18<[Sats; 23]>, +} + +impl SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_under_1h_old_mobile_supply".to_string()), + _1h_to_1d: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1h_to_1d_old_mobile_supply".to_string()), + _1d_to_1w: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1d_to_1w_old_mobile_supply".to_string()), + _1w_to_1m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1w_to_1m_old_mobile_supply".to_string()), + _1m_to_2m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1m_to_2m_old_mobile_supply".to_string()), + _2m_to_3m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2m_to_3m_old_mobile_supply".to_string()), + _3m_to_4m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3m_to_4m_old_mobile_supply".to_string()), + _4m_to_5m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4m_to_5m_old_mobile_supply".to_string()), + _5m_to_6m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5m_to_6m_old_mobile_supply".to_string()), + _6m_to_9m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6m_to_9m_old_mobile_supply".to_string()), + _9m_to_1y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_9m_to_1y_old_mobile_supply".to_string()), + _1y_to_18m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1y_to_18m_old_mobile_supply".to_string()), + _18m_to_2y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_18m_to_2y_old_mobile_supply".to_string()), + _2y_to_3y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2y_to_3y_old_mobile_supply".to_string()), + _3y_to_4y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3y_to_4y_old_mobile_supply".to_string()), + _4y_to_5y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4y_to_5y_old_mobile_supply".to_string()), + _5y_to_6y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5y_to_6y_old_mobile_supply".to_string()), + _6y_to_7y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6y_to_7y_old_mobile_supply".to_string()), + _7y_to_8y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_7y_to_8y_old_mobile_supply".to_string()), + _8y_to_10y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_8y_to_10y_old_mobile_supply".to_string()), + _10y_to_12y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_10y_to_12y_old_mobile_supply".to_string()), + _12y_to_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_12y_to_15y_old_mobile_supply".to_string()), + over_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_over_15y_old_mobile_supply".to_string()), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_mobile_supply_sats".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile { + pub under_1h: BtcCentsSatsUsdPattern, + pub _1h_to_1d: BtcCentsSatsUsdPattern, + pub _1d_to_1w: BtcCentsSatsUsdPattern, + pub _1w_to_1m: BtcCentsSatsUsdPattern, + pub _1m_to_2m: BtcCentsSatsUsdPattern, + pub _2m_to_3m: BtcCentsSatsUsdPattern, + pub _3m_to_4m: BtcCentsSatsUsdPattern, + pub _4m_to_5m: BtcCentsSatsUsdPattern, + pub _5m_to_6m: BtcCentsSatsUsdPattern, + pub _6m_to_9m: BtcCentsSatsUsdPattern, + pub _9m_to_1y: BtcCentsSatsUsdPattern, + pub _1y_to_18m: BtcCentsSatsUsdPattern, + pub _18m_to_2y: BtcCentsSatsUsdPattern, + pub _2y_to_3y: BtcCentsSatsUsdPattern, + pub _3y_to_4y: BtcCentsSatsUsdPattern, + pub _4y_to_5y: BtcCentsSatsUsdPattern, + pub _5y_to_6y: BtcCentsSatsUsdPattern, + pub _6y_to_7y: BtcCentsSatsUsdPattern, + pub _7y_to_8y: BtcCentsSatsUsdPattern, + pub _8y_to_10y: BtcCentsSatsUsdPattern, + pub _10y_to_12y: BtcCentsSatsUsdPattern, + pub _12y_to_15y: BtcCentsSatsUsdPattern, + pub over_15y: BtcCentsSatsUsdPattern, + pub height: SeriesPattern18<[Sats; 23]>, +} + +impl SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + under_1h: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_under_1h_old_immobile_supply".to_string()), + _1h_to_1d: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1h_to_1d_old_immobile_supply".to_string()), + _1d_to_1w: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1d_to_1w_old_immobile_supply".to_string()), + _1w_to_1m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1w_to_1m_old_immobile_supply".to_string()), + _1m_to_2m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1m_to_2m_old_immobile_supply".to_string()), + _2m_to_3m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2m_to_3m_old_immobile_supply".to_string()), + _3m_to_4m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3m_to_4m_old_immobile_supply".to_string()), + _4m_to_5m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4m_to_5m_old_immobile_supply".to_string()), + _5m_to_6m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5m_to_6m_old_immobile_supply".to_string()), + _6m_to_9m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6m_to_9m_old_immobile_supply".to_string()), + _9m_to_1y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_9m_to_1y_old_immobile_supply".to_string()), + _1y_to_18m: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_1y_to_18m_old_immobile_supply".to_string()), + _18m_to_2y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_18m_to_2y_old_immobile_supply".to_string()), + _2y_to_3y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_2y_to_3y_old_immobile_supply".to_string()), + _3y_to_4y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_3y_to_4y_old_immobile_supply".to_string()), + _4y_to_5y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_4y_to_5y_old_immobile_supply".to_string()), + _5y_to_6y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_5y_to_6y_old_immobile_supply".to_string()), + _6y_to_7y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_6y_to_7y_old_immobile_supply".to_string()), + _7y_to_8y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_7y_to_8y_old_immobile_supply".to_string()), + _8y_to_10y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_8y_to_10y_old_immobile_supply".to_string()), + _10y_to_12y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_10y_to_12y_old_immobile_supply".to_string()), + _12y_to_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_12y_to_15y_old_immobile_supply".to_string()), + over_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxos_over_15y_old_immobile_supply".to_string()), + height: SeriesPattern18::new(client.clone(), "utxos_age_range_immobile_supply_sats".to_string()), } } } @@ -6644,9 +8313,9 @@ impl SeriesTree_Models_CapitalSentiment { pub struct SeriesTree_Models_RarityMeter { pub components: SeriesTree_Models_RarityMeter_Components, pub extremes: SeriesTree_Models_RarityMeter_Extremes, - pub full: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern, - pub local: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern, - pub cycle: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern, + pub full: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern, + pub local: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern, + pub cycle: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern, } impl SeriesTree_Models_RarityMeter { @@ -6654,50 +8323,50 @@ impl SeriesTree_Models_RarityMeter { Self { components: SeriesTree_Models_RarityMeter_Components::new(client.clone(), format!("{base_path}_components")), extremes: SeriesTree_Models_RarityMeter_Extremes::new(client.clone(), format!("{base_path}_extremes")), - full: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern::new(client.clone(), "rarity_meter".to_string()), - local: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern::new(client.clone(), "local_rarity_meter".to_string()), - cycle: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern::new(client.clone(), "cycle_rarity_meter".to_string()), + full: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern::new(client.clone(), "rarity_meter".to_string()), + local: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern::new(client.clone(), "local_rarity_meter".to_string()), + cycle: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern::new(client.clone(), "cycle_rarity_meter".to_string()), } } } /// Series tree node. pub struct SeriesTree_Models_RarityMeter_Components { - pub realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub sth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub sth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub lth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub lth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub over_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub over_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub under_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub under_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub vaulted_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub active_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub true_market_mean_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub cointime_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, - pub coinflow_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern, + pub realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub sth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub sth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub lth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub lth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub over_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub over_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub under_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub under_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub vaulted_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub active_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub true_market_mean_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub cointime_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, + pub coinflow_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern, } impl SeriesTree_Models_RarityMeter_Components { pub fn new(client: Arc, base_path: String) -> Self { Self { - realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "realized_price".to_string()), - capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "capitalized_price".to_string()), - sth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "sth_realized_price".to_string()), - sth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "sth_capitalized_price".to_string()), - lth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "lth_realized_price".to_string()), - lth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "lth_capitalized_price".to_string()), - over_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "over_6m_realized_price".to_string()), - over_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "over_4m_realized_price".to_string()), - under_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "under_4m_realized_price".to_string()), - under_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "under_6m_realized_price".to_string()), - vaulted_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "vaulted_price".to_string()), - active_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "active_price".to_string()), - true_market_mean_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "true_market_mean_price".to_string()), - cointime_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "cointime_price".to_string()), - coinflow_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern::new(client.clone(), "coinflow_price".to_string()), + realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "realized_price".to_string()), + capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "capitalized_price".to_string()), + sth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "sth_realized_price".to_string()), + sth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "sth_capitalized_price".to_string()), + lth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "lth_realized_price".to_string()), + lth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "lth_capitalized_price".to_string()), + over_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "over_6m_realized_price".to_string()), + over_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "over_4m_realized_price".to_string()), + under_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "under_4m_realized_price".to_string()), + under_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "under_6m_realized_price".to_string()), + vaulted_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "vaulted_price".to_string()), + active_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "active_price".to_string()), + true_market_mean_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "true_market_mean_price".to_string()), + cointime_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "cointime_price".to_string()), + coinflow_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern::new(client.clone(), "coinflow_price".to_string()), } } } @@ -6705,9 +8374,9 @@ impl SeriesTree_Models_RarityMeter_Components { /// Series tree node. pub struct SeriesTree_Models_RarityMeter_Extremes { pub coins_in_loss: SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss, - pub profit_taking: RankTailThresholdPattern, - pub capitulation: RankTailThresholdPattern, - pub peak_regret: RankTailThresholdPattern, + pub profit_taking: HeightRankTailThresholdPattern, + pub capitulation: HeightRankTailThresholdPattern, + pub peak_regret: HeightRankTailThresholdPattern, pub seller_exhaustion: SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion, } @@ -6715,9 +8384,9 @@ impl SeriesTree_Models_RarityMeter_Extremes { pub fn new(client: Arc, base_path: String) -> Self { Self { coins_in_loss: SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss::new(client.clone(), format!("{base_path}_coins_in_loss")), - profit_taking: RankTailThresholdPattern::new(client.clone(), "rarity_meter_profit_taking".to_string()), - capitulation: RankTailThresholdPattern::new(client.clone(), "rarity_meter_capitulation".to_string()), - peak_regret: RankTailThresholdPattern::new(client.clone(), "rarity_meter_peak_regret".to_string()), + profit_taking: HeightRankTailThresholdPattern::new(client.clone(), "rarity_meter_profit_taking".to_string()), + capitulation: HeightRankTailThresholdPattern::new(client.clone(), "rarity_meter_capitulation".to_string()), + peak_regret: HeightRankTailThresholdPattern::new(client.clone(), "rarity_meter_peak_regret".to_string()), seller_exhaustion: SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion::new(client.clone(), format!("{base_path}_seller_exhaustion")), } } @@ -6728,6 +8397,7 @@ pub struct SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss { pub threshold_pct0_1: SeriesPattern1, pub threshold_pct0_05: SeriesPattern1, pub threshold_pct0_025: SeriesPattern1, + pub height: SeriesPattern18<[Bitcoin; 3]>, pub tail: PercentPpmRatioPattern2, pub rank: SeriesPattern1, } @@ -6738,6 +8408,7 @@ impl SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss { threshold_pct0_1: SeriesPattern1::new(client.clone(), "rarity_meter_coins_in_loss_threshold_pct0_1".to_string()), threshold_pct0_05: SeriesPattern1::new(client.clone(), "rarity_meter_coins_in_loss_threshold_pct0_05".to_string()), threshold_pct0_025: SeriesPattern1::new(client.clone(), "rarity_meter_coins_in_loss_threshold".to_string()), + height: SeriesPattern18::new(client.clone(), "rarity_meter_coins_in_loss_thresholds".to_string()), tail: PercentPpmRatioPattern2::new(client.clone(), "rarity_meter_coins_in_loss_tail".to_string()), rank: SeriesPattern1::new(client.clone(), "rarity_meter_coins_in_loss_rank".to_string()), } @@ -6749,6 +8420,7 @@ pub struct SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion { pub threshold_pct0_1: SeriesPattern1, pub threshold_pct0_05: SeriesPattern1, pub threshold_pct0_025: SeriesPattern1, + pub height: SeriesPattern18<[StoredF32; 3]>, pub tail: PercentPpmRatioPattern2, pub rank: SeriesPattern1, } @@ -6759,6 +8431,7 @@ impl SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion { threshold_pct0_1: SeriesPattern1::new(client.clone(), "rarity_meter_seller_exhaustion_threshold_pct0_1".to_string()), threshold_pct0_05: SeriesPattern1::new(client.clone(), "rarity_meter_seller_exhaustion_threshold_pct0_05".to_string()), threshold_pct0_025: SeriesPattern1::new(client.clone(), "rarity_meter_seller_exhaustion_threshold".to_string()), + height: SeriesPattern18::new(client.clone(), "rarity_meter_seller_exhaustion_thresholds".to_string()), tail: PercentPpmRatioPattern2::new(client.clone(), "rarity_meter_seller_exhaustion_tail".to_string()), rank: SeriesPattern1::new(client.clone(), "rarity_meter_seller_exhaustion_rank".to_string()), } @@ -7990,6 +9663,7 @@ pub struct SeriesTree_Market_MovingAverage_Ema { pub _2y: CentsPpmRatioSatsUsdPattern, pub _200w: CentsPpmRatioSatsUsdPattern, pub _4y: CentsPpmRatioSatsUsdPattern, + pub height: SeriesPattern18<[Cents; 16]>, } impl SeriesTree_Market_MovingAverage_Ema { @@ -8011,6 +9685,7 @@ impl SeriesTree_Market_MovingAverage_Ema { _2y: CentsPpmRatioSatsUsdPattern::new(client.clone(), "price_ema_2y".to_string()), _200w: CentsPpmRatioSatsUsdPattern::new(client.clone(), "price_ema_200w".to_string()), _4y: CentsPpmRatioSatsUsdPattern::new(client.clone(), "price_ema_4y".to_string()), + height: SeriesPattern18::new(client.clone(), "price_ema_cents".to_string()), } } } @@ -8644,6 +10319,8 @@ pub struct SeriesTree_Cohorts_Utxo { pub type_: SeriesTree_Cohorts_Utxo_Type, pub profitability: SeriesTree_Cohorts_Utxo_Profitability, pub matured: SeriesTree_Cohorts_Utxo_Matured, + pub cumulative_matured_sats: SeriesPattern18<[Sats; 23]>, + pub cumulative_matured_cents: SeriesPattern18<[Cents; 23]>, } impl SeriesTree_Cohorts_Utxo { @@ -8664,6 +10341,8 @@ impl SeriesTree_Cohorts_Utxo { type_: SeriesTree_Cohorts_Utxo_Type::new(client.clone(), format!("{base_path}_type")), profitability: SeriesTree_Cohorts_Utxo_Profitability::new(client.clone(), format!("{base_path}_profitability")), matured: SeriesTree_Cohorts_Utxo_Matured::new(client.clone(), format!("{base_path}_matured")), + cumulative_matured_sats: SeriesPattern18::new(client.clone(), "utxos_age_range_matured_supply_cumulative_sats".to_string()), + cumulative_matured_cents: SeriesPattern18::new(client.clone(), "utxos_age_range_matured_supply_cumulative_cents".to_string()), } } } @@ -8713,7 +10392,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Activity { pub transfer_volume: AverageBlockCumulativeInSumPattern, pub coindays_destroyed: AverageBlockCumulativeSumPattern, pub coinyears_destroyed: SeriesPattern1, - pub dormancy: _1m1w1y24hPattern, + pub dormancy: _1m1w1y24hHeightPattern, } impl SeriesTree_Cohorts_Utxo_All_Activity { @@ -8722,7 +10401,7 @@ impl SeriesTree_Cohorts_Utxo_All_Activity { transfer_volume: AverageBlockCumulativeInSumPattern::new(client.clone(), "transfer_volume".to_string()), coindays_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), "coindays_destroyed".to_string()), coinyears_destroyed: SeriesPattern1::new(client.clone(), "coinyears_destroyed".to_string()), - dormancy: _1m1w1y24hPattern::new(client.clone(), "dormancy".to_string()), + dormancy: _1m1w1y24hHeightPattern::new(client.clone(), "dormancy".to_string()), } } } @@ -8737,10 +10416,10 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized { pub net_pnl: BlockChangeCumulativeDeltaSumPattern, pub sopr: SeriesTree_Cohorts_Utxo_All_Realized_Sopr, pub gross_pnl: BlockCumulativeSumPattern, - pub sell_side_risk_ratio: _1m1w1y24hPattern8, + pub sell_side_risk_ratio: _1m1w1y24hHeightPattern3, pub peak_regret: BlockCumulativeSumPattern, pub capitalized: PricePattern, - pub profit_to_loss_ratio: _1m1w1y24hPattern, + pub profit_to_loss_ratio: _1m1w1y24hHeightPattern2, } impl SeriesTree_Cohorts_Utxo_All_Realized { @@ -8754,10 +10433,10 @@ impl SeriesTree_Cohorts_Utxo_All_Realized { net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "net".to_string()), sopr: SeriesTree_Cohorts_Utxo_All_Realized_Sopr::new(client.clone(), format!("{base_path}_sopr")), gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "realized_gross_pnl".to_string()), - sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "sell_side_risk_ratio".to_string()), + sell_side_risk_ratio: _1m1w1y24hHeightPattern3::new(client.clone(), "sell_side_risk_ratio".to_string()), peak_regret: BlockCumulativeSumPattern::new(client.clone(), "realized_peak_regret".to_string()), capitalized: PricePattern::new(client.clone(), "capitalized_price".to_string()), - profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "realized_profit_to_loss_ratio".to_string()), + profit_to_loss_ratio: _1m1w1y24hHeightPattern2::new(client.clone(), "realized_profit_to_loss_ratio".to_string()), } } } @@ -8765,7 +10444,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized { /// Series tree node. pub struct SeriesTree_Cohorts_Utxo_All_Realized_Sopr { pub value_destroyed: AverageBlockCumulativeSumPattern, - pub ratio: _1m1w1y24hPattern, + pub ratio: _1m1w1y24hHeightPattern4, pub adjusted: SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted, } @@ -8773,7 +10452,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized_Sopr { pub fn new(client: Arc, base_path: String) -> Self { Self { value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), "value_destroyed".to_string()), - ratio: _1m1w1y24hPattern::new(client.clone(), "sopr".to_string()), + ratio: _1m1w1y24hHeightPattern4::new(client.clone(), "sopr".to_string()), adjusted: SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted::new(client.clone(), format!("{base_path}_adjusted")), } } @@ -8781,7 +10460,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized_Sopr { /// Series tree node. pub struct SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted { - pub ratio: _1m1w1y24hPattern, + pub ratio: _1m1w1y24hHeightPattern2, pub transfer_volume: AverageBlockCumulativeSumPattern, pub value_destroyed: AverageBlockCumulativeSumPattern, } @@ -8789,7 +10468,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted { impl SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted { pub fn new(client: Arc, base_path: String) -> Self { Self { - ratio: _1m1w1y24hPattern::new(client.clone(), "asopr".to_string()), + ratio: _1m1w1y24hHeightPattern2::new(client.clone(), "asopr".to_string()), transfer_volume: AverageBlockCumulativeSumPattern::new(client.clone(), "adj_value_created".to_string()), value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), "adj_value_destroyed".to_string()), } @@ -8802,8 +10481,8 @@ pub struct SeriesTree_Cohorts_Utxo_All_CostBasis { pub in_loss: PerPattern, pub min: CentsSatsUsdPattern, pub max: CentsSatsUsdPattern, - pub per_coin: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, - pub per_dollar: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, + pub per_coin: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, + pub per_dollar: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern, pub supply_density: PercentPpmRatioPattern2, } @@ -8814,8 +10493,8 @@ impl SeriesTree_Cohorts_Utxo_All_CostBasis { in_loss: PerPattern::new(client.clone(), "cost_basis_in_loss_per".to_string()), min: CentsSatsUsdPattern::new(client.clone(), "cost_basis_min".to_string()), max: CentsSatsUsdPattern::new(client.clone(), "cost_basis_max".to_string()), - per_coin: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), "cost_basis_per_coin".to_string()), - per_dollar: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), "cost_basis_per_dollar".to_string()), + per_coin: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), "cost_basis_per_coin".to_string()), + per_dollar: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern::new(client.clone(), "cost_basis_per_dollar".to_string()), supply_density: PercentPpmRatioPattern2::new(client.clone(), "supply_density".to_string()), } } @@ -8984,10 +10663,10 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized { pub net_pnl: BlockChangeCumulativeDeltaSumPattern, pub sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr, pub gross_pnl: BlockCumulativeSumPattern, - pub sell_side_risk_ratio: _1m1w1y24hPattern8, + pub sell_side_risk_ratio: _1m1w1y24hHeightPattern3, pub peak_regret: BlockCumulativeSumPattern, pub capitalized: PricePattern, - pub profit_to_loss_ratio: _1m1w1y24hPattern, + pub profit_to_loss_ratio: _1m1w1y24hHeightPattern2, } impl SeriesTree_Cohorts_Utxo_Lth_Realized { @@ -9001,10 +10680,10 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized { net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "lth_net".to_string()), sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr::new(client.clone(), format!("{base_path}_sopr")), gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_gross_pnl".to_string()), - sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "lth_sell_side_risk_ratio".to_string()), + sell_side_risk_ratio: _1m1w1y24hHeightPattern3::new(client.clone(), "lth_sell_side_risk_ratio".to_string()), peak_regret: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_peak_regret".to_string()), capitalized: PricePattern::new(client.clone(), "lth_capitalized_price".to_string()), - profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "lth_realized_profit_to_loss_ratio".to_string()), + profit_to_loss_ratio: _1m1w1y24hHeightPattern2::new(client.clone(), "lth_realized_profit_to_loss_ratio".to_string()), } } } @@ -9012,14 +10691,14 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized { /// Series tree node. pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr { pub value_destroyed: AverageBlockCumulativeSumPattern, - pub ratio: _1m1w1y24hPattern, + pub ratio: _1m1w1y24hHeightPattern4, } impl SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr { pub fn new(client: Arc, base_path: String) -> Self { Self { value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), "lth_value_destroyed".to_string()), - ratio: _1m1w1y24hPattern::new(client.clone(), "lth_sopr".to_string()), + ratio: _1m1w1y24hHeightPattern4::new(client.clone(), "lth_sopr".to_string()), } } } @@ -9419,6 +11098,13 @@ pub struct SeriesTree_Cohorts_Utxo_Profitability { pub range: SeriesTree_Cohorts_Utxo_Profitability_Range, pub profit: SeriesTree_Cohorts_Utxo_Profitability_Profit, pub loss: SeriesTree_Cohorts_Utxo_Profitability_Loss, + pub all_supply_sats: SeriesPattern18, + pub sth_supply_sats: SeriesPattern18, + pub all_realized_cap: SeriesPattern18, + pub sth_realized_cap: SeriesPattern18, + pub all_unrealized_pnl: SeriesPattern18, + pub sth_unrealized_pnl: SeriesPattern18, + pub nupl: SeriesPattern18, } impl SeriesTree_Cohorts_Utxo_Profitability { @@ -9427,6 +11113,13 @@ impl SeriesTree_Cohorts_Utxo_Profitability { range: SeriesTree_Cohorts_Utxo_Profitability_Range::new(client.clone(), format!("{base_path}_range")), profit: SeriesTree_Cohorts_Utxo_Profitability_Profit::new(client.clone(), format!("{base_path}_profit")), loss: SeriesTree_Cohorts_Utxo_Profitability_Loss::new(client.clone(), format!("{base_path}_loss")), + all_supply_sats: SeriesPattern18::new(client.clone(), "profitability_all_supply_sats".to_string()), + sth_supply_sats: SeriesPattern18::new(client.clone(), "profitability_sth_supply_sats".to_string()), + all_realized_cap: SeriesPattern18::new(client.clone(), "profitability_all_realized_cap".to_string()), + sth_realized_cap: SeriesPattern18::new(client.clone(), "profitability_sth_realized_cap".to_string()), + all_unrealized_pnl: SeriesPattern18::new(client.clone(), "profitability_all_unrealized_pnl".to_string()), + sth_unrealized_pnl: SeriesPattern18::new(client.clone(), "profitability_sth_unrealized_pnl".to_string()), + nupl: SeriesPattern18::new(client.clone(), "profitability_nupl_ppm".to_string()), } } } diff --git a/crates/brk_cohort/Cargo.toml b/crates/brk_cohort/Cargo.toml index 9748da0e0..7bdb07b5f 100644 --- a/crates/brk_cohort/Cargo.toml +++ b/crates/brk_cohort/Cargo.toml @@ -13,6 +13,7 @@ brk_types = { workspace = true } brk_traversable = { workspace = true } vecdb = { workspace = true } rayon = { workspace = true } +schemars = { workspace = true } serde = { workspace = true } [package.metadata.cargo-machete] diff --git a/crates/brk_cohort/src/age_range.rs b/crates/brk_cohort/src/age_range.rs index f6267f0df..5628c182f 100644 --- a/crates/brk_cohort/src/age_range.rs +++ b/crates/brk_cohort/src/age_range.rs @@ -6,7 +6,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde::Serialize; use vecdb::{ColumnId, VecValue, Version}; -use super::{CohortName, Filter, TimeFilter}; +use super::{CohortContext, CohortName, Filter, TimeFilter}; // Age boundary constants in hours pub const HOURS_1H: usize = 1; @@ -164,6 +164,120 @@ impl ColumnId for AgeRangeId { } } +impl AgeRangeId { + #[inline] + pub fn bounds(self) -> &'static Range { + self.select(&AGE_RANGE_BOUNDS) + } + + #[inline] + pub fn filter(self) -> &'static Filter { + self.select(&AGE_RANGE_FILTERS) + } + + #[inline] + pub fn name(self) -> &'static CohortName { + self.select(&AGE_RANGE_NAMES) + } + + pub fn select(self, values: &AgeRange) -> &T { + match self { + Self::Under1H => &values.under_1h, + Self::From1HTo1D => &values._1h_to_1d, + Self::From1DTo1W => &values._1d_to_1w, + Self::From1WTo1M => &values._1w_to_1m, + Self::From1MTo2M => &values._1m_to_2m, + Self::From2MTo3M => &values._2m_to_3m, + Self::From3MTo4M => &values._3m_to_4m, + Self::From4MTo5M => &values._4m_to_5m, + Self::From5MTo6M => &values._5m_to_6m, + Self::From6MTo9M => &values._6m_to_9m, + Self::From9MTo1Y => &values._9m_to_1y, + Self::From1YTo18M => &values._1y_to_18m, + Self::From18MTo2Y => &values._18m_to_2y, + Self::From2YTo3Y => &values._2y_to_3y, + Self::From3YTo4Y => &values._3y_to_4y, + Self::From4YTo5Y => &values._4y_to_5y, + Self::From5YTo6Y => &values._5y_to_6y, + Self::From6YTo7Y => &values._6y_to_7y, + Self::From7YTo8Y => &values._7y_to_8y, + Self::From8YTo10Y => &values._8y_to_10y, + Self::From10YTo12Y => &values._10y_to_12y, + Self::From12YTo15Y => &values._12y_to_15y, + Self::Over15Y => &values.over_15y, + } + } + + pub fn select_mut(self, values: &mut AgeRange) -> &mut T { + match self { + Self::Under1H => &mut values.under_1h, + Self::From1HTo1D => &mut values._1h_to_1d, + Self::From1DTo1W => &mut values._1d_to_1w, + Self::From1WTo1M => &mut values._1w_to_1m, + Self::From1MTo2M => &mut values._1m_to_2m, + Self::From2MTo3M => &mut values._2m_to_3m, + Self::From3MTo4M => &mut values._3m_to_4m, + Self::From4MTo5M => &mut values._4m_to_5m, + Self::From5MTo6M => &mut values._5m_to_6m, + Self::From6MTo9M => &mut values._6m_to_9m, + Self::From9MTo1Y => &mut values._9m_to_1y, + Self::From1YTo18M => &mut values._1y_to_18m, + Self::From18MTo2Y => &mut values._18m_to_2y, + Self::From2YTo3Y => &mut values._2y_to_3y, + Self::From3YTo4Y => &mut values._3y_to_4y, + Self::From4YTo5Y => &mut values._4y_to_5y, + Self::From5YTo6Y => &mut values._5y_to_6y, + Self::From6YTo7Y => &mut values._6y_to_7y, + Self::From7YTo8Y => &mut values._7y_to_8y, + Self::From8YTo10Y => &mut values._8y_to_10y, + Self::From10YTo12Y => &mut values._10y_to_12y, + Self::From12YTo15Y => &mut values._12y_to_15y, + Self::Over15Y => &mut values.over_15y, + } + } + + pub fn series( + context: CohortContext, + mut create: impl FnMut(Self, &str) -> T, + ) -> AgeRange { + AgeRange::from_fn(|column| { + let name = context.prefixed(column.name().id); + create(column, &name) + }) + } +} + +impl From for AgeRangeId { + #[inline] + fn from(age: Age) -> Self { + match age.hours() { + 0..HOURS_1H => Self::Under1H, + HOURS_1H..HOURS_1D => Self::From1HTo1D, + HOURS_1D..HOURS_1W => Self::From1DTo1W, + HOURS_1W..HOURS_1M => Self::From1WTo1M, + HOURS_1M..HOURS_2M => Self::From1MTo2M, + HOURS_2M..HOURS_3M => Self::From2MTo3M, + HOURS_3M..HOURS_4M => Self::From3MTo4M, + HOURS_4M..HOURS_5M => Self::From4MTo5M, + HOURS_5M..HOURS_6M => Self::From5MTo6M, + HOURS_6M..HOURS_9M => Self::From6MTo9M, + HOURS_9M..HOURS_1Y => Self::From9MTo1Y, + HOURS_1Y..HOURS_18M => Self::From1YTo18M, + HOURS_18M..HOURS_2Y => Self::From18MTo2Y, + HOURS_2Y..HOURS_3Y => Self::From2YTo3Y, + HOURS_3Y..HOURS_4Y => Self::From3YTo4Y, + HOURS_4Y..HOURS_5Y => Self::From4YTo5Y, + HOURS_5Y..HOURS_6Y => Self::From5YTo6Y, + HOURS_6Y..HOURS_7Y => Self::From6YTo7Y, + HOURS_7Y..HOURS_8Y => Self::From7YTo8Y, + HOURS_8Y..HOURS_10Y => Self::From8YTo10Y, + HOURS_10Y..HOURS_12Y => Self::From10YTo12Y, + HOURS_12Y..HOURS_15Y => Self::From12YTo15Y, + _ => Self::Over15Y, + } + } +} + /// Age boundaries in hours. Defines the cohort ranges: /// [0, 1h), [1h, 1d), [1d, 1w), [1w, 1m), ..., [15y, ∞) pub const AGE_BOUNDARIES: [usize; AGE_RANGE_COUNT - 1] = [ @@ -253,12 +367,6 @@ pub const AGE_RANGE_NAMES: AgeRange = AgeRange { over_15y: CohortName::new("over_15y_old", "15y+", "15+ Years Old"), }; -impl AgeRange { - pub const fn names() -> &'static Self { - &AGE_RANGE_NAMES - } -} - #[derive(Default, Clone, Traversable, Serialize)] pub struct AgeRange { pub under_1h: T, @@ -287,183 +395,86 @@ pub struct AgeRange { } impl AgeRange { + pub fn from_fn(mut create: impl FnMut(AgeRangeId) -> T) -> Self { + Self { + under_1h: create(AgeRangeId::Under1H), + _1h_to_1d: create(AgeRangeId::From1HTo1D), + _1d_to_1w: create(AgeRangeId::From1DTo1W), + _1w_to_1m: create(AgeRangeId::From1WTo1M), + _1m_to_2m: create(AgeRangeId::From1MTo2M), + _2m_to_3m: create(AgeRangeId::From2MTo3M), + _3m_to_4m: create(AgeRangeId::From3MTo4M), + _4m_to_5m: create(AgeRangeId::From4MTo5M), + _5m_to_6m: create(AgeRangeId::From5MTo6M), + _6m_to_9m: create(AgeRangeId::From6MTo9M), + _9m_to_1y: create(AgeRangeId::From9MTo1Y), + _1y_to_18m: create(AgeRangeId::From1YTo18M), + _18m_to_2y: create(AgeRangeId::From18MTo2Y), + _2y_to_3y: create(AgeRangeId::From2YTo3Y), + _3y_to_4y: create(AgeRangeId::From3YTo4Y), + _4y_to_5y: create(AgeRangeId::From4YTo5Y), + _5y_to_6y: create(AgeRangeId::From5YTo6Y), + _6y_to_7y: create(AgeRangeId::From6YTo7Y), + _7y_to_8y: create(AgeRangeId::From7YTo8Y), + _8y_to_10y: create(AgeRangeId::From8YTo10Y), + _10y_to_12y: create(AgeRangeId::From10YTo12Y), + _12y_to_15y: create(AgeRangeId::From12YTo15Y), + over_15y: create(AgeRangeId::Over15Y), + } + } + + pub fn try_from_fn(mut create: impl FnMut(AgeRangeId) -> Result) -> Result { + Ok(Self { + under_1h: create(AgeRangeId::Under1H)?, + _1h_to_1d: create(AgeRangeId::From1HTo1D)?, + _1d_to_1w: create(AgeRangeId::From1DTo1W)?, + _1w_to_1m: create(AgeRangeId::From1WTo1M)?, + _1m_to_2m: create(AgeRangeId::From1MTo2M)?, + _2m_to_3m: create(AgeRangeId::From2MTo3M)?, + _3m_to_4m: create(AgeRangeId::From3MTo4M)?, + _4m_to_5m: create(AgeRangeId::From4MTo5M)?, + _5m_to_6m: create(AgeRangeId::From5MTo6M)?, + _6m_to_9m: create(AgeRangeId::From6MTo9M)?, + _9m_to_1y: create(AgeRangeId::From9MTo1Y)?, + _1y_to_18m: create(AgeRangeId::From1YTo18M)?, + _18m_to_2y: create(AgeRangeId::From18MTo2Y)?, + _2y_to_3y: create(AgeRangeId::From2YTo3Y)?, + _3y_to_4y: create(AgeRangeId::From3YTo4Y)?, + _4y_to_5y: create(AgeRangeId::From4YTo5Y)?, + _5y_to_6y: create(AgeRangeId::From5YTo6Y)?, + _6y_to_7y: create(AgeRangeId::From6YTo7Y)?, + _7y_to_8y: create(AgeRangeId::From7YTo8Y)?, + _8y_to_10y: create(AgeRangeId::From8YTo10Y)?, + _10y_to_12y: create(AgeRangeId::From10YTo12Y)?, + _12y_to_15y: create(AgeRangeId::From12YTo15Y)?, + over_15y: create(AgeRangeId::Over15Y)?, + }) + } + /// Get mutable reference by Age. O(1). #[inline] pub fn get_mut(&mut self, age: Age) -> &mut T { - match age.hours() { - 0..HOURS_1H => &mut self.under_1h, - HOURS_1H..HOURS_1D => &mut self._1h_to_1d, - HOURS_1D..HOURS_1W => &mut self._1d_to_1w, - HOURS_1W..HOURS_1M => &mut self._1w_to_1m, - HOURS_1M..HOURS_2M => &mut self._1m_to_2m, - HOURS_2M..HOURS_3M => &mut self._2m_to_3m, - HOURS_3M..HOURS_4M => &mut self._3m_to_4m, - HOURS_4M..HOURS_5M => &mut self._4m_to_5m, - HOURS_5M..HOURS_6M => &mut self._5m_to_6m, - HOURS_6M..HOURS_9M => &mut self._6m_to_9m, - HOURS_9M..HOURS_1Y => &mut self._9m_to_1y, - HOURS_1Y..HOURS_18M => &mut self._1y_to_18m, - HOURS_18M..HOURS_2Y => &mut self._18m_to_2y, - HOURS_2Y..HOURS_3Y => &mut self._2y_to_3y, - HOURS_3Y..HOURS_4Y => &mut self._3y_to_4y, - HOURS_4Y..HOURS_5Y => &mut self._4y_to_5y, - HOURS_5Y..HOURS_6Y => &mut self._5y_to_6y, - HOURS_6Y..HOURS_7Y => &mut self._6y_to_7y, - HOURS_7Y..HOURS_8Y => &mut self._7y_to_8y, - HOURS_8Y..HOURS_10Y => &mut self._8y_to_10y, - HOURS_10Y..HOURS_12Y => &mut self._10y_to_12y, - HOURS_12Y..HOURS_15Y => &mut self._12y_to_15y, - _ => &mut self.over_15y, - } + AgeRangeId::from(age).select_mut(self) } /// Get reference by Age. O(1). #[inline] pub fn get(&self, age: Age) -> &T { - match age.hours() { - 0..HOURS_1H => &self.under_1h, - HOURS_1H..HOURS_1D => &self._1h_to_1d, - HOURS_1D..HOURS_1W => &self._1d_to_1w, - HOURS_1W..HOURS_1M => &self._1w_to_1m, - HOURS_1M..HOURS_2M => &self._1m_to_2m, - HOURS_2M..HOURS_3M => &self._2m_to_3m, - HOURS_3M..HOURS_4M => &self._3m_to_4m, - HOURS_4M..HOURS_5M => &self._4m_to_5m, - HOURS_5M..HOURS_6M => &self._5m_to_6m, - HOURS_6M..HOURS_9M => &self._6m_to_9m, - HOURS_9M..HOURS_1Y => &self._9m_to_1y, - HOURS_1Y..HOURS_18M => &self._1y_to_18m, - HOURS_18M..HOURS_2Y => &self._18m_to_2y, - HOURS_2Y..HOURS_3Y => &self._2y_to_3y, - HOURS_3Y..HOURS_4Y => &self._3y_to_4y, - HOURS_4Y..HOURS_5Y => &self._4y_to_5y, - HOURS_5Y..HOURS_6Y => &self._5y_to_6y, - HOURS_6Y..HOURS_7Y => &self._6y_to_7y, - HOURS_7Y..HOURS_8Y => &self._7y_to_8y, - HOURS_8Y..HOURS_10Y => &self._8y_to_10y, - HOURS_10Y..HOURS_12Y => &self._10y_to_12y, - HOURS_12Y..HOURS_15Y => &self._12y_to_15y, - _ => &self.over_15y, - } - } - - pub fn from_array(arr: [T; AGE_RANGE_COUNT]) -> Self { - let [ - a0, - a1, - a2, - a3, - a4, - a5, - a6, - a7, - a8, - a9, - a10, - a11, - a12, - a13, - a14, - a15, - a16, - a17, - a18, - a19, - a20, - a21, - a22, - ] = arr; - Self { - under_1h: a0, - _1h_to_1d: a1, - _1d_to_1w: a2, - _1w_to_1m: a3, - _1m_to_2m: a4, - _2m_to_3m: a5, - _3m_to_4m: a6, - _4m_to_5m: a7, - _5m_to_6m: a8, - _6m_to_9m: a9, - _9m_to_1y: a10, - _1y_to_18m: a11, - _18m_to_2y: a12, - _2y_to_3y: a13, - _3y_to_4y: a14, - _4y_to_5y: a15, - _5y_to_6y: a16, - _6y_to_7y: a17, - _7y_to_8y: a18, - _8y_to_10y: a19, - _10y_to_12y: a20, - _12y_to_15y: a21, - over_15y: a22, - } + AgeRangeId::from(age).select(self) } pub fn new(mut create: F) -> Self where F: FnMut(Filter, &'static str) -> T, { - let f = AGE_RANGE_FILTERS; - let n = AGE_RANGE_NAMES; - Self { - under_1h: create(f.under_1h.clone(), n.under_1h.id), - _1h_to_1d: create(f._1h_to_1d.clone(), n._1h_to_1d.id), - _1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id), - _1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id), - _1m_to_2m: create(f._1m_to_2m.clone(), n._1m_to_2m.id), - _2m_to_3m: create(f._2m_to_3m.clone(), n._2m_to_3m.id), - _3m_to_4m: create(f._3m_to_4m.clone(), n._3m_to_4m.id), - _4m_to_5m: create(f._4m_to_5m.clone(), n._4m_to_5m.id), - _5m_to_6m: create(f._5m_to_6m.clone(), n._5m_to_6m.id), - _6m_to_9m: create(f._6m_to_9m.clone(), n._6m_to_9m.id), - _9m_to_1y: create(f._9m_to_1y.clone(), n._9m_to_1y.id), - _1y_to_18m: create(f._1y_to_18m.clone(), n._1y_to_18m.id), - _18m_to_2y: create(f._18m_to_2y.clone(), n._18m_to_2y.id), - _2y_to_3y: create(f._2y_to_3y.clone(), n._2y_to_3y.id), - _3y_to_4y: create(f._3y_to_4y.clone(), n._3y_to_4y.id), - _4y_to_5y: create(f._4y_to_5y.clone(), n._4y_to_5y.id), - _5y_to_6y: create(f._5y_to_6y.clone(), n._5y_to_6y.id), - _6y_to_7y: create(f._6y_to_7y.clone(), n._6y_to_7y.id), - _7y_to_8y: create(f._7y_to_8y.clone(), n._7y_to_8y.id), - _8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id), - _10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id), - _12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id), - over_15y: create(f.over_15y.clone(), n.over_15y.id), - } + Self::from_fn(|id| create(id.filter().clone(), id.name().id)) } pub fn try_new(mut create: F) -> Result where F: FnMut(Filter, &'static str) -> Result, { - let f = AGE_RANGE_FILTERS; - let n = AGE_RANGE_NAMES; - Ok(Self { - under_1h: create(f.under_1h.clone(), n.under_1h.id)?, - _1h_to_1d: create(f._1h_to_1d.clone(), n._1h_to_1d.id)?, - _1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id)?, - _1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id)?, - _1m_to_2m: create(f._1m_to_2m.clone(), n._1m_to_2m.id)?, - _2m_to_3m: create(f._2m_to_3m.clone(), n._2m_to_3m.id)?, - _3m_to_4m: create(f._3m_to_4m.clone(), n._3m_to_4m.id)?, - _4m_to_5m: create(f._4m_to_5m.clone(), n._4m_to_5m.id)?, - _5m_to_6m: create(f._5m_to_6m.clone(), n._5m_to_6m.id)?, - _6m_to_9m: create(f._6m_to_9m.clone(), n._6m_to_9m.id)?, - _9m_to_1y: create(f._9m_to_1y.clone(), n._9m_to_1y.id)?, - _1y_to_18m: create(f._1y_to_18m.clone(), n._1y_to_18m.id)?, - _18m_to_2y: create(f._18m_to_2y.clone(), n._18m_to_2y.id)?, - _2y_to_3y: create(f._2y_to_3y.clone(), n._2y_to_3y.id)?, - _3y_to_4y: create(f._3y_to_4y.clone(), n._3y_to_4y.id)?, - _4y_to_5y: create(f._4y_to_5y.clone(), n._4y_to_5y.id)?, - _5y_to_6y: create(f._5y_to_6y.clone(), n._5y_to_6y.id)?, - _6y_to_7y: create(f._6y_to_7y.clone(), n._6y_to_7y.id)?, - _7y_to_8y: create(f._7y_to_8y.clone(), n._7y_to_8y.id)?, - _8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id)?, - _10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id)?, - _12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id)?, - over_15y: create(f.over_15y.clone(), n.over_15y.id)?, - }) + Self::try_from_fn(|id| create(id.filter().clone(), id.name().id)) } pub fn iter(&self) -> impl Iterator { @@ -559,6 +570,8 @@ impl AgeRange { #[cfg(test)] mod tests { + use brk_types::Timestamp; + use super::*; #[test] @@ -599,6 +612,29 @@ mod tests { } } + #[test] + fn column_ids_select_named_fields_and_metadata() { + let mut named = AgeRange::from_fn(|column| column.index()); + for &column in &AGE_RANGE_IDS { + assert_eq!(*column.select(&named), column.index()); + *column.select_mut(&mut named) += AGE_RANGE_COUNT; + assert_eq!(*column.select(&named), column.index() + AGE_RANGE_COUNT); + assert_eq!(column.bounds(), column.select(&AGE_RANGE_BOUNDS)); + assert_eq!(column.filter(), column.select(&AGE_RANGE_FILTERS)); + assert_eq!(column.name().id, column.select(&AGE_RANGE_NAMES).id); + } + + let series = AgeRangeId::series(CohortContext::Utxo, |column, name| { + (column, name.to_owned()) + }); + for (((column, name), expected_column), expected_name) in + series.iter().zip(AGE_RANGE_IDS).zip(AGE_RANGE_NAMES.iter()) + { + assert_eq!(*column, expected_column); + assert_eq!(name, &CohortContext::Utxo.prefixed(expected_name.id)); + } + } + #[test] fn split_range_names_and_boundaries_match() { assert_eq!(HOURS_9M, HOURS_6M + HOURS_3M); @@ -609,15 +645,35 @@ mod tests { assert_eq!(AGE_RANGE_NAMES._18m_to_2y.id, "18m_to_2y_old"); } + #[test] + fn classifier_matches_every_typed_range_boundary() { + for &id in &AGE_RANGE_IDS { + let bounds = id.bounds(); + let at_start = Age::new( + Timestamp::new((bounds.start * 60 * 60) as u32), + Timestamp::ZERO, + ); + assert_eq!(AgeRangeId::from(at_start), id); + + if bounds.end != usize::MAX { + let before_end = Age::new( + Timestamp::new(((bounds.end - 1) * 60 * 60) as u32), + Timestamp::ZERO, + ); + assert_eq!(AgeRangeId::from(before_end), id); + } + } + } + #[test] fn eighteen_month_classifier_stays_anchored_to_one_year_plus_six_months() { let age_at_540_days = Age::new( - brk_types::Timestamp::new((HOURS_6M * 3 * 60 * 60) as u32), - brk_types::Timestamp::ZERO, + Timestamp::new((HOURS_6M * 3 * 60 * 60) as u32), + Timestamp::ZERO, ); let age_at_18m = Age::new( - brk_types::Timestamp::new((HOURS_18M * 60 * 60) as u32), - brk_types::Timestamp::ZERO, + Timestamp::new((HOURS_18M * 60 * 60) as u32), + Timestamp::ZERO, ); assert_eq!(AGE_RANGE_NAMES.get(age_at_540_days).id, "1y_to_18m_old"); diff --git a/crates/brk_cohort/src/by_addr_type.rs b/crates/brk_cohort/src/by_addr_type.rs index 0436e8187..745e225cd 100644 --- a/crates/brk_cohort/src/by_addr_type.rs +++ b/crates/brk_cohort/src/by_addr_type.rs @@ -4,6 +4,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::OutputType; use rayon::prelude::*; +use vecdb::{ColumnId, VecValue, Version}; use super::Filter; @@ -16,6 +17,116 @@ pub const P2WSH: &str = "p2wsh"; pub const P2TR: &str = "p2tr"; pub const P2A: &str = "p2a"; +pub const ADDR_TYPE_COUNT: usize = 8; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum AddrTypeId { + P2PK65, + P2PK33, + P2PKH, + P2SH, + P2WPKH, + P2WSH, + P2TR, + P2A, +} + +pub const ADDR_TYPE_IDS: [AddrTypeId; ADDR_TYPE_COUNT] = [ + AddrTypeId::P2PK65, + AddrTypeId::P2PK33, + AddrTypeId::P2PKH, + AddrTypeId::P2SH, + AddrTypeId::P2WPKH, + AddrTypeId::P2WSH, + AddrTypeId::P2TR, + AddrTypeId::P2A, +]; + +impl ColumnId for AddrTypeId { + type Row + = [T; ADDR_TYPE_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &ADDR_TYPE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(ADDR_TYPE_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +impl AddrTypeId { + pub const fn name(self) -> &'static str { + match self { + Self::P2PK65 => P2PK65, + Self::P2PK33 => P2PK33, + Self::P2PKH => P2PKH, + Self::P2SH => P2SH, + Self::P2WPKH => P2WPKH, + Self::P2WSH => P2WSH, + Self::P2TR => P2TR, + Self::P2A => P2A, + } + } + + pub const fn output_type(self) -> OutputType { + match self { + Self::P2PK65 => OutputType::P2PK65, + Self::P2PK33 => OutputType::P2PK33, + Self::P2PKH => OutputType::P2PKH, + Self::P2SH => OutputType::P2SH, + Self::P2WPKH => OutputType::P2WPKH, + Self::P2WSH => OutputType::P2WSH, + Self::P2TR => OutputType::P2TR, + Self::P2A => OutputType::P2A, + } + } + + pub fn select(self, values: &ByAddrType) -> &T { + values.get_unwrap(self.output_type()) + } + + pub fn select_mut(self, values: &mut ByAddrType) -> &mut T { + values.get_mut_unwrap(self.output_type()) + } + + pub fn series(mut create: impl FnMut(Self, &'static str) -> T) -> ByAddrType { + ByAddrType::from_fn(|id| create(id, id.name())) + } +} + #[derive(Default, Clone, Debug, Traversable)] pub struct ByAddrType { pub p2pk65: T, @@ -29,36 +140,44 @@ pub struct ByAddrType { } impl ByAddrType { + pub fn from_fn(mut create: impl FnMut(AddrTypeId) -> T) -> Self { + Self { + p2pk65: create(AddrTypeId::P2PK65), + p2pk33: create(AddrTypeId::P2PK33), + p2pkh: create(AddrTypeId::P2PKH), + p2sh: create(AddrTypeId::P2SH), + p2wpkh: create(AddrTypeId::P2WPKH), + p2wsh: create(AddrTypeId::P2WSH), + p2tr: create(AddrTypeId::P2TR), + p2a: create(AddrTypeId::P2A), + } + } + + pub fn try_from_fn(mut create: impl FnMut(AddrTypeId) -> Result) -> Result { + Ok(Self { + p2pk65: create(AddrTypeId::P2PK65)?, + p2pk33: create(AddrTypeId::P2PK33)?, + p2pkh: create(AddrTypeId::P2PKH)?, + p2sh: create(AddrTypeId::P2SH)?, + p2wpkh: create(AddrTypeId::P2WPKH)?, + p2wsh: create(AddrTypeId::P2WSH)?, + p2tr: create(AddrTypeId::P2TR)?, + p2a: create(AddrTypeId::P2A)?, + }) + } + pub fn new(mut create: F) -> Self where F: FnMut(Filter) -> T, { - Self { - p2pk65: create(Filter::Type(OutputType::P2PK65)), - p2pk33: create(Filter::Type(OutputType::P2PK33)), - p2pkh: create(Filter::Type(OutputType::P2PKH)), - p2sh: create(Filter::Type(OutputType::P2SH)), - p2wpkh: create(Filter::Type(OutputType::P2WPKH)), - p2wsh: create(Filter::Type(OutputType::P2WSH)), - p2tr: create(Filter::Type(OutputType::P2TR)), - p2a: create(Filter::Type(OutputType::P2A)), - } + Self::from_fn(|id| create(Filter::Type(id.output_type()))) } pub fn new_with_name(f: F) -> Result where F: Fn(&'static str) -> Result, { - Ok(Self { - p2pk65: f(P2PK65)?, - p2pk33: f(P2PK33)?, - p2pkh: f(P2PKH)?, - p2sh: f(P2SH)?, - p2wpkh: f(P2WPKH)?, - p2wsh: f(P2WSH)?, - p2tr: f(P2TR)?, - p2a: f(P2A)?, - }) + Self::try_from_fn(|id| f(id.name())) } pub fn map_with_name(&self, f: impl Fn(&'static str, &T) -> U) -> ByAddrType { @@ -78,16 +197,7 @@ impl ByAddrType { where F: Fn(usize) -> Result, { - Ok(Self { - p2pk65: f(0)?, - p2pk33: f(1)?, - p2pkh: f(2)?, - p2sh: f(3)?, - p2wpkh: f(4)?, - p2wsh: f(5)?, - p2tr: f(6)?, - p2a: f(7)?, - }) + Self::try_from_fn(|id| f(id as usize)) } #[inline] @@ -286,40 +396,26 @@ impl ByAddrType> { } } -/// Zip one ByAddrType with a function, producing a new ByAddrType. -pub fn zip_by_addr_type(source: &ByAddrType, f: F) -> Result> -where - F: Fn(&'static str, &S) -> Result, -{ - Ok(ByAddrType { - p2pk65: f(P2PK65, &source.p2pk65)?, - p2pk33: f(P2PK33, &source.p2pk33)?, - p2pkh: f(P2PKH, &source.p2pkh)?, - p2sh: f(P2SH, &source.p2sh)?, - p2wpkh: f(P2WPKH, &source.p2wpkh)?, - p2wsh: f(P2WSH, &source.p2wsh)?, - p2tr: f(P2TR, &source.p2tr)?, - p2a: f(P2A, &source.p2a)?, - }) -} +#[cfg(test)] +mod tests { + use vecdb::ColumnId; -/// Zip two ByAddrTypes with a function, producing a new ByAddrType. -pub fn zip2_by_addr_type( - a: &ByAddrType, - b: &ByAddrType, - f: F, -) -> Result> -where - F: Fn(&'static str, &S1, &S2) -> Result, -{ - Ok(ByAddrType { - p2pk65: f(P2PK65, &a.p2pk65, &b.p2pk65)?, - p2pk33: f(P2PK33, &a.p2pk33, &b.p2pk33)?, - p2pkh: f(P2PKH, &a.p2pkh, &b.p2pkh)?, - p2sh: f(P2SH, &a.p2sh, &b.p2sh)?, - p2wpkh: f(P2WPKH, &a.p2wpkh, &b.p2wpkh)?, - p2wsh: f(P2WSH, &a.p2wsh, &b.p2wsh)?, - p2tr: f(P2TR, &a.p2tr, &b.p2tr)?, - p2a: f(P2A, &a.p2a, &b.p2a)?, - }) + use super::{ADDR_TYPE_IDS, AddrTypeId}; + + #[test] + fn column_order_matches_named_series() { + assert_eq!(AddrTypeId::ALL, ADDR_TYPE_IDS); + + let series = AddrTypeId::series(|column, _| column); + assert!(series.values().copied().eq(ADDR_TYPE_IDS)); + } + + #[test] + fn row_order_matches_column_indexes() { + let row = AddrTypeId::from_fn(|column| column.index()); + + for column in ADDR_TYPE_IDS { + assert_eq!(*column.get(&row), column.index()); + } + } } diff --git a/crates/brk_cohort/src/by_type.rs b/crates/brk_cohort/src/by_type.rs index 80c7cd914..b08fafccf 100644 --- a/crates/brk_cohort/src/by_type.rs +++ b/crates/brk_cohort/src/by_type.rs @@ -3,10 +3,124 @@ use std::ops::{Add, AddAssign}; use brk_traversable::Traversable; use brk_types::OutputType; use rayon::prelude::*; +use vecdb::{ColumnId, VecValue, Version}; use super::{Filter, SpendableType, UnspendableType}; pub const OP_RETURN: &str = "op_return"; +pub const OUTPUT_TYPE_COUNT: usize = OutputType::COUNT; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum OutputTypeId { + P2PK65, + P2PK33, + P2PKH, + P2MS, + P2SH, + P2WPKH, + P2WSH, + P2TR, + P2A, + Unknown, + Empty, + OpReturn, +} + +pub const OUTPUT_TYPE_IDS: [OutputTypeId; OUTPUT_TYPE_COUNT] = [ + OutputTypeId::P2PK65, + OutputTypeId::P2PK33, + OutputTypeId::P2PKH, + OutputTypeId::P2MS, + OutputTypeId::P2SH, + OutputTypeId::P2WPKH, + OutputTypeId::P2WSH, + OutputTypeId::P2TR, + OutputTypeId::P2A, + OutputTypeId::Unknown, + OutputTypeId::Empty, + OutputTypeId::OpReturn, +]; + +impl OutputTypeId { + pub const fn from_output_type(value: OutputType) -> Self { + match value { + OutputType::P2PK65 => Self::P2PK65, + OutputType::P2PK33 => Self::P2PK33, + OutputType::P2PKH => Self::P2PKH, + OutputType::P2MS => Self::P2MS, + OutputType::P2SH => Self::P2SH, + OutputType::P2WPKH => Self::P2WPKH, + OutputType::P2WSH => Self::P2WSH, + OutputType::P2TR => Self::P2TR, + OutputType::P2A => Self::P2A, + OutputType::Unknown => Self::Unknown, + OutputType::Empty => Self::Empty, + OutputType::OpReturn => Self::OpReturn, + } + } + + pub const fn output_type(self) -> OutputType { + match self { + Self::P2PK65 => OutputType::P2PK65, + Self::P2PK33 => OutputType::P2PK33, + Self::P2PKH => OutputType::P2PKH, + Self::P2MS => OutputType::P2MS, + Self::P2SH => OutputType::P2SH, + Self::P2WPKH => OutputType::P2WPKH, + Self::P2WSH => OutputType::P2WSH, + Self::P2TR => OutputType::P2TR, + Self::P2A => OutputType::P2A, + Self::Unknown => OutputType::Unknown, + Self::Empty => OutputType::Empty, + Self::OpReturn => OutputType::OpReturn, + } + } +} + +impl ColumnId for OutputTypeId { + type Row + = [T; OUTPUT_TYPE_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &OUTPUT_TYPE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self as usize] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self as usize] + } + + #[inline] + fn from_fn(mut f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| f(OUTPUT_TYPE_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(f) + } +} #[derive(Default, Clone, Debug, Traversable)] pub struct ByType { @@ -137,3 +251,30 @@ where self.unspendable += rhs.unspendable; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn column_ids_match_by_type_order() { + let by_type = ByType::new(|filter, _| { + let Filter::Type(output_type) = filter else { + unreachable!() + }; + output_type + }); + let output_types: Vec<_> = by_type.iter().copied().collect(); + let column_output_types: Vec<_> = OutputTypeId::ALL + .iter() + .map(|column| column.output_type()) + .collect(); + + assert_eq!(column_output_types, output_types); + + let row = OutputTypeId::from_fn(|column| column.index()); + for column in OutputTypeId::ALL { + assert_eq!(*column.get(&row), column.index()); + } + } +} diff --git a/crates/brk_cohort/src/lib.rs b/crates/brk_cohort/src/lib.rs index dd906c0f2..86a0de52a 100644 --- a/crates/brk_cohort/src/lib.rs +++ b/crates/brk_cohort/src/lib.rs @@ -19,6 +19,7 @@ mod loss; mod over_age; mod over_amount; mod profit; +mod profitability; mod profitability_range; mod spendable_type; mod state_level; @@ -49,6 +50,7 @@ pub use loss::*; pub use over_age::*; pub use over_amount::*; pub use profit::*; +pub use profitability::*; pub use profitability_range::*; pub use spendable_type::*; pub use state_level::*; diff --git a/crates/brk_cohort/src/profitability.rs b/crates/brk_cohort/src/profitability.rs new file mode 100644 index 000000000..d3c40ad3e --- /dev/null +++ b/crates/brk_cohort/src/profitability.rs @@ -0,0 +1,479 @@ +use std::{fmt, ops::AddAssign}; + +use schemars::JsonSchema; +use serde::Serialize; +use vecdb::{ColumnId, Formattable, VecValue, Version}; + +use crate::{ + LOSS_COUNT, Loss, PROFIT_COUNT, PROFITABILITY_RANGE_COUNT, Profit, ProfitabilityRange, +}; + +pub const PROFITABILITY_COUNT: usize = PROFITABILITY_RANGE_COUNT + PROFIT_COUNT + LOSS_COUNT; + +#[derive(Debug, Clone, Serialize, JsonSchema)] +pub struct ProfitabilityRow { + pub range: [T; PROFITABILITY_RANGE_COUNT], + pub profit: [T; PROFIT_COUNT], + pub loss: [T; LOSS_COUNT], +} + +impl ProfitabilityRow +where + T: AddAssign + Copy + Default, +{ + pub fn from_ranges(range: [T; PROFITABILITY_RANGE_COUNT]) -> Self { + let (profit_ranges, loss_ranges) = range.split_at(PROFIT_COUNT + 1); + + let mut profit = [T::default(); PROFIT_COUNT]; + let mut total = profit_ranges[0]; + for (threshold, &value) in profit.iter_mut().rev().zip(&profit_ranges[1..]) { + total += value; + *threshold = total; + } + + let mut loss = [T::default(); LOSS_COUNT]; + let mut total = loss_ranges[loss_ranges.len() - 1]; + for (threshold, &value) in loss + .iter_mut() + .rev() + .zip(loss_ranges[..loss_ranges.len() - 1].iter().rev()) + { + total += value; + *threshold = total; + } + + Self { + range, + profit, + loss, + } + } +} + +impl Formattable for ProfitabilityRow { + fn write_to(&self, buf: &mut Vec) { + buf.extend_from_slice(b"{\"range\":"); + self.range.write_to(buf); + buf.extend_from_slice(b",\"profit\":"); + self.profit.write_to(buf); + buf.extend_from_slice(b",\"loss\":"); + self.loss.write_to(buf); + buf.push(b'}'); + } + + fn fmt_csv(&self, output: &mut String) -> fmt::Result { + let mut json = Vec::new(); + self.write_to(&mut json); + let json = std::str::from_utf8(&json).map_err(|_| fmt::Error)?; + + output.push('"'); + for character in json.chars() { + if character == '"' { + output.push('"'); + } + output.push(character); + } + output.push('"'); + Ok(()) + } +} + +/// Every profitability range and aggregate threshold in column storage order. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum ProfitabilityId { + RangeOver1000PctInProfit, + Range500To1000PctInProfit, + Range300To500PctInProfit, + Range200To300PctInProfit, + Range100To200PctInProfit, + Range90To100PctInProfit, + Range80To90PctInProfit, + Range70To80PctInProfit, + Range60To70PctInProfit, + Range50To60PctInProfit, + Range40To50PctInProfit, + Range30To40PctInProfit, + Range20To30PctInProfit, + Range10To20PctInProfit, + Range0To10PctInProfit, + Range0To10PctInLoss, + Range10To20PctInLoss, + Range20To30PctInLoss, + Range30To40PctInLoss, + Range40To50PctInLoss, + Range50To60PctInLoss, + Range60To70PctInLoss, + Range70To80PctInLoss, + Range80To90PctInLoss, + Range90To100PctInLoss, + Profit, + ProfitOver10Pct, + ProfitOver20Pct, + ProfitOver30Pct, + ProfitOver40Pct, + ProfitOver50Pct, + ProfitOver60Pct, + ProfitOver70Pct, + ProfitOver80Pct, + ProfitOver90Pct, + ProfitOver100Pct, + ProfitOver200Pct, + ProfitOver300Pct, + ProfitOver500Pct, + Loss, + LossOver10Pct, + LossOver20Pct, + LossOver30Pct, + LossOver40Pct, + LossOver50Pct, + LossOver60Pct, + LossOver70Pct, + LossOver80Pct, +} + +pub const PROFITABILITY_IDS: [ProfitabilityId; PROFITABILITY_COUNT] = [ + ProfitabilityId::RangeOver1000PctInProfit, + ProfitabilityId::Range500To1000PctInProfit, + ProfitabilityId::Range300To500PctInProfit, + ProfitabilityId::Range200To300PctInProfit, + ProfitabilityId::Range100To200PctInProfit, + ProfitabilityId::Range90To100PctInProfit, + ProfitabilityId::Range80To90PctInProfit, + ProfitabilityId::Range70To80PctInProfit, + ProfitabilityId::Range60To70PctInProfit, + ProfitabilityId::Range50To60PctInProfit, + ProfitabilityId::Range40To50PctInProfit, + ProfitabilityId::Range30To40PctInProfit, + ProfitabilityId::Range20To30PctInProfit, + ProfitabilityId::Range10To20PctInProfit, + ProfitabilityId::Range0To10PctInProfit, + ProfitabilityId::Range0To10PctInLoss, + ProfitabilityId::Range10To20PctInLoss, + ProfitabilityId::Range20To30PctInLoss, + ProfitabilityId::Range30To40PctInLoss, + ProfitabilityId::Range40To50PctInLoss, + ProfitabilityId::Range50To60PctInLoss, + ProfitabilityId::Range60To70PctInLoss, + ProfitabilityId::Range70To80PctInLoss, + ProfitabilityId::Range80To90PctInLoss, + ProfitabilityId::Range90To100PctInLoss, + ProfitabilityId::Profit, + ProfitabilityId::ProfitOver10Pct, + ProfitabilityId::ProfitOver20Pct, + ProfitabilityId::ProfitOver30Pct, + ProfitabilityId::ProfitOver40Pct, + ProfitabilityId::ProfitOver50Pct, + ProfitabilityId::ProfitOver60Pct, + ProfitabilityId::ProfitOver70Pct, + ProfitabilityId::ProfitOver80Pct, + ProfitabilityId::ProfitOver90Pct, + ProfitabilityId::ProfitOver100Pct, + ProfitabilityId::ProfitOver200Pct, + ProfitabilityId::ProfitOver300Pct, + ProfitabilityId::ProfitOver500Pct, + ProfitabilityId::Loss, + ProfitabilityId::LossOver10Pct, + ProfitabilityId::LossOver20Pct, + ProfitabilityId::LossOver30Pct, + ProfitabilityId::LossOver40Pct, + ProfitabilityId::LossOver50Pct, + ProfitabilityId::LossOver60Pct, + ProfitabilityId::LossOver70Pct, + ProfitabilityId::LossOver80Pct, +]; + +impl ProfitabilityId { + pub fn range_ids() -> &'static [Self] { + &PROFITABILITY_IDS[..PROFITABILITY_RANGE_COUNT] + } + + pub fn profit_ids() -> &'static [Self] { + &PROFITABILITY_IDS[PROFITABILITY_RANGE_COUNT..PROFITABILITY_RANGE_COUNT + PROFIT_COUNT] + } + + pub fn loss_ids() -> &'static [Self] { + &PROFITABILITY_IDS[PROFITABILITY_RANGE_COUNT + PROFIT_COUNT..] + } + + pub fn range_series( + mut create: impl FnMut(Self, &'static str) -> T, + ) -> ProfitabilityRange { + let names = ProfitabilityRange::names(); + ProfitabilityRange { + over_1000pct_in_profit: create( + Self::RangeOver1000PctInProfit, + names.over_1000pct_in_profit.id, + ), + _500pct_to_1000pct_in_profit: create( + Self::Range500To1000PctInProfit, + names._500pct_to_1000pct_in_profit.id, + ), + _300pct_to_500pct_in_profit: create( + Self::Range300To500PctInProfit, + names._300pct_to_500pct_in_profit.id, + ), + _200pct_to_300pct_in_profit: create( + Self::Range200To300PctInProfit, + names._200pct_to_300pct_in_profit.id, + ), + _100pct_to_200pct_in_profit: create( + Self::Range100To200PctInProfit, + names._100pct_to_200pct_in_profit.id, + ), + _90pct_to_100pct_in_profit: create( + Self::Range90To100PctInProfit, + names._90pct_to_100pct_in_profit.id, + ), + _80pct_to_90pct_in_profit: create( + Self::Range80To90PctInProfit, + names._80pct_to_90pct_in_profit.id, + ), + _70pct_to_80pct_in_profit: create( + Self::Range70To80PctInProfit, + names._70pct_to_80pct_in_profit.id, + ), + _60pct_to_70pct_in_profit: create( + Self::Range60To70PctInProfit, + names._60pct_to_70pct_in_profit.id, + ), + _50pct_to_60pct_in_profit: create( + Self::Range50To60PctInProfit, + names._50pct_to_60pct_in_profit.id, + ), + _40pct_to_50pct_in_profit: create( + Self::Range40To50PctInProfit, + names._40pct_to_50pct_in_profit.id, + ), + _30pct_to_40pct_in_profit: create( + Self::Range30To40PctInProfit, + names._30pct_to_40pct_in_profit.id, + ), + _20pct_to_30pct_in_profit: create( + Self::Range20To30PctInProfit, + names._20pct_to_30pct_in_profit.id, + ), + _10pct_to_20pct_in_profit: create( + Self::Range10To20PctInProfit, + names._10pct_to_20pct_in_profit.id, + ), + _0pct_to_10pct_in_profit: create( + Self::Range0To10PctInProfit, + names._0pct_to_10pct_in_profit.id, + ), + _0pct_to_10pct_in_loss: create( + Self::Range0To10PctInLoss, + names._0pct_to_10pct_in_loss.id, + ), + _10pct_to_20pct_in_loss: create( + Self::Range10To20PctInLoss, + names._10pct_to_20pct_in_loss.id, + ), + _20pct_to_30pct_in_loss: create( + Self::Range20To30PctInLoss, + names._20pct_to_30pct_in_loss.id, + ), + _30pct_to_40pct_in_loss: create( + Self::Range30To40PctInLoss, + names._30pct_to_40pct_in_loss.id, + ), + _40pct_to_50pct_in_loss: create( + Self::Range40To50PctInLoss, + names._40pct_to_50pct_in_loss.id, + ), + _50pct_to_60pct_in_loss: create( + Self::Range50To60PctInLoss, + names._50pct_to_60pct_in_loss.id, + ), + _60pct_to_70pct_in_loss: create( + Self::Range60To70PctInLoss, + names._60pct_to_70pct_in_loss.id, + ), + _70pct_to_80pct_in_loss: create( + Self::Range70To80PctInLoss, + names._70pct_to_80pct_in_loss.id, + ), + _80pct_to_90pct_in_loss: create( + Self::Range80To90PctInLoss, + names._80pct_to_90pct_in_loss.id, + ), + _90pct_to_100pct_in_loss: create( + Self::Range90To100PctInLoss, + names._90pct_to_100pct_in_loss.id, + ), + } + } + + pub fn profit_series(mut create: impl FnMut(Self, &'static str) -> T) -> Profit { + let names = Profit::names(); + Profit { + all: create(Self::Profit, names.all.id), + _10pct: create(Self::ProfitOver10Pct, names._10pct.id), + _20pct: create(Self::ProfitOver20Pct, names._20pct.id), + _30pct: create(Self::ProfitOver30Pct, names._30pct.id), + _40pct: create(Self::ProfitOver40Pct, names._40pct.id), + _50pct: create(Self::ProfitOver50Pct, names._50pct.id), + _60pct: create(Self::ProfitOver60Pct, names._60pct.id), + _70pct: create(Self::ProfitOver70Pct, names._70pct.id), + _80pct: create(Self::ProfitOver80Pct, names._80pct.id), + _90pct: create(Self::ProfitOver90Pct, names._90pct.id), + _100pct: create(Self::ProfitOver100Pct, names._100pct.id), + _200pct: create(Self::ProfitOver200Pct, names._200pct.id), + _300pct: create(Self::ProfitOver300Pct, names._300pct.id), + _500pct: create(Self::ProfitOver500Pct, names._500pct.id), + } + } + + pub fn loss_series(mut create: impl FnMut(Self, &'static str) -> T) -> Loss { + let names = Loss::names(); + Loss { + all: create(Self::Loss, names.all.id), + _10pct: create(Self::LossOver10Pct, names._10pct.id), + _20pct: create(Self::LossOver20Pct, names._20pct.id), + _30pct: create(Self::LossOver30Pct, names._30pct.id), + _40pct: create(Self::LossOver40Pct, names._40pct.id), + _50pct: create(Self::LossOver50Pct, names._50pct.id), + _60pct: create(Self::LossOver60Pct, names._60pct.id), + _70pct: create(Self::LossOver70Pct, names._70pct.id), + _80pct: create(Self::LossOver80Pct, names._80pct.id), + } + } + + #[inline] + pub const fn is_profit(self) -> bool { + let index = self as usize; + index < PROFIT_COUNT + 1 + || (index >= PROFITABILITY_RANGE_COUNT + && index < PROFITABILITY_RANGE_COUNT + PROFIT_COUNT) + } +} + +impl ColumnId for ProfitabilityId { + type Row + = ProfitabilityRow + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &PROFITABILITY_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + let index = self as usize; + if index < PROFITABILITY_RANGE_COUNT { + &row.range[index] + } else if index < PROFITABILITY_RANGE_COUNT + PROFIT_COUNT { + &row.profit[index - PROFITABILITY_RANGE_COUNT] + } else { + &row.loss[index - PROFITABILITY_RANGE_COUNT - PROFIT_COUNT] + } + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + let index = self as usize; + if index < PROFITABILITY_RANGE_COUNT { + &mut row.range[index] + } else if index < PROFITABILITY_RANGE_COUNT + PROFIT_COUNT { + &mut row.profit[index - PROFITABILITY_RANGE_COUNT] + } else { + &mut row.loss[index - PROFITABILITY_RANGE_COUNT - PROFIT_COUNT] + } + } + + #[inline] + fn from_fn(mut f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + ProfitabilityRow { + range: std::array::from_fn(|index| f(PROFITABILITY_IDS[index])), + profit: std::array::from_fn(|index| { + f(PROFITABILITY_IDS[PROFITABILITY_RANGE_COUNT + index]) + }), + loss: std::array::from_fn(|index| { + f(PROFITABILITY_IDS[PROFITABILITY_RANGE_COUNT + PROFIT_COUNT + index]) + }), + } + } + + #[inline] + fn map(row: Self::Row, mut f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + ProfitabilityRow { + range: row.range.map(&mut f), + profit: row.profit.map(&mut f), + loss: row.loss.map(f), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn profitability_ids_cover_the_schema_in_storage_order() { + assert_eq!( + ProfitabilityId::range_ids().len(), + PROFITABILITY_RANGE_COUNT + ); + assert_eq!(ProfitabilityId::profit_ids().len(), PROFIT_COUNT); + assert_eq!(ProfitabilityId::loss_ids().len(), LOSS_COUNT); + for (index, id) in PROFITABILITY_IDS.into_iter().enumerate() { + assert_eq!(id.index(), index); + } + + assert!( + ProfitabilityId::range_series(|id, _| id) + .iter() + .copied() + .eq(ProfitabilityId::range_ids().iter().copied()) + ); + assert!( + ProfitabilityId::profit_series(|id, _| id) + .iter() + .copied() + .eq(ProfitabilityId::profit_ids().iter().copied()) + ); + assert!( + ProfitabilityId::loss_series(|id, _| id) + .iter() + .copied() + .eq(ProfitabilityId::loss_ids().iter().copied()) + ); + } + + #[test] + fn rows_expand_ranges_into_profit_prefixes_and_loss_suffixes() { + let ranges = std::array::from_fn(|index| index + 1); + let row = ProfitabilityRow::from_ranges(ranges); + + assert_eq!( + row.profit[0], + ranges[..PROFIT_COUNT + 1].iter().copied().sum::() + ); + assert_eq!(row.profit[PROFIT_COUNT - 1], ranges[0] + ranges[1]); + assert_eq!( + row.loss[0], + ranges[PROFIT_COUNT + 1..].iter().copied().sum::() + ); + assert_eq!( + row.loss[LOSS_COUNT - 1], + ranges[PROFITABILITY_RANGE_COUNT - 2..] + .iter() + .copied() + .sum::() + ); + } +} diff --git a/crates/brk_cohort/src/spendable_type.rs b/crates/brk_cohort/src/spendable_type.rs index 9eea975ea..df492cd78 100644 --- a/crates/brk_cohort/src/spendable_type.rs +++ b/crates/brk_cohort/src/spendable_type.rs @@ -4,9 +4,121 @@ use brk_traversable::Traversable; use brk_types::OutputType; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde::Serialize; +use vecdb::{ColumnId, VecValue, Version}; use super::{CohortName, Filter}; +pub const SPENDABLE_TYPE_COUNT: usize = 11; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum SpendableTypeId { + P2PK65, + P2PK33, + P2PKH, + P2MS, + P2SH, + P2WPKH, + P2WSH, + P2TR, + P2A, + Unknown, + Empty, +} + +pub const SPENDABLE_TYPE_IDS: [SpendableTypeId; SPENDABLE_TYPE_COUNT] = [ + SpendableTypeId::P2PK65, + SpendableTypeId::P2PK33, + SpendableTypeId::P2PKH, + SpendableTypeId::P2MS, + SpendableTypeId::P2SH, + SpendableTypeId::P2WPKH, + SpendableTypeId::P2WSH, + SpendableTypeId::P2TR, + SpendableTypeId::P2A, + SpendableTypeId::Unknown, + SpendableTypeId::Empty, +]; + +impl SpendableTypeId { + pub const fn from_output_type(value: OutputType) -> Option { + match value { + OutputType::P2PK65 => Some(Self::P2PK65), + OutputType::P2PK33 => Some(Self::P2PK33), + OutputType::P2PKH => Some(Self::P2PKH), + OutputType::P2MS => Some(Self::P2MS), + OutputType::P2SH => Some(Self::P2SH), + OutputType::P2WPKH => Some(Self::P2WPKH), + OutputType::P2WSH => Some(Self::P2WSH), + OutputType::P2TR => Some(Self::P2TR), + OutputType::P2A => Some(Self::P2A), + OutputType::Unknown => Some(Self::Unknown), + OutputType::Empty => Some(Self::Empty), + OutputType::OpReturn => None, + } + } + + pub const fn output_type(self) -> OutputType { + match self { + Self::P2PK65 => OutputType::P2PK65, + Self::P2PK33 => OutputType::P2PK33, + Self::P2PKH => OutputType::P2PKH, + Self::P2MS => OutputType::P2MS, + Self::P2SH => OutputType::P2SH, + Self::P2WPKH => OutputType::P2WPKH, + Self::P2WSH => OutputType::P2WSH, + Self::P2TR => OutputType::P2TR, + Self::P2A => OutputType::P2A, + Self::Unknown => OutputType::Unknown, + Self::Empty => OutputType::Empty, + } + } +} + +impl ColumnId for SpendableTypeId { + type Row + = [T; SPENDABLE_TYPE_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &SPENDABLE_TYPE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self as usize] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self as usize] + } + + #[inline] + fn from_fn(mut f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| f(SPENDABLE_TYPE_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(f) + } +} + /// Spendable type values pub const SPENDABLE_TYPE_VALUES: SpendableType = SpendableType { p2pk65: OutputType::P2PK65, @@ -279,3 +391,28 @@ where self.empty += rhs.empty; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn column_ids_match_spendable_type_order() { + let output_types: Vec<_> = SPENDABLE_TYPE_VALUES.iter().copied().collect(); + let column_output_types: Vec<_> = SpendableTypeId::ALL + .iter() + .map(|column| column.output_type()) + .collect(); + + assert_eq!(column_output_types, output_types); + assert_eq!( + SpendableTypeId::from_output_type(OutputType::OpReturn), + None + ); + + let row = SpendableTypeId::from_fn(|column| column.index()); + for column in SpendableTypeId::ALL { + assert_eq!(*column.get(&row), column.index()); + } + } +} diff --git a/crates/brk_computer/src/distribution/addr/activity.rs b/crates/brk_computer/src/distribution/addr/activity.rs index dd5bc35f2..f39291e80 100644 --- a/crates/brk_computer/src/distribution/addr/activity.rs +++ b/crates/brk_computer/src/distribution/addr/activity.rs @@ -1,29 +1,22 @@ -//! Address activity tracking - per-block counts of address behaviors. -//! -//! Tracks global and per-address-type activity metrics: -//! -//! | Metric | Description | -//! |--------|-------------| -//! | `receiving` | Unique addresses that received this block | -//! | `sending` | Unique addresses that sent this block | -//! | `reactivated` | Addresses that were empty and now have funds | -//! | `bidirectional` | Addresses that both sent AND received in same block | -//! | `active` | Distinct addresses involved (sent ∪ received) | +//! Per-block address activity, split by address type. -use brk_cohort::ByAddrType; +use brk_cohort::{AddrTypeId, ByAddrType}; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::Version; +use brk_types::{StoredU32, StoredU64, Version}; use derive_more::{Deref, DerefMut}; use rayon::prelude::*; -use vecdb::{AnyStoredVec, Database, Rw, StorageMode}; +use vecdb::{AnyStoredVec, AnyVec, ColumnId, Database, ReadOnlyClone, Rw, StorageMode}; use crate::{ indexes, - internal::{CachedWindowStartVec, CountPerBlockRollingAverage, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, LazyPerBlockCumulativeAverage, + StoredU64ToStoredU32, Windows, WithAddrTypes, + }, }; -/// Per-block activity counts - reset each block. +/// Per-block activity counts, reset after every block. #[derive(Debug, Default, Clone)] pub struct BlockActivityCounts { pub reactivated: u32, @@ -33,217 +26,69 @@ pub struct BlockActivityCounts { } impl BlockActivityCounts { - /// Reset all counts to zero. #[inline] pub(crate) fn reset(&mut self) { *self = Self::default(); } + + #[inline(always)] + fn active(&self) -> u32 { + debug_assert!(self.bidirectional <= self.sending.min(self.receiving)); + self.sending + self.receiving - self.bidirectional + } } -/// Per-address-type activity counts - aggregated during block processing. +/// Activity counts accumulated during block processing for each address type. #[derive(Debug, Default, Deref, DerefMut)] pub struct AddrTypeToActivityCounts(pub ByAddrType); impl AddrTypeToActivityCounts { - /// Reset all per-type counts. pub(crate) fn reset(&mut self) { - self.0.values_mut().for_each(|v| v.reset()); + self.0.values_mut().for_each(BlockActivityCounts::reset); } - /// Sum all types to get totals. - pub(crate) fn totals(&self) -> BlockActivityCounts { - let mut total = BlockActivityCounts::default(); - for counts in self.0.values() { - total.reactivated += counts.reactivated; - total.sending += counts.sending; - total.receiving += counts.receiving; - total.bidirectional += counts.bidirectional; - } - total + pub(crate) fn active(&self) -> u32 { + self.0.values().map(BlockActivityCounts::active).sum() } -} -/// Activity count vectors for a single category (e.g., one address type or "all"). -#[derive(Traversable)] -pub struct ActivityCountVecs { - pub reactivated: CountPerBlockRollingAverage, - pub sending: CountPerBlockRollingAverage, - pub receiving: CountPerBlockRollingAverage, - pub bidirectional: CountPerBlockRollingAverage, - /// Distinct addresses involved in this block (sent ∪ received), - /// computed at push time as `sending + receiving - bidirectional` - /// via inclusion-exclusion. For per-type instances this is - /// per-type. For the `all` aggregate it's the cross-type total. - pub active: CountPerBlockRollingAverage, -} - -impl ActivityCountVecs { - /// `prefix` is prepended to each field's disk name. Use `""` for the - /// "all" aggregate and `"{type}_"` for per-address-type instances. - /// Field names are suffixed with `_addrs` so the final disk series - /// are e.g. `active_addrs`, `p2tr_bidirectional_addrs`. - pub(crate) fn forced_import( - db: &Database, - prefix: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result { - Ok(Self { - reactivated: CountPerBlockRollingAverage::forced_import( - db, - &format!("{prefix}reactivated_addrs"), - version, - indexes, - cached_starts, - )?, - sending: CountPerBlockRollingAverage::forced_import( - db, - &format!("{prefix}sending_addrs"), - version, - indexes, - cached_starts, - )?, - receiving: CountPerBlockRollingAverage::forced_import( - db, - &format!("{prefix}receiving_addrs"), - version, - indexes, - cached_starts, - )?, - bidirectional: CountPerBlockRollingAverage::forced_import( - db, - &format!("{prefix}bidirectional_addrs"), - version, - indexes, - cached_starts, - )?, - active: CountPerBlockRollingAverage::forced_import( - db, - &format!("{prefix}active_addrs"), - version, - indexes, - cached_starts, - )?, + #[inline(always)] + fn row( + &self, + value: impl Fn(&BlockActivityCounts) -> u32, + ) -> ::Row { + AddrTypeId::from_fn(|column| { + StoredU64::from(StoredU32::from(value(column.select(&self.0)))) }) } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.reactivated - .min_stateful_len() - .min(self.sending.min_stateful_len()) - .min(self.receiving.min_stateful_len()) - .min(self.bidirectional.min_stateful_len()) - .min(self.active.min_stateful_len()) - } - - pub(crate) fn par_iter_height_mut( - &mut self, - ) -> impl ParallelIterator { - [ - self.reactivated.stored_mut(), - self.sending.stored_mut(), - self.receiving.stored_mut(), - self.bidirectional.stored_mut(), - self.active.stored_mut(), - ] - .into_par_iter() - } - - pub(crate) fn reset_height(&mut self) -> Result<()> { - self.reactivated.reset()?; - self.sending.reset()?; - self.receiving.reset()?; - self.bidirectional.reset()?; - self.active.reset()?; - Ok(()) - } - - #[inline(always)] - pub(crate) fn push_height(&mut self, counts: &BlockActivityCounts) { - self.reactivated.push_block(counts.reactivated.into()); - self.sending.push_block(counts.sending.into()); - self.receiving.push_block(counts.receiving.into()); - self.bidirectional.push_block(counts.bidirectional.into()); - let active = counts.sending + counts.receiving - counts.bidirectional; - self.active.push_block(active.into()); - } } -/// Per-address-type activity count vecs. +#[derive(Clone, Traversable)] +pub struct ActivityCountVecs { + pub reactivated: LazyPerBlockCumulativeAverage, + pub sending: LazyPerBlockCumulativeAverage, + pub receiving: LazyPerBlockCumulativeAverage, + pub bidirectional: LazyPerBlockCumulativeAverage, + pub active: LazyPerBlockCumulativeAverage, +} + +/// Five metric-first cumulative matrices with cohort-first public views. #[derive(Deref, DerefMut, Traversable)] -pub struct AddrTypeToActivityCountVecs(ByAddrType>); - -impl From> for AddrTypeToActivityCountVecs { - #[inline] - fn from(value: ByAddrType) -> Self { - Self(value) - } -} - -impl AddrTypeToActivityCountVecs { - pub(crate) fn forced_import( - db: &Database, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result { - Ok(Self::from(ByAddrType::::new_with_name( - |type_name| { - ActivityCountVecs::forced_import( - db, - &format!("{type_name}_"), - version, - indexes, - cached_starts, - ) - }, - )?)) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.0 - .values() - .map(|v| v.min_stateful_len()) - .min() - .unwrap_or(0) - } - - pub(crate) fn par_iter_height_mut( - &mut self, - ) -> impl ParallelIterator { - let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new(); - for type_vecs in self.0.values_mut() { - vecs.push(type_vecs.reactivated.stored_mut()); - vecs.push(type_vecs.sending.stored_mut()); - vecs.push(type_vecs.receiving.stored_mut()); - vecs.push(type_vecs.bidirectional.stored_mut()); - vecs.push(type_vecs.active.stored_mut()); - } - vecs.into_par_iter() - } - - pub(crate) fn reset_height(&mut self) -> Result<()> { - for v in self.0.values_mut() { - v.reset_height()?; - } - Ok(()) - } - - #[inline(always)] - pub(crate) fn push_height(&mut self, counts: &AddrTypeToActivityCounts) { - for (vecs, c) in self.0.values_mut().zip(counts.0.values()) { - vecs.push_height(c); - } - } -} - -/// Storage for activity metrics (global + per type). -#[derive(Traversable)] pub struct AddrActivityVecs { - pub all: ActivityCountVecs, + #[deref] + #[deref_mut] #[traversable(flatten)] - pub by_addr_type: AddrTypeToActivityCountVecs, + pub series: WithAddrTypes, + + #[traversable(hidden)] + cumulative_reactivated: ColumnarPerBlockCumulativeRolling, + #[traversable(hidden)] + cumulative_sending: ColumnarPerBlockCumulativeRolling, + #[traversable(hidden)] + cumulative_receiving: ColumnarPerBlockCumulativeRolling, + #[traversable(hidden)] + cumulative_bidirectional: ColumnarPerBlockCumulativeRolling, + #[traversable(hidden)] + cumulative_active: ColumnarPerBlockCumulativeRolling, } impl AddrActivityVecs { @@ -253,41 +98,174 @@ impl AddrActivityVecs { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { + let cumulative_version = version + Version::TWO; + let cumulative_reactivated = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "reactivated_addrs_by_type_cumulative", + cumulative_version, + |_| (), + )?; + let cumulative_sending = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "sending_addrs_by_type_cumulative", + cumulative_version, + |_| (), + )?; + let cumulative_receiving = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "receiving_addrs_by_type_cumulative", + cumulative_version, + |_| (), + )?; + let cumulative_bidirectional = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "bidirectional_addrs_by_type_cumulative", + cumulative_version, + |_| (), + )?; + let cumulative_active = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "active_addrs_by_type_cumulative", + cumulative_version, + |_| (), + )?; + + let reactivated = WithAddrTypes::from_columnar_cumulative_average_source( + "reactivated_addrs", + version, + &cumulative_reactivated.cumulative.read_only_clone(), + indexes, + cached_starts, + ); + let sending = WithAddrTypes::from_columnar_cumulative_average_source( + "sending_addrs", + version, + &cumulative_sending.cumulative.read_only_clone(), + indexes, + cached_starts, + ); + let receiving = WithAddrTypes::from_columnar_cumulative_average_source( + "receiving_addrs", + version, + &cumulative_receiving.cumulative.read_only_clone(), + indexes, + cached_starts, + ); + let bidirectional = WithAddrTypes::from_columnar_cumulative_average_source( + "bidirectional_addrs", + version, + &cumulative_bidirectional.cumulative.read_only_clone(), + indexes, + cached_starts, + ); + let active = WithAddrTypes::from_columnar_cumulative_average_source( + "active_addrs", + version, + &cumulative_active.cumulative.read_only_clone(), + indexes, + cached_starts, + ); + + let by_addr_type = AddrTypeId::series(|column, _| ActivityCountVecs { + reactivated: column.select(&reactivated.by_addr_type).clone(), + sending: column.select(&sending.by_addr_type).clone(), + receiving: column.select(&receiving.by_addr_type).clone(), + bidirectional: column.select(&bidirectional.by_addr_type).clone(), + active: column.select(&active.by_addr_type).clone(), + }); + let series = WithAddrTypes { + all: ActivityCountVecs { + reactivated: reactivated.all, + sending: sending.all, + receiving: receiving.all, + bidirectional: bidirectional.all, + active: active.all, + }, + by_addr_type, + }; + Ok(Self { - all: ActivityCountVecs::forced_import(db, "", version, indexes, cached_starts)?, - by_addr_type: AddrTypeToActivityCountVecs::forced_import( - db, - version, - indexes, - cached_starts, - )?, + series, + cumulative_reactivated, + cumulative_sending, + cumulative_receiving, + cumulative_bidirectional, + cumulative_active, }) } pub(crate) fn min_stateful_len(&self) -> usize { - self.all - .min_stateful_len() - .min(self.by_addr_type.min_stateful_len()) + [ + self.cumulative_reactivated.cumulative.len(), + self.cumulative_sending.cumulative.len(), + self.cumulative_receiving.cumulative.len(), + self.cumulative_bidirectional.cumulative.len(), + self.cumulative_active.cumulative.len(), + ] + .into_iter() + .min() + .unwrap_or_default() } pub(crate) fn par_iter_height_mut( &mut self, ) -> impl ParallelIterator { - self.all - .par_iter_height_mut() - .chain(self.by_addr_type.par_iter_height_mut()) + [ + self.cumulative_reactivated.stored_mut(), + self.cumulative_sending.stored_mut(), + self.cumulative_receiving.stored_mut(), + self.cumulative_bidirectional.stored_mut(), + self.cumulative_active.stored_mut(), + ] + .into_par_iter() } pub(crate) fn reset_height(&mut self) -> Result<()> { - self.all.reset_height()?; - self.by_addr_type.reset_height()?; + self.cumulative_reactivated.reset()?; + self.cumulative_sending.reset()?; + self.cumulative_receiving.reset()?; + self.cumulative_bidirectional.reset()?; + self.cumulative_active.reset()?; Ok(()) } #[inline(always)] pub(crate) fn push_height(&mut self, counts: &AddrTypeToActivityCounts) { - let totals = counts.totals(); - self.all.push_height(&totals); - self.by_addr_type.push_height(counts); + self.cumulative_reactivated + .push_block(counts.row(|counts| counts.reactivated)); + self.cumulative_sending + .push_block(counts.row(|counts| counts.sending)); + self.cumulative_receiving + .push_block(counts.row(|counts| counts.receiving)); + self.cumulative_bidirectional + .push_block(counts.row(|counts| counts.bidirectional)); + self.cumulative_active + .push_block(counts.row(BlockActivityCounts::active)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn active_count_sums_distinct_addresses_across_types() { + let counts = AddrTypeToActivityCounts(ByAddrType { + p2pkh: BlockActivityCounts { + sending: 5, + receiving: 4, + bidirectional: 2, + ..BlockActivityCounts::default() + }, + p2tr: BlockActivityCounts { + sending: 3, + receiving: 2, + bidirectional: 1, + ..BlockActivityCounts::default() + }, + ..ByAddrType::default() + }); + + assert_eq!(counts.active(), 11); } } diff --git a/crates/brk_computer/src/distribution/addr/count/all_vecs.rs b/crates/brk_computer/src/distribution/addr/count/all_vecs.rs index 1cb4eda6f..d14da93e5 100644 --- a/crates/brk_computer/src/distribution/addr/count/all_vecs.rs +++ b/crates/brk_computer/src/distribution/addr/count/all_vecs.rs @@ -1,12 +1,14 @@ +use brk_cohort::AddrTypeId; use brk_error::Result; use brk_traversable::Traversable; use brk_types::{StoredU64, Version}; use derive_more::{Deref, DerefMut}; -use vecdb::{Database, Rw, StorageMode}; +use rayon::prelude::*; +use vecdb::{AnyStoredVec, AnyVec, Database, Rw, StorageMode, WritableVec}; use crate::{ indexes, - internal::{PerBlock, WithAddrTypes}, + internal::{ColumnarPerBlock, LazyColumnPerBlock, LazyPerBlock, WithAddrTypes}, }; use super::AddrTypeToAddrCount; @@ -16,7 +18,13 @@ use super::AddrTypeToAddrCount; /// funded/total pairs used by exposed, reused, and respent. #[derive(Deref, DerefMut, Traversable)] pub struct AddrCountsVecs( - #[traversable(flatten)] pub WithAddrTypes>, + #[traversable(flatten)] + pub ColumnarPerBlock< + StoredU64, + AddrTypeId, + WithAddrTypes, LazyPerBlock>, + M, + >, ); impl AddrCountsVecs { @@ -26,13 +34,31 @@ impl AddrCountsVecs { version: Version, indexes: &indexes::Vecs, ) -> Result { - Ok(Self(WithAddrTypes::>::forced_import( - db, name, version, indexes, + Ok(Self(ColumnarPerBlock::forced_import( + db, + &format!("{name}_by_type"), + version, + |source| WithAddrTypes::from_columnar_source(name, version, source, indexes), )?)) } + pub(crate) fn min_stateful_len(&self) -> usize { + self.height.len() + } + + pub(crate) fn par_iter_height_mut( + &mut self, + ) -> impl ParallelIterator { + rayon::iter::once(&mut self.height as &mut dyn AnyStoredVec) + } + + pub(crate) fn reset_height(&mut self) -> Result<()> { + self.height.reset()?; + Ok(()) + } + #[inline(always)] pub(crate) fn push_counts(&mut self, counts: &AddrTypeToAddrCount) { - self.push_height(counts.sum(), counts.values().copied()); + self.push(counts.row()); } } diff --git a/crates/brk_computer/src/distribution/addr/count/funded_total_vecs.rs b/crates/brk_computer/src/distribution/addr/count/funded_total_vecs.rs index cdf9b3708..d58bf606c 100644 --- a/crates/brk_computer/src/distribution/addr/count/funded_total_vecs.rs +++ b/crates/brk_computer/src/distribution/addr/count/funded_total_vecs.rs @@ -1,9 +1,8 @@ use brk_error::Result; -use brk_indexer::Lengths; use brk_traversable::Traversable; use brk_types::Version; use rayon::prelude::*; -use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode}; +use vecdb::{AnyStoredVec, Database, Rw, StorageMode}; use crate::indexes; @@ -70,10 +69,4 @@ impl AddrCountFundedTotalVecs { self.funded.push_counts(funded); self.total.push_counts(total); } - - pub(crate) fn compute_rest(&mut self, starting_lengths: &Lengths, exit: &Exit) -> Result<()> { - self.funded.compute_rest(starting_lengths, exit)?; - self.total.compute_rest(starting_lengths, exit)?; - Ok(()) - } } diff --git a/crates/brk_computer/src/distribution/addr/count/state.rs b/crates/brk_computer/src/distribution/addr/count/state.rs index f1f73c2e9..55d911328 100644 --- a/crates/brk_computer/src/distribution/addr/count/state.rs +++ b/crates/brk_computer/src/distribution/addr/count/state.rs @@ -1,7 +1,7 @@ -use brk_cohort::ByAddrType; -use brk_types::Height; +use brk_cohort::{AddrTypeId, ByAddrType}; +use brk_types::{Height, StoredU64}; use derive_more::{Deref, DerefMut}; -use vecdb::ReadableVec; +use vecdb::{ColumnId, ReadableVec}; use super::AddrCountsVecs; @@ -12,9 +12,8 @@ use super::AddrCountsVecs; pub struct AddrTypeToAddrCount(ByAddrType); impl AddrTypeToAddrCount { - #[inline] - pub(crate) fn sum(&self) -> u64 { - self.0.values().sum() + pub(crate) fn row(&self) -> ::Row { + AddrTypeId::from_fn(|id| StoredU64::from(*id.select(&self.0))) } } diff --git a/crates/brk_computer/src/distribution/addr/exposed/mod.rs b/crates/brk_computer/src/distribution/addr/exposed/mod.rs index ac996dd46..5653d6bd7 100644 --- a/crates/brk_computer/src/distribution/addr/exposed/mod.rs +++ b/crates/brk_computer/src/distribution/addr/exposed/mod.rs @@ -88,12 +88,21 @@ impl ExposedAddrVecs { .min(self.supply.min_stateful_len()) } + pub(crate) fn par_iter_stateful_height_mut( + &mut self, + ) -> impl ParallelIterator { + self.count + .par_iter_height_mut() + .chain(self.supply.par_iter_height_mut()) + } + pub(crate) fn par_iter_height_mut( &mut self, ) -> impl ParallelIterator { self.count .par_iter_height_mut() .chain(self.supply.par_iter_height_mut()) + .chain(rayon::iter::once(self.supply_share.stored_mut())) } pub(crate) fn reset_height(&mut self) -> Result<()> { @@ -115,7 +124,6 @@ impl ExposedAddrVecs { type_supply_sats: &ByAddrType<&impl ReadableVec>, exit: &Exit, ) -> Result<()> { - self.count.compute_rest(starting_lengths, exit)?; self.supply_share.compute_rest( starting_lengths.height, &self.supply, diff --git a/crates/brk_computer/src/distribution/addr/new_addr_count.rs b/crates/brk_computer/src/distribution/addr/new_addr_count.rs index 2916ba264..7ef741416 100644 --- a/crates/brk_computer/src/distribution/addr/new_addr_count.rs +++ b/crates/brk_computer/src/distribution/addr/new_addr_count.rs @@ -23,7 +23,7 @@ impl NewAddrCountVecs { cached_starts: &Windows<&CachedWindowStartVec>, ) -> Self { Self(WithAddrTypes { - all: LazyPerBlockCumulativeRolling::from_source( + all: LazyPerBlockCumulativeRolling::from_lazy_source( "new_addr_count", version, &total.all, @@ -31,7 +31,7 @@ impl NewAddrCountVecs { indexes, ), by_addr_type: total.by_addr_type.map_with_name(|name, total| { - LazyPerBlockCumulativeRolling::from_source( + LazyPerBlockCumulativeRolling::from_column_source( &format!("{name}_new_addr_count"), version, total, diff --git a/crates/brk_computer/src/distribution/addr/reused/events/state.rs b/crates/brk_computer/src/distribution/addr/reused/events/state.rs index 2aaef4d50..d62f613d9 100644 --- a/crates/brk_computer/src/distribution/addr/reused/events/state.rs +++ b/crates/brk_computer/src/distribution/addr/reused/events/state.rs @@ -1,5 +1,7 @@ -use brk_cohort::ByAddrType; +use brk_cohort::{AddrTypeId, ByAddrType}; +use brk_types::StoredU64; use derive_more::{Deref, DerefMut}; +use vecdb::ColumnId; /// Per-block running counter of address-reuse events, per address type. Shared /// across reused (receive-based) and respent (spend-based) flavors, and @@ -18,6 +20,11 @@ impl AddrTypeToAddrEventCount { self.0.values().sum() } + #[inline] + pub(crate) fn row(&self) -> ::Row { + AddrTypeId::from_fn(|column| StoredU64::from(*column.select(&self.0))) + } + #[inline] pub(crate) fn reset(&mut self) { for v in self.0.values_mut() { diff --git a/crates/brk_computer/src/distribution/addr/reused/events/vecs.rs b/crates/brk_computer/src/distribution/addr/reused/events/vecs.rs index 6d047d639..e6be55933 100644 --- a/crates/brk_computer/src/distribution/addr/reused/events/vecs.rs +++ b/crates/brk_computer/src/distribution/addr/reused/events/vecs.rs @@ -1,4 +1,4 @@ -use brk_cohort::{ByAddrType, zip2_by_addr_type}; +use brk_cohort::{AddrTypeId, ByAddrType}; use brk_error::Result; use brk_indexer::Lengths; use brk_traversable::Traversable; @@ -9,9 +9,10 @@ use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec}; use crate::{ indexes, inputs, internal::{ - CachedBlockCountReader, CachedWindowStartVec, CountPerBlockRollingAverage, - LazyPercentCumulativeRolling, PerBlockCumulativeRolling, PerBlockRollingAverage, RatioU64, - Windows, WithAddrTypes, + CachedBlockCountReader, CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, + CountPerBlockRollingAverage, LazyColumnPerBlockCumulativeRolling, + LazyPerBlockCumulativeRolling, LazyPercentCumulativeRolling, PerBlockRollingAverage, + RatioU64, Windows, WithAddrTypes, }, outputs, }; @@ -78,33 +79,45 @@ impl AddrEventShares { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, all: LazyPercentCumulativeRolling, - numerators: &ByAddrType>, + numerators: &ByAddrType>, denominators: &ByAddrType, - ) -> Result { - let by_addr_type = zip2_by_addr_type( - numerators, - denominators, - |type_name, numerator, denominator| { - Ok(LazyPercentCumulativeRolling::from_cached_block_count( - &format!("{type_name}_{name}"), - version, - &numerator.cumulative.height, - denominator.clone(), - cached_starts, - indexes, - )) - }, - )?; - Ok(Self { all, by_addr_type }) + ) -> Self { + let by_addr_type = AddrTypeId::series(|column, type_name| { + LazyPercentCumulativeRolling::from_cached_block_count( + &format!("{type_name}_{name}"), + version, + &column.select(numerators).cumulative.height, + column.select(denominators).clone(), + cached_starts, + indexes, + ) + }); + Self { all, by_addr_type } } } #[derive(Traversable)] pub struct AddrEventsVecs { - pub output_to_reused_addr_count: WithAddrTypes>, + pub output_to_reused_addr_count: ColumnarPerBlockCumulativeRolling< + StoredU64, + AddrTypeId, + WithAddrTypes< + LazyColumnPerBlockCumulativeRolling, + LazyPerBlockCumulativeRolling, + >, + M, + >, pub output_to_reused_addr_share: AddrEventShares, pub spendable_output_to_reused_addr_share: LazyPercentCumulativeRolling, - pub input_from_reused_addr_count: WithAddrTypes>, + pub input_from_reused_addr_count: ColumnarPerBlockCumulativeRolling< + StoredU64, + AddrTypeId, + WithAddrTypes< + LazyColumnPerBlockCumulativeRolling, + LazyPerBlockCumulativeRolling, + >, + M, + >, pub input_from_reused_addr_share: AddrEventShares, pub active_reused_addr_count: CountPerBlockRollingAverage, pub active_reused_addr_share: PerBlockRollingAverage, @@ -121,12 +134,19 @@ impl AddrEventsVecs { inputs_by_type: &inputs::ByTypeVecs, ) -> Result { let import_count = |name: &str| { - WithAddrTypes::>::forced_import( + ColumnarPerBlockCumulativeRolling::forced_import( db, - name, + &format!("{name}_by_type_cumulative"), version, - indexes, - cached_starts, + |source| { + WithAddrTypes::from_columnar_cumulative_source( + name, + version, + source, + indexes, + cached_starts, + ) + }, ) }; @@ -147,7 +167,7 @@ impl AddrEventsVecs { ), &output_to_reused_addr_count.by_addr_type, &output_denominators, - )?; + ); let spendable_share_name = format!("spendable_output_to_{name}_addr_share"); let spendable_output_to_reused_addr_share = LazyPercentCumulativeRolling::from_cumulative_ratio::< @@ -179,7 +199,7 @@ impl AddrEventsVecs { ), &input_from_reused_addr_count.by_addr_type, &input_denominators, - )?; + ); let active_reused_addr_count = CountPerBlockRollingAverage::forced_import( db, @@ -209,8 +229,9 @@ impl AddrEventsVecs { pub(crate) fn min_stateful_len(&self) -> usize { self.output_to_reused_addr_count - .min_stateful_len() - .min(self.input_from_reused_addr_count.min_stateful_len()) + .cumulative + .len() + .min(self.input_from_reused_addr_count.cumulative.len()) .min(self.active_reused_addr_count.min_stateful_len()) .min(self.active_reused_addr_share.block.len()) } @@ -218,9 +239,10 @@ impl AddrEventsVecs { pub(crate) fn par_iter_height_mut( &mut self, ) -> impl ParallelIterator { - self.output_to_reused_addr_count - .par_iter_height_mut() - .chain(self.input_from_reused_addr_count.par_iter_height_mut()) + rayon::iter::once(self.output_to_reused_addr_count.stored_mut()) + .chain(rayon::iter::once( + self.input_from_reused_addr_count.stored_mut(), + )) .chain([ self.active_reused_addr_count.stored_mut(), &mut self.active_reused_addr_share.block as &mut dyn AnyStoredVec, @@ -228,8 +250,8 @@ impl AddrEventsVecs { } pub(crate) fn reset_height(&mut self) -> Result<()> { - self.output_to_reused_addr_count.reset_height()?; - self.input_from_reused_addr_count.reset_height()?; + self.output_to_reused_addr_count.reset()?; + self.input_from_reused_addr_count.reset()?; self.active_reused_addr_count.reset()?; self.active_reused_addr_share.block.reset()?; Ok(()) @@ -243,10 +265,8 @@ impl AddrEventsVecs { active_addr_count: u32, active_reused_addr_count: u32, ) { - self.output_to_reused_addr_count - .push_height(uses.sum(), uses.values().copied()); - self.input_from_reused_addr_count - .push_height(spends.sum(), spends.values().copied()); + self.output_to_reused_addr_count.push_block(uses.row()); + self.input_from_reused_addr_count.push_block(spends.row()); self.active_reused_addr_count .push_block(StoredU32::from(active_reused_addr_count)); // Stored as a percentage in [0, 100] to match the rest of the diff --git a/crates/brk_computer/src/distribution/addr/reused/mod.rs b/crates/brk_computer/src/distribution/addr/reused/mod.rs index 2c0d928b2..4832f9e95 100644 --- a/crates/brk_computer/src/distribution/addr/reused/mod.rs +++ b/crates/brk_computer/src/distribution/addr/reused/mod.rs @@ -97,6 +97,15 @@ impl ReusedAddrVecs { .min(self.supply.min_stateful_len()) } + pub(crate) fn par_iter_stateful_height_mut( + &mut self, + ) -> impl ParallelIterator { + self.count + .par_iter_height_mut() + .chain(self.events.par_iter_height_mut()) + .chain(self.supply.par_iter_height_mut()) + } + pub(crate) fn par_iter_height_mut( &mut self, ) -> impl ParallelIterator { @@ -104,6 +113,7 @@ impl ReusedAddrVecs { .par_iter_height_mut() .chain(self.events.par_iter_height_mut()) .chain(self.supply.par_iter_height_mut()) + .chain(rayon::iter::once(self.supply_share.stored_mut())) } pub(crate) fn reset_height(&mut self) -> Result<()> { @@ -136,7 +146,6 @@ impl ReusedAddrVecs { type_supply_sats: &ByAddrType<&impl ReadableVec>, exit: &Exit, ) -> Result<()> { - self.count.compute_rest(starting_lengths, exit)?; self.events.compute_rest(starting_lengths, exit)?; self.supply_share.compute_rest( starting_lengths.height, diff --git a/crates/brk_computer/src/distribution/addr/supply/share.rs b/crates/brk_computer/src/distribution/addr/supply/share.rs index d7cf75f8f..fe6d01568 100644 --- a/crates/brk_computer/src/distribution/addr/supply/share.rs +++ b/crates/brk_computer/src/distribution/addr/supply/share.rs @@ -1,13 +1,16 @@ -use brk_cohort::ByAddrType; +use brk_cohort::{AddrTypeId, ByAddrType}; use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Height, PartsPerMillion32, Sats, Version}; -use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode, WritableVec}; +use vecdb::{ + AnyStoredVec, BinaryTransform, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, + WritableVec, +}; use crate::{ distribution::metrics::AllSupplyCache, indexes, - internal::{LazyPercentPerBlock, PercentPerBlock, RatioSats}, + internal::{ColumnarPerBlock, LazyColumnPercentPerBlock, LazyPercentPerBlock, RatioSats}, }; use super::vecs::AddrSupplyVecs; @@ -20,7 +23,9 @@ use super::vecs::AddrSupplyVecs; pub struct AddrSupplyShareVecs { pub all: LazyPercentPerBlock, #[traversable(flatten)] - pub by_addr_type: ByAddrType>, + pub by_addr_type: ByAddrType>, + #[traversable(hidden)] + ppm: ColumnarPerBlock, } impl AddrSupplyShareVecs { @@ -40,20 +45,35 @@ impl AddrSupplyShareVecs { all_supply.cached_boxed_clone(), indexes, ); - let by_addr_type = ByAddrType::new_with_name(|type_name| { - PercentPerBlock::forced_import(db, &format!("{type_name}_{name}"), version, indexes) - })?; + let ppm = + ColumnarPerBlock::forced_import(db, &format!("{name}_ppm_by_type"), version, |_| ())?; + let source = ppm.height.read_only_clone(); + let by_addr_type = AddrTypeId::series(|column, type_name| { + LazyColumnPercentPerBlock::new( + &format!("{type_name}_{name}"), + version, + &source, + column, + indexes, + ) + }); - Ok(Self { all, by_addr_type }) + Ok(Self { + all, + by_addr_type, + ppm, + }) } pub(crate) fn reset_height(&mut self) -> Result<()> { - for share in self.by_addr_type.values_mut() { - share.ppm.height.reset()?; - } + self.ppm.height.reset()?; Ok(()) } + pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec { + self.ppm.stored_mut() + } + pub(crate) fn compute_rest( &mut self, max_from: Height, @@ -61,18 +81,12 @@ impl AddrSupplyShareVecs { type_supply_sats: &ByAddrType<&impl ReadableVec>, exit: &Exit, ) -> Result<()> { - for ((_, share), ((_, cat), (_, denom))) in self - .by_addr_type - .iter_mut() - .zip(supply.by_addr_type.iter().zip(type_supply_sats.iter())) - { - share.compute_binary::>( - max_from, - &cat.sats.height, - *denom, - exit, - )?; - } - Ok(()) + self.ppm.compute_matrix_columns2( + max_from, + &supply.height, + |column| *column.select(type_supply_sats), + |_, category, total| RatioSats::::apply(category, total), + exit, + ) } } diff --git a/crates/brk_computer/src/distribution/addr/supply/state.rs b/crates/brk_computer/src/distribution/addr/supply/state.rs index f3b36c8bf..62c685cd1 100644 --- a/crates/brk_computer/src/distribution/addr/supply/state.rs +++ b/crates/brk_computer/src/distribution/addr/supply/state.rs @@ -1,7 +1,7 @@ -use brk_cohort::ByAddrType; +use brk_cohort::{AddrTypeId, ByAddrType}; use brk_types::{Height, OutputType, Sats}; use derive_more::{Deref, DerefMut}; -use vecdb::ReadableVec; +use vecdb::{ColumnId, ReadableVec}; use super::vecs::AddrSupplyVecs; @@ -12,8 +12,8 @@ pub struct AddrTypeToSupply(ByAddrType); impl AddrTypeToSupply { #[inline] - pub(crate) fn sum(&self) -> Sats { - self.0.values().copied().sum() + pub(crate) fn row(&self) -> ::Row { + AddrTypeId::from_fn(|column| *column.select(&self.0)) } /// Apply a signed `after - before` delta to the slot for `output_type`. diff --git a/crates/brk_computer/src/distribution/addr/supply/vecs.rs b/crates/brk_computer/src/distribution/addr/supply/vecs.rs index 7d718ffeb..72b13f177 100644 --- a/crates/brk_computer/src/distribution/addr/supply/vecs.rs +++ b/crates/brk_computer/src/distribution/addr/supply/vecs.rs @@ -1,12 +1,16 @@ +use brk_cohort::AddrTypeId; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Cents, Height, Version}; +use brk_types::{Cents, Height, Sats, Version}; use derive_more::{Deref, DerefMut}; -use vecdb::{CachedBoxedVec, Database, Rw, StorageMode}; +use rayon::prelude::*; +use vecdb::{AnyStoredVec, AnyVec, CachedBoxedVec, Database, Rw, StorageMode, WritableVec}; use crate::{ indexes, - internal::{SpotValuePerBlock, WithAddrTypes}, + internal::{ + ColumnarPerBlock, LazyColumnSpotValuePerBlock, LazySpotValuePerBlock, WithAddrTypes, + }, }; use super::AddrTypeToSupply; @@ -17,7 +21,13 @@ use super::AddrTypeToSupply; /// sats × spot price. #[derive(Deref, DerefMut, Traversable)] pub struct AddrSupplyVecs( - #[traversable(flatten)] pub WithAddrTypes>, + #[traversable(flatten)] + pub ColumnarPerBlock< + Sats, + AddrTypeId, + WithAddrTypes, LazySpotValuePerBlock>, + M, + >, ); impl AddrSupplyVecs { @@ -28,17 +38,36 @@ impl AddrSupplyVecs { indexes: &indexes::Vecs, spot_price: &CachedBoxedVec, ) -> Result { - Ok(Self(WithAddrTypes::::forced_import( + let name = format!("{name}_addr_supply"); + Ok(Self(ColumnarPerBlock::forced_import( db, - &format!("{name}_addr_supply"), + &format!("{name}_sats_by_type"), version, - indexes, - spot_price, + |source| { + WithAddrTypes::from_columnar_spot_value_source( + &name, version, source, indexes, spot_price, + ) + }, )?)) } + pub(crate) fn min_stateful_len(&self) -> usize { + self.height.len() + } + + pub(crate) fn par_iter_height_mut( + &mut self, + ) -> impl ParallelIterator { + rayon::iter::once(self.stored_mut()) + } + + pub(crate) fn reset_height(&mut self) -> Result<()> { + self.height.reset()?; + Ok(()) + } + #[inline(always)] pub(crate) fn push_supply(&mut self, supply: &AddrTypeToSupply) { - self.push_height(supply.sum(), supply.values().copied()); + self.push(supply.row()); } } diff --git a/crates/brk_computer/src/distribution/addr/total_addr_count.rs b/crates/brk_computer/src/distribution/addr/total_addr_count.rs index 55a1de047..c98b76977 100644 --- a/crates/brk_computer/src/distribution/addr/total_addr_count.rs +++ b/crates/brk_computer/src/distribution/addr/total_addr_count.rs @@ -1,21 +1,17 @@ +use brk_cohort::AddrTypeId; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Height, StoredU64, Version}; +use brk_types::{Height, Version}; use derive_more::{Deref, DerefMut}; -use vecdb::{Database, Exit, Rw, StorageMode}; +use vecdb::{ColumnId, Database, Exit, ReadOnlyClone, Rw, StorageMode}; -use crate::{ - indexes, - internal::{PerBlock, WithAddrTypes}, -}; +use crate::indexes; use super::AddrCountsVecs; /// Total address count (global + per-type) with all derived indexes. #[derive(Deref, DerefMut, Traversable)] -pub struct TotalAddrCountVecs( - #[traversable(flatten)] pub WithAddrTypes>, -); +pub struct TotalAddrCountVecs(#[traversable(flatten)] pub AddrCountsVecs); impl TotalAddrCountVecs { pub(crate) fn forced_import( @@ -23,7 +19,7 @@ impl TotalAddrCountVecs { version: Version, indexes: &indexes::Vecs, ) -> Result { - Ok(Self(WithAddrTypes::>::forced_import( + Ok(Self(AddrCountsVecs::forced_import( db, "total_addr_count", version, @@ -39,24 +35,21 @@ impl TotalAddrCountVecs { empty_addr_count: &AddrCountsVecs, exit: &Exit, ) -> Result<()> { - self.all.height.compute_add( + let addr_count = addr_count.height.read_only_clone(); + let empty_addr_count = empty_addr_count.height.read_only_clone(); + self.height.compute_transform2( max_from, - &addr_count.all.height, - &empty_addr_count.all.height, + &addr_count, + &empty_addr_count, + |(height, addr_count, empty_addr_count, ..)| { + let total = AddrTypeId::from_fn(|column| { + *column.get(&addr_count) + *column.get(&empty_addr_count) + }); + (height, total) + }, exit, )?; - for ((_, total), ((_, addr), (_, empty))) in self.by_addr_type.iter_mut().zip( - addr_count - .by_addr_type - .iter() - .zip(empty_addr_count.by_addr_type.iter()), - ) { - total - .height - .compute_add(max_from, &addr.height, &empty.height, exit)?; - } - Ok(()) } } diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/fenwick.rs b/crates/brk_computer/src/distribution/cohorts/utxo/fenwick.rs index ca7993bab..7fec88b0f 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/fenwick.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/fenwick.rs @@ -1,7 +1,11 @@ +use std::collections::BTreeMap; + use brk_cohort::{ - AGE_RANGE_COUNT, Filter, PROFITABILITY_RANGE_COUNT, compute_profitability_boundaries, + AGE_RANGE_COUNT, AgeRangeId, Filter, PROFITABILITY_RANGE_COUNT, + compute_profitability_boundaries, }; use brk_types::{Cents, CentsCompact, PartsPerMillion32, Sats}; +use vecdb::ColumnId; use crate::{ distribution::state::PendingDelta, @@ -123,18 +127,14 @@ impl CostBasisFenwick { } /// Pre-compute `is_sth` lookup from the STH filter and age-range filters. - pub(super) fn compute_is_sth<'a>( - &mut self, - sth_filter: &Filter, - age_range_filters: impl Iterator, - ) { - for (i, f) in age_range_filters.enumerate() { - self.is_sth[i] = sth_filter.includes(f); + pub(super) fn compute_is_sth(&mut self, sth_filter: &Filter) { + for id in AgeRangeId::ALL { + self.is_sth[id.index()] = sth_filter.includes(id.filter()); } } - pub(super) fn is_sth_at(&self, age_range_idx: usize) -> bool { - self.is_sth[age_range_idx] + pub(super) fn is_sth(&self, id: AgeRangeId) -> bool { + self.is_sth[id.index()] } /// Apply a net delta from a pending map entry. @@ -158,7 +158,7 @@ impl CostBasisFenwick { /// Bulk-initialize from age-range maps. pub(super) fn bulk_init<'a>( &mut self, - maps: impl Iterator, bool)>, + maps: impl Iterator, bool)>, ) { self.tree.reset(); self.totals = CostBasisNode::default(); diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs index bf9685129..19c34a63b 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs @@ -1,8 +1,8 @@ use std::path::Path; use brk_cohort::{ - AGE_RANGE_COUNT, AgeRange, AmountRange, ByEntry, ByEpoch, Class, CohortContext, Filter, - Filtered, OverAge, OverAmount, SpendableType, Term, UnderAge, UnderAmount, + AGE_RANGE_COUNT, AgeRange, AgeRangeId, AmountRange, ByEntry, ByEpoch, Class, CohortContext, + Filter, OverAge, OverAmount, SpendableType, Term, UnderAge, UnderAmount, }; use brk_error::Result; use brk_indexer::Lengths; @@ -10,7 +10,8 @@ use brk_traversable::Traversable; use brk_types::{Cents, CentsSquaredSats, Height, Sats, Version}; use rayon::prelude::*; use vecdb::{ - AnyStoredVec, AnyVec, CachedBoxedVec, Database, Exit, ReadOnlyClone, Rw, StorageMode, + AnyStoredVec, AnyVec, BinaryTransform, CachedBoxedVec, ColumnId, ColumnarVec, Database, + EagerVec, Exit, ImportableVec, PcoVec, ReadOnlyClone, ReadableVec, Rw, StorageMode, WritableVec, }; @@ -18,15 +19,17 @@ use crate::{ distribution::{ AllChainCache, DynCohortVecs, metrics::{ - AllCohortMetrics, AllSupplyCache, BasicCohortMetrics, CohortMetricsBase, - CoreCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, - MinimalCohortMetrics, ProfitabilityMetrics, RealizedFullAccum, SupplyCore, - TypeCohortMetrics, + AgeRangeSupplySources, AllCohortMetrics, AllSupplyCache, BasicCohortMetrics, + CohortMetricsBase, CoreCohortMetrics, ExtendedAdjustedCohortMetrics, + ExtendedCohortMetrics, ImportConfig, MinimalCohortMetrics, ProfitabilityMetrics, + RealizedFullAccum, TypeCohortMetrics, }, state::UTXOCohortState, }, indexes, - internal::{CachedWindowStartVec, ValuePerBlockCumulativeRolling, Windows}, + internal::{ + CachedWindowStartVec, LazyColumnValuePerBlockCumulativeRolling, SatsToCents, Windows, + }, price, }; @@ -52,7 +55,12 @@ pub struct UTXOCohorts { #[traversable(rename = "type")] pub type_: SpendableType>>, pub profitability: ProfitabilityMetrics, - pub matured: AgeRange>, + #[traversable(skip)] + age_range_supply: AgeRangeSupplySources, + pub matured: AgeRange>, + pub cumulative_matured_sats: M::Stored, AgeRangeId>>>, + pub cumulative_matured_cents: + M::Stored, AgeRangeId>>>, #[traversable(skip)] all_supply_cache: AllSupplyCache, #[traversable(skip)] @@ -69,6 +77,10 @@ pub(crate) struct UTXOCohortsTransientState { pub(super) tick_tock_cached_positions: [usize; AGE_RANGE_COUNT - 1], } +const MATURED_VERSION: Version = Version::new(4); +const AGE_RANGE_SUPPLY_VERSION: Version = Version::ONE; +const WRITE_INTERVAL: usize = 10_000; + impl UTXOCohorts { /// Separate cohorts currently total 74: /// 23 age + 5 epoch + 18 class + 2 entry + 15 amount + 11 spendable type. @@ -85,8 +97,9 @@ impl UTXOCohorts { spot_price: &CachedBoxedVec, ) -> Result { let v = version + VERSION; + let prefix = CohortContext::Utxo.prefix(); - // Phase 1: Import "all" supply first so it can be referenced by all cohorts' relative metrics. + // Phase 1: Import the age-range supply source and its cached all-column view. let all_full_name = CohortContext::Utxo.full_name(&Filter::All, ""); let all_cfg = ImportConfig { db, @@ -97,33 +110,31 @@ impl UTXOCohorts { cached_starts, spot_price, }; - let all_supply = SupplyCore::forced_import_all(&all_cfg)?; - let all_supply_cache = AllSupplyCache::new(&all_supply.total.sats.height); + let age_range_supply = + AgeRangeSupplySources::forced_import(db, prefix, v + AGE_RANGE_SUPPLY_VERSION)?; + let (all_supply, all_supply_cache) = age_range_supply.all(&all_cfg); let all_chain_cache = AllChainCache::new(&all_supply_cache, spot_price); // Phase 2: Import separate (stateful) cohorts. - - // Helper for separate cohorts with BasicCohortMetrics + full state - let basic_separate = - |f: Filter, name: &'static str| -> Result> { - let full_name = CohortContext::Utxo.full_name(&f, name); - let cfg = ImportConfig { - db, - filter: &f, - full_name: &full_name, - version: v, - indexes, - cached_starts, - spot_price, - }; - let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name))); - Ok(UTXOCohortVecs::new( - state, - BasicCohortMetrics::forced_import(&cfg, &all_supply_cache)?, - )) + let age_range = AgeRange::try_from_fn(|id| { + let filter = id.filter().clone(); + let full_name = CohortContext::Utxo.full_name(&filter, id.name().id); + let cfg = ImportConfig { + db, + filter: &filter, + full_name: &full_name, + version: v, + indexes, + cached_starts, + spot_price, }; - - let age_range = AgeRange::try_new(&basic_separate)?; + let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name))); + let supply = age_range_supply.column(&cfg, id, &all_supply_cache); + Ok::<_, brk_error::Error>(UTXOCohortVecs::new( + state, + BasicCohortMetrics::forced_import(&cfg, supply)?, + )) + })?; let core_separate = |f: Filter, name: &'static str| -> Result> { @@ -221,7 +232,7 @@ impl UTXOCohorts { None, ExtendedAdjustedCohortMetrics::forced_import( &cfg, - &all_supply_cache, + age_range_supply.filtered(&cfg, &f, &all_supply_cache), &all_chain_cache, )?, ) @@ -242,7 +253,11 @@ impl UTXOCohorts { }; UTXOCohortVecs::new( None, - ExtendedCohortMetrics::forced_import(&cfg, &all_supply_cache, &all_chain_cache)?, + ExtendedCohortMetrics::forced_import( + &cfg, + age_range_supply.filtered(&cfg, &f, &all_supply_cache), + &all_chain_cache, + )?, ) }; @@ -259,9 +274,10 @@ impl UTXOCohorts { cached_starts, spot_price, }; + let supply = age_range_supply.filtered(&cfg, &f, &all_supply_cache); Ok(UTXOCohortVecs::new( None, - CoreCohortMetrics::forced_import(&cfg, &all_supply_cache)?, + CoreCohortMetrics::forced_import_with_supply(&cfg, supply)?, )) }; @@ -292,18 +308,32 @@ impl UTXOCohorts { let under_amount = UnderAmount::try_new(&minimal_no_state)?; let over_amount = OverAmount::try_new(&minimal_no_state)?; - let prefix = CohortContext::Utxo.prefix(); - let matured = AgeRange::try_new(&|_f: Filter, - name: &'static str| - -> Result { - ValuePerBlockCumulativeRolling::forced_import( + let matured_version = v + MATURED_VERSION; + let matured_sats = + EagerVec::, AgeRangeId>>::forced_import( db, - &format!("{prefix}_{name}_matured_supply"), - v, + &format!("{prefix}_age_range_matured_supply_cumulative_sats"), + matured_version, + )?; + let matured_cents = + EagerVec::, AgeRangeId>>::forced_import( + db, + &format!("{prefix}_age_range_matured_supply_cumulative_cents"), + matured_version, + )?; + let matured_sats_ref = matured_sats.read_only_clone(); + let matured_cents_ref = matured_cents.read_only_clone(); + let matured = AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnValuePerBlockCumulativeRolling::new( + &format!("{name}_matured_supply"), + matured_version, + &matured_sats_ref, + &matured_cents_ref, + column, indexes, cached_starts, ) - })?; + }); Ok(Self { all, @@ -320,7 +350,10 @@ impl UTXOCohorts { under_amount, over_amount, profitability, + age_range_supply, matured, + cumulative_matured_sats: matured_sats, + cumulative_matured_cents: matured_cents, all_supply_cache, caches: UTXOCohortsTransientState::default(), }) @@ -348,20 +381,18 @@ impl UTXOCohorts { age_range, .. } = self; - caches - .fenwick - .compute_is_sth(&sth.metrics.filter, age_range.iter().map(|v| v.filter())); + caches.fenwick.compute_is_sth(&sth.metrics.filter); - let maps: Vec<_> = age_range + let maps: Vec<_> = AgeRangeId::ALL .iter() - .enumerate() - .filter_map(|(i, sub)| { + .filter_map(|&id| { + let sub = id.select(age_range); let state = sub.state.as_ref()?; let map = state.cost_basis_map(); if map.is_empty() { return None; } - Some((map, caches.fenwick.is_sth_at(i))) + Some((map, caches.fenwick.is_sth(id))) }) .collect(); caches.fenwick.bulk_init(maps.into_iter()); @@ -377,9 +408,10 @@ impl UTXOCohorts { let Self { caches, age_range, .. } = self; - for (i, sub) in age_range.iter().enumerate() { + for &id in AgeRangeId::ALL { + let sub = id.select(age_range); if let Some(state) = sub.state.as_ref() { - let is_sth = caches.fenwick.is_sth_at(i); + let is_sth = caches.fenwick.is_sth(id); state.for_each_cost_basis_pending(|&price, delta| { caches.fenwick.apply_delta(price, delta, is_sth); }); @@ -390,9 +422,49 @@ impl UTXOCohorts { /// Push maturation sats to the matured vecs for the given height. #[inline(always)] pub(crate) fn push_maturation(&mut self, matured: &AgeRange) { - for (v, &sats) in self.matured.iter_mut().zip(matured.iter()) { - v.push_block_sats(sats); + let mut cumulative = self + .cumulative_matured_sats + .collect_last() + .unwrap_or_default(); + for column in AgeRangeId::ALL { + *column.get_mut(&mut cumulative) += *column.select(matured); } + self.cumulative_matured_sats.push(cumulative); + } + + fn compute_matured_cents( + &mut self, + starting_height: Height, + prices: &price::Vecs, + exit: &Exit, + ) -> Result<()> { + let cumulative_sats = self.cumulative_matured_sats.read_only_clone(); + let mut previous_sats = None; + + self.cumulative_matured_cents.compute_transform2_batched( + starting_height, + &cumulative_sats, + &prices.spot.cents.height, + WRITE_INTERVAL, + |(height, current_sats, price, target)| { + let prior_sats = previous_sats.replace(current_sats).unwrap_or_else(|| { + height + .decremented() + .and_then(|height| cumulative_sats.collect_one(height)) + .unwrap_or_default() + }); + let mut current_cents = target.collect_last().unwrap_or_default(); + + for column in AgeRangeId::ALL { + let block_sats = *column.get(¤t_sats) - *column.get(&prior_sats); + *column.get_mut(&mut current_cents) += SatsToCents::apply(block_sats, price); + } + + (height, current_cents) + }, + exit, + )?; + Ok(()) } pub(crate) fn par_iter_separate_mut( @@ -574,10 +646,8 @@ impl UTXOCohorts { .try_for_each(|v| v.compute_rest_part1(prices, starting_lengths, exit))?; } - // Compute matured cumulative + cents from sats × price - self.matured - .par_iter_mut() - .try_for_each(|v| v.compute_rest(starting_lengths.height, prices, exit))?; + // Compute matured cumulative cents from the cumulative sats matrix × price. + self.compute_matured_cents(starting_lengths.height, prices, exit)?; // Compute profitability supply cents and realized price self.profitability.compute(prices, starting_lengths, exit)?; @@ -751,11 +821,9 @@ impl UTXOCohorts { vecs.extend(v.metrics.collect_all_vecs_mut()); } vecs.extend(self.profitability.collect_all_vecs_mut()); - for v in self.matured.iter_mut() { - let inner = &mut v.inner; - vecs.push(&mut inner.cumulative.sats.height); - vecs.push(&mut inner.cumulative.cents.height); - } + vecs.extend(self.age_range_supply.stored_vecs_mut()); + vecs.push(&mut self.cumulative_matured_sats); + vecs.push(&mut self.cumulative_matured_cents); vecs.into_par_iter() } @@ -768,13 +836,12 @@ impl UTXOCohorts { pub(crate) fn min_stateful_len(&self) -> Height { self.iter_separate() .map(|v| Height::from(v.min_stateful_len())) - .chain( - self.matured - .iter() - .map(|v| Height::from(v.cumulative.sats.height.len())), - ) + .chain(std::iter::once(Height::from( + self.cumulative_matured_sats.len(), + ))) .min() .unwrap_or_default() + .min(Height::from(self.age_range_supply.len())) .min(Height::from(self.profitability.min_stateful_len())) .min(Height::from(self.all.min_stateful_len())) .min(Height::from(self.sth.min_stateful_len())) @@ -828,6 +895,7 @@ impl UTXOCohorts { sth, lth, age_range, + age_range_supply, .. } = self; @@ -841,11 +909,19 @@ impl UTXOCohorts { let mut sth_ccap = (0u128, 0u128); let mut lth_ccap = (0u128, 0u128); - for ar in age_range.iter_mut() { + let mut total_supply = AgeRangeId::from_fn(|_| Sats::ZERO); + let mut supply_in_profit = AgeRangeId::from_fn(|_| Sats::ZERO); + let mut supply_in_loss = AgeRangeId::from_fn(|_| Sats::ZERO); + + for &id in AgeRangeId::ALL { + let ar = id.select_mut(age_range); if let Some(state) = ar.state.as_mut() { all_acc.add(&state.realized); let u = state.compute_unrealized_state(height_price); + *id.get_mut(&mut total_supply) = state.supply.value; + *id.get_mut(&mut supply_in_profit) = u.supply_in_profit; + *id.get_mut(&mut supply_in_loss) = u.supply_in_loss; all_ccap.0 += u.capitalized_cap_in_profit_raw; all_ccap.1 += u.capitalized_cap_in_loss_raw; @@ -861,6 +937,8 @@ impl UTXOCohorts { } } + age_range_supply.push(total_supply, supply_in_profit, supply_in_loss); + let all_capitalized_price = all.metrics.realized.push_accum(&all_acc); sth.metrics.realized.push_accum(&sth_acc); lth.metrics.realized.push_accum(<h_acc); diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs b/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs index b9f9c5ada..f63bd0fa9 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs @@ -1,11 +1,16 @@ -use std::{cmp::Reverse, collections::BinaryHeap, path::Path}; +use std::{ + cmp::Reverse, + collections::{BTreeMap, BinaryHeap}, + path::Path, +}; use brk_cohort::{ - AGE_RANGE_NAMES, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES, UTXO_ALL_NAME, + AgeRangeId, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES, UTXO_ALL_NAME, }; use brk_error::Result; use brk_types::{Cents, CentsCompact, Date, Dollars, PartsPerMillion32, Sats, UrpdRaw}; use rayon::prelude::*; +use vecdb::ColumnId; use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics}; @@ -45,20 +50,16 @@ impl UTXOCohorts { /// consumers can avoid writing and immediately rereading the current day. pub(crate) fn age_range_urpd_entries( &self, - ) -> impl Iterator + '_ { - let sth_filter = &self.sth.metrics.filter; - self.age_range - .iter() - .enumerate() - .flat_map(move |(age, cohort)| { - let is_sth = sth_filter.includes(cohort.filter()); - cohort.state.iter().flat_map(move |state| { - state - .cost_basis_map() - .iter() - .map(move |(&price, &sats)| (age, is_sth, rounded_urpd_price(price), sats)) - }) + ) -> impl Iterator + '_ { + AgeRangeId::ALL.iter().copied().flat_map(move |id| { + let cohort = id.select(&self.age_range); + cohort.state.iter().flat_map(move |state| { + state + .cost_basis_map() + .iter() + .map(move |(&price, &sats)| (id, rounded_urpd_price(price), sats)) }) + }) } /// Push all Fenwick-derived per-block results: percentiles, density, profitability. @@ -82,12 +83,12 @@ impl UTXOCohorts { fn write_disk_distributions(&mut self, date: Date, states_path: &Path) -> Result<()> { let sth_filter = self.sth.metrics.filter.clone(); - self.age_range + AgeRangeId::ALL .iter() - .zip(AGE_RANGE_NAMES.iter()) + .map(|&id| (id, id.select(&self.age_range))) .collect::>() .into_par_iter() - .try_for_each(|(sub, name)| -> Result<()> { + .try_for_each(|(id, sub)| -> Result<()> { let Some(state) = sub.state.as_ref() else { return Ok(()); }; @@ -102,7 +103,7 @@ impl UTXOCohorts { merged.push((rounded, sats)); } } - let full = CohortContext::Utxo.prefixed(name.id); + let full = CohortContext::Utxo.prefixed(id.name().id); UrpdRaw::write(states_path, &full, date, merged.into_iter()) })?; @@ -173,15 +174,12 @@ fn push_profitability( buckets: &[ProfitabilityRangeResult; PROFITABILITY_RANGE_COUNT], metrics: &mut ProfitabilityMetrics, ) { - for (i, bucket) in metrics.range.as_array_mut().into_iter().enumerate() { - let r = &buckets[i]; - bucket.push( - Sats::from(r.all_sats), - Sats::from(r.sth_sats), - raw_usd_to_dollars(r.all_usd), - raw_usd_to_dollars(r.sth_usd), - ); - } + metrics.push_ranges( + (*buckets).map(|range| Sats::from(range.all_sats)), + (*buckets).map(|range| Sats::from(range.sth_sats)), + (*buckets).map(|range| raw_usd_to_dollars(range.all_usd)), + (*buckets).map(|range| raw_usd_to_dollars(range.sth_usd)), + ); } // --------------------------------------------------------------------------- @@ -243,7 +241,7 @@ impl MergeTarget { /// K-way merge via BinaryHeap over BTreeMap iterators. /// Only builds merged distribution for disk writes. fn merge_k_way( - maps: &[(&std::collections::BTreeMap, bool)], + maps: &[(&BTreeMap, bool)], targets: &mut AllSthLth, ) { let mut iters: Vec<_> = maps diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/tick_tock.rs b/crates/brk_computer/src/distribution/cohorts/utxo/tick_tock.rs index 568868964..7804fc20e 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/tick_tock.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/tick_tock.rs @@ -1,4 +1,4 @@ -use brk_cohort::{AGE_BOUNDARIES, AGE_RANGE_COUNT, AgeRange}; +use brk_cohort::{AGE_BOUNDARIES, AGE_RANGE_IDS, AgeRange}; use brk_types::{CostBasisSnapshot, ONE_HOUR_IN_SEC, Sats, Timestamp}; use vecdb::{Rw, unlikely}; @@ -35,17 +35,18 @@ impl UTXOCohorts { return AgeRange::default(); } - let mut matured = [Sats::ZERO; AGE_RANGE_COUNT]; - - // Get age_range cohort states (indexed 0..AGE_RANGE_COUNT) - // Cohort i covers hours [BOUNDARIES[i-1], BOUNDARIES[i]) - // Cohort 0 covers [0, 1) hours - // The final cohort covers [15*365*24, infinity) hours - let mut age_cohorts: Vec<_> = self.age_range.iter_mut().map(|v| &mut v.state).collect(); - let cached = &mut self.caches.tick_tock_cached_positions; + let mut matured = AgeRange::default(); + let Self { + age_range, caches, .. + } = self; + let cached = &mut caches.tick_tock_cached_positions; // For each boundary (in hours), find blocks that just crossed it - for (boundary_idx, &boundary_hours) in AGE_BOUNDARIES.iter().enumerate() { + for (boundary_idx, (&boundary_hours, adjacent)) in AGE_BOUNDARIES + .iter() + .zip(AGE_RANGE_IDS.windows(2)) + .enumerate() + { let boundary_seconds = (boundary_hours as u32) * ONE_HOUR_IN_SEC; // Blocks crossing boundary B have timestamps in (prev - B*HOUR, curr - B*HOUR] @@ -81,18 +82,21 @@ impl UTXOCohorts { .map_or(chain_state.len(), |pos| start_idx + pos); // Move supply from younger cohort to older cohort + let younger = adjacent[0]; + let older = adjacent[1]; + let matured_sats = younger.select_mut(&mut matured); for block_state in &chain_state[start_idx..end_idx] { let snapshot = CostBasisSnapshot::from_utxo(block_state.price, &block_state.supply); - if let Some(state) = age_cohorts[boundary_idx].as_mut() { + if let Some(state) = younger.select_mut(age_range).state.as_mut() { state.decrement_snapshot(&snapshot); } - if let Some(state) = age_cohorts[boundary_idx + 1].as_mut() { + if let Some(state) = older.select_mut(age_range).state.as_mut() { state.increment_snapshot(&snapshot); } - matured[boundary_idx] += block_state.supply.value; + *matured_sats += block_state.supply.value; } } - AgeRange::from_array(matured) + matured } } diff --git a/crates/brk_computer/src/distribution/compute/block_loop.rs b/crates/brk_computer/src/distribution/compute/block_loop.rs index 58a3454cd..740df3bcf 100644 --- a/crates/brk_computer/src/distribution/compute/block_loop.rs +++ b/crates/brk_computer/src/distribution/compute/block_loop.rs @@ -434,9 +434,7 @@ pub(crate) fn process_blocks( // Update Fenwick tree from pending deltas (must happen before push_cohort_states drains pending) vecs.utxo_cohorts.update_fenwick_from_pending(); - let activity_totals = state.activity.totals(); - let active_addr_count = - activity_totals.sending + activity_totals.receiving - activity_totals.bidirectional; + let active_addr_count = state.activity.active(); vecs.addrs.push_height(&state, active_addr_count); let is_last_of_day = is_last_of_day[offset]; diff --git a/crates/brk_computer/src/distribution/metrics/activity/full.rs b/crates/brk_computer/src/distribution/metrics/activity/full.rs index 2d605faa6..3ed191bcc 100644 --- a/crates/brk_computer/src/distribution/metrics/activity/full.rs +++ b/crates/brk_computer/src/distribution/metrics/activity/full.rs @@ -5,7 +5,7 @@ use brk_types::{StoredF32, StoredF64, Version}; use derive_more::{Deref, DerefMut}; use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; -use crate::internal::{Identity, LazyPerBlock, PerBlock, Windows}; +use crate::internal::{ColumnarRollingWindows, Identity, LazyPerBlock}; use crate::{ distribution::{ @@ -26,7 +26,7 @@ pub struct ActivityFull { pub coinyears_destroyed: LazyPerBlock, - pub dormancy: Windows>, + pub dormancy: ColumnarRollingWindows, } impl ActivityFull { @@ -41,14 +41,12 @@ impl ActivityFull { cfg.indexes, ); - let dormancy = Windows::try_from_fn(|suffix| { - PerBlock::forced_import( - cfg.db, - &cfg.name(&format!("dormancy_{suffix}")), - cfg.version + v1, - cfg.indexes, - ) - })?; + let dormancy = ColumnarRollingWindows::forced_import( + cfg.db, + &cfg.name("dormancy"), + cfg.version + v1, + cfg.indexes, + )?; Ok(Self { inner, @@ -71,9 +69,7 @@ impl ActivityFull { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { let mut vecs = self.inner.collect_vecs_mut(); - for d in self.dormancy.as_mut_array() { - vecs.push(&mut d.height); - } + vecs.push(self.dormancy.stored_mut()); vecs } @@ -96,28 +92,25 @@ impl ActivityFull { self.inner .compute_rest_part1(prices, starting_lengths, exit)?; - for ((dormancy, cdd_sum), tv_sum) in self - .dormancy - .as_mut_array() - .into_iter() - .zip(self.inner.coindays_destroyed.sum.as_array()) - .zip(self.inner.minimal.transfer_volume.sum.0.as_array()) - { - dormancy.height.compute_transform2( - starting_lengths.height, - &cdd_sum.height, - &tv_sum.btc.height, - |(i, rolling_cdd, rolling_btc, ..)| { - let btc = f64::from(rolling_btc); - if btc == 0.0 { - (i, StoredF32::from(0.0f32)) - } else { - (i, StoredF32::from((f64::from(rolling_cdd) / btc) as f32)) - } - }, - exit, - )?; - } + let Self { + inner, dormancy, .. + } = self; + let cdd_sums = &inner.coindays_destroyed.sum; + let transfer_volume_sums = &inner.minimal.transfer_volume.sum.0; + dormancy.compute_columns2( + starting_lengths.height, + |window| &window.select(cdd_sums).height, + |window| &window.select(transfer_volume_sums).btc.height, + |_, rolling_cdd, rolling_btc| { + let btc = f64::from(rolling_btc); + if btc == 0.0 { + StoredF32::from(0.0f32) + } else { + StoredF32::from((f64::from(rolling_cdd) / btc) as f32) + } + }, + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs index 7dd3fc387..5e9ea26ed 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs @@ -6,8 +6,8 @@ use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; use crate::{ distribution::metrics::{ - ActivityCore, AllSupplyCache, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, - SupplyCore, UnrealizedCore, + ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore, + UnrealizedCore, }, price, }; @@ -43,8 +43,7 @@ impl CohortMetricsBase for BasicCohortMetrics { } impl BasicCohortMetrics { - pub(crate) fn forced_import(cfg: &ImportConfig, all_supply: &AllSupplyCache) -> Result { - let supply = SupplyCore::forced_import(cfg, all_supply)?; + pub(crate) fn forced_import(cfg: &ImportConfig, supply: SupplyCore) -> Result { let realized = RealizedCore::forced_import(cfg)?; let unrealized = UnrealizedCore::forced_import(cfg, &realized.price.ppm)?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/core.rs b/crates/brk_computer/src/distribution/metrics/cohort/core.rs index 6d5aa9ee6..26b95a65b 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/core.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/core.rs @@ -26,12 +26,20 @@ pub struct CoreCohortMetrics { impl CoreCohortMetrics { pub(crate) fn forced_import(cfg: &ImportConfig, all_supply: &AllSupplyCache) -> Result { + let supply = SupplyCore::forced_import(cfg, all_supply)?; + Self::forced_import_with_supply(cfg, supply) + } + + pub(crate) fn forced_import_with_supply( + cfg: &ImportConfig, + supply: SupplyCore, + ) -> Result { let realized = RealizedCore::forced_import(cfg)?; let unrealized = UnrealizedCore::forced_import(cfg, &realized.price.ppm)?; Ok(Self { filter: cfg.filter.clone(), - supply: Box::new(SupplyCore::forced_import(cfg, all_supply)?), + supply: Box::new(supply), outputs: Box::new(OutputsBase::forced_import(cfg)?), activity: Box::new(ActivityCore::forced_import(cfg)?), realized: Box::new(realized), diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs index 7d28fa377..97e50b455 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs @@ -9,8 +9,8 @@ use vecdb::{Exit, Rw, StorageMode}; use crate::{ distribution::AllChainCache, distribution::metrics::{ - ActivityFull, AllSupplyCache, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase, - RealizedFull, RelativeWithExtended, SupplyCore, UnrealizedFull, + ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase, RealizedFull, + RelativeWithExtended, SupplyCore, UnrealizedFull, }, price, }; @@ -69,10 +69,9 @@ impl CohortMetricsBase for ExtendedCohortMetrics { impl ExtendedCohortMetrics { pub(crate) fn forced_import( cfg: &ImportConfig, - all_supply: &AllSupplyCache, + supply: SupplyCore, all_chain: &AllChainCache, ) -> Result { - let supply = SupplyCore::forced_import(cfg, all_supply)?; let realized = RealizedFull::forced_import(cfg, all_chain)?; let unrealized = UnrealizedFull::forced_import(cfg, &realized.price.ppm)?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs index b6fd07949..487f24db9 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs @@ -8,7 +8,7 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{ distribution::AllChainCache, distribution::metrics::{ - ActivityFull, AdjustedSopr, AllSupplyCache, CohortMetricsBase, ImportConfig, RealizedFull, + ActivityFull, AdjustedSopr, CohortMetricsBase, ImportConfig, RealizedFull, SupplyCore, UnrealizedFull, }, price, @@ -52,10 +52,10 @@ impl CohortMetricsBase for ExtendedAdjustedCohortMetrics { impl ExtendedAdjustedCohortMetrics { pub(crate) fn forced_import( cfg: &ImportConfig, - all_supply: &AllSupplyCache, + supply: SupplyCore, all_chain: &AllChainCache, ) -> Result { - let inner = ExtendedCohortMetrics::forced_import(cfg, all_supply, all_chain)?; + let inner = ExtendedCohortMetrics::forced_import(cfg, supply, all_chain)?; let asopr = AdjustedSopr::forced_import(cfg)?; Ok(Self { inner, diff --git a/crates/brk_computer/src/distribution/metrics/config.rs b/crates/brk_computer/src/distribution/metrics/config.rs index 6beb9b199..774946f7f 100644 --- a/crates/brk_computer/src/distribution/metrics/config.rs +++ b/crates/brk_computer/src/distribution/metrics/config.rs @@ -9,11 +9,12 @@ use vecdb::{BytesVec, BytesVecValue, CachedBoxedVec, Database, ImportableVec}; use crate::{ indexes, internal::{ - CachedWindowStartVec, FiatPerBlock, FiatPerBlockCumulativeWithSums, FiatType, NumericValue, - PerBlock, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, Price, + CachedWindowStartVec, ColumnarPercentRollingWindows, ColumnarRollingWindows, + ColumnarRollingWindowsFrom1w, FiatPerBlock, FiatPerBlockCumulativeWithSums, FiatType, + NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, Price, PriceWithRatioPerBlock, RatioPerBlock, RollingWindow24hPerBlock, RollingWindows, - RollingWindowsFrom1w, SpotValuePerBlock, ValuePerBlock, ValuePerBlockCumulative, - ValuePerBlockCumulativeRolling, Windows, + SpotValuePerBlock, ValuePerBlock, ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, + Windows, }, }; @@ -43,7 +44,7 @@ impl_config_import!( PercentPerBlock, PercentPerBlock, PercentPerBlock, - PercentRollingWindows, + ColumnarPercentRollingWindows, Price>, ); @@ -96,6 +97,11 @@ impl ConfigImport for RollingWindows { Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes) } } +impl ConfigImport for ColumnarRollingWindows { + fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result { + Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes) + } +} impl ConfigImport for RollingWindow24hPerBlock { fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result { Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes) @@ -123,7 +129,7 @@ impl ConfigImport for FiatPerBlockCumulativeWithSums { ) } } -impl ConfigImport for RollingWindowsFrom1w { +impl ConfigImport for ColumnarRollingWindowsFrom1w { fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result { Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes) } diff --git a/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs b/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs index 31f8b9753..13dd571e2 100644 --- a/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs @@ -89,6 +89,8 @@ impl CostBasis { .len() .min(self.max.cents.height.len()) .min(self.supply_density.ppm.height.len()) + .min(self.per_coin.prices.height.len()) + .min(self.per_dollar.prices.height.len()) } #[inline(always)] @@ -130,18 +132,8 @@ impl CostBasis { &mut self.max.cents.height, &mut self.supply_density.ppm.height, ]; - vecs.extend( - self.per_coin - .vecs - .iter_mut() - .map(|v| &mut v.cents.height as &mut dyn AnyStoredVec), - ); - vecs.extend( - self.per_dollar - .vecs - .iter_mut() - .map(|v| &mut v.cents.height as &mut dyn AnyStoredVec), - ); + vecs.push(self.per_coin.prices.stored_mut()); + vecs.push(self.per_dollar.prices.stored_mut()); vecs } diff --git a/crates/brk_computer/src/distribution/metrics/profitability.rs b/crates/brk_computer/src/distribution/metrics/profitability.rs index 414f97b60..bf097068e 100644 --- a/crates/brk_computer/src/distribution/metrics/profitability.rs +++ b/crates/brk_computer/src/distribution/metrics/profitability.rs @@ -1,256 +1,159 @@ -use brk_cohort::{Loss, Profit, ProfitabilityRange}; +use brk_cohort::{ + Loss, PROFITABILITY_RANGE_COUNT, Profit, ProfitabilityId, ProfitabilityRange, ProfitabilityRow, +}; use brk_error::Result; use brk_indexer::Lengths; use brk_traversable::Traversable; use brk_types::{Bitcoin, Cents, Dollars, Height, PartsPerMillionSigned32, Sats, Version}; -use vecdb::{AnyStoredVec, AnyVec, CachedBoxedVec, Database, Exit, Rw, StorageMode, WritableVec}; +use vecdb::{ + AnyStoredVec, AnyVec, CachedBoxedVec, ColumnId, ColumnarVec, Database, EagerVec, Exit, + ImportableVec, PcoVec, ReadOnlyClone, ReadOnlyColumnarVec, Rw, StorageMode, WritableVec, +}; use crate::{ indexes, internal::{ - CachedWindowStartVec, PerBlock, RatioPerBlock, SpotValuePerBlock, - SpotValuePerBlockWithDeltas, Windows, + CachedWindowStartVec, LazyColumnPerBlock, LazyColumnRatioPerBlock, + LazyColumnSpotValuePerBlock, LazyColumnSpotValuePerBlockWithDeltas, Windows, }, price, }; -#[derive(Traversable)] +#[derive(Clone, Traversable)] pub struct WithSth { pub all: All, pub sth: Sth, } -#[derive(Traversable)] -pub struct ProfitabilityBucket { - pub supply: WithSth, SpotValuePerBlock>, - pub realized_cap: WithSth>, - pub unrealized_pnl: WithSth>, - pub nupl: RatioPerBlock, +#[derive(Clone, Traversable)] +pub struct ProfitabilityBucket { + pub supply: WithSth< + LazyColumnSpotValuePerBlockWithDeltas, + LazyColumnSpotValuePerBlock, + >, + pub realized_cap: WithSth< + LazyColumnPerBlock, + LazyColumnPerBlock, + >, + pub unrealized_pnl: WithSth< + LazyColumnPerBlock, + LazyColumnPerBlock, + >, + pub nupl: LazyColumnRatioPerBlock, } -impl ProfitabilityBucket { - fn min_len(&self) -> usize { - self.supply - .all - .sats - .height - .len() - .min(self.realized_cap.all.height.len()) - } +struct ProfitabilitySources { + all_supply_sats: ReadOnlyColumnarVec, ProfitabilityId>, + sth_supply_sats: ReadOnlyColumnarVec, ProfitabilityId>, + all_realized_cap: ReadOnlyColumnarVec, ProfitabilityId>, + sth_realized_cap: ReadOnlyColumnarVec, ProfitabilityId>, + all_unrealized_pnl: ReadOnlyColumnarVec, ProfitabilityId>, + sth_unrealized_pnl: ReadOnlyColumnarVec, ProfitabilityId>, + nupl: ReadOnlyColumnarVec, ProfitabilityId>, } impl ProfitabilityBucket { - fn forced_import( - db: &Database, + fn new( name: &str, version: Version, + column: ProfitabilityId, + sources: &ProfitabilitySources, indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, spot_price: &CachedBoxedVec, - ) -> Result { - Ok(Self { + ) -> Self { + Self { supply: WithSth { - all: SpotValuePerBlockWithDeltas::forced_import( - db, + all: LazyColumnSpotValuePerBlockWithDeltas::new( &format!("{name}_supply"), version, + &sources.all_supply_sats, + column, indexes, cached_starts, spot_price, - )?, - sth: SpotValuePerBlock::forced_import( - db, + ), + sth: LazyColumnSpotValuePerBlock::new( &format!("{name}_sth_supply"), version, + &sources.sth_supply_sats, + column, indexes, spot_price, - )?, + ), }, realized_cap: WithSth { - all: PerBlock::forced_import( - db, + all: LazyColumnPerBlock::new( &format!("{name}_realized_cap"), version, + &sources.all_realized_cap, + column, indexes, - )?, - sth: PerBlock::forced_import( - db, + ), + sth: LazyColumnPerBlock::new( &format!("{name}_sth_realized_cap"), version, + &sources.sth_realized_cap, + column, indexes, - )?, + ), }, unrealized_pnl: WithSth { - all: PerBlock::forced_import( - db, + all: LazyColumnPerBlock::new( &format!("{name}_unrealized_pnl"), version, + &sources.all_unrealized_pnl, + column, indexes, - )?, - sth: PerBlock::forced_import( - db, + ), + sth: LazyColumnPerBlock::new( &format!("{name}_sth_unrealized_pnl"), version, + &sources.sth_unrealized_pnl, + column, indexes, - )?, + ), }, - nupl: RatioPerBlock::forced_import_ppm( - db, + nupl: LazyColumnRatioPerBlock::new( &format!("{name}_nupl"), - version + Version::ONE, + version + Version::new(4), + &sources.nupl, + column, indexes, - )?, - }) - } - - #[inline(always)] - pub(crate) fn push( - &mut self, - supply: Sats, - sth_supply: Sats, - realized_cap: Dollars, - sth_realized_cap: Dollars, - ) { - self.supply.all.sats.height.push(supply); - self.supply.sth.sats.height.push(sth_supply); - self.realized_cap.all.height.push(realized_cap); - self.realized_cap.sth.height.push(sth_realized_cap); - } - - pub(crate) fn compute( - &mut self, - prices: &price::Vecs, - starting_lengths: &Lengths, - is_profit: bool, - exit: &Exit, - ) -> Result<()> { - let max_from = starting_lengths.height; - - self.unrealized_pnl.all.height.compute_transform3( - max_from, - &prices.spot.cents.height, - &self.realized_cap.all.height, - &self.supply.all.sats.height, - |(i, spot, cap, supply, ..)| { - let mv = f64::from(Dollars::from(spot)) * f64::from(Bitcoin::from(supply)); - let rc = f64::from(cap); - let pnl = if is_profit { mv - rc } else { rc - mv }.max(0.0); - (i, Dollars::from(pnl)) - }, - exit, - )?; - self.unrealized_pnl.sth.height.compute_transform3( - max_from, - &prices.spot.cents.height, - &self.realized_cap.sth.height, - &self.supply.sth.sats.height, - |(i, spot, cap, supply, ..)| { - let mv = f64::from(Dollars::from(spot)) * f64::from(Bitcoin::from(supply)); - let rc = f64::from(cap); - let pnl = if is_profit { mv - rc } else { rc - mv }.max(0.0); - (i, Dollars::from(pnl)) - }, - exit, - )?; - - self.nupl.ppm.height.compute_transform3( - max_from, - &prices.spot.cents.height, - &self.realized_cap.all.height, - &self.supply.all.sats.height, - |(i, spot, cap_dollars, supply_sats, ..)| { - let p = spot.as_u128(); - let supply = supply_sats.as_u128(); - if p == 0 || supply == 0 { - (i, PartsPerMillionSigned32::ZERO) - } else { - let rp = Cents::from(cap_dollars).as_u128() * Sats::ONE_BTC_U128 / supply; - let ratio = (p as f64 - rp as f64) / p as f64; - (i, PartsPerMillionSigned32::from(ratio)) - } - }, - exit, - )?; - - Ok(()) - } - - pub(crate) fn compute_from_ranges( - &mut self, - prices: &price::Vecs, - starting_lengths: &Lengths, - is_profit: bool, - sources: &[&ProfitabilityBucket], - exit: &Exit, - ) -> Result<()> { - let max_from = starting_lengths.height; - - self.supply.all.sats.height.compute_sum_of_others( - max_from, - &sources - .iter() - .map(|s| &s.supply.all.sats.height) - .collect::>(), - exit, - )?; - self.supply.sth.sats.height.compute_sum_of_others( - max_from, - &sources - .iter() - .map(|s| &s.supply.sth.sats.height) - .collect::>(), - exit, - )?; - self.realized_cap.all.height.compute_sum_of_others( - max_from, - &sources - .iter() - .map(|s| &s.realized_cap.all.height) - .collect::>(), - exit, - )?; - self.realized_cap.sth.height.compute_sum_of_others( - max_from, - &sources - .iter() - .map(|s| &s.realized_cap.sth.height) - .collect::>(), - exit, - )?; - - self.compute(prices, starting_lengths, is_profit, exit) - } - - pub(crate) fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - vec![ - &mut self.supply.all.inner.sats.height as &mut dyn AnyStoredVec, - &mut self.supply.sth.sats.height, - &mut self.realized_cap.all.height, - &mut self.realized_cap.sth.height, - &mut self.unrealized_pnl.all.height, - &mut self.unrealized_pnl.sth.height, - &mut self.nupl.ppm.height, - ] + ), + } } } /// All profitability metrics: 25 ranges + 14 profit thresholds + 9 loss thresholds. #[derive(Traversable)] pub struct ProfitabilityMetrics { - pub range: ProfitabilityRange>, - pub profit: Profit>, - pub loss: Loss>, + pub range: ProfitabilityRange, + pub profit: Profit, + pub loss: Loss, + pub all_supply_sats: M::Stored, ProfitabilityId>>>, + pub sth_supply_sats: M::Stored, ProfitabilityId>>>, + pub all_realized_cap: + M::Stored, ProfitabilityId>>>, + pub sth_realized_cap: + M::Stored, ProfitabilityId>>>, + pub all_unrealized_pnl: + M::Stored, ProfitabilityId>>>, + pub sth_unrealized_pnl: + M::Stored, ProfitabilityId>>>, + pub nupl: + M::Stored, ProfitabilityId>>>, } impl ProfitabilityMetrics { - pub fn iter(&self) -> impl Iterator> { + pub fn iter(&self) -> impl Iterator { self.range .iter() .chain(self.profit.iter()) .chain(self.loss.iter()) } - pub fn iter_mut(&mut self) -> impl Iterator> { + pub fn iter_mut(&mut self) -> impl Iterator { self.range .iter_mut() .chain(self.profit.iter_mut()) @@ -258,7 +161,7 @@ impl ProfitabilityMetrics { } pub(crate) fn min_stateful_len(&self) -> usize { - self.range.iter().map(|b| b.min_len()).min().unwrap_or(0) + self.all_supply_sats.len().min(self.all_realized_cap.len()) } } @@ -270,75 +173,274 @@ impl ProfitabilityMetrics { cached_starts: &Windows<&CachedWindowStartVec>, spot_price: &CachedBoxedVec, ) -> Result { - let range = ProfitabilityRange::try_new(|name| { - ProfitabilityBucket::forced_import( + let source_version = version + Version::TWO; + let all_supply_sats = + EagerVec::, ProfitabilityId>>::forced_import( db, + "profitability_all_supply_sats", + source_version, + )?; + let sth_supply_sats = + EagerVec::, ProfitabilityId>>::forced_import( + db, + "profitability_sth_supply_sats", + source_version, + )?; + let all_realized_cap = + EagerVec::, ProfitabilityId>>::forced_import( + db, + "profitability_all_realized_cap", + source_version, + )?; + let sth_realized_cap = + EagerVec::, ProfitabilityId>>::forced_import( + db, + "profitability_sth_realized_cap", + source_version, + )?; + let all_unrealized_pnl = + EagerVec::, ProfitabilityId>>::forced_import( + db, + "profitability_all_unrealized_pnl", + source_version, + )?; + let sth_unrealized_pnl = + EagerVec::, ProfitabilityId>>::forced_import( + db, + "profitability_sth_unrealized_pnl", + source_version, + )?; + let nupl = EagerVec::, + ProfitabilityId, + >>::forced_import( + db, + "profitability_nupl_ppm", + source_version + Version::new(4), + )?; + + let sources = ProfitabilitySources { + all_supply_sats: all_supply_sats.read_only_clone(), + sth_supply_sats: sth_supply_sats.read_only_clone(), + all_realized_cap: all_realized_cap.read_only_clone(), + sth_realized_cap: sth_realized_cap.read_only_clone(), + all_unrealized_pnl: all_unrealized_pnl.read_only_clone(), + sth_unrealized_pnl: sth_unrealized_pnl.read_only_clone(), + nupl: nupl.read_only_clone(), + }; + + let range = ProfitabilityId::range_series(|column, name| { + ProfitabilityBucket::new( name, version, + column, + &sources, indexes, cached_starts, spot_price, ) - })?; + }); let aggregate_version = version + Version::TWO; - - let profit = Profit::try_new(|name| { - ProfitabilityBucket::forced_import( - db, + let profit = ProfitabilityId::profit_series(|column, name| { + ProfitabilityBucket::new( name, aggregate_version, + column, + &sources, indexes, cached_starts, spot_price, ) - })?; + }); - let loss = Loss::try_new(|name| { - ProfitabilityBucket::forced_import( - db, + let loss = ProfitabilityId::loss_series(|column, name| { + ProfitabilityBucket::new( name, aggregate_version, + column, + &sources, indexes, cached_starts, spot_price, ) - })?; + }); Ok(Self { range, profit, loss, + all_supply_sats, + sth_supply_sats, + all_realized_cap, + sth_realized_cap, + all_unrealized_pnl, + sth_unrealized_pnl, + nupl, }) } + #[inline(always)] + pub(crate) fn push_ranges( + &mut self, + all_supply_sats: [Sats; PROFITABILITY_RANGE_COUNT], + sth_supply_sats: [Sats; PROFITABILITY_RANGE_COUNT], + all_realized_cap: [Dollars; PROFITABILITY_RANGE_COUNT], + sth_realized_cap: [Dollars; PROFITABILITY_RANGE_COUNT], + ) { + self.all_supply_sats + .push(ProfitabilityRow::from_ranges(all_supply_sats)); + self.sth_supply_sats + .push(ProfitabilityRow::from_ranges(sth_supply_sats)); + self.all_realized_cap + .push(ProfitabilityRow::from_ranges(all_realized_cap)); + self.sth_realized_cap + .push(ProfitabilityRow::from_ranges(sth_realized_cap)); + } + pub(crate) fn compute( &mut self, prices: &price::Vecs, starting_lengths: &Lengths, exit: &Exit, ) -> Result<()> { - for (is_profit, bucket) in self.range.iter_mut_with_is_profit() { - bucket.compute(prices, starting_lengths, is_profit, exit)?; - } + let Self { + all_supply_sats, + sth_supply_sats, + all_realized_cap, + sth_realized_cap, + all_unrealized_pnl, + sth_unrealized_pnl, + nupl, + .. + } = self; + let max_from = starting_lengths.height; - let range_arr = self.range.as_array(); - - for (threshold, sources) in self.profit.iter_mut_with_growing_prefix(&range_arr) { - threshold.compute_from_ranges(prices, starting_lengths, true, sources, exit)?; - } - for (threshold, sources) in self.loss.iter_mut_with_growing_suffix(&range_arr) { - threshold.compute_from_ranges(prices, starting_lengths, false, sources, exit)?; - } + all_unrealized_pnl.compute_transform3( + max_from, + &prices.spot.cents.height, + all_realized_cap, + all_supply_sats, + |(height, spot, cap, supply, ..)| (height, unrealized_pnl_row(spot, cap, supply)), + exit, + )?; + sth_unrealized_pnl.compute_transform3( + max_from, + &prices.spot.cents.height, + sth_realized_cap, + sth_supply_sats, + |(height, spot, cap, supply, ..)| (height, unrealized_pnl_row(spot, cap, supply)), + exit, + )?; + nupl.compute_transform3( + max_from, + &prices.spot.cents.height, + all_realized_cap, + all_supply_sats, + |(height, spot, cap, supply, ..)| (height, nupl_row(spot, cap, supply)), + exit, + )?; Ok(()) } - pub(crate) fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs = Vec::new(); - for bucket in self.iter_mut() { - vecs.extend(bucket.collect_all_vecs_mut()); - } - vecs + pub(crate) fn collect_all_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; 7] { + [ + &mut self.all_supply_sats as &mut dyn AnyStoredVec, + &mut self.sth_supply_sats, + &mut self.all_realized_cap, + &mut self.sth_realized_cap, + &mut self.all_unrealized_pnl, + &mut self.sth_unrealized_pnl, + &mut self.nupl, + ] + } +} + +fn unrealized_pnl_row( + spot: Cents, + cap: ProfitabilityRow, + supply: ProfitabilityRow, +) -> ProfitabilityRow { + ProfitabilityId::from_fn(|column| { + let market_value = + f64::from(Dollars::from(spot)) * f64::from(Bitcoin::from(*column.get(&supply))); + let realized_cap = f64::from(*column.get(&cap)); + let pnl = if column.is_profit() { + market_value - realized_cap + } else { + realized_cap - market_value + } + .max(0.0); + Dollars::from(pnl) + }) +} + +fn nupl_row( + spot: Cents, + cap: ProfitabilityRow, + supply: ProfitabilityRow, +) -> ProfitabilityRow { + ProfitabilityId::from_fn(|column| { + let spot = spot.as_u128(); + let supply = column.get(&supply).as_u128(); + if spot == 0 || supply == 0 { + PartsPerMillionSigned32::ZERO + } else { + let realized_price = + Cents::from(*column.get(&cap)).as_u128() * Sats::ONE_BTC_U128 / supply; + PartsPerMillionSigned32::from((spot as f64 - realized_price as f64) / spot as f64) + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use brk_cohort::PROFIT_COUNT; + + #[test] + fn expanded_profitability_thresholds_match_prefix_and_suffix_sums() { + let ranges = std::array::from_fn(|index| Sats::from(index as u64 + 1)); + let row = ProfitabilityRow::from_ranges(ranges); + let sum = |values: &[Sats]| { + values + .iter() + .copied() + .fold(Sats::default(), |total, value| total + value) + }; + + for (threshold, &column) in ProfitabilityId::profit_ids().iter().enumerate() { + assert_eq!( + *column.get(&row), + sum(&ranges[..PROFIT_COUNT + 1 - threshold]) + ); + } + for (threshold, &column) in ProfitabilityId::loss_ids().iter().enumerate() { + assert_eq!( + *column.get(&row), + sum(&ranges[PROFIT_COUNT + 1 + threshold..]) + ); + } + } + + #[test] + fn derived_rows_preserve_profit_and_loss_polarity() { + let supply = ProfitabilityId::from_fn(|_| Sats::ONE_BTC); + let cap = ProfitabilityId::from_fn(|column| { + Dollars::from(if column.is_profit() { 1.0 } else { 3.0 }) + }); + let spot = Cents::from(200_u64); + + let pnl = unrealized_pnl_row(spot, cap.clone(), supply.clone()); + let nupl = nupl_row(spot, cap, supply); + + for column in ProfitabilityId::ALL { + assert_eq!(*column.get(&pnl), Dollars::from(1.0)); + assert_eq!( + *column.get(&nupl), + PartsPerMillionSigned32::from(if column.is_profit() { 0.5 } else { -0.5 }) + ); + } } } diff --git a/crates/brk_computer/src/distribution/metrics/realized/adjusted.rs b/crates/brk_computer/src/distribution/metrics/realized/adjusted.rs index 9ba5deb2d..7b4a8078f 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/adjusted.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/adjusted.rs @@ -2,16 +2,16 @@ use brk_error::Result; use brk_indexer::Lengths; use brk_traversable::Traversable; use brk_types::{Cents, Height, StoredF64, Version}; -use vecdb::{Exit, ReadableVec, Rw, StorageMode}; +use vecdb::{BinaryTransform, Exit, ReadableVec, Rw, StorageMode}; use crate::{ distribution::metrics::ImportConfig, - internal::{PerBlockCumulativeRolling, RatioCents64, RollingWindows}, + internal::{ColumnarRollingWindows, PerBlockCumulativeRolling, RatioCents64}, }; #[derive(Traversable)] pub struct AdjustedSopr { - pub ratio: RollingWindows, + pub ratio: ColumnarRollingWindows, pub transfer_volume: PerBlockCumulativeRolling, pub value_destroyed: PerBlockCumulativeRolling, } @@ -48,20 +48,15 @@ impl AdjustedSopr { exit, )?; - for ((sopr, tv), vd) in self - .ratio - .as_mut_array() - .into_iter() - .zip(self.transfer_volume.sum.as_array()) - .zip(self.value_destroyed.sum.as_array()) - { - sopr.compute_binary::( - starting_lengths.height, - &tv.height, - &vd.height, - exit, - )?; - } + self.ratio.compute_columns2( + starting_lengths.height, + |window| &window.select(&self.transfer_volume.sum).height, + |window| &window.select(&self.value_destroyed.sum).height, + |_, transfer_volume, value_destroyed| { + RatioCents64::apply(transfer_volume, value_destroyed) + }, + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index f00a27648..3ec6371f2 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -6,15 +6,18 @@ use brk_types::{ PartsPerMillion64, PartsPerMillionSigned64, StoredF64, Version, }; use derive_more::{Deref, DerefMut}; -use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec}; +use vecdb::{ + AnyStoredVec, AnyVec, BinaryTransform, BytesVec, Exit, ReadableVec, Rw, StorageMode, + WritableVec, +}; use crate::{ distribution::AllChainCache, distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital}, internal::{ + ColumnarPercentRollingWindows, ColumnarRollingWindows, ColumnarRollingWindowsFrom1w, FiatPerBlockCumulativeWithSums, LazyPercentPerBlock, PercentPerBlock, - PercentRollingWindows, PriceWithRatioPerBlock, RatioCents, RatioCents64, - RatioCentsSignedCents, RollingWindows, RollingWindowsFrom1w, + PriceWithRatioPerBlock, RatioCents, RatioCents64, RatioCentsSignedCents, ValuePerBlockCumulativeRolling, }, price, @@ -35,7 +38,7 @@ pub struct RealizedNetPnl { #[derive(Traversable)] pub struct RealizedSopr { #[traversable(rename = "ratio")] - pub ratio_extended: RollingWindowsFrom1w, + pub ratio_extended: ColumnarRollingWindowsFrom1w, } #[derive(Traversable)] @@ -61,13 +64,13 @@ pub struct RealizedFull { #[traversable(wrap = "cap", rename = "to_own_mcap")] pub cap_to_own_mcap: LazyPercentPerBlock, pub gross_pnl: FiatPerBlockCumulativeWithSums, - pub sell_side_risk_ratio: PercentRollingWindows, + pub sell_side_risk_ratio: ColumnarPercentRollingWindows, pub net_pnl: RealizedNetPnl, pub sopr: RealizedSopr, pub peak_regret: RealizedPeakRegret, pub capitalized: RealizedCapitalized, - pub profit_to_loss_ratio: RollingWindows, + pub profit_to_loss_ratio: ColumnarRollingWindows, #[traversable(hidden)] cap_raw: M::Stored>, @@ -246,21 +249,22 @@ impl RealizedFull { )?; // SOPR ratios from lazy rolling sums (1w, 1m, 1y) - for ((sopr, vc), vd) in self - .sopr - .ratio_extended - .as_mut_array() - .into_iter() - .zip(activity_transfer_volume.sum.0.as_array()[1..].iter()) - .zip(self.core.sopr.value_destroyed.sum.as_array()[1..].iter()) - { - sopr.compute_binary::( - starting_lengths.height, - &vc.cents.height, - &vd.height, - exit, - )?; - } + self.sopr.ratio_extended.compute_columns2( + starting_lengths.height, + |window| { + &window + .select_full(&activity_transfer_volume.sum.0) + .cents + .height + }, + |window| { + &window + .select_full(&self.core.sopr.value_destroyed.sum) + .height + }, + |_, value_created, value_destroyed| RatioCents64::apply(value_created, value_destroyed), + exit, + )?; // Gross PnL self.gross_pnl.compute_from_cumulative_pair( @@ -281,35 +285,24 @@ impl RealizedFull { exit, )?; // Sell-side risk ratios - for (ssrr, rv) in self - .sell_side_risk_ratio - .as_mut_array() - .into_iter() - .zip(self.gross_pnl.sum.as_array()) - { - ssrr.compute_binary::>( - starting_lengths.height, - &rv.cents.height, - &self.core.minimal.cap.cents.height, - exit, - )?; - } + self.sell_side_risk_ratio.compute_columns2( + starting_lengths.height, + |window| &window.select(&self.gross_pnl.sum).cents.height, + |_| &self.core.minimal.cap.cents.height, + |_, realized_value, realized_cap| { + RatioCents::::apply(realized_value, realized_cap) + }, + exit, + )?; // Realized profit to loss ratios - for ((ratio, profit), loss) in self - .profit_to_loss_ratio - .as_mut_array() - .into_iter() - .zip(self.core.minimal.profit.sum.as_array()) - .zip(self.core.minimal.loss.sum.as_array()) - { - ratio.compute_binary::( - starting_lengths.height, - &profit.cents.height, - &loss.cents.height, - exit, - )?; - } + self.profit_to_loss_ratio.compute_columns2( + starting_lengths.height, + |window| &window.select(&self.core.minimal.profit.sum).cents.height, + |window| &window.select(&self.core.minimal.loss.sum).cents.height, + |_, profit, loss| RatioCents64::apply(profit, loss), + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/supply/age_range.rs b/crates/brk_computer/src/distribution/metrics/supply/age_range.rs new file mode 100644 index 000000000..af8a1d568 --- /dev/null +++ b/crates/brk_computer/src/distribution/metrics/supply/age_range.rs @@ -0,0 +1,176 @@ +use brk_cohort::{AGE_RANGE_IDS, AgeRangeId, Filter}; +use brk_error::Result; +use brk_types::{Height, Sats, Version}; +use vecdb::{ + AnyStoredVec, AnyVec, ColumnId, ColumnarVec, Database, EagerVec, ImportableVec, PcoVec, + ReadOnlyClone, ReadableColumnarVec, Rw, StorageMode, WritableVec, +}; + +use crate::{ + distribution::metrics::{AllSupplyCache, ImportConfig, SupplyCore}, + internal::LazySpotValuePerBlock, +}; + +type Source = M::Stored, AgeRangeId>>>; + +pub(crate) struct AgeRangeSupplySources { + total: Source, + in_profit: Source, + in_loss: Source, +} + +impl AgeRangeSupplySources { + pub(crate) fn forced_import( + db: &Database, + prefix: &str, + version: Version, + ) -> Result { + Ok(Self { + total: EagerVec::forced_import( + db, + &format!("{prefix}_age_range_supply_sats"), + version, + )?, + in_profit: EagerVec::forced_import( + db, + &format!("{prefix}_age_range_supply_in_profit_sats"), + version, + )?, + in_loss: EagerVec::forced_import( + db, + &format!("{prefix}_age_range_supply_in_loss_sats"), + version, + )?, + }) + } + + pub(crate) fn all(&self, cfg: &ImportConfig) -> (SupplyCore, AllSupplyCache) { + let total_name = cfg.name("supply"); + let total_source = self.total.read_only_clone().sum_columns( + &format!("{total_name}_sats"), + cfg.version, + AGE_RANGE_IDS, + ); + let all_supply = AllSupplyCache::new(total_source); + let total = LazySpotValuePerBlock::from_sats_source( + &total_name, + cfg.version, + all_supply.readable_boxed_clone(), + cfg.indexes, + cfg.spot_price, + ); + let in_profit = + Self::summed_spot(&self.in_profit, cfg, "supply_in_profit", AGE_RANGE_IDS); + let in_loss = Self::summed_spot(&self.in_loss, cfg, "supply_in_loss", AGE_RANGE_IDS); + + ( + SupplyCore::from_lazy_all(cfg, total, in_profit, in_loss), + all_supply, + ) + } + + pub(crate) fn column( + &self, + cfg: &ImportConfig, + column: AgeRangeId, + all_supply: &AllSupplyCache, + ) -> SupplyCore { + SupplyCore::from_lazy( + cfg, + Self::column_spot(&self.total, cfg, "supply", column), + Self::column_spot(&self.in_profit, cfg, "supply_in_profit", column), + Self::column_spot(&self.in_loss, cfg, "supply_in_loss", column), + all_supply, + ) + } + + pub(crate) fn filtered( + &self, + cfg: &ImportConfig, + filter: &Filter, + all_supply: &AllSupplyCache, + ) -> SupplyCore { + let columns: Vec<_> = AGE_RANGE_IDS + .into_iter() + .filter(|column| filter.includes(column.filter())) + .collect(); + + SupplyCore::from_lazy( + cfg, + Self::summed_spot(&self.total, cfg, "supply", columns.iter().copied()), + Self::summed_spot( + &self.in_profit, + cfg, + "supply_in_profit", + columns.iter().copied(), + ), + Self::summed_spot(&self.in_loss, cfg, "supply_in_loss", columns), + all_supply, + ) + } + + fn column_spot( + source: &EagerVec, AgeRangeId>>, + cfg: &ImportConfig, + suffix: &str, + column: AgeRangeId, + ) -> LazySpotValuePerBlock { + let name = cfg.name(suffix); + let source = source.read_only_clone().column( + &format!("{name}_sats"), + cfg.version, + column, + ); + LazySpotValuePerBlock::from_sats_source( + &name, + cfg.version, + source, + cfg.indexes, + cfg.spot_price, + ) + } + + fn summed_spot( + source: &EagerVec, AgeRangeId>>, + cfg: &ImportConfig, + suffix: &str, + columns: impl IntoIterator, + ) -> LazySpotValuePerBlock { + let name = cfg.name(suffix); + let source = source.read_only_clone().sum_columns( + &format!("{name}_sats"), + cfg.version, + columns, + ); + LazySpotValuePerBlock::from_sats_source( + &name, + cfg.version, + source, + cfg.indexes, + cfg.spot_price, + ) + } + + #[inline(always)] + pub(crate) fn push( + &mut self, + total: ::Row, + in_profit: ::Row, + in_loss: ::Row, + ) { + self.total.push(total); + self.in_profit.push(in_profit); + self.in_loss.push(in_loss); + } + + pub(crate) fn stored_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; 3] { + [&mut self.total, &mut self.in_profit, &mut self.in_loss] + } + + pub(crate) fn len(&self) -> usize { + self.total + .len() + .min(self.in_profit.len()) + .min(self.in_loss.len()) + } +} diff --git a/crates/brk_computer/src/distribution/metrics/supply/avg_amount.rs b/crates/brk_computer/src/distribution/metrics/supply/avg_amount.rs index 4f2dd22e7..19b642ccb 100644 --- a/crates/brk_computer/src/distribution/metrics/supply/avg_amount.rs +++ b/crates/brk_computer/src/distribution/metrics/supply/avg_amount.rs @@ -1,38 +1,44 @@ -use brk_cohort::ByAddrType; +use brk_cohort::{AddrTypeId, ByAddrType}; use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Cents, Height, Sats, StoredU64, Version}; +use derive_more::{Deref, DerefMut}; use rayon::prelude::*; use vecdb::{ - AnyStoredVec, CachedBoxedVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec, + AnyStoredVec, CachedBoxedVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, + WritableVec, }; use crate::{ distribution::AllChainCache, indexes, - internal::{LazySpotValuePerBlock, SpotValuePerBlock}, + internal::{ + ColumnarPerBlock, LazyColumnSpotValuePerBlock, LazySpotValuePerBlock, WithAddrTypes, + }, }; /// Average amount held per UTXO and per funded address. /// /// `utxo = supply / utxo_count`, `addr = supply / funded_addr_count`. -#[derive(Traversable)] -pub struct AvgAmountMetrics { - pub utxo: SpotValuePerBlock, - pub addr: SpotValuePerBlock, -} - #[derive(Clone, Traversable)] -pub struct LazyAvgAmountMetrics { - pub utxo: LazySpotValuePerBlock, - pub addr: LazySpotValuePerBlock, +pub struct AvgAmountMetrics { + pub utxo: V, + pub addr: V, } -#[derive(Traversable)] +#[derive(Deref, DerefMut, Traversable)] pub struct AvgAmountVecs { - pub all: LazyAvgAmountMetrics, + #[deref] + #[deref_mut] #[traversable(flatten)] - pub by_addr_type: ByAddrType>, + pub series: WithAddrTypes< + AvgAmountMetrics>, + AvgAmountMetrics, + >, + #[traversable(hidden)] + utxo_source: ColumnarPerBlock, + #[traversable(hidden)] + addr_source: ColumnarPerBlock, } impl AvgAmountVecs { @@ -57,7 +63,7 @@ impl AvgAmountVecs { funded_addr_count, |_, count, supply| supply / count, ); - let all = LazyAvgAmountMetrics { + let all = AvgAmountMetrics { utxo: LazySpotValuePerBlock::from_sats_source( "avg_utxo_amount", version, @@ -73,92 +79,74 @@ impl AvgAmountVecs { spot_price, ), }; - let by_addr_type = ByAddrType::new_with_name(|type_name| { - AvgAmountMetrics::forced_import(db, type_name, version, indexes, spot_price) - })?; - Ok(Self { all, by_addr_type }) + let utxo_source = + ColumnarPerBlock::forced_import(db, "avg_utxo_amount_sats_by_type", version, |_| ())?; + let addr_source = + ColumnarPerBlock::forced_import(db, "avg_addr_amount_sats_by_type", version, |_| ())?; + let utxo = utxo_source.height.read_only_clone(); + let addr = addr_source.height.read_only_clone(); + let by_addr_type = AddrTypeId::series(|column, type_name| AvgAmountMetrics { + utxo: LazyColumnSpotValuePerBlock::new( + &format!("{type_name}_avg_utxo_amount"), + version, + &utxo, + column, + indexes, + spot_price, + ), + addr: LazyColumnSpotValuePerBlock::new( + &format!("{type_name}_avg_addr_amount"), + version, + &addr, + column, + indexes, + spot_price, + ), + }); + + Ok(Self { + series: WithAddrTypes { all, by_addr_type }, + utxo_source, + addr_source, + }) } pub(crate) fn par_iter_height_mut( &mut self, ) -> impl ParallelIterator { - self.by_addr_type - .par_values_mut() - .flat_map_iter(AvgAmountMetrics::collect_vecs_mut) + rayon::iter::once(self.utxo_source.stored_mut()) + .chain(rayon::iter::once(self.addr_source.stored_mut())) } pub(crate) fn reset_height(&mut self) -> Result<()> { - for metrics in self.by_addr_type.values_mut() { - metrics.reset_height()?; - } - Ok(()) - } -} - -impl AvgAmountMetrics { - pub(crate) fn forced_import( - db: &Database, - prefix: &str, - version: Version, - indexes: &indexes::Vecs, - spot_price: &CachedBoxedVec, - ) -> Result { - let name = |suffix: &str| { - if prefix.is_empty() { - suffix.to_string() - } else { - format!("{prefix}_{suffix}") - } - }; - Ok(Self { - utxo: SpotValuePerBlock::forced_import( - db, - &name("avg_utxo_amount"), - version, - indexes, - spot_price, - )?, - addr: SpotValuePerBlock::forced_import( - db, - &name("avg_addr_amount"), - version, - indexes, - spot_price, - )?, - }) - } - - pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - vec![ - &mut self.utxo.sats.height as &mut dyn AnyStoredVec, - &mut self.addr.sats.height, - ] - } - - pub(crate) fn reset_height(&mut self) -> Result<()> { - self.utxo.sats.height.reset()?; - self.addr.sats.height.reset()?; + self.utxo_source.height.reset()?; + self.addr_source.height.reset()?; Ok(()) } pub(crate) fn compute( &mut self, - supply_sats: &impl ReadableVec, - utxo_count: &impl ReadableVec, - funded_addr_count: &impl ReadableVec, + supply_sats: &ByAddrType<&impl ReadableVec>, + utxo_count: &ByAddrType<&impl ReadableVec>, + funded_addr_count: &ByAddrType<&impl ReadableVec>, max_from: Height, exit: &Exit, ) -> Result<()> { - self.utxo - .sats - .height - .compute_divide(max_from, supply_sats, utxo_count, exit)?; - - self.addr - .sats - .height - .compute_divide(max_from, supply_sats, funded_addr_count, exit)?; + self.utxo_source.compute_columns2( + max_from, + |column| *column.select(supply_sats), + |column| *column.select(utxo_count), + |_, supply, count| supply / count, + exit, + )?; + self.addr_source.compute_columns2( + max_from, + |column| *column.select(supply_sats), + |column| *column.select(funded_addr_count), + |_, supply, count| supply / count, + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/supply/base.rs b/crates/brk_computer/src/distribution/metrics/supply/base.rs index 3ac958b8e..53e43e894 100644 --- a/crates/brk_computer/src/distribution/metrics/supply/base.rs +++ b/crates/brk_computer/src/distribution/metrics/supply/base.rs @@ -3,14 +3,15 @@ use brk_indexer::Lengths; use brk_traversable::Traversable; use brk_types::{Height, PartsPerMillion32, PartsPerMillionSigned64, Sats, SatsSigned, Version}; use vecdb::{ - AnyStoredVec, AnyVec, BinaryTransform, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec, + AnyStoredVec, AnyVec, BinaryTransform, EagerVec, Exit, ImportableVec, PcoVec, ReadOnlyClone, + ReadableCloneableVec, Rw, StorageMode, WritableVec, }; use crate::distribution::state::{CohortState, CostBasisOps, RealizedOps}; use crate::internal::{ - LazyIndexedVec, LazyPercentPerBlock, LazyRollingDeltasAmountFromHeight, RatioSats, - SpotValuePerBlock, + LazyIndexedVec, LazyPercentPerBlock, LazyRollingDeltasAmountFromHeight, LazySpotValuePerBlock, + RatioSats, }; use crate::distribution::metrics::{AllSupplyCache, ImportConfig}; @@ -18,15 +19,49 @@ use crate::distribution::metrics::{AllSupplyCache, ImportConfig}; /// Base supply metrics: total supply + dominance (share of circulating). #[derive(Traversable)] pub struct SupplyBase { - pub total: SpotValuePerBlock, + pub total: LazySpotValuePerBlock, pub delta: LazyRollingDeltasAmountFromHeight, #[traversable(rename = "dominance")] pub dominance: LazyPercentPerBlock, + #[traversable(hidden)] + source: Option>>>, } impl SupplyBase { pub(crate) fn forced_import(cfg: &ImportConfig, all_supply: &AllSupplyCache) -> Result { - let supply: SpotValuePerBlock = cfg.import("supply", Version::ZERO)?; + let name = cfg.name("supply"); + let sats_source = + EagerVec::forced_import(cfg.db, &format!("{name}_sats"), cfg.version)?; + let supply = LazySpotValuePerBlock::from_sats_source( + &name, + cfg.version, + sats_source.read_only_clone(), + cfg.indexes, + cfg.spot_price, + ); + let name = cfg.name("supply_dominance"); + let dominance_source = LazyIndexedVec::new( + &format!("{name}_ppm_source"), + cfg.version, + supply.sats.height.read_only_boxed_clone(), + all_supply.cached_boxed_clone(), + |_, supply, all_supply| RatioSats::::apply(supply, all_supply), + ); + let dominance = LazyPercentPerBlock::from_height_source( + &name, + cfg.version, + dominance_source, + cfg.indexes, + ); + + Ok(Self::new(cfg, supply, dominance, Some(sats_source))) + } + + pub(crate) fn from_lazy( + cfg: &ImportConfig, + supply: LazySpotValuePerBlock, + all_supply: &AllSupplyCache, + ) -> Self { let name = cfg.name("supply_dominance"); let source = LazyIndexedVec::new( &format!("{name}_ppm_source"), @@ -38,11 +73,10 @@ impl SupplyBase { let dominance = LazyPercentPerBlock::from_height_source(&name, cfg.version, source, cfg.indexes); - Ok(Self::new(cfg, supply, dominance)) + Self::new(cfg, supply, dominance, None) } - pub(crate) fn forced_import_all(cfg: &ImportConfig) -> Result { - let supply: SpotValuePerBlock = cfg.import("supply", Version::ZERO)?; + pub(crate) fn from_lazy_all(cfg: &ImportConfig, supply: LazySpotValuePerBlock) -> Self { let dominance = LazyPercentPerBlock::from_indexed_source( &cfg.name("supply_dominance"), cfg.version, @@ -51,7 +85,7 @@ impl SupplyBase { cfg.indexes, ); - Ok(Self::new(cfg, supply, dominance)) + Self::new(cfg, supply, dominance, None) } fn all_dominance(_height: Height, supply: Sats) -> PartsPerMillion32 { @@ -60,8 +94,9 @@ impl SupplyBase { fn new( cfg: &ImportConfig, - supply: SpotValuePerBlock, + supply: LazySpotValuePerBlock, dominance: LazyPercentPerBlock, + source: Option>>, ) -> Self { let delta = LazyRollingDeltasAmountFromHeight::new( &cfg.name("supply_delta"), @@ -75,6 +110,7 @@ impl SupplyBase { total: supply, delta, dominance, + source, } } @@ -84,11 +120,16 @@ impl SupplyBase { #[inline(always)] pub(crate) fn push_state(&mut self, state: &CohortState) { - self.total.sats.height.push(state.supply.value); + if let Some(source) = self.source.as_mut() { + source.push(state.supply.value); + } } pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - vec![&mut self.total.sats.height as &mut dyn AnyStoredVec] + self.source + .iter_mut() + .map(|source| source as &mut dyn AnyStoredVec) + .collect() } pub(crate) fn compute_from_stateful( @@ -97,14 +138,16 @@ impl SupplyBase { others: &[&Self], exit: &Exit, ) -> Result<()> { - self.total.sats.height.compute_sum_of_others( - starting_lengths.height, - &others - .iter() - .map(|v| &v.total.sats.height) - .collect::>(), - exit, - )?; + if let Some(source) = self.source.as_mut() { + source.compute_sum_of_others( + starting_lengths.height, + &others + .iter() + .map(|v| &v.total.sats.height) + .collect::>(), + exit, + )?; + } Ok(()) } } diff --git a/crates/brk_computer/src/distribution/metrics/supply/cache.rs b/crates/brk_computer/src/distribution/metrics/supply/cache.rs index dd8e8a56f..31209a863 100644 --- a/crates/brk_computer/src/distribution/metrics/supply/cache.rs +++ b/crates/brk_computer/src/distribution/metrics/supply/cache.rs @@ -1,7 +1,7 @@ use brk_types::{Height, Sats}; use vecdb::{ - CachedBoxedVec, CachedReadableVec, CachedVec, EagerVec, PcoVec, PcodecStrategy, ReadOnlyClone, - ReadOnlyCompressedVec, + CachedBoxedVec, CachedReadableVec, CachedVec, ReadableBoxedVec, ReadableCloneableVec, + ReadableVec, TypedVec, }; /// Pinned in-memory snapshot of the all-cohort supply. @@ -10,20 +10,35 @@ use vecdb::{ /// global cache budget because evicting it would make each lazy read hit disk. #[derive(Clone)] pub(crate) struct AllSupplyCache { - cache: CachedVec>>, + cache: CachedBoxedVec, + source: ReadableBoxedVec, } impl AllSupplyCache { - pub(crate) fn new(source: &EagerVec>) -> Self { - Self { - cache: CachedVec::wrap(source.read_only_clone()), - } + pub(crate) fn new(source: V) -> Self + where + V: TypedVec + + ReadableVec + + Clone + + Send + + Sync + + 'static, + { + let cache = CachedVec::wrap(source); + let source = ReadableCloneableVec::read_only_boxed_clone(&cache); + let cache = cache.cached_boxed_clone(); + + Self { cache, source } } pub(crate) fn cached_boxed_clone(&self) -> CachedBoxedVec { self.cache.cached_boxed_clone() } + pub(crate) fn readable_boxed_clone(&self) -> ReadableBoxedVec { + self.source.read_only_boxed_clone() + } + pub(crate) fn clear(&self) { self.cache.clear(); } @@ -32,7 +47,7 @@ impl AllSupplyCache { #[cfg(test)] mod tests { use brk_types::Version; - use vecdb::{AnyStoredVec, Database, ImportableVec, WritableVec}; + use vecdb::{AnyStoredVec, Database, EagerVec, ImportableVec, PcoVec, ReadOnlyClone, WritableVec}; use super::*; @@ -54,7 +69,7 @@ mod tests { source.push(Sats::new(20)); source.write().unwrap(); - let cache = AllSupplyCache::new(&source); + let cache = AllSupplyCache::new(source.read_only_clone()); let reader = cache.cached_boxed_clone(); assert_eq!(&*reader.cached(), &[Sats::new(10), Sats::new(20)]); diff --git a/crates/brk_computer/src/distribution/metrics/supply/core.rs b/crates/brk_computer/src/distribution/metrics/supply/core.rs index 4d1aa1caf..11fcb35d3 100644 --- a/crates/brk_computer/src/distribution/metrics/supply/core.rs +++ b/crates/brk_computer/src/distribution/metrics/supply/core.rs @@ -1,21 +1,25 @@ use brk_error::Result; use brk_indexer::Lengths; use brk_traversable::Traversable; -use brk_types::Version; +use brk_types::{Height, Sats, Version}; use derive_more::{Deref, DerefMut}; -use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec}; +use vecdb::{ + AnyStoredVec, AnyVec, EagerVec, Exit, ImportableVec, PcoVec, ReadOnlyClone, Rw, StorageMode, + WritableVec, +}; use crate::distribution::state::UnrealizedState; use crate::internal::{ - HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValuePerBlock, SpotValuePerBlock, + HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazySpotValuePerBlock, + LazyValuePerBlock, }; use crate::distribution::metrics::{AllSupplyCache, ImportConfig}; use super::SupplyBase; -/// Core supply metrics: total + halved + in_profit/in_loss (4 stored vecs). +/// Core supply metrics: total + halved + in_profit/in_loss. #[derive(Deref, DerefMut, Traversable)] pub struct SupplyCore { #[deref] @@ -24,22 +28,86 @@ pub struct SupplyCore { pub base: SupplyBase, pub half: LazyValuePerBlock, - pub in_profit: SpotValuePerBlock, - pub in_loss: SpotValuePerBlock, + pub in_profit: LazySpotValuePerBlock, + pub in_loss: LazySpotValuePerBlock, + #[traversable(hidden)] + in_profit_source: Option>>>, + #[traversable(hidden)] + in_loss_source: Option>>>, } impl SupplyCore { pub(crate) fn forced_import(cfg: &ImportConfig, all_supply: &AllSupplyCache) -> Result { - Self::forced_import_with_base(cfg, SupplyBase::forced_import(cfg, all_supply)?) + let base = SupplyBase::forced_import(cfg, all_supply)?; + let (in_profit, in_profit_source) = Self::import_spot(cfg, "supply_in_profit")?; + let (in_loss, in_loss_source) = Self::import_spot(cfg, "supply_in_loss")?; + Ok(Self::new( + cfg, + base, + in_profit, + in_loss, + Some(in_profit_source), + Some(in_loss_source), + )) } - pub(crate) fn forced_import_all(cfg: &ImportConfig) -> Result { - Self::forced_import_with_base(cfg, SupplyBase::forced_import_all(cfg)?) + pub(crate) fn from_lazy( + cfg: &ImportConfig, + total: LazySpotValuePerBlock, + in_profit: LazySpotValuePerBlock, + in_loss: LazySpotValuePerBlock, + all_supply: &AllSupplyCache, + ) -> Self { + Self::new( + cfg, + SupplyBase::from_lazy(cfg, total, all_supply), + in_profit, + in_loss, + None, + None, + ) } - fn forced_import_with_base(cfg: &ImportConfig, base: SupplyBase) -> Result { - let v0 = Version::ZERO; + pub(crate) fn from_lazy_all( + cfg: &ImportConfig, + total: LazySpotValuePerBlock, + in_profit: LazySpotValuePerBlock, + in_loss: LazySpotValuePerBlock, + ) -> Self { + Self::new( + cfg, + SupplyBase::from_lazy_all(cfg, total), + in_profit, + in_loss, + None, + None, + ) + } + fn import_spot( + cfg: &ImportConfig, + suffix: &str, + ) -> Result<(LazySpotValuePerBlock, EagerVec>)> { + let name = cfg.name(suffix); + let source = EagerVec::forced_import(cfg.db, &format!("{name}_sats"), cfg.version)?; + let series = LazySpotValuePerBlock::from_sats_source( + &name, + cfg.version, + source.read_only_clone(), + cfg.indexes, + cfg.spot_price, + ); + Ok((series, source)) + } + + fn new( + cfg: &ImportConfig, + base: SupplyBase, + in_profit: LazySpotValuePerBlock, + in_loss: LazySpotValuePerBlock, + in_profit_source: Option>>, + in_loss_source: Option>>, + ) -> Self { let half = LazyValuePerBlock::from_spot_block_source::< HalveSats, HalveSatsToBitcoin, @@ -47,12 +115,14 @@ impl SupplyCore { HalveDollars, >(&cfg.name("supply_half"), &base.total, cfg.version); - Ok(Self { + Self { base, half, - in_profit: cfg.import("supply_in_profit", v0)?, - in_loss: cfg.import("supply_in_loss", v0)?, - }) + in_profit, + in_loss, + in_profit_source, + in_loss_source, + } } pub(crate) fn min_len(&self) -> usize { @@ -64,14 +134,26 @@ impl SupplyCore { #[inline(always)] pub(crate) fn push_profitability(&mut self, state: &UnrealizedState) { - self.in_profit.sats.height.push(state.supply_in_profit); - self.in_loss.sats.height.push(state.supply_in_loss); + if let Some(source) = self.in_profit_source.as_mut() { + source.push(state.supply_in_profit); + } + if let Some(source) = self.in_loss_source.as_mut() { + source.push(state.supply_in_loss); + } } pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { let mut vecs = self.base.collect_vecs_mut(); - vecs.push(&mut self.in_profit.sats.height as &mut dyn AnyStoredVec); - vecs.push(&mut self.in_loss.sats.height); + vecs.extend( + self.in_profit_source + .iter_mut() + .map(|source| source as &mut dyn AnyStoredVec), + ); + vecs.extend( + self.in_loss_source + .iter_mut() + .map(|source| source as &mut dyn AnyStoredVec), + ); vecs } @@ -88,8 +170,26 @@ impl SupplyCore { let base_refs: Vec<&SupplyBase> = others.iter().map(|o| &o.base).collect(); self.base .compute_from_stateful(starting_lengths, &base_refs, exit)?; - sum_others!(self, starting_lengths, others, exit; in_profit.sats.height); - sum_others!(self, starting_lengths, others, exit; in_loss.sats.height); + if let Some(source) = self.in_profit_source.as_mut() { + source.compute_sum_of_others( + starting_lengths.height, + &others + .iter() + .map(|other| &other.in_profit.sats.height) + .collect::>(), + exit, + )?; + } + if let Some(source) = self.in_loss_source.as_mut() { + source.compute_sum_of_others( + starting_lengths.height, + &others + .iter() + .map(|other| &other.in_loss.sats.height) + .collect::>(), + exit, + )?; + } Ok(()) } } diff --git a/crates/brk_computer/src/distribution/metrics/supply/mod.rs b/crates/brk_computer/src/distribution/metrics/supply/mod.rs index dc2c2a33f..8e0b42860 100644 --- a/crates/brk_computer/src/distribution/metrics/supply/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/supply/mod.rs @@ -1,8 +1,10 @@ +mod age_range; mod avg_amount; mod base; mod cache; mod core; +pub(crate) use age_range::AgeRangeSupplySources; pub use self::core::SupplyCore; pub use avg_amount::AvgAmountVecs; pub use base::SupplyBase; diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index bac037ef3..abc7fff71 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use brk_cohort::{ByAddrType, EntryPrice, Filter}; +use brk_cohort::{AddrTypeId, EntryPrice}; use brk_error::Result; use brk_indexer::Indexer; use brk_traversable::Traversable; @@ -65,6 +65,7 @@ impl AddrMetricsVecs { self.funded.reset_height()?; self.empty.reset_height()?; self.activity.reset_height()?; + self.total.reset_height()?; self.reused.reset_height()?; self.respent.reset_height()?; self.exposed.reset_height()?; @@ -91,9 +92,9 @@ impl AddrMetricsVecs { .par_iter_height_mut() .chain(self.empty.par_iter_height_mut()) .chain(self.activity.par_iter_height_mut()) - .chain(self.reused.par_iter_height_mut()) - .chain(self.respent.par_iter_height_mut()) - .chain(self.exposed.par_iter_height_mut()) + .chain(self.reused.par_iter_stateful_height_mut()) + .chain(self.respent.par_iter_stateful_height_mut()) + .chain(self.exposed.par_iter_stateful_height_mut()) } /// All height-indexed vecs including derived (`avg_amount`). Used for @@ -105,6 +106,7 @@ impl AddrMetricsVecs { .par_iter_height_mut() .chain(self.empty.par_iter_height_mut()) .chain(self.activity.par_iter_height_mut()) + .chain(self.total.par_iter_height_mut()) .chain(self.reused.par_iter_height_mut()) .chain(self.respent.par_iter_height_mut()) .chain(self.exposed.par_iter_height_mut()) @@ -660,15 +662,17 @@ impl Vecs { r2?; } - // 6b. Compute address count sum (by addr_type -> all) - self.addrs.funded.compute_rest(&starting_lengths, exit)?; - self.addrs.empty.compute_rest(&starting_lengths, exit)?; + // 6b. Compute address metrics derived from stored per-type sources. let t = &self.utxo_cohorts.type_; - let type_supply_sats = ByAddrType::new(|filter| { - let Filter::Type(ot) = filter else { - unreachable!() - }; - &t.get(ot).metrics.supply.total.sats.height + let type_supply_sats = AddrTypeId::series(|column, _| { + &t.get(column.output_type()).metrics.supply.total.sats.height + }); + let type_utxo_counts = AddrTypeId::series(|column, _| { + &t.get(column.output_type()) + .metrics + .outputs + .unspent_count + .height }); self.addrs .reused @@ -680,23 +684,15 @@ impl Vecs { .exposed .compute_rest(&starting_lengths, &type_supply_sats, exit)?; - // Average amount (supply / utxo_count, supply / funded_addr_count) for `all` and per addr type. - for ((ot, avg), (_, funded)) in self - .addrs - .avg_amount - .by_addr_type - .iter_mut() - .zip(self.addrs.funded.by_addr_type.iter()) - { - let type_m = &t.get(ot).metrics; - avg.compute( - &type_m.supply.total.sats.height, - &type_m.outputs.unspent_count.height, - &funded.height, - starting_lengths.height, - exit, - )?; - } + let type_funded_addr_counts = + AddrTypeId::series(|column, _| &column.select(&self.addrs.funded.by_addr_type).height); + self.addrs.avg_amount.compute( + &type_supply_sats, + &type_utxo_counts, + &type_funded_addr_counts, + starting_lengths.height, + exit, + )?; // 6c. Compute total_addr_count = addr_count + empty_addr_count self.addrs.total.compute( diff --git a/crates/brk_computer/src/frameworks/coinflow/compute.rs b/crates/brk_computer/src/frameworks/coinflow/compute.rs index fb6cc8df1..877b4cf33 100644 --- a/crates/brk_computer/src/frameworks/coinflow/compute.rs +++ b/crates/brk_computer/src/frameworks/coinflow/compute.rs @@ -1,18 +1,18 @@ use brk_error::Result; use brk_indexer::{Indexer, Lengths}; use brk_types::{Bitcoin, Cents, Height, Sats, StoredF64, Timestamp, Version}; -use vecdb::{AnyStoredVec, Exit, ReadableVec, WritableVec}; +use vecdb::{AnyStoredVec, ColumnId, Exit, ReadableVec, WritableVec}; -use brk_cohort::{AGE_RANGE_FILTERS, ByTerm, TERM_FILTERS}; +use brk_cohort::{AgeRange, AgeRangeId, ByTerm, TERM_FILTERS}; use super::super::cointime; use super::{ - AGE_COHORT_COUNT, AgeBand, AggregateVecs, CohortVecs, HORIZON_COUNT, Horizons, - MINIMUM_DURATION_DAYS, Vecs, age_bounds_days, horizon_mobility, mobility, + AGE_COHORT_COUNT, AgeBand, AggregateVecs, Horizons, MINIMUM_DURATION_DAYS, Vecs, + age_bounds_days, horizon_mobility, mobility, }; use crate::{ distribution, - frameworks::{WeightedCohortContribution, WeightedCohortState, WeightedRatio, realized_price}, + frameworks::{WeightedCohortContribution, WeightedCohortState, WeightedRatio}, indexes, internal::db_utils::validate_any_computed_version_or_reset, price, @@ -50,8 +50,8 @@ impl AggregateState { loss_supply: Sats, total_cap: Cents, mobility: StoredF64, - horizon_mobilities: &Horizons<[f64; AGE_COHORT_COUNT]>, - age: usize, + horizon_mobilities: &Horizons>, + age: AgeRangeId, ) -> WeightedCohortContribution { let contribution = self .weighted @@ -63,7 +63,7 @@ impl AggregateState { .iter_mut() .zip(horizon_mobilities.iter()) { - ratio.add(loss, total, weights[age]); + ratio.add(loss, total, *age.select(weights)); } contribution } @@ -92,42 +92,46 @@ impl Vecs { exit: &Exit, ) -> Result<()> { let starting_lengths = indexer.safe_lengths(); - let source_cohorts: Vec<_> = distribution.utxo_cohorts.age_range.iter().collect(); - let transfer_volumes: Vec<_> = source_cohorts - .iter() - .map(|cohort| { - &cohort - .metrics - .activity - .transfer_volume - .cumulative - .sats - .height - }) - .collect(); - let supplies: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.supply.total.sats.height) - .collect(); - let loss_supplies: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.supply.in_loss.sats.height) - .collect(); - let realized_caps: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.realized.cap.cents.height) - .collect(); - let coindays_created: Vec<_> = cointime - .age_range - .iter() - .map(|cohort| &cohort.coindays_created.cumulative.height) - .collect(); + let transfer_volumes = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .activity + .transfer_volume + .cumulative + .sats + .height + }); + let supplies = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .supply + .total + .sats + .height + }); + let loss_supplies = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .supply + .in_loss + .sats + .height + }); + let realized_caps = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .realized + .cap + .cents + .height + }); + let coindays_created = &cointime.age_range.coindays_created.cumulative; self.compute_primary( &starting_lengths, &indexes.timestamp.monotonic, &transfer_volumes, - &coindays_created, + coindays_created, &supplies, &loss_supplies, &realized_caps, @@ -142,28 +146,22 @@ impl Vecs { &mut self, starting_lengths: &Lengths, timestamps: &T, - transfer_volumes: &[&S], - coindays_created: &[&D], - supplies: &[&S], - loss_supplies: &[&S], - realized_caps: &[&C], + transfer_volumes: &AgeRange<&S>, + coindays_created: &D, + supplies: &AgeRange<&S>, + loss_supplies: &AgeRange<&S>, + realized_caps: &AgeRange<&C>, exit: &Exit, ) -> Result where T: ReadableVec, - D: ReadableVec, + D: ReadableVec, S: ReadableVec, C: ReadableVec, { - debug_assert_eq!(transfer_volumes.len(), AGE_COHORT_COUNT); - debug_assert_eq!(coindays_created.len(), AGE_COHORT_COUNT); - debug_assert_eq!(supplies.len(), AGE_COHORT_COUNT); - debug_assert_eq!(loss_supplies.len(), AGE_COHORT_COUNT); - debug_assert_eq!(realized_caps.len(), AGE_COHORT_COUNT); - let source_version: Version = std::iter::once(timestamps.version()) .chain(transfer_volumes.iter().map(|vec| vec.version())) - .chain(coindays_created.iter().map(|vec| vec.version())) + .chain(std::iter::once(coindays_created.version())) .chain(supplies.iter().map(|vec| vec.version())) .chain(loss_supplies.iter().map(|vec| vec.version())) .chain(realized_caps.iter().map(|vec| vec.version())) @@ -175,7 +173,6 @@ impl Vecs { let start = self .primary_vecs_mut() - .into_iter() .map(|vec| vec.len()) .min() .unwrap_or_default() @@ -188,7 +185,7 @@ impl Vecs { let source_end = transfer_volumes .iter() .map(|vec| vec.len()) - .chain(coindays_created.iter().map(|vec| vec.len())) + .chain(std::iter::once(coindays_created.len())) .chain(supplies.iter().map(|vec| vec.len())) .chain(loss_supplies.iter().map(|vec| vec.len())) .chain(realized_caps.iter().map(|vec| vec.len())) @@ -204,61 +201,63 @@ impl Vecs { .collect_one(Height::ZERO) .unwrap_or(Timestamp::ZERO); let bounds = age_bounds_days(); - let mut age_filters = AGE_RANGE_FILTERS.iter(); - let is_sth: [bool; AGE_COHORT_COUNT] = - std::array::from_fn(|_| TERM_FILTERS.short.includes(age_filters.next().unwrap())); - let mut chunk_start = start; while chunk_start < source_end { let chunk_end = (chunk_start + WRITE_INTERVAL).min(source_end); let timestamp_batch = timestamps.collect_range_at(chunk_start, chunk_end); - let transfer_batches: Vec<_> = transfer_volumes - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let coinday_batches: Vec<_> = coindays_created - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let supply_batches: Vec<_> = supplies - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let loss_supply_batches: Vec<_> = loss_supplies - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let cap_batches: Vec<_> = realized_caps - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); + let transfer_batches = AgeRange::from_fn(|id| { + id.select(transfer_volumes) + .collect_range_at(chunk_start, chunk_end) + }); + let coinday_batch = coindays_created.collect_range_at(chunk_start, chunk_end); + let supply_batches = AgeRange::from_fn(|id| { + id.select(supplies).collect_range_at(chunk_start, chunk_end) + }); + let loss_supply_batches = AgeRange::from_fn(|id| { + id.select(loss_supplies) + .collect_range_at(chunk_start, chunk_end) + }); + let cap_batches = AgeRange::from_fn(|id| { + id.select(realized_caps) + .collect_range_at(chunk_start, chunk_end) + }); for offset in 0..(chunk_end - chunk_start) { - let hazards = std::array::from_fn(|index| { + let hazards = AgeRange::from_fn(|id| { spending_rate( - transfer_batches[index][offset], - coinday_batches[index][offset], + id.select(&transfer_batches)[offset], + *id.get(&coinday_batch[offset]), ) }); let network_age = timestamp_batch[offset] .difference_in_days_between_float(genesis_timestamp) .max(MINIMUM_DURATION_DAYS); - let exposures = spending_exposures(&hazards, network_age, &bounds); - let mobilities = exposures.map(mobility); - let horizon_mobilities: Horizons<[f64; AGE_COHORT_COUNT]> = + let exposures = DecayFit::exposures(&hazards, network_age, &bounds); + let mobilities = AgeRange::from_fn(|id| mobility(*id.select(&exposures))); + let horizon_mobilities: Horizons> = Horizons::from_fn(|_, horizon| { - std::array::from_fn(|age| horizon_mobility(&hazards, age, horizon, &bounds)) + AgeRange::from_fn(|age| horizon_mobility(&hazards, age, horizon, &bounds)) }); + self.age_range.spending_rate.push(AgeRangeId::from_fn(|id| { + StoredF64::from(*id.select(&hazards)) + })); + self.age_range + .spending_exposure + .push(AgeRangeId::from_fn(|id| { + StoredF64::from(*id.select(&exposures)) + })); let mut terms = ByTerm::::default(); + let mut mobile_supply = AgeRange::default(); + let mut immobile_supply = AgeRange::default(); - for (index, cohort) in self.age_range.iter_mut().enumerate() { - let mobility = StoredF64::from(mobilities[index]); - let total_supply = supply_batches[index][offset]; - let total_cap = cap_batches[index][offset]; - let loss_supply = loss_supply_batches[index][offset]; + for &id in AgeRangeId::ALL { + let mobility = StoredF64::from(*id.select(&mobilities)); + let total_supply = id.select(&supply_batches)[offset]; + let total_cap = id.select(&cap_batches)[offset]; + let loss_supply = id.select(&loss_supply_batches)[offset]; - let term = if is_sth[index] { + let term = if TERM_FILTERS.short.includes(id.filter()) { &mut terms.short } else { &mut terms.long @@ -269,31 +268,22 @@ impl Vecs { total_cap, mobility, &horizon_mobilities, - index, + id, ); - cohort - .spending_rate - .height - .push(StoredF64::from(hazards[index])); - cohort - .spending_exposure - .height - .push(StoredF64::from(exposures[index])); - cohort - .supply - .mobile - .sats - .height - .push(contribution.weighted_supply); - cohort - .supply - .immobile - .sats - .height - .push(contribution.complement_supply); + *id.select_mut(&mut mobile_supply) = contribution.weighted_supply; + *id.select_mut(&mut immobile_supply) = contribution.complement_supply; } + self.age_range + .supply + .mobile + .push(AgeRangeId::from_fn(|id| *id.select(&mobile_supply))); + self.age_range + .supply + .immobile + .push(AgeRangeId::from_fn(|id| *id.select(&immobile_supply))); + self.all.push(terms.short.merged(terms.long)); self.sth.push(terms.short); self.lth.push(terms.long); @@ -311,17 +301,17 @@ impl Vecs { Ok(Height::from(start)) } - fn primary_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs = Vec::with_capacity(AGE_COHORT_COUNT * 4 + 3 * (5 + HORIZON_COUNT)); - - for cohort in self.age_range.iter_mut() { - vecs.extend(cohort.primary_vecs_mut()); - } - - vecs.extend(self.all.primary_vecs_mut()); - vecs.extend(self.sth.primary_vecs_mut()); - vecs.extend(self.lth.primary_vecs_mut()); - vecs + fn primary_vecs_mut(&mut self) -> impl Iterator { + [ + self.age_range.spending_rate.stored_mut(), + self.age_range.spending_exposure.stored_mut(), + self.age_range.supply.mobile.stored_mut(), + self.age_range.supply.immobile.stored_mut(), + ] + .into_iter() + .chain(self.all.primary_vecs_mut()) + .chain(self.sth.primary_vecs_mut()) + .chain(self.lth.primary_vecs_mut()) } } @@ -348,38 +338,26 @@ impl AggregateVecs { output.supply_in_loss_share.height.push(ratio.value()); } self.cap.cents.height.push(state.weighted.weighted_cap); - self.price.cents.height.push(realized_price( - state.weighted.weighted_cap, - state.weighted.weighted_supply, - )); + self.price + .cents + .height + .push(state.weighted.realized_price()); } - fn primary_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs = Vec::with_capacity(5 + HORIZON_COUNT); - vecs.extend([ + fn primary_vecs_mut(&mut self) -> impl Iterator { + [ &mut self.supply.mobile.sats.height as &mut dyn AnyStoredVec, &mut self.supply.immobile.sats.height, &mut self.supply_in_loss_share.height, &mut self.cap.cents.height, &mut self.price.cents.height, - ]); - vecs.extend( + ] + .into_iter() + .chain( self.horizon .iter_mut() .map(|horizon| &mut horizon.supply_in_loss_share.height as &mut dyn AnyStoredVec), - ); - vecs - } -} - -impl CohortVecs { - fn primary_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; 4] { - [ - &mut self.spending_rate.height, - &mut self.spending_exposure.height, - &mut self.supply.mobile.sats.height, - &mut self.supply.immobile.sats.height, - ] + ) } } @@ -393,135 +371,135 @@ fn spending_rate(transfer_volume: Sats, coindays_created: StoredF64) -> f64 { } } -fn fit_decay( - hazards: &[f64; AGE_COHORT_COUNT], - network_age: f64, - bounds: &[AgeBand; AGE_COHORT_COUNT], -) -> Option { - let mut total_duration = 0.0; - let mut weighted_age = 0.0; - let mut weighted_log_hazard = 0.0; - let mut anchor = None; +impl DecayFit { + fn fit(hazards: &AgeRange, network_age: f64, bounds: &AgeRange) -> Option { + let mut total_duration = 0.0; + let mut weighted_age = 0.0; + let mut weighted_log_hazard = 0.0; + let mut anchor = None; - for (index, band) in bounds[..AGE_COHORT_COUNT - 1].iter().enumerate() { - let hazard = hazards[index]; - if band.upper > network_age || !hazard.is_finite() || hazard <= 0.0 { - continue; + for &id in &AgeRangeId::ALL[..AGE_COHORT_COUNT - 1] { + let band = *id.select(bounds); + let hazard = *id.select(hazards); + if band.upper > network_age || !hazard.is_finite() || hazard <= 0.0 { + continue; + } + + let age = (band.lower + band.upper) / 2.0; + let duration = band.upper - band.lower; + let log_hazard = hazard.ln(); + total_duration += duration; + weighted_age += duration * age; + weighted_log_hazard += duration * log_hazard; + anchor = Some((band.upper, hazard)); } - let age = (band.lower + band.upper) / 2.0; - let duration = band.upper - band.lower; - let log_hazard = hazard.ln(); - total_duration += duration; - weighted_age += duration * age; - weighted_log_hazard += duration * log_hazard; - anchor = Some((band.upper, hazard)); - } - - if total_duration <= 0.0 { - return None; - } - - let mean_age = weighted_age / total_duration; - let mean_log_hazard = weighted_log_hazard / total_duration; - let mut covariance = 0.0; - let mut age_variance = 0.0; - - for (index, band) in bounds[..AGE_COHORT_COUNT - 1].iter().enumerate() { - let hazard = hazards[index]; - if band.upper > network_age || !hazard.is_finite() || hazard <= 0.0 { - continue; + if total_duration <= 0.0 { + return None; } - let age = (band.lower + band.upper) / 2.0; - let duration = band.upper - band.lower; - let log_hazard = hazard.ln(); - let age_offset = age - mean_age; - covariance += duration * age_offset * (log_hazard - mean_log_hazard); - age_variance += duration * age_offset.powi(2); - } + let mean_age = weighted_age / total_duration; + let mean_log_hazard = weighted_log_hazard / total_duration; + let mut covariance = 0.0; + let mut age_variance = 0.0; - if age_variance <= f64::EPSILON { - return None; - } + for &id in &AgeRangeId::ALL[..AGE_COHORT_COUNT - 1] { + let band = *id.select(bounds); + let hazard = *id.select(hazards); + if band.upper > network_age || !hazard.is_finite() || hazard <= 0.0 { + continue; + } - let slope = covariance / age_variance; - if slope >= 0.0 { - return None; - } - - let tau = -1.0 / slope; - if !tau.is_finite() || tau <= 0.0 { - return None; - } - - let (anchor_age, anchor_hazard) = anchor?; - Some(DecayFit { - slope, - tau, - anchor_age, - anchor_hazard, - }) -} - -fn spending_exposures( - hazards: &[f64; AGE_COHORT_COUNT], - network_age: f64, - bounds: &[AgeBand; AGE_COHORT_COUNT], -) -> [f64; AGE_COHORT_COUNT] { - let Some(fit) = fit_decay(hazards, network_age, bounds) else { - return [0.0; AGE_COHORT_COUNT]; - }; - - std::array::from_fn(|start_band| { - spending_exposure(hazards, start_band, network_age, bounds, fit) - }) -} - -fn spending_exposure( - hazards: &[f64; AGE_COHORT_COUNT], - start_band: usize, - network_age: f64, - bounds: &[AgeBand; AGE_COHORT_COUNT], - fit: DecayFit, -) -> f64 { - let start = bounds[start_band]; - let occupied_upper = if start.upper.is_finite() { - start.upper.min(network_age.max(start.lower)) - } else { - start.lower - }; - let mut age = if start.upper.is_finite() { - (start.lower + occupied_upper) / 2.0 - } else { - start.lower - }; - let mut exposure = 0.0; - - for band_index in start_band..AGE_COHORT_COUNT - 1 { - let band = bounds[band_index]; - let duration = (band.upper - age.max(band.lower)).max(MINIMUM_DURATION_DAYS); - let hazard = hazards[band_index]; - let observed = band.upper <= network_age && hazard.is_finite() && hazard > 0.0; - if !observed { - break; + let age = (band.lower + band.upper) / 2.0; + let duration = band.upper - band.lower; + let log_hazard = hazard.ln(); + let age_offset = age - mean_age; + covariance += duration * age_offset * (log_hazard - mean_log_hazard); + age_variance += duration * age_offset.powi(2); } - exposure += hazard * duration; - age = band.upper; + if age_variance <= f64::EPSILON { + return None; + } + + let slope = covariance / age_variance; + if slope >= 0.0 { + return None; + } + + let tau = -1.0 / slope; + if !tau.is_finite() || tau <= 0.0 { + return None; + } + + let (anchor_age, anchor_hazard) = anchor?; + Some(Self { + slope, + tau, + anchor_age, + anchor_hazard, + }) } - let tail = bounds[AGE_COHORT_COUNT - 1]; - let tail_hazard = hazards[AGE_COHORT_COUNT - 1]; - let observed_tail = network_age > tail.lower && tail_hazard.is_finite() && tail_hazard > 0.0; - let (anchor_age, anchor_hazard) = if observed_tail { - (tail.lower, tail_hazard) - } else { - (fit.anchor_age, fit.anchor_hazard) - }; - let continuation_age = age.max(anchor_age); - let continuation_hazard = anchor_hazard * (fit.slope * (continuation_age - anchor_age)).exp(); - exposure + (continuation_hazard * fit.tau).max(0.0) + fn exposures( + hazards: &AgeRange, + network_age: f64, + bounds: &AgeRange, + ) -> AgeRange { + let Some(fit) = Self::fit(hazards, network_age, bounds) else { + return AgeRange::default(); + }; + + AgeRange::from_fn(|start_band| fit.exposure(hazards, start_band, network_age, bounds)) + } + + fn exposure( + self, + hazards: &AgeRange, + start_band: AgeRangeId, + network_age: f64, + bounds: &AgeRange, + ) -> f64 { + let start = *start_band.select(bounds); + let occupied_upper = if start.upper.is_finite() { + start.upper.min(network_age.max(start.lower)) + } else { + start.lower + }; + let mut age = if start.upper.is_finite() { + (start.lower + occupied_upper) / 2.0 + } else { + start.lower + }; + let mut exposure = 0.0; + + for &band_id in &AgeRangeId::ALL[start_band.index()..AGE_COHORT_COUNT - 1] { + let band = *band_id.select(bounds); + let duration = (band.upper - age.max(band.lower)).max(MINIMUM_DURATION_DAYS); + let hazard = *band_id.select(hazards); + let observed = band.upper <= network_age && hazard.is_finite() && hazard > 0.0; + if !observed { + break; + } + + exposure += hazard * duration; + age = band.upper; + } + + let tail = bounds.over_15y; + let tail_hazard = hazards.over_15y; + let observed_tail = + network_age > tail.lower && tail_hazard.is_finite() && tail_hazard > 0.0; + let (anchor_age, anchor_hazard) = if observed_tail { + (tail.lower, tail_hazard) + } else { + (self.anchor_age, self.anchor_hazard) + }; + let continuation_age = age.max(anchor_age); + let continuation_hazard = + anchor_hazard * (self.slope * (continuation_age - anchor_age)).exp(); + exposure + (continuation_hazard * self.tau).max(0.0) + } } #[cfg(test)] @@ -541,8 +519,8 @@ mod tests { #[test] fn fixed_horizon_compounds_hazards_across_age_ranges() { let bounds = age_bounds_days(); - let hazards = [0.01; AGE_COHORT_COUNT]; - let probability = horizon_mobility(&hazards, 2, 30.0, &bounds); + let hazards = AgeRange::from_fn(|_| 0.01); + let probability = horizon_mobility(&hazards, AgeRangeId::From1DTo1W, 30.0, &bounds); assert!((probability - mobility(0.3)).abs() < 1e-12); } @@ -551,8 +529,8 @@ mod tests { fn decay_fit_recovers_an_exponential_lifetime() { let bounds = age_bounds_days(); let expected_tau = 1_000.0; - let hazards = std::array::from_fn(|index| { - let band = bounds[index]; + let hazards = AgeRange::from_fn(|id| { + let band = *id.select(&bounds); let age = if band.upper.is_finite() { (band.lower + band.upper) / 2.0 } else { @@ -561,7 +539,7 @@ mod tests { (-age / expected_tau).exp() }); - let fit = fit_decay(&hazards, 20.0 * 365.0, &bounds).unwrap(); + let fit = DecayFit::fit(&hazards, 20.0 * 365.0, &bounds).unwrap(); assert!((fit.tau - expected_tau).abs() < 1e-9); } @@ -569,8 +547,8 @@ mod tests { #[test] fn oldest_cohort_exposure_is_its_observed_tail_lifetime() { let bounds = age_bounds_days(); - let hazards = std::array::from_fn(|index| { - let band = bounds[index]; + let hazards = AgeRange::from_fn(|id| { + let band = *id.select(&bounds); let age = if band.upper.is_finite() { (band.lower + band.upper) / 2.0 } else { @@ -579,13 +557,10 @@ mod tests { (-age / 1_000.0).exp() }); let network_age = 20.0 * 365.0; - let fit = fit_decay(&hazards, network_age, &bounds).unwrap(); - let exposures = spending_exposures(&hazards, network_age, &bounds); + let fit = DecayFit::fit(&hazards, network_age, &bounds).unwrap(); + let exposures = DecayFit::exposures(&hazards, network_age, &bounds); - assert!( - (exposures[AGE_COHORT_COUNT - 1] - hazards[AGE_COHORT_COUNT - 1] * fit.tau).abs() - < 1e-12 - ); + assert!((exposures.over_15y - hazards.over_15y * fit.tau).abs() < 1e-12); } #[test] @@ -605,7 +580,7 @@ mod tests { #[test] fn term_aggregates_merge_into_all() { - let horizons = Horizons::from_fn(|_, _| [0.25; AGE_COHORT_COUNT]); + let horizons = Horizons::from_fn(|_, _| AgeRange::from_fn(|_| 0.25)); let mut direct = AggregateState::default(); direct.add( Sats::from(100_u64), @@ -613,7 +588,7 @@ mod tests { Cents::from(1_000_u64), StoredF64::from(0.3), &horizons, - 0, + AgeRangeId::Under1H, ); direct.add( Sats::from(200_u64), @@ -621,7 +596,7 @@ mod tests { Cents::from(3_000_u64), StoredF64::from(0.4), &horizons, - 1, + AgeRangeId::From1HTo1D, ); let mut sth = AggregateState::default(); @@ -631,7 +606,7 @@ mod tests { Cents::from(1_000_u64), StoredF64::from(0.3), &horizons, - 0, + AgeRangeId::Under1H, ); let mut lth = AggregateState::default(); lth.add( @@ -640,7 +615,7 @@ mod tests { Cents::from(3_000_u64), StoredF64::from(0.4), &horizons, - 1, + AgeRangeId::From1HTo1D, ); let merged = sth.merged(lth); @@ -668,10 +643,10 @@ mod tests { #[test] fn age_ranges_belong_to_exactly_one_term() { - for filter in AGE_RANGE_FILTERS.iter() { + for id in AgeRangeId::ALL { assert_ne!( - TERM_FILTERS.short.includes(filter), - TERM_FILTERS.long.includes(filter) + TERM_FILTERS.short.includes(id.filter()), + TERM_FILTERS.long.includes(id.filter()) ); } } diff --git a/crates/brk_computer/src/frameworks/coinflow/import.rs b/crates/brk_computer/src/frameworks/coinflow/import.rs index 7a66f1457..80952e272 100644 --- a/crates/brk_computer/src/frameworks/coinflow/import.rs +++ b/crates/brk_computer/src/frameworks/coinflow/import.rs @@ -1,15 +1,23 @@ -use brk_cohort::{AgeRange, CohortContext, TERM_NAMES}; +use brk_cohort::{AgeRangeId, CohortContext, TERM_NAMES}; use brk_error::Result; use brk_types::{Cents, Height, StoredF64, Version}; -use vecdb::{CachedBoxedVec, Database, ReadableCloneableVec, UnaryTransform}; - -use super::{AggregateVecs, CohortVecs, HorizonVecs, Horizons, Split, Vecs, mobility}; -use crate::{ - indexes, - internal::{FiatPerBlock, LazyPerBlock, PerBlock, PriceWithRatioPerBlock, SpotValuePerBlock}, +use vecdb::{ + CachedBoxedVec, Database, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec, UnaryTransform, }; -const VERSION: Version = Version::TWO; +use super::{ + AgeRangeVecs, AggregateVecs, HorizonVecs, Horizons, SpendingExposureSeries, Split, Vecs, + mobility, +}; +use crate::{ + indexes, + internal::{ + ColumnarPerBlock, FiatPerBlock, LazyColumnPerBlock, LazyColumnSpotValuePerBlock, + LazyPerBlock, PerBlock, PriceWithRatioPerBlock, SpotValuePerBlock, + }, +}; + +const VERSION: Version = Version::new(4); struct ExposureToMobility; @@ -20,46 +28,35 @@ impl UnaryTransform for ExposureToMobility { } } -fn import_split(mut import: impl FnMut(&str) -> Result) -> Result> { - Ok(Split { - mobile: import("mobile")?, - immobile: import("immobile")?, - }) -} - -impl CohortVecs { - fn forced_import( - db: &vecdb::Database, - name: &str, +impl SpendingExposureSeries { + fn new( version: Version, + source: &ReadOnlyColumnarVec, AgeRangeId>, indexes: &indexes::Vecs, - spot_price: &CachedBoxedVec, - ) -> Result { - let spending_rate = - PerBlock::forced_import(db, &format!("{name}_spending_rate"), version, indexes)?; - let spending_exposure = - PerBlock::forced_import(db, &format!("{name}_spending_exposure"), version, indexes)?; - let mobility = LazyPerBlock::from_computed::( - &format!("{name}_mobility"), - version, - spending_exposure.height.read_only_boxed_clone(), - &spending_exposure, - ); + ) -> Self { + let age_range = AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnPerBlock::new( + &format!("{name}_spending_exposure"), + version, + source, + column, + indexes, + ) + }); + let mobility = AgeRangeId::series(CohortContext::Utxo, |column, name| { + let exposure = column.select(&age_range); + LazyPerBlock::from_resolutions::( + &format!("{name}_mobility"), + version, + exposure.height.read_only_boxed_clone(), + &exposure.resolutions, + ) + }); - Ok(Self { - spending_rate, - spending_exposure, + Self { + age_range, mobility, - supply: import_split(|side| { - SpotValuePerBlock::forced_import( - db, - &format!("{name}_{side}_supply"), - version, - indexes, - spot_price, - ) - })?, - }) + } } } @@ -78,7 +75,7 @@ impl AggregateVecs { }; Ok(Self { - supply: import_split(|side| { + supply: Split::try_from_fn(|side| { SpotValuePerBlock::forced_import( db, &format!("{prefix}{side}_supply"), @@ -130,12 +127,54 @@ impl Vecs { let version = parent_version + VERSION; let aggregate_version = version + Version::ONE; let spot_price = prices.spot.cents.height.read_only_cached_boxed_clone(); + let spending_rate = ColumnarPerBlock::forced_import( + db, + &CohortContext::Utxo.prefixed("age_range_spending_rate"), + version, + |source| { + AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnPerBlock::new( + &format!("{name}_spending_rate"), + version, + source, + column, + indexes, + ) + }) + }, + )?; + let spending_exposure = ColumnarPerBlock::forced_import( + db, + &CohortContext::Utxo.prefixed("age_range_spending_exposure"), + version, + |source| SpendingExposureSeries::new(version, source, indexes), + )?; + let supply = Split::try_from_fn(|side| { + ColumnarPerBlock::forced_import( + db, + &CohortContext::Utxo.prefixed(&format!("age_range_{side}_supply_sats")), + version, + |source| { + AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnSpotValuePerBlock::new( + &format!("{name}_{side}_supply"), + version, + source, + column, + indexes, + &spot_price, + ) + }) + }, + ) + })?; let this = Self { - age_range: AgeRange::try_new(|_, name| { - let name = CohortContext::Utxo.prefixed(name); - CohortVecs::forced_import(db, &name, version, indexes, &spot_price) - })?, + age_range: AgeRangeVecs { + spending_rate, + spending_exposure, + supply, + }, all: AggregateVecs::forced_import(db, "", version, indexes, &spot_price)?, sth: AggregateVecs::forced_import( db, diff --git a/crates/brk_computer/src/frameworks/coinflow/mod.rs b/crates/brk_computer/src/frameworks/coinflow/mod.rs index 892463771..45a045281 100644 --- a/crates/brk_computer/src/frameworks/coinflow/mod.rs +++ b/crates/brk_computer/src/frameworks/coinflow/mod.rs @@ -2,32 +2,34 @@ mod compute; mod import; mod vecs; -use brk_cohort::AGE_RANGE_BOUNDS; +use brk_cohort::{AgeRange, AgeRangeId}; +use vecdb::ColumnId; pub(crate) use brk_cohort::AGE_RANGE_COUNT as AGE_COHORT_COUNT; pub(crate) use vecs::HORIZON_DAYS; -pub use vecs::{AggregateVecs, CohortVecs, HorizonVecs, Horizons, Split, Vecs}; +pub use vecs::{ + AgeRangeVecs, AggregateVecs, HorizonVecs, Horizons, SpendingExposureSeries, Split, Vecs, +}; pub(crate) const HORIZON_COUNT: usize = 7; pub(crate) const HOURS_PER_DAY: f64 = 24.0; pub(crate) const MINIMUM_DURATION_DAYS: f64 = 1.0 / HOURS_PER_DAY; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub(crate) struct AgeBand { pub lower: f64, pub upper: f64, } -pub(crate) fn age_bounds_days() -> [AgeBand; AGE_COHORT_COUNT] { - let mut bounds = AGE_RANGE_BOUNDS.iter(); - std::array::from_fn(|index| { - let bound = bounds.next().unwrap(); +pub(crate) fn age_bounds_days() -> AgeRange { + AgeRange::from_fn(|id| { + let bound = id.bounds(); AgeBand { lower: bound.start as f64 / HOURS_PER_DAY, - upper: if index + 1 < AGE_COHORT_COUNT { - bound.end as f64 / HOURS_PER_DAY - } else { + upper: if id == AgeRangeId::Over15Y { f64::INFINITY + } else { + bound.end as f64 / HOURS_PER_DAY }, } }) @@ -43,34 +45,34 @@ pub(crate) fn mobility(exposure: f64) -> f64 { } pub(crate) fn horizon_mobility( - hazards: &[f64; AGE_COHORT_COUNT], - start_band: usize, + hazards: &AgeRange, + start_band: AgeRangeId, horizon: f64, - bounds: &[AgeBand; AGE_COHORT_COUNT], + bounds: &AgeRange, ) -> f64 { - let start = bounds[start_band]; + let start = *start_band.select(bounds); let mut age = if start.upper.is_finite() { (start.lower + start.upper) / 2.0 } else { start.lower }; let mut remaining = horizon; - let mut band = start_band; let mut exposure = 0.0; - while remaining > 0.0 && band < AGE_COHORT_COUNT { - let upper = bounds[band].upper; + for &band in &AgeRangeId::ALL[start_band.index()..] { + if remaining <= 0.0 { + break; + } + let bound = *band.select(bounds); + let upper = bound.upper; let duration = if upper.is_finite() { remaining.min((upper - age).max(MINIMUM_DURATION_DAYS)) } else { remaining }; - exposure += hazards[band].max(0.0) * duration; + exposure += band.select(hazards).max(0.0) * duration; remaining -= duration; - band += 1; - if band < AGE_COHORT_COUNT { - age = bounds[band].lower; - } + age = upper; } mobility(exposure) diff --git a/crates/brk_computer/src/frameworks/coinflow/vecs.rs b/crates/brk_computer/src/frameworks/coinflow/vecs.rs index 09b307372..9c4ebe6ad 100644 --- a/crates/brk_computer/src/frameworks/coinflow/vecs.rs +++ b/crates/brk_computer/src/frameworks/coinflow/vecs.rs @@ -1,10 +1,12 @@ -use brk_cohort::AgeRange; +use brk_cohort::{AgeRange, AgeRangeId}; use brk_traversable::Traversable; -use brk_types::{Cents, StoredF64}; +use brk_types::{Cents, Sats, StoredF64}; +use derive_more::{Deref, DerefMut}; use vecdb::{Rw, StorageMode}; use crate::internal::{ - FiatPerBlock, LazyPerBlock, PerBlock, PriceWithRatioPerBlock, SpotValuePerBlock, + ColumnarPerBlock, FiatPerBlock, LazyColumnPerBlock, LazyColumnSpotValuePerBlock, LazyPerBlock, + PerBlock, PriceWithRatioPerBlock, SpotValuePerBlock, }; #[derive(Clone, Copy, Traversable)] @@ -108,12 +110,36 @@ pub struct Split { pub immobile: T, } +impl Split { + pub(crate) fn try_from_fn(mut create: impl FnMut(&str) -> Result) -> Result { + Ok(Self { + mobile: create("mobile")?, + immobile: create("immobile")?, + }) + } +} + +#[derive(Clone, Deref, DerefMut, Traversable)] +pub struct SpendingExposureSeries { + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub age_range: AgeRange>, + pub mobility: AgeRange>, +} + #[derive(Traversable)] -pub struct CohortVecs { - pub spending_rate: PerBlock, - pub spending_exposure: PerBlock, - pub mobility: LazyPerBlock, - pub supply: Split>, +pub struct AgeRangeVecs { + pub spending_rate: ColumnarPerBlock< + StoredF64, + AgeRangeId, + AgeRange>, + M, + >, + pub spending_exposure: ColumnarPerBlock, + pub supply: Split< + ColumnarPerBlock>, M>, + >, } #[derive(Traversable)] @@ -128,7 +154,7 @@ pub struct AggregateVecs { #[derive(Traversable)] pub struct Vecs { - pub age_range: AgeRange>, + pub age_range: AgeRangeVecs, #[traversable(flatten)] pub all: AggregateVecs, pub sth: AggregateVecs, diff --git a/crates/brk_computer/src/frameworks/cointime/activity/mod.rs b/crates/brk_computer/src/frameworks/cointime/activity/mod.rs index 7aea78f39..c2c7f1642 100644 --- a/crates/brk_computer/src/frameworks/cointime/activity/mod.rs +++ b/crates/brk_computer/src/frameworks/cointime/activity/mod.rs @@ -2,5 +2,4 @@ mod compute; mod import; mod vecs; -pub(crate) use compute::compute_rest; pub use vecs::{DerivedVecs, Vecs}; diff --git a/crates/brk_computer/src/frameworks/cointime/age_range/compute.rs b/crates/brk_computer/src/frameworks/cointime/age_range/compute.rs index 09e34c3fe..13a94ab92 100644 --- a/crates/brk_computer/src/frameworks/cointime/age_range/compute.rs +++ b/crates/brk_computer/src/frameworks/cointime/age_range/compute.rs @@ -1,12 +1,11 @@ -use brk_cohort::{AGE_RANGE_BOUNDS, AGE_RANGE_COUNT}; +use brk_cohort::{AgeRange, AgeRangeId}; use brk_error::Result; use brk_indexer::Indexer; use brk_types::{Bitcoin, Height, ONE_DAY_IN_SEC_F64, Sats, StoredF64, Timestamp, Version}; -use vecdb::{AnyVec, Exit, ReadableVec}; +use vecdb::{AnyVec, CheckedSub, ColumnId, Exit, ReadableVec, StoredVec, WritableVec}; -use super::super::activity; -use super::{CohortVecs, SupplyVecs, Vecs}; -use crate::{distribution, indexes}; +use super::Vecs; +use crate::{distribution, frameworks::WeightedCohortState, indexes}; const HOURS_PER_DAY: f64 = 24.0; const WRITE_INTERVAL: usize = 10_000; @@ -20,19 +19,29 @@ impl Vecs { exit: &Exit, ) -> Result<()> { let starting_height = indexer.safe_lengths().height; - let source_cohorts: Vec<_> = distribution.utxo_cohorts.age_range.iter().collect(); - let supplies: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.supply.total.sats.height) - .collect(); - let transfer_volumes: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.activity.transfer_volume.block.sats) - .collect(); - let coindays_destroyed: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.activity.coindays_destroyed.block) - .collect(); + let supplies = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .supply + .total + .sats + .height + }); + let transfer_volumes = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .activity + .transfer_volume + .block + .sats + }); + let coindays_destroyed = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .activity + .coindays_destroyed + .block + }); self.compute_created( starting_height, @@ -53,116 +62,73 @@ impl Vecs { &mut self, starting_height: Height, timestamps: &T, - supplies: &[&S], + supplies: &AgeRange<&S>, exit: &Exit, ) -> Result<()> where T: ReadableVec, S: ReadableVec, { - debug_assert_eq!(supplies.len(), AGE_RANGE_COUNT); - - let created_version: Version = std::iter::once(timestamps.version()) + let version: Version = std::iter::once(timestamps.version()) .chain(supplies.iter().map(|vec| vec.version())) .sum(); - let mut cohorts: Vec<&mut CohortVecs> = self.iter_mut().collect(); - - for cohort in cohorts.iter_mut() { - cohort - .coindays_created - .validate_computed_version_or_reset(created_version)?; - } - - let start = cohorts - .iter() - .map(|cohort| cohort.coindays_created.cumulative.height.len()) - .min() - .unwrap_or_default() - .min(usize::from(starting_height)); - - for cohort in cohorts.iter_mut() { - cohort.coindays_created.truncate_if_needed_at(start)?; - } - let source_end = supplies .iter() .map(|vec| vec.len()) .chain(std::iter::once(timestamps.len())) .min() .unwrap_or_default(); + self.coindays_created.cumulative.compute_batched_to( + starting_height, + source_end, + version, + WRITE_INTERVAL, + |created, range| { + let mut cumulative = created + .collect_last() + .unwrap_or_else(|| AgeRangeId::from_fn(|_| StoredF64::default())); + let timestamp_start = range.start.saturating_sub(1); + let timestamp_batch = timestamps.collect_range_at(timestamp_start, range.end); + let supply_batches = AgeRange::from_fn(|id| { + id.select(supplies).collect_range_at(range.start, range.end) + }); - let mut chunk_start = start; - while chunk_start < source_end { - let chunk_end = (chunk_start + WRITE_INTERVAL).min(source_end); - let timestamp_start = chunk_start.saturating_sub(1); - let timestamp_batch = timestamps.collect_range_at(timestamp_start, chunk_end); - let supply_batches: Vec<_> = supplies - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - - for (offset, _) in supply_batches.first().unwrap().iter().enumerate() { - let interval_seconds = - monotonic_interval_seconds(×tamp_batch, chunk_start, offset); - - for (index, cohort) in cohorts.iter_mut().enumerate() { - cohort.coindays_created.push_block(coindays_created( - supply_batches[index][offset], - interval_seconds, - )); + for offset in 0..range.len() { + let interval_seconds = + monotonic_interval_seconds(×tamp_batch, range.start, offset); + created.push(AgeRangeId::from_fn(|column| { + let value = column.get_mut(&mut cumulative); + *value += coindays_created( + column.select(&supply_batches)[offset], + interval_seconds, + ); + *value + })); } - } - - { - let _lock = exit.lock(); - for cohort in cohorts.iter_mut() { - cohort.coindays_created.write()?; - } - } - chunk_start = chunk_end; - } + Ok(()) + }, + exit, + )?; Ok(()) } fn compute_consumed( &mut self, starting_height: Height, - transfer_volumes: &[&V], - source_coindays_destroyed: &[&D], + transfer_volumes: &AgeRange<&V>, + source_coindays_destroyed: &AgeRange<&D>, exit: &Exit, ) -> Result<()> where V: ReadableVec, D: ReadableVec, { - debug_assert_eq!(transfer_volumes.len(), AGE_RANGE_COUNT); - debug_assert_eq!(source_coindays_destroyed.len(), AGE_RANGE_COUNT); - - let destroyed_version: Version = transfer_volumes + let version: Version = transfer_volumes .iter() .map(|vec| vec.version()) .chain(source_coindays_destroyed.iter().map(|vec| vec.version())) .sum(); - let mut cohorts: Vec<&mut CohortVecs> = self.iter_mut().collect(); - - for cohort in cohorts.iter_mut() { - cohort - .coindays_consumed - .validate_computed_version_or_reset(destroyed_version)?; - } - - let start = cohorts - .iter() - .map(|cohort| cohort.coindays_consumed.cumulative.height.len()) - .min() - .unwrap_or_default() - .min(usize::from(starting_height)); - - for cohort in cohorts.iter_mut() { - cohort.coindays_consumed.truncate_if_needed_at(start)?; - } - let source_end = transfer_volumes .iter() .map(|vec| vec.len()) @@ -170,107 +136,156 @@ impl Vecs { .min() .unwrap_or_default(); let bounds = age_bounds_days(); - - let mut chunk_start = start; - while chunk_start < source_end { - let chunk_end = (chunk_start + WRITE_INTERVAL).min(source_end); - let transfer_batches: Vec<_> = transfer_volumes - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let destroyed_batches: Vec<_> = source_coindays_destroyed - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - - for offset in 0..(chunk_end - chunk_start) { - let volumes_btc: [f64; AGE_RANGE_COUNT] = std::array::from_fn(|index| { - f64::from(Bitcoin::from(transfer_batches[index][offset])) + self.coindays_consumed.cumulative.compute_batched_to( + starting_height, + source_end, + version, + WRITE_INTERVAL, + |target, range| { + let mut cumulative = target + .collect_last() + .unwrap_or_else(|| AgeRangeId::from_fn(|_| StoredF64::default())); + let transfer_batches = AgeRange::from_fn(|id| { + id.select(transfer_volumes) + .collect_range_at(range.start, range.end) + }); + let destroyed_batches = AgeRange::from_fn(|id| { + id.select(source_coindays_destroyed) + .collect_range_at(range.start, range.end) }); - let cdd: [f64; AGE_RANGE_COUNT] = - std::array::from_fn(|index| f64::from(destroyed_batches[index][offset])); - let consumed = allocate_consumed_coindays(volumes_btc, cdd, &bounds); - for (index, cohort) in cohorts.iter_mut().enumerate() { - cohort - .coindays_consumed - .push_block(StoredF64::from(consumed[index])); + for offset in 0..range.len() { + let volumes_btc = AgeRange::from_fn(|id| { + f64::from(Bitcoin::from(id.select(&transfer_batches)[offset])) + }); + let coindays_destroyed = + AgeRange::from_fn(|id| f64::from(id.select(&destroyed_batches)[offset])); + let consumed = + allocate_consumed_coindays(volumes_btc, coindays_destroyed, &bounds); + target.push(AgeRangeId::from_fn(|column| { + let value = column.get_mut(&mut cumulative); + *value += StoredF64::from(*column.select(&consumed)); + *value + })); } - } - - { - let _lock = exit.lock(); - for cohort in cohorts.iter_mut() { - cohort.coindays_consumed.write()?; - } - } - chunk_start = chunk_end; - } + Ok(()) + }, + exit, + )?; Ok(()) } fn compute_rest( &mut self, starting_height: Height, - supplies: &[&S], + supplies: &AgeRange<&S>, exit: &Exit, ) -> Result<()> where S: ReadableVec, { - debug_assert_eq!(supplies.len(), AGE_RANGE_COUNT); - - for (cohort, &total_supply) in self.iter_mut().zip(supplies) { - let CohortVecs { - coindays_created, - coindays_consumed, - coindays_stored, - activity: activity_vecs, - supply, - } = cohort; - - activity::compute_rest( + { + let created = self.coindays_created.cumulative.read_only_clone(); + let consumed = self.coindays_consumed.cumulative.read_only_clone(); + self.coindays_stored.cumulative.compute_transform2_batched( starting_height, - coindays_created, - coindays_consumed, - coindays_stored, - &mut activity_vecs.wakefulness, + &created, + &consumed, + WRITE_INTERVAL, + |(height, created, consumed, ..)| { + let stored = AgeRangeId::from_fn(|column| { + (*column.get(&created)) + .checked_sub(*column.get(&consumed)) + .expect("coindays stored underflow") + }); + (height, stored) + }, exit, )?; - - supply.compute_from( + self.activity.height.compute_transform2_batched( starting_height, - total_supply, - &activity_vecs.wakefulness.height, - &activity_vecs.dormancy.height, + &consumed, + &created, + WRITE_INTERVAL, + |(height, consumed, created, ..)| { + let wakefulness = AgeRangeId::from_fn(|column| { + *column.get(&consumed) / *column.get(&created) + }); + (height, wakefulness) + }, exit, )?; } - Ok(()) + self.compute_supply(starting_height, supplies, exit) } -} -impl SupplyVecs { - fn compute_from( + fn compute_supply( &mut self, starting_height: Height, - total_supply: &impl ReadableVec, - wakefulness: &impl ReadableVec, - dormancy: &impl ReadableVec, + supplies: &AgeRange<&S>, exit: &Exit, - ) -> Result<()> { - self.awake.sats.height.compute_multiply( - starting_height, - total_supply, - wakefulness, - exit, - )?; - self.dormant - .sats - .height - .compute_multiply(starting_height, total_supply, dormancy, exit)?; + ) -> Result<()> + where + S: ReadableVec, + { + let wakefulness = self.activity.height.read_only_clone(); + let source_version: Version = std::iter::once(wakefulness.version()) + .chain(supplies.iter().map(|vec| vec.version())) + .sum(); + let supply = &mut self.supply; + supply + .awake + .validate_computed_version_or_reset(source_version)?; + supply + .dormant + .validate_computed_version_or_reset(source_version)?; + + let start = [supply.awake.height.len(), supply.dormant.height.len()] + .into_iter() + .min() + .unwrap_or_default() + .min(usize::from(starting_height)); + supply.awake.truncate_if_needed_at(start)?; + supply.dormant.truncate_if_needed_at(start)?; + + let source_end = supplies + .iter() + .map(|vec| vec.len()) + .chain(std::iter::once(wakefulness.len())) + .min() + .unwrap_or_default(); + let mut chunk_start = start; + while chunk_start < source_end { + let chunk_end = (chunk_start + WRITE_INTERVAL).min(source_end); + let supply_batches = AgeRange::from_fn(|id| { + id.select(supplies).collect_range_at(chunk_start, chunk_end) + }); + let wakefulness_batch = wakefulness.collect_range_at(chunk_start, chunk_end); + + for (offset, weights) in wakefulness_batch.iter().enumerate() { + let split = AgeRange::from_fn(|id| { + WeightedCohortState::split_supply( + id.select(&supply_batches)[offset], + *id.get(weights), + ) + }); + supply + .awake + .push(AgeRangeId::from_fn(|id| id.select(&split).0)); + supply + .dormant + .push(AgeRangeId::from_fn(|id| id.select(&split).1)); + } + + { + let _lock = exit.lock(); + supply.awake.write()?; + supply.dormant.write()?; + } + chunk_start = chunk_end; + } Ok(()) } @@ -295,34 +310,34 @@ fn coindays_created(supply: Sats, interval_seconds: u32) -> StoredF64 { StoredF64::from(f64::from(Bitcoin::from(supply)) * interval_seconds as f64 / ONE_DAY_IN_SEC_F64) } -fn age_bounds_days() -> [(f64, f64); AGE_RANGE_COUNT] { - let mut bounds = AGE_RANGE_BOUNDS.iter(); - std::array::from_fn(|index| { - let bound = bounds.next().unwrap(); +fn age_bounds_days() -> AgeRange<(f64, f64)> { + AgeRange::from_fn(|id| { + let bound = id.bounds(); let lower = bound.start as f64 / HOURS_PER_DAY; - let width = if index + 1 < AGE_RANGE_COUNT { - (bound.end - bound.start) as f64 / HOURS_PER_DAY - } else { + let width = if id == AgeRangeId::Over15Y { 0.0 + } else { + (bound.end - bound.start) as f64 / HOURS_PER_DAY }; (lower, width) }) } fn allocate_consumed_coindays( - transfer_volume_btc: [f64; AGE_RANGE_COUNT], - coindays_destroyed: [f64; AGE_RANGE_COUNT], - bounds: &[(f64, f64); AGE_RANGE_COUNT], -) -> [f64; AGE_RANGE_COUNT] { - let mut result = [0.0; AGE_RANGE_COUNT]; + transfer_volume_btc: AgeRange, + coindays_destroyed: AgeRange, + bounds: &AgeRange<(f64, f64)>, +) -> AgeRange { + let mut result = AgeRange::default(); let mut older_transfer_volume = 0.0; - for index in (0..AGE_RANGE_COUNT).rev() { - let (lower_days, width_days) = bounds[index]; + for &id in AgeRangeId::ALL.iter().rev() { + let (lower_days, width_days) = *id.select(bounds); + let transfer_volume = *id.select(&transfer_volume_btc); let within_cohort = - (coindays_destroyed[index] - transfer_volume_btc[index] * lower_days).max(0.0); - result[index] = within_cohort + older_transfer_volume * width_days; - older_transfer_volume += transfer_volume_btc[index]; + (*id.select(&coindays_destroyed) - transfer_volume * lower_days).max(0.0); + *id.select_mut(&mut result) = within_cohort + older_transfer_volume * width_days; + older_transfer_volume += transfer_volume; } result @@ -330,6 +345,8 @@ fn allocate_consumed_coindays( #[cfg(test)] mod tests { + use brk_cohort::AGE_RANGE_COUNT; + use super::*; #[test] @@ -355,67 +372,76 @@ mod tests { #[test] fn destruction_at_a_boundary_stays_in_the_ranges_already_traversed() { - let mut volumes = [0.0; AGE_RANGE_COUNT]; - let mut cdd = [0.0; AGE_RANGE_COUNT]; - volumes[2] = 1.0; - cdd[2] = 1.0; + let mut volumes = AgeRange::default(); + let mut cdd = AgeRange::default(); + volumes._1d_to_1w = 1.0; + cdd._1d_to_1w = 1.0; let allocated = allocate_consumed_coindays(volumes, cdd, &age_bounds_days()); - assert!((allocated[0] - 1.0 / HOURS_PER_DAY).abs() < 1e-12); - assert!((allocated[1] - 23.0 / HOURS_PER_DAY).abs() < 1e-12); - assert!(allocated[2].abs() < 1e-12); + assert!((allocated.under_1h - 1.0 / HOURS_PER_DAY).abs() < 1e-12); + assert!((allocated._1h_to_1d - 23.0 / HOURS_PER_DAY).abs() < 1e-12); + assert!(allocated._1d_to_1w.abs() < 1e-12); assert!((allocated.iter().sum::() - 1.0).abs() < 1e-12); } #[test] fn consumed_coindays_cover_every_traversed_cohort() { - let mut volumes = [0.0; AGE_RANGE_COUNT]; - let mut cdd = [0.0; AGE_RANGE_COUNT]; - volumes[2] = 2.0; - cdd[2] = 20.0; + let mut volumes = AgeRange::default(); + let mut cdd = AgeRange::default(); + volumes._1d_to_1w = 2.0; + cdd._1d_to_1w = 20.0; let allocated = allocate_consumed_coindays(volumes, cdd, &age_bounds_days()); - assert!((allocated[0] - 2.0 / HOURS_PER_DAY).abs() < 1e-12); - assert!((allocated[1] - 46.0 / HOURS_PER_DAY).abs() < 1e-12); - assert!((allocated[2] - 18.0).abs() < 1e-12); + assert!((allocated.under_1h - 2.0 / HOURS_PER_DAY).abs() < 1e-12); + assert!((allocated._1h_to_1d - 46.0 / HOURS_PER_DAY).abs() < 1e-12); + assert!((allocated._1d_to_1w - 18.0).abs() < 1e-12); assert!((allocated.iter().sum::() - 20.0).abs() < 1e-12); } #[test] fn allocated_coindays_conserve_mixed_cohort_destruction() { let bounds = age_bounds_days(); - let mut volumes = [0.0; AGE_RANGE_COUNT]; - let mut cdd = [0.0; AGE_RANGE_COUNT]; + let mut volumes = AgeRange::default(); + let mut cdd = AgeRange::default(); - for index in [0, 1, 2, 10, 20, AGE_RANGE_COUNT - 2, AGE_RANGE_COUNT - 1] { - let (lower, width) = bounds[index]; - let volume = index as f64 + 1.0; + for id in [ + AgeRangeId::Under1H, + AgeRangeId::From1HTo1D, + AgeRangeId::From1DTo1W, + AgeRangeId::From9MTo1Y, + AgeRangeId::From10YTo12Y, + AgeRangeId::From12YTo15Y, + AgeRangeId::Over15Y, + ] { + let (lower, width) = *id.select(&bounds); + let volume = id.index() as f64 + 1.0; let age = lower + if width > 0.0 { width / 2.0 } else { 30.0 }; - volumes[index] = volume; - cdd[index] = volume * age; + *id.select_mut(&mut volumes) = volume; + *id.select_mut(&mut cdd) = volume * age; } + let expected = cdd.iter().sum::(); let allocated = allocate_consumed_coindays(volumes, cdd, &bounds); - assert!((allocated.iter().sum::() - cdd.iter().sum::()).abs() < 1e-9); + assert!((allocated.iter().sum::() - expected).abs() < 1e-9); } #[test] fn bounds_and_allocation_cover_every_canonical_age_range() { let bounds = age_bounds_days(); - let mut volumes = [0.0; AGE_RANGE_COUNT]; - let mut cdd = [0.0; AGE_RANGE_COUNT]; - let last = AGE_RANGE_COUNT - 1; + let mut volumes = AgeRange::default(); + let mut cdd = AgeRange::default(); - volumes[last] = 1.0; - cdd[last] = bounds[last].0 + 30.0; + volumes.over_15y = 1.0; + cdd.over_15y = bounds.over_15y.0 + 30.0; + let expected = cdd.iter().sum::(); let allocated = allocate_consumed_coindays(volumes, cdd, &bounds); - assert_eq!(bounds.len(), AGE_RANGE_BOUNDS.iter().count()); - assert!(allocated[last] > 0.0); - assert!((allocated.iter().sum::() - cdd.iter().sum::()).abs() < 1e-9); + assert_eq!(bounds.iter().count(), AGE_RANGE_COUNT); + assert!(allocated.over_15y > 0.0); + assert!((allocated.iter().sum::() - expected).abs() < 1e-9); } } diff --git a/crates/brk_computer/src/frameworks/cointime/age_range/import.rs b/crates/brk_computer/src/frameworks/cointime/age_range/import.rs index 44d53a1ca..318a56be6 100644 --- a/crates/brk_computer/src/frameworks/cointime/age_range/import.rs +++ b/crates/brk_computer/src/frameworks/cointime/age_range/import.rs @@ -1,110 +1,59 @@ -use brk_cohort::{AgeRange, CohortContext}; +use brk_cohort::{AgeRangeId, CohortContext}; use brk_error::Result; -use brk_types::{Cents, Height, Version}; -use vecdb::{CachedBoxedVec, Database, ReadableCloneableVec}; +use brk_types::{Cents, Height, StoredF64, Version}; +use vecdb::{CachedBoxedVec, Database, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec}; -use super::{ActivityVecs, CohortVecs, SupplyVecs, Vecs}; +use super::{ActivitySeries, SupplyVecs, Vecs}; use crate::{ indexes, internal::{ - CachedWindowStartVec, LazyPerBlock, OddsF64, OneMinusF64, PerBlock, - PerBlockCumulativeRolling, SpotValuePerBlock, Windows, + CachedWindowStartVec, ColumnarPerBlock, ColumnarPerBlockCumulativeRolling, + LazyColumnPerBlock, LazyColumnPerBlockCumulativeRolling, LazyColumnSpotValuePerBlock, + LazyPerBlock, OddsF64, OneMinusF64, Windows, }, }; -const VERSION: Version = Version::ONE; +const VERSION: Version = Version::TWO; -impl ActivityVecs { - fn forced_import( - db: &Database, - name: &str, +impl ActivitySeries { + fn new( version: Version, + source: &ReadOnlyColumnarVec, AgeRangeId>, indexes: &indexes::Vecs, - ) -> Result { - let wakefulness = - PerBlock::forced_import(db, &format!("{name}_wakefulness"), version, indexes)?; - let dormancy = LazyPerBlock::from_computed::( - &format!("{name}_dormancy"), - version, - wakefulness.height.read_only_boxed_clone(), - &wakefulness, - ); - let wakefulness_to_dormancy = LazyPerBlock::from_computed::( - &format!("{name}_wakefulness_to_dormancy"), - version, - wakefulness.height.read_only_boxed_clone(), - &wakefulness, - ); + ) -> Self { + let wakefulness = AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnPerBlock::new( + &format!("{name}_wakefulness"), + version, + source, + column, + indexes, + ) + }); + let dormancy = AgeRangeId::series(CohortContext::Utxo, |column, name| { + let wakefulness = column.select(&wakefulness); + LazyPerBlock::from_resolutions::( + &format!("{name}_dormancy"), + version, + wakefulness.height.read_only_boxed_clone(), + &wakefulness.resolutions, + ) + }); + let wakefulness_to_dormancy = AgeRangeId::series(CohortContext::Utxo, |column, name| { + let wakefulness = column.select(&wakefulness); + LazyPerBlock::from_resolutions::( + &format!("{name}_wakefulness_to_dormancy"), + version, + wakefulness.height.read_only_boxed_clone(), + &wakefulness.resolutions, + ) + }); - Ok(Self { + Self { wakefulness, dormancy, wakefulness_to_dormancy, - }) - } -} - -impl SupplyVecs { - fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - spot_price: &CachedBoxedVec, - ) -> Result { - Ok(Self { - awake: SpotValuePerBlock::forced_import( - db, - &format!("{name}_awake_supply"), - version, - indexes, - spot_price, - )?, - dormant: SpotValuePerBlock::forced_import( - db, - &format!("{name}_dormant_supply"), - version, - indexes, - spot_price, - )?, - }) - } -} - -impl CohortVecs { - fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - spot_price: &CachedBoxedVec, - ) -> Result { - Ok(Self { - coindays_created: PerBlockCumulativeRolling::forced_import( - db, - &format!("{name}_coindays_created"), - version, - indexes, - cached_starts, - )?, - coindays_consumed: PerBlockCumulativeRolling::forced_import( - db, - &format!("{name}_coindays_consumed"), - version, - indexes, - cached_starts, - )?, - coindays_stored: PerBlockCumulativeRolling::forced_import( - db, - &format!("{name}_coindays_stored"), - version, - indexes, - cached_starts, - )?, - activity: ActivityVecs::forced_import(db, name, version, indexes)?, - supply: SupplyVecs::forced_import(db, name, version, indexes, spot_price)?, - }) + } } } @@ -117,10 +66,93 @@ impl Vecs { spot_price: &CachedBoxedVec, ) -> Result { let version = parent_version + VERSION; + let coindays_created = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &CohortContext::Utxo.prefixed("age_range_coindays_created_cumulative"), + version, + |source| { + AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{name}_coindays_created"), + version, + source, + column, + indexes, + cached_starts, + ) + }) + }, + )?; + let coindays_consumed = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &CohortContext::Utxo.prefixed("age_range_coindays_consumed_cumulative"), + version, + |source| { + AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{name}_coindays_consumed"), + version, + source, + column, + indexes, + cached_starts, + ) + }) + }, + )?; + let coindays_stored = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &CohortContext::Utxo.prefixed("age_range_coindays_stored_cumulative"), + version, + |source| { + AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{name}_coindays_stored"), + version, + source, + column, + indexes, + cached_starts, + ) + }) + }, + )?; + let activity = ColumnarPerBlock::forced_import( + db, + &CohortContext::Utxo.prefixed("age_range_wakefulness"), + version, + |source| ActivitySeries::new(version, source, indexes), + )?; + let import_supply = |side: &str| { + ColumnarPerBlock::forced_import( + db, + &CohortContext::Utxo.prefixed(&format!("age_range_{side}_supply_sats")), + version, + |source| { + AgeRangeId::series(CohortContext::Utxo, |column, name| { + LazyColumnSpotValuePerBlock::new( + &format!("{name}_{side}_supply"), + version, + source, + column, + indexes, + spot_price, + ) + }) + }, + ) + }; + let supply = SupplyVecs { + awake: import_supply("awake")?, + dormant: import_supply("dormant")?, + }; - Ok(Self(AgeRange::try_new(|_, name| { - let name = CohortContext::Utxo.prefixed(name); - CohortVecs::forced_import(db, &name, version, indexes, cached_starts, spot_price) - })?)) + Ok(Self { + coindays_created, + coindays_consumed, + coindays_stored, + activity, + supply, + }) } } diff --git a/crates/brk_computer/src/frameworks/cointime/age_range/mod.rs b/crates/brk_computer/src/frameworks/cointime/age_range/mod.rs index 6d7b7c8a5..e77e0252d 100644 --- a/crates/brk_computer/src/frameworks/cointime/age_range/mod.rs +++ b/crates/brk_computer/src/frameworks/cointime/age_range/mod.rs @@ -2,4 +2,4 @@ mod compute; mod import; mod vecs; -pub use vecs::{ActivityVecs, CohortVecs, SupplyVecs, Vecs}; +pub use vecs::{ActivitySeries, SupplyVecs, Vecs}; diff --git a/crates/brk_computer/src/frameworks/cointime/age_range/vecs.rs b/crates/brk_computer/src/frameworks/cointime/age_range/vecs.rs index 148345f15..5304b70d0 100644 --- a/crates/brk_computer/src/frameworks/cointime/age_range/vecs.rs +++ b/crates/brk_computer/src/frameworks/cointime/age_range/vecs.rs @@ -1,36 +1,48 @@ -use brk_cohort::AgeRange; +use brk_cohort::{AgeRange, AgeRangeId}; use brk_traversable::Traversable; -use brk_types::StoredF64; -use derive_more::{Deref, DerefMut}; +use brk_types::{Sats, StoredF64}; use vecdb::{Rw, StorageMode}; -use crate::internal::{LazyPerBlock, PerBlock, PerBlockCumulativeRolling, SpotValuePerBlock}; +use crate::internal::{ + ColumnarPerBlock, ColumnarPerBlockCumulativeRolling, LazyColumnPerBlock, + LazyColumnPerBlockCumulativeRolling, LazyColumnSpotValuePerBlock, LazyPerBlock, +}; -#[derive(Traversable)] -pub struct ActivityVecs { - pub wakefulness: PerBlock, - pub dormancy: LazyPerBlock, - pub wakefulness_to_dormancy: LazyPerBlock, +#[derive(Clone, Traversable)] +pub struct ActivitySeries { + pub wakefulness: AgeRange>, + pub dormancy: AgeRange>, + pub wakefulness_to_dormancy: AgeRange>, } #[derive(Traversable)] -pub struct SupplyVecs { - pub awake: SpotValuePerBlock, - pub dormant: SpotValuePerBlock, +pub struct SupplyVecs { + pub awake: T, + pub dormant: T, } -#[derive(Deref, DerefMut, Traversable)] -pub struct CohortVecs { - pub coindays_created: PerBlockCumulativeRolling, - pub coindays_consumed: PerBlockCumulativeRolling, - pub coindays_stored: PerBlockCumulativeRolling, - #[deref] - #[deref_mut] - #[traversable(flatten)] - pub activity: ActivityVecs, - pub supply: SupplyVecs, +#[derive(Traversable)] +pub struct Vecs { + pub coindays_created: ColumnarPerBlockCumulativeRolling< + StoredF64, + AgeRangeId, + AgeRange>, + M, + >, + pub coindays_consumed: ColumnarPerBlockCumulativeRolling< + StoredF64, + AgeRangeId, + AgeRange>, + M, + >, + pub coindays_stored: ColumnarPerBlockCumulativeRolling< + StoredF64, + AgeRangeId, + AgeRange>, + M, + >, + pub activity: ColumnarPerBlock, + pub supply: SupplyVecs< + ColumnarPerBlock>, M>, + >, } - -#[derive(Deref, DerefMut, Traversable)] -#[traversable(transparent)] -pub struct Vecs(pub AgeRange>); diff --git a/crates/brk_computer/src/frameworks/cointime/aggregate/compute.rs b/crates/brk_computer/src/frameworks/cointime/aggregate/compute.rs index 537f1a10a..204af151e 100644 --- a/crates/brk_computer/src/frameworks/cointime/aggregate/compute.rs +++ b/crates/brk_computer/src/frameworks/cointime/aggregate/compute.rs @@ -1,13 +1,13 @@ -use brk_cohort::{AGE_RANGE_COUNT, AGE_RANGE_FILTERS, ByTerm, TERM_FILTERS}; +use brk_cohort::{AGE_RANGE_COUNT, AgeRange, AgeRangeId, ByTerm, TERM_FILTERS}; use brk_error::Result; use brk_indexer::Indexer; use brk_types::{Cents, Height, Sats, StoredF64, Version}; -use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, WritableVec}; +use vecdb::{AnyStoredVec, AnyVec, ColumnId, Exit, ReadableVec, WritableVec}; use super::{AllCohortVecs, StoredCohortVecs, Vecs}; use crate::{ distribution, - frameworks::{WeightedCohortState, realized_price}, + frameworks::WeightedCohortState, internal::{PerBlock, db_utils::validate_any_computed_version_or_reset}, }; @@ -25,30 +25,38 @@ impl Vecs { exit: &Exit, ) -> Result<()> { let starting_height = indexer.safe_lengths().height; - let source_cohorts: Vec<_> = distribution.utxo_cohorts.age_range.iter().collect(); - let supplies: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.supply.total.sats.height) - .collect(); - let loss_supplies: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.supply.in_loss.sats.height) - .collect(); - let realized_caps: Vec<_> = source_cohorts - .iter() - .map(|cohort| &cohort.metrics.realized.cap.cents.height) - .collect(); - let weights: Vec<_> = age_range - .iter() - .map(|cohort| &cohort.wakefulness.height) - .collect(); + let supplies = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .supply + .total + .sats + .height + }); + let loss_supplies = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .supply + .in_loss + .sats + .height + }); + let realized_caps = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .realized + .cap + .cents + .height + }); + let weights = &age_range.activity.height; self.compute_primary( starting_height, &supplies, &loss_supplies, &realized_caps, - &weights, + weights, all_supply_in_loss_share, exit, ) @@ -58,29 +66,24 @@ impl Vecs { fn compute_primary( &mut self, starting_height: Height, - supplies: &[&S], - loss_supplies: &[&S], - realized_caps: &[&C], - weights: &[&W], + supplies: &AgeRange<&S>, + loss_supplies: &AgeRange<&S>, + realized_caps: &AgeRange<&C>, + weights: &W, all_supply_in_loss_share: &mut PerBlock, exit: &Exit, ) -> Result<()> where S: ReadableVec, C: ReadableVec, - W: ReadableVec, + W: ReadableVec, { - debug_assert_eq!(supplies.len(), AGE_RANGE_COUNT); - debug_assert_eq!(loss_supplies.len(), AGE_RANGE_COUNT); - debug_assert_eq!(realized_caps.len(), AGE_RANGE_COUNT); - debug_assert_eq!(weights.len(), AGE_RANGE_COUNT); - let source_version: Version = supplies .iter() .map(|vec| vec.version()) .chain(loss_supplies.iter().map(|vec| vec.version())) .chain(realized_caps.iter().map(|vec| vec.version())) - .chain(weights.iter().map(|vec| vec.version())) + .chain(std::iter::once(weights.version())) .sum(); for vec in self.primary_vecs_mut() { @@ -91,7 +94,6 @@ impl Vecs { let start = self .primary_vecs_mut() - .into_iter() .map(|vec| vec.len()) .chain(std::iter::once(all_supply_in_loss_share.len())) .min() @@ -108,47 +110,39 @@ impl Vecs { .map(|vec| vec.len()) .chain(loss_supplies.iter().map(|vec| vec.len())) .chain(realized_caps.iter().map(|vec| vec.len())) - .chain(weights.iter().map(|vec| vec.len())) + .chain(std::iter::once(weights.len())) .min() .unwrap_or_default(); - let mut age_filters = AGE_RANGE_FILTERS.iter(); - let is_sth: [bool; AGE_RANGE_COUNT] = - std::array::from_fn(|_| TERM_FILTERS.short.includes(age_filters.next().unwrap())); - let mut chunk_start = start; while chunk_start < source_end { let chunk_end = (chunk_start + WRITE_INTERVAL).min(source_end); - let supply_batches: Vec<_> = supplies - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let loss_batches: Vec<_> = loss_supplies - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let cap_batches: Vec<_> = realized_caps - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); - let weight_batches: Vec<_> = weights - .iter() - .map(|vec| vec.collect_range_at(chunk_start, chunk_end)) - .collect(); + let supply_batches = AgeRange::from_fn(|id| { + id.select(supplies).collect_range_at(chunk_start, chunk_end) + }); + let loss_batches = AgeRange::from_fn(|id| { + id.select(loss_supplies) + .collect_range_at(chunk_start, chunk_end) + }); + let cap_batches = AgeRange::from_fn(|id| { + id.select(realized_caps) + .collect_range_at(chunk_start, chunk_end) + }); + let weight_batch = weights.collect_range_at(chunk_start, chunk_end); - for offset in 0..(chunk_end - chunk_start) { + for (offset, weights) in weight_batch.iter().enumerate() { let mut terms = ByTerm::::default(); - for age in 0..AGE_RANGE_COUNT { - let term = if is_sth[age] { + for &id in AgeRangeId::ALL { + let term = if TERM_FILTERS.short.includes(id.filter()) { &mut terms.short } else { &mut terms.long }; term.add( - supply_batches[age][offset], - loss_batches[age][offset], - cap_batches[age][offset], - weight_batches[age][offset], + id.select(&supply_batches)[offset], + id.select(&loss_batches)[offset], + id.select(&cap_batches)[offset], + *id.get(weights), ); } let all = terms.short.merged(terms.long); @@ -171,12 +165,12 @@ impl Vecs { Ok(()) } - fn primary_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs = Vec::with_capacity(ALL_PRIMARY_VEC_COUNT + 2 * STORED_PRIMARY_VEC_COUNT); - vecs.extend(self.all.primary_vecs_mut()); - vecs.extend(self.sth.primary_vecs_mut()); - vecs.extend(self.lth.primary_vecs_mut()); - vecs + fn primary_vecs_mut(&mut self) -> impl Iterator { + self.all + .primary_vecs_mut() + .into_iter() + .chain(self.sth.primary_vecs_mut()) + .chain(self.lth.primary_vecs_mut()) } } @@ -189,11 +183,7 @@ impl AllCohortVecs { .height .push(state.complement_supply); self.awake.cap.cents.height.push(state.weighted_cap); - self.awake - .price - .cents - .height - .push(realized_price(state.weighted_cap, state.weighted_supply)); + self.awake.price.cents.height.push(state.realized_price()); } fn primary_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; ALL_PRIMARY_VEC_COUNT] { @@ -219,11 +209,7 @@ impl StoredCohortVecs { .height .push(state.supply_in_loss.value()); self.awake.cap.cents.height.push(state.weighted_cap); - self.awake - .price - .cents - .height - .push(realized_price(state.weighted_cap, state.weighted_supply)); + self.awake.price.cents.height.push(state.realized_price()); } fn primary_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; STORED_PRIMARY_VEC_COUNT] { diff --git a/crates/brk_computer/src/frameworks/mod.rs b/crates/brk_computer/src/frameworks/mod.rs index b0d63a503..9eab224aa 100644 --- a/crates/brk_computer/src/frameworks/mod.rs +++ b/crates/brk_computer/src/frameworks/mod.rs @@ -58,6 +58,11 @@ pub(crate) struct WeightedCohortState { } impl WeightedCohortState { + #[inline] + pub(crate) fn split_supply(total: Sats, weight: StoredF64) -> (Sats, Sats) { + (weight * total, (StoredF64::from(1.0) - weight) * total) + } + #[inline] pub(crate) fn add( &mut self, @@ -66,9 +71,10 @@ impl WeightedCohortState { total_cap: Cents, weight: StoredF64, ) -> WeightedCohortContribution { + let (weighted_supply, complement_supply) = Self::split_supply(total_supply, weight); let contribution = WeightedCohortContribution { - weighted_supply: weight * total_supply, - complement_supply: (StoredF64::from(1.0) - weight) * total_supply, + weighted_supply, + complement_supply, weighted_cap: if total_supply.is_zero() { Cents::ZERO } else { @@ -96,18 +102,18 @@ impl WeightedCohortState { self.supply_in_loss.merge(other.supply_in_loss); self } -} -#[inline] -pub(crate) fn realized_price(cap: Cents, supply: Sats) -> Cents { - if unlikely(cap.is_nan()) { - return Cents::NAN; + #[inline] + pub(crate) fn realized_price(&self) -> Cents { + if unlikely(self.weighted_cap.is_nan()) { + return Cents::NAN; + } + + (self.weighted_cap.as_u128() * Sats::ONE_BTC_U128) + .checked_div(self.weighted_supply.as_u128()) + .map(Cents::from) + .unwrap_or(Cents::ZERO) } - - (cap.as_u128() * Sats::ONE_BTC_U128) - .checked_div(supply.as_u128()) - .map(Cents::from) - .unwrap_or(Cents::ZERO) } #[cfg(test)] @@ -122,10 +128,7 @@ mod tests { assert_eq!(contribution.weighted_cap, Cents::ZERO); assert_eq!(state.weighted_cap, Cents::ZERO); - assert_eq!( - realized_price(state.weighted_cap, state.weighted_supply), - Cents::ZERO - ); + assert_eq!(state.realized_price(), Cents::ZERO); } #[test] @@ -140,6 +143,6 @@ mod tests { ); assert!(state.weighted_cap.is_nan()); - assert!(realized_price(state.weighted_cap, state.weighted_supply).is_nan()); + assert!(state.realized_price().is_nan()); } } diff --git a/crates/brk_computer/src/indexes/timestamp.rs b/crates/brk_computer/src/indexes/timestamp.rs index 1b5f465bd..f08dfc907 100644 --- a/crates/brk_computer/src/indexes/timestamp.rs +++ b/crates/brk_computer/src/indexes/timestamp.rs @@ -28,7 +28,6 @@ pub struct Timestamps { #[deref] #[deref_mut] #[traversable(flatten)] - #[allow(clippy::type_complexity)] pub resolutions: PerResolution< LazyVec, LazyVec, diff --git a/crates/brk_computer/src/inputs/by_type/compute.rs b/crates/brk_computer/src/inputs/by_type/compute.rs index bdc30f39d..d692dd6bb 100644 --- a/crates/brk_computer/src/inputs/by_type/compute.rs +++ b/crates/brk_computer/src/inputs/by_type/compute.rs @@ -1,6 +1,8 @@ +use brk_cohort::SpendableTypeId; use brk_error::{OptionData, Result}; use brk_indexer::Indexer; -use vecdb::{AnyVec, Exit, ReadableVec, VecIndex}; +use brk_types::{StoredU16, StoredU64}; +use vecdb::{AnyVec, ColumnId, Exit, ReadableVec, VecIndex}; use super::Vecs; use crate::internal::{CoinbasePolicy, walk_blocks}; @@ -18,13 +20,15 @@ impl Vecs { self.input_count .validate_and_truncate(dep_version, starting_lengths.height)?; + self.input_count.clear(); self.tx_count .validate_and_truncate(dep_version, starting_lengths.height)?; let skip = self .input_count - .min_stateful_len() - .min(self.tx_count.min_stateful_len()); + .height + .len() + .min(self.tx_count.cumulative.len()); let first_tx_index = &indexer.vecs().transactions.first_tx_index; let end = first_tx_index.len(); @@ -59,8 +63,14 @@ impl Vecs { Ok(()) }, |agg| { - self.input_count.push_block(&agg.entries_per_type); - self.tx_count.push_block(&agg.txs_per_type); + self.input_count.push(SpendableTypeId::from_fn(|column| { + let value = agg.entries_per_type[column.output_type() as usize]; + debug_assert!(u16::try_from(value).is_ok()); + StoredU16::new(value as u16) + })); + self.tx_count.push_block(SpendableTypeId::from_fn(|column| { + StoredU64::from(agg.txs_per_type[column.output_type() as usize]) + })); height += 1; if height.is_multiple_of(WRITE_INTERVAL) { diff --git a/crates/brk_computer/src/inputs/by_type/import.rs b/crates/brk_computer/src/inputs/by_type/import.rs index aca92e4bd..0ed03dbd7 100644 --- a/crates/brk_computer/src/inputs/by_type/import.rs +++ b/crates/brk_computer/src/inputs/by_type/import.rs @@ -1,11 +1,14 @@ +use brk_cohort::SpendableTypeId; use brk_error::Result; -use brk_types::{Height, StoredU64, Version}; +use brk_types::{Height, StoredU16, StoredU64, Version}; use vecdb::Database; use super::{Vecs, WithInputTypes}; use crate::{ indexes, - internal::{CachedWindowStartVec, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlock, ColumnarPerBlockCumulativeRolling, Windows, + }, }; fn identity(_: Height, value: StoredU64) -> StoredU64 { @@ -23,34 +26,50 @@ impl Vecs { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { - let input_count_source = indexes.input_count_source(); - let input_count = WithInputTypes::forced_import_counts( + let columnar_version = version + Version::ONE; + let all_input_count = indexes.input_count_source(); + let input_count = ColumnarPerBlock::::forced_import( db, - "input_count_bis", - |t| format!("{t}_prevout_count"), - version, - (input_count_source, identity), - indexes, - cached_starts, + "prevout_count_by_type", + columnar_version, + |source| { + WithInputTypes::from_columnar_count_source( + "input_count_bis", + |t| format!("{t}_prevout_count"), + columnar_version, + (all_input_count, identity), + source, + indexes, + cached_starts, + ) + }, )?; let input_share = input_count.lazy_shares( - version, + columnar_version, |name| format!("{name}_prevout_share"), cached_starts, indexes, ); let transaction_count_source = indexes.transaction_count_source(); - let tx_count = WithInputTypes::forced_import( - db, - "non_coinbase_tx_count", - |t| format!("tx_count_with_{t}_prevout"), - version, - (transaction_count_source, without_coinbase), - indexes, - cached_starts, - )?; + let tx_count = + ColumnarPerBlockCumulativeRolling::::forced_import( + db, + "tx_count_with_prevout_by_type_cumulative", + columnar_version, + |source| { + WithInputTypes::from_columnar_source( + "non_coinbase_tx_count", + |t| format!("tx_count_with_{t}_prevout"), + columnar_version, + (transaction_count_source, without_coinbase), + source, + indexes, + cached_starts, + ) + }, + )?; let tx_share = tx_count.lazy_shares( - version, + columnar_version, |name| format!("tx_share_with_{name}_prevout"), cached_starts, indexes, diff --git a/crates/brk_computer/src/inputs/by_type/vecs.rs b/crates/brk_computer/src/inputs/by_type/vecs.rs index 8f7935b43..849ff0ec3 100644 --- a/crates/brk_computer/src/inputs/by_type/vecs.rs +++ b/crates/brk_computer/src/inputs/by_type/vecs.rs @@ -1,17 +1,28 @@ -use brk_cohort::SpendableType; +use brk_cohort::{SpendableType, SpendableTypeId}; use brk_traversable::Traversable; -use brk_types::{PartsPerMillion32, StoredU64}; +use brk_types::{PartsPerMillion32, StoredU16, StoredU64}; use vecdb::{Rw, StorageMode}; use super::WithInputTypes; use crate::internal::{ - CachedCountPerBlockCumulativeRolling, LazyPercentCumulativeRolling, PerBlockCumulativeRolling, + ColumnarPerBlock, ColumnarPerBlockCumulativeRolling, LazyColumnCountPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, LazyPercentCumulativeRolling, }; #[derive(Traversable)] pub struct Vecs { - pub input_count: WithInputTypes>, + pub input_count: ColumnarPerBlock< + StoredU16, + SpendableTypeId, + WithInputTypes, + M, + >, pub input_share: SpendableType>, - pub tx_count: WithInputTypes>, + pub tx_count: ColumnarPerBlockCumulativeRolling< + StoredU64, + SpendableTypeId, + WithInputTypes>, + M, + >, pub tx_share: SpendableType>, } diff --git a/crates/brk_computer/src/inputs/by_type/with_input_types.rs b/crates/brk_computer/src/inputs/by_type/with_input_types.rs index 43dfe27c4..15f17427b 100644 --- a/crates/brk_computer/src/inputs/by_type/with_input_types.rs +++ b/crates/brk_computer/src/inputs/by_type/with_input_types.rs @@ -1,27 +1,26 @@ //! Generic `all` + per-input-type container (11 spendable types — no //! op_return since op_return outputs are non-spendable). -use brk_cohort::{ByAddrType, Filter, SpendableType}; -use brk_error::Result; +use brk_cohort::{ByAddrType, Filter, SpendableType, SpendableTypeId}; use brk_traversable::Traversable; -use brk_types::{Height, PartsPerMillion32, StoredU64, Version}; +use brk_types::{Height, PartsPerMillion32, StoredU16, StoredU64, Version}; use vecdb::{ - AnyStoredVec, AnyVec, CachedBoxedVec, CachedReadableVec, Database, ReadableCloneableVec, - ReadableVec, TypedVec, WritableVec, + CachedBoxedVec, CachedReadableVec, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec, + ReadableVec, TypedVec, }; use crate::{ indexes, internal::{ - CachedBlockCountReader, CachedCountPerBlockCumulativeRolling, CachedWindowStartVec, - LazyPerBlockCumulativeRolling, LazyPercentCumulativeRolling, PerBlockCumulativeRolling, - RatioU64, Windows, + CachedBlockCountReader, CachedWindowStartVec, LazyColumnCountPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, LazyPerBlockCumulativeRolling, + LazyPercentCumulativeRolling, RatioU64, Windows, }, }; /// `all` aggregate plus per-input-type breakdown across the 11 spendable /// output types. The "type" of an input is the previous output it spends. -#[derive(Traversable)] +#[derive(Clone, Traversable)] pub struct WithInputTypes { pub all: LazyPerBlockCumulativeRolling, #[traversable(skip)] @@ -64,20 +63,6 @@ impl WithInputTypes { } } - fn min_stateful_len_with(&self, len: impl Fn(&V) -> usize) -> usize { - self.by_type.iter().map(len).min().unwrap() - } - - fn try_for_each_type_mut(&mut self, mut apply: impl FnMut(&mut V) -> Result<()>) -> Result<()> { - self.by_type.iter_mut().try_for_each(&mut apply) - } - - fn push_block_with(&mut self, per_type: &[u64; 12], mut push: impl FnMut(&mut V, StoredU64)) { - for (output_type, vec) in self.by_type.iter_typed_mut() { - push(vec, StoredU64::from(per_type[output_type as usize])); - } - } - pub(crate) fn lazy_share( &self, name: &str, @@ -102,16 +87,16 @@ impl WithInputTypes { } } -impl WithInputTypes { - pub(crate) fn forced_import_counts( - db: &Database, +impl WithInputTypes { + pub(crate) fn from_columnar_count_source( all_name: &str, per_type_name: impl Fn(&str) -> String, version: Version, all: (S, fn(Height, StoredU64) -> StoredU64), + source: &ReadOnlyColumnarVec, SpendableTypeId>, indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result + ) -> Self where S: TypedVec + ReadableVec @@ -119,36 +104,21 @@ impl WithInputTypes { + Clone + 'static, { - let by_type = SpendableType::try_new(|_, name| { - CachedCountPerBlockCumulativeRolling::forced_import( - db, - &per_type_name(name), - version, - indexes, - cached_starts, - ) - })?; - Ok(Self::new( - all_name, - version, - all, - by_type, - indexes, - cached_starts, - )) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.min_stateful_len_with(|vec| vec.min_stateful_len()) - } - - pub(crate) fn cached_addr_type_counts(&self) -> ByAddrType { - ByAddrType::new(|filter| { + let by_type = SpendableType::new(|filter, name| { let Filter::Type(output_type) = filter else { unreachable!() }; - self.by_type.get(output_type).cached_cumulative() - }) + LazyColumnCountPerBlockCumulativeRolling::new( + &per_type_name(name), + version, + source, + SpendableTypeId::from_output_type(output_type) + .expect("spendable output type column"), + indexes, + cached_starts, + ) + }); + Self::new(all_name, version, all, by_type, indexes, cached_starts) } pub(crate) fn lazy_shares( @@ -173,38 +143,30 @@ impl WithInputTypes { }) } - pub(crate) fn write(&mut self) -> Result<()> { - self.try_for_each_type_mut(|vec| vec.write()) + pub(crate) fn cached_addr_type_counts(&self) -> ByAddrType { + ByAddrType::new(|filter| { + let Filter::Type(output_type) = filter else { + unreachable!() + }; + self.by_type.get(output_type).cached_cumulative() + }) } - pub(crate) fn validate_and_truncate( - &mut self, - dependency_version: Version, - at_height: Height, - ) -> Result<()> { - self.try_for_each_type_mut(|vec| vec.validate_and_truncate(dependency_version, at_height)) - } - - pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.try_for_each_type_mut(|vec| vec.truncate_if_needed_at(len)) - } - - #[inline] - pub(crate) fn push_block(&mut self, per_type: &[u64; 12]) { - self.push_block_with(per_type, |vec, value| vec.push_block(value)); + pub(crate) fn clear(&self) { + self.by_type.iter().for_each(|count| count.clear()); } } -impl WithInputTypes> { - pub(crate) fn forced_import( - db: &Database, +impl WithInputTypes> { + pub(crate) fn from_columnar_source( all_name: &str, per_type_name: impl Fn(&str) -> String, version: Version, all: (S, fn(Height, StoredU64) -> StoredU64), + source: &ReadOnlyColumnarVec, SpendableTypeId>, indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result + ) -> Self where S: TypedVec + ReadableVec @@ -212,27 +174,21 @@ impl WithInputTypes> { + Clone + 'static, { - let by_type = SpendableType::try_new(|_, name| { - PerBlockCumulativeRolling::forced_import( - db, + let by_type = SpendableType::new(|filter, name| { + let Filter::Type(output_type) = filter else { + unreachable!() + }; + LazyColumnPerBlockCumulativeRolling::new( &per_type_name(name), version, + source, + SpendableTypeId::from_output_type(output_type) + .expect("spendable output type column"), indexes, cached_starts, ) - })?; - Ok(Self::new( - all_name, - version, - all, - by_type, - indexes, - cached_starts, - )) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.min_stateful_len_with(|vec| vec.cumulative.height.len()) + }); + Self::new(all_name, version, all, by_type, indexes, cached_starts) } pub(crate) fn lazy_shares( @@ -255,33 +211,4 @@ impl WithInputTypes> { ) }) } - - pub(crate) fn write(&mut self) -> Result<()> { - self.try_for_each_type_mut(|vec| { - vec.cumulative.height.write()?; - Ok(()) - }) - } - - pub(crate) fn validate_and_truncate( - &mut self, - dependency_version: Version, - at_height: Height, - ) -> Result<()> { - self.try_for_each_type_mut(|vec| { - Ok(vec - .cumulative - .height - .validate_and_truncate(dependency_version, at_height)?) - }) - } - - pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.try_for_each_type_mut(|vec| Ok(vec.cumulative.height.truncate_if_needed_at(len)?)) - } - - #[inline] - pub(crate) fn push_block(&mut self, per_type: &[u64; 12]) { - self.push_block_with(per_type, |vec, value| vec.push_block(value)); - } } diff --git a/crates/brk_computer/src/internal/block_walker.rs b/crates/brk_computer/src/internal/block_walker.rs index ec35020ef..c3b3f96bf 100644 --- a/crates/brk_computer/src/internal/block_walker.rs +++ b/crates/brk_computer/src/internal/block_walker.rs @@ -4,13 +4,13 @@ //! to the caller. use brk_error::Result; -use brk_types::TxIndex; +use brk_types::{OutputType, TxIndex}; use vecdb::VecIndex; /// Aggregated per-block counters produced by [`walk_blocks`]. pub(crate) struct BlockAggregate { - pub entries_per_type: [u64; 12], - pub txs_per_type: [u64; 12], + pub entries_per_type: [u64; OutputType::COUNT], + pub txs_per_type: [u64; OutputType::COUNT], } /// Whether to include the coinbase tx (first tx in each block) in the walk. @@ -21,7 +21,7 @@ pub(crate) enum CoinbasePolicy { } /// Walk every block in `fi_batch`, calling `scan_tx` once per tx (which -/// fills a `[u32; 12]` with the per-output-type count for that tx), +/// fills a per-output-type count array for that tx), /// aggregating into a [`BlockAggregate`] and handing it to `store`. /// #[inline] @@ -29,7 +29,7 @@ pub(crate) fn walk_blocks( fi_batch: &[TxIndex], txid_len: usize, coinbase: CoinbasePolicy, - mut scan_tx: impl FnMut(usize, &mut [u32; 12]) -> Result<()>, + mut scan_tx: impl FnMut(usize, &mut [u32; OutputType::COUNT]) -> Result<()>, mut store: impl FnMut(BlockAggregate) -> Result<()>, ) -> Result<()> { for (j, first_tx) in fi_batch.iter().enumerate() { @@ -44,11 +44,11 @@ pub(crate) fn walk_blocks( CoinbasePolicy::Skip => fi + 1, }; - let mut entries_per_type = [0u64; 12]; - let mut txs_per_type = [0u64; 12]; + let mut entries_per_type = [0u64; OutputType::COUNT]; + let mut txs_per_type = [0u64; OutputType::COUNT]; for tx_pos in start_tx..next_fi { - let mut per_tx = [0u32; 12]; + let mut per_tx = [0u32; OutputType::COUNT]; scan_tx(tx_pos, &mut per_tx)?; for (i, &n) in per_tx.iter().enumerate() { if n > 0 { diff --git a/crates/brk_computer/src/internal/containers/windows.rs b/crates/brk_computer/src/internal/containers/windows.rs index eb699ee46..f6e6bf48d 100644 --- a/crates/brk_computer/src/internal/containers/windows.rs +++ b/crates/brk_computer/src/internal/containers/windows.rs @@ -1,4 +1,96 @@ use brk_traversable::Traversable; +use brk_types::Version; +use vecdb::{ColumnId, VecValue}; + +const WINDOW_COUNT: usize = 4; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum WindowId { + Day1, + Week1, + Month1, + Year1, +} + +const WINDOW_IDS: [WindowId; WINDOW_COUNT] = [ + WindowId::Day1, + WindowId::Week1, + WindowId::Month1, + WindowId::Year1, +]; + +impl WindowId { + pub const fn suffix(self) -> &'static str { + match self { + Self::Day1 => "24h", + Self::Week1 => "1w", + Self::Month1 => "1m", + Self::Year1 => "1y", + } + } + + pub fn series(mut create: impl FnMut(Self) -> T) -> Windows { + Windows { + _24h: create(Self::Day1), + _1w: create(Self::Week1), + _1m: create(Self::Month1), + _1y: create(Self::Year1), + } + } + + pub fn select(self, windows: &Windows) -> &T { + match self { + Self::Day1 => &windows._24h, + Self::Week1 => &windows._1w, + Self::Month1 => &windows._1m, + Self::Year1 => &windows._1y, + } + } +} + +impl ColumnId for WindowId { + type Row + = [T; WINDOW_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &WINDOW_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(WINDOW_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} #[derive(Clone, Copy, Traversable)] pub struct Windows { @@ -79,3 +171,21 @@ impl Windows<(A, B)> { ) } } + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{WINDOW_IDS, WindowId}; + + #[test] + fn window_columns_match_named_fields() { + assert_eq!(WindowId::ALL, WINDOW_IDS); + + let windows = WindowId::series(|window| window); + assert_eq!(windows._24h, WindowId::Day1); + assert_eq!(windows._1w, WindowId::Week1); + assert_eq!(windows._1m, WindowId::Month1); + assert_eq!(windows._1y, WindowId::Year1); + } +} diff --git a/crates/brk_computer/src/internal/containers/windows_from_1w.rs b/crates/brk_computer/src/internal/containers/windows_from_1w.rs index f2a1cba70..4081522dc 100644 --- a/crates/brk_computer/src/internal/containers/windows_from_1w.rs +++ b/crates/brk_computer/src/internal/containers/windows_from_1w.rs @@ -1,4 +1,101 @@ use brk_traversable::Traversable; +use brk_types::Version; +use vecdb::{ColumnId, VecValue}; + +use super::Windows; + +const WINDOW_FROM_1W_COUNT: usize = 3; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum WindowFrom1wId { + Week1, + Month1, + Year1, +} + +const WINDOW_FROM_1W_IDS: [WindowFrom1wId; WINDOW_FROM_1W_COUNT] = [ + WindowFrom1wId::Week1, + WindowFrom1wId::Month1, + WindowFrom1wId::Year1, +]; + +impl WindowFrom1wId { + pub const fn suffix(self) -> &'static str { + match self { + Self::Week1 => "1w", + Self::Month1 => "1m", + Self::Year1 => "1y", + } + } + + pub fn series(mut create: impl FnMut(Self) -> T) -> WindowsFrom1w { + WindowsFrom1w { + _1w: create(Self::Week1), + _1m: create(Self::Month1), + _1y: create(Self::Year1), + } + } + + pub fn select(self, windows: &WindowsFrom1w) -> &T { + match self { + Self::Week1 => &windows._1w, + Self::Month1 => &windows._1m, + Self::Year1 => &windows._1y, + } + } + + pub fn select_full(self, windows: &Windows) -> &T { + match self { + Self::Week1 => &windows._1w, + Self::Month1 => &windows._1m, + Self::Year1 => &windows._1y, + } + } +} + +impl ColumnId for WindowFrom1wId { + type Row + = [T; WINDOW_FROM_1W_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &WINDOW_FROM_1W_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(WINDOW_FROM_1W_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} #[derive(Clone, Traversable)] pub struct WindowsFrom1w { @@ -28,3 +125,20 @@ impl WindowsFrom1w { [&mut self._1w, &mut self._1m, &mut self._1y] } } + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{WINDOW_FROM_1W_IDS, WindowFrom1wId}; + + #[test] + fn window_from_1w_columns_match_named_fields() { + assert_eq!(WindowFrom1wId::ALL, WINDOW_FROM_1W_IDS); + + let windows = WindowFrom1wId::series(|window| window); + assert_eq!(windows._1w, WindowFrom1wId::Week1); + assert_eq!(windows._1m, WindowFrom1wId::Month1); + assert_eq!(windows._1y, WindowFrom1wId::Year1); + } +} diff --git a/crates/brk_computer/src/internal/daily_metric.rs b/crates/brk_computer/src/internal/daily_metric.rs index adfda6df4..ec31f528e 100644 --- a/crates/brk_computer/src/internal/daily_metric.rs +++ b/crates/brk_computer/src/internal/daily_metric.rs @@ -6,12 +6,14 @@ use brk_types::{ Date, Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Minute10, Minute30, Month1, Month3, Month6, Timestamp, Version, Week1, Year1, Year10, }; +use derive_more::{Deref, DerefMut}; use schemars::JsonSchema; use serde::Serialize; use vecdb::{ - AnyExportableVec, AnyVec, Database, EagerVec, Formattable, ImportableVec, LazyVec, PcoVec, - PcoVecValue, ReadableBoxedVec, ReadableCloneableVec, ReadableVec, Rw, StorageMode, TypedVec, - UnaryTransform, VecIndex, VecValue, short_type_name, + AnyExportableVec, AnyStoredVec, AnyVec, ColumnId, ColumnarVec, Database, EagerVec, Formattable, + ImportableVec, LazyColumnVec, LazyVec, PcoVec, PcoVecValue, ReadOnlyClone, ReadOnlyColumnarVec, + ReadableBoxedVec, ReadableCloneableVec, ReadableColumnarVec, ReadableVec, Rw, StorageMode, + TypedVec, UnaryTransform, VecIndex, VecValue, WritableVec, short_type_name, }; use crate::indexes; @@ -172,6 +174,83 @@ where } } +#[derive(Clone, Traversable)] +#[traversable(merge)] +pub struct LazyColumnDailyMetric +where + T: DailyValue + PcoVecValue, + C: ColumnId, +{ + pub day1: LazyColumnVec, C>, C>, + #[traversable(flatten)] + pub views: Box>, +} + +impl LazyColumnDailyMetric +where + T: DailyValue + PcoVecValue, + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + mappings: &DailyMappings, + ) -> Self { + let day1 = source.column(name, version, column); + let views = Box::new(DailyViews::new( + name, + day1.read_only_boxed_clone(), + version, + mappings, + )); + + Self { day1, views } + } +} + +#[derive(Deref, DerefMut, Traversable)] +pub struct ColumnarDailyMetric +where + T: DailyValue + PcoVecValue, + C: ColumnId, +{ + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub series: S, + #[traversable(hidden)] + pub day1: M::Stored, C>>>, +} + +impl ColumnarDailyMetric +where + T: DailyValue + PcoVecValue, + C: ColumnId, +{ + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + build_series: impl FnOnce(&ReadOnlyColumnarVec, C>) -> S, + ) -> Result { + let day1 = EagerVec::forced_import(db, name, version)?; + let series = build_series(&day1.read_only_clone()); + + Ok(Self { series, day1 }) + } + + #[inline(always)] + pub(crate) fn push(&mut self, row: C::Row) { + self.day1.push(row); + } + + pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec { + &mut self.day1 + } +} + type LazyDay = LazyVec; #[derive(Clone, Traversable)] diff --git a/crates/brk_computer/src/internal/indexes/lazy.rs b/crates/brk_computer/src/internal/indexes/lazy.rs index 7ad9dfa08..f52e45511 100644 --- a/crates/brk_computer/src/internal/indexes/lazy.rs +++ b/crates/brk_computer/src/internal/indexes/lazy.rs @@ -12,7 +12,6 @@ use crate::internal::{ComputedVecValue, PerResolution}; #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(transparent)] pub struct LazyIndexes( - #[allow(clippy::type_complexity)] pub PerResolution< LazyVec, LazyVec, diff --git a/crates/brk_computer/src/internal/per_block/computed/cached_count_cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/cached_count_cumulative_rolling.rs deleted file mode 100644 index fec220150..000000000 --- a/crates/brk_computer/src/internal/per_block/computed/cached_count_cumulative_rolling.rs +++ /dev/null @@ -1,217 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use brk_types::{Height, StoredU16, StoredU64, Version}; -use vecdb::{ - AnyStoredVec, AnyVec, CachedVec, Database, EagerVec, ImportableVec, LazyVec, PcoVec, Rw, - StorageMode, WritableVec, -}; - -use crate::{ - indexes, - internal::{ - CachedBlockCountReader, CachedWindowStartVec, Identity, LazyPerBlock, - LazyRollingAvgsFromHeight, LazyRollingSumsFromHeight, StoredU16ToStoredU64, Windows, - }, -}; - -#[derive(Traversable)] -pub struct CachedCountPerBlockCumulativeRolling { - pub block: LazyVec, - pub cumulative: LazyPerBlock, - pub sum: LazyRollingSumsFromHeight, - pub average: LazyRollingAvgsFromHeight, - #[traversable(hidden)] - source: CachedVec>>>, - #[traversable(skip)] - cached_cumulative: CachedBlockCountReader, -} - -impl CachedCountPerBlockCumulativeRolling { - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result { - let source = CachedVec::wrap(EagerVec::forced_import(db, name, version)?); - let cached_cumulative = CachedBlockCountReader::new(source.read_only_cached_boxed_clone()); - let block = LazyVec::transformed::( - name, - version, - source.read_only_boxed_clone(), - ); - let cumulative = LazyPerBlock::from_uncached_height_source::, _>( - &format!("{name}_cumulative"), - version, - cached_cumulative.clone(), - indexes, - ); - let sum = LazyRollingSumsFromHeight::new_uncached( - &format!("{name}_sum"), - version, - &cached_cumulative, - cached_starts, - indexes, - ); - let average = LazyRollingAvgsFromHeight::new( - &format!("{name}_average"), - version, - &cached_cumulative, - cached_starts, - indexes, - ); - - Ok(Self { - block, - cumulative, - sum, - average, - source, - cached_cumulative, - }) - } - - #[inline(always)] - pub(crate) fn cached_cumulative(&self) -> CachedBlockCountReader { - self.cached_cumulative.clone() - } - - #[inline(always)] - pub(crate) fn min_stateful_len(&self) -> usize { - self.source.len() - } - - #[inline(always)] - pub(crate) fn push_block(&mut self, value: StoredU64) { - let value = u64::from(value); - debug_assert!(u16::try_from(value).is_ok()); - self.source.inner.push(StoredU16::new(value as u16)); - } - - pub(crate) fn validate_and_truncate( - &mut self, - dependency_version: Version, - at_height: Height, - ) -> Result<()> { - self.source - .inner - .validate_and_truncate(dependency_version, at_height)?; - self.source.clear(); - Ok(()) - } - - pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.source.inner.truncate_if_needed_at(len)?; - self.source.clear(); - Ok(()) - } - - pub(crate) fn write(&mut self) -> Result<()> { - self.source.inner.write()?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use std::time::{SystemTime, UNIX_EPOCH}; - - use brk_indexer::Indexer; - use brk_reader::Reader; - use brk_rpc::{Auth, Client}; - use brk_traversable::Traversable; - use vecdb::{AnyVec, ReadableVec}; - - use super::*; - use crate::blocks::LookbackVecs; - - #[test] - fn owns_retains_and_refreshes_its_compact_source() { - let suffix = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos(); - let path = std::env::temp_dir().join(format!( - "brk-cached-count-owner-{}-{suffix}", - std::process::id() - )); - - let client = Client::new("http://127.0.0.1:1", Auth::None).unwrap(); - let reader = Reader::new_without_rlimit(path.join("blocks"), &client); - let indexer = Indexer::import(&path, &reader).unwrap(); - let indexes = indexes::Vecs::forced_import(&path, Version::ONE, &indexer).unwrap(); - let lookback = LookbackVecs::new( - Version::ONE, - indexes.timestamp.monotonic.read_only_cached_boxed_clone(), - ); - let cached_starts = lookback.cached_window_starts(); - let db = Database::open(&path.join("counts")).unwrap(); - let mut count = CachedCountPerBlockCumulativeRolling::forced_import( - &db, - "count", - Version::ONE, - &indexes, - &cached_starts, - ) - .unwrap(); - - count - .validate_and_truncate(Version::ONE, Height::ZERO) - .unwrap(); - for value in [1_u64, 2, 3] { - count.push_block(StoredU64::from(value)); - } - count.write().unwrap(); - - assert_eq!( - count.block.collect_range_at(0, 3), - [1_u64, 2, 3].map(StoredU64::from) - ); - assert_eq!( - count.cumulative.height.collect_range_at(0, 3), - [1_u64, 3, 6].map(StoredU64::from) - ); - - let source_regions = count.source.region_names(); - assert!(!source_regions.is_empty()); - let exportable_regions = count - .iter_any_exportable() - .flat_map(AnyVec::region_names) - .collect::>(); - assert!( - source_regions - .iter() - .all(|region| exportable_regions.contains(region)) - ); - assert!( - count - .iter_any_visible() - .flat_map(AnyVec::region_names) - .next() - .is_none() - ); - - count.truncate_if_needed_at(0).unwrap(); - for value in [4_u64, 5, 6] { - count.push_block(StoredU64::from(value)); - } - count.write().unwrap(); - - assert_eq!( - count.block.collect_range_at(0, 3), - [4_u64, 5, 6].map(StoredU64::from) - ); - assert_eq!( - count.cumulative.height.collect_range_at(0, 3), - [4_u64, 9, 15].map(StoredU64::from) - ); - - drop(count); - drop(db); - drop(lookback); - drop(indexes); - drop(indexer); - std::fs::remove_dir_all(path).unwrap(); - } -} diff --git a/crates/brk_computer/src/internal/per_block/computed/columnar/base.rs b/crates/brk_computer/src/internal/per_block/computed/columnar/base.rs new file mode 100644 index 000000000..dda8aebb0 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/columnar/base.rs @@ -0,0 +1,363 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{Height, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + AnyStoredVec, AnyVec, ColumnId, ColumnarVec, Database, EagerVec, Exit, ImportableVec, PcoVec, + PcoVecValue, ReadOnlyClone, ReadOnlyColumnarVec, ReadableVec, Rw, StorageMode, VecValue, + WritableVec, +}; + +#[derive(Deref, DerefMut, Traversable)] +pub struct ColumnarPerBlock +where + T: PcoVecValue, + C: ColumnId, +{ + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub series: S, + pub height: M::Stored, C>>>, +} + +impl ColumnarPerBlock +where + T: PcoVecValue, + C: ColumnId, +{ + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + build_series: impl FnOnce(&ReadOnlyColumnarVec, C>) -> S, + ) -> Result { + let height = EagerVec::forced_import(db, name, version)?; + let series = build_series(&height.read_only_clone()); + + Ok(Self { series, height }) + } + + #[inline(always)] + pub(crate) fn push(&mut self, row: C::Row) { + self.height.push(row); + } + + pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { + self.height + .validate_and_truncate(version, height) + .map_err(Into::into) + } + + pub(crate) fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> { + self.height + .validate_computed_version_or_reset(version) + .map_err(Into::into) + } + + pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { + self.height.truncate_if_needed_at(len).map_err(Into::into) + } + + pub(crate) fn write(&mut self) -> Result<()> { + self.height.write().map(|_| ()).map_err(Into::into) + } + + pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec { + &mut self.height + } + + /// Computes one stored matrix from two scalar sources per column. + pub(crate) fn compute_columns2<'a, A, B, V1, V2>( + &mut self, + max_from: Height, + source1: impl Fn(C) -> &'a V1, + source2: impl Fn(C) -> &'a V2, + mut transform: impl FnMut(C, A, B) -> T, + exit: &Exit, + ) -> Result<()> + where + A: VecValue, + B: VecValue, + V1: ReadableVec + 'a, + V2: ReadableVec + 'a, + { + let dependency_version = C::ALL + .iter() + .flat_map(|&column| [source1(column).version(), source2(column).version()]) + .sum(); + let source_end = C::ALL + .iter() + .flat_map(|&column| [source1(column).len(), source2(column).len()]) + .min() + .unwrap_or_default(); + + self.height + .validate_computed_version_or_reset(dependency_version)?; + self.height.truncate_if_needed(max_from)?; + self.height.repeat_until_complete(exit, |target| { + let start = target.len(); + let end = target.batch_end(source_end); + if start >= end { + return Ok(()); + } + + let source1_batches: Vec<_> = C::ALL + .iter() + .map(|&column| source1(column).collect_range_at(start, end)) + .collect(); + let source2_batches: Vec<_> = C::ALL + .iter() + .map(|&column| source2(column).collect_range_at(start, end)) + .collect(); + for offset in 0..(end - start) { + target.push(C::from_fn(|column| { + transform( + column, + source1_batches[column.index()][offset].clone(), + source2_batches[column.index()][offset].clone(), + ) + })); + } + + Ok(()) + })?; + + Ok(()) + } + + /// Computes one stored matrix from a matrix source and one scalar source per column. + pub(crate) fn compute_matrix_columns2<'a, A, B, V1, V2>( + &mut self, + max_from: Height, + source1: &V1, + source2: impl Fn(C) -> &'a V2, + mut transform: impl FnMut(C, A, B) -> T, + exit: &Exit, + ) -> Result<()> + where + A: VecValue, + B: VecValue, + V1: ReadableVec>, + V2: ReadableVec + 'a, + { + let dependency_version = C::ALL + .iter() + .map(|&column| source2(column).version()) + .sum::() + + source1.version(); + let source_end = C::ALL + .iter() + .map(|&column| source2(column).len()) + .chain(std::iter::once(source1.len())) + .min() + .unwrap_or_default(); + + self.height + .validate_computed_version_or_reset(dependency_version)?; + self.height.truncate_if_needed(max_from)?; + self.height.repeat_until_complete(exit, |target| { + let start = target.len(); + let end = target.batch_end(source_end); + if start >= end { + return Ok(()); + } + + let source1_rows = source1.collect_range_at(start, end); + let source2_batches: Vec<_> = C::ALL + .iter() + .map(|&column| source2(column).collect_range_at(start, end)) + .collect(); + for (offset, source1_row) in source1_rows.into_iter().enumerate() { + target.push(C::from_fn(|column| { + transform( + column, + column.get(&source1_row).clone(), + source2_batches[column.index()][offset].clone(), + ) + })); + } + + Ok(()) + })?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use brk_types::{Height, StoredU64, Version}; + use vecdb::{ + AnyStoredVec, ColumnId, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadOnlyClone, + ReadableVec, VecValue, WritableVec, + }; + + use super::ColumnarPerBlock; + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + enum Column { + A, + B, + } + + impl ColumnId for Column { + type Row + = [T; 2] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &[Self::A, Self::B]; + + fn index(self) -> usize { + self as usize + } + + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + fn from_fn(mut f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + [f(Self::A), f(Self::B)] + } + + fn map(row: Self::Row, mut f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + let [a, b] = row; + [f(a), f(b)] + } + } + + #[test] + fn computes_from_scalar_columns_and_matrix_rows() { + let suffix = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "brk-columnar-compute-{}-{suffix}", + std::process::id() + )); + let db = Database::open(&path).unwrap(); + + let mut left_a: EagerVec> = + EagerVec::forced_import(&db, "left_a", Version::ONE).unwrap(); + let mut left_b: EagerVec> = + EagerVec::forced_import(&db, "left_b", Version::ONE).unwrap(); + let mut right_a: EagerVec> = + EagerVec::forced_import(&db, "right_a", Version::ONE).unwrap(); + let mut right_b: EagerVec> = + EagerVec::forced_import(&db, "right_b", Version::ONE).unwrap(); + + for value in [1_u64, 2, 3] { + left_a.push(value.into()); + } + for value in [4_u64, 5] { + left_b.push(value.into()); + } + for value in [10_u64, 20, 30] { + right_a.push(value.into()); + } + for value in [40_u64, 50, 60] { + right_b.push(value.into()); + } + left_a.write().unwrap(); + left_b.write().unwrap(); + right_a.write().unwrap(); + right_b.write().unwrap(); + + let mut sums = ColumnarPerBlock::::forced_import( + &db, + "sums", + Version::ONE, + |_| (), + ) + .unwrap(); + sums.compute_columns2( + Height::ZERO, + |column| match column { + Column::A => &left_a, + Column::B => &left_b, + }, + |column| match column { + Column::A => &right_a, + Column::B => &right_b, + }, + |_, left, right| StoredU64::from(u64::from(left) + u64::from(right)), + &Exit::new(), + ) + .unwrap(); + assert_eq!( + sums.height.collect_range_at(0, 3), + [ + [StoredU64::from(11_u64), StoredU64::from(44_u64)], + [StoredU64::from(22_u64), StoredU64::from(55_u64)], + ] + ); + + let mut factor_a: EagerVec> = + EagerVec::forced_import(&db, "factor_a", Version::ONE).unwrap(); + let mut factor_b: EagerVec> = + EagerVec::forced_import(&db, "factor_b", Version::ONE).unwrap(); + for value in [2_u64, 3] { + factor_a.push(value.into()); + } + for value in [5_u64, 7] { + factor_b.push(value.into()); + } + factor_a.write().unwrap(); + factor_b.write().unwrap(); + + let sums = sums.height.read_only_clone(); + let mut products = ColumnarPerBlock::::forced_import( + &db, + "products", + Version::ONE, + |_| (), + ) + .unwrap(); + products + .compute_matrix_columns2( + Height::ZERO, + &sums, + |column| match column { + Column::A => &factor_a, + Column::B => &factor_b, + }, + |_, value, factor| StoredU64::from(u64::from(value) * u64::from(factor)), + &Exit::new(), + ) + .unwrap(); + assert_eq!( + products.height.collect_range_at(0, 3), + [ + [StoredU64::from(22_u64), StoredU64::from(220_u64)], + [StoredU64::from(66_u64), StoredU64::from(385_u64)], + ] + ); + + drop(products); + drop(sums); + drop(factor_b); + drop(factor_a); + drop(right_b); + drop(right_a); + drop(left_b); + drop(left_a); + drop(db); + std::fs::remove_dir_all(path).unwrap(); + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/columnar/cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/columnar/cumulative_rolling.rs new file mode 100644 index 000000000..b9e505b04 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/columnar/cumulative_rolling.rs @@ -0,0 +1,277 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{Height, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + AnyStoredVec, AnyVec, ColumnId, ColumnarVec, Database, EagerVec, Exit, ImportableVec, PcoVec, + ReadOnlyClone, ReadOnlyColumnarVec, ReadableVec, Rw, StorageMode, VecValue, WritableVec, +}; + +use crate::internal::NumericValue; + +#[derive(Deref, DerefMut, Traversable)] +pub struct ColumnarPerBlockCumulativeRolling +where + T: NumericValue, + C: ColumnId, +{ + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub series: S, + pub cumulative: M::Stored, C>>>, + #[traversable(skip)] + last_cumulative: Option<(usize, C::Row)>, +} + +impl ColumnarPerBlockCumulativeRolling +where + T: NumericValue, + C: ColumnId, +{ + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + build_series: impl FnOnce(&ReadOnlyColumnarVec, C>) -> S, + ) -> Result { + let cumulative = EagerVec::forced_import(db, name, version)?; + let last_cumulative = cumulative.collect_last().map(|row| (cumulative.len(), row)); + let series = build_series(&cumulative.read_only_clone()); + + Ok(Self { + series, + cumulative, + last_cumulative, + }) + } + + #[inline(always)] + pub(crate) fn push_block(&mut self, row: C::Row) { + let len = self.cumulative.len(); + let mut cumulative = match &self.last_cumulative { + Some((cached_len, row)) if *cached_len == len => row.clone(), + _ => self + .cumulative + .collect_last() + .unwrap_or_else(|| C::from_fn(|_| T::default())), + }; + for &column in C::ALL { + *column.get_mut(&mut cumulative) += *column.get(&row); + } + self.cumulative.push(cumulative.clone()); + self.last_cumulative = Some((len + 1, cumulative)); + } + + pub(crate) fn reset(&mut self) -> Result<()> { + self.last_cumulative = None; + self.cumulative.reset().map_err(Into::into) + } + + pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec { + self.last_cumulative = None; + &mut self.cumulative + } + + pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { + self.cumulative + .validate_and_truncate(version, height) + .map_err(Into::into) + } + + pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { + self.cumulative + .truncate_if_needed_at(len) + .map_err(Into::into) + } + + pub(crate) fn write(&mut self) -> Result<()> { + self.cumulative.write().map(|_| ()).map_err(Into::into) + } + + /// Computes one cumulative matrix from one scalar per-block source per column. + pub(crate) fn compute_columns<'a, V, U>( + &mut self, + max_from: Height, + source: impl Fn(C) -> &'a V, + exit: &Exit, + ) -> Result<()> + where + V: ReadableVec + 'a, + U: VecValue + Into, + { + let dependency_version = C::ALL.iter().map(|&column| source(column).version()).sum(); + let source_end = C::ALL + .iter() + .map(|&column| source(column).len()) + .min() + .unwrap_or_default(); + + self.cumulative + .validate_computed_version_or_reset(dependency_version)?; + self.cumulative.truncate_if_needed(max_from)?; + self.last_cumulative = None; + let mut last_cumulative = None; + self.cumulative.repeat_until_complete(exit, |target| { + let start = target.len(); + let end = target.batch_end(source_end); + if start >= end { + return Ok(()); + } + + let mut batches: Vec<_> = C::ALL + .iter() + .map(|&column| source(column).collect_range_at(start, end).into_iter()) + .collect(); + let mut cumulative = last_cumulative.take().unwrap_or_else(|| { + target + .collect_last() + .unwrap_or_else(|| C::from_fn(|_| T::default())) + }); + for _ in start..end { + for (&column, batch) in C::ALL.iter().zip(&mut batches) { + *column.get_mut(&mut cumulative) += + batch.next().expect("complete source batch").into(); + } + target.push(cumulative.clone()); + } + last_cumulative = Some(cumulative); + + Ok(()) + })?; + + let len = self.cumulative.len(); + self.last_cumulative = last_cumulative + .or_else(|| self.cumulative.collect_last()) + .map(|row| (len, row)); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use brk_types::{Height, StoredU64, Version}; + use vecdb::{ + AnyStoredVec, ColumnId, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, + VecValue, WritableVec, + }; + + use super::ColumnarPerBlockCumulativeRolling; + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + enum Column { + A, + B, + } + + impl ColumnId for Column { + type Row + = [T; 2] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &[Self::A, Self::B]; + + fn index(self) -> usize { + self as usize + } + + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + fn from_fn(mut f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + [f(Self::A), f(Self::B)] + } + + fn map(row: Self::Row, mut f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + let [a, b] = row; + [f(a), f(b)] + } + } + + #[test] + fn pushes_delta_rows_and_recovers_after_truncation() { + let suffix = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "brk-columnar-cumulative-{}-{suffix}", + std::process::id() + )); + let db = Database::open(&path).unwrap(); + let mut vec = ColumnarPerBlockCumulativeRolling::::forced_import( + &db, + "values", + Version::ONE, + |_| (), + ) + .unwrap(); + + vec.push_block([1_u64.into(), 2_u64.into()]); + vec.push_block([3_u64.into(), 4_u64.into()]); + assert_eq!( + vec.cumulative.collect_range_at(0, 2), + [[1_u64.into(), 2_u64.into()], [4_u64.into(), 6_u64.into()]] + ); + + vec.truncate_if_needed_at(1).unwrap(); + vec.push_block([10_u64.into(), 20_u64.into()]); + assert_eq!( + vec.cumulative.collect_one(Height::new(1)), + Some([11_u64.into(), 22_u64.into()]) + ); + + vec.write().unwrap(); + let mut a: EagerVec> = + EagerVec::forced_import(&db, "a", Version::ONE).unwrap(); + let mut b: EagerVec> = + EagerVec::forced_import(&db, "b", Version::ONE).unwrap(); + for value in [1_u64, 3] { + a.push(value.into()); + } + for value in [2_u64, 4] { + b.push(value.into()); + } + a.write().unwrap(); + b.write().unwrap(); + + vec.compute_columns( + Height::ZERO, + |column| match column { + Column::A => &a, + Column::B => &b, + }, + &Exit::new(), + ) + .unwrap(); + assert_eq!( + vec.cumulative.collect_range_at(0, 2), + [ + [StoredU64::from(1_u64), StoredU64::from(2_u64)], + [StoredU64::from(4_u64), StoredU64::from(6_u64)], + ] + ); + + drop(vec); + drop(b); + drop(a); + drop(db); + std::fs::remove_dir_all(path).unwrap(); + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/columnar/lazy.rs b/crates/brk_computer/src/internal/per_block/computed/columnar/lazy.rs new file mode 100644 index 000000000..ae4fd80a6 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/columnar/lazy.rs @@ -0,0 +1,47 @@ +use brk_traversable::Traversable; +use brk_types::{Height, Version}; +use derive_more::{Deref, DerefMut}; +use schemars::JsonSchema; +use serde::Serialize; +use vecdb::{ + ColumnId, Formattable, LazyColumnVec, PcoVec, PcoVecValue, ReadOnlyColumnarVec, + ReadableColumnarVec, +}; + +use crate::{indexes, internal::Resolutions}; + +#[derive(Clone, Deref, DerefMut, Traversable)] +#[traversable(merge)] +pub struct LazyColumnPerBlock +where + T: PcoVecValue + Formattable + PartialOrd + Serialize + JsonSchema, + C: ColumnId, +{ + pub height: LazyColumnVec, C>, C>, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub resolutions: Box>, +} + +impl LazyColumnPerBlock +where + T: PcoVecValue + Formattable + PartialOrd + Serialize + JsonSchema + 'static, + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + ) -> Self { + let height = source.column(name, version, column); + let resolutions = Resolutions::forced_import(name, height.clone(), version, indexes); + + Self { + height, + resolutions: Box::new(resolutions), + } + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/columnar/lazy_count_cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/columnar/lazy_count_cumulative_rolling.rs new file mode 100644 index 000000000..727c60c53 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/columnar/lazy_count_cumulative_rolling.rs @@ -0,0 +1,79 @@ +use brk_traversable::Traversable; +use brk_types::{Height, StoredU16, StoredU64, Version}; +use vecdb::{ + CachedReadableVec, CachedVec, ColumnId, LazyVec, PcoVec, ReadOnlyColumnarVec, + ReadableColumnarVec, +}; + +use crate::{ + indexes, + internal::{ + CachedBlockCountReader, CachedWindowStartVec, Identity, LazyPerBlock, + LazyRollingAvgsFromHeight, LazyRollingSumsFromHeight, StoredU16ToStoredU64, Windows, + }, +}; + +#[derive(Clone, Traversable)] +pub struct LazyColumnCountPerBlockCumulativeRolling { + pub block: LazyVec, + pub cumulative: LazyPerBlock, + pub sum: LazyRollingSumsFromHeight, + pub average: LazyRollingAvgsFromHeight, + #[traversable(skip)] + cached_cumulative: CachedBlockCountReader, +} + +impl LazyColumnCountPerBlockCumulativeRolling { + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Self + where + C: ColumnId, + { + let column = CachedVec::wrap(source.column(name, version, column)); + let cached_cumulative = CachedBlockCountReader::new(column.cached_boxed_clone()); + let block = LazyVec::transformed::(name, version, Box::new(column)); + let cumulative = LazyPerBlock::from_uncached_height_source::, _>( + &format!("{name}_cumulative"), + version, + cached_cumulative.clone(), + indexes, + ); + let sum = LazyRollingSumsFromHeight::new_uncached( + &format!("{name}_sum"), + version, + &cached_cumulative, + cached_starts, + indexes, + ); + let average = LazyRollingAvgsFromHeight::new( + &format!("{name}_average"), + version, + &cached_cumulative, + cached_starts, + indexes, + ); + + Self { + block, + cumulative, + sum, + average, + cached_cumulative, + } + } + + #[inline(always)] + pub(crate) fn cached_cumulative(&self) -> CachedBlockCountReader { + self.cached_cumulative.clone() + } + + pub(crate) fn clear(&self) { + self.cached_cumulative.clear(); + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/columnar/lazy_cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/columnar/lazy_cumulative_rolling.rs new file mode 100644 index 000000000..799e05e23 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/columnar/lazy_cumulative_rolling.rs @@ -0,0 +1,57 @@ +use brk_traversable::Traversable; +use brk_types::{Height, Version}; +use schemars::JsonSchema; +use vecdb::{ColumnId, PcoVec, ReadOnlyColumnarVec}; + +use super::{super::lazy_cumulative_rolling::lazy_parts, LazyColumnPerBlock}; +use crate::{ + indexes, + internal::{ + CachedWindowStartVec, LazyPreviousDeltaVec, LazyRollingAvgsFromHeight, + LazyRollingSumsFromHeight, NumericValue, Windows, + }, +}; + +#[derive(Clone, Traversable)] +pub struct LazyColumnPerBlockCumulativeRolling +where + T: NumericValue + JsonSchema, + C: ColumnId, +{ + pub block: LazyPreviousDeltaVec, + pub cumulative: LazyColumnPerBlock, + pub sum: LazyRollingSumsFromHeight, + pub average: LazyRollingAvgsFromHeight, +} + +impl LazyColumnPerBlockCumulativeRolling +where + T: NumericValue + JsonSchema, + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Self { + let cumulative = LazyColumnPerBlock::new( + &format!("{name}_cumulative"), + version, + source, + column, + indexes, + ); + let (block, sum, average) = + lazy_parts(name, version, &cumulative.height, cached_starts, indexes); + + Self { + block, + cumulative, + sum, + average, + } + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/columnar/mod.rs b/crates/brk_computer/src/internal/per_block/computed/columnar/mod.rs new file mode 100644 index 000000000..c4a9291a4 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/columnar/mod.rs @@ -0,0 +1,11 @@ +mod base; +mod cumulative_rolling; +mod lazy; +mod lazy_count_cumulative_rolling; +mod lazy_cumulative_rolling; + +pub use base::*; +pub use cumulative_rolling::*; +pub use lazy::*; +pub use lazy_count_cumulative_rolling::*; +pub use lazy_cumulative_rolling::*; diff --git a/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs index 084a4b18c..47282eb9a 100644 --- a/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs +++ b/crates/brk_computer/src/internal/per_block/computed/cumulative_rolling.rs @@ -78,22 +78,6 @@ where self.last_cumulative = Some((len + 1, cumulative)); } - pub(crate) fn compute_cumulative( - &mut self, - max_from: Height, - source: &impl ReadableVec, - exit: &Exit, - ) -> Result<()> - where - S: VecValue + Into, - T: Copy, - { - Ok(self - .cumulative - .height - .compute_cumulative(max_from, source, exit)?) - } - pub(crate) fn compute_cumulative_transformed( &mut self, max_from: Height, diff --git a/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_average.rs b/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_average.rs new file mode 100644 index 000000000..b7386c28c --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_average.rs @@ -0,0 +1,69 @@ +use brk_traversable::Traversable; +use brk_types::{Height, Version}; +use schemars::JsonSchema; +use vecdb::{Ident, ReadableCloneableVec, UnaryTransform}; + +use crate::{ + indexes, + internal::{ + CachedWindowStartVec, LazyPreviousDeltaVec, LazyRollingAvgsFromHeight, NumericValue, + Windows, + }, +}; + +/// Lazy exact per-block values and rolling averages backed by one cumulative source. +#[derive(Traversable)] +pub struct LazyPerBlockCumulativeAverage +where + T: NumericValue + JsonSchema, + C: NumericValue + JsonSchema, + F: UnaryTransform, +{ + pub block: LazyPreviousDeltaVec, + #[traversable(flatten)] + pub average: LazyRollingAvgsFromHeight, +} + +impl Clone for LazyPerBlockCumulativeAverage +where + T: NumericValue + JsonSchema, + C: NumericValue + JsonSchema, + F: UnaryTransform, +{ + fn clone(&self) -> Self { + Self { + block: self.block.clone(), + average: self.average.clone(), + } + } +} + +impl LazyPerBlockCumulativeAverage +where + T: NumericValue + JsonSchema, + C: NumericValue + JsonSchema, + F: UnaryTransform, +{ + pub(crate) fn new( + name: &str, + version: Version, + cumulative: &(impl ReadableCloneableVec + 'static), + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Self { + Self { + block: LazyPreviousDeltaVec::transformed( + name, + version, + cumulative.read_only_boxed_clone(), + ), + average: LazyRollingAvgsFromHeight::new( + &format!("{name}_average"), + version + Version::TWO, + cumulative, + cached_starts, + indexes, + ), + } + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_rolling.rs b/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_rolling.rs index 1e645c138..f87f11d8b 100644 --- a/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_rolling.rs +++ b/crates/brk_computer/src/internal/per_block/computed/lazy_cumulative_rolling.rs @@ -3,13 +3,13 @@ use brk_traversable::Traversable; use brk_types::{Height, Version}; use schemars::JsonSchema; -use vecdb::{ReadableCloneableVec, ReadableVec, TypedVec, VecValue}; +use vecdb::{ColumnId, ReadableCloneableVec, ReadableVec, TypedVec, VecValue}; use crate::{ indexes, internal::{ - CachedWindowStartVec, Identity, LazyPerBlock, LazyPreviousDeltaVec, - LazyRollingAvgsFromHeight, LazyRollingSumsFromHeight, NumericValue, PerBlock, Windows, + CachedWindowStartVec, Identity, LazyColumnPerBlock, LazyPerBlock, LazyPreviousDeltaVec, + LazyRollingAvgsFromHeight, LazyRollingSumsFromHeight, NumericValue, Windows, }, }; @@ -99,18 +99,34 @@ where Self::from_cumulative(name, version, cumulative, cached_starts, indexes) } - pub(crate) fn from_source( + pub(crate) fn from_lazy_source( name: &str, version: Version, - source: &PerBlock, + source: &LazyPerBlock, cached_starts: &Windows<&CachedWindowStartVec>, indexes: &indexes::Vecs, ) -> Self { - let cumulative = LazyPerBlock::from_computed::>( + let cumulative = LazyPerBlock::from_lazy::, T>( + &format!("{name}_cumulative"), + version, + source, + ); + + Self::from_cumulative(name, version, cumulative, cached_starts, indexes) + } + + pub(crate) fn from_column_source( + name: &str, + version: Version, + source: &LazyColumnPerBlock, + cached_starts: &Windows<&CachedWindowStartVec>, + indexes: &indexes::Vecs, + ) -> Self { + let cumulative = LazyPerBlock::from_resolutions::>( &format!("{name}_cumulative"), version, source.height.read_only_boxed_clone(), - source, + &source.resolutions, ); Self::from_cumulative(name, version, cumulative, cached_starts, indexes) diff --git a/crates/brk_computer/src/internal/per_block/computed/mod.rs b/crates/brk_computer/src/internal/per_block/computed/mod.rs index eba9dcfd4..8da757203 100644 --- a/crates/brk_computer/src/internal/per_block/computed/mod.rs +++ b/crates/brk_computer/src/internal/per_block/computed/mod.rs @@ -1,13 +1,14 @@ mod aggregated; mod base; mod cached; -mod cached_count_cumulative_rolling; mod cached_cumulative_rolling; mod cached_rolling; +mod columnar; mod cumulative_rolling; mod distribution; mod full; mod full_from_cumulative; +mod lazy_cumulative_average; mod lazy_cumulative_rolling; mod lazy_distribution; mod lazy_rolling; @@ -18,13 +19,14 @@ mod with_deltas; pub use aggregated::*; pub use base::*; pub use cached::*; -pub use cached_count_cumulative_rolling::*; pub use cached_cumulative_rolling::*; pub use cached_rolling::*; +pub use columnar::*; pub use cumulative_rolling::*; pub use distribution::*; pub use full::*; pub use full_from_cumulative::*; +pub use lazy_cumulative_average::*; pub use lazy_cumulative_rolling::*; pub use lazy_distribution::*; pub use lazy_rolling::*; diff --git a/crates/brk_computer/src/internal/per_block/computed/resolutions.rs b/crates/brk_computer/src/internal/per_block/computed/resolutions.rs index 77ebe35c3..169f96467 100644 --- a/crates/brk_computer/src/internal/per_block/computed/resolutions.rs +++ b/crates/brk_computer/src/internal/per_block/computed/resolutions.rs @@ -66,7 +66,6 @@ where #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(transparent)] pub struct Resolutions( - #[allow(clippy::type_complexity)] pub PerResolution< LazyAggVec, Height, Height, T>, LazyAggVec, Height, Height, T>, diff --git a/crates/brk_computer/src/internal/per_block/lazy/cached_block_count_reader.rs b/crates/brk_computer/src/internal/per_block/lazy/cached_block_count_reader.rs index fa5204f06..8ee901622 100644 --- a/crates/brk_computer/src/internal/per_block/lazy/cached_block_count_reader.rs +++ b/crates/brk_computer/src/internal/per_block/lazy/cached_block_count_reader.rs @@ -30,6 +30,10 @@ impl CachedBlockCountReader { } } + pub(crate) fn clear(&self) { + self.block.clear(); + } + pub fn cumulative_at(&self, index: usize) -> Option { let (block, checkpoints) = self.snapshot(); (index < block.len()) diff --git a/crates/brk_computer/src/internal/per_block/lazy/derived_resolutions.rs b/crates/brk_computer/src/internal/per_block/lazy/derived_resolutions.rs index 4426e2f2c..771458356 100644 --- a/crates/brk_computer/src/internal/per_block/lazy/derived_resolutions.rs +++ b/crates/brk_computer/src/internal/per_block/lazy/derived_resolutions.rs @@ -17,7 +17,6 @@ use super::{LazyTransformLast, MapOption}; #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(transparent)] pub struct DerivedResolutions( - #[allow(clippy::type_complexity)] pub PerResolution< LazyTransformLast, Option>, LazyTransformLast, Option>, diff --git a/crates/brk_computer/src/internal/per_block/percent/columnar.rs b/crates/brk_computer/src/internal/per_block/percent/columnar.rs new file mode 100644 index 000000000..d572ca1bf --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/percent/columnar.rs @@ -0,0 +1,52 @@ +use brk_traversable::Traversable; +use brk_types::{Height, StoredF32, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ColumnId, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec}; + +use crate::{ + indexes, + internal::{FixedRatio, LazyColumnPerBlock, LazyPerBlock, Percent}, +}; + +/// Fixed-point column projection with lazy ratio and percentage views. +#[derive(Clone, Deref, DerefMut, Traversable)] +#[traversable(transparent)] +pub struct LazyColumnPercentPerBlock( + pub Percent, LazyPerBlock>, +); + +impl LazyColumnPercentPerBlock { + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + ) -> Self { + let ppm = LazyColumnPerBlock::new( + &format!("{name}_{}", B::SUFFIX), + version, + source, + column, + indexes, + ); + let ratio = LazyPerBlock::from_resolutions::( + &format!("{name}_ratio"), + version, + ppm.height.read_only_boxed_clone(), + &ppm.resolutions, + ); + let percent = LazyPerBlock::from_resolutions::( + name, + version, + ppm.height.read_only_boxed_clone(), + &ppm.resolutions, + ); + + Self(Percent { + ppm, + ratio, + percent, + }) + } +} diff --git a/crates/brk_computer/src/internal/per_block/percent/lazy_vec.rs b/crates/brk_computer/src/internal/per_block/percent/lazy_vec.rs index 0fd55817b..bab36152a 100644 --- a/crates/brk_computer/src/internal/per_block/percent/lazy_vec.rs +++ b/crates/brk_computer/src/internal/per_block/percent/lazy_vec.rs @@ -8,7 +8,6 @@ use crate::internal::{FixedRatio, Percent}; /// Fully lazy lightweight percent container with no derived resolutions. #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(transparent)] -#[allow(clippy::type_complexity)] pub struct LazyPercentVec( pub Percent, LazyVec>, ); diff --git a/crates/brk_computer/src/internal/per_block/percent/mod.rs b/crates/brk_computer/src/internal/per_block/percent/mod.rs index 9a8b07252..70d72fff3 100644 --- a/crates/brk_computer/src/internal/per_block/percent/mod.rs +++ b/crates/brk_computer/src/internal/per_block/percent/mod.rs @@ -1,4 +1,5 @@ mod base; +mod columnar; mod lazy; mod lazy_cumulative_rolling; mod lazy_vec; @@ -6,6 +7,7 @@ mod lazy_windows; mod windows; pub use base::*; +pub use columnar::*; pub use lazy::*; pub use lazy_cumulative_rolling::*; pub use lazy_vec::*; diff --git a/crates/brk_computer/src/internal/per_block/percent/windows.rs b/crates/brk_computer/src/internal/per_block/percent/windows.rs index 8ca04ae23..972583894 100644 --- a/crates/brk_computer/src/internal/per_block/percent/windows.rs +++ b/crates/brk_computer/src/internal/per_block/percent/windows.rs @@ -6,26 +6,38 @@ use vecdb::{Database, Rw, StorageMode}; use crate::{ indexes, - internal::{FixedRatio, PercentPerBlock, Windows}, + internal::{ColumnarPerBlock, FixedRatio, LazyColumnPercentPerBlock, WindowId, Windows}, }; -/// 4 rolling window vecs (24h, 1w, 1m, 1y), each storing fixed-point values -/// with lazy ratio and percent float views. +/// Four named fixed-point percentage views backed by one columnar source. #[derive(Deref, DerefMut, Traversable)] #[traversable(transparent)] -pub struct PercentRollingWindows( - pub Windows>, +pub struct ColumnarPercentRollingWindows( + pub ColumnarPerBlock>, M>, ); -impl PercentRollingWindows { +impl ColumnarPercentRollingWindows { pub(crate) fn forced_import( db: &Database, name: &str, version: Version, indexes: &indexes::Vecs, ) -> Result { - Ok(Self(Windows::try_from_fn(|suffix| { - PercentPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes) - })?)) + Ok(Self(ColumnarPerBlock::forced_import( + db, + &format!("{name}_{}", B::SUFFIX), + version, + |source| { + WindowId::series(|window| { + LazyColumnPercentPerBlock::new( + &format!("{name}_{}", window.suffix()), + version, + source, + window, + indexes, + ) + }) + }, + )?)) } } diff --git a/crates/brk_computer/src/internal/per_block/percentiles.rs b/crates/brk_computer/src/internal/per_block/percentiles.rs index 0974466ac..9d867cb43 100644 --- a/crates/brk_computer/src/internal/per_block/percentiles.rs +++ b/crates/brk_computer/src/internal/per_block/percentiles.rs @@ -1,18 +1,73 @@ use brk_error::Result; -use brk_traversable::{Traversable, TreeNode}; +use brk_traversable::Traversable; use brk_types::{Cents, Version}; -use vecdb::{AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec}; +pub use brk_types::{PERCENTILES, PERCENTILES_LEN, PercentileId}; +use derive_more::{Deref, DerefMut}; +use vecdb::{Database, Rw, StorageMode}; use crate::indexes; -use crate::internal::{PerBlock, Price}; +use crate::internal::{ColumnarPerBlock, LazyColumnPerBlock, Price}; -pub const PERCENTILES: [u8; 19] = [ - 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, -]; -pub const PERCENTILES_LEN: usize = PERCENTILES.len(); +#[derive(Clone, Traversable)] +pub struct PercentilePrices { + pub pct05: T, + pub pct10: T, + pub pct15: T, + pub pct20: T, + pub pct25: T, + pub pct30: T, + pub pct35: T, + pub pct40: T, + pub pct45: T, + pub pct50: T, + pub pct55: T, + pub pct60: T, + pub pct65: T, + pub pct70: T, + pub pct75: T, + pub pct80: T, + pub pct85: T, + pub pct90: T, + pub pct95: T, +} +impl PercentilePrices { + fn from_fn(mut f: impl FnMut(PercentileId) -> T) -> Self { + Self { + pct05: f(PercentileId::Pct05), + pct10: f(PercentileId::Pct10), + pct15: f(PercentileId::Pct15), + pct20: f(PercentileId::Pct20), + pct25: f(PercentileId::Pct25), + pct30: f(PercentileId::Pct30), + pct35: f(PercentileId::Pct35), + pct40: f(PercentileId::Pct40), + pct45: f(PercentileId::Pct45), + pct50: f(PercentileId::Pct50), + pct55: f(PercentileId::Pct55), + pct60: f(PercentileId::Pct60), + pct65: f(PercentileId::Pct65), + pct70: f(PercentileId::Pct70), + pct75: f(PercentileId::Pct75), + pct80: f(PercentileId::Pct80), + pct85: f(PercentileId::Pct85), + pct90: f(PercentileId::Pct90), + pct95: f(PercentileId::Pct95), + } + } +} + +#[derive(Deref, DerefMut, Traversable)] pub struct PercentilesVecs { - pub vecs: [Price>; PERCENTILES_LEN], + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub prices: ColumnarPerBlock< + Cents, + PercentileId, + PercentilePrices>>, + M, + >, } const VERSION: Version = Version::ONE; @@ -24,64 +79,36 @@ impl PercentilesVecs { version: Version, indexes: &indexes::Vecs, ) -> Result { - let vecs = PERCENTILES - .into_iter() - .map(|p| { - let series_name = format!("{prefix}_pct{p:02}"); - Price::forced_import(db, &series_name, version + VERSION, indexes) - }) - .collect::>>()? - .try_into() - .ok() - .expect("PERCENTILES length mismatch"); + let version = version + VERSION; + let prices = ColumnarPerBlock::::forced_import( + db, + &format!("{prefix}_cents"), + version, + |source| { + PercentilePrices::from_fn(|id| { + Price::from_columnar_source( + &format!("{prefix}_pct{:02}", id.percentile()), + version, + source, + id, + indexes, + ) + }) + }, + )?; - Ok(Self { vecs }) + Ok(Self { prices }) } /// Push percentile prices (in cents). #[inline(always)] pub(crate) fn push(&mut self, percentile_prices: &[Cents; PERCENTILES_LEN]) { - for (i, v) in self.vecs.iter_mut().enumerate() { - v.cents.height.push(percentile_prices[i]); - } + self.prices.push(*percentile_prices); } /// Validate computed versions or reset if mismatched. pub(crate) fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> { - for vec in self.vecs.iter_mut() { - vec.cents - .height - .validate_computed_version_or_reset(version)?; - } + self.prices.validate_computed_version_or_reset(version)?; Ok(()) } } - -impl ReadOnlyClone for PercentilesVecs { - type ReadOnly = PercentilesVecs; - - fn read_only_clone(&self) -> Self::ReadOnly { - PercentilesVecs { - vecs: self.vecs.each_ref().map(|v| v.read_only_clone()), - } - } -} - -impl Traversable for PercentilesVecs -where - Price>: Traversable, -{ - fn to_tree_node(&self) -> TreeNode { - TreeNode::Branch( - PERCENTILES - .iter() - .zip(self.vecs.iter()) - .map(|(p, v)| (format!("pct{p:02}"), v.to_tree_node())) - .collect(), - ) - } - - fn iter_any_exportable(&self) -> impl Iterator { - self.vecs.iter().flat_map(|p| p.iter_any_exportable()) - } -} diff --git a/crates/brk_computer/src/internal/per_block/price.rs b/crates/brk_computer/src/internal/per_block/price.rs index 33281bf90..2a84adecc 100644 --- a/crates/brk_computer/src/internal/per_block/price.rs +++ b/crates/brk_computer/src/internal/per_block/price.rs @@ -6,11 +6,14 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Cents, Dollars, SatsFract, Version}; +use brk_types::{Cents, Dollars, Height, SatsFract, Version}; use schemars::JsonSchema; -use vecdb::{Database, ReadableCloneableVec, ReadableVec, TypedVec, UnaryTransform}; +use vecdb::{ + ColumnId, Database, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec, ReadableVec, TypedVec, + UnaryTransform, +}; -use super::{LazyPerBlock, PerBlock}; +use super::{LazyColumnPerBlock, LazyPerBlock, PerBlock}; use crate::{ indexes, internal::{CentsUnsignedToDollars, ComputedVecValue, DollarsToSatsFract}, @@ -48,6 +51,35 @@ impl Price> { } } +impl Price> +where + C: ColumnId, +{ + pub(crate) fn from_columnar_source( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + ) -> Self { + let cents = + LazyColumnPerBlock::new(&format!("{name}_cents"), version, source, column, indexes); + let usd = LazyPerBlock::from_resolutions::( + name, + version, + cents.height.read_only_boxed_clone(), + ¢s.resolutions, + ); + let sats = LazyPerBlock::from_lazy::( + &format!("{name}_sats"), + version, + &usd, + ); + + Self { usd, cents, sats } + } +} + impl Price> { pub(crate) fn from_lazy_cents_source( name: &str, diff --git a/crates/brk_computer/src/internal/per_block/ratio/columnar.rs b/crates/brk_computer/src/internal/per_block/ratio/columnar.rs new file mode 100644 index 000000000..3dfedb0e4 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/ratio/columnar.rs @@ -0,0 +1,48 @@ +use brk_traversable::Traversable; +use brk_types::{Height, StoredF32, Version}; +use vecdb::{ColumnId, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec}; + +use crate::{ + indexes, + internal::{FixedRatio, LazyColumnPerBlock, LazyPerBlock}, +}; + +#[derive(Clone, Traversable)] +pub struct LazyColumnRatioPerBlock +where + R: FixedRatio, + C: ColumnId, +{ + pub ppm: LazyColumnPerBlock, + pub ratio: LazyPerBlock, +} + +impl LazyColumnRatioPerBlock +where + R: FixedRatio, + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + ) -> Self { + let ppm = LazyColumnPerBlock::new( + &format!("{name}_{}", R::SUFFIX), + version, + source, + column, + indexes, + ); + let ratio = LazyPerBlock::from_resolutions::( + name, + version, + ppm.height.read_only_boxed_clone(), + &ppm.resolutions, + ); + + Self { ppm, ratio } + } +} diff --git a/crates/brk_computer/src/internal/per_block/ratio/columnar_price.rs b/crates/brk_computer/src/internal/per_block/ratio/columnar_price.rs new file mode 100644 index 000000000..01f0b4f49 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/ratio/columnar_price.rs @@ -0,0 +1,60 @@ +use brk_traversable::Traversable; +use brk_types::{Cents, Dollars, Height, PartsPerMillion64, SatsFract, StoredF32, Version}; +use vecdb::{CachedBoxedVec, ColumnId, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec}; + +use crate::{ + indexes, + internal::{LazyColumnPerBlock, LazyIndexedVec, LazyPerBlock, LazyRatioPerBlock, Price}, +}; + +use super::price::price_ratio; + +#[derive(Clone, Traversable)] +pub struct LazyColumnPriceWithRatioPerBlock +where + C: ColumnId, +{ + pub usd: LazyPerBlock, + pub cents: LazyColumnPerBlock, + pub sats: LazyPerBlock, + pub ppm: LazyPerBlock, + pub ratio: LazyPerBlock, +} + +impl LazyColumnPriceWithRatioPerBlock +where + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + spot_price: &CachedBoxedVec, + ) -> Self { + let price = Price::from_columnar_source(name, version, source, column, indexes); + let ratio_version = version + Version::new(4); + let ppm_source = LazyIndexedVec::new( + &format!("{name}_ratio_ppm_source"), + ratio_version, + price.cents.height.read_only_boxed_clone(), + spot_price.clone(), + |_, price, spot| price_ratio(spot, price), + ); + let ratio = LazyRatioPerBlock::from_height_source( + &format!("{name}_ratio"), + ratio_version, + ppm_source, + indexes, + ); + + Self { + usd: price.usd, + cents: price.cents, + sats: price.sats, + ppm: ratio.ppm, + ratio: ratio.ratio, + } + } +} diff --git a/crates/brk_computer/src/internal/per_block/ratio/mod.rs b/crates/brk_computer/src/internal/per_block/ratio/mod.rs index b63f9d097..3cfee57af 100644 --- a/crates/brk_computer/src/internal/per_block/ratio/mod.rs +++ b/crates/brk_computer/src/internal/per_block/ratio/mod.rs @@ -1,9 +1,13 @@ mod base; +mod columnar; +mod columnar_price; mod lazy; mod lazy_price; mod price; pub use base::*; +pub use columnar::*; +pub use columnar_price::*; pub use lazy::*; pub use lazy_price::*; pub use price::*; diff --git a/crates/brk_computer/src/internal/per_block/rolling/windows.rs b/crates/brk_computer/src/internal/per_block/rolling/windows.rs index b1ce3e5fe..056234621 100644 --- a/crates/brk_computer/src/internal/per_block/rolling/windows.rs +++ b/crates/brk_computer/src/internal/per_block/rolling/windows.rs @@ -15,7 +15,8 @@ use crate::{ blocks::lookback::LazyWindowStartVec, indexes, internal::{ - ComputedVecValue, NumericValue, PerBlock, RollingWindow24h, Windows, WindowsFrom1w, + ColumnarPerBlock, ComputedVecValue, LazyColumnPerBlock, NumericValue, PerBlock, + RollingWindow24h, WindowFrom1wId, WindowId, Windows, WindowsFrom1w, }, }; @@ -48,6 +49,44 @@ where } } +/// Four named rolling-window views backed by one columnar source. +#[derive(Deref, DerefMut, Traversable)] +#[traversable(transparent)] +pub struct ColumnarRollingWindows( + pub ColumnarPerBlock>, M>, +) +where + T: NumericValue + JsonSchema; + +impl ColumnarRollingWindows +where + T: NumericValue + JsonSchema, +{ + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + Ok(Self(ColumnarPerBlock::forced_import( + db, + name, + version, + |source| { + WindowId::series(|window| { + LazyColumnPerBlock::new( + &format!("{name}_{}", window.suffix()), + version, + source, + window, + indexes, + ) + }) + }, + )?)) + } +} + /// Single 24h rolling window backed by PerBlock (1 stored vec). #[derive(Deref, DerefMut, Traversable)] #[traversable(transparent)] @@ -71,14 +110,16 @@ where } } -/// Extended rolling windows: 1w + 1m + 1y (3 stored vecs). +/// The 1w, 1m, and 1y views backed by one columnar source. #[derive(Deref, DerefMut, Traversable)] #[traversable(transparent)] -pub struct RollingWindowsFrom1w(pub WindowsFrom1w>) +pub struct ColumnarRollingWindowsFrom1w( + pub ColumnarPerBlock>, M>, +) where - T: ComputedVecValue + PartialOrd + JsonSchema; + T: NumericValue + JsonSchema; -impl RollingWindowsFrom1w +impl ColumnarRollingWindowsFrom1w where T: NumericValue + JsonSchema, { @@ -88,8 +129,21 @@ where version: Version, indexes: &indexes::Vecs, ) -> Result { - Ok(Self(WindowsFrom1w::try_from_fn(|suffix| { - PerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes) - })?)) + Ok(Self(ColumnarPerBlock::forced_import( + db, + name, + version, + |source| { + WindowFrom1wId::series(|window| { + LazyColumnPerBlock::new( + &format!("{name}_{}", window.suffix()), + version, + source, + window, + indexes, + ) + }) + }, + )?)) } } diff --git a/crates/brk_computer/src/internal/per_block/value/columnar.rs b/crates/brk_computer/src/internal/per_block/value/columnar.rs new file mode 100644 index 000000000..e610c49f4 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/value/columnar.rs @@ -0,0 +1,239 @@ +use brk_traversable::Traversable; +use brk_types::{ + Bitcoin, Cents, Dollars, Height, PartsPerMillionSigned64, Sats, SatsSigned, Version, +}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + BinaryTransform, CachedBoxedVec, ColumnId, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec, +}; + +use crate::{ + indexes, + internal::{ + CachedWindowStartVec, CentsUnsignedToDollars, Identity, LazyColumnPerBlock, LazyIndexedVec, + LazyPerBlock, LazyRollingAvgsAmountFromHeight, LazyRollingDeltasAmountFromHeight, + LazyRollingSumsAmountFromHeight, LazyValueBlock, SatsToBitcoin, SatsToCents, Windows, + }, +}; + +#[derive(Clone, Traversable)] +pub struct LazyColumnValuePerBlock +where + C: ColumnId, +{ + pub btc: LazyPerBlock, + pub sats: LazyColumnPerBlock, + pub usd: LazyPerBlock, + pub cents: LazyColumnPerBlock, +} + +#[derive(Clone, Traversable)] +pub struct LazyColumnSpotValuePerBlock +where + C: ColumnId, +{ + pub btc: LazyPerBlock, + pub sats: LazyColumnPerBlock, + pub usd: LazyPerBlock, + pub cents: LazyPerBlock, +} + +impl LazyColumnSpotValuePerBlock +where + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + spot_price: &CachedBoxedVec, + ) -> Self { + let sats = + LazyColumnPerBlock::new(&format!("{name}_sats"), version, source, column, indexes); + let btc = LazyPerBlock::from_resolutions::( + name, + version, + sats.height.read_only_boxed_clone(), + &sats.resolutions, + ); + let cents_source = LazyIndexedVec::new( + &format!("{name}_cents_source"), + version, + sats.height.read_only_boxed_clone(), + spot_price.clone(), + |_, sats, spot| SatsToCents::apply(sats, spot), + ); + let cents = LazyPerBlock::from_height_source::, _>( + &format!("{name}_cents"), + version, + cents_source, + indexes, + ); + let usd = LazyPerBlock::from_lazy::( + &format!("{name}_usd"), + version, + ¢s, + ); + + Self { + btc, + sats, + usd, + cents, + } + } +} + +#[derive(Clone, Deref, DerefMut, Traversable)] +pub struct LazyColumnSpotValuePerBlockWithDeltas +where + C: ColumnId, +{ + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub inner: LazyColumnSpotValuePerBlock, + pub delta: LazyRollingDeltasAmountFromHeight, +} + +impl LazyColumnSpotValuePerBlockWithDeltas +where + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + spot_price: &CachedBoxedVec, + ) -> Self { + let inner = + LazyColumnSpotValuePerBlock::new(name, version, source, column, indexes, spot_price); + let delta = LazyRollingDeltasAmountFromHeight::new( + &format!("{name}_delta"), + version + Version::TWO, + &inner.sats.height, + cached_starts, + indexes, + ); + + Self { inner, delta } + } +} + +impl LazyColumnValuePerBlock +where + C: ColumnId, +{ + pub(crate) fn new( + name: &str, + version: Version, + sats_source: &ReadOnlyColumnarVec, C>, + cents_source: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + ) -> Self { + let sats = LazyColumnPerBlock::new( + &format!("{name}_sats"), + version, + sats_source, + column, + indexes, + ); + let btc = LazyPerBlock::from_resolutions::( + name, + version, + sats.height.read_only_boxed_clone(), + &sats.resolutions, + ); + let cents = LazyColumnPerBlock::new( + &format!("{name}_cents"), + version, + cents_source, + column, + indexes, + ); + let usd = LazyPerBlock::from_resolutions::( + &format!("{name}_usd"), + version, + cents.height.read_only_boxed_clone(), + ¢s.resolutions, + ); + + Self { + btc, + sats, + usd, + cents, + } + } +} + +#[derive(Clone, Traversable)] +pub struct LazyColumnValuePerBlockCumulativeRolling +where + C: ColumnId, +{ + pub block: LazyValueBlock, + pub cumulative: LazyColumnValuePerBlock, + pub sum: LazyRollingSumsAmountFromHeight, + pub average: LazyRollingAvgsAmountFromHeight, +} + +impl LazyColumnValuePerBlockCumulativeRolling +where + C: ColumnId, +{ + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + name: &str, + version: Version, + cumulative_sats: &ReadOnlyColumnarVec, C>, + cumulative_cents: &ReadOnlyColumnarVec, C>, + column: C, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Self { + let cumulative = LazyColumnValuePerBlock::new( + &format!("{name}_cumulative"), + version, + cumulative_sats, + cumulative_cents, + column, + indexes, + ); + let block = LazyValueBlock::from_cumulative_sources( + name, + version, + &cumulative.sats.height, + &cumulative.cents.height, + ); + let sum = LazyRollingSumsAmountFromHeight::new( + &format!("{name}_sum"), + version, + &cumulative.sats.height, + &cumulative.cents.height, + cached_starts, + indexes, + ); + let average = LazyRollingAvgsAmountFromHeight::new( + &format!("{name}_average"), + version, + &cumulative.sats.height, + &cumulative.cents.height, + cached_starts, + indexes, + ); + + Self { + block, + cumulative, + sum, + average, + } + } +} diff --git a/crates/brk_computer/src/internal/per_block/value/mod.rs b/crates/brk_computer/src/internal/per_block/value/mod.rs index 00d391f0a..9bc1e8379 100644 --- a/crates/brk_computer/src/internal/per_block/value/mod.rs +++ b/crates/brk_computer/src/internal/per_block/value/mod.rs @@ -1,6 +1,7 @@ mod base; mod cached; mod cached_full; +mod columnar; mod cumulative; mod cumulative_rolling; mod lazy; @@ -11,11 +12,11 @@ mod lazy_rolling_sum; mod lazy_spot; mod rolling_distribution; mod spot; -mod spot_with_deltas; pub use base::*; pub use cached::*; pub use cached_full::*; +pub use columnar::*; pub use cumulative::*; pub use cumulative_rolling::*; pub use lazy::*; @@ -26,4 +27,3 @@ pub use lazy_rolling_sum::*; pub use lazy_spot::*; pub use rolling_distribution::*; pub use spot::*; -pub use spot_with_deltas::*; diff --git a/crates/brk_computer/src/internal/per_block/value/spot_with_deltas.rs b/crates/brk_computer/src/internal/per_block/value/spot_with_deltas.rs deleted file mode 100644 index 86969b8d0..000000000 --- a/crates/brk_computer/src/internal/per_block/value/spot_with_deltas.rs +++ /dev/null @@ -1,44 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use brk_types::{Cents, Height, PartsPerMillionSigned64, Sats, SatsSigned, Version}; -use derive_more::{Deref, DerefMut}; -use vecdb::{CachedBoxedVec, Database, Rw, StorageMode}; - -use crate::{ - indexes, - internal::{ - CachedWindowStartVec, LazyRollingDeltasAmountFromHeight, SpotValuePerBlock, Windows, - }, -}; - -#[derive(Deref, DerefMut, Traversable)] -pub struct SpotValuePerBlockWithDeltas { - #[deref] - #[deref_mut] - #[traversable(flatten)] - pub inner: SpotValuePerBlock, - pub delta: LazyRollingDeltasAmountFromHeight, -} - -impl SpotValuePerBlockWithDeltas { - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - spot_price: &CachedBoxedVec, - ) -> Result { - let inner = SpotValuePerBlock::forced_import(db, name, version, indexes, spot_price)?; - - let delta = LazyRollingDeltasAmountFromHeight::new( - &format!("{name}_delta"), - version + Version::TWO, - &inner.sats.height, - cached_starts, - indexes, - ); - - Ok(Self { inner, delta }) - } -} diff --git a/crates/brk_computer/src/internal/transform/mod.rs b/crates/brk_computer/src/internal/transform/mod.rs index a8b3b384d..dd2beeb99 100644 --- a/crates/brk_computer/src/internal/transform/mod.rs +++ b/crates/brk_computer/src/internal/transform/mod.rs @@ -21,7 +21,7 @@ pub use derived::{ }; pub use fixed_ratio::{FixedToPercent, FixedToRatio}; pub use ratio::{ - RatioCents, RatioCentsSignedCents, RatioDiffCents, RatioDiffDollars, RatioDiffF32, + RatioBytes, RatioCents, RatioCentsSignedCents, RatioDiffCents, RatioDiffDollars, RatioDiffF32, RatioDollars, RatioSats, RatioU64, }; pub use specialized::{ diff --git a/crates/brk_computer/src/internal/transform/ratio.rs b/crates/brk_computer/src/internal/transform/ratio.rs index 831b03e91..efab37c02 100644 --- a/crates/brk_computer/src/internal/transform/ratio.rs +++ b/crates/brk_computer/src/internal/transform/ratio.rs @@ -1,9 +1,9 @@ use std::marker::PhantomData; -use brk_types::{Cents, CentsSigned, Dollars, Sats, StoredF32, StoredU64}; +use brk_types::{Bytes, Cents, CentsSigned, Dollars, Sats, StoredF32, StoredU64}; use vecdb::{BinaryTransform, unlikely}; -use crate::internal::FixedRatio; +use crate::internal::{FixedRatio, NumericValue}; pub struct RatioU64

(PhantomData

); @@ -18,6 +18,20 @@ impl BinaryTransform for RatioU64

{ } } +pub struct RatioBytes

(PhantomData

); + +impl BinaryTransform for RatioBytes

{ + #[inline(always)] + fn apply(numerator: Bytes, denominator: D) -> P { + let denominator: f64 = denominator.into(); + if denominator > 0.0 { + P::from(f64::from(numerator) / denominator) + } else { + P::default() + } + } +} + pub struct RatioSats

(PhantomData

); impl BinaryTransform for RatioSats

{ diff --git a/crates/brk_computer/src/internal/with_addr_types.rs b/crates/brk_computer/src/internal/with_addr_types.rs deleted file mode 100644 index 3e6336980..000000000 --- a/crates/brk_computer/src/internal/with_addr_types.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! Generic `all` + per-`AddrType` container, mirrors the `WithSth` pattern -//! along the address-type axis. Used by every metric that tracks one -//! aggregate value alongside a per-address-type breakdown. - -use brk_cohort::ByAddrType; -use brk_error::Result; -use brk_indexer::Lengths; -use brk_traversable::Traversable; -use brk_types::{Cents, Height, Sats, Version}; -use rayon::prelude::*; -use schemars::JsonSchema; -use vecdb::{AnyStoredVec, AnyVec, CachedBoxedVec, Database, EagerVec, Exit, PcoVec, WritableVec}; - -use crate::indexes; - -use super::{ - CachedWindowStartVec, NumericValue, PerBlock, PerBlockCumulativeRolling, SpotValuePerBlock, - Windows, -}; - -/// `all` aggregate plus per-`AddrType` breakdown. -#[derive(Clone, Traversable)] -pub struct WithAddrTypes { - pub all: T, - #[traversable(flatten)] - pub by_addr_type: ByAddrType, -} - -impl WithAddrTypes> -where - T: NumericValue + JsonSchema, -{ - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - ) -> Result { - let all = PerBlock::forced_import(db, name, version, indexes)?; - let by_addr_type = ByAddrType::new_with_name(|type_name| { - PerBlock::forced_import(db, &format!("{type_name}_{name}"), version, indexes) - })?; - Ok(Self { all, by_addr_type }) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.by_addr_type - .values() - .map(|v| v.height.len()) - .min() - .unwrap() - .min(self.all.height.len()) - } - - pub(crate) fn par_iter_height_mut( - &mut self, - ) -> impl ParallelIterator { - rayon::iter::once(&mut self.all.height as &mut dyn AnyStoredVec).chain( - self.by_addr_type - .par_values_mut() - .map(|v| &mut v.height as &mut dyn AnyStoredVec), - ) - } - - pub(crate) fn reset_height(&mut self) -> Result<()> { - self.all.height.reset()?; - for v in self.by_addr_type.values_mut() { - v.height.reset()?; - } - Ok(()) - } - - #[inline(always)] - pub(crate) fn push_height(&mut self, total: U, per_type: impl IntoIterator) - where - U: Into, - { - self.all.height.push(total.into()); - for (v, value) in self.by_addr_type.values_mut().zip(per_type) { - v.height.push(value.into()); - } - } - - /// Compute `all.height` as the per-block sum of the per-type vecs. - pub(crate) fn compute_rest(&mut self, starting_lengths: &Lengths, exit: &Exit) -> Result<()> { - let sources: Vec<&EagerVec>> = - self.by_addr_type.values().map(|v| &v.height).collect(); - self.all - .height - .compute_sum_of_others(starting_lengths.height, &sources, exit)?; - Ok(()) - } -} - -impl WithAddrTypes> -where - T: NumericValue + JsonSchema, -{ - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result { - let all = - PerBlockCumulativeRolling::forced_import(db, name, version, indexes, cached_starts)?; - let by_addr_type = ByAddrType::new_with_name(|type_name| { - PerBlockCumulativeRolling::forced_import( - db, - &format!("{type_name}_{name}"), - version, - indexes, - cached_starts, - ) - })?; - Ok(Self { all, by_addr_type }) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.by_addr_type - .values() - .map(|v| v.cumulative.height.len()) - .min() - .unwrap() - .min(self.all.cumulative.height.len()) - } - - pub(crate) fn par_iter_height_mut( - &mut self, - ) -> impl ParallelIterator { - rayon::iter::once(&mut self.all.cumulative.height as &mut dyn AnyStoredVec).chain( - self.by_addr_type - .par_values_mut() - .map(|v| &mut v.cumulative.height as &mut dyn AnyStoredVec), - ) - } - - pub(crate) fn reset_height(&mut self) -> Result<()> { - self.all.cumulative.height.reset()?; - for v in self.by_addr_type.values_mut() { - v.cumulative.height.reset()?; - } - Ok(()) - } - - #[inline(always)] - pub(crate) fn push_height(&mut self, total: U, per_type: impl IntoIterator) - where - U: Into, - { - self.all.push_block(total.into()); - for (v, value) in self.by_addr_type.values_mut().zip(per_type) { - v.push_block(value.into()); - } - } -} - -impl WithAddrTypes { - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - spot_price: &CachedBoxedVec, - ) -> Result { - let all = SpotValuePerBlock::forced_import(db, name, version, indexes, spot_price)?; - let by_addr_type = ByAddrType::new_with_name(|type_name| { - SpotValuePerBlock::forced_import( - db, - &format!("{type_name}_{name}"), - version, - indexes, - spot_price, - ) - })?; - Ok(Self { all, by_addr_type }) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.by_addr_type - .values() - .map(|v| v.sats.height.len()) - .min() - .unwrap() - .min(self.all.sats.height.len()) - } - - pub(crate) fn par_iter_height_mut( - &mut self, - ) -> impl ParallelIterator { - rayon::iter::once(&mut self.all.sats.height as &mut dyn AnyStoredVec).chain( - self.by_addr_type - .par_values_mut() - .map(|v| &mut v.sats.height as &mut dyn AnyStoredVec), - ) - } - - pub(crate) fn reset_height(&mut self) -> Result<()> { - self.all.sats.height.reset()?; - for v in self.by_addr_type.values_mut() { - v.sats.height.reset()?; - } - Ok(()) - } - - /// Push the stateful sats value for `all` and each per-type. - #[inline(always)] - pub(crate) fn push_height(&mut self, total: U, per_type: impl IntoIterator) - where - U: Into, - { - self.all.sats.height.push(total.into()); - for (v, value) in self.by_addr_type.values_mut().zip(per_type) { - v.sats.height.push(value.into()); - } - } -} diff --git a/crates/brk_computer/src/internal/with_addr_types/columnar.rs b/crates/brk_computer/src/internal/with_addr_types/columnar.rs new file mode 100644 index 000000000..61e697df9 --- /dev/null +++ b/crates/brk_computer/src/internal/with_addr_types/columnar.rs @@ -0,0 +1,143 @@ +use brk_cohort::{ADDR_TYPE_IDS, AddrTypeId}; +use brk_types::{Cents, Height, Sats, Version}; +use schemars::JsonSchema; +use vecdb::{ + CachedBoxedVec, CachedVec, PcoVec, PcoVecValue, ReadOnlyColumnarVec, ReadableColumnarVec, + UnaryTransform, +}; + +use crate::{ + indexes, + internal::{ + CachedWindowStartVec, Identity, LazyColumnPerBlock, LazyColumnPerBlockCumulativeRolling, + LazyColumnSpotValuePerBlock, LazyPerBlock, LazyPerBlockCumulativeAverage, + LazyPerBlockCumulativeRolling, LazySpotValuePerBlock, NumericValue, Windows, + }, +}; + +use super::WithAddrTypes; + +impl WithAddrTypes, LazyPerBlock> +where + T: NumericValue + JsonSchema + 'static, +{ + pub(crate) fn from_columnar_source( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, AddrTypeId>, + indexes: &indexes::Vecs, + ) -> Self { + let all = LazyPerBlock::from_height_source::, _>( + name, + version, + source.sum_columns(name, version, ADDR_TYPE_IDS), + indexes, + ); + let by_addr_type = AddrTypeId::series(|column, type_name| { + LazyColumnPerBlock::new( + &format!("{type_name}_{name}"), + version, + source, + column, + indexes, + ) + }); + + Self { all, by_addr_type } + } +} + +impl WithAddrTypes, LazySpotValuePerBlock> { + pub(crate) fn from_columnar_spot_value_source( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, AddrTypeId>, + indexes: &indexes::Vecs, + spot_price: &CachedBoxedVec, + ) -> Self { + let sats = + CachedVec::wrap(source.sum_columns(&format!("{name}_sats"), version, ADDR_TYPE_IDS)); + let all = LazySpotValuePerBlock::from_sats_source(name, version, sats, indexes, spot_price); + let by_addr_type = AddrTypeId::series(|column, type_name| { + LazyColumnSpotValuePerBlock::new( + &format!("{type_name}_{name}"), + version, + source, + column, + indexes, + spot_price, + ) + }); + + Self { all, by_addr_type } + } +} + +impl + WithAddrTypes< + LazyColumnPerBlockCumulativeRolling, + LazyPerBlockCumulativeRolling, + > +where + T: NumericValue + JsonSchema + PcoVecValue, +{ + pub(crate) fn from_columnar_cumulative_source( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, AddrTypeId>, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Self { + let cumulative = CachedVec::wrap(source.sum_columns( + &format!("{name}_cumulative"), + version, + ADDR_TYPE_IDS, + )); + let all = LazyPerBlockCumulativeRolling::from_cached_cumulative_source( + name, + version, + cumulative, + cached_starts, + indexes, + ); + let by_addr_type = AddrTypeId::series(|column, type_name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{type_name}_{name}"), + version, + source, + column, + indexes, + cached_starts, + ) + }); + + Self { all, by_addr_type } + } +} + +impl WithAddrTypes> +where + T: NumericValue + JsonSchema, + C: NumericValue + JsonSchema + PcoVecValue, + F: UnaryTransform, +{ + pub(crate) fn from_columnar_cumulative_average_source( + name: &str, + version: Version, + source: &ReadOnlyColumnarVec, AddrTypeId>, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + ) -> Self { + let cumulative_name = format!("{name}_cumulative"); + let cumulative = source.sum_columns(&cumulative_name, version, ADDR_TYPE_IDS); + let all = + LazyPerBlockCumulativeAverage::new(name, version, &cumulative, indexes, cached_starts); + let by_addr_type = AddrTypeId::series(|column, type_name| { + let name = format!("{type_name}_{name}"); + let cumulative = source.column(&format!("{name}_cumulative"), version, column); + LazyPerBlockCumulativeAverage::new(&name, version, &cumulative, indexes, cached_starts) + }); + + Self { all, by_addr_type } + } +} diff --git a/crates/brk_computer/src/internal/with_addr_types/mod.rs b/crates/brk_computer/src/internal/with_addr_types/mod.rs new file mode 100644 index 000000000..9ba6f585c --- /dev/null +++ b/crates/brk_computer/src/internal/with_addr_types/mod.rs @@ -0,0 +1,12 @@ +mod columnar; + +use brk_cohort::ByAddrType; +use brk_traversable::Traversable; + +/// `all` aggregate plus a per-address-type breakdown. +#[derive(Clone, Traversable)] +pub struct WithAddrTypes { + pub all: A, + #[traversable(flatten)] + pub by_addr_type: ByAddrType, +} diff --git a/crates/brk_computer/src/lib.rs b/crates/brk_computer/src/lib.rs index 5da73d876..ab192a237 100644 --- a/crates/brk_computer/src/lib.rs +++ b/crates/brk_computer/src/lib.rs @@ -1,4 +1,5 @@ #![doc = include_str!("../README.md")] +#![allow(clippy::type_complexity)] use std::{fs, path::Path, thread, time::Instant}; diff --git a/crates/brk_computer/src/market/moving_average/compute.rs b/crates/brk_computer/src/market/moving_average/compute.rs index ac7b11f17..5a0e0cfb6 100644 --- a/crates/brk_computer/src/market/moving_average/compute.rs +++ b/crates/brk_computer/src/market/moving_average/compute.rs @@ -16,32 +16,12 @@ impl Vecs { let starting_lengths = indexer.safe_lengths(); let close = &prices.spot.cents.height; - for (ema, period) in [ - (&mut self.ema._1w, 7), - (&mut self.ema._8d, 8), - (&mut self.ema._12d, 12), - (&mut self.ema._13d, 13), - (&mut self.ema._21d, 21), - (&mut self.ema._26d, 26), - (&mut self.ema._1m, 30), - (&mut self.ema._34d, 34), - (&mut self.ema._55d, 55), - (&mut self.ema._89d, 89), - (&mut self.ema._144d, 144), - (&mut self.ema._200d, 200), - (&mut self.ema._1y, 365), - (&mut self.ema._2y, 2 * 365), - (&mut self.ema._200w, 200 * 7), - (&mut self.ema._4y, 4 * 365), - ] { - let window_starts = blocks.lookback.start_vec(period); - ema.cents.height.compute_rolling_ema( - starting_lengths.height, - window_starts, - close, - exit, - )?; - } + self.ema.height.compute_rolling_ema_columns( + starting_lengths.height, + |period| blocks.lookback.start_vec(period.days()), + close, + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/market/moving_average/import.rs b/crates/brk_computer/src/market/moving_average/import.rs index 8d1d08dbe..7ece29102 100644 --- a/crates/brk_computer/src/market/moving_average/import.rs +++ b/crates/brk_computer/src/market/moving_average/import.rs @@ -2,8 +2,13 @@ use brk_error::Result; use brk_types::{Cents, Height, Version}; use vecdb::{CachedBoxedVec, Database}; -use super::{Vecs, sma::SmaVecs, vecs::EmaVecs}; -use crate::{blocks, indexes, internal::PriceWithRatioPerBlock}; +use super::{Vecs, sma::SmaVecs, vecs::EmaPeriodId}; +use crate::{ + blocks, indexes, + internal::{ColumnarPerBlock, LazyColumnPriceWithRatioPerBlock}, +}; + +const EMA_VERSION: Version = Version::ONE; impl Vecs { pub(crate) fn forced_import( @@ -13,32 +18,20 @@ impl Vecs { blocks: &blocks::Vecs, spot_price: &CachedBoxedVec, ) -> Result { - macro_rules! import_ema { - ($name:expr) => { - PriceWithRatioPerBlock::forced_import(db, $name, version, indexes, spot_price)? - }; - } - let sma = SmaVecs::new(version, indexes, &blocks.lookback, spot_price.clone()); - - let ema = EmaVecs { - _1w: import_ema!("price_ema_1w"), - _8d: import_ema!("price_ema_8d"), - _12d: import_ema!("price_ema_12d"), - _13d: import_ema!("price_ema_13d"), - _21d: import_ema!("price_ema_21d"), - _26d: import_ema!("price_ema_26d"), - _1m: import_ema!("price_ema_1m"), - _34d: import_ema!("price_ema_34d"), - _55d: import_ema!("price_ema_55d"), - _89d: import_ema!("price_ema_89d"), - _144d: import_ema!("price_ema_144d"), - _200d: import_ema!("price_ema_200d"), - _1y: import_ema!("price_ema_1y"), - _2y: import_ema!("price_ema_2y"), - _200w: import_ema!("price_ema_200w"), - _4y: import_ema!("price_ema_4y"), - }; + let ema_version = version + EMA_VERSION; + let ema = ColumnarPerBlock::forced_import(db, "price_ema_cents", ema_version, |source| { + EmaPeriodId::series(|period| { + LazyColumnPriceWithRatioPerBlock::new( + &format!("price_ema_{}", period.suffix()), + ema_version, + source, + period, + indexes, + spot_price, + ) + }) + })?; Ok(Self { sma, ema }) } diff --git a/crates/brk_computer/src/market/moving_average/vecs.rs b/crates/brk_computer/src/market/moving_average/vecs.rs index 051ff6bbd..a046b2329 100644 --- a/crates/brk_computer/src/market/moving_average/vecs.rs +++ b/crates/brk_computer/src/market/moving_average/vecs.rs @@ -1,30 +1,216 @@ use brk_traversable::Traversable; -use vecdb::{Rw, StorageMode}; +use brk_types::{Cents, Version}; +use vecdb::{ColumnId, Rw, StorageMode, VecValue}; -use crate::internal::PriceWithRatioPerBlock; +use crate::internal::{ColumnarPerBlock, LazyColumnPriceWithRatioPerBlock}; -#[derive(Traversable)] -pub struct EmaVecs { - pub _1w: PriceWithRatioPerBlock, - pub _8d: PriceWithRatioPerBlock, - pub _12d: PriceWithRatioPerBlock, - pub _13d: PriceWithRatioPerBlock, - pub _21d: PriceWithRatioPerBlock, - pub _26d: PriceWithRatioPerBlock, - pub _1m: PriceWithRatioPerBlock, - pub _34d: PriceWithRatioPerBlock, - pub _55d: PriceWithRatioPerBlock, - pub _89d: PriceWithRatioPerBlock, - pub _144d: PriceWithRatioPerBlock, - pub _200d: PriceWithRatioPerBlock, - pub _1y: PriceWithRatioPerBlock, - pub _2y: PriceWithRatioPerBlock, - pub _200w: PriceWithRatioPerBlock, - pub _4y: PriceWithRatioPerBlock, +const EMA_PERIOD_COUNT: usize = 16; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum EmaPeriodId { + OneWeek, + EightDays, + TwelveDays, + ThirteenDays, + TwentyOneDays, + TwentySixDays, + OneMonth, + ThirtyFourDays, + FiftyFiveDays, + EightyNineDays, + OneHundredFortyFourDays, + TwoHundredDays, + OneYear, + TwoYears, + TwoHundredWeeks, + FourYears, +} + +const EMA_PERIOD_IDS: [EmaPeriodId; EMA_PERIOD_COUNT] = [ + EmaPeriodId::OneWeek, + EmaPeriodId::EightDays, + EmaPeriodId::TwelveDays, + EmaPeriodId::ThirteenDays, + EmaPeriodId::TwentyOneDays, + EmaPeriodId::TwentySixDays, + EmaPeriodId::OneMonth, + EmaPeriodId::ThirtyFourDays, + EmaPeriodId::FiftyFiveDays, + EmaPeriodId::EightyNineDays, + EmaPeriodId::OneHundredFortyFourDays, + EmaPeriodId::TwoHundredDays, + EmaPeriodId::OneYear, + EmaPeriodId::TwoYears, + EmaPeriodId::TwoHundredWeeks, + EmaPeriodId::FourYears, +]; + +impl EmaPeriodId { + pub(super) const fn days(self) -> usize { + match self { + Self::OneWeek => 7, + Self::EightDays => 8, + Self::TwelveDays => 12, + Self::ThirteenDays => 13, + Self::TwentyOneDays => 21, + Self::TwentySixDays => 26, + Self::OneMonth => 30, + Self::ThirtyFourDays => 34, + Self::FiftyFiveDays => 55, + Self::EightyNineDays => 89, + Self::OneHundredFortyFourDays => 144, + Self::TwoHundredDays => 200, + Self::OneYear => 365, + Self::TwoYears => 2 * 365, + Self::TwoHundredWeeks => 200 * 7, + Self::FourYears => 4 * 365, + } + } + + pub(super) const fn suffix(self) -> &'static str { + match self { + Self::OneWeek => "1w", + Self::EightDays => "8d", + Self::TwelveDays => "12d", + Self::ThirteenDays => "13d", + Self::TwentyOneDays => "21d", + Self::TwentySixDays => "26d", + Self::OneMonth => "1m", + Self::ThirtyFourDays => "34d", + Self::FiftyFiveDays => "55d", + Self::EightyNineDays => "89d", + Self::OneHundredFortyFourDays => "144d", + Self::TwoHundredDays => "200d", + Self::OneYear => "1y", + Self::TwoYears => "2y", + Self::TwoHundredWeeks => "200w", + Self::FourYears => "4y", + } + } + + pub(super) fn series(mut create: impl FnMut(Self) -> T) -> EmaVecs { + EmaVecs { + _1w: create(Self::OneWeek), + _8d: create(Self::EightDays), + _12d: create(Self::TwelveDays), + _13d: create(Self::ThirteenDays), + _21d: create(Self::TwentyOneDays), + _26d: create(Self::TwentySixDays), + _1m: create(Self::OneMonth), + _34d: create(Self::ThirtyFourDays), + _55d: create(Self::FiftyFiveDays), + _89d: create(Self::EightyNineDays), + _144d: create(Self::OneHundredFortyFourDays), + _200d: create(Self::TwoHundredDays), + _1y: create(Self::OneYear), + _2y: create(Self::TwoYears), + _200w: create(Self::TwoHundredWeeks), + _4y: create(Self::FourYears), + } + } +} + +impl ColumnId for EmaPeriodId { + type Row + = [T; EMA_PERIOD_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &EMA_PERIOD_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(EMA_PERIOD_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Clone, Traversable)] +pub struct EmaVecs { + pub _1w: T, + pub _8d: T, + pub _12d: T, + pub _13d: T, + pub _21d: T, + pub _26d: T, + pub _1m: T, + pub _34d: T, + pub _55d: T, + pub _89d: T, + pub _144d: T, + pub _200d: T, + pub _1y: T, + pub _2y: T, + pub _200w: T, + pub _4y: T, } #[derive(Traversable)] pub struct Vecs { pub sma: super::sma::SmaVecs, - pub ema: EmaVecs, + pub ema: ColumnarPerBlock< + Cents, + EmaPeriodId, + EmaVecs>, + M, + >, +} + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{EMA_PERIOD_IDS, EmaPeriodId}; + + #[test] + fn ema_columns_match_public_fields() { + assert_eq!(EmaPeriodId::ALL, EMA_PERIOD_IDS); + + let series = EmaPeriodId::series(|period| period); + assert_eq!(series._1w, EmaPeriodId::OneWeek); + assert_eq!(series._8d, EmaPeriodId::EightDays); + assert_eq!(series._12d, EmaPeriodId::TwelveDays); + assert_eq!(series._13d, EmaPeriodId::ThirteenDays); + assert_eq!(series._21d, EmaPeriodId::TwentyOneDays); + assert_eq!(series._26d, EmaPeriodId::TwentySixDays); + assert_eq!(series._1m, EmaPeriodId::OneMonth); + assert_eq!(series._34d, EmaPeriodId::ThirtyFourDays); + assert_eq!(series._55d, EmaPeriodId::FiftyFiveDays); + assert_eq!(series._89d, EmaPeriodId::EightyNineDays); + assert_eq!(series._144d, EmaPeriodId::OneHundredFortyFourDays); + assert_eq!(series._200d, EmaPeriodId::TwoHundredDays); + assert_eq!(series._1y, EmaPeriodId::OneYear); + assert_eq!(series._2y, EmaPeriodId::TwoYears); + assert_eq!(series._200w, EmaPeriodId::TwoHundredWeeks); + assert_eq!(series._4y, EmaPeriodId::FourYears); + } } diff --git a/crates/brk_computer/src/models/bedrock/compute.rs b/crates/brk_computer/src/models/bedrock/compute.rs index 334299661..e23ad1853 100644 --- a/crates/brk_computer/src/models/bedrock/compute.rs +++ b/crates/brk_computer/src/models/bedrock/compute.rs @@ -1,20 +1,30 @@ -use std::{cmp::Ordering, collections::BTreeMap, fs, path::Path}; +use std::{ + cmp::Ordering, + collections::BTreeMap, + fs, + io::{Error, ErrorKind}, + path::Path, +}; -use brk_cohort::{AGE_RANGE_FILTERS, AGE_RANGE_NAMES, CohortContext, TERM_FILTERS, UTXO_ALL_NAME}; +use brk_cohort::{ + AgeRange, AgeRangeId, ByTerm, CohortContext, TERM_FILTERS, TERM_NAMES, UTXO_ALL_NAME, +}; use brk_error::Result; use brk_indexer::Indexer; use brk_types::{Cents, CentsCompact, Date, Day1, Sats, StoredF64, UrpdRaw, UrpdWeight, Version}; -use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecValue, WritableVec}; +use vecdb::{AnyStoredVec, AnyVec, ColumnId, Exit, ReadableVec, VecValue}; use super::{ - STORED_URPD_COHORTS, - vecs::{Levels, MODE_COUNT, MODE_NAMES, ModeVecs, Percentiles, Vecs}, + vecs::{ + LEVEL_IDS, Levels, LossPercentileId, ModeId, ModeVecs, Modes, Percentiles, PriceBandId, + PriceBands, Vecs, WeightedModeId, WeightedModes, + }, weighted_urpd_name, }; use crate::{ distribution, frameworks::{ - coinflow::{self, AGE_COHORT_COUNT, AgeBand, HORIZON_COUNT, HORIZON_DAYS, age_bounds_days}, + coinflow::{self, AgeBand, HORIZON_COUNT, HORIZON_DAYS, age_bounds_days}, cointime, }, indexes, @@ -23,39 +33,65 @@ use crate::{ const MIN_CALIBRATION_DAYS: usize = 365; const WRITE_INTERVAL_DAYS: usize = 100; -const PERCENTILE_COUNT: usize = 5; -const LEVEL_COUNT: usize = 9; -const WEIGHTED_MODE_COUNT: usize = MODE_COUNT - 1; -const STORED_WEIGHT_COUNT: usize = UrpdWeight::WEIGHTED.len(); -const PERCENTILES: [f64; PERCENTILE_COUNT] = [0.95, 0.98, 0.99, 0.995, 0.999]; -const LEVEL_PERCENTILES: [f64; LEVEL_COUNT] = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; +const PERCENTILES: Percentiles = Percentiles { + pct95: 0.95, + pct98: 0.98, + pct99: 0.99, + pct99_5: 0.995, + pct99_9: 0.999, +}; +const LEVEL_PERCENTILES: Levels = Levels { + pct10: 0.1, + pct20: 0.2, + pct30: 0.3, + pct40: 0.4, + pct50: 0.5, + pct60: 0.6, + pct70: 0.7, + pct80: 0.8, + pct90: 0.9, +}; const WEIGHTED_URPD_VERSION: Version = Version::TWO; const WEIGHTED_URPD_VERSION_FILE: &str = "bedrock_urpd.version"; -const RAW_MODE: usize = 0; -const COINTIME_MODE: usize = 1; -const COINFLOW_MODE: usize = 2; -const COINFLOW_HORIZON_START: usize = 3; -const STH_TERM: usize = 0; -const LTH_TERM: usize = 1; -const TERM_COUNT: usize = 2; +type Thresholds = Modes>>; +type ModeWeights = Modes>>; +type WeightedUrpdNames = AggregateCohorts>; -type Thresholds = [Option<[f64; PERCENTILE_COUNT]>; MODE_COUNT]; -type ModeWeights = [Option<[f64; AGE_COHORT_COUNT]>; MODE_COUNT]; -type AllWeightedUrpds = [UrpdRaw; WEIGHTED_MODE_COUNT]; -type TermWeightedUrpds = [[UrpdRaw; STORED_WEIGHT_COUNT]; TERM_COUNT]; -type WeightedUrpdNames = [[String; STORED_WEIGHT_COUNT]; STORED_URPD_COHORTS.len()]; +#[derive(Default)] +struct StoredWeights { + cointime: T, + coinflow: T, +} + +impl StoredWeights { + fn from_fn(mut create: impl FnMut(UrpdWeight) -> T) -> Self { + Self { + cointime: create(UrpdWeight::Cointime), + coinflow: create(UrpdWeight::Coinflow), + } + } + + fn iter(&self) -> impl Iterator { + [&self.cointime, &self.coinflow].into_iter() + } +} + +struct AggregateCohorts { + all: T, + term: ByTerm, +} struct WeightedMasses { - all: [f64; WEIGHTED_MODE_COUNT], - terms: [[f64; STORED_WEIGHT_COUNT]; TERM_COUNT], + all: WeightedModes, + term: ByTerm>, } impl Default for WeightedMasses { fn default() -> Self { Self { - all: [0.0; WEIGHTED_MODE_COUNT], - terms: [[0.0; STORED_WEIGHT_COUNT]; TERM_COUNT], + all: WeightedModes::from_fn(|_| 0.0), + term: ByTerm::default(), } } } @@ -64,48 +100,52 @@ type WeightedUrpd = BTreeMap; struct DayUrpds { raw: UrpdRaw, - all: AllWeightedUrpds, - terms: TermWeightedUrpds, + all: WeightedModes, + term: ByTerm>, } impl DayUrpds { - fn mode(&self, mode: usize) -> &UrpdRaw { - if mode == RAW_MODE { - &self.raw - } else { - &self.all[mode - 1] + fn mode(&self, mode: ModeId) -> &UrpdRaw { + match mode { + ModeId::Raw => &self.raw, + _ => self.all.select(mode.weighted().expect("weighted mode")), } } } +struct ModeResult { + loss_threshold: Percentiles, + prices: PriceBands, +} + struct DayResult { - loss_threshold: [[StoredF64; PERCENTILE_COUNT]; MODE_COUNT], - floor: [[Cents; PERCENTILE_COUNT]; MODE_COUNT], - level: [[Cents; LEVEL_COUNT]; MODE_COUNT], + by_mode: Modes, } impl DayResult { fn from_thresholds(thresholds: &Thresholds) -> Self { Self { - loss_threshold: thresholds.map(|thresholds| { - thresholds - .map(|values| values.map(StoredF64::from)) - .unwrap_or([StoredF64::NAN; PERCENTILE_COUNT]) + by_mode: Modes::from_fn(|mode| ModeResult { + loss_threshold: match thresholds.select(mode) { + Some(values) => Percentiles::from_fn(|percentile| { + StoredF64::from(*percentile.select(values)) + }), + None => Percentiles::from_fn(|_| StoredF64::NAN), + }, + prices: PriceBands::from_fn(|_| Cents::NAN), }), - floor: [[Cents::NAN; PERCENTILE_COUNT]; MODE_COUNT], - level: [[Cents::NAN; LEVEL_COUNT]; MODE_COUNT], } } } struct Calibration { - histories: [Vec; MODE_COUNT], + histories: Modes>, } impl Calibration { fn from_sources( raw: &impl ReadableVec>, - weighted: &[&impl ReadableVec>], + weighted: &WeightedModes<&impl ReadableVec>>, end: usize, ) -> Self where @@ -113,28 +153,30 @@ impl Calibration { U: VecValue, f64: From + From, { - let mut histories = std::array::from_fn(|_| Vec::new()); - histories[RAW_MODE] = collect_loss_history(raw, end); - for (history, source) in histories[1..].iter_mut().zip(weighted) { - *history = collect_loss_history(*source, end); + let mut histories = Modes::from_fn(|_| Vec::new()); + histories.raw = collect_loss_history(raw, end); + for id in WeightedModeId::ALL { + let source = weighted.select(id); + *histories.select_mut(id.mode()) = collect_loss_history(*source, end); } Self { histories } } - fn thresholds(&self, current: &[Option; MODE_COUNT]) -> Thresholds { - std::array::from_fn(|mode| { - (current[mode].is_some() && self.histories[mode].len() >= MIN_CALIBRATION_DAYS).then( - || { - PERCENTILES.map(|percentile| { - quantile(&self.histories[mode], percentile).expect("non-empty history") - }) - }, - ) + fn thresholds(&self, current: &Modes>) -> Thresholds { + Modes::from_fn(|mode| { + let history = self.histories.select(mode); + (current.select(mode).is_some() && history.len() >= MIN_CALIBRATION_DAYS).then(|| { + Percentiles::from_fn(|percentile| { + quantile(history, *percentile.select(&PERCENTILES)).expect("non-empty history") + }) + }) }) } - fn observe(&mut self, shares: [Option; MODE_COUNT]) { - for (history, share) in self.histories.iter_mut().zip(shares) { + fn observe(&mut self, shares: Modes>) { + for mode in ModeId::ALL { + let history = self.histories.select_mut(mode); + let share = *shares.select(mode); if let Some(share) = share { insert_sorted(history, share.clamp(0.0, 1.0)); } @@ -142,79 +184,17 @@ impl Calibration { } } -impl Percentiles { - fn as_mut_array(&mut self) -> [&mut T; PERCENTILE_COUNT] { - [ - &mut self.pct95, - &mut self.pct98, - &mut self.pct99, - &mut self.pct99_5, - &mut self.pct99_9, - ] - } -} - -impl Levels { - fn as_mut_array(&mut self) -> [&mut T; LEVEL_COUNT] { - [ - &mut self.pct10, - &mut self.pct20, - &mut self.pct30, - &mut self.pct40, - &mut self.pct50, - &mut self.pct60, - &mut self.pct70, - &mut self.pct80, - &mut self.pct90, - ] - } -} - impl ModeVecs { - fn stored_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs: Vec<&mut dyn AnyStoredVec> = - Vec::with_capacity(2 * PERCENTILE_COUNT + LEVEL_COUNT); - vecs.extend( - self.loss_threshold - .as_mut_array() - .into_iter() - .map(|vec| &mut vec.day1 as &mut dyn AnyStoredVec), - ); - vecs.extend( - self.floor - .as_mut_array() - .into_iter() - .map(|vec| &mut vec.cents.day1 as &mut dyn AnyStoredVec), - ); - vecs.extend( - self.level - .as_mut_array() - .into_iter() - .map(|vec| &mut vec.cents.day1 as &mut dyn AnyStoredVec), - ); - vecs + fn stored_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; 2] { + [self.loss_threshold.stored_mut(), self.prices.stored_mut()] } - fn push( - &mut self, - loss_threshold: [StoredF64; PERCENTILE_COUNT], - floor: [Cents; PERCENTILE_COUNT], - level: [Cents; LEVEL_COUNT], - ) { - for (vec, value) in self - .loss_threshold - .as_mut_array() - .into_iter() - .zip(loss_threshold) - { - vec.day1.push(value); - } - for (vec, value) in self.floor.as_mut_array().into_iter().zip(floor) { - vec.cents.day1.push(value); - } - for (vec, value) in self.level.as_mut_array().into_iter().zip(level) { - vec.cents.day1.push(value); - } + fn push(&mut self, result: &ModeResult) { + self.loss_threshold.push(LossPercentileId::from_fn(|id| { + *id.select(&result.loss_threshold) + })); + self.prices + .push(PriceBandId::from_fn(|id| *id.select(&result.prices))); } } @@ -228,27 +208,23 @@ impl Vecs { coinflow: &coinflow::Vecs, exit: &Exit, ) -> Result<()> { - let cointime_wakefulness: Vec<_> = cointime - .age_range - .iter() - .map(|cohort| &cohort.wakefulness.day1) - .collect(); - let age_supplies: Vec<_> = distribution - .utxo_cohorts - .age_range - .iter() - .map(|cohort| &cohort.metrics.supply.total.sats.day1) - .collect(); - let coinflow_mobility: Vec<_> = coinflow - .age_range - .iter() - .map(|cohort| &cohort.mobility.day1.0) - .collect(); - let coinflow_spending_rate: Vec<_> = coinflow - .age_range - .iter() - .map(|cohort| &cohort.spending_rate.day1) - .collect(); + let cointime_wakefulness = + AgeRange::from_fn(|id| &id.select(&cointime.age_range.activity.wakefulness).day1); + let age_supplies = AgeRange::from_fn(|id| { + &id.select(&distribution.utxo_cohorts.age_range) + .metrics + .supply + .total + .sats + .day1 + }); + let coinflow_mobility = AgeRange::from_fn(|id| { + &id.select(&coinflow.age_range.spending_exposure.mobility) + .day1 + .0 + }); + let coinflow_spending_rate = + AgeRange::from_fn(|id| &id.select(&coinflow.age_range.spending_rate).day1); let raw_loss_share = &distribution .utxo_cohorts .all @@ -257,20 +233,17 @@ impl Vecs { .supply_in_loss_share .ppm .day1; - let weighted_loss_shares: Vec<_> = [ - &cointime.supply.active_supply_in_loss_share.day1, - &coinflow.all.supply_in_loss_share.day1, - ] - .into_iter() - .chain( - coinflow - .all - .horizon - .iter() - .map(|horizon| &horizon.supply_in_loss_share.day1), - ) - .collect(); - debug_assert_eq!(weighted_loss_shares.len(), MODE_COUNT - 1); + let weighted_loss_shares = WeightedModes { + cointime: &cointime.supply.active_supply_in_loss_share.day1, + coinflow: &coinflow.all.supply_in_loss_share.day1, + coinflow_8y: &coinflow.all.horizon._8y.supply_in_loss_share.day1, + coinflow_4y: &coinflow.all.horizon._4y.supply_in_loss_share.day1, + coinflow_2y: &coinflow.all.horizon._2y.supply_in_loss_share.day1, + coinflow_1y: &coinflow.all.horizon._1y.supply_in_loss_share.day1, + coinflow_6m: &coinflow.all.horizon._6m.supply_in_loss_share.day1, + coinflow_3m: &coinflow.all.horizon._3m.supply_in_loss_share.day1, + coinflow_1m: &coinflow.all.horizon._1m.supply_in_loss_share.day1, + }; let weighted_urpd_names = weighted_urpd_names(); let weighted_urpd_source_version: Version = std::iter::once(WEIGHTED_URPD_VERSION) @@ -374,12 +347,10 @@ impl Vecs { } calibration.observe(loss_shares); - for (mode, output) in self.modes.as_mut_array().into_iter().enumerate() { - output.push( - result.loss_threshold[mode], - result.floor[mode], - result.level[mode], - ); + for mode in ModeId::ALL { + let output = self.modes.select_mut(mode); + let mode_result = result.by_mode.select(mode); + output.push(mode_result); } if (day_index + 1).is_multiple_of(WRITE_INTERVAL_DAYS) || day_index + 1 == source_end { @@ -400,17 +371,12 @@ impl Vecs { Ok(()) } - fn stored_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs = Vec::with_capacity(MODE_COUNT * (2 * PERCENTILE_COUNT + LEVEL_COUNT)); - for mode in self.modes.as_mut_array() { - vecs.extend(mode.stored_vecs_mut()); - } - vecs + fn stored_vecs_mut(&mut self) -> impl Iterator { + self.modes.iter_mut().flat_map(ModeVecs::stored_vecs_mut) } fn minimum_len(&mut self) -> usize { self.stored_vecs_mut() - .into_iter() .map(|vec| vec.len()) .min() .unwrap_or_default() @@ -447,18 +413,19 @@ where fn collect_loss_shares( raw: &impl ReadableVec>, - weighted: &[&impl ReadableVec>], + weighted: &WeightedModes<&impl ReadableVec>>, day: Day1, -) -> [Option; MODE_COUNT] +) -> Modes> where T: VecValue, U: VecValue, f64: From + From, { - let mut shares = [None; MODE_COUNT]; - shares[RAW_MODE] = collect_loss_share(raw, day); - for (share, source) in shares[1..].iter_mut().zip(weighted) { - *share = collect_loss_share(*source, day); + let mut shares = Modes::from_fn(|_| None); + shares.raw = collect_loss_share(raw, day); + for id in WeightedModeId::ALL { + let source = weighted.select(id); + *shares.select_mut(id.mode()) = collect_loss_share(*source, day); } shares } @@ -478,24 +445,27 @@ fn recompute_day(indexer: &Indexer, indexes: &indexes::Vecs) -> Option { fn mode_weights( day: Day1, - age_supplies: &[&impl ReadableVec>], - cointime_wakefulness: &[&impl ReadableVec>], - coinflow_mobility: &[&impl ReadableVec>], - coinflow_spending_rate: &[&impl ReadableVec>], - bounds: &[AgeBand; AGE_COHORT_COUNT], + age_supplies: &AgeRange<&impl ReadableVec>>, + cointime_wakefulness: &AgeRange<&impl ReadableVec>>, + coinflow_mobility: &AgeRange<&impl ReadableVec>>, + coinflow_spending_rate: &AgeRange<&impl ReadableVec>>, + bounds: &AgeRange, ) -> ModeWeights { - debug_assert_eq!(COINFLOW_HORIZON_START + HORIZON_COUNT, MODE_COUNT); - let mut weights = [None; MODE_COUNT]; - weights[RAW_MODE] = Some([1.0; AGE_COHORT_COUNT]); - weights[COINTIME_MODE] = collect_age_values(cointime_wakefulness, age_supplies, day) - .map(|values| values.map(|v| v.clamp(0.0, 1.0))); - weights[COINFLOW_MODE] = collect_age_values(coinflow_mobility, age_supplies, day) - .map(|values| values.map(|v| v.clamp(0.0, 1.0))); + debug_assert_eq!(WeightedModeId::COINFLOW_HORIZONS.len(), HORIZON_COUNT); + let mut weights = Modes::from_fn(|_| None); + weights.raw = Some(AgeRange::from_fn(|_| 1.0)); + weights.cointime = collect_age_values(cointime_wakefulness, age_supplies, day) + .map(|values| AgeRange::from_fn(|id| (*id.select(&values)).clamp(0.0, 1.0))); + weights.coinflow = collect_age_values(coinflow_mobility, age_supplies, day) + .map(|values| AgeRange::from_fn(|id| (*id.select(&values)).clamp(0.0, 1.0))); if let Some(hazards) = collect_age_values(coinflow_spending_rate, age_supplies, day) { - let hazards = hazards.map(|value| value.max(0.0)); - for (offset, horizon) in HORIZON_DAYS.iter().copied().enumerate() { - weights[COINFLOW_HORIZON_START + offset] = Some(std::array::from_fn(|age| { + let hazards = AgeRange::from_fn(|id| (*id.select(&hazards)).max(0.0)); + for (id, horizon) in WeightedModeId::COINFLOW_HORIZONS + .into_iter() + .zip(HORIZON_DAYS.iter().copied()) + { + *weights.select_mut(id.mode()) = Some(AgeRange::from_fn(|age| { coinflow::horizon_mobility(&hazards, age, horizon, bounds) })); } @@ -504,22 +474,19 @@ fn mode_weights( } fn collect_age_values( - sources: &[&impl ReadableVec>], - supplies: &[&impl ReadableVec>], + sources: &AgeRange<&impl ReadableVec>>, + supplies: &AgeRange<&impl ReadableVec>>, day: Day1, -) -> Option<[f64; AGE_COHORT_COUNT]> +) -> Option> where T: VecValue, f64: From, { - if sources.len() != AGE_COHORT_COUNT || supplies.len() != AGE_COHORT_COUNT { - return None; - } - - let mut values = [0.0; AGE_COHORT_COUNT]; - for ((value, source), supply) in values.iter_mut().zip(sources).zip(supplies) { - let supply = supply.collect_one(day).flatten()?; - *value = resolve_age_value(source.collect_one(day).flatten(), supply)?; + let mut values = AgeRange::default(); + for &id in AgeRangeId::ALL { + let supply = id.select(supplies).collect_one(day).flatten()?; + *id.select_mut(&mut values) = + resolve_age_value(id.select(sources).collect_one(day).flatten(), supply)?; } Some(values) } @@ -544,11 +511,15 @@ fn read_weighted_urpd_version(states_path: &Path) -> Result> { } fn reset_weighted_urpds(states_path: &Path, weighted_names: &WeightedUrpdNames) -> Result<()> { - for name in weighted_names.iter().flatten() { + for name in weighted_names + .all + .iter() + .chain(weighted_names.term.iter().flat_map(StoredWeights::iter)) + { remove_urpd_dir(states_path, name)?; } - for mode in &MODE_NAMES[COINFLOW_HORIZON_START..] { - remove_urpd_dir(states_path, &format!("bedrock_{mode}"))?; + for id in WeightedModeId::COINFLOW_HORIZONS { + remove_urpd_dir(states_path, &format!("bedrock_{}", id.mode().name()))?; } Ok(()) } @@ -557,8 +528,8 @@ fn remove_urpd_dir(states_path: &Path, name: &str) -> Result<()> { let path = UrpdRaw::dir(states_path, name); match fs::remove_dir_all(&path) { Ok(()) => Ok(()), - Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()), - Err(error) => Err(std::io::Error::new( + Err(error) if error.kind() == ErrorKind::NotFound => Ok(()), + Err(error) => Err(Error::new( error.kind(), format!("Cannot reset URPD '{}': {error}", path.display()), ) @@ -574,21 +545,13 @@ fn read_day_urpds( let raw = UrpdRaw::read(distribution_states_path, UTXO_ALL_NAME.id, date)?; let mut weighted = WeightedUrpd::new(); - for (age, (name, filter)) in AGE_RANGE_NAMES - .iter() - .zip(AGE_RANGE_FILTERS.iter()) - .enumerate() - { - let cohort = CohortContext::Utxo.prefixed(name.id); + for &age in AgeRangeId::ALL { + let cohort = CohortContext::Utxo.prefixed(age.name().id); let source = UrpdRaw::read(distribution_states_path, &cohort, date)?; - let term = if TERM_FILTERS.short.includes(filter) { - STH_TERM - } else { - LTH_TERM - }; + let is_short = TERM_FILTERS.short.includes(age.filter()); for (price, sats) in source.map { - add_weighted_entry(&mut weighted, price, sats, age, term, weights); + add_weighted_entry(&mut weighted, price, sats, age, is_short, weights); } } @@ -600,16 +563,16 @@ fn build_current_day_urpds(utxos: &distribution::UTXOCohorts, weights: &ModeWeig } fn build_urpds_from_age_entries( - entries: impl IntoIterator, + entries: impl IntoIterator, weights: &ModeWeights, ) -> DayUrpds { let mut raw = UrpdRaw::default(); let mut weighted = WeightedUrpd::new(); - for (age, is_sth, price, sats) in entries { + for (age, price, sats) in entries { *raw.map.entry(price).or_default() += sats; - let term = if is_sth { STH_TERM } else { LTH_TERM }; - add_weighted_entry(&mut weighted, price, sats, age, term, weights); + let is_short = TERM_FILTERS.short.includes(age.filter()); + add_weighted_entry(&mut weighted, price, sats, age, is_short, weights); } finalize_day_urpds(raw, weighted) @@ -619,46 +582,64 @@ fn add_weighted_entry( weighted: &mut WeightedUrpd, price: CentsCompact, sats: Sats, - age: usize, - term: usize, + age: AgeRangeId, + is_short: bool, weights: &ModeWeights, ) { let mass = u64::from(sats) as f64; let bucket = weighted.entry(price).or_default(); - for (mode, (all, weights)) in bucket.all.iter_mut().zip(&weights[1..]).enumerate() { - if let Some(weights) = weights { - let weighted_mass = mass * weights[age]; - *all += weighted_mass; - if mode < STORED_WEIGHT_COUNT { - bucket.terms[term][mode] += weighted_mass; + for id in WeightedModeId::ALL { + let mode = id.mode(); + if let Some(mode_weights) = weights.select(mode) { + let weighted_mass = mass * *age.select(mode_weights); + *bucket.all.select_mut(id) += weighted_mass; + let term = if is_short { + &mut bucket.term.short + } else { + &mut bucket.term.long + }; + match mode { + ModeId::Cointime => term.cointime += weighted_mass, + ModeId::Coinflow => term.coinflow += weighted_mass, + _ => {} } } } } fn finalize_day_urpds(raw: UrpdRaw, weighted: WeightedUrpd) -> DayUrpds { - let mut all: AllWeightedUrpds = std::array::from_fn(|_| UrpdRaw::default()); - let mut terms: TermWeightedUrpds = - std::array::from_fn(|_| std::array::from_fn(|_| UrpdRaw::default())); + let mut all = WeightedModes::from_fn(|_| UrpdRaw::default()); + let mut term = ByTerm::>::default(); for (price, masses) in weighted { - for (distribution, mass) in all.iter_mut().zip(masses.all) { - let sats = floor_weighted_sats(mass); + for id in WeightedModeId::ALL { + let distribution = all.select_mut(id); + let sats = floor_weighted_sats(*masses.all.select(id)); if sats != Sats::ZERO { distribution.map.insert(price, sats); } } - for (distributions, masses) in terms.iter_mut().zip(masses.terms) { - for (distribution, mass) in distributions.iter_mut().zip(masses) { - let sats = floor_weighted_sats(mass); - if sats != Sats::ZERO { - distribution.map.insert(price, sats); - } - } - } + insert_stored_weighted_masses(price, &mut term.short, &masses.term.short); + insert_stored_weighted_masses(price, &mut term.long, &masses.term.long); } - DayUrpds { raw, all, terms } + DayUrpds { raw, all, term } +} + +fn insert_stored_weighted_masses( + price: CentsCompact, + distributions: &mut StoredWeights, + masses: &StoredWeights, +) { + insert_weighted_mass(price, &mut distributions.cointime, masses.cointime); + insert_weighted_mass(price, &mut distributions.coinflow, masses.coinflow); +} + +fn insert_weighted_mass(price: CentsCompact, distribution: &mut UrpdRaw, mass: f64) { + let sats = floor_weighted_sats(mass); + if sats != Sats::ZERO { + distribution.map.insert(price, sats); + } } fn floor_weighted_sats(mass: f64) -> Sats { @@ -672,37 +653,69 @@ fn write_weighted_day_urpds( date: Date, urpds: &DayUrpds, ) -> Result<()> { - let distributions = [ - &urpds.all[..STORED_WEIGHT_COUNT], - &urpds.terms[STH_TERM], - &urpds.terms[LTH_TERM], - ]; - for (names, distributions) in weighted_names.iter().zip(distributions) { - for (name, distribution) in names.iter().zip(distributions) { - UrpdRaw::write( - models_states_path, - name, - date, - distribution.map.iter().map(|(&price, &sats)| (price, sats)), - )?; - } - } - Ok(()) + write_stored_weights( + models_states_path, + &weighted_names.all, + date, + &urpds.all.cointime, + &urpds.all.coinflow, + )?; + write_stored_weights( + models_states_path, + &weighted_names.term.short, + date, + &urpds.term.short.cointime, + &urpds.term.short.coinflow, + )?; + write_stored_weights( + models_states_path, + &weighted_names.term.long, + date, + &urpds.term.long.cointime, + &urpds.term.long.coinflow, + ) +} + +fn write_stored_weights( + models_states_path: &Path, + names: &StoredWeights, + date: Date, + cointime: &UrpdRaw, + coinflow: &UrpdRaw, +) -> Result<()> { + write_weighted_urpd(models_states_path, &names.cointime, date, cointime)?; + write_weighted_urpd(models_states_path, &names.coinflow, date, coinflow) +} + +fn write_weighted_urpd( + models_states_path: &Path, + name: &str, + date: Date, + distribution: &UrpdRaw, +) -> Result<()> { + UrpdRaw::write( + models_states_path, + name, + date, + distribution.map.iter().map(|(&price, &sats)| (price, sats)), + ) } fn weighted_urpd_names() -> WeightedUrpdNames { - std::array::from_fn(|cohort| { - std::array::from_fn(|mode| { - weighted_urpd_name(UrpdWeight::WEIGHTED[mode], STORED_URPD_COHORTS[cohort].id) - }) - }) + AggregateCohorts { + all: StoredWeights::from_fn(|weight| weighted_urpd_name(weight, UTXO_ALL_NAME.id)), + term: ByTerm { + short: StoredWeights::from_fn(|weight| weighted_urpd_name(weight, TERM_NAMES.short.id)), + long: StoredWeights::from_fn(|weight| weighted_urpd_name(weight, TERM_NAMES.long.id)), + }, + } } fn evaluate_day(urpds: &DayUrpds, thresholds: &Thresholds, result: &mut DayResult) { - for mode in 0..MODE_COUNT { + for mode in ModeId::ALL { let urpd = urpds.mode(mode); let denominator = urpd.map.values().copied().map(u64::from).sum::(); - let Some(thresholds) = thresholds[mode] else { + let Some(thresholds) = thresholds.select(mode) else { continue; }; if denominator == 0 @@ -715,15 +728,16 @@ fn evaluate_day(urpds: &DayUrpds, thresholds: &Thresholds, result: &mut DayResul } let mut remaining_loss = denominator; - let mut floors = [Cents::NAN; PERCENTILE_COUNT]; + let mut floors = Percentiles::from_fn(|_| Cents::NAN); let mut p95_floor = None; for (price, sats) in &urpd.map { remaining_loss -= u64::from(*sats); let remaining_share = remaining_loss as f64 / denominator as f64; - for percentile in 0..PERCENTILE_COUNT { - if floors[percentile].is_nan() && remaining_share <= thresholds[percentile] { - floors[percentile] = Cents::from(*price); - if percentile == 0 { + for &percentile in LossPercentileId::ALL { + let floor = percentile.select_mut(&mut floors); + if floor.is_nan() && remaining_share <= *percentile.select(thresholds) { + *floor = Cents::from(*price); + if percentile == LossPercentileId::Pct95 { p95_floor = Some(*price); } } @@ -732,15 +746,16 @@ fn evaluate_day(urpds: &DayUrpds, thresholds: &Thresholds, result: &mut DayResul break; } } - result.floor[mode] = floors; + let mode_result = result.by_mode.select_mut(mode); + mode_result.prices.floor = floors; if let Some(p95_floor) = p95_floor { - result.level[mode] = conditional_levels(urpd, p95_floor); + mode_result.prices.level = conditional_levels(urpd, p95_floor); } } } -fn conditional_levels(urpd: &UrpdRaw, lower: CentsCompact) -> [Cents; LEVEL_COUNT] { - let mut levels = [Cents::NAN; LEVEL_COUNT]; +fn conditional_levels(urpd: &UrpdRaw, lower: CentsCompact) -> Levels { + let mut levels = Levels::from_fn(|_| Cents::NAN); let total = urpd .map .range(lower..) @@ -751,20 +766,20 @@ fn conditional_levels(urpd: &UrpdRaw, lower: CentsCompact) -> [Cents; LEVEL_COUN } let mut cumulative = 0_u64; - let mut percentile = 0; + let mut percentiles = LEVEL_IDS.iter().copied().peekable(); for (price, sats) in urpd.map.range(lower..) { let sats = u64::from(*sats); if sats == 0 { continue; } cumulative += sats; - while percentile < LEVEL_COUNT - && cumulative as f64 >= total as f64 * LEVEL_PERCENTILES[percentile] + while let Some(percentile) = percentiles.peek().copied() + && cumulative as f64 >= total as f64 * *percentile.select(&LEVEL_PERCENTILES) { - levels[percentile] = Cents::from(*price); - percentile += 1; + *percentile.select_mut(&mut levels) = Cents::from(*price); + percentiles.next(); } - if percentile == LEVEL_COUNT { + if percentiles.peek().is_none() { break; } } @@ -800,8 +815,11 @@ mod tests { .collect::>(); DayUrpds { raw: UrpdRaw { map: map.clone() }, - all: std::array::from_fn(|_| UrpdRaw { map: map.clone() }), - terms: std::array::from_fn(|_| std::array::from_fn(|_| UrpdRaw { map: map.clone() })), + all: WeightedModes::from_fn(|_| UrpdRaw { map: map.clone() }), + term: ByTerm { + short: StoredWeights::from_fn(|_| UrpdRaw { map: map.clone() }), + long: StoredWeights::from_fn(|_| UrpdRaw { map: map.clone() }), + }, } } @@ -835,38 +853,39 @@ mod tests { fn daily_loss_share_calibrates_the_floor() { let urpds = repeated_day_urpds([(100, 50), (200, 50)]); let mut calibration = Calibration { - histories: std::array::from_fn(|_| vec![0.5; MIN_CALIBRATION_DAYS]), + histories: Modes::from_fn(|_| vec![0.5; MIN_CALIBRATION_DAYS]), }; - let shares = [Some(0.5); MODE_COUNT]; + let shares = Modes::from_fn(|_| Some(0.5)); let thresholds = calibration.thresholds(&shares); let mut result = DayResult::from_thresholds(&thresholds); evaluate_day(&urpds, &thresholds, &mut result); calibration.observe(shares); + let result = &result.by_mode.coinflow; assert_eq!( - result.loss_threshold[COINFLOW_MODE], - [StoredF64::from(0.5); PERCENTILE_COUNT] + result.loss_threshold, + Percentiles::from_fn(|_| StoredF64::from(0.5)) ); assert_eq!( - result.floor[COINFLOW_MODE], - [Cents::new(100); PERCENTILE_COUNT] + result.prices.floor, + Percentiles::from_fn(|_| Cents::new(100)) ); assert_eq!( - result.level[COINFLOW_MODE], - [ - Cents::new(100), - Cents::new(100), - Cents::new(100), - Cents::new(100), - Cents::new(100), - Cents::new(200), - Cents::new(200), - Cents::new(200), - Cents::new(200), - ] + result.prices.level, + Levels { + pct10: Cents::new(100), + pct20: Cents::new(100), + pct30: Cents::new(100), + pct40: Cents::new(100), + pct50: Cents::new(100), + pct60: Cents::new(200), + pct70: Cents::new(200), + pct80: Cents::new(200), + pct90: Cents::new(200), + } ); assert_eq!( - calibration.histories[COINFLOW_MODE].len(), + calibration.histories.coinflow.len(), MIN_CALIBRATION_DAYS + 1 ); } @@ -875,33 +894,31 @@ mod tests { fn zero_cost_distribution_stays_missing() { let urpds = repeated_day_urpds([(0, 100)]); let mut calibration = Calibration { - histories: std::array::from_fn(|_| vec![0.5; MIN_CALIBRATION_DAYS]), + histories: Modes::from_fn(|_| vec![0.5; MIN_CALIBRATION_DAYS]), }; - let shares = [Some(1.0); MODE_COUNT]; + let shares = Modes::from_fn(|_| Some(1.0)); let thresholds = calibration.thresholds(&shares); let mut result = DayResult::from_thresholds(&thresholds); evaluate_day(&urpds, &thresholds, &mut result); calibration.observe(shares); + let result = &result.by_mode.raw; - assert_eq!(result.loss_threshold[RAW_MODE][0], StoredF64::from(0.5)); - assert!(result.floor[RAW_MODE][0].is_nan()); - assert_eq!( - calibration.histories[RAW_MODE].len(), - MIN_CALIBRATION_DAYS + 1 - ); + assert_eq!(result.loss_threshold.pct95, StoredF64::from(0.5)); + assert!(result.prices.floor.pct95.is_nan()); + assert_eq!(calibration.histories.raw.len(), MIN_CALIBRATION_DAYS + 1); } #[test] fn missing_framework_share_does_not_update_history() { let mut calibration = Calibration { - histories: std::array::from_fn(|_| Vec::new()), + histories: Modes::from_fn(|_| Vec::new()), }; - let shares = [None; MODE_COUNT]; + let shares = Modes::from_fn(|_| None); let thresholds = calibration.thresholds(&shares); calibration.observe(shares); - assert_eq!(thresholds, [None; MODE_COUNT]); - assert!(calibration.histories[RAW_MODE].is_empty()); + assert!(thresholds.iter().all(Option::is_none)); + assert!(calibration.histories.raw.is_empty()); } #[test] @@ -913,18 +930,24 @@ mod tests { let weighted = BTreeMap::from([( CentsCompact::new(100), WeightedMasses { - all: [summed_mass; WEIGHTED_MODE_COUNT], - terms: [[0.6; STORED_WEIGHT_COUNT]; TERM_COUNT], + all: WeightedModes::from_fn(|_| summed_mass), + term: ByTerm { + short: StoredWeights::from_fn(|_| 0.6), + long: StoredWeights::from_fn(|_| 0.6), + }, }, )]); let urpds = finalize_day_urpds(UrpdRaw::default(), weighted); assert_eq!( - urpds.all[COINTIME_MODE - 1].map[&CentsCompact::new(100)], + urpds.all.cointime.map[&CentsCompact::new(100)], Sats::from(1_u64) ); assert!( - !urpds.terms[STH_TERM][COINTIME_MODE - 1] + !urpds + .term + .short + .cointime .map .contains_key(&CentsCompact::new(100)) ); @@ -932,26 +955,20 @@ mod tests { #[test] fn current_day_entries_build_raw_and_weighted_urpds() { - let weights: ModeWeights = std::array::from_fn(|_| Some([0.5; AGE_COHORT_COUNT])); + let weights = Modes::from_fn(|_| Some(AgeRange::from_fn(|_| 0.5))); let price = CentsCompact::new(100); let urpds = build_urpds_from_age_entries( [ - (0, true, price, Sats::from(3_u64)), - (1, false, price, Sats::from(5_u64)), + (AgeRangeId::Under1H, price, Sats::from(3_u64)), + (AgeRangeId::From5MTo6M, price, Sats::from(5_u64)), ], &weights, ); assert_eq!(urpds.raw.map[&price], Sats::from(8_u64)); - assert_eq!(urpds.all[COINTIME_MODE - 1].map[&price], Sats::from(4_u64)); - assert_eq!( - urpds.terms[STH_TERM][COINTIME_MODE - 1].map[&price], - Sats::from(1_u64) - ); - assert_eq!( - urpds.terms[LTH_TERM][COINTIME_MODE - 1].map[&price], - Sats::from(2_u64) - ); + assert_eq!(urpds.all.cointime.map[&price], Sats::from(4_u64)); + assert_eq!(urpds.term.short.cointime.map[&price], Sats::from(1_u64)); + assert_eq!(urpds.term.long.cointime.map[&price], Sats::from(2_u64)); } #[test] @@ -963,10 +980,10 @@ mod tests { let date = Date::new(2026, 8, 4); let names = weighted_urpd_names(); let expected = repeated_day_urpds([(100, 21), (200, 34)]); - assert_eq!(names[0][0], "bedrock_cointime"); - assert_eq!(names[0][1], "bedrock_coinflow"); - assert_eq!(names[1][0], "bedrock_cointime_sth"); - assert_eq!(names[2][1], "bedrock_coinflow_lth"); + assert_eq!(names.all.cointime, "bedrock_cointime"); + assert_eq!(names.all.coinflow, "bedrock_coinflow"); + assert_eq!(names.term.short.cointime, "bedrock_cointime_sth"); + assert_eq!(names.term.long.coinflow, "bedrock_coinflow_lth"); UrpdRaw::write( &distribution_states, @@ -977,20 +994,18 @@ mod tests { .unwrap(); write_weighted_day_urpds(&models_states, &names, date, &expected).unwrap(); - for (cohort, names) in names.iter().enumerate() { - let expected_distributions: &[UrpdRaw] = match cohort { - 0 => &expected.all[..STORED_WEIGHT_COUNT], - 1 => &expected.terms[STH_TERM], - 2 => &expected.terms[LTH_TERM], - _ => unreachable!(), - }; - for (name, expected) in names.iter().zip(expected_distributions) { - assert_eq!( - UrpdRaw::read(&models_states, name, date).unwrap().map, - expected.map - ); - } - } + let assert_stored = |name: &str, expected: &UrpdRaw| { + assert_eq!( + UrpdRaw::read(&models_states, name, date).unwrap().map, + expected.map + ); + }; + assert_stored(&names.all.cointime, &expected.all.cointime); + assert_stored(&names.all.coinflow, &expected.all.coinflow); + assert_stored(&names.term.short.cointime, &expected.term.short.cointime); + assert_stored(&names.term.short.coinflow, &expected.term.short.coinflow); + assert_stored(&names.term.long.cointime, &expected.term.long.cointime); + assert_stored(&names.term.long.coinflow, &expected.term.long.coinflow); assert!(!UrpdRaw::path(&models_states, "bedrock_raw", date).exists()); assert!(!UrpdRaw::path(&models_states, "bedrock_coinflow_8y", date).exists()); @@ -1006,8 +1021,9 @@ mod tests { assert!(!UrpdRaw::path(&models_states, "bedrock_coinflow_8y", date).exists()); assert!( names + .all .iter() - .flatten() + .chain(names.term.iter().flat_map(StoredWeights::iter)) .all(|name| !UrpdRaw::path(&models_states, name, date).exists()) ); diff --git a/crates/brk_computer/src/models/bedrock/import.rs b/crates/brk_computer/src/models/bedrock/import.rs index 05d080151..db736a9db 100644 --- a/crates/brk_computer/src/models/bedrock/import.rs +++ b/crates/brk_computer/src/models/bedrock/import.rs @@ -1,84 +1,66 @@ use std::path::PathBuf; use brk_error::Result; -use brk_types::{StoredF64, Version}; +use brk_types::Version; use vecdb::Database; use super::{ - price::Price, - vecs::{Levels, ModeVecs, Modes, Percentiles, Vecs}, + price::LazyColumnPrice, + vecs::{LossPercentileId, ModeVecs, Modes, PriceBandId, Vecs}, }; use crate::{ indexes, - internal::{DailyMappings, DailyMetric}, + internal::{ColumnarDailyMetric, DailyMappings, LazyColumnDailyMetric}, }; -const VERSION: Version = Version::new(4); +const VERSION: Version = Version::new(5); -fn import_percentiles(mut import: impl FnMut(&str) -> Result) -> Result> { - Ok(Percentiles { - pct95: import("pct95")?, - pct98: import("pct98")?, - pct99: import("pct99")?, - pct99_5: import("pct99_5")?, - pct99_9: import("pct99_9")?, - }) -} +impl ModeVecs { + fn forced_import( + db: &Database, + name: &str, + version: Version, + mappings: &DailyMappings, + ) -> Result { + let loss_threshold = ColumnarDailyMetric::forced_import( + db, + &format!("{name}_loss_thresholds"), + version, + |source| { + LossPercentileId::series(|percentile| { + LazyColumnDailyMetric::new( + &format!("{name}_loss_threshold_{}", percentile.suffix()), + version, + source, + percentile, + mappings, + ) + }) + }, + )?; -fn import_levels(mut import: impl FnMut(&str) -> Result) -> Result> { - Ok(Levels { - pct10: import("pct10")?, - pct20: import("pct20")?, - pct30: import("pct30")?, - pct40: import("pct40")?, - pct50: import("pct50")?, - pct60: import("pct60")?, - pct70: import("pct70")?, - pct80: import("pct80")?, - pct90: import("pct90")?, - }) -} + let prices = ColumnarDailyMetric::forced_import( + db, + &format!("{name}_price_bands"), + version, + |source| { + PriceBandId::series(|band| { + LazyColumnPrice::new( + &format!("{name}_{}", band.suffix()), + version, + source, + band, + mappings, + ) + }) + }, + )?; -fn import_ratio( - db: &Database, - name: &str, - version: Version, - mappings: &DailyMappings, -) -> Result> { - DailyMetric::forced_import(db, name, version, mappings) -} - -fn import_price( - db: &Database, - name: &str, - version: Version, - mappings: &DailyMappings, -) -> Result { - Price::forced_import(db, name, version, mappings) -} - -fn import_mode( - db: &Database, - name: &str, - version: Version, - mappings: &DailyMappings, -) -> Result { - Ok(ModeVecs { - loss_threshold: import_percentiles(|percentile| { - import_ratio( - db, - &format!("{name}_loss_threshold_{percentile}"), - version, - mappings, - ) - })?, - floor: import_percentiles(|percentile| { - import_price(db, &format!("{name}_floor_{percentile}"), version, mappings) - })?, - level: import_levels(|percentile| { - import_price(db, &format!("{name}_level_{percentile}"), version, mappings) - })?, - }) + Ok(Self { + loss_threshold, + prices, + }) + } } impl Vecs { @@ -93,8 +75,9 @@ impl Vecs { Ok(Self { states_path, - modes: Modes::try_from_fn(|name| { - import_mode(db, &format!("bedrock_{name}"), version, &mappings) + modes: Modes::try_from_fn(|mode| { + let name = mode.name(); + ModeVecs::forced_import(db, &format!("bedrock_{name}"), version, &mappings) })?, }) } diff --git a/crates/brk_computer/src/models/bedrock/mod.rs b/crates/brk_computer/src/models/bedrock/mod.rs index ad4eab946..2d21c51fc 100644 --- a/crates/brk_computer/src/models/bedrock/mod.rs +++ b/crates/brk_computer/src/models/bedrock/mod.rs @@ -5,18 +5,16 @@ mod vecs; use std::path::PathBuf; -use brk_cohort::{AGE_RANGE_NAMES, UTXO_AGGREGATE_NAMES, UTXO_ALL_NAME}; +use brk_cohort::{AgeRangeId, UTXO_ALL_NAME}; use brk_error::Result; use brk_types::{Cohort, Date, Day1, UrpdRaw, UrpdWeight}; -use vecdb::{ReadableVec, StorageMode}; +use vecdb::{ColumnId, ReadableVec, StorageMode}; use self::compute::resolve_age_value; use crate::Computer; pub use vecs::Vecs; -pub(crate) const STORED_URPD_COHORTS: [brk_cohort::CohortName; 3] = UTXO_AGGREGATE_NAMES; - pub(crate) fn weighted_urpd_name(weight: UrpdWeight, cohort: &str) -> String { debug_assert!(weight.is_weighted()); if cohort == UTXO_ALL_NAME.id { @@ -57,20 +55,18 @@ impl Computer { weight: UrpdWeight, ) -> Option { let cohort_id = cohort.strip_prefix("utxos_")?; - let age = AGE_RANGE_NAMES + let age = AgeRangeId::ALL .iter() - .position(|name| name.id == cohort_id)?; + .copied() + .find(|age| age.name().id == cohort_id)?; if weight == UrpdWeight::Raw { return Some(1.0); } - let supply = self - .distribution - .utxo_cohorts - .age_range - .iter() - .nth(age)? + let supplies = &self.distribution.utxo_cohorts.age_range; + let supply = age + .select(supplies) .metrics .supply .total @@ -82,13 +78,20 @@ impl Computer { match weight { UrpdWeight::Raw => Some(1.0), UrpdWeight::Cointime => { - let cohort = self.frameworks.cointime.age_range.iter().nth(age)?; - resolve_age_value(cohort.wakefulness.day1.collect_one(day).flatten(), supply) + let cohort = age.select(&self.frameworks.cointime.age_range.activity.wakefulness); + resolve_age_value(cohort.day1.collect_one(day).flatten(), supply) .map(|value| value.clamp(0.0, 1.0)) } UrpdWeight::Coinflow => { - let cohort = self.frameworks.coinflow.age_range.iter().nth(age)?; - resolve_age_value(cohort.mobility.day1.0.collect_one(day).flatten(), supply) + let cohort = age.select( + &self + .frameworks + .coinflow + .age_range + .spending_exposure + .mobility, + ); + resolve_age_value(cohort.day1.0.collect_one(day).flatten(), supply) .map(|value| value.clamp(0.0, 1.0)) } } diff --git a/crates/brk_computer/src/models/bedrock/price.rs b/crates/brk_computer/src/models/bedrock/price.rs index 0c0cf6499..aaf5fb915 100644 --- a/crates/brk_computer/src/models/bedrock/price.rs +++ b/crates/brk_computer/src/models/bedrock/price.rs @@ -1,32 +1,35 @@ -//! Bedrock-local price wrapper for metrics whose stored source is indexed by day. -//! -//! This mirrors the per-block `Price` conversion chain while preserving Bedrock's -//! custom daily repeat/last-day views: cents are stored, USD is derived from cents, -//! and sats are derived from USD. - use brk_traversable::Traversable; -use brk_types::{Cents, Dollars, SatsFract, Version}; -use vecdb::{ReadableCloneableVec, Rw, StorageMode}; +use brk_types::{Cents, Day1, Dollars, SatsFract, Version}; +use vecdb::{ColumnId, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec}; use crate::internal::{ - CentsUnsignedToDollars, DailyMappings, DailyMetric, DollarsToSatsFract, LazyDailyMetric, + CentsUnsignedToDollars, DailyMappings, DollarsToSatsFract, LazyColumnDailyMetric, + LazyDailyMetric, }; -#[derive(Traversable)] -pub struct Price { +#[derive(Clone, Traversable)] +pub struct LazyColumnPrice +where + C: ColumnId, +{ pub usd: LazyDailyMetric, - pub cents: DailyMetric, + pub cents: LazyColumnDailyMetric, pub sats: LazyDailyMetric, } -impl Price { - pub(crate) fn forced_import( - db: &vecdb::Database, +impl LazyColumnPrice +where + C: ColumnId, +{ + pub(crate) fn new( name: &str, version: Version, + source: &ReadOnlyColumnarVec, C>, + column: C, mappings: &DailyMappings, - ) -> brk_error::Result { - let cents = DailyMetric::forced_import(db, &format!("{name}_cents"), version, mappings)?; + ) -> Self { + let cents = + LazyColumnDailyMetric::new(&format!("{name}_cents"), version, source, column, mappings); let usd = LazyDailyMetric::from_source::( name, version, @@ -40,6 +43,6 @@ impl Price { mappings, ); - Ok(Self { usd, cents, sats }) + Self { usd, cents, sats } } } diff --git a/crates/brk_computer/src/models/bedrock/vecs.rs b/crates/brk_computer/src/models/bedrock/vecs.rs index ec1acea0e..b9210b35a 100644 --- a/crates/brk_computer/src/models/bedrock/vecs.rs +++ b/crates/brk_computer/src/models/bedrock/vecs.rs @@ -1,28 +1,412 @@ use std::path::PathBuf; use brk_traversable::Traversable; -use brk_types::StoredF64; +use brk_types::{Cents, StoredF64, Version}; use derive_more::{Deref, DerefMut}; -use vecdb::{Rw, StorageMode}; +use vecdb::{ColumnId, Rw, StorageMode, VecValue}; -use super::price::Price; -use crate::internal::DailyMetric; +use super::price::LazyColumnPrice; +use crate::internal::{ColumnarDailyMetric, LazyColumnDailyMetric}; pub(crate) const MODE_COUNT: usize = 10; -pub(crate) const MODE_NAMES: [&str; MODE_COUNT] = [ - "raw", - "cointime", - "coinflow", - "coinflow_8y", - "coinflow_4y", - "coinflow_2y", - "coinflow_1y", - "coinflow_6m", - "coinflow_3m", - "coinflow_1m", +pub(crate) const PERCENTILE_COUNT: usize = 5; +pub(crate) const LEVEL_COUNT: usize = 9; +const WEIGHTED_MODE_COUNT: usize = MODE_COUNT - 1; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u8)] +pub(super) enum ModeId { + Raw, + Cointime, + Coinflow, + Coinflow8Y, + Coinflow4Y, + Coinflow2Y, + Coinflow1Y, + Coinflow6M, + Coinflow3M, + Coinflow1M, +} + +impl ModeId { + pub(super) const ALL: [Self; MODE_COUNT] = [ + Self::Raw, + Self::Cointime, + Self::Coinflow, + Self::Coinflow8Y, + Self::Coinflow4Y, + Self::Coinflow2Y, + Self::Coinflow1Y, + Self::Coinflow6M, + Self::Coinflow3M, + Self::Coinflow1M, + ]; + + pub(super) const fn name(self) -> &'static str { + match self { + Self::Raw => "raw", + Self::Cointime => "cointime", + Self::Coinflow => "coinflow", + Self::Coinflow8Y => "coinflow_8y", + Self::Coinflow4Y => "coinflow_4y", + Self::Coinflow2Y => "coinflow_2y", + Self::Coinflow1Y => "coinflow_1y", + Self::Coinflow6M => "coinflow_6m", + Self::Coinflow3M => "coinflow_3m", + Self::Coinflow1M => "coinflow_1m", + } + } + + pub(super) const fn weighted(self) -> Option { + match self { + Self::Raw => None, + Self::Cointime => Some(WeightedModeId::Cointime), + Self::Coinflow => Some(WeightedModeId::Coinflow), + Self::Coinflow8Y => Some(WeightedModeId::Coinflow8Y), + Self::Coinflow4Y => Some(WeightedModeId::Coinflow4Y), + Self::Coinflow2Y => Some(WeightedModeId::Coinflow2Y), + Self::Coinflow1Y => Some(WeightedModeId::Coinflow1Y), + Self::Coinflow6M => Some(WeightedModeId::Coinflow6M), + Self::Coinflow3M => Some(WeightedModeId::Coinflow3M), + Self::Coinflow1M => Some(WeightedModeId::Coinflow1M), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum WeightedModeId { + Cointime, + Coinflow, + Coinflow8Y, + Coinflow4Y, + Coinflow2Y, + Coinflow1Y, + Coinflow6M, + Coinflow3M, + Coinflow1M, +} + +impl WeightedModeId { + pub(super) const ALL: [Self; WEIGHTED_MODE_COUNT] = [ + Self::Cointime, + Self::Coinflow, + Self::Coinflow8Y, + Self::Coinflow4Y, + Self::Coinflow2Y, + Self::Coinflow1Y, + Self::Coinflow6M, + Self::Coinflow3M, + Self::Coinflow1M, + ]; + + pub(super) const COINFLOW_HORIZONS: [Self; 7] = [ + Self::Coinflow8Y, + Self::Coinflow4Y, + Self::Coinflow2Y, + Self::Coinflow1Y, + Self::Coinflow6M, + Self::Coinflow3M, + Self::Coinflow1M, + ]; + + pub(super) const fn mode(self) -> ModeId { + match self { + Self::Cointime => ModeId::Cointime, + Self::Coinflow => ModeId::Coinflow, + Self::Coinflow8Y => ModeId::Coinflow8Y, + Self::Coinflow4Y => ModeId::Coinflow4Y, + Self::Coinflow2Y => ModeId::Coinflow2Y, + Self::Coinflow1Y => ModeId::Coinflow1Y, + Self::Coinflow6M => ModeId::Coinflow6M, + Self::Coinflow3M => ModeId::Coinflow3M, + Self::Coinflow1M => ModeId::Coinflow1M, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum LossPercentileId { + Pct95, + Pct98, + Pct99, + Pct99_5, + Pct99_9, +} + +impl LossPercentileId { + pub(super) const fn suffix(self) -> &'static str { + match self { + Self::Pct95 => "pct95", + Self::Pct98 => "pct98", + Self::Pct99 => "pct99", + Self::Pct99_5 => "pct99_5", + Self::Pct99_9 => "pct99_9", + } + } + + pub(super) fn select(self, values: &Percentiles) -> &T { + match self { + Self::Pct95 => &values.pct95, + Self::Pct98 => &values.pct98, + Self::Pct99 => &values.pct99, + Self::Pct99_5 => &values.pct99_5, + Self::Pct99_9 => &values.pct99_9, + } + } + + pub(super) fn select_mut(self, values: &mut Percentiles) -> &mut T { + match self { + Self::Pct95 => &mut values.pct95, + Self::Pct98 => &mut values.pct98, + Self::Pct99 => &mut values.pct99, + Self::Pct99_5 => &mut values.pct99_5, + Self::Pct99_9 => &mut values.pct99_9, + } + } +} + +const LOSS_PERCENTILE_IDS: [LossPercentileId; PERCENTILE_COUNT] = [ + LossPercentileId::Pct95, + LossPercentileId::Pct98, + LossPercentileId::Pct99, + LossPercentileId::Pct99_5, + LossPercentileId::Pct99_9, ]; -#[derive(Traversable)] +impl ColumnId for LossPercentileId { + type Row + = [T; PERCENTILE_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &LOSS_PERCENTILE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(LOSS_PERCENTILE_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum PriceBandId { + FloorPct95, + FloorPct98, + FloorPct99, + FloorPct99_5, + FloorPct99_9, + LevelPct10, + LevelPct20, + LevelPct30, + LevelPct40, + LevelPct50, + LevelPct60, + LevelPct70, + LevelPct80, + LevelPct90, +} + +impl PriceBandId { + pub(super) const fn suffix(self) -> &'static str { + match self { + Self::FloorPct95 => "floor_pct95", + Self::FloorPct98 => "floor_pct98", + Self::FloorPct99 => "floor_pct99", + Self::FloorPct99_5 => "floor_pct99_5", + Self::FloorPct99_9 => "floor_pct99_9", + Self::LevelPct10 => "level_pct10", + Self::LevelPct20 => "level_pct20", + Self::LevelPct30 => "level_pct30", + Self::LevelPct40 => "level_pct40", + Self::LevelPct50 => "level_pct50", + Self::LevelPct60 => "level_pct60", + Self::LevelPct70 => "level_pct70", + Self::LevelPct80 => "level_pct80", + Self::LevelPct90 => "level_pct90", + } + } + + pub(super) fn select(self, values: &PriceBands) -> &T { + match self { + Self::FloorPct95 => &values.floor.pct95, + Self::FloorPct98 => &values.floor.pct98, + Self::FloorPct99 => &values.floor.pct99, + Self::FloorPct99_5 => &values.floor.pct99_5, + Self::FloorPct99_9 => &values.floor.pct99_9, + Self::LevelPct10 => &values.level.pct10, + Self::LevelPct20 => &values.level.pct20, + Self::LevelPct30 => &values.level.pct30, + Self::LevelPct40 => &values.level.pct40, + Self::LevelPct50 => &values.level.pct50, + Self::LevelPct60 => &values.level.pct60, + Self::LevelPct70 => &values.level.pct70, + Self::LevelPct80 => &values.level.pct80, + Self::LevelPct90 => &values.level.pct90, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum LevelId { + Pct10, + Pct20, + Pct30, + Pct40, + Pct50, + Pct60, + Pct70, + Pct80, + Pct90, +} + +impl LevelId { + pub(super) fn select(self, values: &Levels) -> &T { + match self { + Self::Pct10 => &values.pct10, + Self::Pct20 => &values.pct20, + Self::Pct30 => &values.pct30, + Self::Pct40 => &values.pct40, + Self::Pct50 => &values.pct50, + Self::Pct60 => &values.pct60, + Self::Pct70 => &values.pct70, + Self::Pct80 => &values.pct80, + Self::Pct90 => &values.pct90, + } + } + + pub(super) fn select_mut(self, values: &mut Levels) -> &mut T { + match self { + Self::Pct10 => &mut values.pct10, + Self::Pct20 => &mut values.pct20, + Self::Pct30 => &mut values.pct30, + Self::Pct40 => &mut values.pct40, + Self::Pct50 => &mut values.pct50, + Self::Pct60 => &mut values.pct60, + Self::Pct70 => &mut values.pct70, + Self::Pct80 => &mut values.pct80, + Self::Pct90 => &mut values.pct90, + } + } + + const fn price_band(self) -> PriceBandId { + match self { + Self::Pct10 => PriceBandId::LevelPct10, + Self::Pct20 => PriceBandId::LevelPct20, + Self::Pct30 => PriceBandId::LevelPct30, + Self::Pct40 => PriceBandId::LevelPct40, + Self::Pct50 => PriceBandId::LevelPct50, + Self::Pct60 => PriceBandId::LevelPct60, + Self::Pct70 => PriceBandId::LevelPct70, + Self::Pct80 => PriceBandId::LevelPct80, + Self::Pct90 => PriceBandId::LevelPct90, + } + } +} + +const PRICE_BAND_COUNT: usize = PERCENTILE_COUNT + LEVEL_COUNT; +const PRICE_BAND_IDS: [PriceBandId; PRICE_BAND_COUNT] = [ + PriceBandId::FloorPct95, + PriceBandId::FloorPct98, + PriceBandId::FloorPct99, + PriceBandId::FloorPct99_5, + PriceBandId::FloorPct99_9, + PriceBandId::LevelPct10, + PriceBandId::LevelPct20, + PriceBandId::LevelPct30, + PriceBandId::LevelPct40, + PriceBandId::LevelPct50, + PriceBandId::LevelPct60, + PriceBandId::LevelPct70, + PriceBandId::LevelPct80, + PriceBandId::LevelPct90, +]; + +pub(super) const LEVEL_IDS: [LevelId; LEVEL_COUNT] = [ + LevelId::Pct10, + LevelId::Pct20, + LevelId::Pct30, + LevelId::Pct40, + LevelId::Pct50, + LevelId::Pct60, + LevelId::Pct70, + LevelId::Pct80, + LevelId::Pct90, +]; + +impl ColumnId for PriceBandId { + type Row + = [T; PRICE_BAND_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &PRICE_BAND_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(PRICE_BAND_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Traversable)] pub struct Percentiles { pub pct95: T, pub pct98: T, @@ -31,7 +415,30 @@ pub struct Percentiles { pub pct99_9: T, } -#[derive(Traversable)] +impl Percentiles { + pub(super) fn from_fn(mut create: impl FnMut(LossPercentileId) -> T) -> Self { + Self { + pct95: create(LossPercentileId::Pct95), + pct98: create(LossPercentileId::Pct98), + pct99: create(LossPercentileId::Pct99), + pct99_5: create(LossPercentileId::Pct99_5), + pct99_9: create(LossPercentileId::Pct99_9), + } + } + + pub(super) fn iter(&self) -> impl Iterator { + [ + &self.pct95, + &self.pct98, + &self.pct99, + &self.pct99_5, + &self.pct99_9, + ] + .into_iter() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Traversable)] pub struct Levels { pub pct10: T, pub pct20: T, @@ -44,16 +451,72 @@ pub struct Levels { pub pct90: T, } -#[derive(Traversable)] +impl Levels { + pub(super) fn from_fn(mut create: impl FnMut(LevelId) -> T) -> Self { + Self { + pct10: create(LevelId::Pct10), + pct20: create(LevelId::Pct20), + pct30: create(LevelId::Pct30), + pct40: create(LevelId::Pct40), + pct50: create(LevelId::Pct50), + pct60: create(LevelId::Pct60), + pct70: create(LevelId::Pct70), + pct80: create(LevelId::Pct80), + pct90: create(LevelId::Pct90), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Traversable)] +pub struct PriceBands { + pub floor: Percentiles, + pub level: Levels, +} + +impl PriceBands { + pub(super) fn from_fn(mut create: impl FnMut(PriceBandId) -> T) -> Self { + Self { + floor: Percentiles { + pct95: create(PriceBandId::FloorPct95), + pct98: create(PriceBandId::FloorPct98), + pct99: create(PriceBandId::FloorPct99), + pct99_5: create(PriceBandId::FloorPct99_5), + pct99_9: create(PriceBandId::FloorPct99_9), + }, + level: Levels::from_fn(|id| create(id.price_band())), + } + } +} + +impl LossPercentileId { + pub(super) fn series(mut create: impl FnMut(Self) -> T) -> Percentiles { + Percentiles::from_fn(&mut create) + } +} + +impl PriceBandId { + pub(super) fn series(mut create: impl FnMut(Self) -> T) -> PriceBands { + PriceBands::from_fn(&mut create) + } +} + +#[derive(Deref, DerefMut, Traversable)] pub struct ModeVecs { - pub loss_threshold: Percentiles>, - pub floor: Percentiles>, - pub level: Levels>, + pub loss_threshold: ColumnarDailyMetric< + StoredF64, + LossPercentileId, + Percentiles>, + M, + >, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub prices: + ColumnarDailyMetric>, M>, } #[derive(Traversable)] -pub struct Modes { - pub raw: T, +pub struct WeightedModes { pub cointime: T, pub coinflow: T, pub coinflow_8y: T, @@ -65,27 +528,82 @@ pub struct Modes { pub coinflow_1m: T, } -impl Modes { - pub(crate) fn try_from_fn( - mut create: impl FnMut(&'static str) -> Result, +impl WeightedModes { + pub(super) fn from_fn(mut create: impl FnMut(WeightedModeId) -> T) -> Self { + Self { + cointime: create(WeightedModeId::Cointime), + coinflow: create(WeightedModeId::Coinflow), + coinflow_8y: create(WeightedModeId::Coinflow8Y), + coinflow_4y: create(WeightedModeId::Coinflow4Y), + coinflow_2y: create(WeightedModeId::Coinflow2Y), + coinflow_1y: create(WeightedModeId::Coinflow1Y), + coinflow_6m: create(WeightedModeId::Coinflow6M), + coinflow_3m: create(WeightedModeId::Coinflow3M), + coinflow_1m: create(WeightedModeId::Coinflow1M), + } + } + + pub(super) fn try_from_fn( + mut create: impl FnMut(WeightedModeId) -> Result, ) -> Result { Ok(Self { - raw: create(MODE_NAMES[0])?, - cointime: create(MODE_NAMES[1])?, - coinflow: create(MODE_NAMES[2])?, - coinflow_8y: create(MODE_NAMES[3])?, - coinflow_4y: create(MODE_NAMES[4])?, - coinflow_2y: create(MODE_NAMES[5])?, - coinflow_1y: create(MODE_NAMES[6])?, - coinflow_6m: create(MODE_NAMES[7])?, - coinflow_3m: create(MODE_NAMES[8])?, - coinflow_1m: create(MODE_NAMES[9])?, + cointime: create(WeightedModeId::Cointime)?, + coinflow: create(WeightedModeId::Coinflow)?, + coinflow_8y: create(WeightedModeId::Coinflow8Y)?, + coinflow_4y: create(WeightedModeId::Coinflow4Y)?, + coinflow_2y: create(WeightedModeId::Coinflow2Y)?, + coinflow_1y: create(WeightedModeId::Coinflow1Y)?, + coinflow_6m: create(WeightedModeId::Coinflow6M)?, + coinflow_3m: create(WeightedModeId::Coinflow3M)?, + coinflow_1m: create(WeightedModeId::Coinflow1M)?, }) } - pub(crate) fn as_mut_array(&mut self) -> [&mut T; MODE_COUNT] { + pub(super) fn select_mut(&mut self, id: WeightedModeId) -> &mut T { + match id { + WeightedModeId::Cointime => &mut self.cointime, + WeightedModeId::Coinflow => &mut self.coinflow, + WeightedModeId::Coinflow8Y => &mut self.coinflow_8y, + WeightedModeId::Coinflow4Y => &mut self.coinflow_4y, + WeightedModeId::Coinflow2Y => &mut self.coinflow_2y, + WeightedModeId::Coinflow1Y => &mut self.coinflow_1y, + WeightedModeId::Coinflow6M => &mut self.coinflow_6m, + WeightedModeId::Coinflow3M => &mut self.coinflow_3m, + WeightedModeId::Coinflow1M => &mut self.coinflow_1m, + } + } + + pub(super) fn select(&self, id: WeightedModeId) -> &T { + match id { + WeightedModeId::Cointime => &self.cointime, + WeightedModeId::Coinflow => &self.coinflow, + WeightedModeId::Coinflow8Y => &self.coinflow_8y, + WeightedModeId::Coinflow4Y => &self.coinflow_4y, + WeightedModeId::Coinflow2Y => &self.coinflow_2y, + WeightedModeId::Coinflow1Y => &self.coinflow_1y, + WeightedModeId::Coinflow6M => &self.coinflow_6m, + WeightedModeId::Coinflow3M => &self.coinflow_3m, + WeightedModeId::Coinflow1M => &self.coinflow_1m, + } + } + + pub(super) fn iter(&self) -> impl Iterator { + [ + &self.cointime, + &self.coinflow, + &self.coinflow_8y, + &self.coinflow_4y, + &self.coinflow_2y, + &self.coinflow_1y, + &self.coinflow_6m, + &self.coinflow_3m, + &self.coinflow_1m, + ] + .into_iter() + } + + pub(super) fn iter_mut(&mut self) -> impl Iterator { [ - &mut self.raw, &mut self.cointime, &mut self.coinflow, &mut self.coinflow_8y, @@ -96,6 +614,58 @@ impl Modes { &mut self.coinflow_3m, &mut self.coinflow_1m, ] + .into_iter() + } +} + +#[derive(Deref, DerefMut, Traversable)] +pub struct Modes { + pub raw: T, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub weighted: WeightedModes, +} + +impl Modes { + pub(super) fn from_fn(mut create: impl FnMut(ModeId) -> T) -> Self { + Self { + raw: create(ModeId::Raw), + weighted: WeightedModes::from_fn(|id| create(id.mode())), + } + } + + pub(super) fn try_from_fn( + mut create: impl FnMut(ModeId) -> Result, + ) -> Result { + Ok(Self { + raw: create(ModeId::Raw)?, + weighted: WeightedModes::try_from_fn(|id| create(id.mode()))?, + }) + } + + pub(super) fn select_mut(&mut self, id: ModeId) -> &mut T { + match id { + ModeId::Raw => &mut self.raw, + _ => self + .weighted + .select_mut(id.weighted().expect("weighted mode")), + } + } + + pub(super) fn select(&self, id: ModeId) -> &T { + match id { + ModeId::Raw => &self.raw, + _ => self.weighted.select(id.weighted().expect("weighted mode")), + } + } + + pub(super) fn iter(&self) -> impl Iterator { + std::iter::once(&self.raw).chain(self.weighted.iter()) + } + + pub(super) fn iter_mut(&mut self) -> impl Iterator { + std::iter::once(&mut self.raw).chain(self.weighted.iter_mut()) } } @@ -109,3 +679,86 @@ pub struct Vecs { #[traversable(flatten)] pub modes: Modes>, } + +#[cfg(test)] +mod tests { + use std::convert::Infallible; + + use vecdb::ColumnId; + + use super::{ + LOSS_PERCENTILE_IDS, LossPercentileId, ModeId, Modes, PRICE_BAND_IDS, PriceBandId, + WeightedModeId, + }; + + #[test] + fn bedrock_columns_match_public_band_order() { + assert_eq!(LossPercentileId::ALL, LOSS_PERCENTILE_IDS); + assert_eq!( + LossPercentileId::from_fn(|percentile| percentile), + LOSS_PERCENTILE_IDS + ); + assert_eq!(PriceBandId::ALL, PRICE_BAND_IDS); + assert_eq!(PriceBandId::from_fn(|band| band), PRICE_BAND_IDS); + assert_eq!( + LossPercentileId::from_fn(LossPercentileId::suffix), + ["pct95", "pct98", "pct99", "pct99_5", "pct99_9"] + ); + assert_eq!( + PriceBandId::from_fn(PriceBandId::suffix), + [ + "floor_pct95", + "floor_pct98", + "floor_pct99", + "floor_pct99_5", + "floor_pct99_9", + "level_pct10", + "level_pct20", + "level_pct30", + "level_pct40", + "level_pct50", + "level_pct60", + "level_pct70", + "level_pct80", + "level_pct90", + ] + ); + } + + #[test] + fn mode_ids_match_named_fields_and_storage_names() { + assert_eq!( + WeightedModeId::ALL.map(WeightedModeId::mode).as_slice(), + &ModeId::ALL[1..] + ); + assert_eq!( + WeightedModeId::COINFLOW_HORIZONS + .map(WeightedModeId::mode) + .as_slice(), + &ModeId::ALL[3..] + ); + + let mut modes = Modes::try_from_fn(|id| Ok::<_, Infallible>((id, false))).unwrap(); + for id in ModeId::ALL { + let mode = modes.select_mut(id); + assert_eq!(mode.0, id); + mode.1 = true; + } + assert!(modes.iter().all(|(_, visited)| *visited)); + assert_eq!( + ModeId::ALL.map(ModeId::name), + [ + "raw", + "cointime", + "coinflow", + "coinflow_8y", + "coinflow_4y", + "coinflow_2y", + "coinflow_1y", + "coinflow_6m", + "coinflow_3m", + "coinflow_1m", + ] + ); + } +} diff --git a/crates/brk_computer/src/models/rarity_meter/components.rs b/crates/brk_computer/src/models/rarity_meter/components.rs index 955cf94b3..e7493746b 100644 --- a/crates/brk_computer/src/models/rarity_meter/components.rs +++ b/crates/brk_computer/src/models/rarity_meter/components.rs @@ -1,52 +1,42 @@ use brk_error::Result; use brk_indexer::{Indexer, Lengths}; use brk_traversable::Traversable; -use brk_types::{Cents, Height, PartsPerMillion32, StoredF32, Version}; +use brk_types::{ + Cents, Height, PartsPerMillion32, RARITY_PERCENTILES, RARITY_PERCENTILES_LEN, + RarityPercentileId, StoredF32, Version, +}; use vecdb::{ - AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, - WritableVec, + AnyVec, ColumnId, ColumnarVec, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadOnlyClone, + ReadableVec, Rw, StorageMode, WritableVec, }; use crate::{ distribution, frameworks::{coinflow, cointime}, indexes, - internal::{LazyPerBlock, Price, RatioPerBlock}, + internal::{LazyColumnRatioPerBlock, LazyPerBlock, Price}, }; use super::{ + COMPUTE_BATCH_SIZE, cached_component_price::CachedComponentPrice, - percentiles::{BlockDecayPercentiles, START_HEIGHT}, + percentiles::{BlockDecayPercentiles, RarityPercentiles, START_HEIGHT, suffix}, }; -#[derive(Traversable)] -pub struct Band { +#[derive(Clone, Traversable)] +pub struct Band { #[traversable(flatten)] - pub ratio: RatioPerBlock, + pub ratio: LazyColumnRatioPerBlock, pub price: Price>, } #[derive(Traversable)] pub struct Component { - pub pct0_1: Band, - pub pct0_5: Band, - pub pct1: Band, - pub pct2: Band, - pub pct5: Band, - pub pct10: Band, - pub pct20: Band, - pub pct30: Band, - pub pct40: Band, - pub pct50: Band, - pub pct60: Band, - pub pct70: Band, - pub pct80: Band, - pub pct90: Band, - pub pct95: Band, - pub pct98: Band, - pub pct99: Band, - pub pct99_5: Band, - pub pct99_9: Band, + #[traversable(flatten)] + pub bands: RarityPercentiles, + + pub ratios: + M::Stored, RarityPercentileId>>>, #[traversable(skip)] block_decay_pct: BlockDecayPercentiles, @@ -55,7 +45,7 @@ pub struct Component { cached_price: CachedComponentPrice, } -const VERSION: Version = Version::new(10); +const VERSION: Version = Version::new(11); impl Component { fn forced_import( @@ -67,51 +57,31 @@ impl Component { ) -> Result { let version = version + VERSION; let cached_price = CachedComponentPrice::new(name, version, price_source); - - macro_rules! import_ratio { - ($suffix:expr) => { - RatioPerBlock::forced_import_ppm( - db, - &format!("{name}_{}", $suffix), - version, - indexes, - )? - }; - } - - macro_rules! import_band { - ($pct:expr) => {{ - let ratio = import_ratio!(concat!("ratio_", $pct)); - let price = cached_price.price_for_ratio( - &format!("{name}_{}", $pct), - version, - &ratio.ppm.height, - indexes, - ); - Band { ratio, price } - }}; - } + let ratios = EagerVec::< + ColumnarVec, RarityPercentileId>, + >::forced_import(db, &format!("{name}_ratios_ppm"), version)?; + let source = ratios.read_only_clone(); + let bands = RarityPercentiles::from_fn(|id| { + let suffix = suffix(id); + let ratio = LazyColumnRatioPerBlock::new( + &format!("{name}_ratio_{suffix}"), + version, + &source, + id, + indexes, + ); + let price = cached_price.price_for_ratio( + &format!("{name}_{suffix}"), + version, + &ratio.ppm.height, + indexes, + ); + Band { ratio, price } + }); Ok(Self { - pct0_1: import_band!("pct0_1"), - pct0_5: import_band!("pct0_5"), - pct1: import_band!("pct1"), - pct2: import_band!("pct2"), - pct5: import_band!("pct5"), - pct10: import_band!("pct10"), - pct20: import_band!("pct20"), - pct30: import_band!("pct30"), - pct40: import_band!("pct40"), - pct50: import_band!("pct50"), - pct60: import_band!("pct60"), - pct70: import_band!("pct70"), - pct80: import_band!("pct80"), - pct90: import_band!("pct90"), - pct95: import_band!("pct95"), - pct98: import_band!("pct98"), - pct99: import_band!("pct99"), - pct99_5: import_band!("pct99_5"), - pct99_9: import_band!("pct99_9"), + bands, + ratios, block_decay_pct: BlockDecayPercentiles::default(), cached_price, }) @@ -126,111 +96,64 @@ impl Component { self.cached_price .clear_if_recomputed_from(starting_lengths.height); - let ratio_version = ratio_source.version(); - - self.mut_pct_vecs().try_for_each(|vec| -> Result<()> { - vec.validate_computed_version_or_reset(ratio_version)?; - Ok(()) - })?; - - let starting_height = self - .mut_pct_vecs() - .map(|vec| Height::from(vec.len())) - .min() - .unwrap() - .min(starting_lengths.height); - - let start = starting_height.to_usize(); - let ratio_len = ratio_source.len(); - - if ratio_len > start { - let expected_len = start.saturating_sub(START_HEIGHT); - if self.block_decay_pct.len() != expected_len { - self.block_decay_pct.reset(); - if start > START_HEIGHT { - let historical = ratio_source.collect_range_at(START_HEIGHT, start); - self.block_decay_pct.add_bulk(START_HEIGHT, &historical); + let block_decay_pct = &mut self.block_decay_pct; + self.ratios.compute_batched_to( + starting_lengths.height, + ratio_source.len(), + ratio_source.version(), + COMPUTE_BATCH_SIZE, + |ratios, range| { + let expected_len = range.start.saturating_sub(START_HEIGHT); + if block_decay_pct.len() != expected_len { + block_decay_pct.reset(); + if range.start > START_HEIGHT { + let historical = ratio_source.collect_range_at(START_HEIGHT, range.start); + block_decay_pct.add_bulk(START_HEIGHT, &historical); + } } - } - let new_ratios = ratio_source.collect_range_at(start, ratio_len); - let mut pct_vecs: [&mut EagerVec>; 19] = [ - &mut self.pct0_1.ratio.ppm.height, - &mut self.pct0_5.ratio.ppm.height, - &mut self.pct1.ratio.ppm.height, - &mut self.pct2.ratio.ppm.height, - &mut self.pct5.ratio.ppm.height, - &mut self.pct10.ratio.ppm.height, - &mut self.pct20.ratio.ppm.height, - &mut self.pct30.ratio.ppm.height, - &mut self.pct40.ratio.ppm.height, - &mut self.pct50.ratio.ppm.height, - &mut self.pct60.ratio.ppm.height, - &mut self.pct70.ratio.ppm.height, - &mut self.pct80.ratio.ppm.height, - &mut self.pct90.ratio.ppm.height, - &mut self.pct95.ratio.ppm.height, - &mut self.pct98.ratio.ppm.height, - &mut self.pct99.ratio.ppm.height, - &mut self.pct99_5.ratio.ppm.height, - &mut self.pct99_9.ratio.ppm.height, - ]; - const PCTS: [f64; 19] = [ - 0.001, 0.005, 0.01, 0.02, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, - 0.90, 0.95, 0.98, 0.99, 0.995, 0.999, - ]; - let mut out = [0.0; 19]; - - for vec in &mut pct_vecs { - vec.truncate_if_needed_at(start)?; - } - - for (offset, &ratio) in new_ratios.iter().enumerate() { - let height = start + offset; - if height >= START_HEIGHT { - self.block_decay_pct.add(height, *ratio); + let new_ratios = ratio_source.collect_range_at(range.start, range.end); + let mut out = [0.0; RARITY_PERCENTILES_LEN]; + for (offset, &ratio) in new_ratios.iter().enumerate() { + let height = range.start + offset; + if height >= START_HEIGHT { + block_decay_pct.add(height, *ratio); + } + block_decay_pct.quantiles(&RARITY_PERCENTILES, &mut out); + ratios.push(RarityPercentileId::from_fn(|id| { + PartsPerMillion32::from(out[id.index()]) + })); } - self.block_decay_pct.quantiles(&PCTS, &mut out); - for (vec, &value) in pct_vecs.iter_mut().zip(&out) { - vec.push(PartsPerMillion32::from(value)); - } - } - } - { - let _lock = exit.lock(); - self.mut_pct_vecs() - .try_for_each(|vec| vec.write().map(|_| ()))?; - } + Ok(()) + }, + exit, + )?; Ok(()) } - fn mut_pct_vecs( - &mut self, - ) -> impl Iterator>> { - [ - &mut self.pct0_1.ratio.ppm.height, - &mut self.pct0_5.ratio.ppm.height, - &mut self.pct1.ratio.ppm.height, - &mut self.pct2.ratio.ppm.height, - &mut self.pct5.ratio.ppm.height, - &mut self.pct10.ratio.ppm.height, - &mut self.pct20.ratio.ppm.height, - &mut self.pct30.ratio.ppm.height, - &mut self.pct40.ratio.ppm.height, - &mut self.pct50.ratio.ppm.height, - &mut self.pct60.ratio.ppm.height, - &mut self.pct70.ratio.ppm.height, - &mut self.pct80.ratio.ppm.height, - &mut self.pct90.ratio.ppm.height, - &mut self.pct95.ratio.ppm.height, - &mut self.pct98.ratio.ppm.height, - &mut self.pct99.ratio.ppm.height, - &mut self.pct99_5.ratio.ppm.height, - &mut self.pct99_9.ratio.ppm.height, - ] - .into_iter() + pub(super) fn boundary_version(&self) -> Version { + self.bands + .boundary_refs() + .into_iter() + .map(|band| band.price.cents.height.version()) + .sum() + } + + pub(super) fn boundary_len(&self) -> usize { + self.bands + .boundary_refs() + .into_iter() + .map(|band| band.price.cents.height.len()) + .min() + .unwrap_or_default() + } + + pub(super) fn collect_boundary_prices(&self, start: usize, end: usize) -> [Vec; 10] { + self.bands + .boundary_refs() + .map(|band| band.price.cents.height.collect_range_at(start, end)) } } diff --git a/crates/brk_computer/src/models/rarity_meter/extremes.rs b/crates/brk_computer/src/models/rarity_meter/extremes.rs index 3137cec6d..dce1e497c 100644 --- a/crates/brk_computer/src/models/rarity_meter/extremes.rs +++ b/crates/brk_computer/src/models/rarity_meter/extremes.rs @@ -4,21 +4,23 @@ use brk_error::Result; use brk_indexer::Indexer; use brk_traversable::Traversable; use brk_types::{Bitcoin, Dollars, Height, PartsPerMillion32, StoredF32, StoredU8, Version}; +use derive_more::{Deref, DerefMut}; use schemars::JsonSchema; use vecdb::{ - AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, WritableVec, + AnyStoredVec, AnyVec, ColumnId, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, + VecValue, WritableVec, }; use crate::{ indexes, internal::{ - NumericValue, PerBlock, PercentPerBlock, + ColumnarPerBlock, LazyColumnPerBlock, NumericValue, PerBlock, PercentPerBlock, algo::{ExactOrderStats, FenwickTree}, db_utils::validate_any_computed_version_or_reset, }, }; -const VERSION: Version = Version::new(6); +const VERSION: Version = Version::new(7); const MIN_HISTORY_BLOCKS: usize = 210_000; const WRITE_INTERVAL: usize = 10_000; /// Above this many missing outputs, coordinate compression is faster than @@ -26,6 +28,82 @@ const WRITE_INTERVAL: usize = 10_000; const BULK_BACKFILL_THRESHOLD: usize = 10_000; const BANDS: [(f64, u8); 3] = [(0.00025, 3), (0.0005, 2), (0.001, 1)]; +const THRESHOLD_COUNT: usize = 3; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ExtremeThresholdId { + Pct0_1, + Pct0_05, + Pct0_025, +} + +const EXTREME_THRESHOLD_IDS: [ExtremeThresholdId; THRESHOLD_COUNT] = [ + ExtremeThresholdId::Pct0_1, + ExtremeThresholdId::Pct0_05, + ExtremeThresholdId::Pct0_025, +]; + +impl ExtremeThresholdId { + fn series(mut create: impl FnMut(Self) -> T) -> ThresholdVecs { + ThresholdVecs { + threshold_pct0_1: create(Self::Pct0_1), + threshold_pct0_05: create(Self::Pct0_05), + threshold_pct0_025: create(Self::Pct0_025), + } + } +} + +impl ColumnId for ExtremeThresholdId { + type Row + = [T; THRESHOLD_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &EXTREME_THRESHOLD_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(EXTREME_THRESHOLD_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Clone, Traversable)] +pub struct ThresholdVecs { + pub threshold_pct0_1: T, + pub threshold_pct0_05: T, + pub threshold_pct0_025: T, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum WindowKind { Full, @@ -61,14 +139,20 @@ const SELLER_EXHAUSTION: Config = Config { /// /// `tail` is the current observation's top- or bottom-tail share, the three /// `threshold` fields are the highlight boundaries, and `rank` is 0 through 3. -#[derive(Traversable)] +#[derive(Deref, DerefMut, Traversable)] pub struct Extreme where T: NumericValue + JsonSchema, { - pub threshold_pct0_1: PerBlock, - pub threshold_pct0_05: PerBlock, - pub threshold_pct0_025: PerBlock, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub thresholds: ColumnarPerBlock< + T, + ExtremeThresholdId, + ThresholdVecs>, + M, + >, pub tail: PercentPerBlock, pub rank: PerBlock, @@ -86,25 +170,24 @@ where version: Version, indexes: &indexes::Vecs, ) -> Result { + let thresholds = ColumnarPerBlock::forced_import( + db, + &format!("{name}_thresholds"), + version, + |source| { + ExtremeThresholdId::series(|threshold| { + let series_name = match threshold { + ExtremeThresholdId::Pct0_1 => format!("{name}_threshold_pct0_1"), + ExtremeThresholdId::Pct0_05 => format!("{name}_threshold_pct0_05"), + ExtremeThresholdId::Pct0_025 => format!("{name}_threshold"), + }; + LazyColumnPerBlock::new(&series_name, version, source, threshold, indexes) + }) + }, + )?; + Ok(Self { - threshold_pct0_1: PerBlock::forced_import( - db, - &format!("{name}_threshold_pct0_1"), - version, - indexes, - )?, - threshold_pct0_05: PerBlock::forced_import( - db, - &format!("{name}_threshold_pct0_05"), - version, - indexes, - )?, - threshold_pct0_025: PerBlock::forced_import( - db, - &format!("{name}_threshold"), - version, - indexes, - )?, + thresholds, tail: PercentPerBlock::forced_import(db, &format!("{name}_tail"), version, indexes)?, rank: PerBlock::forced_import(db, &format!("{name}_rank"), version, indexes)?, history: LiveHistory::new(), @@ -120,9 +203,7 @@ where ) -> Result<()> { let dependency_version = source.version(); for output in [ - &mut self.threshold_pct0_1.height as &mut dyn AnyStoredVec, - &mut self.threshold_pct0_05.height, - &mut self.threshold_pct0_025.height, + &mut self.thresholds.height as &mut dyn AnyStoredVec, &mut self.tail.ppm.height, &mut self.rank.height, ] { @@ -131,9 +212,7 @@ where let source_end = source.len(); let start = [ - self.threshold_pct0_1.height.len(), - self.threshold_pct0_05.height.len(), - self.threshold_pct0_025.height.len(), + self.thresholds.height.len(), self.tail.ppm.height.len(), self.rank.height.len(), indexer.safe_lengths().height.to_usize(), @@ -143,15 +222,7 @@ where .min() .unwrap_or_default(); - self.threshold_pct0_1 - .height - .any_truncate_if_needed_at(start)?; - self.threshold_pct0_05 - .height - .any_truncate_if_needed_at(start)?; - self.threshold_pct0_025 - .height - .any_truncate_if_needed_at(start)?; + self.thresholds.height.any_truncate_if_needed_at(start)?; self.tail.ppm.height.any_truncate_if_needed_at(start)?; self.rank.height.any_truncate_if_needed_at(start)?; @@ -257,15 +328,12 @@ where } fn push_state(&mut self, state: EventState) { - self.threshold_pct0_1 - .height - .push(T::from(state.thresholds.pct0_1)); - self.threshold_pct0_05 - .height - .push(T::from(state.thresholds.pct0_05)); - self.threshold_pct0_025 - .height - .push(T::from(state.thresholds.pct0_025)); + self.thresholds + .push(ExtremeThresholdId::from_fn(|threshold| match threshold { + ExtremeThresholdId::Pct0_1 => T::from(state.thresholds.pct0_1), + ExtremeThresholdId::Pct0_05 => T::from(state.thresholds.pct0_05), + ExtremeThresholdId::Pct0_025 => T::from(state.thresholds.pct0_025), + })); self.tail .ppm .height @@ -281,9 +349,7 @@ where ) -> Result<()> { if (height_index + 1).is_multiple_of(WRITE_INTERVAL) || height_index + 1 == source_end { let _lock = exit.lock(); - self.threshold_pct0_1.height.write()?; - self.threshold_pct0_05.height.write()?; - self.threshold_pct0_025.height.write()?; + self.thresholds.write()?; self.tail.ppm.height.write()?; self.rank.height.write()?; } @@ -641,6 +707,15 @@ fn bucket(coordinates: &[f64], value: f64) -> usize { mod tests { use super::*; + #[test] + fn threshold_columns_match_public_fields() { + assert_eq!(ExtremeThresholdId::ALL, EXTREME_THRESHOLD_IDS); + let fields = ExtremeThresholdId::series(|threshold| threshold); + assert_eq!(fields.threshold_pct0_1, ExtremeThresholdId::Pct0_1); + assert_eq!(fields.threshold_pct0_05, ExtremeThresholdId::Pct0_05); + assert_eq!(fields.threshold_pct0_025, ExtremeThresholdId::Pct0_025); + } + fn history(values: &[f64], config: Config) -> LiveHistory { let mut history = LiveHistory::new(); history.rebuild(values.to_vec(), config); diff --git a/crates/brk_computer/src/models/rarity_meter/inner.rs b/crates/brk_computer/src/models/rarity_meter/inner.rs index 3307013cb..5bb5f3222 100644 --- a/crates/brk_computer/src/models/rarity_meter/inner.rs +++ b/crates/brk_computer/src/models/rarity_meter/inner.rs @@ -1,41 +1,36 @@ use brk_error::Result; use brk_indexer::Indexer; use brk_traversable::Traversable; -use brk_types::{Cents, Height, StoredI8, Version}; -use vecdb::{AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, WritableVec}; +use brk_types::{Cents, Height, RARITY_PERCENTILES_LEN, RarityPercentileId, StoredI8, Version}; +use vecdb::{AnyVec, ColumnId, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec}; use crate::{ indexes, - internal::{PerBlock, Price}, + internal::{ColumnarPerBlock, LazyColumnPerBlock, PerBlock, Price}, }; -use super::Component; +use super::{ + COMPUTE_BATCH_SIZE, Component, + percentiles::{ + BOUNDARY_PERCENTILE_IDS, RarityPercentiles, boundary_index, is_lower_boundary, price_suffix, + }, +}; #[derive(Traversable)] pub struct RarityMeterInner { - pub pct0_1: Price>, - pub pct0_5: Price>, - pub pct1: Price>, - pub pct2: Price>, - pub pct5: Price>, - pub pct10: Price>, - pub pct20: Price>, - pub pct30: Price>, - pub pct40: Price>, - pub pct50: Price>, - pub pct60: Price>, - pub pct70: Price>, - pub pct80: Price>, - pub pct90: Price>, - pub pct95: Price>, - pub pct98: Price>, - pub pct99: Price>, - pub pct99_5: Price>, - pub pct99_9: Price>, + #[traversable(flatten)] + pub prices: ColumnarPerBlock< + Cents, + RarityPercentileId, + RarityPercentiles>>, + M, + >, pub index: PerBlock, pub score: PerBlock, } +const VERSION: Version = Version::ONE; + impl RarityMeterInner { pub(crate) fn forced_import( db: &Database, @@ -43,26 +38,26 @@ impl RarityMeterInner { version: Version, indexes: &indexes::Vecs, ) -> Result { + let version = version + VERSION; + let prices = ColumnarPerBlock::::forced_import( + db, + &format!("{prefix}_percentiles_cents"), + version, + |source| { + RarityPercentiles::from_fn(|id| { + Price::from_columnar_source( + &format!("{prefix}_{}", price_suffix(id)), + version, + source, + id, + indexes, + ) + }) + }, + )?; + Ok(Self { - pct0_1: Price::forced_import(db, &format!("{prefix}_pct0_1"), version, indexes)?, - pct0_5: Price::forced_import(db, &format!("{prefix}_pct0_5"), version, indexes)?, - pct1: Price::forced_import(db, &format!("{prefix}_pct01"), version, indexes)?, - pct2: Price::forced_import(db, &format!("{prefix}_pct02"), version, indexes)?, - pct5: Price::forced_import(db, &format!("{prefix}_pct05"), version, indexes)?, - pct10: Price::forced_import(db, &format!("{prefix}_pct10"), version, indexes)?, - pct20: Price::forced_import(db, &format!("{prefix}_pct20"), version, indexes)?, - pct30: Price::forced_import(db, &format!("{prefix}_pct30"), version, indexes)?, - pct40: Price::forced_import(db, &format!("{prefix}_pct40"), version, indexes)?, - pct50: Price::forced_import(db, &format!("{prefix}_pct50"), version, indexes)?, - pct60: Price::forced_import(db, &format!("{prefix}_pct60"), version, indexes)?, - pct70: Price::forced_import(db, &format!("{prefix}_pct70"), version, indexes)?, - pct80: Price::forced_import(db, &format!("{prefix}_pct80"), version, indexes)?, - pct90: Price::forced_import(db, &format!("{prefix}_pct90"), version, indexes)?, - pct95: Price::forced_import(db, &format!("{prefix}_pct95"), version, indexes)?, - pct98: Price::forced_import(db, &format!("{prefix}_pct98"), version, indexes)?, - pct99: Price::forced_import(db, &format!("{prefix}_pct99"), version, indexes)?, - pct99_5: Price::forced_import(db, &format!("{prefix}_pct99_5"), version, indexes)?, - pct99_9: Price::forced_import(db, &format!("{prefix}_pct99_9"), version, indexes)?, + prices, index: PerBlock::forced_import(db, &format!("{prefix}_index"), version, indexes)?, score: PerBlock::forced_import(db, &format!("{prefix}_score"), version, indexes)?, }) @@ -76,142 +71,38 @@ impl RarityMeterInner { exit: &Exit, ) -> Result<()> { let starting_height = indexer.safe_lengths().height; - let gather = |f: fn(&Component) -> &_| -> Vec<_> { - components.iter().map(|component| f(component)).collect() - }; + let dependency_version = components + .iter() + .map(|component| component.boundary_version()) + .sum(); + let source_end = components + .iter() + .map(|component| component.boundary_len()) + .min() + .unwrap_or_default(); - // Lower percentiles: max across all models (tightest lower bound) - self.pct0_1.cents.height.compute_max_of_others( + self.prices.height.compute_batched_to( starting_height, - &gather(|component| &component.pct0_1.price.cents.height), - exit, - )?; - self.pct0_5.cents.height.compute_max_of_others( - starting_height, - &gather(|component| &component.pct0_5.price.cents.height), - exit, - )?; - self.pct1.cents.height.compute_max_of_others( - starting_height, - &gather(|component| &component.pct1.price.cents.height), - exit, - )?; - self.pct2.cents.height.compute_max_of_others( - starting_height, - &gather(|component| &component.pct2.price.cents.height), - exit, - )?; - self.pct5.cents.height.compute_max_of_others( - starting_height, - &gather(|component| &component.pct5.price.cents.height), - exit, - )?; + source_end, + dependency_version, + COMPUTE_BATCH_SIZE, + |cents, range| { + let component_prices: Vec<_> = components + .iter() + .map(|component| component.collect_boundary_prices(range.start, range.end)) + .collect(); - // Upper percentiles: min across all models (tightest upper bound) - self.pct95.cents.height.compute_min_of_others( - starting_height, - &gather(|component| &component.pct95.price.cents.height), - exit, - )?; - self.pct98.cents.height.compute_min_of_others( - starting_height, - &gather(|component| &component.pct98.price.cents.height), - exit, - )?; - self.pct99.cents.height.compute_min_of_others( - starting_height, - &gather(|component| &component.pct99.price.cents.height), - exit, - )?; - self.pct99_5.cents.height.compute_min_of_others( - starting_height, - &gather(|component| &component.pct99_5.price.cents.height), - exit, - )?; - self.pct99_9.cents.height.compute_min_of_others( - starting_height, - &gather(|component| &component.pct99_9.price.cents.height), - exit, - )?; + for offset in 0..range.len() { + cents.push(combine_percentiles(&component_prices, offset)); + } - compute_inner_percentile( - &mut self.pct10.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 10, - exit, - )?; - compute_inner_percentile( - &mut self.pct20.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 20, - exit, - )?; - compute_inner_percentile( - &mut self.pct30.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 30, - exit, - )?; - compute_inner_percentile( - &mut self.pct40.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 40, - exit, - )?; - compute_inner_percentile( - &mut self.pct50.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 50, - exit, - )?; - compute_inner_percentile( - &mut self.pct60.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 60, - exit, - )?; - compute_inner_percentile( - &mut self.pct70.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 70, - exit, - )?; - compute_inner_percentile( - &mut self.pct80.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 80, - exit, - )?; - compute_inner_percentile( - &mut self.pct90.cents.height, - starting_height, - &self.pct5.cents.height, - &self.pct95.cents.height, - 90, + Ok(()) + }, exit, )?; self.compute_index(spot, indexer, exit)?; - - self.compute_score(components, spot, indexer, exit)?; - - Ok(()) + self.compute_score(components, spot, indexer, exit) } fn compute_index( @@ -221,45 +112,32 @@ impl RarityMeterInner { exit: &Exit, ) -> Result<()> { let starting_height = indexer.safe_lengths().height; - let bands = [ - &self.pct0_1.cents.height, - &self.pct0_5.cents.height, - &self.pct1.cents.height, - &self.pct2.cents.height, - &self.pct5.cents.height, - &self.pct95.cents.height, - &self.pct98.cents.height, - &self.pct99.cents.height, - &self.pct99_5.cents.height, - &self.pct99_9.cents.height, - ]; + let bands = self.prices.boundary_refs().map(|price| &price.cents.height); + let source_end = bands + .iter() + .map(|band| band.len()) + .min() + .unwrap_or_default() + .min(spot.len()); - let dep_version: Version = - bands.iter().map(|b| b.version()).sum::() + spot.version(); + self.index.height.compute_batched_to( + starting_height, + source_end, + self.prices.height.version() + spot.version(), + COMPUTE_BATCH_SIZE, + |index, range| { + let spot = spot.collect_range_at(range.start, range.end); + let bands = bands + .each_ref() + .map(|band| band.collect_range_at(range.start, range.end)); + for (offset, price) in spot.into_iter().enumerate() { + index.push(StoredI8::new(score_at(price, &bands, offset))); + } - self.index - .height - .validate_computed_version_or_reset(dep_version)?; - self.index.height.truncate_if_needed(starting_height)?; - - self.index.height.repeat_until_complete(exit, |vec| { - let skip = vec.len(); - let source_end = bands.iter().map(|b| b.len()).min().unwrap().min(spot.len()); - let end = vec.batch_end(source_end); - - if skip >= end { - return Ok(()); - } - - let spot_batch = spot.collect_range_at(skip, end); - let b: [Vec; 10] = bands.each_ref().map(|v| v.collect_range_at(skip, end)); - - for (j, price) in spot_batch.into_iter().enumerate() { - vec.push(StoredI8::new(score_at(price, &b, j))); - } - - Ok(()) - })?; + Ok(()) + }, + exit, + )?; Ok(()) } @@ -272,180 +150,81 @@ impl RarityMeterInner { exit: &Exit, ) -> Result<()> { let starting_height = indexer.safe_lengths().height; - let dep_version: Version = components + let dependency_version = components .iter() - .map(|component| { - component.pct0_1.price.cents.height.version() - + component.pct0_5.price.cents.height.version() - + component.pct1.price.cents.height.version() - + component.pct2.price.cents.height.version() - + component.pct5.price.cents.height.version() - + component.pct95.price.cents.height.version() - + component.pct98.price.cents.height.version() - + component.pct99.price.cents.height.version() - + component.pct99_5.price.cents.height.version() - + component.pct99_9.price.cents.height.version() - }) + .map(|component| component.boundary_version()) .sum::() + spot.version(); + let source_end = components + .iter() + .map(|component| component.boundary_len()) + .min() + .unwrap_or_default() + .min(spot.len()); - self.score - .height - .validate_computed_version_or_reset(dep_version)?; - self.score.height.truncate_if_needed(starting_height)?; - - self.score.height.repeat_until_complete(exit, |vec| { - let skip = vec.len(); - let source_end = components - .iter() - .flat_map(|component| { - [ - component.pct0_1.price.cents.height.len(), - component.pct0_5.price.cents.height.len(), - component.pct1.price.cents.height.len(), - component.pct2.price.cents.height.len(), - component.pct5.price.cents.height.len(), - component.pct95.price.cents.height.len(), - component.pct98.price.cents.height.len(), - component.pct99.price.cents.height.len(), - component.pct99_5.price.cents.height.len(), - component.pct99_9.price.cents.height.len(), - ] - }) - .min() - .unwrap() - .min(spot.len()); - let end = vec.batch_end(source_end); - - if skip >= end { - return Ok(()); - } - - let spot_batch = spot.collect_range_at(skip, end); - - let bands: Vec<[Vec; 10]> = components - .iter() - .map(|component| { - [ - component - .pct0_1 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct0_5 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct1 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct2 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct5 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct95 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct98 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct99 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct99_5 - .price - .cents - .height - .collect_range_at(skip, end), - component - .pct99_9 - .price - .cents - .height - .collect_range_at(skip, end), - ] - }) - .collect(); - - for (j, price) in spot_batch.into_iter().enumerate() { - let mut total: i8 = 0; - - for component in &bands { - total += score_at(price, component, j); + self.score.height.compute_batched_to( + starting_height, + source_end, + dependency_version, + COMPUTE_BATCH_SIZE, + |score, range| { + let spot = spot.collect_range_at(range.start, range.end); + let component_prices: Vec<_> = components + .iter() + .map(|component| component.collect_boundary_prices(range.start, range.end)) + .collect(); + for (offset, price) in spot.into_iter().enumerate() { + let value = component_prices + .iter() + .map(|bands| score_at(price, bands, offset)) + .sum(); + score.push(StoredI8::new(value)); } - vec.push(StoredI8::new(total)); - } - - Ok(()) - })?; + Ok(()) + }, + exit, + )?; Ok(()) } } -fn compute_inner_percentile( - out: &mut EagerVec>, - max_from: Height, - lower: &EagerVec>, - upper: &EagerVec>, - percentile: u8, - exit: &Exit, -) -> Result<()> { - debug_assert!((5..=95).contains(&percentile)); - let position = (f64::from(percentile) - 5.0) / 90.0; - - out.validate_and_truncate(lower.version() + upper.version(), max_from)?; - - out.repeat_until_complete(exit, |vec| { - let skip = vec.len(); - let source_end = lower.len().min(upper.len()); - let end = vec.batch_end(source_end); - if skip >= end { - return Ok(()); +fn combine_percentiles( + component_prices: &[[Vec; 10]], + offset: usize, +) -> [Cents; RARITY_PERCENTILES_LEN] { + let boundary_values = BOUNDARY_PERCENTILE_IDS.map(|id| { + let index = boundary_index(id).expect("boundary percentile"); + let values = component_prices + .iter() + .map(|component| component[index][offset]); + if is_lower_boundary(id) { + values.max().expect("rarity meter component") + } else { + values.min().expect("rarity meter component") } + }); + let lower = boundary_values[boundary_index(RarityPercentileId::Pct5).unwrap()]; + let upper = boundary_values[boundary_index(RarityPercentileId::Pct95).unwrap()]; - let lower_batch = lower.collect_range_at(skip, end); - let upper_batch = upper.collect_range_at(skip, end); - for j in 0..(end - skip) { - let lower = f64::from(lower_batch[j]); - let upper = f64::from(upper_batch[j]); - let value = if lower > 0.0 && upper > 0.0 { - (lower.ln() + position * (upper.ln() - lower.ln())).exp() - } else { - lower + position * (upper - lower) - }; - vec.push(Cents::from(value.round())); - } + RarityPercentileId::from_fn(|id| { + boundary_index(id) + .map(|index| boundary_values[index]) + .unwrap_or_else(|| interpolate(lower, upper, id.percentile())) + }) +} - Ok(()) - })?; - - Ok(()) +fn interpolate(lower: Cents, upper: Cents, percentile: f64) -> Cents { + let position = (percentile - 0.05) / 0.90; + let lower = f64::from(lower); + let upper = f64::from(upper); + let value = if lower > 0.0 && upper > 0.0 { + (lower.ln() + position * (upper.ln() - lower.ln())).exp() + } else { + lower + position * (upper - lower) + }; + Cents::from(value.round()) } fn score_at(price: Cents, bands: &[Vec; 10], index: usize) -> i8 { @@ -454,3 +233,32 @@ fn score_at(price: Cents, bands: &[Vec; 10], index: usize) -> i8 { upper - lower } + +#[cfg(test)] +mod tests { + use super::*; + + fn bands(values: [u64; 10]) -> [Vec; 10] { + values.map(|value| vec![Cents::from(value)]) + } + + #[test] + fn combines_tightest_boundaries_and_interpolates_inner_percentiles() { + use RarityPercentileId::*; + + let components = [ + bands([10, 20, 30, 40, 50, 500, 600, 700, 800, 900]), + bands([15, 25, 35, 45, 55, 450, 550, 650, 750, 850]), + ]; + let row = combine_percentiles(&components, 0); + + assert_eq!(*Pct0_1.get(&row), Cents::from(15_u64)); + assert_eq!(*Pct5.get(&row), Cents::from(55_u64)); + assert_eq!(*Pct95.get(&row), Cents::from(450_u64)); + assert_eq!(*Pct99_9.get(&row), Cents::from(850_u64)); + assert_eq!( + *Pct50.get(&row), + interpolate(Cents::from(55_u64), Cents::from(450_u64), 0.5) + ); + } +} diff --git a/crates/brk_computer/src/models/rarity_meter/mod.rs b/crates/brk_computer/src/models/rarity_meter/mod.rs index 583e24594..b0a3fdee7 100644 --- a/crates/brk_computer/src/models/rarity_meter/mod.rs +++ b/crates/brk_computer/src/models/rarity_meter/mod.rs @@ -30,6 +30,7 @@ pub struct RarityMeter { } const VERSION: Version = Version::new(7); +const COMPUTE_BATCH_SIZE: usize = 100_000; impl RarityMeter { pub(crate) fn forced_import( diff --git a/crates/brk_computer/src/models/rarity_meter/percentiles.rs b/crates/brk_computer/src/models/rarity_meter/percentiles.rs index 25eac1325..15a44c950 100644 --- a/crates/brk_computer/src/models/rarity_meter/percentiles.rs +++ b/crates/brk_computer/src/models/rarity_meter/percentiles.rs @@ -1,7 +1,168 @@ -use brk_types::StoredF32; +use brk_traversable::Traversable; +use brk_types::{RarityPercentileId, StoredF32}; use crate::internal::algo::FenwickTree; +pub(crate) const BOUNDARY_PERCENTILE_IDS: [RarityPercentileId; 10] = [ + RarityPercentileId::Pct0_1, + RarityPercentileId::Pct0_5, + RarityPercentileId::Pct1, + RarityPercentileId::Pct2, + RarityPercentileId::Pct5, + RarityPercentileId::Pct95, + RarityPercentileId::Pct98, + RarityPercentileId::Pct99, + RarityPercentileId::Pct99_5, + RarityPercentileId::Pct99_9, +]; + +pub(crate) const fn boundary_index(id: RarityPercentileId) -> Option { + use RarityPercentileId::*; + + match id { + Pct0_1 => Some(0), + Pct0_5 => Some(1), + Pct1 => Some(2), + Pct2 => Some(3), + Pct5 => Some(4), + Pct95 => Some(5), + Pct98 => Some(6), + Pct99 => Some(7), + Pct99_5 => Some(8), + Pct99_9 => Some(9), + _ => None, + } +} + +pub(crate) const fn is_lower_boundary(id: RarityPercentileId) -> bool { + matches!( + id, + RarityPercentileId::Pct0_1 + | RarityPercentileId::Pct0_5 + | RarityPercentileId::Pct1 + | RarityPercentileId::Pct2 + | RarityPercentileId::Pct5 + ) +} + +#[derive(Clone, Traversable)] +pub struct RarityPercentiles { + pub pct0_1: T, + pub pct0_5: T, + pub pct1: T, + pub pct2: T, + pub pct5: T, + pub pct10: T, + pub pct20: T, + pub pct30: T, + pub pct40: T, + pub pct50: T, + pub pct60: T, + pub pct70: T, + pub pct80: T, + pub pct90: T, + pub pct95: T, + pub pct98: T, + pub pct99: T, + pub pct99_5: T, + pub pct99_9: T, +} + +impl RarityPercentiles { + pub(crate) fn from_fn(mut f: impl FnMut(RarityPercentileId) -> T) -> Self { + use RarityPercentileId::*; + + Self { + pct0_1: f(Pct0_1), + pct0_5: f(Pct0_5), + pct1: f(Pct1), + pct2: f(Pct2), + pct5: f(Pct5), + pct10: f(Pct10), + pct20: f(Pct20), + pct30: f(Pct30), + pct40: f(Pct40), + pct50: f(Pct50), + pct60: f(Pct60), + pct70: f(Pct70), + pct80: f(Pct80), + pct90: f(Pct90), + pct95: f(Pct95), + pct98: f(Pct98), + pct99: f(Pct99), + pct99_5: f(Pct99_5), + pct99_9: f(Pct99_9), + } + } + + pub(crate) fn get(&self, id: RarityPercentileId) -> &T { + use RarityPercentileId::*; + + match id { + Pct0_1 => &self.pct0_1, + Pct0_5 => &self.pct0_5, + Pct1 => &self.pct1, + Pct2 => &self.pct2, + Pct5 => &self.pct5, + Pct10 => &self.pct10, + Pct20 => &self.pct20, + Pct30 => &self.pct30, + Pct40 => &self.pct40, + Pct50 => &self.pct50, + Pct60 => &self.pct60, + Pct70 => &self.pct70, + Pct80 => &self.pct80, + Pct90 => &self.pct90, + Pct95 => &self.pct95, + Pct98 => &self.pct98, + Pct99 => &self.pct99, + Pct99_5 => &self.pct99_5, + Pct99_9 => &self.pct99_9, + } + } + + pub(crate) fn boundary_refs(&self) -> [&T; 10] { + BOUNDARY_PERCENTILE_IDS.map(|id| self.get(id)) + } +} + +pub(crate) const fn suffix(id: RarityPercentileId) -> &'static str { + use RarityPercentileId::*; + + match id { + Pct0_1 => "pct0_1", + Pct0_5 => "pct0_5", + Pct1 => "pct1", + Pct2 => "pct2", + Pct5 => "pct5", + Pct10 => "pct10", + Pct20 => "pct20", + Pct30 => "pct30", + Pct40 => "pct40", + Pct50 => "pct50", + Pct60 => "pct60", + Pct70 => "pct70", + Pct80 => "pct80", + Pct90 => "pct90", + Pct95 => "pct95", + Pct98 => "pct98", + Pct99 => "pct99", + Pct99_5 => "pct99_5", + Pct99_9 => "pct99_9", + } +} + +pub(crate) const fn price_suffix(id: RarityPercentileId) -> &'static str { + use RarityPercentileId::*; + + match id { + Pct1 => "pct01", + Pct2 => "pct02", + Pct5 => "pct05", + _ => suffix(id), + } +} + /// First block included in the Rarity Meter distribution. pub const START_HEIGHT: usize = 210_000; @@ -111,6 +272,24 @@ impl BlockDecayPercentiles { mod tests { use super::*; + #[test] + fn schema_preserves_existing_names_and_boundaries() { + use RarityPercentileId::*; + + let percentiles = RarityPercentiles::from_fn(suffix); + assert_eq!(percentiles.pct0_1, "pct0_1"); + assert_eq!(percentiles.pct50, "pct50"); + assert_eq!(percentiles.pct99_9, "pct99_9"); + assert_eq!(price_suffix(Pct1), "pct01"); + assert_eq!(price_suffix(Pct2), "pct02"); + assert_eq!(price_suffix(Pct5), "pct05"); + + for (index, id) in BOUNDARY_PERCENTILE_IDS.into_iter().enumerate() { + assert_eq!(boundary_index(id), Some(index)); + } + assert!(boundary_index(Pct50).is_none()); + } + fn quantile(percentiles: &BlockDecayPercentiles, q: f64) -> f64 { let mut out = [0.0; 8]; percentiles.quantiles(&[q, q, q, q, q, q, q, q], &mut out); diff --git a/crates/brk_computer/src/op_return/breakdown.rs b/crates/brk_computer/src/op_return/breakdown.rs new file mode 100644 index 000000000..5d1297672 --- /dev/null +++ b/crates/brk_computer/src/op_return/breakdown.rs @@ -0,0 +1,346 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{ + Bytes, Height, OpReturnKind, OpReturnPolicyId, PartsPerMillion32, Sats, StoredU64, VSize, + Version, +}; +use derive_more::{Deref, DerefMut}; +use vecdb::{AnyVec, CachedBoxedVec, ColumnId, Database, Rw, StorageMode}; + +use super::{by_kind::ByKind, policy::Policy}; +use crate::{ + indexes, + internal::{ + CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, LazyPercentCumulativeRolling, LazyPercentPerBlock, + RatioBytes, RatioSats, Windows, + }, +}; + +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct BlockMetrics { + pub output_count: u64, + pub data_bytes: Bytes, + pub tx_count: u64, + pub tx_vsize: VSize, + pub fees: Sats, +} + +pub trait BreakdownAxis: ColumnId { + type Series: Clone + Traversable + where + T: Clone + Traversable + Send + Sync + 'static; + + fn series(create: impl FnMut(Self, &'static str) -> T) -> Self::Series + where + T: Clone + Traversable + Send + Sync + 'static; +} + +impl BreakdownAxis for OpReturnKind { + type Series + = ByKind + where + T: Clone + Traversable + Send + Sync + 'static; + + fn series(create: impl FnMut(Self, &'static str) -> T) -> Self::Series + where + T: Clone + Traversable + Send + Sync + 'static, + { + ByKind::new(create) + } +} + +impl BreakdownAxis for OpReturnPolicyId { + type Series + = Policy + where + T: Clone + Traversable + Send + Sync + 'static; + + fn series(create: impl FnMut(Self, &'static str) -> T) -> Self::Series + where + T: Clone + Traversable + Send + Sync + 'static, + { + Policy::new(create) + } +} + +#[derive(Clone, Deref, DerefMut, Traversable)] +pub struct DataBytesSeries { + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub data_bytes: LazyColumnPerBlockCumulativeRolling, + pub data_share: LazyPercentPerBlock, + pub chain_share: LazyPercentPerBlock, +} + +#[derive(Clone, Deref, DerefMut, Traversable)] +pub struct FeesSeries { + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub fees: LazyColumnPerBlockCumulativeRolling, + pub fee_share: LazyPercentCumulativeRolling, +} + +impl DataBytesSeries { + fn new( + prefix: &str, + version: Version, + data_bytes: LazyColumnPerBlockCumulativeRolling, + total_data: CachedBoxedVec, + block_size: CachedBoxedVec, + indexes: &indexes::Vecs, + ) -> Self { + let data_share = + LazyPercentPerBlock::from_cached_ratio::>( + &format!("{prefix}_data_share"), + version, + &data_bytes.cumulative.height, + total_data, + indexes, + ); + let chain_share = + LazyPercentPerBlock::from_cached_ratio::>( + &format!("{prefix}_chain_share"), + version, + &data_bytes.cumulative.height, + block_size, + indexes, + ); + + Self { + data_bytes, + data_share, + chain_share, + } + } +} + +impl FeesSeries { + fn new( + prefix: &str, + version: Version, + fees: LazyColumnPerBlockCumulativeRolling, + chain_fees: CachedBoxedVec, + cached_starts: &Windows<&CachedWindowStartVec>, + indexes: &indexes::Vecs, + ) -> Self { + let fee_share = LazyPercentCumulativeRolling::from_cumulative_ratio::< + Sats, + Sats, + RatioSats, + >( + &format!("{prefix}_fee_share"), + version, + &fees.cumulative.height, + chain_fees, + cached_starts, + indexes, + ); + + Self { fees, fee_share } + } +} + +#[derive(Traversable)] +pub struct BreakdownVecs { + pub output_count: ColumnarPerBlockCumulativeRolling< + StoredU64, + C, + C::Series>, + M, + >, + pub data_bytes: ColumnarPerBlockCumulativeRolling>, M>, + pub tx_count: ColumnarPerBlockCumulativeRolling< + StoredU64, + C, + C::Series>, + M, + >, + pub tx_vsize: ColumnarPerBlockCumulativeRolling< + VSize, + C, + C::Series>, + M, + >, + pub fees: ColumnarPerBlockCumulativeRolling>, M>, +} + +impl BreakdownVecs { + #[allow(clippy::too_many_arguments)] + pub(crate) fn forced_import( + db: &Database, + source_prefix: &str, + series_prefix: &str, + version: Version, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + total_data: &CachedBoxedVec, + block_size: &CachedBoxedVec, + chain_fees: &CachedBoxedVec, + ) -> Result { + let output_count = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &format!("{source_prefix}_output_count"), + version, + |source| { + C::series(|column, name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{series_prefix}_{name}_output_count"), + version, + source, + column, + indexes, + cached_starts, + ) + }) + }, + )?; + let data_bytes = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &format!("{source_prefix}_data_bytes"), + version, + |source| { + C::series(|column, name| { + let prefix = format!("{series_prefix}_{name}"); + let data_bytes = LazyColumnPerBlockCumulativeRolling::new( + &format!("{prefix}_data_bytes"), + version, + source, + column, + indexes, + cached_starts, + ); + DataBytesSeries::new( + &prefix, + version, + data_bytes, + total_data.clone(), + block_size.clone(), + indexes, + ) + }) + }, + )?; + let tx_count = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &format!("{source_prefix}_tx_count"), + version, + |source| { + C::series(|column, name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{series_prefix}_{name}_tx_count"), + version, + source, + column, + indexes, + cached_starts, + ) + }) + }, + )?; + let tx_vsize = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &format!("{source_prefix}_tx_vsize"), + version, + |source| { + C::series(|column, name| { + LazyColumnPerBlockCumulativeRolling::new( + &format!("{series_prefix}_{name}_tx_vsize"), + version, + source, + column, + indexes, + cached_starts, + ) + }) + }, + )?; + let fees = ColumnarPerBlockCumulativeRolling::forced_import( + db, + &format!("{source_prefix}_fees"), + version, + |source| { + C::series(|column, name| { + let prefix = format!("{series_prefix}_{name}"); + let fees = LazyColumnPerBlockCumulativeRolling::new( + &format!("{prefix}_fees"), + version, + source, + column, + indexes, + cached_starts, + ); + FeesSeries::new( + &prefix, + version, + fees, + chain_fees.clone(), + cached_starts, + indexes, + ) + }) + }, + )?; + + Ok(Self { + output_count, + data_bytes, + tx_count, + tx_vsize, + fees, + }) + } + + pub(crate) fn len(&self) -> usize { + self.output_count + .cumulative + .len() + .min(self.data_bytes.cumulative.len()) + .min(self.tx_count.cumulative.len()) + .min(self.tx_vsize.cumulative.len()) + .min(self.fees.cumulative.len()) + } + + pub(crate) fn push(&mut self, row: C::Row) { + self.output_count.push_block(C::map(row.clone(), |metrics| { + StoredU64::from(metrics.output_count) + })); + self.data_bytes + .push_block(C::map(row.clone(), |metrics| metrics.data_bytes)); + self.tx_count.push_block(C::map(row.clone(), |metrics| { + StoredU64::from(metrics.tx_count) + })); + self.tx_vsize + .push_block(C::map(row.clone(), |metrics| metrics.tx_vsize)); + self.fees.push_block(C::map(row, |metrics| metrics.fees)); + } + + pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { + self.output_count.validate_and_truncate(version, height)?; + self.data_bytes.validate_and_truncate(version, height)?; + self.tx_count.validate_and_truncate(version, height)?; + self.tx_vsize.validate_and_truncate(version, height)?; + self.fees.validate_and_truncate(version, height)?; + Ok(()) + } + + pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { + self.output_count.truncate_if_needed_at(len)?; + self.data_bytes.truncate_if_needed_at(len)?; + self.tx_count.truncate_if_needed_at(len)?; + self.tx_vsize.truncate_if_needed_at(len)?; + self.fees.truncate_if_needed_at(len)?; + Ok(()) + } + + pub(crate) fn write(&mut self) -> Result<()> { + self.output_count.write()?; + self.data_bytes.write()?; + self.tx_count.write()?; + self.tx_vsize.write()?; + self.fees.write()?; + Ok(()) + } +} diff --git a/crates/brk_computer/src/op_return/by_kind.rs b/crates/brk_computer/src/op_return/by_kind.rs index 83b58341d..d46a5b48b 100644 --- a/crates/brk_computer/src/op_return/by_kind.rs +++ b/crates/brk_computer/src/op_return/by_kind.rs @@ -3,39 +3,25 @@ use brk_types::OpReturnKind; macro_rules! define_by_kind { ($($field:ident => $kind:ident),+ $(,)?) => { - #[derive(Traversable)] + #[derive(Clone, Traversable)] pub struct ByKind { $(pub $field: T),+ } impl ByKind { - pub fn try_new( - mut create: impl FnMut(OpReturnKind, &'static str) -> Result, - ) -> Result { - Ok(Self { - $($field: create(OpReturnKind::$kind, stringify!($field))?),+ - }) + pub fn new(mut create: impl FnMut(OpReturnKind, &'static str) -> T) -> Self { + Self { + $($field: create(OpReturnKind::$kind, stringify!($field))),+ + } } pub fn iter(&self) -> impl Iterator { [$( &self.$field ),+].into_iter() } - pub fn iter_mut(&mut self) -> impl Iterator { - let Self { $($field),+ } = self; - [$($field),+].into_iter() - } - pub fn iter_typed(&self) -> impl Iterator { [$( (OpReturnKind::$kind, &self.$field) ),+].into_iter() } - - pub fn iter_typed_mut( - &mut self, - ) -> impl Iterator { - let Self { $($field),+ } = self; - [$( (OpReturnKind::$kind, $field) ),+].into_iter() - } } }; } @@ -68,16 +54,13 @@ define_by_kind! { #[cfg(test)] mod tests { - use std::convert::Infallible; - use brk_types::OpReturnKind; use super::ByKind; #[test] fn covers_every_kind_in_discriminant_order() { - let by_kind = - ByKind::try_new(|kind, _| Ok::<_, Infallible>(kind)).expect("infallible constructor"); + let by_kind = ByKind::new(|kind, _| kind); let kinds: Vec<_> = by_kind.iter_typed().collect(); assert_eq!(kinds.len(), OpReturnKind::Unknown as usize + 1); diff --git a/crates/brk_computer/src/op_return/compute.rs b/crates/brk_computer/src/op_return/compute.rs index 26b51e9fd..67025d9bc 100644 --- a/crates/brk_computer/src/op_return/compute.rs +++ b/crates/brk_computer/src/op_return/compute.rs @@ -1,37 +1,29 @@ use brk_error::Result; use brk_indexer::Indexer; -use brk_types::{OpReturnKind, Sats, VSize}; -use vecdb::{AnyVec, Exit, ReadableVec, VecIndex}; +use brk_types::{Bytes, OP_RETURN_KIND_COUNT, OpReturnKind, OpReturnPolicyId, Sats, VSize}; +use vecdb::{AnyVec, ColumnId, Exit, ReadableVec, VecIndex}; -use super::{Vecs, vecs::Totals}; +use super::{Vecs, breakdown::BlockMetrics, policy::Policy}; use crate::transactions; -const KIND_COUNT: usize = OpReturnKind::Unknown as usize + 1; -const OLD_STANDARD_MAX_POST_OP_RETURN_BYTES: u64 = 82; +const OLD_STANDARD_MAX_POST_OP_RETURN_BYTES: Bytes = Bytes::new(82); const WRITE_INTERVAL: usize = 10_000; - -#[derive(Clone, Copy, Default)] -struct PolicyTotals { - pre_v30_standard: Totals, - pre_v30_nonstandard: Totals, - oversized: Totals, - multiple: Totals, -} +const _: () = assert!(OP_RETURN_KIND_COUNT <= u32::BITS as usize); #[derive(Clone, Copy, Default)] struct Carrier { kinds: u32, output_count: u64, - data_bytes: u64, + data_bytes: Bytes, oversized_output_count: u64, - oversized_data_bytes: u64, + oversized_data_bytes: Bytes, vsize: VSize, fees: Sats, } impl Carrier { - fn add_output(&mut self, kind: OpReturnKind, data_bytes: u64) { - self.kinds |= kind_bit(kind); + fn add_output(&mut self, kind: OpReturnKind, data_bytes: Bytes) { + self.kinds |= Self::kind_bit(kind); self.output_count += 1; self.data_bytes += data_bytes; if data_bytes > OLD_STANDARD_MAX_POST_OP_RETURN_BYTES { @@ -39,6 +31,59 @@ impl Carrier { self.oversized_data_bytes += data_bytes; } } + + fn finalize_into( + self, + total: &mut BlockMetrics, + by_kind: &mut [BlockMetrics; OP_RETURN_KIND_COUNT], + policy: &mut Policy, + ) { + if self.output_count == 0 { + return; + } + + total.add_carrier(self); + let mut kinds = self.kinds; + while kinds != 0 { + let kind_index = kinds.trailing_zeros() as usize; + by_kind[kind_index].add_carrier(self); + kinds &= kinds - 1; + } + + if self.oversized_output_count > 0 { + policy.oversized.output_count += self.oversized_output_count; + policy.oversized.data_bytes += self.oversized_data_bytes; + policy.oversized.add_carrier(self); + } + + if self.output_count > 1 { + policy.multiple.output_count += self.output_count; + policy.multiple.data_bytes += self.data_bytes; + policy.multiple.add_carrier(self); + } + + if self.oversized_output_count > 0 || self.output_count > 1 { + policy.pre_v30_nonstandard.output_count += self.output_count; + policy.pre_v30_nonstandard.data_bytes += self.data_bytes; + policy.pre_v30_nonstandard.add_carrier(self); + } else { + policy.pre_v30_standard.output_count += self.output_count; + policy.pre_v30_standard.data_bytes += self.data_bytes; + policy.pre_v30_standard.add_carrier(self); + } + } + + const fn kind_bit(kind: OpReturnKind) -> u32 { + 1_u32 << kind as u8 + } +} + +impl BlockMetrics { + fn add_carrier(&mut self, carrier: Carrier) { + self.tx_count += 1; + self.tx_vsize += carrier.vsize; + self.fees += carrier.fees; + } } impl Vecs { @@ -88,20 +133,20 @@ impl Vecs { kind_cursor.advance(start - kind_cursor.position()); post_op_return_bytes.advance(start - post_op_return_bytes.position()); - let mut total = Totals::default(); - let mut by_kind = [Totals::default(); KIND_COUNT]; - let mut policy = PolicyTotals::default(); + let mut total = BlockMetrics::default(); + let mut by_kind = [BlockMetrics::default(); OP_RETURN_KIND_COUNT]; + let mut policy = Policy::default(); let mut current_tx = None; let mut carrier = Carrier::default(); for _ in start..block_end { let tx_index = tx_cursor.next().unwrap(); let kind = kind_cursor.next().unwrap(); - let bytes = u32::from(post_op_return_bytes.next().unwrap()) as u64; - let kind_index = kind as usize; + let bytes = Bytes::from(u32::from(post_op_return_bytes.next().unwrap())); + let kind_index = kind.index(); if current_tx != Some(tx_index) { - finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier); + carrier.finalize_into(&mut total, &mut by_kind, &mut policy); current_tx = Some(tx_index); carrier = Carrier::default(); @@ -118,18 +163,12 @@ impl Vecs { carrier.add_output(kind, bytes); } - finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier); + carrier.finalize_into(&mut total, &mut by_kind, &mut policy); self.total.push(total); - for (kind, metrics) in self.by_kind.iter_typed_mut() { - metrics.push(by_kind[kind as usize]); - } - self.policy.pre_v30_standard.push(policy.pre_v30_standard); + self.by_kind.push(by_kind); self.policy - .pre_v30_nonstandard - .push(policy.pre_v30_nonstandard); - self.policy.oversized.push(policy.oversized); - self.policy.multiple.push(policy.multiple); + .push(OpReturnPolicyId::from_fn(|id| *policy.get(id))); if (height + 1).is_multiple_of(WRITE_INTERVAL) { let _lock = exit.lock(); @@ -151,82 +190,31 @@ impl Vecs { } } -fn finalize_transaction( - total: &mut Totals, - by_kind: &mut [Totals; KIND_COUNT], - policy: &mut PolicyTotals, - carrier: Carrier, -) { - if carrier.output_count == 0 { - return; - } - - add_carrier(total, carrier); - let mut kinds = carrier.kinds; - while kinds != 0 { - let kind_index = kinds.trailing_zeros() as usize; - add_carrier(&mut by_kind[kind_index], carrier); - kinds &= kinds - 1; - } - - if carrier.oversized_output_count > 0 { - policy.oversized.output_count += carrier.oversized_output_count; - policy.oversized.data_bytes += carrier.oversized_data_bytes; - add_carrier(&mut policy.oversized, carrier); - } - - if carrier.output_count > 1 { - policy.multiple.output_count += carrier.output_count; - policy.multiple.data_bytes += carrier.data_bytes; - add_carrier(&mut policy.multiple, carrier); - } - - if carrier.oversized_output_count > 0 || carrier.output_count > 1 { - policy.pre_v30_nonstandard.output_count += carrier.output_count; - policy.pre_v30_nonstandard.data_bytes += carrier.data_bytes; - add_carrier(&mut policy.pre_v30_nonstandard, carrier); - } else { - policy.pre_v30_standard.output_count += carrier.output_count; - policy.pre_v30_standard.data_bytes += carrier.data_bytes; - add_carrier(&mut policy.pre_v30_standard, carrier); - } -} - -fn add_carrier(metrics: &mut Totals, carrier: Carrier) { - metrics.tx_count += 1; - metrics.tx_vsize += carrier.vsize; - metrics.fees += carrier.fees; -} - -const fn kind_bit(kind: OpReturnKind) -> u32 { - 1_u32 << kind as u8 -} - #[cfg(test)] mod tests { use super::*; #[test] fn multiple_kinds_count_one_total_carrier() { - let mut total = Totals::default(); - let mut by_kind = [Totals::default(); KIND_COUNT]; - let mut policy = PolicyTotals::default(); + let mut total = BlockMetrics::default(); + let mut by_kind = [BlockMetrics::default(); OP_RETURN_KIND_COUNT]; + let mut policy = Policy::default(); let mut carrier = Carrier { vsize: VSize::new(100), fees: Sats::new(500), ..Carrier::default() }; - carrier.add_output(OpReturnKind::Runes, 15); - carrier.add_output(OpReturnKind::Omni, 15); + carrier.add_output(OpReturnKind::Runes, Bytes::new(15)); + carrier.add_output(OpReturnKind::Omni, Bytes::new(15)); - finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier); + carrier.finalize_into(&mut total, &mut by_kind, &mut policy); assert_eq!(total.tx_count, 1); - assert_eq!(by_kind[OpReturnKind::Runes as usize].tx_count, 1); - assert_eq!(by_kind[OpReturnKind::Omni as usize].tx_count, 1); + assert_eq!(by_kind[OpReturnKind::Runes.index()].tx_count, 1); + assert_eq!(by_kind[OpReturnKind::Omni.index()].tx_count, 1); assert_eq!(total.fees, Sats::new(500)); - assert_eq!(by_kind[OpReturnKind::Runes as usize].fees, Sats::new(500)); - assert_eq!(by_kind[OpReturnKind::Omni as usize].fees, Sats::new(500)); + assert_eq!(by_kind[OpReturnKind::Runes.index()].fees, Sats::new(500)); + assert_eq!(by_kind[OpReturnKind::Omni.index()].fees, Sats::new(500)); assert_eq!(policy.multiple.fees, Sats::new(500)); assert_eq!(policy.pre_v30_nonstandard.fees, Sats::new(500)); assert_eq!(policy.multiple.output_count, 2); @@ -237,16 +225,16 @@ mod tests { #[test] fn oversized_output_marks_pre_v30_nonstandard_once() { - let mut total = Totals::default(); - let mut by_kind = [Totals::default(); KIND_COUNT]; - let mut policy = PolicyTotals::default(); + let mut total = BlockMetrics::default(); + let mut by_kind = [BlockMetrics::default(); OP_RETURN_KIND_COUNT]; + let mut policy = Policy::default(); let mut carrier = Carrier { vsize: VSize::new(120), ..Carrier::default() }; - carrier.add_output(OpReturnKind::Unknown, 83); + carrier.add_output(OpReturnKind::Unknown, Bytes::new(83)); - finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier); + carrier.finalize_into(&mut total, &mut by_kind, &mut policy); assert_eq!(policy.oversized.output_count, 1); assert_eq!(policy.oversized.tx_vsize, VSize::new(120)); @@ -257,19 +245,19 @@ mod tests { #[test] fn standard_output_is_recorded_directly() { - let mut total = Totals::default(); - let mut by_kind = [Totals::default(); KIND_COUNT]; - let mut policy = PolicyTotals::default(); + let mut total = BlockMetrics::default(); + let mut by_kind = [BlockMetrics::default(); OP_RETURN_KIND_COUNT]; + let mut policy = Policy::default(); let mut carrier = Carrier { vsize: VSize::new(100), ..Carrier::default() }; - carrier.add_output(OpReturnKind::Runes, 15); + carrier.add_output(OpReturnKind::Runes, Bytes::new(15)); - finalize_transaction(&mut total, &mut by_kind, &mut policy, carrier); + carrier.finalize_into(&mut total, &mut by_kind, &mut policy); assert_eq!(policy.pre_v30_standard.output_count, 1); - assert_eq!(policy.pre_v30_standard.data_bytes, 15); + assert_eq!(policy.pre_v30_standard.data_bytes, Bytes::new(15)); assert_eq!(policy.pre_v30_standard.tx_count, 1); assert_eq!(policy.pre_v30_standard.tx_vsize, VSize::new(100)); assert_eq!(policy.pre_v30_nonstandard.tx_count, 0); diff --git a/crates/brk_computer/src/op_return/import.rs b/crates/brk_computer/src/op_return/import.rs index df7c197d9..8bfbc02b0 100644 --- a/crates/brk_computer/src/op_return/import.rs +++ b/crates/brk_computer/src/op_return/import.rs @@ -4,7 +4,7 @@ use brk_error::Result; use brk_types::{Height, Sats, StoredU64, Version}; use vecdb::CachedBoxedVec; -use super::{ByKind, Policy, Total, Vecs, vecs::BreakdownImporter}; +use super::{Vecs, breakdown::BreakdownVecs, total::Total}; use crate::{ indexes, internal::{ @@ -32,18 +32,30 @@ impl Vecs { &block_size, &chain_fees, )?; + let columnar_version = version + Version::ONE; let total_data = total.cached_data_bytes(); - let breakdowns = BreakdownImporter::new( + let by_kind = BreakdownVecs::forced_import( &db, - version, + "op_return_cumulative_by_kind", + "op_return", + columnar_version, indexes, cached_starts, &total_data, &block_size, &chain_fees, - ); - let by_kind = ByKind::try_new(|_, name| breakdowns.import(&format!("op_return_{name}")))?; - let policy = Policy::forced_import(&breakdowns)?; + )?; + let policy = BreakdownVecs::forced_import( + &db, + "op_return_cumulative_policy", + "op_return_policy", + columnar_version, + indexes, + cached_starts, + &total_data, + &block_size, + &chain_fees, + )?; let this = Self { db, diff --git a/crates/brk_computer/src/op_return/mod.rs b/crates/brk_computer/src/op_return/mod.rs index dd2c5bfd5..fe0d2c395 100644 --- a/crates/brk_computer/src/op_return/mod.rs +++ b/crates/brk_computer/src/op_return/mod.rs @@ -1,9 +1,11 @@ +mod breakdown; mod by_kind; mod compute; mod import; +mod policy; +mod total; mod vecs; -pub use by_kind::ByKind; -pub use vecs::{Policy, Total, Vecs}; +pub use vecs::Vecs; pub const DB_NAME: &str = "op_return"; diff --git a/crates/brk_computer/src/op_return/policy.rs b/crates/brk_computer/src/op_return/policy.rs new file mode 100644 index 000000000..9afce54d8 --- /dev/null +++ b/crates/brk_computer/src/op_return/policy.rs @@ -0,0 +1,30 @@ +use brk_traversable::Traversable; +use brk_types::OpReturnPolicyId; + +#[derive(Clone, Copy, Default, Traversable)] +pub struct Policy { + pub pre_v30_standard: T, + pub pre_v30_nonstandard: T, + pub oversized: T, + pub multiple: T, +} + +impl Policy { + pub fn new(mut create: impl FnMut(OpReturnPolicyId, &'static str) -> T) -> Self { + Self { + pre_v30_standard: create(OpReturnPolicyId::PreV30Standard, "pre_v30_standard"), + pre_v30_nonstandard: create(OpReturnPolicyId::PreV30Nonstandard, "pre_v30_nonstandard"), + oversized: create(OpReturnPolicyId::Oversized, "oversized"), + multiple: create(OpReturnPolicyId::Multiple, "multiple"), + } + } + + pub fn get(&self, policy: OpReturnPolicyId) -> &T { + match policy { + OpReturnPolicyId::PreV30Standard => &self.pre_v30_standard, + OpReturnPolicyId::PreV30Nonstandard => &self.pre_v30_nonstandard, + OpReturnPolicyId::Oversized => &self.oversized, + OpReturnPolicyId::Multiple => &self.multiple, + } + } +} diff --git a/crates/brk_computer/src/op_return/total.rs b/crates/brk_computer/src/op_return/total.rs new file mode 100644 index 000000000..232126a49 --- /dev/null +++ b/crates/brk_computer/src/op_return/total.rs @@ -0,0 +1,169 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{Bytes, Height, PartsPerMillion32, Sats, StoredU64, VSize, Version}; +use vecdb::{AnyVec, CachedBoxedVec, Database, ReadOnlyClone, Rw, StorageMode}; + +use super::breakdown::BlockMetrics; +use crate::{ + indexes, + internal::{ + CachedPerBlockCumulativeRolling, CachedWindowStartVec, LazyPercentCumulativeRolling, + LazyPercentPerBlock, PerBlockCumulativeRolling, RatioBytes, RatioSats, Windows, + }, +}; + +#[derive(Traversable)] +pub struct Total { + pub data_bytes: CachedPerBlockCumulativeRolling, + pub tx_count: PerBlockCumulativeRolling, + pub tx_vsize: PerBlockCumulativeRolling, + pub fees: PerBlockCumulativeRolling, + pub chain_share: LazyPercentPerBlock, + pub fee_share: LazyPercentCumulativeRolling, +} + +impl Total { + pub(crate) fn forced_import( + db: &Database, + prefix: &str, + version: Version, + indexes: &indexes::Vecs, + cached_starts: &Windows<&CachedWindowStartVec>, + block_size: &CachedBoxedVec, + chain_fees: &CachedBoxedVec, + ) -> Result { + let data_bytes = CachedPerBlockCumulativeRolling::forced_import( + db, + &format!("{prefix}_data_bytes"), + version, + indexes, + cached_starts, + )?; + let tx_count = PerBlockCumulativeRolling::forced_import( + db, + &format!("{prefix}_tx_count"), + version, + indexes, + cached_starts, + )?; + let tx_vsize = PerBlockCumulativeRolling::forced_import( + db, + &format!("{prefix}_tx_vsize"), + version, + indexes, + cached_starts, + )?; + let fees = PerBlockCumulativeRolling::forced_import( + db, + &format!("{prefix}_fees"), + version, + indexes, + cached_starts, + )?; + + Ok(Self { + chain_share: Self::lazy_chain_share( + prefix, + version, + &data_bytes, + block_size.clone(), + indexes, + ), + fee_share: Self::lazy_fee_share( + prefix, + version, + &fees, + chain_fees.clone(), + cached_starts, + indexes, + ), + data_bytes, + tx_count, + tx_vsize, + fees, + }) + } + + fn lazy_chain_share( + prefix: &str, + version: Version, + data_bytes: &CachedPerBlockCumulativeRolling, + block_size: CachedBoxedVec, + indexes: &indexes::Vecs, + ) -> LazyPercentPerBlock { + let data_bytes = data_bytes.cumulative.height.read_only_clone(); + LazyPercentPerBlock::from_cached_ratio::>( + &format!("{prefix}_chain_share"), + version, + &data_bytes, + block_size, + indexes, + ) + } + + fn lazy_fee_share( + prefix: &str, + version: Version, + fees: &PerBlockCumulativeRolling, + chain_fees: CachedBoxedVec, + cached_starts: &Windows<&CachedWindowStartVec>, + indexes: &indexes::Vecs, + ) -> LazyPercentCumulativeRolling { + LazyPercentCumulativeRolling::from_cumulative_ratio::< + Sats, + Sats, + RatioSats, + >( + &format!("{prefix}_fee_share"), + version, + &fees.cumulative.height, + chain_fees, + cached_starts, + indexes, + ) + } + + pub(crate) fn cached_data_bytes(&self) -> CachedBoxedVec { + self.data_bytes.cached_cumulative() + } + + pub(crate) fn len(&self) -> usize { + self.data_bytes + .block + .len() + .min(self.tx_count.block.len()) + .min(self.tx_vsize.block.len()) + .min(self.fees.block.len()) + } + + pub(super) fn push(&mut self, block: BlockMetrics) { + self.data_bytes.push_block(block.data_bytes); + self.tx_count.push_block(block.tx_count.into()); + self.tx_vsize.push_block(block.tx_vsize); + self.fees.push_block(block.fees); + } + + pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { + self.data_bytes.validate_and_truncate(version, height)?; + self.tx_count.validate_and_truncate(version, height)?; + self.tx_vsize.validate_and_truncate(version, height)?; + self.fees.validate_and_truncate(version, height)?; + Ok(()) + } + + pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { + self.data_bytes.truncate_if_needed_at(len)?; + self.tx_count.truncate_if_needed_at(len)?; + self.tx_vsize.truncate_if_needed_at(len)?; + self.fees.truncate_if_needed_at(len)?; + Ok(()) + } + + pub(crate) fn write(&mut self) -> Result<()> { + self.data_bytes.write()?; + self.tx_count.write()?; + self.tx_vsize.write()?; + self.fees.write()?; + Ok(()) + } +} diff --git a/crates/brk_computer/src/op_return/vecs.rs b/crates/brk_computer/src/op_return/vecs.rs index f403263a8..f1724047a 100644 --- a/crates/brk_computer/src/op_return/vecs.rs +++ b/crates/brk_computer/src/op_return/vecs.rs @@ -1,541 +1,45 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Height, PartsPerMillion32, Sats, StoredU64, VSize, Version}; -use derive_more::{Deref, DerefMut}; -use vecdb::{AnyVec, CachedBoxedVec, Database, ReadOnlyClone, Rw, StorageMode}; +use brk_types::{Height, OpReturnKind, OpReturnPolicyId, Version}; +use vecdb::{Database, Rw, StorageMode}; -use super::ByKind; -use crate::{ - indexes, - internal::{ - CachedPerBlockCumulativeRolling, CachedWindowStartVec, LazyPercentCumulativeRolling, - LazyPercentPerBlock, PerBlockCumulativeRolling, RatioSats, RatioU64, Windows, - }, -}; - -#[derive(Clone, Copy, Default)] -pub(super) struct Totals { - pub output_count: u64, - pub data_bytes: u64, - pub tx_count: u64, - pub tx_vsize: VSize, - pub fees: Sats, -} - -#[derive(Traversable)] -pub struct TotalMetrics { - pub data_bytes: PerBlockCumulativeRolling, - pub tx_count: PerBlockCumulativeRolling, - pub tx_vsize: PerBlockCumulativeRolling, - /// Full fees paid by carrier transactions. A transaction carrying multiple - /// kinds contributes its fee to each kind, matching `tx_count` and `tx_vsize`. - pub fees: PerBlockCumulativeRolling, -} - -impl TotalMetrics { - pub(crate) fn forced_import( - db: &Database, - prefix: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result { - Ok(Self { - data_bytes: PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_data_bytes"), - version, - indexes, - cached_starts, - )?, - tx_count: PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_tx_count"), - version, - indexes, - cached_starts, - )?, - tx_vsize: PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_tx_vsize"), - version, - indexes, - cached_starts, - )?, - fees: PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_fees"), - version, - indexes, - cached_starts, - )?, - }) - } - - fn len(&self) -> usize { - self.data_bytes - .block - .len() - .min(self.tx_count.block.len()) - .min(self.tx_vsize.block.len()) - .min(self.fees.block.len()) - } - - fn lazy_fee_share( - &self, - prefix: &str, - version: Version, - chain_fees: CachedBoxedVec, - cached_starts: &Windows<&CachedWindowStartVec>, - indexes: &indexes::Vecs, - ) -> LazyPercentCumulativeRolling { - LazyPercentCumulativeRolling::from_cumulative_ratio::< - Sats, - Sats, - RatioSats, - >( - &format!("{prefix}_fee_share"), - version, - &self.fees.cumulative.height, - chain_fees, - cached_starts, - indexes, - ) - } - - pub(super) fn push(&mut self, block: Totals) { - self.data_bytes.push_block(block.data_bytes.into()); - self.tx_count.push_block(block.tx_count.into()); - self.tx_vsize.push_block(block.tx_vsize); - self.fees.push_block(block.fees); - } - - fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { - self.data_bytes.validate_and_truncate(version, height)?; - self.tx_count.validate_and_truncate(version, height)?; - self.tx_vsize.validate_and_truncate(version, height)?; - self.fees.validate_and_truncate(version, height)?; - Ok(()) - } - - fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.data_bytes.truncate_if_needed_at(len)?; - self.tx_count.truncate_if_needed_at(len)?; - self.tx_vsize.truncate_if_needed_at(len)?; - self.fees.truncate_if_needed_at(len)?; - Ok(()) - } - - fn write(&mut self) -> Result<()> { - self.data_bytes.write()?; - self.tx_count.write()?; - self.tx_vsize.write()?; - self.fees.write()?; - Ok(()) - } -} - -#[derive(Traversable)] -pub struct Total { - pub data_bytes: CachedPerBlockCumulativeRolling, - pub tx_count: PerBlockCumulativeRolling, - pub tx_vsize: PerBlockCumulativeRolling, - /// Full fees paid by carrier transactions. A transaction carrying multiple - /// kinds contributes its fee to each kind, matching `tx_count` and `tx_vsize`. - pub fees: PerBlockCumulativeRolling, - pub chain_share: LazyPercentPerBlock, - /// Share of all transaction fees, based on cumulative and rolling fee sums. - pub fee_share: LazyPercentCumulativeRolling, -} - -impl Total { - pub(crate) fn forced_import( - db: &Database, - prefix: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - block_size: &CachedBoxedVec, - chain_fees: &CachedBoxedVec, - ) -> Result { - let data_bytes = CachedPerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_data_bytes"), - version, - indexes, - cached_starts, - )?; - let tx_count = PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_tx_count"), - version, - indexes, - cached_starts, - )?; - let tx_vsize = PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_tx_vsize"), - version, - indexes, - cached_starts, - )?; - let fees = PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_fees"), - version, - indexes, - cached_starts, - )?; - - Ok(Self { - chain_share: Self::lazy_chain_share( - prefix, - version, - &data_bytes, - block_size.clone(), - indexes, - ), - fee_share: Self::lazy_fee_share( - prefix, - version, - &fees, - chain_fees.clone(), - cached_starts, - indexes, - ), - data_bytes, - tx_count, - tx_vsize, - fees, - }) - } - - fn lazy_chain_share( - prefix: &str, - version: Version, - data_bytes: &CachedPerBlockCumulativeRolling, - block_size: CachedBoxedVec, - indexes: &indexes::Vecs, - ) -> LazyPercentPerBlock { - let data_bytes = data_bytes.cumulative.height.read_only_clone(); - LazyPercentPerBlock::from_cached_ratio::>( - &format!("{prefix}_chain_share"), - version, - &data_bytes, - block_size, - indexes, - ) - } - - fn lazy_fee_share( - prefix: &str, - version: Version, - fees: &PerBlockCumulativeRolling, - chain_fees: CachedBoxedVec, - cached_starts: &Windows<&CachedWindowStartVec>, - indexes: &indexes::Vecs, - ) -> LazyPercentCumulativeRolling { - LazyPercentCumulativeRolling::from_cumulative_ratio::< - Sats, - Sats, - RatioSats, - >( - &format!("{prefix}_fee_share"), - version, - &fees.cumulative.height, - chain_fees, - cached_starts, - indexes, - ) - } - - pub(crate) fn cached_data_bytes(&self) -> CachedBoxedVec { - self.data_bytes.cached_cumulative() - } - - fn len(&self) -> usize { - self.data_bytes - .block - .len() - .min(self.tx_count.block.len()) - .min(self.tx_vsize.block.len()) - .min(self.fees.block.len()) - } - - pub(super) fn push(&mut self, block: Totals) { - self.data_bytes.push_block(block.data_bytes.into()); - self.tx_count.push_block(block.tx_count.into()); - self.tx_vsize.push_block(block.tx_vsize); - self.fees.push_block(block.fees); - } - - fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { - self.data_bytes.validate_and_truncate(version, height)?; - self.tx_count.validate_and_truncate(version, height)?; - self.tx_vsize.validate_and_truncate(version, height)?; - self.fees.validate_and_truncate(version, height)?; - Ok(()) - } - - fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.data_bytes.truncate_if_needed_at(len)?; - self.tx_count.truncate_if_needed_at(len)?; - self.tx_vsize.truncate_if_needed_at(len)?; - self.fees.truncate_if_needed_at(len)?; - Ok(()) - } - - fn write(&mut self) -> Result<()> { - self.data_bytes.write()?; - self.tx_count.write()?; - self.tx_vsize.write()?; - self.fees.write()?; - Ok(()) - } -} - -#[derive(Traversable)] -pub struct Metrics { - pub output_count: PerBlockCumulativeRolling, - #[traversable(flatten)] - pub total: TotalMetrics, -} - -impl Metrics { - pub(crate) fn forced_import( - db: &Database, - prefix: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result { - Ok(Self { - output_count: PerBlockCumulativeRolling::forced_import( - db, - &format!("{prefix}_output_count"), - version, - indexes, - cached_starts, - )?, - total: TotalMetrics::forced_import(db, prefix, version, indexes, cached_starts)?, - }) - } - - fn len(&self) -> usize { - self.output_count.block.len().min(self.total.len()) - } - - fn lazy_data_share( - &self, - name: &str, - version: Version, - denominator: CachedBoxedVec, - indexes: &indexes::Vecs, - ) -> LazyPercentPerBlock { - LazyPercentPerBlock::from_cached_ratio::>( - name, - version, - &self.total.data_bytes.cumulative.height, - denominator, - indexes, - ) - } - - pub(super) fn push(&mut self, block: Totals) { - self.output_count.push_block(block.output_count.into()); - self.total.push(block); - } - - fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { - self.output_count.validate_and_truncate(version, height)?; - self.total.validate_and_truncate(version, height)?; - Ok(()) - } - - fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.output_count.truncate_if_needed_at(len)?; - self.total.truncate_if_needed_at(len)?; - Ok(()) - } - - fn write(&mut self) -> Result<()> { - self.output_count.write()?; - self.total.write()?; - Ok(()) - } -} - -#[derive(Deref, DerefMut, Traversable)] -pub struct Breakdown { - #[deref] - #[deref_mut] - #[traversable(flatten)] - pub metrics: Metrics, - pub data_share: LazyPercentPerBlock, - pub chain_share: LazyPercentPerBlock, - /// Share of all transaction fees, based on cumulative and rolling fee sums. - pub fee_share: LazyPercentCumulativeRolling, -} - -pub(super) struct BreakdownImporter<'a> { - db: &'a Database, - version: Version, - indexes: &'a indexes::Vecs, - cached_starts: &'a Windows<&'a CachedWindowStartVec>, - total_data: &'a CachedBoxedVec, - block_size: &'a CachedBoxedVec, - chain_fees: &'a CachedBoxedVec, -} - -impl<'a> BreakdownImporter<'a> { - pub(super) fn new( - db: &'a Database, - version: Version, - indexes: &'a indexes::Vecs, - cached_starts: &'a Windows<&'a CachedWindowStartVec>, - total_data: &'a CachedBoxedVec, - block_size: &'a CachedBoxedVec, - chain_fees: &'a CachedBoxedVec, - ) -> Self { - Self { - db, - version, - indexes, - cached_starts, - total_data, - block_size, - chain_fees, - } - } - - pub(super) fn import(&self, prefix: &str) -> Result { - let metrics = Metrics::forced_import( - self.db, - prefix, - self.version, - self.indexes, - self.cached_starts, - )?; - let fee_share = metrics.total.lazy_fee_share( - prefix, - self.version, - self.chain_fees.clone(), - self.cached_starts, - self.indexes, - ); - - Ok(Breakdown { - data_share: metrics.lazy_data_share( - &format!("{prefix}_data_share"), - self.version, - self.total_data.clone(), - self.indexes, - ), - chain_share: metrics.lazy_data_share( - &format!("{prefix}_chain_share"), - self.version, - self.block_size.clone(), - self.indexes, - ), - metrics, - fee_share, - }) - } -} - -#[derive(Traversable)] -pub struct Policy { - pub pre_v30_standard: Breakdown, - pub pre_v30_nonstandard: Breakdown, - pub oversized: Breakdown, - pub multiple: Breakdown, -} - -impl Policy { - pub(super) fn forced_import(importer: &BreakdownImporter<'_>) -> Result { - let import = |name| importer.import(&format!("op_return_policy_{name}")); - - Ok(Self { - pre_v30_standard: import("pre_v30_standard")?, - pre_v30_nonstandard: import("pre_v30_nonstandard")?, - oversized: import("oversized")?, - multiple: import("multiple")?, - }) - } - - fn iter(&self) -> impl Iterator { - [ - &self.pre_v30_standard, - &self.pre_v30_nonstandard, - &self.oversized, - &self.multiple, - ] - .into_iter() - } - - pub(super) fn iter_mut(&mut self) -> impl Iterator { - [ - &mut self.pre_v30_standard, - &mut self.pre_v30_nonstandard, - &mut self.oversized, - &mut self.multiple, - ] - .into_iter() - } -} +use super::{breakdown::BreakdownVecs, total::Total}; #[derive(Traversable)] pub struct Vecs { #[traversable(skip)] pub(crate) db: Database, pub total: Total, - pub by_kind: ByKind>, - pub policy: Policy, + pub by_kind: BreakdownVecs, + pub policy: BreakdownVecs, } impl Vecs { pub(crate) fn min_len(&self) -> usize { - let len = self - .by_kind - .iter() - .map(|metrics| metrics.len()) - .fold(self.total.len(), usize::min); - self.policy - .iter() - .map(|metrics| metrics.len()) - .fold(len, usize::min) + self.total + .len() + .min(self.by_kind.len()) + .min(self.policy.len()) } pub(crate) fn validate_and_truncate(&mut self, version: Version, height: Height) -> Result<()> { self.total.validate_and_truncate(version, height)?; - for metrics in self.by_kind.iter_mut() { - metrics.validate_and_truncate(version, height)?; - } - for metrics in self.policy.iter_mut() { - metrics.validate_and_truncate(version, height)?; - } + self.by_kind.validate_and_truncate(version, height)?; + self.policy.validate_and_truncate(version, height)?; Ok(()) } pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { self.total.truncate_if_needed_at(len)?; - for metrics in self.by_kind.iter_mut() { - metrics.truncate_if_needed_at(len)?; - } - for metrics in self.policy.iter_mut() { - metrics.truncate_if_needed_at(len)?; - } + self.by_kind.truncate_if_needed_at(len)?; + self.policy.truncate_if_needed_at(len)?; Ok(()) } pub(crate) fn write(&mut self) -> Result<()> { self.total.write()?; - for metrics in self.by_kind.iter_mut() { - metrics.write()?; - } - for metrics in self.policy.iter_mut() { - metrics.write()?; - } + self.by_kind.write()?; + self.policy.write()?; Ok(()) } } diff --git a/crates/brk_computer/src/outputs/by_type/compute.rs b/crates/brk_computer/src/outputs/by_type/compute.rs index d0f5456be..f4fe0114b 100644 --- a/crates/brk_computer/src/outputs/by_type/compute.rs +++ b/crates/brk_computer/src/outputs/by_type/compute.rs @@ -1,6 +1,8 @@ +use brk_cohort::OutputTypeId; use brk_error::{OptionData, Result}; use brk_indexer::Indexer; -use vecdb::{AnyVec, Exit, ReadableVec, VecIndex}; +use brk_types::{StoredU16, StoredU64}; +use vecdb::{AnyVec, ColumnId, Exit, ReadableVec, VecIndex}; use super::Vecs; use crate::internal::{CoinbasePolicy, walk_blocks}; @@ -18,13 +20,15 @@ impl Vecs { self.output_count .validate_and_truncate(dep_version, starting_lengths.height)?; + self.output_count.clear(); self.tx_count .validate_and_truncate(dep_version, starting_lengths.height)?; let skip = self .output_count - .min_stateful_len() - .min(self.tx_count.min_stateful_len()); + .height + .len() + .min(self.tx_count.cumulative.len()); let first_tx_index = &indexer.vecs().transactions.first_tx_index; let end = first_tx_index.len(); @@ -65,8 +69,15 @@ impl Vecs { Ok(()) }, |agg| { - self.output_count.push_block(&agg.entries_per_type); - self.tx_count.push_block(&agg.txs_per_type); + self.output_count.push(OutputTypeId::from_fn(|column| { + let value = agg.entries_per_type[column.output_type() as usize]; + debug_assert!(u16::try_from(value).is_ok()); + StoredU16::new(value as u16) + })); + self.tx_count.push_block(OutputTypeId::from_fn(|column| { + StoredU64::from(agg.txs_per_type[column.output_type() as usize]) + })); + height += 1; if height.is_multiple_of(WRITE_INTERVAL) { let _lock = exit.lock(); diff --git a/crates/brk_computer/src/outputs/by_type/import.rs b/crates/brk_computer/src/outputs/by_type/import.rs index 2ec6c4530..e61b3fd16 100644 --- a/crates/brk_computer/src/outputs/by_type/import.rs +++ b/crates/brk_computer/src/outputs/by_type/import.rs @@ -1,11 +1,14 @@ +use brk_cohort::OutputTypeId; use brk_error::Result; -use brk_types::Version; +use brk_types::{StoredU16, StoredU64, Version}; use vecdb::Database; use super::{CachedSpendableOutputCount, Vecs, WithOutputTypes, with_output_types::identity}; use crate::{ indexes, - internal::{CachedWindowStartVec, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlock, ColumnarPerBlockCumulativeRolling, Windows, + }, }; impl Vecs { @@ -15,34 +18,50 @@ impl Vecs { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { - let output_count_source = indexes.output_count_source(); - let output_count = WithOutputTypes::forced_import_counts( + let columnar_version = version + Version::ONE; + let all_output_count = indexes.output_count_source(); + let output_count = ColumnarPerBlock::::forced_import( db, - "output_count_bis", - |t| format!("{t}_output_count"), - version, - (output_count_source, identity), - indexes, - cached_starts, + "output_count_by_type", + columnar_version, + |source| { + WithOutputTypes::from_columnar_count_source( + "output_count_bis", + |t| format!("{t}_output_count"), + columnar_version, + (all_output_count, identity), + source, + indexes, + cached_starts, + ) + }, )?; let output_share = output_count.lazy_shares( - version, + columnar_version, |name| format!("{name}_output_share"), cached_starts, indexes, ); - let transaction_count_source = indexes.transaction_count_source(); - let tx_count = WithOutputTypes::forced_import( - db, - "tx_count_bis", - |t| format!("tx_count_with_{t}_output"), - version, - (transaction_count_source, identity), - indexes, - cached_starts, - )?; + let all_tx_count = indexes.transaction_count_source(); + let tx_count = + ColumnarPerBlockCumulativeRolling::::forced_import( + db, + "tx_count_with_output_by_type_cumulative", + columnar_version, + |source| { + WithOutputTypes::from_columnar_source( + "tx_count_bis", + |t| format!("tx_count_with_{t}_output"), + columnar_version, + (all_tx_count, identity), + source, + indexes, + cached_starts, + ) + }, + )?; let tx_share = tx_count.lazy_shares( - version, + columnar_version, |name| format!("tx_share_with_{name}_output"), cached_starts, indexes, diff --git a/crates/brk_computer/src/outputs/by_type/vecs.rs b/crates/brk_computer/src/outputs/by_type/vecs.rs index a7cc63e0e..dc9da59f9 100644 --- a/crates/brk_computer/src/outputs/by_type/vecs.rs +++ b/crates/brk_computer/src/outputs/by_type/vecs.rs @@ -1,18 +1,29 @@ -use brk_cohort::ByType; +use brk_cohort::{ByType, OutputTypeId}; use brk_traversable::Traversable; -use brk_types::{PartsPerMillion32, StoredU64}; +use brk_types::{PartsPerMillion32, StoredU16, StoredU64}; use vecdb::{Rw, StorageMode}; use super::{CachedSpendableOutputCount, WithOutputTypes}; use crate::internal::{ - CachedCountPerBlockCumulativeRolling, LazyPercentCumulativeRolling, PerBlockCumulativeRolling, + ColumnarPerBlock, ColumnarPerBlockCumulativeRolling, LazyColumnCountPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, LazyPercentCumulativeRolling, }; #[derive(Traversable)] pub struct Vecs { - pub output_count: WithOutputTypes>, + pub output_count: ColumnarPerBlock< + StoredU16, + OutputTypeId, + WithOutputTypes, + M, + >, pub spendable_output_count: CachedSpendableOutputCount, pub output_share: ByType>, - pub tx_count: WithOutputTypes>, + pub tx_count: ColumnarPerBlockCumulativeRolling< + StoredU64, + OutputTypeId, + WithOutputTypes>, + M, + >, pub tx_share: ByType>, } diff --git a/crates/brk_computer/src/outputs/by_type/with_output_types.rs b/crates/brk_computer/src/outputs/by_type/with_output_types.rs index 91e5c5705..061e6dfad 100644 --- a/crates/brk_computer/src/outputs/by_type/with_output_types.rs +++ b/crates/brk_computer/src/outputs/by_type/with_output_types.rs @@ -1,21 +1,20 @@ //! Generic `all` + per-`OutputType` container (12 output types, including //! op_return). -use brk_cohort::{ByAddrType, ByType, Filter}; -use brk_error::Result; +use brk_cohort::{ByAddrType, ByType, Filter, OutputTypeId}; use brk_traversable::Traversable; -use brk_types::{Height, PartsPerMillion32, StoredU64, Version}; +use brk_types::{Height, PartsPerMillion32, StoredU16, StoredU64, Version}; use vecdb::{ - AnyStoredVec, AnyVec, CachedBoxedVec, CachedReadableVec, Database, ReadableCloneableVec, - ReadableVec, TypedVec, WritableVec, + CachedBoxedVec, CachedReadableVec, PcoVec, ReadOnlyColumnarVec, ReadableCloneableVec, + ReadableVec, TypedVec, }; use crate::{ indexes, internal::{ - CachedBlockCountReader, CachedCountPerBlockCumulativeRolling, CachedWindowStartVec, - LazyPerBlockCumulativeRolling, LazyPercentCumulativeRolling, PerBlockCumulativeRolling, - RatioU64, Windows, + CachedBlockCountReader, CachedWindowStartVec, LazyColumnCountPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, LazyPerBlockCumulativeRolling, + LazyPercentCumulativeRolling, RatioU64, Windows, }, }; @@ -25,7 +24,7 @@ pub(super) fn identity(_: Height, value: StoredU64) -> StoredU64 { /// `all` aggregate plus per-`OutputType` breakdown across all 12 output /// types (spendable + op_return). -#[derive(Traversable)] +#[derive(Clone, Traversable)] pub struct WithOutputTypes { pub all: LazyPerBlockCumulativeRolling, #[traversable(skip)] @@ -65,20 +64,6 @@ impl WithOutputTypes { } } - fn min_stateful_len_with(&self, len: impl Fn(&V) -> usize) -> usize { - self.by_type.iter().map(len).min().unwrap() - } - - fn try_for_each_type_mut(&mut self, mut apply: impl FnMut(&mut V) -> Result<()>) -> Result<()> { - self.by_type.iter_mut().try_for_each(&mut apply) - } - - fn push_block_with(&mut self, per_type: &[u64; 12], mut push: impl FnMut(&mut V, StoredU64)) { - for (output_type, vec) in self.by_type.iter_typed_mut() { - push(vec, StoredU64::from(per_type[output_type as usize])); - } - } - pub(crate) fn lazy_share( &self, name: &str, @@ -102,16 +87,16 @@ impl WithOutputTypes { } } -impl WithOutputTypes { - pub(crate) fn forced_import_counts( - db: &Database, +impl WithOutputTypes { + pub(crate) fn from_columnar_count_source( all_name: &str, per_type_name: impl Fn(&str) -> String, version: Version, all: (S, fn(Height, StoredU64) -> StoredU64), + source: &ReadOnlyColumnarVec, OutputTypeId>, indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result + ) -> Self where S: TypedVec + ReadableVec @@ -119,36 +104,20 @@ impl WithOutputTypes { + Clone + 'static, { - let by_type = ByType::try_new(|_, name| { - CachedCountPerBlockCumulativeRolling::forced_import( - db, - &per_type_name(name), - version, - indexes, - cached_starts, - ) - })?; - Ok(Self::new( - all_name, - version, - all, - by_type, - indexes, - cached_starts, - )) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.min_stateful_len_with(|vec| vec.min_stateful_len()) - } - - pub(crate) fn cached_addr_type_counts(&self) -> ByAddrType { - ByAddrType::new(|filter| { + let by_type = ByType::new(|filter, name| { let Filter::Type(output_type) = filter else { unreachable!() }; - self.by_type.get(output_type).cached_cumulative() - }) + LazyColumnCountPerBlockCumulativeRolling::new( + &per_type_name(name), + version, + source, + OutputTypeId::from_output_type(output_type), + indexes, + cached_starts, + ) + }); + Self::new(all_name, version, all, by_type, indexes, cached_starts) } pub(crate) fn lazy_shares( @@ -173,38 +142,30 @@ impl WithOutputTypes { }) } - pub(crate) fn write(&mut self) -> Result<()> { - self.try_for_each_type_mut(|vec| vec.write()) + pub(crate) fn cached_addr_type_counts(&self) -> ByAddrType { + ByAddrType::new(|filter| { + let Filter::Type(output_type) = filter else { + unreachable!() + }; + self.by_type.get(output_type).cached_cumulative() + }) } - pub(crate) fn validate_and_truncate( - &mut self, - dependency_version: Version, - at_height: Height, - ) -> Result<()> { - self.try_for_each_type_mut(|vec| vec.validate_and_truncate(dependency_version, at_height)) - } - - pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.try_for_each_type_mut(|vec| vec.truncate_if_needed_at(len)) - } - - #[inline] - pub(crate) fn push_block(&mut self, per_type: &[u64; 12]) { - self.push_block_with(per_type, |vec, value| vec.push_block(value)); + pub(crate) fn clear(&self) { + self.by_type.iter().for_each(|count| count.clear()); } } -impl WithOutputTypes> { - pub(crate) fn forced_import( - db: &Database, +impl WithOutputTypes> { + pub(crate) fn from_columnar_source( all_name: &str, per_type_name: impl Fn(&str) -> String, version: Version, all: (S, fn(Height, StoredU64) -> StoredU64), + source: &ReadOnlyColumnarVec, OutputTypeId>, indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, - ) -> Result + ) -> Self where S: TypedVec + ReadableVec @@ -212,27 +173,20 @@ impl WithOutputTypes> { + Clone + 'static, { - let by_type = ByType::try_new(|_, name| { - PerBlockCumulativeRolling::forced_import( - db, + let by_type = ByType::new(|filter, name| { + let Filter::Type(output_type) = filter else { + unreachable!() + }; + LazyColumnPerBlockCumulativeRolling::new( &per_type_name(name), version, + source, + OutputTypeId::from_output_type(output_type), indexes, cached_starts, ) - })?; - Ok(Self::new( - all_name, - version, - all, - by_type, - indexes, - cached_starts, - )) - } - - pub(crate) fn min_stateful_len(&self) -> usize { - self.min_stateful_len_with(|vec| vec.cumulative.height.len()) + }); + Self::new(all_name, version, all, by_type, indexes, cached_starts) } pub(crate) fn lazy_shares( @@ -255,33 +209,4 @@ impl WithOutputTypes> { ) }) } - - pub(crate) fn write(&mut self) -> Result<()> { - self.try_for_each_type_mut(|vec| { - vec.cumulative.height.write()?; - Ok(()) - }) - } - - pub(crate) fn validate_and_truncate( - &mut self, - dependency_version: Version, - at_height: Height, - ) -> Result<()> { - self.try_for_each_type_mut(|vec| { - Ok(vec - .cumulative - .height - .validate_and_truncate(dependency_version, at_height)?) - }) - } - - pub(crate) fn truncate_if_needed_at(&mut self, len: usize) -> Result<()> { - self.try_for_each_type_mut(|vec| Ok(vec.cumulative.height.truncate_if_needed_at(len)?)) - } - - #[inline] - pub(crate) fn push_block(&mut self, per_type: &[u64; 12]) { - self.push_block_with(per_type, |vec, value| vec.push_block(value)); - } } diff --git a/crates/brk_computer/src/price/ohlcs.rs b/crates/brk_computer/src/price/ohlcs.rs index 0c5f63376..0df7acc55 100644 --- a/crates/brk_computer/src/price/ohlcs.rs +++ b/crates/brk_computer/src/price/ohlcs.rs @@ -20,7 +20,6 @@ use super::lazy_ohlc::LazyOhlcVec; #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(merge)] pub struct LazyOhlcCentsVecs( - #[allow(clippy::type_complexity)] pub PerResolution< LazyOhlcVec, LazyOhlcVec, @@ -119,7 +118,6 @@ where #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(merge)] pub struct LazyOhlcVecs( - #[allow(clippy::type_complexity)] pub PerResolution< LazyVec, LazyVec, diff --git a/crates/brk_computer/src/transactions/features/compute.rs b/crates/brk_computer/src/transactions/features/compute.rs index 41efb8347..d7fe24cdd 100644 --- a/crates/brk_computer/src/transactions/features/compute.rs +++ b/crates/brk_computer/src/transactions/features/compute.rs @@ -2,28 +2,25 @@ use brk_error::Result; use brk_indexer::Indexer; use vecdb::Exit; -use super::Vecs; +use super::{FeatureId, Vecs}; impl Vecs { pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> { let starting_height = indexer.safe_lengths().height; let source = &indexer.vecs().transaction_features.count; - for (metrics, source) in [ - (&mut self.count.inscription, &source.inscription), - (&mut self.count.annex, &source.annex), - (&mut self.count.sighash_all, &source.sighash_all), - (&mut self.count.sighash_none, &source.sighash_none), - (&mut self.count.sighash_single, &source.sighash_single), - (&mut self.count.sighash_default, &source.sighash_default), - ( - &mut self.count.sighash_anyone_can_pay, - &source.sighash_anyone_can_pay, - ), - (&mut self.count.dust_output, &source.dust_output), - ] { - metrics.compute_cumulative(starting_height, source, exit)?; - } - - Ok(()) + self.count.compute_columns( + starting_height, + |feature| match feature { + FeatureId::Inscription => &source.inscription, + FeatureId::Annex => &source.annex, + FeatureId::SighashAll => &source.sighash_all, + FeatureId::SighashNone => &source.sighash_none, + FeatureId::SighashSingle => &source.sighash_single, + FeatureId::SighashDefault => &source.sighash_default, + FeatureId::SighashAnyoneCanPay => &source.sighash_anyone_can_pay, + FeatureId::DustOutput => &source.dust_output, + }, + exit, + ) } } diff --git a/crates/brk_computer/src/transactions/features/import.rs b/crates/brk_computer/src/transactions/features/import.rs index 10a176c42..479b20936 100644 --- a/crates/brk_computer/src/transactions/features/import.rs +++ b/crates/brk_computer/src/transactions/features/import.rs @@ -1,11 +1,14 @@ use brk_error::Result; use brk_types::Version; -use vecdb::Database; +use vecdb::{Database, ReadOnlyClone}; -use super::{CountVecs, Vecs}; +use super::{CountVecs, FeatureId, Vecs}; use crate::{ indexes, - internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, Windows, + }, }; impl Vecs { @@ -15,20 +18,38 @@ impl Vecs { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { - let import = |name| { - PerBlockCumulativeRolling::forced_import(db, name, version, indexes, cached_starts) + let source = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "tx_feature_count_cumulative", + version, + |_| (), + )?; + let counts = source.cumulative.read_only_clone(); + let import = |name, feature| { + LazyColumnPerBlockCumulativeRolling::new( + name, + version, + &counts, + feature, + indexes, + cached_starts, + ) }; Ok(Self { count: CountVecs { - inscription: import("tx_count_inscription")?, - annex: import("tx_count_annex")?, - sighash_all: import("tx_count_sighash_all")?, - sighash_none: import("tx_count_sighash_none")?, - sighash_single: import("tx_count_sighash_single")?, - sighash_default: import("tx_count_sighash_default")?, - sighash_anyone_can_pay: import("tx_count_sighash_anyone_can_pay")?, - dust_output: import("tx_count_dust_output")?, + inscription: import("tx_count_inscription", FeatureId::Inscription), + annex: import("tx_count_annex", FeatureId::Annex), + sighash_all: import("tx_count_sighash_all", FeatureId::SighashAll), + sighash_none: import("tx_count_sighash_none", FeatureId::SighashNone), + sighash_single: import("tx_count_sighash_single", FeatureId::SighashSingle), + sighash_default: import("tx_count_sighash_default", FeatureId::SighashDefault), + sighash_anyone_can_pay: import( + "tx_count_sighash_anyone_can_pay", + FeatureId::SighashAnyoneCanPay, + ), + dust_output: import("tx_count_dust_output", FeatureId::DustOutput), + source, }, }) } diff --git a/crates/brk_computer/src/transactions/features/mod.rs b/crates/brk_computer/src/transactions/features/mod.rs index a3b2299b5..283cfa2ea 100644 --- a/crates/brk_computer/src/transactions/features/mod.rs +++ b/crates/brk_computer/src/transactions/features/mod.rs @@ -2,4 +2,4 @@ mod compute; mod import; mod vecs; -pub use vecs::{CountVecs, Vecs}; +pub use vecs::{CountVecs, FeatureId, Vecs}; diff --git a/crates/brk_computer/src/transactions/features/vecs.rs b/crates/brk_computer/src/transactions/features/vecs.rs index 46c4e3131..d89ef07e1 100644 --- a/crates/brk_computer/src/transactions/features/vecs.rs +++ b/crates/brk_computer/src/transactions/features/vecs.rs @@ -1,33 +1,132 @@ use brk_traversable::Traversable; -use brk_types::StoredU64; -use vecdb::{Rw, StorageMode}; +use brk_types::{StoredU64, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ColumnId, Rw, StorageMode, VecValue}; -use crate::internal::PerBlockCumulativeRolling; +use crate::internal::{ColumnarPerBlockCumulativeRolling, LazyColumnPerBlockCumulativeRolling}; + +const FEATURE_COUNT: usize = 8; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum FeatureId { + Inscription, + Annex, + SighashAll, + SighashNone, + SighashSingle, + SighashDefault, + SighashAnyoneCanPay, + DustOutput, +} + +const FEATURE_IDS: [FeatureId; FEATURE_COUNT] = [ + FeatureId::Inscription, + FeatureId::Annex, + FeatureId::SighashAll, + FeatureId::SighashNone, + FeatureId::SighashSingle, + FeatureId::SighashDefault, + FeatureId::SighashAnyoneCanPay, + FeatureId::DustOutput, +]; + +impl ColumnId for FeatureId { + type Row + = [T; FEATURE_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &FEATURE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(FEATURE_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} /// Transaction counts by detected feature. /// /// Each metric counts transactions containing the feature at least once, not /// individual occurrences. A transaction can contribute to multiple metrics. -#[derive(Traversable)] +#[derive(Deref, DerefMut, Traversable)] pub struct CountVecs { - pub inscription: PerBlockCumulativeRolling, - pub annex: PerBlockCumulativeRolling, + pub inscription: LazyColumnPerBlockCumulativeRolling, + pub annex: LazyColumnPerBlockCumulativeRolling, /// Transactions containing at least one `SIGHASH_ALL` signature. - pub sighash_all: PerBlockCumulativeRolling, + pub sighash_all: LazyColumnPerBlockCumulativeRolling, /// Transactions containing at least one `SIGHASH_NONE` signature. - pub sighash_none: PerBlockCumulativeRolling, + pub sighash_none: LazyColumnPerBlockCumulativeRolling, /// Transactions containing at least one `SIGHASH_SINGLE` signature. - pub sighash_single: PerBlockCumulativeRolling, + pub sighash_single: LazyColumnPerBlockCumulativeRolling, /// Transactions containing at least one Taproot `SIGHASH_DEFAULT` signature. - pub sighash_default: PerBlockCumulativeRolling, + pub sighash_default: LazyColumnPerBlockCumulativeRolling, /// Transactions containing at least one `SIGHASH_ANYONECANPAY` signature. /// /// This modifier is counted independently from ALL, NONE, and SINGLE. - pub sighash_anyone_can_pay: PerBlockCumulativeRolling, - pub dust_output: PerBlockCumulativeRolling, + pub sighash_anyone_can_pay: LazyColumnPerBlockCumulativeRolling, + pub dust_output: LazyColumnPerBlockCumulativeRolling, + #[deref] + #[deref_mut] + #[traversable(hidden)] + pub source: ColumnarPerBlockCumulativeRolling, } #[derive(Traversable)] pub struct Vecs { pub count: CountVecs, } + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{FEATURE_IDS, FeatureId}; + + #[test] + fn feature_columns_match_public_field_order() { + assert_eq!(FeatureId::ALL, FEATURE_IDS); + assert_eq!( + FeatureId::from_fn(|feature| feature), + [ + FeatureId::Inscription, + FeatureId::Annex, + FeatureId::SighashAll, + FeatureId::SighashNone, + FeatureId::SighashSingle, + FeatureId::SighashDefault, + FeatureId::SighashAnyoneCanPay, + FeatureId::DustOutput, + ] + ); + } +} diff --git a/crates/brk_computer/src/transactions/fees/compute.rs b/crates/brk_computer/src/transactions/fees/compute.rs index bd030b164..731512f04 100644 --- a/crates/brk_computer/src/transactions/fees/compute.rs +++ b/crates/brk_computer/src/transactions/fees/compute.rs @@ -5,10 +5,12 @@ use brk_types::{ TxIndex, VSize, linearize, }; use smallvec::SmallVec; -use vecdb::{AnyStoredVec, AnyVec, Exit, PcoVec, ReadableVec, VecIndex, WritableVec, unlikely}; +use vecdb::{ + AnyStoredVec, AnyVec, ColumnId, Exit, PcoVec, ReadableVec, VecIndex, WritableVec, unlikely, +}; use super::super::size; -use super::Vecs; +use super::{CpfpRoleId, Vecs}; use crate::indexes; impl Vecs { @@ -88,15 +90,10 @@ impl Vecs { self.effective_fee_rate .tx_index .validate_computed_version_or_reset(dep_version)?; - self.is_cpfp_parent - .validate_computed_version_or_reset(dep_version)?; - self.is_cpfp_child + self.cpfp_flags_source .validate_computed_version_or_reset(dep_version)?; self.count - .cpfp_parent - .validate_computed_version_or_reset(dep_version)?; - self.count - .cpfp_child + .cumulative .validate_computed_version_or_reset(dep_version)?; let target = self @@ -110,8 +107,7 @@ impl Vecs { .len() .min(self.fee_rate.len()) .min(self.effective_fee_rate.tx_index.len()) - .min(self.is_cpfp_parent.len()) - .min(self.is_cpfp_child.len()) + .min(self.cpfp_flags_source.len()) .min(starting_lengths.tx_index.to_usize()); let max_height = indexer .vecs() @@ -128,14 +124,7 @@ impl Vecs { .unwrap() .to_usize() }; - let count_len = self - .count - .cpfp_parent - .cumulative - .height - .len() - .min(self.count.cpfp_child.cumulative.height.len()) - .min(max_height); + let count_len = self.count.cumulative.len().min(max_height); let start_height = count_len.min(next_height); if start_height >= max_height { return Ok(()); @@ -155,12 +144,9 @@ impl Vecs { self.effective_fee_rate .tx_index .truncate_if_needed(TxIndex::from(start_tx))?; - self.is_cpfp_parent + self.cpfp_flags_source .truncate_if_needed(TxIndex::from(start_tx))?; - self.is_cpfp_child - .truncate_if_needed(TxIndex::from(start_tx))?; - self.count.cpfp_parent.truncate_if_needed_at(start_height)?; - self.count.cpfp_child.truncate_if_needed_at(start_height)?; + self.count.truncate_if_needed_at(start_height)?; let mut tx_count = indexes.height.tx_index_count.cursor(); let mut next_block_input = indexer.vecs().inputs.first_txin_index.cursor(); @@ -240,25 +226,22 @@ impl Vecs { parent_count += is_parent as u64; child_count += is_child as u64; self.effective_fee_rate.tx_index.push(effective); - self.is_cpfp_parent.push(StoredBool::from(is_parent)); - self.is_cpfp_child.push(StoredBool::from(is_child)); + self.cpfp_flags_source + .push([StoredBool::from(is_parent), StoredBool::from(is_child)]); } self.count - .cpfp_parent - .push_block(StoredU64::from(parent_count)); - self.count - .cpfp_child - .push_block(StoredU64::from(child_count)); + .push_block(CpfpRoleId::from_fn(|role| match role { + CpfpRoleId::Parent => StoredU64::from(parent_count), + CpfpRoleId::Child => StoredU64::from(child_count), + })); if h % 1_000 == 0 { let _lock = exit.lock(); self.fee.tx_index.write()?; self.fee_rate.write()?; self.effective_fee_rate.tx_index.write()?; - self.is_cpfp_parent.write()?; - self.is_cpfp_child.write()?; - self.count.cpfp_parent.write()?; - self.count.cpfp_child.write()?; + self.cpfp_flags_source.write()?; + self.count.write()?; } first_tx += n; @@ -268,10 +251,8 @@ impl Vecs { self.fee.tx_index.write()?; self.fee_rate.write()?; self.effective_fee_rate.tx_index.write()?; - self.is_cpfp_parent.write()?; - self.is_cpfp_child.write()?; - self.count.cpfp_parent.write()?; - self.count.cpfp_child.write()?; + self.cpfp_flags_source.write()?; + self.count.write()?; Ok(()) } diff --git a/crates/brk_computer/src/transactions/fees/import.rs b/crates/brk_computer/src/transactions/fees/import.rs index d73de75ac..d4f56dd74 100644 --- a/crates/brk_computer/src/transactions/fees/import.rs +++ b/crates/brk_computer/src/transactions/fees/import.rs @@ -1,11 +1,16 @@ use brk_error::Result; -use brk_types::Version; -use vecdb::{Database, EagerVec, ImportableVec}; +use brk_types::{StoredBool, TxIndex, Version}; +use vecdb::{ + ColumnarVec, Database, EagerVec, ImportableVec, PcoVec, ReadOnlyClone, ReadableColumnarVec, +}; -use super::{CountVecs, Vecs}; +use super::{CountVecs, CpfpFlags, CpfpRoleId, Vecs}; use crate::{ indexes, - internal::{CachedWindowStartVec, PerBlockCumulativeRolling, PerTxDistribution, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, PerTxDistribution, Windows, + }, }; /// Bump this when fee/feerate aggregation logic changes (e.g., skip coinbase, skip zero-fee). @@ -19,23 +24,43 @@ impl Vecs { cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { let v = version + VERSION; + let count_source = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "cpfp_count_cumulative", + version, + |_| (), + )?; + let counts = count_source.cumulative.read_only_clone(); + let count = CountVecs { + cpfp_parent: LazyColumnPerBlockCumulativeRolling::new( + "cpfp_parent_count", + version, + &counts, + CpfpRoleId::Parent, + indexes, + cached_starts, + ), + cpfp_child: LazyColumnPerBlockCumulativeRolling::new( + "cpfp_child_count", + version, + &counts, + CpfpRoleId::Child, + indexes, + cached_starts, + ), + source: count_source, + }; + + let cpfp_flags_source = + EagerVec::, CpfpRoleId>>::forced_import( + db, + "cpfp_flags", + version, + )?; + let flags = cpfp_flags_source.read_only_clone(); + Ok(Self { - count: CountVecs { - cpfp_parent: PerBlockCumulativeRolling::forced_import( - db, - "cpfp_parent_count", - version, - indexes, - cached_starts, - )?, - cpfp_child: PerBlockCumulativeRolling::forced_import( - db, - "cpfp_child_count", - version, - indexes, - cached_starts, - )?, - }, + count, input_value: EagerVec::forced_import(db, "input_value", version)?, output_value: EagerVec::forced_import(db, "output_value", version)?, fee: PerTxDistribution::forced_import(db, "fee", v, indexes)?, @@ -46,8 +71,11 @@ impl Vecs { v, indexes, )?, - is_cpfp_parent: EagerVec::forced_import(db, "is_cpfp_parent", version)?, - is_cpfp_child: EagerVec::forced_import(db, "is_cpfp_child", version)?, + cpfp_flags: CpfpFlags { + is_cpfp_parent: flags.column("is_cpfp_parent", version, CpfpRoleId::Parent), + is_cpfp_child: flags.column("is_cpfp_child", version, CpfpRoleId::Child), + }, + cpfp_flags_source, }) } } diff --git a/crates/brk_computer/src/transactions/fees/mod.rs b/crates/brk_computer/src/transactions/fees/mod.rs index a3b2299b5..3dfdf55ed 100644 --- a/crates/brk_computer/src/transactions/fees/mod.rs +++ b/crates/brk_computer/src/transactions/fees/mod.rs @@ -2,4 +2,4 @@ mod compute; mod import; mod vecs; -pub use vecs::{CountVecs, Vecs}; +pub use vecs::{CountVecs, CpfpFlags, CpfpRoleId, Vecs}; diff --git a/crates/brk_computer/src/transactions/fees/vecs.rs b/crates/brk_computer/src/transactions/fees/vecs.rs index c2d4b4328..b68ca42c9 100644 --- a/crates/brk_computer/src/transactions/fees/vecs.rs +++ b/crates/brk_computer/src/transactions/fees/vecs.rs @@ -1,17 +1,87 @@ use brk_traversable::Traversable; -use brk_types::{FeeRate, Sats, StoredBool, StoredU64, TxIndex}; -use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; +use brk_types::{FeeRate, Sats, StoredBool, StoredU64, TxIndex, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + ColumnId, ColumnarVec, EagerVec, LazyColumnVec, PcoVec, ReadOnlyColumnarVec, Rw, StorageMode, + VecValue, +}; -use crate::internal::{PerBlockCumulativeRolling, PerTxDistribution}; +use crate::internal::{ + ColumnarPerBlockCumulativeRolling, LazyColumnPerBlockCumulativeRolling, PerTxDistribution, +}; -/// Confirmed transactions participating in same-block CPFP clusters. -#[derive(Traversable)] -pub struct CountVecs { - pub cpfp_parent: PerBlockCumulativeRolling, - pub cpfp_child: PerBlockCumulativeRolling, +const CPFP_ROLE_COUNT: usize = 2; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum CpfpRoleId { + Parent, + Child, } -#[derive(Traversable)] +const CPFP_ROLE_IDS: [CpfpRoleId; CPFP_ROLE_COUNT] = [CpfpRoleId::Parent, CpfpRoleId::Child]; + +impl ColumnId for CpfpRoleId { + type Row + = [T; CPFP_ROLE_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &CPFP_ROLE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(CPFP_ROLE_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Clone, Traversable)] +pub struct CpfpFlags { + pub is_cpfp_parent: V, + pub is_cpfp_child: V, +} + +/// Confirmed transactions participating in same-block CPFP clusters. +#[derive(Deref, DerefMut, Traversable)] +pub struct CountVecs { + pub cpfp_parent: LazyColumnPerBlockCumulativeRolling, + pub cpfp_child: LazyColumnPerBlockCumulativeRolling, + #[deref] + #[deref_mut] + #[traversable(hidden)] + pub source: ColumnarPerBlockCumulativeRolling, +} + +#[derive(Deref, DerefMut, Traversable)] pub struct Vecs { pub count: CountVecs, pub input_value: M::Stored>>, @@ -19,6 +89,29 @@ pub struct Vecs { pub fee: PerTxDistribution, pub fee_rate: M::Stored>>, pub effective_fee_rate: PerTxDistribution, - pub is_cpfp_parent: M::Stored>>, - pub is_cpfp_child: M::Stored>>, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub cpfp_flags: CpfpFlags< + LazyColumnVec, CpfpRoleId>, CpfpRoleId>, + >, + #[traversable(hidden)] + pub cpfp_flags_source: + M::Stored, CpfpRoleId>>>, +} + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{CPFP_ROLE_IDS, CpfpRoleId}; + + #[test] + fn cpfp_role_columns_match_public_field_order() { + assert_eq!(CpfpRoleId::ALL, CPFP_ROLE_IDS); + assert_eq!( + CpfpRoleId::from_fn(|role| role), + [CpfpRoleId::Parent, CpfpRoleId::Child] + ); + } } diff --git a/crates/brk_computer/src/transactions/patterns/compute.rs b/crates/brk_computer/src/transactions/patterns/compute.rs index 7b517188b..95701865e 100644 --- a/crates/brk_computer/src/transactions/patterns/compute.rs +++ b/crates/brk_computer/src/transactions/patterns/compute.rs @@ -1,9 +1,9 @@ use brk_error::Result; use brk_indexer::Indexer; use brk_types::{Sats, StoredBool, StoredU64, TxInIndex, TxIndex}; -use vecdb::{AnyStoredVec, AnyVec, Exit, PcoVec, ReadableVec, VecIndex, WritableVec}; +use vecdb::{AnyStoredVec, AnyVec, ColumnId, Exit, PcoVec, ReadableVec, VecIndex, WritableVec}; -use super::{Vecs, coinjoin::Candidate}; +use super::{PatternId, Vecs, coinjoin::Candidate}; use crate::indexes; const WRITE_INTERVAL: usize = 10_000; @@ -32,38 +32,23 @@ impl Vecs { + features.has_inscription.version() + indexes.height.tx_index_count.version(); - for vec in [ - &mut self.is_coinjoin, - &mut self.is_consolidation, - &mut self.is_batch_payout, - ] { - vec.validate_computed_version_or_reset(version)?; - } - for vec in [ - &mut self.count.coinjoin, - &mut self.count.consolidation, - &mut self.count.batch_payout, - ] { - vec.validate_computed_version_or_reset(version)?; - } + self.flags_source + .validate_computed_version_or_reset(version)?; + self.count + .cumulative + .validate_computed_version_or_reset(version)?; let starting_lengths = indexer.safe_lengths(); let target_tx = indexes.tx_index.input_count.len(); let target_height = indexes.height.tx_index_count.len(); let tx_len = self - .is_coinjoin + .flags_source .len() - .min(self.is_consolidation.len()) - .min(self.is_batch_payout.len()) .min(starting_lengths.tx_index.to_usize()); let count_len = self .count - .coinjoin .cumulative - .height .len() - .min(self.count.consolidation.cumulative.height.len()) - .min(self.count.batch_payout.cumulative.height.len()) .min(starting_lengths.height.to_usize()); let start_height = count_len.min(next_height(indexes, tx_len, target_tx, target_height)); if start_height >= target_height { @@ -72,16 +57,8 @@ impl Vecs { let first_tx = &indexer.vecs().transactions.first_tx_index; let start_tx = first_tx.collect_one_at(start_height).unwrap().to_usize(); - self.is_coinjoin.truncate_if_needed_at(start_tx)?; - self.is_consolidation.truncate_if_needed_at(start_tx)?; - self.is_batch_payout.truncate_if_needed_at(start_tx)?; - self.count.coinjoin.truncate_if_needed_at(start_height)?; - self.count - .consolidation - .truncate_if_needed_at(start_height)?; - self.count - .batch_payout - .truncate_if_needed_at(start_height)?; + self.flags_source.truncate_if_needed_at(start_tx)?; + self.count.truncate_if_needed_at(start_height)?; let first_txin = indexer .vecs() @@ -171,20 +148,19 @@ impl Vecs { coinjoin_count += coinjoin as u64; consolidation_count += consolidation as u64; batch_payout_count += batch_payout as u64; - self.is_coinjoin.push(StoredBool::from(coinjoin)); - self.is_consolidation.push(StoredBool::from(consolidation)); - self.is_batch_payout.push(StoredBool::from(batch_payout)); + self.flags_source.push([ + StoredBool::from(coinjoin), + StoredBool::from(consolidation), + StoredBool::from(batch_payout), + ]); } self.count - .coinjoin - .push_block(StoredU64::from(coinjoin_count)); - self.count - .consolidation - .push_block(StoredU64::from(consolidation_count)); - self.count - .batch_payout - .push_block(StoredU64::from(batch_payout_count)); + .push_block(PatternId::from_fn(|pattern| match pattern { + PatternId::Coinjoin => StoredU64::from(coinjoin_count), + PatternId::Consolidation => StoredU64::from(consolidation_count), + PatternId::BatchPayout => StoredU64::from(batch_payout_count), + })); if (height + 1).is_multiple_of(WRITE_INTERVAL) { let _lock = exit.lock(); @@ -199,12 +175,8 @@ impl Vecs { } fn write(&mut self) -> Result<()> { - self.is_coinjoin.write()?; - self.is_consolidation.write()?; - self.is_batch_payout.write()?; - self.count.coinjoin.write()?; - self.count.consolidation.write()?; - self.count.batch_payout.write()?; + self.flags_source.write()?; + self.count.write()?; Ok(()) } } diff --git a/crates/brk_computer/src/transactions/patterns/import.rs b/crates/brk_computer/src/transactions/patterns/import.rs index 75cff9747..035dff3b5 100644 --- a/crates/brk_computer/src/transactions/patterns/import.rs +++ b/crates/brk_computer/src/transactions/patterns/import.rs @@ -1,11 +1,16 @@ use brk_error::Result; -use brk_types::Version; -use vecdb::{Database, EagerVec, ImportableVec}; +use brk_types::{StoredBool, TxIndex, Version}; +use vecdb::{ + ColumnarVec, Database, EagerVec, ImportableVec, PcoVec, ReadOnlyClone, ReadableColumnarVec, +}; -use super::{CountVecs, Vecs}; +use super::{CountVecs, Flags, PatternId, Vecs}; use crate::{ indexes, - internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, Windows, + }, }; impl Vecs { @@ -15,33 +20,61 @@ impl Vecs { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { + let count_source = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "transaction_pattern_count_cumulative", + version, + |_| (), + )?; + let counts = count_source.cumulative.read_only_clone(); + let count = CountVecs { + coinjoin: LazyColumnPerBlockCumulativeRolling::new( + "coinjoin_count", + version, + &counts, + PatternId::Coinjoin, + indexes, + cached_starts, + ), + consolidation: LazyColumnPerBlockCumulativeRolling::new( + "consolidation_count", + version, + &counts, + PatternId::Consolidation, + indexes, + cached_starts, + ), + batch_payout: LazyColumnPerBlockCumulativeRolling::new( + "batch_payout_count", + version, + &counts, + PatternId::BatchPayout, + indexes, + cached_starts, + ), + source: count_source, + }; + + let flags_source = + EagerVec::, PatternId>>::forced_import( + db, + "transaction_pattern_flags", + version, + )?; + let flags = flags_source.read_only_clone(); + Ok(Self { - count: CountVecs { - coinjoin: PerBlockCumulativeRolling::forced_import( - db, - "coinjoin_count", + count, + flags: Flags { + is_coinjoin: flags.column("is_coinjoin", version, PatternId::Coinjoin), + is_consolidation: flags.column( + "is_consolidation", version, - indexes, - cached_starts, - )?, - consolidation: PerBlockCumulativeRolling::forced_import( - db, - "consolidation_count", - version, - indexes, - cached_starts, - )?, - batch_payout: PerBlockCumulativeRolling::forced_import( - db, - "batch_payout_count", - version, - indexes, - cached_starts, - )?, + PatternId::Consolidation, + ), + is_batch_payout: flags.column("is_batch_payout", version, PatternId::BatchPayout), }, - is_coinjoin: EagerVec::forced_import(db, "is_coinjoin", version)?, - is_consolidation: EagerVec::forced_import(db, "is_consolidation", version)?, - is_batch_payout: EagerVec::forced_import(db, "is_batch_payout", version)?, + flags_source, }) } } diff --git a/crates/brk_computer/src/transactions/patterns/mod.rs b/crates/brk_computer/src/transactions/patterns/mod.rs index b34029e5a..fa5f78c94 100644 --- a/crates/brk_computer/src/transactions/patterns/mod.rs +++ b/crates/brk_computer/src/transactions/patterns/mod.rs @@ -3,4 +3,4 @@ mod compute; mod import; mod vecs; -pub use vecs::{CountVecs, Vecs}; +pub use vecs::{CountVecs, Flags, PatternId, Vecs}; diff --git a/crates/brk_computer/src/transactions/patterns/vecs.rs b/crates/brk_computer/src/transactions/patterns/vecs.rs index ae8b88875..1b2746e05 100644 --- a/crates/brk_computer/src/transactions/patterns/vecs.rs +++ b/crates/brk_computer/src/transactions/patterns/vecs.rs @@ -1,26 +1,125 @@ use brk_traversable::Traversable; -use brk_types::{StoredBool, StoredU64, TxIndex}; -use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; +use brk_types::{StoredBool, StoredU64, TxIndex, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + ColumnId, ColumnarVec, EagerVec, LazyColumnVec, PcoVec, ReadOnlyColumnarVec, Rw, StorageMode, + VecValue, +}; -use crate::internal::PerBlockCumulativeRolling; +use crate::internal::{ColumnarPerBlockCumulativeRolling, LazyColumnPerBlockCumulativeRolling}; + +const PATTERN_COUNT: usize = 3; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum PatternId { + Coinjoin, + Consolidation, + BatchPayout, +} + +const PATTERN_IDS: [PatternId; PATTERN_COUNT] = [ + PatternId::Coinjoin, + PatternId::Consolidation, + PatternId::BatchPayout, +]; + +impl ColumnId for PatternId { + type Row + = [T; PATTERN_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &PATTERN_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(PATTERN_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Clone, Traversable)] +pub struct Flags { + pub is_coinjoin: V, + pub is_consolidation: V, + pub is_batch_payout: V, +} /// Transaction counts by detected structural pattern. /// /// These are heuristic classifications of transactions, not protocol labels. -#[derive(Traversable)] +#[derive(Deref, DerefMut, Traversable)] pub struct CountVecs { /// CoinJoin candidates with repeated output values and no address reuse. - pub coinjoin: PerBlockCumulativeRolling, + pub coinjoin: LazyColumnPerBlockCumulativeRolling, /// Transactions with at least five times as many inputs as outputs. - pub consolidation: PerBlockCumulativeRolling, + pub consolidation: LazyColumnPerBlockCumulativeRolling, /// Non-coinbase transactions with at least five times as many outputs as inputs. - pub batch_payout: PerBlockCumulativeRolling, + pub batch_payout: LazyColumnPerBlockCumulativeRolling, + #[deref] + #[deref_mut] + #[traversable(hidden)] + pub source: ColumnarPerBlockCumulativeRolling, } -#[derive(Traversable)] +#[derive(Deref, DerefMut, Traversable)] pub struct Vecs { pub count: CountVecs, - pub is_coinjoin: M::Stored>>, - pub is_consolidation: M::Stored>>, - pub is_batch_payout: M::Stored>>, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub flags: Flags< + LazyColumnVec, PatternId>, PatternId>, + >, + #[traversable(hidden)] + pub flags_source: M::Stored, PatternId>>>, +} + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{PATTERN_IDS, PatternId}; + + #[test] + fn pattern_columns_match_public_field_order() { + assert_eq!(PatternId::ALL, PATTERN_IDS); + assert_eq!( + PatternId::from_fn(|pattern| pattern), + [ + PatternId::Coinjoin, + PatternId::Consolidation, + PatternId::BatchPayout, + ] + ); + } } diff --git a/crates/brk_computer/src/transactions/versions/compute.rs b/crates/brk_computer/src/transactions/versions/compute.rs index c7690ce21..d982f441c 100644 --- a/crates/brk_computer/src/transactions/versions/compute.rs +++ b/crates/brk_computer/src/transactions/versions/compute.rs @@ -2,22 +2,22 @@ use brk_error::Result; use brk_indexer::Indexer; use vecdb::Exit; -use super::Vecs; +use super::{Vecs, VersionId}; impl Vecs { pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> { let lengths = indexer.safe_lengths(); let starting_height = lengths.height; let counts = &indexer.vecs().transaction_features.count; - for (metrics, source) in [ - (&mut self.v1, &counts.v1), - (&mut self.v2, &counts.v2), - (&mut self.v3, &counts.v3), - (&mut self.other, &counts.other_version), - ] { - metrics.compute_cumulative(starting_height, source, exit)?; - } - - Ok(()) + self.compute_columns( + starting_height, + |version| match version { + VersionId::V1 => &counts.v1, + VersionId::V2 => &counts.v2, + VersionId::V3 => &counts.v3, + VersionId::Other => &counts.other_version, + }, + exit, + ) } } diff --git a/crates/brk_computer/src/transactions/versions/import.rs b/crates/brk_computer/src/transactions/versions/import.rs index eab5cf7aa..cc4bcf7f9 100644 --- a/crates/brk_computer/src/transactions/versions/import.rs +++ b/crates/brk_computer/src/transactions/versions/import.rs @@ -1,11 +1,14 @@ use brk_error::Result; use brk_types::Version; -use vecdb::Database; +use vecdb::{Database, ReadOnlyClone}; -use super::Vecs; +use super::{Vecs, VersionId}; use crate::{ indexes, - internal::{CachedWindowStartVec, PerBlockCumulativeRolling, Windows}, + internal::{ + CachedWindowStartVec, ColumnarPerBlockCumulativeRolling, + LazyColumnPerBlockCumulativeRolling, Windows, + }, }; impl Vecs { @@ -15,35 +18,30 @@ impl Vecs { indexes: &indexes::Vecs, cached_starts: &Windows<&CachedWindowStartVec>, ) -> Result { + let source = ColumnarPerBlockCumulativeRolling::forced_import( + db, + "tx_version_count_cumulative", + version, + |_| (), + )?; + let counts = source.cumulative.read_only_clone(); + let import = |name, version_id| { + LazyColumnPerBlockCumulativeRolling::new( + name, + version, + &counts, + version_id, + indexes, + cached_starts, + ) + }; + Ok(Self { - v1: PerBlockCumulativeRolling::forced_import( - db, - "tx_v1", - version, - indexes, - cached_starts, - )?, - v2: PerBlockCumulativeRolling::forced_import( - db, - "tx_v2", - version, - indexes, - cached_starts, - )?, - v3: PerBlockCumulativeRolling::forced_import( - db, - "tx_v3", - version, - indexes, - cached_starts, - )?, - other: PerBlockCumulativeRolling::forced_import( - db, - "tx_other_version", - version, - indexes, - cached_starts, - )?, + v1: import("tx_v1", VersionId::V1), + v2: import("tx_v2", VersionId::V2), + v3: import("tx_v3", VersionId::V3), + other: import("tx_other_version", VersionId::Other), + source, }) } } diff --git a/crates/brk_computer/src/transactions/versions/mod.rs b/crates/brk_computer/src/transactions/versions/mod.rs index 1136f9ebd..3bd413fb5 100644 --- a/crates/brk_computer/src/transactions/versions/mod.rs +++ b/crates/brk_computer/src/transactions/versions/mod.rs @@ -2,4 +2,4 @@ mod compute; mod import; mod vecs; -pub use vecs::Vecs; +pub use vecs::{Vecs, VersionId}; diff --git a/crates/brk_computer/src/transactions/versions/vecs.rs b/crates/brk_computer/src/transactions/versions/vecs.rs index f2c5adc5b..7b12c7763 100644 --- a/crates/brk_computer/src/transactions/versions/vecs.rs +++ b/crates/brk_computer/src/transactions/versions/vecs.rs @@ -1,13 +1,100 @@ use brk_traversable::Traversable; -use brk_types::StoredU64; -use vecdb::{Rw, StorageMode}; +use brk_types::{StoredU64, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ColumnId, Rw, StorageMode, VecValue}; -use crate::internal::PerBlockCumulativeRolling; +use crate::internal::{ColumnarPerBlockCumulativeRolling, LazyColumnPerBlockCumulativeRolling}; -#[derive(Traversable)] -pub struct Vecs { - pub v1: PerBlockCumulativeRolling, - pub v2: PerBlockCumulativeRolling, - pub v3: PerBlockCumulativeRolling, - pub other: PerBlockCumulativeRolling, +const VERSION_COUNT: usize = 4; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum VersionId { + V1, + V2, + V3, + Other, +} + +const VERSION_IDS: [VersionId; VERSION_COUNT] = [ + VersionId::V1, + VersionId::V2, + VersionId::V3, + VersionId::Other, +]; + +impl ColumnId for VersionId { + type Row + = [T; VERSION_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &VERSION_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + std::array::from_fn(|index| create(VERSION_IDS[index])) + } + + #[inline] + fn map(row: Self::Row, create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(create) + } +} + +#[derive(Deref, DerefMut, Traversable)] +pub struct Vecs { + pub v1: LazyColumnPerBlockCumulativeRolling, + pub v2: LazyColumnPerBlockCumulativeRolling, + pub v3: LazyColumnPerBlockCumulativeRolling, + pub other: LazyColumnPerBlockCumulativeRolling, + #[deref] + #[deref_mut] + #[traversable(hidden)] + pub source: ColumnarPerBlockCumulativeRolling, +} + +#[cfg(test)] +mod tests { + use vecdb::ColumnId; + + use super::{VERSION_IDS, VersionId}; + + #[test] + fn version_columns_match_public_field_order() { + assert_eq!(VersionId::ALL, VERSION_IDS); + assert_eq!( + VersionId::from_fn(|version| version), + [ + VersionId::V1, + VersionId::V2, + VersionId::V3, + VersionId::Other + ] + ); + } } diff --git a/crates/brk_mcp/Cargo.toml b/crates/brk_mcp/Cargo.toml index caac8efbc..00ba0d1c0 100644 --- a/crates/brk_mcp/Cargo.toml +++ b/crates/brk_mcp/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace = true axum = { workspace = true } base64 = "0.23.1" brk_logger = { workspace = true } -rmcp = { version = "3.1.1", default-features = false, features = ["server", "transport-streamable-http-server"] } +rmcp = { version = "3.1.2", default-features = false, features = ["server", "transport-streamable-http-server"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["macros", "net", "sync", "time"] } diff --git a/crates/brk_traversable/src/lib.rs b/crates/brk_traversable/src/lib.rs index 18d392c3c..a1c08f1ae 100644 --- a/crates/brk_traversable/src/lib.rs +++ b/crates/brk_traversable/src/lib.rs @@ -8,9 +8,11 @@ pub use brk_traversable_derive::Traversable; use schemars::JsonSchema; use serde::Serialize; use vecdb::{ - AggFold, AnyExportableVec, AnyVec, BytesVec, BytesVecValue, CachedVec, CompressionStrategy, - DeltaOp, EagerVec, Formattable, LazyAggVec, LazyDeltaVec, LazyVec, RawStrategy, - ReadOnlyCompressedVec, ReadOnlyRawVec, StoredVec, TypedVec, VecIndex, VecValue, + AggFold, AnyExportableVec, AnyVec, BytesVec, BytesVecValue, CachedVec, ColumnId, ColumnarVec, + CompressionStrategy, DeltaOp, EagerVec, Formattable, LazyAggVec, LazyColumnSumVec, + LazyColumnVec, LazyColumnarVec, LazyDeltaVec, LazyVec, RawStrategy, ReadOnlyColumnarVec, + ReadOnlyCompressedVec, ReadOnlyRawVec, ReadableColumnarVec, StoredVec, TypedVec, VecIndex, + VecValue, }; pub trait Traversable { @@ -135,6 +137,52 @@ where } } +impl Traversable for ColumnarVec +where + V: StoredVec, + C: ColumnId, + C::Row: Formattable + Serialize + JsonSchema, +{ + fn iter_any_exportable(&self) -> impl Iterator { + std::iter::once(self as &dyn AnyExportableVec) + } + + fn to_tree_node(&self) -> TreeNode { + make_leaf::, _>(self) + } +} + +impl Traversable for ReadOnlyColumnarVec +where + V: StoredVec, + C: ColumnId, + C::Row: Formattable + Serialize + JsonSchema, +{ + fn iter_any_exportable(&self) -> impl Iterator { + std::iter::once(self as &dyn AnyExportableVec) + } + + fn to_tree_node(&self) -> TreeNode { + make_leaf::, _>(self) + } +} + +impl Traversable for LazyColumnarVec +where + C: ColumnId, + S: ReadableColumnarVec, + T: VecValue, + C::Row: Formattable + Serialize + JsonSchema, +{ + fn iter_any_exportable(&self) -> impl Iterator { + std::iter::once(self as &dyn AnyExportableVec) + } + + fn to_tree_node(&self) -> TreeNode { + make_leaf::, _>(self) + } +} + // Read-only compressed vec (PcoVec::ReadOnly, LZ4Vec::ReadOnly, ZstdVec::ReadOnly) impl Traversable for ReadOnlyCompressedVec where @@ -217,6 +265,36 @@ where } } +impl Traversable for LazyColumnVec +where + C: ColumnId, + S: ReadableColumnarVec, + S::T: Formattable + Serialize + JsonSchema, +{ + fn iter_any_exportable(&self) -> impl Iterator { + std::iter::once(self as &dyn AnyExportableVec) + } + + fn to_tree_node(&self) -> TreeNode { + make_leaf::(self) + } +} + +impl Traversable for LazyColumnSumVec +where + C: ColumnId, + S: ReadableColumnarVec, + S::T: Formattable + Serialize + JsonSchema + std::ops::AddAssign, +{ + fn iter_any_exportable(&self) -> impl Iterator { + std::iter::once(self as &dyn AnyExportableVec) + } + + fn to_tree_node(&self) -> TreeNode { + make_leaf::(self) + } +} + impl Traversable for CachedVec { fn to_tree_node(&self) -> TreeNode { self.inner.to_tree_node() diff --git a/crates/brk_types/src/byte_count.rs b/crates/brk_types/src/byte_count.rs new file mode 100644 index 000000000..3475a9b43 --- /dev/null +++ b/crates/brk_types/src/byte_count.rs @@ -0,0 +1,144 @@ +use std::{ + iter::Sum, + ops::{Add, AddAssign, Div, Sub, SubAssign}, +}; + +use derive_more::Deref; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use vecdb::{CheckedSub, Formattable, Pco}; + +#[derive( + Debug, + Default, + Deref, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Pco, + JsonSchema, +)] +pub struct Bytes(u64); + +impl Bytes { + #[inline] + pub const fn new(value: u64) -> Self { + Self(value) + } +} + +impl From for Bytes { + #[inline] + fn from(value: u64) -> Self { + Self(value) + } +} + +impl From for Bytes { + #[inline] + fn from(value: u32) -> Self { + Self(u64::from(value)) + } +} + +impl From for Bytes { + #[inline] + fn from(value: usize) -> Self { + Self(value as u64) + } +} + +impl From for u64 { + #[inline] + fn from(value: Bytes) -> Self { + value.0 + } +} + +impl From for Bytes { + #[inline] + fn from(value: f64) -> Self { + Self(value.max(0.0) as u64) + } +} + +impl From for f64 { + #[inline] + fn from(value: Bytes) -> Self { + value.0 as f64 + } +} + +impl Add for Bytes { + type Output = Self; + + #[inline] + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for Bytes { + #[inline] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sub for Bytes { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for Bytes { + #[inline] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl Div for Bytes { + type Output = Self; + + #[inline] + fn div(self, rhs: usize) -> Self::Output { + Self(self.0 / rhs as u64) + } +} + +impl Sum for Bytes { + fn sum>(iter: I) -> Self { + Self(iter.map(|value| value.0).sum()) + } +} + +impl CheckedSub for Bytes { + #[inline] + fn checked_sub(self, rhs: Self) -> Option { + self.0.checked_sub(rhs.0).map(Self) + } +} + +impl std::fmt::Display for Bytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buffer = itoa::Buffer::new(); + f.write_str(buffer.format(self.0)) + } +} + +impl Formattable for Bytes { + #[inline(always)] + fn write_to(&self, buffer: &mut Vec) { + let mut formatted = itoa::Buffer::new(); + buffer.extend_from_slice(formatted.format(self.0).as_bytes()); + } +} diff --git a/crates/brk_types/src/lib.rs b/crates/brk_types/src/lib.rs index 8a3ed429f..8f4f76435 100644 --- a/crates/brk_types/src/lib.rs +++ b/crates/brk_types/src/lib.rs @@ -37,6 +37,7 @@ mod block_tx_index; mod block_weight_entry; mod blockhash; mod blockhash_prefix; +mod byte_count; mod bytes; mod capital_sentiment_phase; mod cents; @@ -96,6 +97,7 @@ mod next_block_hash; mod ohlc; mod op_return_index; mod op_return_kind; +mod op_return_policy_id; mod option_ext; mod outpoint; mod outpoint_prefix; @@ -239,6 +241,7 @@ pub use block_tx_index::*; pub use block_weight_entry::*; pub use blockhash::*; pub use blockhash_prefix::*; +pub use byte_count::*; pub use bytes::*; pub use capital_sentiment_phase::*; pub use cents::*; @@ -298,6 +301,7 @@ pub use next_block_hash::*; pub use ohlc::*; pub use op_return_index::*; pub use op_return_kind::*; +pub use op_return_policy_id::*; pub use option_ext::*; pub use outpoint::*; pub use outpoint_prefix::*; diff --git a/crates/brk_types/src/op_return_kind.rs b/crates/brk_types/src/op_return_kind.rs index 425189ed7..cb08aa358 100644 --- a/crates/brk_types/src/op_return_kind.rs +++ b/crates/brk_types/src/op_return_kind.rs @@ -1,7 +1,9 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use strum::{AsRefStr, Display}; -use vecdb::{Bytes, Formattable, Pco, TransparentPco}; +use vecdb::{Bytes, ColumnId, Formattable, Pco, TransparentPco, VecValue, Version}; + +pub const OP_RETURN_KIND_COUNT: usize = OpReturnKind::Unknown as usize + 1; #[derive( Debug, @@ -47,6 +49,32 @@ pub enum OpReturnKind { Unknown, } +pub const OP_RETURN_KINDS: [OpReturnKind; OP_RETURN_KIND_COUNT] = [ + OpReturnKind::Runes, + OpReturnKind::VeriBlock, + OpReturnKind::Omni, + OpReturnKind::Stacks, + OpReturnKind::Blockstack, + OpReturnKind::Colu, + OpReturnKind::OpenAssets, + OpReturnKind::Komodo, + OpReturnKind::CoinSpark, + OpReturnKind::Poet, + OpReturnKind::Docproof, + OpReturnKind::OpenTimestamps, + OpReturnKind::Factom, + OpReturnKind::EternityWall, + OpReturnKind::Memo, + OpReturnKind::Bitproof, + OpReturnKind::Ascribe, + OpReturnKind::Stampery, + OpReturnKind::Epobc, + OpReturnKind::BareHash, + OpReturnKind::Text, + OpReturnKind::Empty, + OpReturnKind::Unknown, +]; + impl OpReturnKind { fn is_valid(value: u8) -> bool { value <= Self::Unknown as u8 @@ -96,3 +124,60 @@ impl Pco for OpReturnKind { } impl TransparentPco for OpReturnKind {} + +impl ColumnId for OpReturnKind { + type Row + = [T; OP_RETURN_KIND_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &OP_RETURN_KINDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + OP_RETURN_KINDS.map(f) + } + + #[inline] + fn map(row: Self::Row, f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn column_order_matches_discriminants() { + for (index, kind) in OP_RETURN_KINDS.into_iter().enumerate() { + assert_eq!(kind as usize, index); + assert_eq!(kind.index(), index); + } + } +} diff --git a/crates/brk_types/src/op_return_policy_id.rs b/crates/brk_types/src/op_return_policy_id.rs new file mode 100644 index 000000000..3b889b604 --- /dev/null +++ b/crates/brk_types/src/op_return_policy_id.rs @@ -0,0 +1,76 @@ +use vecdb::{ColumnId, VecValue, Version}; + +pub const OP_RETURN_POLICY_COUNT: usize = OpReturnPolicyId::Multiple as usize + 1; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum OpReturnPolicyId { + PreV30Standard, + PreV30Nonstandard, + Oversized, + Multiple, +} + +pub const OP_RETURN_POLICY_IDS: [OpReturnPolicyId; OP_RETURN_POLICY_COUNT] = [ + OpReturnPolicyId::PreV30Standard, + OpReturnPolicyId::PreV30Nonstandard, + OpReturnPolicyId::Oversized, + OpReturnPolicyId::Multiple, +]; + +impl ColumnId for OpReturnPolicyId { + type Row + = [T; OP_RETURN_POLICY_COUNT] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &OP_RETURN_POLICY_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + OP_RETURN_POLICY_IDS.map(f) + } + + #[inline] + fn map(row: Self::Row, f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn column_order_matches_discriminants() { + for (index, policy) in OP_RETURN_POLICY_IDS.into_iter().enumerate() { + assert_eq!(policy as usize, index); + assert_eq!(policy.index(), index); + } + } +} diff --git a/crates/brk_types/src/output_type.rs b/crates/brk_types/src/output_type.rs index 98e88f2b5..7468aea6a 100644 --- a/crates/brk_types/src/output_type.rs +++ b/crates/brk_types/src/output_type.rs @@ -78,6 +78,8 @@ pub enum OutputTypeNormalized { } impl OutputType { + pub const COUNT: usize = Self::Unknown as usize + 1; + pub const ADDR_TYPES: [Self; 8] = [ Self::P2PK65, Self::P2PK33, diff --git a/crates/brk_types/src/percentile.rs b/crates/brk_types/src/percentile.rs index 0f87dfc2e..215821022 100644 --- a/crates/brk_types/src/percentile.rs +++ b/crates/brk_types/src/percentile.rs @@ -1,4 +1,5 @@ use crate::VSize; +use vecdb::{ColumnId, VecValue, Version}; /// Standard percentile values used throughout BRK. pub const PERCENTILES: [u8; 19] = [ @@ -8,6 +9,208 @@ pub const PERCENTILES: [u8; 19] = [ /// Length of the PERCENTILES array. pub const PERCENTILES_LEN: usize = PERCENTILES.len(); +/// Percentiles used by the rarity meter, in physical column order. +pub const RARITY_PERCENTILES: [f64; 19] = [ + 0.001, 0.005, 0.01, 0.02, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 0.95, + 0.98, 0.99, 0.995, 0.999, +]; + +pub const RARITY_PERCENTILES_LEN: usize = RARITY_PERCENTILES.len(); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum PercentileId { + Pct05, + Pct10, + Pct15, + Pct20, + Pct25, + Pct30, + Pct35, + Pct40, + Pct45, + Pct50, + Pct55, + Pct60, + Pct65, + Pct70, + Pct75, + Pct80, + Pct85, + Pct90, + Pct95, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum RarityPercentileId { + Pct0_1, + Pct0_5, + Pct1, + Pct2, + Pct5, + Pct10, + Pct20, + Pct30, + Pct40, + Pct50, + Pct60, + Pct70, + Pct80, + Pct90, + Pct95, + Pct98, + Pct99, + Pct99_5, + Pct99_9, +} + +pub const RARITY_PERCENTILE_IDS: [RarityPercentileId; RARITY_PERCENTILES_LEN] = [ + RarityPercentileId::Pct0_1, + RarityPercentileId::Pct0_5, + RarityPercentileId::Pct1, + RarityPercentileId::Pct2, + RarityPercentileId::Pct5, + RarityPercentileId::Pct10, + RarityPercentileId::Pct20, + RarityPercentileId::Pct30, + RarityPercentileId::Pct40, + RarityPercentileId::Pct50, + RarityPercentileId::Pct60, + RarityPercentileId::Pct70, + RarityPercentileId::Pct80, + RarityPercentileId::Pct90, + RarityPercentileId::Pct95, + RarityPercentileId::Pct98, + RarityPercentileId::Pct99, + RarityPercentileId::Pct99_5, + RarityPercentileId::Pct99_9, +]; + +pub const PERCENTILE_IDS: [PercentileId; PERCENTILES_LEN] = [ + PercentileId::Pct05, + PercentileId::Pct10, + PercentileId::Pct15, + PercentileId::Pct20, + PercentileId::Pct25, + PercentileId::Pct30, + PercentileId::Pct35, + PercentileId::Pct40, + PercentileId::Pct45, + PercentileId::Pct50, + PercentileId::Pct55, + PercentileId::Pct60, + PercentileId::Pct65, + PercentileId::Pct70, + PercentileId::Pct75, + PercentileId::Pct80, + PercentileId::Pct85, + PercentileId::Pct90, + PercentileId::Pct95, +]; + +impl PercentileId { + #[inline] + pub const fn percentile(self) -> u8 { + PERCENTILES[self as usize] + } +} + +impl RarityPercentileId { + #[inline] + pub const fn percentile(self) -> f64 { + RARITY_PERCENTILES[self as usize] + } +} + +impl ColumnId for PercentileId { + type Row + = [T; PERCENTILES_LEN] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &PERCENTILE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + PERCENTILE_IDS.map(f) + } + + #[inline] + fn map(row: Self::Row, f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(f) + } +} + +impl ColumnId for RarityPercentileId { + type Row + = [T; RARITY_PERCENTILES_LEN] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &RARITY_PERCENTILE_IDS; + + #[inline] + fn index(self) -> usize { + self as usize + } + + #[inline] + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + #[inline] + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + #[inline] + fn from_fn(f: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + RARITY_PERCENTILE_IDS.map(f) + } + + #[inline] + fn map(row: Self::Row, f: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + row.map(f) + } +} + /// Get a percentile value from a sorted slice using nearest-rank method. /// /// # Panics @@ -43,3 +246,24 @@ pub fn get_weighted_percentile(sorted_with_vsizes: &[(T, VSize)], perc } sorted_with_vsizes.last().unwrap().0.clone() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn percentile_ids_match_values_and_storage_order() { + for (index, id) in PERCENTILE_IDS.into_iter().enumerate() { + assert_eq!(id.index(), index); + assert_eq!(id.percentile(), PERCENTILES[index]); + } + } + + #[test] + fn rarity_percentile_ids_match_values_and_storage_order() { + for (index, id) in RARITY_PERCENTILE_IDS.into_iter().enumerate() { + assert_eq!(id.index(), index); + assert_eq!(id.percentile(), RARITY_PERCENTILES[index]); + } + } +} diff --git a/crates/rawdb/Cargo.toml b/crates/rawdb/Cargo.toml index 3af9820ba..b3ecc4ea2 100644 --- a/crates/rawdb/Cargo.toml +++ b/crates/rawdb/Cargo.toml @@ -17,7 +17,7 @@ memmap2 = "0.9.11" parking_lot = { workspace = true } rayon = { workspace = true } smallvec = "1.15" -thiserror = "2.0.19" +thiserror = "2.0.20" [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/vecdb/src/traits/formattable.rs b/crates/vecdb/src/traits/formattable.rs index 84be8befb..058413cf5 100644 --- a/crates/vecdb/src/traits/formattable.rs +++ b/crates/vecdb/src/traits/formattable.rs @@ -70,6 +70,35 @@ impl Formattable for bool { } } +impl Formattable for [T; N] { + fn write_to(&self, buf: &mut Vec) { + buf.push(b'['); + for (index, value) in self.iter().enumerate() { + if index > 0 { + buf.push(b','); + } + value.fmt_json(buf); + } + buf.push(b']'); + } + + fn fmt_csv(&self, output: &mut String) -> fmt::Result { + let mut json = Vec::new(); + self.write_to(&mut json); + let json = std::str::from_utf8(&json).map_err(|_| fmt::Error)?; + + output.push('"'); + for character in json.chars() { + if character == '"' { + output.push('"'); + } + output.push(character); + } + output.push('"'); + Ok(()) + } +} + impl Formattable for Option { #[inline] fn write_to(&self, buf: &mut Vec) { @@ -94,3 +123,20 @@ impl Formattable for Option { } } } + +#[cfg(test)] +mod tests { + use super::Formattable; + + #[test] + fn arrays_format_as_json_and_one_csv_cell() { + let values = [1_u16, 2, 3]; + let mut json = Vec::new(); + values.fmt_json(&mut json); + assert_eq!(json, b"[1,2,3]"); + + let mut csv = String::new(); + values.fmt_csv(&mut csv).unwrap(); + assert_eq!(csv, "\"[1,2,3]\""); + } +} diff --git a/crates/vecdb/src/traits/printable.rs b/crates/vecdb/src/traits/printable.rs index 7f2a949e9..bb76708f0 100644 --- a/crates/vecdb/src/traits/printable.rs +++ b/crates/vecdb/src/traits/printable.rs @@ -27,6 +27,14 @@ pub fn short_type_name() -> &'static str { /// Also unwraps `Option` to just `T` since Option is a serialization /// concern (null in JSON) not a type identity. fn shorten_type_name(full: &str) -> String { + if let Some(inner) = full + .strip_prefix('[') + .and_then(|value| value.strip_suffix(']')) + && let Some((element, len)) = inner.rsplit_once(';') + { + return format!("[{}; {}]", shorten_type_name(element.trim()), len.trim()); + } + let generic_start = full.find('<'); let (base, generics) = match generic_start { @@ -56,6 +64,19 @@ fn shorten_type_name(full: &str) -> String { } } +#[cfg(test)] +mod tests { + use super::shorten_type_name; + + #[test] + fn shortens_array_element_paths_without_dropping_brackets() { + assert_eq!( + shorten_type_name("[some::module::Value; 23]"), + "[Value; 23]" + ); + } +} + /// Split generic parameters respecting nested angle brackets. /// `A, B, E` -> ["A", "B", "E"] fn split_generic_params(params: &str) -> Vec<&str> { diff --git a/crates/vecdb/src/variants/cached/cloneable.rs b/crates/vecdb/src/variants/cached/cloneable.rs index b146c9e18..f7e4b66dc 100644 --- a/crates/vecdb/src/variants/cached/cloneable.rs +++ b/crates/vecdb/src/variants/cached/cloneable.rs @@ -10,6 +10,7 @@ where T: VecValue, { fn cached(&self) -> Arc<[T]>; + fn clear(&self); fn cached_boxed_clone(&self) -> CachedBoxedVec; } @@ -35,6 +36,10 @@ where self.cached() } + fn clear(&self) { + CachedVec::clear(self); + } + fn cached_boxed_clone(&self) -> CachedBoxedVec { Box::new(self.clone()) } diff --git a/crates/vecdb/src/variants/columnar/mod.rs b/crates/vecdb/src/variants/columnar/mod.rs index fc7b6a707..78d5acace 100644 --- a/crates/vecdb/src/variants/columnar/mod.rs +++ b/crates/vecdb/src/variants/columnar/mod.rs @@ -80,23 +80,27 @@ where } } -/// Owned scalar projection of one columnar source. -pub struct ColumnarVecColumn +/// Lazy scalar projection of one columnar source. +pub struct LazyColumnVec where C: ColumnId, S: ReadableColumnarVec, { + name: Arc, + base_version: Version, source: S, column: C, } -impl Clone for ColumnarVecColumn +impl Clone for LazyColumnVec where C: ColumnId, S: ReadableColumnarVec, { fn clone(&self) -> Self { Self { + name: Arc::clone(&self.name), + base_version: self.base_version, source: self.source.clone(), column: self.column, } @@ -178,19 +182,24 @@ where /// Returns an owned read-only view of one persisted scalar column. /// Uncommitted rows become visible to the view after `write()`. - pub fn column(&self, column: C) -> ColumnarVecColumn, C> { - self.read_only_clone().column(column) + pub fn column( + &self, + name: &str, + version: Version, + column: C, + ) -> LazyColumnVec, C> { + self.read_only_clone().column(name, version, column) } /// Returns a lazy sum of selected persisted columns. /// Uncommitted rows become visible to the view after `write()`. - pub fn sum_columns( + pub fn sum_columns( &self, name: &str, version: Version, - columns: [C; M], - ) -> ColumnarSumVec, C> { - ColumnarSumVec::new(name, version, self.read_only_clone(), columns) + columns: impl IntoIterator, + ) -> LazyColumnSumVec, C> { + LazyColumnSumVec::new(name, version, self.read_only_clone(), columns) } pub fn reserve_pushed(&mut self, additional: usize) { diff --git a/crates/vecdb/src/variants/columnar/read.rs b/crates/vecdb/src/variants/columnar/read.rs index ce2c5e130..6b6e72c14 100644 --- a/crates/vecdb/src/variants/columnar/read.rs +++ b/crates/vecdb/src/variants/columnar/read.rs @@ -1,8 +1,8 @@ -use crate::{AnyVec, READ_CHUNK_SIZE, ReadableVec, StoredVec, VecIndex, VecValue}; +use crate::{AnyVec, READ_CHUNK_SIZE, ReadableVec, StoredVec, VecIndex, VecValue, Version}; use super::{ - ColumnId, ColumnarVec, ColumnarVecColumn, ReadOnlyColumnarVec, ReadableColumnarVec, - rows_per_block, schema::validate_column, + ColumnId, ColumnarVec, LazyColumnVec, ReadOnlyColumnarVec, ReadableColumnarVec, rows_per_block, + schema::validate_column, }; /// Reads matrix rows from a flat page-blocked scalar vector. @@ -123,6 +123,17 @@ where V: StoredVec, C: ColumnId, { + #[inline(always)] + fn collect_one_at(&self, index: usize) -> Option> { + if index >= self.stored_rows { + return self.pushed.get(index - self.stored_rows).cloned(); + } + + let mut row = Vec::with_capacity(1); + read_rows::(&self.vec, self.flat_rows(), index, index + 1, &mut row); + row.pop() + } + fn cursor_chunk_size(&self) -> usize { Self::ROWS_PER_BLOCK * READ_CHUNK_SIZE.div_ceil(Self::ROWS_PER_BLOCK) } @@ -216,14 +227,19 @@ where } } -impl ColumnarVecColumn +impl LazyColumnVec where C: ColumnId, S: ReadableColumnarVec, { - pub(super) fn new(source: S, column: C) -> Self { + pub(super) fn new(name: &str, version: Version, source: S, column: C) -> Self { validate_column(column); - Self { source, column } + Self { + name: name.into(), + base_version: version, + source, + column, + } } } @@ -289,7 +305,7 @@ where Ok(acc.expect("column fold accumulator")) } -impl ReadableVec for ColumnarVecColumn +impl ReadableVec for LazyColumnVec where C: ColumnId, S: ReadableColumnarVec, @@ -345,7 +361,7 @@ where let from = from.min(vec.len()); let to = to.min(vec.len()); let chunk = vec.cursor_chunk_size().max(1); - let mut buf = Vec::with_capacity(chunk); + let mut buf = Vec::with_capacity(chunk.min(to.saturating_sub(from))); let mut at = from; while at < to { let end = (at + chunk).min(to); @@ -375,7 +391,7 @@ where let from = from.min(vec.len()); let to = to.min(vec.len()); let chunk = vec.cursor_chunk_size().max(1); - let mut buf = Vec::with_capacity(chunk); + let mut buf = Vec::with_capacity(chunk.min(to.saturating_sub(from))); let mut at = from; while at < to { let end = (at + chunk).min(to); diff --git a/crates/vecdb/src/variants/columnar/schema.rs b/crates/vecdb/src/variants/columnar/schema.rs index 7af8868d4..d631377d5 100644 --- a/crates/vecdb/src/variants/columnar/schema.rs +++ b/crates/vecdb/src/variants/columnar/schema.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use crate::{Error, ReadableVec, Result, VecIndex, VecValue, Version}; -use super::{ColumnarSumVec, ColumnarVecColumn}; +use super::{LazyColumnSumVec, LazyColumnVec}; /// Typed description of a fixed column set and its logical row representation. pub trait ColumnId: Copy + Debug + Eq + Ord + Send + Sync + 'static { @@ -49,23 +49,23 @@ where where F: FnMut(C, usize, &[Self::T]); - fn column(&self, column: C) -> ColumnarVecColumn + fn column(&self, name: &str, version: Version, column: C) -> LazyColumnVec where Self: Sized, { - ColumnarVecColumn::new(self.clone(), column) + LazyColumnVec::new(name, version, self.clone(), column) } - fn sum_columns( + fn sum_columns( &self, name: &str, version: Version, - columns: [C; M], - ) -> ColumnarSumVec + columns: impl IntoIterator, + ) -> LazyColumnSumVec where Self: Sized, { - ColumnarSumVec::new(name, version, self.clone(), columns) + LazyColumnSumVec::new(name, version, self.clone(), columns) } } @@ -93,13 +93,3 @@ pub(super) fn validate_column(column: C) { "invalid column ID at physical index {index}", ); } - -pub(super) fn selection_version(kind: u32, columns: &[C]) -> Version { - let mut hash = 2_166_136_261_u32 ^ kind; - for column in columns { - let index = column.index() as u64 + 1; - hash ^= index as u32 ^ (index >> 32) as u32; - hash = hash.wrapping_mul(16_777_619); - } - Version::new(kind * 1_000_000 + hash % 1_000_000 + 1) -} diff --git a/crates/vecdb/src/variants/columnar/sum.rs b/crates/vecdb/src/variants/columnar/sum.rs index db005e8b4..c40b8c30f 100644 --- a/crates/vecdb/src/variants/columnar/sum.rs +++ b/crates/vecdb/src/variants/columnar/sum.rs @@ -5,11 +5,11 @@ use crate::{AnyVec, ReadOnlyClone, ReadableVec, TypedVec, Version, short_type_na use super::{ ColumnId, ReadableColumnarVec, read::{fold_readable, try_fold_readable}, - schema::{selection_version, validate_column}, + schema::validate_column, }; /// Lazy scalar sum of selected columns from any readable columnar source. -pub struct ColumnarSumVec +pub struct LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, @@ -18,10 +18,9 @@ where base_version: Version, source: S, columns: Box<[C]>, - selection_version: Version, } -impl Clone for ColumnarSumVec +impl Clone for LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, @@ -32,46 +31,51 @@ where base_version: self.base_version, source: self.source.clone(), columns: self.columns.clone(), - selection_version: self.selection_version, } } } -impl ColumnarSumVec +impl LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, { - /// Creates a lazy sum from a non-empty array of distinct column IDs. - pub fn new(name: &str, version: Version, source: S, columns: [C; M]) -> Self { - assert!(M > 0, "ColumnarSumVec requires at least one column"); - let mut columns = columns.to_vec(); + /// Creates a lazy sum from non-empty, distinct column IDs. + pub fn new( + name: &str, + version: Version, + source: S, + columns: impl IntoIterator, + ) -> Self { + let mut columns: Vec<_> = columns.into_iter().collect(); + assert!( + !columns.is_empty(), + "LazyColumnSumVec requires at least one column" + ); for &column in &columns { validate_column(column); } columns.sort_unstable_by_key(|column| column.index()); assert!( columns.windows(2).all(|pair| pair[0] != pair[1]), - "ColumnarSumVec cannot sum the same column twice", + "LazyColumnSumVec cannot sum the same column twice", ); - let selection_version = selection_version(2, &columns); Self { name: Arc::from(name), base_version: version, source, columns: columns.into_boxed_slice(), - selection_version, } } } -impl AnyVec for ColumnarSumVec +impl AnyVec for LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, { fn version(&self) -> Version { - self.base_version + self.source.version() + self.selection_version + self.base_version + self.source.version() + Version::from(self.columns.len()) } fn name(&self) -> &str { @@ -99,7 +103,7 @@ where } } -impl TypedVec for ColumnarSumVec +impl TypedVec for LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, @@ -108,7 +112,7 @@ where type T = S::T; } -impl ReadableVec for ColumnarSumVec +impl ReadableVec for LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, @@ -165,7 +169,7 @@ where } } -impl ReadOnlyClone for ColumnarSumVec +impl ReadOnlyClone for LazyColumnSumVec where C: ColumnId, S: ReadableColumnarVec, diff --git a/crates/vecdb/src/variants/columnar/traits.rs b/crates/vecdb/src/variants/columnar/traits.rs index 6a458ba4a..2134f88c8 100644 --- a/crates/vecdb/src/variants/columnar/traits.rs +++ b/crates/vecdb/src/variants/columnar/traits.rs @@ -8,10 +8,7 @@ use crate::{ short_type_name, }; -use super::{ - ColumnId, ColumnarVec, ColumnarVecColumn, ReadOnlyColumnarVec, ReadableColumnarVec, - schema::selection_version, -}; +use super::{ColumnId, ColumnarVec, LazyColumnVec, ReadOnlyColumnarVec, ReadableColumnarVec}; impl ImportableVec for ColumnarVec where @@ -103,17 +100,17 @@ where } } -impl AnyVec for ColumnarVecColumn +impl AnyVec for LazyColumnVec where C: ColumnId, S: ReadableColumnarVec, { fn version(&self) -> Version { - self.source.version() + selection_version(1, &[self.column]) + self.base_version + self.source.version() + Version::ONE } fn name(&self) -> &str { - self.source.name() + &self.name } fn len(&self) -> usize { @@ -133,7 +130,7 @@ where } fn region_names(&self) -> Vec { - self.source.region_names() + Vec::new() } } @@ -155,7 +152,7 @@ where type T = C::Row; } -impl TypedVec for ColumnarVecColumn +impl TypedVec for LazyColumnVec where C: ColumnId, S: ReadableColumnarVec, diff --git a/crates/vecdb/src/variants/eager/compute/statistics.rs b/crates/vecdb/src/variants/eager/compute/statistics.rs index f8634f189..1d565a813 100644 --- a/crates/vecdb/src/variants/eager/compute/statistics.rs +++ b/crates/vecdb/src/variants/eager/compute/statistics.rs @@ -8,6 +8,8 @@ use crate::{ AnyVec, CheckedSub, Error, Exit, ReadableVec, Result, StoredVec, VecIndex, VecValue, Version, WritableVec, }; +#[cfg(feature = "pco")] +use crate::{ColumnId, ColumnarVec, PcoVec, PcoVecValue}; use super::super::EagerVec; @@ -1174,3 +1176,205 @@ where ) } } + +#[cfg(feature = "pco")] +impl EagerVec, C>> +where + I: VecIndex, + T: PcoVecValue, + C: ColumnId, +{ + /// Computes one rolling EMA per column from a shared value source. + pub fn compute_rolling_ema_columns<'a, A, W>( + &mut self, + max_from: I, + window_starts: impl Fn(C) -> &'a W, + values: &impl ReadableVec, + exit: &Exit, + ) -> Result<()> + where + A: VecValue, + W: ReadableVec + 'a, + f64: From + From, + T: From + Default, + { + let window_starts: Vec<_> = C::ALL.iter().map(|&column| window_starts(column)).collect(); + let dependency_version = Version::new(2) + + values.version() + + window_starts + .iter() + .map(|window_starts| window_starts.version()) + .sum(); + let source_len = window_starts + .iter() + .map(|window_starts| window_starts.len()) + .chain(std::iter::once(values.len())) + .min() + .unwrap_or_default(); + + self.compute_init(dependency_version, max_from, exit, |this| { + let start = this.len(); + let end = this.batch_end(source_len); + if start >= end { + return Ok(()); + } + + let mut previous = if start > 0 { + C::map(this.collect_one_at(start - 1).unwrap(), f64::from) + } else { + C::from_fn(|_| 0.0_f64) + }; + let window_start_batches: Vec<_> = window_starts + .iter() + .map(|window_starts| window_starts.collect_range_at(start, end)) + .collect(); + let values = values.collect_range_at(start, end); + + for (offset, value) in values.into_iter().enumerate() { + let index = start + offset; + let value = f64::from(value); + this.push(C::from_fn(|column| { + let window_start = window_start_batches[column.index()][offset].to_usize(); + let span = (index - window_start + 1) as f64; + let alpha = 2.0 / (span + 1.0); + let previous = column.get_mut(&mut previous); + *previous = alpha * value + (1.0 - alpha) * *previous; + T::from(*previous) + })); + } + + Ok(()) + }) + } +} + +#[cfg(all(test, feature = "pco"))] +mod columnar_tests { + use crate::{ + AnyStoredVec, BytesVec, ColumnId, ColumnarVec, Database, EagerVec, Exit, ImportableVec, + PcoVec, ReadableVec, VecValue, Version, WritableVec, + }; + + use super::*; + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + enum Column { + Short, + Long, + } + + impl ColumnId for Column { + type Row + = [T; 2] + where + T: VecValue; + + const VERSION: Version = Version::ONE; + const ALL: &'static [Self] = &[Self::Short, Self::Long]; + + fn index(self) -> usize { + self as usize + } + + fn get(self, row: &Self::Row) -> &T { + &row[self.index()] + } + + fn get_mut(self, row: &mut Self::Row) -> &mut T { + &mut row[self.index()] + } + + fn from_fn(mut create: F) -> Self::Row + where + T: VecValue, + F: FnMut(Self) -> T, + { + [create(Self::Short), create(Self::Long)] + } + + fn map(row: Self::Row, mut create: F) -> Self::Row + where + T: VecValue, + U: VecValue, + F: FnMut(T) -> U, + { + let [short, long] = row; + [create(short), create(long)] + } + } + + #[test] + fn columnar_ema_matches_individual_emas() { + let suffix = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!( + "vecdb-columnar-ema-{}-{suffix}", + std::process::id() + )); + let db = Database::open(&path).unwrap(); + + let mut values: EagerVec> = + EagerVec::forced_import(&db, "values", Version::ONE).unwrap(); + let mut short_starts: EagerVec> = + EagerVec::forced_import(&db, "short_starts", Version::ONE).unwrap(); + let mut long_starts: EagerVec> = + EagerVec::forced_import(&db, "long_starts", Version::ONE).unwrap(); + + for value in [100.0, 110.0, 90.0, 120.0, 80.0, 130.0] { + values.push(value); + } + for start in [0, 0, 1, 2, 3, 4] { + short_starts.push(start); + } + for start in [0, 0, 0, 0, 0, 0] { + long_starts.push(start); + } + values.write().unwrap(); + short_starts.write().unwrap(); + long_starts.write().unwrap(); + + let exit = Exit::new(); + let mut short: EagerVec> = + EagerVec::forced_import(&db, "short", Version::ONE).unwrap(); + let mut long: EagerVec> = + EagerVec::forced_import(&db, "long", Version::ONE).unwrap(); + short + .compute_rolling_ema(0, &short_starts, &values, &exit) + .unwrap(); + long.compute_rolling_ema(0, &long_starts, &values, &exit) + .unwrap(); + + let mut columnar: EagerVec, Column>> = + EagerVec::forced_import(&db, "columnar", Version::ONE).unwrap(); + columnar + .compute_rolling_ema_columns( + 0, + |column| match column { + Column::Short => &short_starts, + Column::Long => &long_starts, + }, + &values, + &exit, + ) + .unwrap(); + + let expected: Vec<_> = short + .collect_range_at(0, values.len()) + .into_iter() + .zip(long.collect_range_at(0, values.len())) + .map(|(short, long)| [short, long]) + .collect(); + assert_eq!(columnar.collect_range_at(0, values.len()), expected); + + drop(columnar); + drop(long); + drop(short); + drop(long_starts); + drop(short_starts); + drop(values); + drop(db); + std::fs::remove_dir_all(path).unwrap(); + } +} diff --git a/crates/vecdb/src/variants/eager/compute/transforms.rs b/crates/vecdb/src/variants/eager/compute/transforms.rs index ab480f2f2..219c81c19 100644 --- a/crates/vecdb/src/variants/eager/compute/transforms.rs +++ b/crates/vecdb/src/variants/eager/compute/transforms.rs @@ -1,6 +1,8 @@ +use std::ops::Range; + use crate::{ - AnyVec, BinaryTransform, Cursor, Exit, ReadableVec, Result, StoredVec, VecIndex, VecValue, - Version, WritableVec, + AnyStoredVec, AnyVec, BinaryTransform, Cursor, Error, Exit, ReadableVec, Result, StoredVec, + VecIndex, VecValue, Version, WritableVec, }; use super::super::EagerVec; @@ -9,6 +11,51 @@ impl EagerVec where V: StoredVec, { + /// Computes a fixed output range from source data prepared in bounded batches. + /// The callback must append exactly one value for every index in its range. + pub fn compute_batched_to( + &mut self, + max_from: V::I, + to: usize, + version: Version, + batch_size: usize, + mut compute: F, + exit: &Exit, + ) -> Result<()> + where + F: FnMut(&mut Self, Range) -> Result<()>, + { + if batch_size == 0 { + return Err(Error::InvalidArgument( + "EagerVec batch size must be greater than zero", + )); + } + + self.validate_computed_version_or_reset(version)?; + self.truncate_if_needed(max_from)?; + + while self.len() < to { + let from = self.len(); + let end = from.saturating_add(batch_size).min(to); + compute(self, from..end)?; + if self.len() != end { + return Err(Error::InvalidArgument( + "EagerVec batch callback must append one value per index", + )); + } + + let _lock = exit.lock(); + self.write()?; + } + + if self.is_dirty() { + let _lock = exit.lock(); + self.write()?; + } + + Ok(()) + } + pub fn compute_to( &mut self, max_from: V::I, @@ -102,6 +149,24 @@ where max_from: V::I, other1: &impl ReadableVec, other2: &impl ReadableVec, + t: F, + exit: &Exit, + ) -> Result<()> + where + A: VecValue, + B: VecValue, + F: FnMut((V::I, A, B, &Self)) -> (V::I, V::T), + { + let batch_size = self.batch_capacity(); + self.compute_transform2_batched(max_from, other1, other2, batch_size, t, exit) + } + + pub fn compute_transform2_batched( + &mut self, + max_from: V::I, + other1: &impl ReadableVec, + other2: &impl ReadableVec, + batch_size: usize, mut t: F, exit: &Exit, ) -> Result<()> @@ -110,29 +175,25 @@ where B: VecValue, F: FnMut((V::I, A, B, &Self)) -> (V::I, V::T), { - self.compute_init( - other1.version() + other2.version(), + let source_end = other1.len().min(other2.len()); + self.compute_batched_to( max_from, - exit, - |this| { - let skip = this.len(); - let source_end = other1.len().min(other2.len()); - let end = this.batch_end(source_end); - if skip >= end { - return Ok(()); - } - - let batch2 = other2.collect_range_at(skip, end); + source_end, + other1.version() + other2.version(), + batch_size, + |this, range| { + let batch2 = other2.collect_range_at(range.start, range.end); let mut iter2 = batch2.into_iter(); - let mut i = skip; + let mut i = range.start; - other1.try_fold_range_at(skip, end, (), |(), b: A| { + other1.try_fold_range_at(range.start, range.end, (), |(), b: A| { let (idx, v) = t((V::I::from(i), b, iter2.next().unwrap(), &*this)); i += 1; this.debug_checked_push(idx, v); Ok(()) }) }, + exit, ) } diff --git a/crates/vecdb/src/variants/eager/mod.rs b/crates/vecdb/src/variants/eager/mod.rs index e5134851e..9d7e838ed 100644 --- a/crates/vecdb/src/variants/eager/mod.rs +++ b/crates/vecdb/src/variants/eager/mod.rs @@ -54,9 +54,15 @@ where /// Ensures `pushed_len * SIZE_OF_T >= MAX_CACHE_SIZE` so `batch_limit_reached()` fires. #[inline] pub fn batch_end(&self, max_end: usize) -> usize { + self.len() + .saturating_add(self.batch_capacity()) + .min(max_end) + } + + #[inline] + fn batch_capacity(&self) -> usize { let size = size_of::().max(1); - let cap = MAX_CACHE_SIZE.div_ceil(size); - (self.len() + cap).min(max_end) + MAX_CACHE_SIZE.div_ceil(size) } /// Helper that repeatedly calls a compute function until it completes. diff --git a/crates/vecdb/tests/columnar.rs b/crates/vecdb/tests/columnar.rs index 1809c2833..3507cb97e 100644 --- a/crates/vecdb/tests/columnar.rs +++ b/crates/vecdb/tests/columnar.rs @@ -7,9 +7,10 @@ use std::{ use tempfile::tempdir; use vecdb::{ - AnyStoredVec, AnyVec, BytesVec, ColumnId, ColumnarSumVec, ColumnarVec, Database, ImportOptions, - ImportableVec, LazyColumnarVec, PrintableIndex, ReadableColumnarVec, ReadableVec, Result, - Stamp, StoredVec, UnaryTransform, VecIndex, VecValue, Version, WritableVec, + AnyStoredVec, AnyVec, BytesVec, ColumnId, ColumnarVec, Database, EagerVec, Exit, ImportOptions, + ImportableVec, LazyColumnSumVec, LazyColumnarVec, PrintableIndex, ReadableColumnarVec, + ReadableVec, Result, Stamp, StoredVec, UnaryTransform, VecIndex, VecValue, Version, + WritableVec, }; const COLUMNS: usize = 3; @@ -145,7 +146,8 @@ fn bytes_columnar_roundtrip_and_projection() -> Result<()> { vec.write()?; assert_eq!(vec.region_names().len(), 1); - let second = vec.column(TestColumn::Second); + let second = vec.column("second", Version::ONE, TestColumn::Second); + assert!(second.region_names().is_empty()); assert_eq!(second.collect_one_at(2_345), Some(row(2_345)[1])); assert_eq!(second.collect_range_at(4_990, 5_000).len(), 10); drop(second); @@ -156,7 +158,8 @@ fn bytes_columnar_roundtrip_and_projection() -> Result<()> { assert_eq!(vec.collect_one_at(0), Some(row(0))); assert_eq!(vec.collect_one_at(4_999), Some(row(4_999))); assert_eq!( - vec.column(TestColumn::Third).collect_one_at(4_321), + vec.column("third", Version::ONE, TestColumn::Third) + .collect_one_at(4_321), Some(row(4_321)[2]) ); @@ -174,6 +177,66 @@ fn bytes_columnar_roundtrip_and_projection() -> Result<()> { Ok(()) } +#[test] +fn eager_columnar_collect_last_tracks_persisted_and_pending_rows() -> Result<()> { + type V = EagerVec, TestColumn>>; + + let temp = tempdir()?; + let db = Database::open(temp.path())?; + let mut vec = V::forced_import(&db, "collect_last", Version::ONE)?; + + vec.push(row(1)); + vec.write()?; + assert_eq!(vec.collect_last(), Some(row(1))); + + vec.push(row(2)); + assert_eq!(vec.collect_last(), Some(row(2))); + + Ok(()) +} + +#[test] +fn eager_columnar_computes_and_persists_rows() -> Result<()> { + type Source = BytesVec; + type Target = EagerVec, TestColumn>>; + + let temp = tempdir()?; + let db = Database::open(temp.path())?; + let mut source1 = Source::forced_import(&db, "eager_columnar_source_1", Version::ONE)?; + let mut source2 = Source::forced_import(&db, "eager_columnar_source_2", Version::ONE)?; + for value in 0..5_000_u64 { + source1.push(value); + source2.push(value + 10_000); + } + source1.write()?; + source2.write()?; + + let mut target = Target::forced_import(&db, "eager_columnar_target", Version::ONE)?; + target.compute_transform2_batched( + 0, + &source1, + &source2, + 777, + |(index, value1, value2, ..)| (index, [value1, value2, value1 + value2]), + &Exit::new(), + )?; + + assert_eq!(target.len(), 5_000); + assert_eq!(target.collect_one_at(4_321), Some([4_321, 14_321, 18_642])); + assert_eq!( + target + .read_only_clone() + .column("eager_columnar_second", Version::ONE, TestColumn::Second) + .collect_one_at(4_321), + Some(14_321) + ); + drop(target); + + let target = Target::import(&db, "eager_columnar_target", Version::ONE)?; + assert_eq!(target.collect_one_at(4_999), Some([4_999, 14_999, 19_998])); + Ok(()) +} + #[test] fn projection_is_isolated_from_pushed_rows_until_write() -> Result<()> { type V = ColumnarVec, TestColumn>; @@ -185,7 +248,7 @@ fn projection_is_isolated_from_pushed_rows_until_write() -> Result<()> { vec.push(row(index)); } vec.write()?; - let projection = vec.column(TestColumn::Second); + let projection = vec.column("second", Version::ONE, TestColumn::Second); let sum = vec.sum_columns( "projection_isolation_sum", Version::ONE, @@ -230,7 +293,8 @@ fn lazy_columnar_transform_preserves_rows_and_columns() -> Result<()> { Some(row(index).map(|value| value * 2)) ); assert_eq!( - lazy.column(TestColumn::Second).collect_one_at(index), + lazy.column("second", Version::ONE, TestColumn::Second) + .collect_one_at(index), Some(row(index)[1] * 2) ); } @@ -244,7 +308,7 @@ fn lazy_columnar_transform_preserves_rows_and_columns() -> Result<()> { .collect::>() ); assert_eq!( - lazy.column(TestColumn::Second) + lazy.column("second", Version::ONE, TestColumn::Second) .fold_range_at(from, to, 0, u64::wrapping_add), (from..to) .map(|index| row(index)[1] * 2) @@ -271,23 +335,30 @@ fn columnar_sum_accepts_stored_and_lazy_sources() -> Result<()> { [TestColumn::Third, TestColumn::First], ); let source = vec.read_only_clone(); - let reordered_sum = ColumnarSumVec::new( + let reordered_sum = LazyColumnSumVec::new( "reordered_sum", Version::ONE, source.clone(), [TestColumn::First, TestColumn::Third], ); assert_eq!(stored_sum.version(), reordered_sum.version()); - let different_sum = ColumnarSumVec::new( - "different_sum", + let same_length_sum = LazyColumnSumVec::new( + "same_length_sum", Version::ONE, source.clone(), [TestColumn::First, TestColumn::Second], ); - assert_ne!(stored_sum.version(), different_sum.version()); + assert_eq!(stored_sum.version(), same_length_sum.version()); + let shorter_sum = LazyColumnSumVec::new( + "shorter_sum", + Version::ONE, + source.clone(), + [TestColumn::First], + ); + assert_ne!(stored_sum.version(), shorter_sum.version()); assert!( - catch_unwind(AssertUnwindSafe(|| ColumnarSumVec::new( + catch_unwind(AssertUnwindSafe(|| LazyColumnSumVec::new( "empty_sum", Version::ONE, source.clone(), @@ -296,7 +367,7 @@ fn columnar_sum_accepts_stored_and_lazy_sources() -> Result<()> { .is_err() ); assert!( - catch_unwind(AssertUnwindSafe(|| ColumnarSumVec::new( + catch_unwind(AssertUnwindSafe(|| LazyColumnSumVec::new( "duplicate_sum", Version::ONE, source.clone(), @@ -419,15 +490,12 @@ fn projected_try_fold_stops_at_the_first_error() -> Result<()> { vec.write()?; let mut seen = 0; - let result = vec.column(TestColumn::First).try_fold_range_at( - 0, - 5_000, - (), - |(), _| -> std::result::Result<(), ()> { + let result = vec + .column("first", Version::ONE, TestColumn::First) + .try_fold_range_at(0, 5_000, (), |(), _| -> std::result::Result<(), ()> { seen += 1; if seen == 17 { Err(()) } else { Ok(()) } - }, - ); + }); assert_eq!(result, Err(())); assert_eq!(seen, 17); Ok(()) @@ -506,7 +574,7 @@ fn pco_columnar_roundtrip_reads_only_selected_stream() -> Result<()> { let vec = V::import(&db, "pco_matrix", Version::ONE)?; assert_eq!(vec.collect_one_at(9_999), Some(row(9_999))); assert_eq!( - vec.column(TestColumn::Second) + vec.column("second", Version::ONE, TestColumn::Second) .collect_range_at(9_990, 10_000), (9_990..10_000) .map(|index| row(index)[1]) @@ -517,7 +585,7 @@ fn pco_columnar_roundtrip_reads_only_selected_stream() -> Result<()> { #[cfg(feature = "pco")] #[test] -fn pco_repeated_small_writes_keep_partial_pages_raw() -> Result<()> { +fn pco_repeated_small_writes_compress_completed_pages_and_keep_tail_raw() -> Result<()> { use vecdb::PcoVec; type V = ColumnarVec, TestColumn>; @@ -539,6 +607,8 @@ fn pco_repeated_small_writes_keep_partial_pages_raw() -> Result<()> { .chunks_exact(16) .map(|bytes| u32::from_le_bytes(bytes[12..16].try_into().unwrap())) .collect::>(); + // Completed column pages are compressed; only the final physical tail page + // is required to remain raw while the logical row block is incomplete. let completed_pages = vec.len() / U64S_PER_PAGE * COLUMNS; for &values in &pages[..completed_pages] { assert_eq!(values & (1 << 31), 0); @@ -559,7 +629,7 @@ fn concurrent_projection_reads_survive_incremental_writes() -> Result<()> { vec.push(row(index)); } vec.write()?; - let projection = Arc::new(vec.column(TestColumn::Third)); + let projection = Arc::new(vec.column("third", Version::ONE, TestColumn::Third)); let readers = (0..4) .map(|_| { let projection = Arc::clone(&projection); @@ -622,7 +692,8 @@ where let vec = ColumnarVec::::import(&db, "backend", Version::ONE)?; assert_eq!(vec.collect_one_at(4_199), Some(row(4_199))); assert_eq!( - vec.column(TestColumn::First).collect_one_at(3_123), + vec.column("first", Version::ONE, TestColumn::First) + .collect_one_at(3_123), Some(row(3_123)[0]) ); Ok(()) diff --git a/crates/vecdb/tests/initial_capacity.rs b/crates/vecdb/tests/initial_capacity.rs index 36be44d33..5d0dcb8ae 100644 --- a/crates/vecdb/tests/initial_capacity.rs +++ b/crates/vecdb/tests/initial_capacity.rs @@ -71,11 +71,8 @@ fn compressed_vec_uses_index_initial_capacity() -> vecdb::Result<()> { let temp = TempDir::new()?; let db = Database::open(temp.path())?; - let vec = vecdb::PcoVec::::forced_import( - &db, - "compressed_values", - Version::ONE, - )?; + let vec = + vecdb::PcoVec::::forced_import(&db, "compressed_values", Version::ONE)?; let expected = (HEADER_OFFSET + 10_000 * size_of::()).next_multiple_of(PAGE_SIZE); assert_eq!(vec.region().meta().len(), HEADER_OFFSET); assert_eq!(vec.region().meta().reserved(), expected); diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index f101a9403..7d2868352 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -335,6 +335,7 @@ prior template's transactions or a full transaction body. * @property {Timestamp} timestamp - Unix timestamp at the window midpoint * @property {Weight} avgWeight - Rolling 24h median block weight (weight units) */ +/** @typedef {number} Bytes */ /** * Investor phase from the Capital Sentiment model. * @@ -1486,6 +1487,32 @@ on serialization otherwise. */ /** @typedef {number} Year1 */ /** @typedef {number} Year10 */ +/** @typedef {Bitcoin[]} [Bitcoin; 3] */ +/** @typedef {Bytes[]} [Bytes; 23] */ +/** @typedef {Bytes[]} [Bytes; 4] */ +/** @typedef {Cents[]} [Cents; 16] */ +/** @typedef {Cents[]} [Cents; 19] */ +/** @typedef {Cents[]} [Cents; 23] */ +/** @typedef {Dollars[]} [Dollars; 3] */ +/** @typedef {PartsPerMillion32[]} [PartsPerMillion32; 19] */ +/** @typedef {PartsPerMillion32[]} [PartsPerMillion32; 4] */ +/** @typedef {Sats[]} [Sats; 23] */ +/** @typedef {Sats[]} [Sats; 4] */ +/** @typedef {Sats[]} [Sats; 8] */ +/** @typedef {StoredF32[]} [StoredF32; 3] */ +/** @typedef {StoredF32[]} [StoredF32; 4] */ +/** @typedef {StoredF64[]} [StoredF64; 23] */ +/** @typedef {StoredF64[]} [StoredF64; 3] */ +/** @typedef {StoredF64[]} [StoredF64; 4] */ +/** @typedef {StoredU16[]} [StoredU16; 11] */ +/** @typedef {StoredU16[]} [StoredU16; 12] */ +/** @typedef {StoredU64[]} [StoredU64; 11] */ +/** @typedef {StoredU64[]} [StoredU64; 12] */ +/** @typedef {StoredU64[]} [StoredU64; 23] */ +/** @typedef {StoredU64[]} [StoredU64; 4] */ +/** @typedef {StoredU64[]} [StoredU64; 8] */ +/** @typedef {VSize[]} [VSize; 23] */ +/** @typedef {VSize[]} [VSize; 4] */ /** * @typedef {Object} BrkClientOptions @@ -2506,7 +2533,120 @@ function createSeriesPattern35(client, name) { return /** @type {SeriesPattern35 // Reusable structural pattern factories /** - * @typedef {Object} IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern + * @typedef {Object} _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mCumulativeOverUnderPattern + * @property {AverageBlockCumulativeSumPattern} _10yTo12y + * @property {AverageBlockCumulativeSumPattern} _12yTo15y + * @property {AverageBlockCumulativeSumPattern} _18mTo2y + * @property {AverageBlockCumulativeSumPattern} _1dTo1w + * @property {AverageBlockCumulativeSumPattern} _1hTo1d + * @property {AverageBlockCumulativeSumPattern} _1mTo2m + * @property {AverageBlockCumulativeSumPattern} _1wTo1m + * @property {AverageBlockCumulativeSumPattern} _1yTo18m + * @property {AverageBlockCumulativeSumPattern} _2mTo3m + * @property {AverageBlockCumulativeSumPattern} _2yTo3y + * @property {AverageBlockCumulativeSumPattern} _3mTo4m + * @property {AverageBlockCumulativeSumPattern} _3yTo4y + * @property {AverageBlockCumulativeSumPattern} _4mTo5m + * @property {AverageBlockCumulativeSumPattern} _4yTo5y + * @property {AverageBlockCumulativeSumPattern} _5mTo6m + * @property {AverageBlockCumulativeSumPattern} _5yTo6y + * @property {AverageBlockCumulativeSumPattern} _6mTo9m + * @property {AverageBlockCumulativeSumPattern} _6yTo7y + * @property {AverageBlockCumulativeSumPattern} _7yTo8y + * @property {AverageBlockCumulativeSumPattern} _8yTo10y + * @property {AverageBlockCumulativeSumPattern} _9mTo1y + * @property {SeriesPattern18<[StoredF64; 23]>} cumulative + * @property {AverageBlockCumulativeSumPattern} over15y + * @property {AverageBlockCumulativeSumPattern} under1h + */ + +/** + * @typedef {Object} _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mHeightOverUnderPattern2 + * @property {BtcCentsSatsUsdPattern} _10yTo12y + * @property {BtcCentsSatsUsdPattern} _12yTo15y + * @property {BtcCentsSatsUsdPattern} _18mTo2y + * @property {BtcCentsSatsUsdPattern} _1dTo1w + * @property {BtcCentsSatsUsdPattern} _1hTo1d + * @property {BtcCentsSatsUsdPattern} _1mTo2m + * @property {BtcCentsSatsUsdPattern} _1wTo1m + * @property {BtcCentsSatsUsdPattern} _1yTo18m + * @property {BtcCentsSatsUsdPattern} _2mTo3m + * @property {BtcCentsSatsUsdPattern} _2yTo3y + * @property {BtcCentsSatsUsdPattern} _3mTo4m + * @property {BtcCentsSatsUsdPattern} _3yTo4y + * @property {BtcCentsSatsUsdPattern} _4mTo5m + * @property {BtcCentsSatsUsdPattern} _4yTo5y + * @property {BtcCentsSatsUsdPattern} _5mTo6m + * @property {BtcCentsSatsUsdPattern} _5yTo6y + * @property {BtcCentsSatsUsdPattern} _6mTo9m + * @property {BtcCentsSatsUsdPattern} _6yTo7y + * @property {BtcCentsSatsUsdPattern} _7yTo8y + * @property {BtcCentsSatsUsdPattern} _8yTo10y + * @property {BtcCentsSatsUsdPattern} _9mTo1y + * @property {SeriesPattern18<[Sats; 23]>} height + * @property {BtcCentsSatsUsdPattern} over15y + * @property {BtcCentsSatsUsdPattern} under1h + */ + +/** + * @template T + * @typedef {Object} AscribeBareBitproofBlockstackCoinColuCumulativeDocproofEmptyEpobcEternityFactomKomodoMemoOmniOpenPoetRunesStacksStamperyTextUnknownVeriPattern3 + * @property {AverageBlockCumulativeSumPattern} ascribe + * @property {AverageBlockCumulativeSumPattern} bareHash + * @property {AverageBlockCumulativeSumPattern} bitproof + * @property {AverageBlockCumulativeSumPattern} blockstack + * @property {AverageBlockCumulativeSumPattern} coinSpark + * @property {AverageBlockCumulativeSumPattern} colu + * @property {SeriesPattern18} cumulative + * @property {AverageBlockCumulativeSumPattern} docproof + * @property {AverageBlockCumulativeSumPattern} empty + * @property {AverageBlockCumulativeSumPattern} epobc + * @property {AverageBlockCumulativeSumPattern} eternityWall + * @property {AverageBlockCumulativeSumPattern} factom + * @property {AverageBlockCumulativeSumPattern} komodo + * @property {AverageBlockCumulativeSumPattern} memo + * @property {AverageBlockCumulativeSumPattern} omni + * @property {AverageBlockCumulativeSumPattern} openAssets + * @property {AverageBlockCumulativeSumPattern} openTimestamps + * @property {AverageBlockCumulativeSumPattern} poet + * @property {AverageBlockCumulativeSumPattern} runes + * @property {AverageBlockCumulativeSumPattern} stacks + * @property {AverageBlockCumulativeSumPattern} stampery + * @property {AverageBlockCumulativeSumPattern} text + * @property {AverageBlockCumulativeSumPattern} unknown + * @property {AverageBlockCumulativeSumPattern} veriBlock + */ + +/** + * @typedef {Object} _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mOverUnderPattern3 + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} over15y + * @property {SeriesPattern1} under1h + */ + +/** + * @typedef {Object} HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern + * @property {SeriesPattern18<[Cents; 19]>} height * @property {SeriesPattern1} index * @property {CentsSatsUsdPattern} pct01 * @property {CentsSatsUsdPattern} pct05 @@ -2531,13 +2671,14 @@ function createSeriesPattern35(client, name) { return /** @type {SeriesPattern35 */ /** - * Create a IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern pattern node + * Create a HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern pattern node * @param {BrkClient} client * @param {string} acc - Accumulated series name - * @returns {IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} + * @returns {HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} */ -function createIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, acc) { +function createHeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, acc) { return { + height: createSeriesPattern18(client, _m(acc, 'percentiles_cents')), index: createSeriesPattern1(client, _m(acc, 'index')), pct01: createCentsSatsUsdPattern(client, _m(acc, 'pct0_1')), pct05: createCentsSatsUsdPattern(client, _m(acc, 'pct0_5')), @@ -2563,7 +2704,8 @@ function createIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct9 } /** - * @typedef {Object} Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern + * @typedef {Object} HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern + * @property {SeriesPattern18<[Cents; 19]>} height * @property {CentsSatsUsdPattern} pct05 * @property {CentsSatsUsdPattern} pct10 * @property {CentsSatsUsdPattern} pct15 @@ -2586,13 +2728,14 @@ function createIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct9 */ /** - * Create a Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern pattern node + * Create a HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern pattern node * @param {BrkClient} client * @param {string} acc - Accumulated series name - * @returns {Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} + * @returns {HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} */ -function createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, acc) { +function createHeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, acc) { return { + height: createSeriesPattern18(client, _m(acc, 'cents')), pct05: createCentsSatsUsdPattern(client, _m(acc, 'pct05')), pct10: createCentsSatsUsdPattern(client, _m(acc, 'pct10')), pct15: createCentsSatsUsdPattern(client, _m(acc, 'pct15')), @@ -2616,7 +2759,7 @@ function createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65 } /** - * @typedef {Object} Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern + * @typedef {Object} Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern * @property {PpmPriceRatioPattern} pct01 * @property {PpmPriceRatioPattern} pct05 * @property {PpmPriceRatioPattern} pct1 @@ -2636,15 +2779,16 @@ function createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65 * @property {PpmPriceRatioPattern} pct99 * @property {PpmPriceRatioPattern} pct995 * @property {PpmPriceRatioPattern} pct999 + * @property {SeriesPattern18<[PartsPerMillion32; 19]>} ratios */ /** - * Create a Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern pattern node + * Create a Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern pattern node * @param {BrkClient} client * @param {string} acc - Accumulated series name - * @returns {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} + * @returns {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} */ -function createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, acc) { +function createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, acc) { return { pct01: createPpmPriceRatioPattern(client, acc, 'pct0_1'), pct05: createPpmPriceRatioPattern(client, acc, 'pct0_5'), @@ -2665,47 +2809,7 @@ function createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct9 pct99: createPpmPriceRatioPattern(client, acc, 'pct99'), pct995: createPpmPriceRatioPattern(client, acc, 'pct99_5'), pct999: createPpmPriceRatioPattern(client, acc, 'pct99_9'), - }; -} - -/** - * @typedef {Object} AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern - * @property {AverageBlockCumulativeSumPattern} all - * @property {AverageBlockCumulativeSumPattern} empty - * @property {AverageBlockCumulativeSumPattern} opReturn - * @property {AverageBlockCumulativeSumPattern} p2a - * @property {AverageBlockCumulativeSumPattern} p2ms - * @property {AverageBlockCumulativeSumPattern} p2pk33 - * @property {AverageBlockCumulativeSumPattern} p2pk65 - * @property {AverageBlockCumulativeSumPattern} p2pkh - * @property {AverageBlockCumulativeSumPattern} p2sh - * @property {AverageBlockCumulativeSumPattern} p2tr - * @property {AverageBlockCumulativeSumPattern} p2wpkh - * @property {AverageBlockCumulativeSumPattern} p2wsh - * @property {AverageBlockCumulativeSumPattern} unknown - */ - -/** - * Create a AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern} - */ -function createAllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern(client, acc) { - return { - all: createAverageBlockCumulativeSumPattern(client, _m(acc, 'bis')), - empty: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_empty_outputs_output')), - opReturn: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_op_return_output')), - p2a: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2a_output')), - p2ms: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2ms_output')), - p2pk33: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2pk33_output')), - p2pk65: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2pk65_output')), - p2pkh: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2pkh_output')), - p2sh: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2sh_output')), - p2tr: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2tr_output')), - p2wpkh: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2wpkh_output')), - p2wsh: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2wsh_output')), - unknown: createAverageBlockCumulativeSumPattern(client, _m(acc, 'with_unknown_outputs_output')), + ratios: createSeriesPattern18(client, _m(acc, 'ratios_ppm')), }; } @@ -2787,22 +2891,6 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, acc) { }; } -/** - * @typedef {Object} AllEmptyP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern - * @property {AverageBlockCumulativeSumPattern} all - * @property {AverageBlockCumulativeSumPattern} empty - * @property {AverageBlockCumulativeSumPattern} p2a - * @property {AverageBlockCumulativeSumPattern} p2ms - * @property {AverageBlockCumulativeSumPattern} p2pk33 - * @property {AverageBlockCumulativeSumPattern} p2pk65 - * @property {AverageBlockCumulativeSumPattern} p2pkh - * @property {AverageBlockCumulativeSumPattern} p2sh - * @property {AverageBlockCumulativeSumPattern} p2tr - * @property {AverageBlockCumulativeSumPattern} p2wpkh - * @property {AverageBlockCumulativeSumPattern} p2wsh - * @property {AverageBlockCumulativeSumPattern} unknown - */ - /** * @typedef {Object} CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern * @property {CentsDeltaToUsdPattern} cap @@ -2814,8 +2902,8 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, acc) { * @property {BlockCumulativeSumPattern} peakRegret * @property {CentsPpmRatioSatsUsdPattern} price * @property {BlockCumulativeSumPattern} profit - * @property {_1m1w1y24hPattern} profitToLossRatio - * @property {_1m1w1y24hPattern8} sellSideRiskRatio + * @property {_1m1w1y24hHeightPattern2} profitToLossRatio + * @property {_1m1w1y24hHeightPattern3} sellSideRiskRatio * @property {AdjustedRatioValuePattern} sopr */ @@ -2836,8 +2924,8 @@ function createCapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern(clie peakRegret: createBlockCumulativeSumPattern(client, _m(acc, 'realized_peak_regret')), price: createCentsPpmRatioSatsUsdPattern(client, _m(acc, 'realized_price')), profit: createBlockCumulativeSumPattern(client, _m(acc, 'realized_profit')), - profitToLossRatio: create_1m1w1y24hPattern(client, _m(acc, 'realized_profit_to_loss_ratio')), - sellSideRiskRatio: create_1m1w1y24hPattern8(client, _m(acc, 'sell_side_risk_ratio')), + profitToLossRatio: create_1m1w1y24hHeightPattern2(client, _m(acc, 'realized_profit_to_loss_ratio')), + sellSideRiskRatio: create_1m1w1y24hHeightPattern3(client, _m(acc, 'sell_side_risk_ratio')), sopr: createAdjustedRatioValuePattern(client, acc), }; } @@ -2881,6 +2969,21 @@ function createEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2 }; } +/** + * @typedef {Object} AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern + * @property {BtcCentsSatsUsdPattern} all + * @property {SeriesPattern18<[Sats; 8]>} height + * @property {BtcCentsSatsUsdPattern} p2a + * @property {BtcCentsSatsUsdPattern} p2pk33 + * @property {BtcCentsSatsUsdPattern} p2pk65 + * @property {BtcCentsSatsUsdPattern} p2pkh + * @property {BtcCentsSatsUsdPattern} p2sh + * @property {BtcCentsSatsUsdPattern} p2tr + * @property {BtcCentsSatsUsdPattern} p2wpkh + * @property {BtcCentsSatsUsdPattern} p2wsh + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} share + */ + /** * @typedef {Object} AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern * @property {_1m1w1y24hPattern} average @@ -2995,22 +3098,9 @@ function createAverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern(c } /** - * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern - * @property {BtcCentsSatsUsdPattern} all - * @property {BtcCentsSatsUsdPattern} p2a - * @property {BtcCentsSatsUsdPattern} p2pk33 - * @property {BtcCentsSatsUsdPattern} p2pk65 - * @property {BtcCentsSatsUsdPattern} p2pkh - * @property {BtcCentsSatsUsdPattern} p2sh - * @property {BtcCentsSatsUsdPattern} p2tr - * @property {BtcCentsSatsUsdPattern} p2wpkh - * @property {BtcCentsSatsUsdPattern} p2wsh - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5} share - */ - -/** - * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 + * @typedef {Object} AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern * @property {AverageBlockCumulativeSumPattern} all + * @property {SeriesPattern18<[StoredU64; 8]>} cumulative * @property {AverageBlockCumulativeSumPattern} p2a * @property {AverageBlockCumulativeSumPattern} p2pk33 * @property {AverageBlockCumulativeSumPattern} p2pk65 @@ -3022,27 +3112,21 @@ function createAverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern(c */ /** - * Create a AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} + * @typedef {Object} AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern + * @property {SeriesPattern1} all + * @property {SeriesPattern18<[StoredU64; 8]>} height + * @property {SeriesPattern1} p2a + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh */ -function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, acc) { - return { - all: createAverageBlockCumulativeSumPattern(client, acc), - p2a: createAverageBlockCumulativeSumPattern(client, _p('p2a', acc)), - p2pk33: createAverageBlockCumulativeSumPattern(client, _p('p2pk33', acc)), - p2pk65: createAverageBlockCumulativeSumPattern(client, _p('p2pk65', acc)), - p2pkh: createAverageBlockCumulativeSumPattern(client, _p('p2pkh', acc)), - p2sh: createAverageBlockCumulativeSumPattern(client, _p('p2sh', acc)), - p2tr: createAverageBlockCumulativeSumPattern(client, _p('p2tr', acc)), - p2wpkh: createAverageBlockCumulativeSumPattern(client, _p('p2wpkh', acc)), - p2wsh: createAverageBlockCumulativeSumPattern(client, _p('p2wsh', acc)), - }; -} /** - * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 + * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 * @property {PercentPpmRatioPattern2} all * @property {PercentPpmRatioPattern2} p2a * @property {PercentPpmRatioPattern2} p2pk33 @@ -3055,12 +3139,12 @@ function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, acc) { */ /** - * Create a AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 pattern node + * Create a AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 pattern node * @param {BrkClient} client * @param {string} acc - Accumulated series name - * @returns {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5} + * @returns {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} */ -function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(client, acc) { +function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, acc) { return { all: createPercentPpmRatioPattern2(client, acc), p2a: createPercentPpmRatioPattern2(client, _p('p2a', acc)), @@ -3075,40 +3159,7 @@ function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(client, acc) { } /** - * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 - * @property {SeriesPattern1} all - * @property {SeriesPattern1} p2a - * @property {SeriesPattern1} p2pk33 - * @property {SeriesPattern1} p2pk65 - * @property {SeriesPattern1} p2pkh - * @property {SeriesPattern1} p2sh - * @property {SeriesPattern1} p2tr - * @property {SeriesPattern1} p2wpkh - * @property {SeriesPattern1} p2wsh - */ - -/** - * Create a AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} - */ -function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, acc) { - return { - all: createSeriesPattern1(client, acc), - p2a: createSeriesPattern1(client, _p('p2a', acc)), - p2pk33: createSeriesPattern1(client, _p('p2pk33', acc)), - p2pk65: createSeriesPattern1(client, _p('p2pk65', acc)), - p2pkh: createSeriesPattern1(client, _p('p2pkh', acc)), - p2sh: createSeriesPattern1(client, _p('p2sh', acc)), - p2tr: createSeriesPattern1(client, _p('p2tr', acc)), - p2wpkh: createSeriesPattern1(client, _p('p2wpkh', acc)), - p2wsh: createSeriesPattern1(client, _p('p2wsh', acc)), - }; -} - -/** - * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 + * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 * @property {_1m1w1y24hPercentPpmRatioPattern} all * @property {_1m1w1y24hPercentPpmRatioPattern} p2a * @property {_1m1w1y24hPercentPpmRatioPattern} p2pk33 @@ -3121,12 +3172,12 @@ function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, acc) { */ /** - * Create a AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 pattern node + * Create a AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 pattern node * @param {BrkClient} client * @param {string} acc - Accumulated series name - * @returns {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} + * @returns {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} */ -function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(client, acc) { +function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, acc) { return { all: create_1m1w1y24hPercentPpmRatioPattern(client, acc), p2a: create_1m1w1y24hPercentPpmRatioPattern(client, _p('p2a', acc)), @@ -3239,37 +3290,6 @@ function createPct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern(client, acc) }; } -/** - * @typedef {Object} ChainDataFeeFeesOutputTxPattern - * @property {PercentPpmRatioPattern2} chainShare - * @property {AverageBlockCumulativeSumPattern} dataBytes - * @property {PercentPpmRatioPattern2} dataShare - * @property {_1m1w1y24hPercentPpmRatioPattern} feeShare - * @property {AverageBlockCumulativeSumPattern} fees - * @property {AverageBlockCumulativeSumPattern} outputCount - * @property {AverageBlockCumulativeSumPattern} txCount - * @property {AverageBlockCumulativeSumPattern} txVsize - */ - -/** - * Create a ChainDataFeeFeesOutputTxPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {ChainDataFeeFeesOutputTxPattern} - */ -function createChainDataFeeFeesOutputTxPattern(client, acc) { - return { - chainShare: createPercentPpmRatioPattern2(client, _m(acc, 'chain_share')), - dataBytes: createAverageBlockCumulativeSumPattern(client, _m(acc, 'data_bytes')), - dataShare: createPercentPpmRatioPattern2(client, _m(acc, 'data_share')), - feeShare: create_1m1w1y24hPercentPpmRatioPattern(client, _m(acc, 'fee_share')), - fees: createAverageBlockCumulativeSumPattern(client, _m(acc, 'fees')), - outputCount: createAverageBlockCumulativeSumPattern(client, _m(acc, 'output_count')), - txCount: createAverageBlockCumulativeSumPattern(client, _m(acc, 'tx_count')), - txVsize: createAverageBlockCumulativeSumPattern(client, _m(acc, 'tx_vsize')), - }; -} - /** * @typedef {Object} _10y2y3y4y5y6y8yPattern * @property {PercentPpmRatioPattern} _10y @@ -3361,10 +3381,10 @@ function create_1m1y2y3m4y6m8yPattern(client, acc) { * @typedef {Object} ActiveInputOutputSpendablePattern * @property {_1m1w1y24hBlockPattern} activeReusedAddrCount * @property {_1m1w1y24hBlockPattern2} activeReusedAddrShare - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} inputFromReusedAddrCount - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} inputFromReusedAddrShare - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} outputToReusedAddrCount - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} outputToReusedAddrShare + * @property {AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} inputFromReusedAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} inputFromReusedAddrShare + * @property {AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} outputToReusedAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} outputToReusedAddrShare * @property {_1m1w1y24hPercentPpmRatioPattern} spendableOutputToReusedAddrShare */ @@ -3397,43 +3417,14 @@ function createCapLossMvrvNetPriceProfitSoprPattern(client, acc) { }; } -/** - * @typedef {Object} CoindaysDormancySupplyWakefulnessPattern - * @property {AverageBlockCumulativeSumPattern} coindaysConsumed - * @property {AverageBlockCumulativeSumPattern} coindaysCreated - * @property {AverageBlockCumulativeSumPattern} coindaysStored - * @property {SeriesPattern1} dormancy - * @property {AwakeDormantPattern} supply - * @property {SeriesPattern1} wakefulness - * @property {SeriesPattern1} wakefulnessToDormancy - */ - -/** - * Create a CoindaysDormancySupplyWakefulnessPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {CoindaysDormancySupplyWakefulnessPattern} - */ -function createCoindaysDormancySupplyWakefulnessPattern(client, acc) { - return { - coindaysConsumed: createAverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_consumed')), - coindaysCreated: createAverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_created')), - coindaysStored: createAverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_stored')), - dormancy: createSeriesPattern1(client, _m(acc, 'dormancy')), - supply: createAwakeDormantPattern(client, acc), - wakefulness: createSeriesPattern1(client, _m(acc, 'wakefulness')), - wakefulnessToDormancy: createSeriesPattern1(client, _m(acc, 'wakefulness_to_dormancy')), - }; -} - /** * @typedef {Object} InMaxMinPerSupplyPattern * @property {PerPattern} inLoss * @property {PerPattern} inProfit * @property {CentsSatsUsdPattern} max * @property {CentsSatsUsdPattern} min - * @property {Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perCoin - * @property {Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perDollar + * @property {HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perCoin + * @property {HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perDollar * @property {PercentPpmRatioPattern2} supplyDensity */ @@ -3449,8 +3440,8 @@ function createInMaxMinPerSupplyPattern(client, acc) { inProfit: createPerPattern(client, _m(acc, 'cost_basis_in_profit_per')), max: createCentsSatsUsdPattern(client, _m(acc, 'cost_basis_max')), min: createCentsSatsUsdPattern(client, _m(acc, 'cost_basis_min')), - perCoin: createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_coin')), - perDollar: createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_dollar')), + perCoin: createHeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_coin')), + perDollar: createHeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_dollar')), supplyDensity: createPercentPpmRatioPattern2(client, _m(acc, 'supply_density')), }; } @@ -3515,6 +3506,33 @@ function createMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, acc) { }; } +/** + * @typedef {Object} AverageBlockChainCumulativeDataSumPattern + * @property {_1m1w1y24hPattern} average + * @property {SeriesPattern18} block + * @property {PercentPpmRatioPattern2} chainShare + * @property {SeriesPattern1} cumulative + * @property {PercentPpmRatioPattern2} dataShare + * @property {_1m1w1y24hPattern} sum + */ + +/** + * Create a AverageBlockChainCumulativeDataSumPattern pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {AverageBlockChainCumulativeDataSumPattern} + */ +function createAverageBlockChainCumulativeDataSumPattern(client, acc) { + return { + average: create_1m1w1y24hPattern(client, _m(acc, 'data_bytes_average')), + block: createSeriesPattern18(client, _m(acc, 'data_bytes')), + chainShare: createPercentPpmRatioPattern2(client, _m(acc, 'chain_share')), + cumulative: createSeriesPattern1(client, _m(acc, 'data_bytes_cumulative')), + dataShare: createPercentPpmRatioPattern2(client, _m(acc, 'data_share')), + sum: create_1m1w1y24hPattern(client, _m(acc, 'data_bytes_sum')), + }; +} + /** * @typedef {Object} AverageBlockCumulativeInSumPattern * @property {_1m1w1y24hPattern3} average @@ -3623,6 +3641,58 @@ function createDeltaDominanceHalfInTotalPattern(client, acc) { }; } +/** + * @typedef {Object} HeightRankTailThresholdPattern + * @property {SeriesPattern18<[Dollars; 3]>} height + * @property {SeriesPattern1} rank + * @property {PercentPpmRatioPattern2} tail + * @property {SeriesPattern1} thresholdPct0025 + * @property {SeriesPattern1} thresholdPct005 + * @property {SeriesPattern1} thresholdPct01 + */ + +/** + * Create a HeightRankTailThresholdPattern pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {HeightRankTailThresholdPattern} + */ +function createHeightRankTailThresholdPattern(client, acc) { + return { + height: createSeriesPattern18(client, _m(acc, 'thresholds')), + rank: createSeriesPattern1(client, _m(acc, 'rank')), + tail: createPercentPpmRatioPattern2(client, _m(acc, 'tail')), + thresholdPct0025: createSeriesPattern1(client, _m(acc, 'threshold')), + thresholdPct005: createSeriesPattern1(client, _m(acc, 'threshold_pct0_05')), + thresholdPct01: createSeriesPattern1(client, _m(acc, 'threshold_pct0_1')), + }; +} + +/** + * @typedef {Object} _1m1w1y24hHeightPattern3 + * @property {PercentPpmRatioPattern2} _1m + * @property {PercentPpmRatioPattern2} _1w + * @property {PercentPpmRatioPattern2} _1y + * @property {PercentPpmRatioPattern2} _24h + * @property {SeriesPattern18<[PartsPerMillion32; 4]>} height + */ + +/** + * Create a _1m1w1y24hHeightPattern3 pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {_1m1w1y24hHeightPattern3} + */ +function create_1m1w1y24hHeightPattern3(client, acc) { + return { + _1m: createPercentPpmRatioPattern2(client, _m(acc, '1m')), + _1w: createPercentPpmRatioPattern2(client, _m(acc, '1w')), + _1y: createPercentPpmRatioPattern2(client, _m(acc, '1y')), + _24h: createPercentPpmRatioPattern2(client, _m(acc, '24h')), + height: createSeriesPattern18(client, _m(acc, 'ppm')), + }; +} + /** * @typedef {Object} _1m1w1y24hBlockPattern2 * @property {SeriesPattern1} _1m @@ -3673,6 +3743,81 @@ function create_1m1w1y24hBlockPattern(client, acc) { }; } +/** + * @typedef {Object} _1m1w1y24hHeightPattern + * @property {SeriesPattern1} _1m + * @property {SeriesPattern1} _1w + * @property {SeriesPattern1} _1y + * @property {SeriesPattern1} _24h + * @property {SeriesPattern18<[StoredF32; 4]>} height + */ + +/** + * Create a _1m1w1y24hHeightPattern pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {_1m1w1y24hHeightPattern} + */ +function create_1m1w1y24hHeightPattern(client, acc) { + return { + _1m: createSeriesPattern1(client, _m(acc, '1m')), + _1w: createSeriesPattern1(client, _m(acc, '1w')), + _1y: createSeriesPattern1(client, _m(acc, '1y')), + _24h: createSeriesPattern1(client, _m(acc, '24h')), + height: createSeriesPattern18(client, acc), + }; +} + +/** + * @typedef {Object} _1m1w1y24hHeightPattern4 + * @property {SeriesPattern1} _1m + * @property {SeriesPattern1} _1w + * @property {SeriesPattern1} _1y + * @property {SeriesPattern1} _24h + * @property {SeriesPattern18<[StoredF64; 3]>} height + */ + +/** + * Create a _1m1w1y24hHeightPattern4 pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {_1m1w1y24hHeightPattern4} + */ +function create_1m1w1y24hHeightPattern4(client, acc) { + return { + _1m: createSeriesPattern1(client, _m(acc, '1m')), + _1w: createSeriesPattern1(client, _m(acc, '1w')), + _1y: createSeriesPattern1(client, _m(acc, '1y')), + _24h: createSeriesPattern1(client, _m(acc, '24h')), + height: createSeriesPattern18(client, acc), + }; +} + +/** + * @typedef {Object} _1m1w1y24hHeightPattern2 + * @property {SeriesPattern1} _1m + * @property {SeriesPattern1} _1w + * @property {SeriesPattern1} _1y + * @property {SeriesPattern1} _24h + * @property {SeriesPattern18<[StoredF64; 4]>} height + */ + +/** + * Create a _1m1w1y24hHeightPattern2 pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {_1m1w1y24hHeightPattern2} + */ +function create_1m1w1y24hHeightPattern2(client, acc) { + return { + _1m: createSeriesPattern1(client, _m(acc, '1m')), + _1w: createSeriesPattern1(client, _m(acc, '1w')), + _1y: createSeriesPattern1(client, _m(acc, '1y')), + _24h: createSeriesPattern1(client, _m(acc, '24h')), + height: createSeriesPattern18(client, acc), + }; +} + /** * @typedef {Object} ActiveBidirectionalReactivatedReceivingSendingPattern * @property {_1m1w1y24hBlockPattern} active @@ -3798,6 +3943,31 @@ function createActivityOutputsRealizedSupplyUnrealizedPattern2(client, acc) { }; } +/** + * @typedef {Object} AverageBlockCumulativeFeeSumPattern + * @property {_1m1w1y24hPattern} average + * @property {SeriesPattern18} block + * @property {SeriesPattern1} cumulative + * @property {_1m1w1y24hPercentPpmRatioPattern} feeShare + * @property {_1m1w1y24hPattern} sum + */ + +/** + * Create a AverageBlockCumulativeFeeSumPattern pattern node + * @param {BrkClient} client + * @param {string} acc - Accumulated series name + * @returns {AverageBlockCumulativeFeeSumPattern} + */ +function createAverageBlockCumulativeFeeSumPattern(client, acc) { + return { + average: create_1m1w1y24hPattern(client, _m(acc, 'fees_average')), + block: createSeriesPattern18(client, _m(acc, 'fees')), + cumulative: createSeriesPattern1(client, _m(acc, 'fees_cumulative')), + feeShare: create_1m1w1y24hPercentPpmRatioPattern(client, _m(acc, 'fee_share')), + sum: create_1m1w1y24hPattern(client, _m(acc, 'fees_sum')), + }; +} + /** * @typedef {Object} BlockChangeCumulativeDeltaSumPattern * @property {CentsUsdPattern4} block @@ -4042,30 +4212,15 @@ function createPhsReboundThsPattern(client, acc) { } /** - * @typedef {Object} RankTailThresholdPattern - * @property {SeriesPattern1} rank - * @property {PercentPpmRatioPattern2} tail - * @property {SeriesPattern1} thresholdPct0025 - * @property {SeriesPattern1} thresholdPct005 - * @property {SeriesPattern1} thresholdPct01 + * @template T + * @typedef {Object} CumulativeMultipleOversizedPrePattern3 + * @property {SeriesPattern18} cumulative + * @property {AverageBlockCumulativeSumPattern} multiple + * @property {AverageBlockCumulativeSumPattern} oversized + * @property {AverageBlockCumulativeSumPattern} preV30Nonstandard + * @property {AverageBlockCumulativeSumPattern} preV30Standard */ -/** - * Create a RankTailThresholdPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {RankTailThresholdPattern} - */ -function createRankTailThresholdPattern(client, acc) { - return { - rank: createSeriesPattern1(client, _m(acc, 'rank')), - tail: createPercentPpmRatioPattern2(client, _m(acc, 'tail')), - thresholdPct0025: createSeriesPattern1(client, _m(acc, 'threshold')), - thresholdPct005: createSeriesPattern1(client, _m(acc, 'threshold_pct0_05')), - thresholdPct01: createSeriesPattern1(client, _m(acc, 'threshold_pct0_1')), - }; -} - /** * @typedef {Object} _1m1w1y24hPattern4 * @property {BtcCentsSatsUsdPattern} _1m @@ -4227,29 +4382,6 @@ function create_1m1w1y24hPattern2(client, acc) { }; } -/** - * @typedef {Object} _1m1w1y24hPattern8 - * @property {PercentPpmRatioPattern2} _1m - * @property {PercentPpmRatioPattern2} _1w - * @property {PercentPpmRatioPattern2} _1y - * @property {PercentPpmRatioPattern2} _24h - */ - -/** - * Create a _1m1w1y24hPattern8 pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {_1m1w1y24hPattern8} - */ -function create_1m1w1y24hPattern8(client, acc) { - return { - _1m: createPercentPpmRatioPattern2(client, _m(acc, '1m')), - _1w: createPercentPpmRatioPattern2(client, _m(acc, '1w')), - _1y: createPercentPpmRatioPattern2(client, _m(acc, '1y')), - _24h: createPercentPpmRatioPattern2(client, _m(acc, '24h')), - }; -} - /** * @typedef {Object} AverageBlockCumulativeSumPattern2 * @property {_1m1w1y24hPattern3} average @@ -4446,7 +4578,7 @@ function createCentsToUsdPattern3(client, acc) { * @typedef {Object} CoindaysCoinyearsDormancyTransferPattern * @property {AverageBlockCumulativeSumPattern} coindaysDestroyed * @property {SeriesPattern1} coinyearsDestroyed - * @property {_1m1w1y24hPattern} dormancy + * @property {_1m1w1y24hHeightPattern} dormancy * @property {AverageBlockCumulativeInSumPattern} transferVolume */ @@ -4460,7 +4592,7 @@ function createCoindaysCoinyearsDormancyTransferPattern(client, acc) { return { coindaysDestroyed: createAverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_destroyed')), coinyearsDestroyed: createSeriesPattern1(client, _m(acc, 'coinyears_destroyed')), - dormancy: create_1m1w1y24hPattern(client, _m(acc, 'dormancy')), + dormancy: create_1m1w1y24hHeightPattern(client, _m(acc, 'dormancy')), transferVolume: createAverageBlockCumulativeInSumPattern(client, _m(acc, 'transfer_volume')), }; } @@ -4488,29 +4620,6 @@ function createLossNetNuplProfitPattern(client, acc) { }; } -/** - * @typedef {Object} MobilitySpendingSupplyPattern - * @property {SeriesPattern1} mobility - * @property {SeriesPattern1} spendingExposure - * @property {SeriesPattern1} spendingRate - * @property {ImmobileMobilePattern} supply - */ - -/** - * Create a MobilitySpendingSupplyPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {MobilitySpendingSupplyPattern} - */ -function createMobilitySpendingSupplyPattern(client, acc) { - return { - mobility: createSeriesPattern1(client, _m(acc, 'mobility')), - spendingExposure: createSeriesPattern1(client, _m(acc, 'spending_exposure')), - spendingRate: createSeriesPattern1(client, _m(acc, 'spending_rate')), - supply: createImmobileMobilePattern(client, acc), - }; -} - /** * @typedef {Object} NuplRealizedSupplyUnrealizedPattern * @property {PpmRatioPattern} nupl @@ -4587,7 +4696,7 @@ function createAverageBlockCumulativeSumPattern(client, acc) { /** * @typedef {Object} AdjustedRatioValuePattern * @property {RatioTransferValuePattern} adjusted - * @property {_1m1w1y24hPattern} ratio + * @property {_1m1w1y24hHeightPattern4} ratio * @property {AverageBlockCumulativeSumPattern} valueDestroyed */ @@ -4600,7 +4709,7 @@ function createAverageBlockCumulativeSumPattern(client, acc) { function createAdjustedRatioValuePattern(client, acc) { return { adjusted: createRatioTransferValuePattern(client, acc), - ratio: create_1m1w1y24hPattern(client, _m(acc, 'sopr')), + ratio: create_1m1w1y24hHeightPattern4(client, _m(acc, 'sopr')), valueDestroyed: createAverageBlockCumulativeSumPattern(client, _m(acc, 'value_destroyed')), }; } @@ -4784,7 +4893,7 @@ function createCentsSatsUsdPattern(client, acc) { * @typedef {Object} CountEventsSupplyPattern * @property {FundedTotalPattern} count * @property {ActiveInputOutputSpendablePattern} events - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern} supply + * @property {AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern} supply */ /** @@ -5000,7 +5109,7 @@ function createPpmPriceRatioPattern(client, acc, disc) { /** * @typedef {Object} RatioTransferValuePattern - * @property {_1m1w1y24hPattern} ratio + * @property {_1m1w1y24hHeightPattern2} ratio * @property {AverageBlockCumulativeSumPattern} transferVolume * @property {AverageBlockCumulativeSumPattern} valueDestroyed */ @@ -5013,7 +5122,7 @@ function createPpmPriceRatioPattern(client, acc, disc) { */ function createRatioTransferValuePattern(client, acc) { return { - ratio: create_1m1w1y24hPattern(client, _m(acc, 'asopr')), + ratio: create_1m1w1y24hHeightPattern2(client, _m(acc, 'asopr')), transferVolume: createAverageBlockCumulativeSumPattern(client, _m(acc, 'adj_value_created')), valueDestroyed: createAverageBlockCumulativeSumPattern(client, _m(acc, 'adj_value_destroyed')), }; @@ -5179,25 +5288,6 @@ function createAllSthPattern(client, acc, disc) { }; } -/** - * @typedef {Object} AwakeDormantPattern - * @property {BtcCentsSatsUsdPattern} awake - * @property {BtcCentsSatsUsdPattern} dormant - */ - -/** - * Create a AwakeDormantPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {AwakeDormantPattern} - */ -function createAwakeDormantPattern(client, acc) { - return { - awake: createBtcCentsSatsUsdPattern(client, _m(acc, 'awake_supply')), - dormant: createBtcCentsSatsUsdPattern(client, _m(acc, 'dormant_supply')), - }; -} - /** * @typedef {Object} AwakeDormantPattern2 * @property {CapPriceSupplyPattern} awake @@ -5396,48 +5486,16 @@ function createCoindaysTransferPattern(client, acc) { /** * @typedef {Object} FundedTotalPattern - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} funded - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} total + * @property {AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} funded + * @property {AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} total */ -/** - * Create a FundedTotalPattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {FundedTotalPattern} - */ -function createFundedTotalPattern(client, acc) { - return { - funded: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, acc), - total: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, _p('total', acc)), - }; -} - /** * @typedef {Object} ImmobileMobilePattern2 * @property {BtcCentsSatsUsdPattern} immobile * @property {BtcCentsInSatsUsdPattern} mobile */ -/** - * @typedef {Object} ImmobileMobilePattern - * @property {BtcCentsSatsUsdPattern} immobile - * @property {BtcCentsSatsUsdPattern} mobile - */ - -/** - * Create a ImmobileMobilePattern pattern node - * @param {BrkClient} client - * @param {string} acc - Accumulated series name - * @returns {ImmobileMobilePattern} - */ -function createImmobileMobilePattern(client, acc) { - return { - immobile: createBtcCentsSatsUsdPattern(client, _m(acc, 'immobile_supply')), - mobile: createBtcCentsSatsUsdPattern(client, _m(acc, 'mobile_supply')), - }; -} - /** * @typedef {Object} InPattern2 * @property {CentsUsdPattern3} inLoss @@ -6150,6 +6208,7 @@ function createUnspentPattern(client, acc) { * @property {AverageBlockCumulativeSumPattern} p2a * @property {AverageBlockCumulativeSumPattern} unknown * @property {AverageBlockCumulativeSumPattern} empty + * @property {SeriesPattern18<[StoredU16; 11]>} height */ /** @@ -6181,6 +6240,7 @@ function createUnspentPattern(client, acc) { * @property {AverageBlockCumulativeSumPattern} p2a * @property {AverageBlockCumulativeSumPattern} unknown * @property {AverageBlockCumulativeSumPattern} empty + * @property {SeriesPattern18<[StoredU64; 11]>} cumulative */ /** @@ -6222,7 +6282,7 @@ function createUnspentPattern(client, acc) { * @property {SeriesTree_Outputs_ByType_OutputCount} outputCount * @property {AverageBlockCumulativeSumPattern} spendableOutputCount * @property {SeriesTree_Outputs_ByType_OutputShare} outputShare - * @property {AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern} txCount + * @property {SeriesTree_Outputs_ByType_TxCount} txCount * @property {EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2} txShare */ @@ -6241,6 +6301,7 @@ function createUnspentPattern(client, acc) { * @property {AverageBlockCumulativeSumPattern} unknown * @property {AverageBlockCumulativeSumPattern} empty * @property {AverageBlockCumulativeSumPattern} opReturn + * @property {SeriesPattern18<[StoredU16; 12]>} height */ /** @@ -6259,6 +6320,24 @@ function createUnspentPattern(client, acc) { * @property {_1m1w1y24hPercentPpmRatioPattern} opReturn */ +/** + * @typedef {Object} SeriesTree_Outputs_ByType_TxCount + * @property {AverageBlockCumulativeSumPattern} all + * @property {AverageBlockCumulativeSumPattern} p2pk65 + * @property {AverageBlockCumulativeSumPattern} p2pk33 + * @property {AverageBlockCumulativeSumPattern} p2pkh + * @property {AverageBlockCumulativeSumPattern} p2ms + * @property {AverageBlockCumulativeSumPattern} p2sh + * @property {AverageBlockCumulativeSumPattern} p2wpkh + * @property {AverageBlockCumulativeSumPattern} p2wsh + * @property {AverageBlockCumulativeSumPattern} p2tr + * @property {AverageBlockCumulativeSumPattern} p2a + * @property {AverageBlockCumulativeSumPattern} unknown + * @property {AverageBlockCumulativeSumPattern} empty + * @property {AverageBlockCumulativeSumPattern} opReturn + * @property {SeriesPattern18<[StoredU64; 12]>} cumulative + */ + /** * @typedef {Object} SeriesTree_Outputs_Value * @property {BlockCumulativePattern} opReturn @@ -6269,11 +6348,11 @@ function createUnspentPattern(client, acc) { * @property {SeriesTree_Addrs_Raw} raw * @property {SeriesTree_Addrs_Indexes} indexes * @property {SeriesTree_Addrs_Data} data - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} funded - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} empty + * @property {SeriesTree_Addrs_Funded} funded + * @property {SeriesTree_Addrs_Empty} empty * @property {SeriesTree_Addrs_Activity} activity - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} total - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} new + * @property {SeriesTree_Addrs_Total} total + * @property {SeriesTree_Addrs_New} new * @property {SeriesTree_Addrs_Reused} reused * @property {SeriesTree_Addrs_Respent} respent * @property {SeriesTree_Addrs_Exposed} exposed @@ -6361,6 +6440,34 @@ function createUnspentPattern(client, acc) { * @property {SeriesPattern35} empty */ +/** + * @typedef {Object} SeriesTree_Addrs_Funded + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Empty + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + /** * @typedef {Object} SeriesTree_Addrs_Activity * @property {SeriesTree_Addrs_Activity_All} all @@ -6383,24 +6490,113 @@ function createUnspentPattern(client, acc) { * @property {_1m1w1y24hBlockPattern} active */ +/** + * @typedef {Object} SeriesTree_Addrs_Total + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + +/** + * @typedef {Object} SeriesTree_Addrs_New + * @property {AverageBlockCumulativeSumPattern} all + * @property {AverageBlockCumulativeSumPattern} p2pk65 + * @property {AverageBlockCumulativeSumPattern} p2pk33 + * @property {AverageBlockCumulativeSumPattern} p2pkh + * @property {AverageBlockCumulativeSumPattern} p2sh + * @property {AverageBlockCumulativeSumPattern} p2wpkh + * @property {AverageBlockCumulativeSumPattern} p2wsh + * @property {AverageBlockCumulativeSumPattern} p2tr + * @property {AverageBlockCumulativeSumPattern} p2a + */ + /** * @typedef {Object} SeriesTree_Addrs_Reused - * @property {FundedTotalPattern} count + * @property {SeriesTree_Addrs_Reused_Count} count * @property {SeriesTree_Addrs_Reused_Events} events * @property {SeriesTree_Addrs_Reused_Supply} supply */ +/** + * @typedef {Object} SeriesTree_Addrs_Reused_Count + * @property {SeriesTree_Addrs_Reused_Count_Funded} funded + * @property {SeriesTree_Addrs_Reused_Count_Total} total + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Reused_Count_Funded + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Reused_Count_Total + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + /** * @typedef {Object} SeriesTree_Addrs_Reused_Events - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} outputToReusedAddrCount - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} outputToReusedAddrShare + * @property {SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount} outputToReusedAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} outputToReusedAddrShare * @property {_1m1w1y24hPercentPpmRatioPattern} spendableOutputToReusedAddrShare - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} inputFromReusedAddrCount - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} inputFromReusedAddrShare + * @property {SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount} inputFromReusedAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} inputFromReusedAddrShare * @property {_1m1w1y24hBlockPattern} activeReusedAddrCount * @property {_1m1w1y24hBlockPattern2} activeReusedAddrShare */ +/** + * @typedef {Object} SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount + * @property {AverageBlockCumulativeSumPattern} all + * @property {AverageBlockCumulativeSumPattern} p2pk65 + * @property {AverageBlockCumulativeSumPattern} p2pk33 + * @property {AverageBlockCumulativeSumPattern} p2pkh + * @property {AverageBlockCumulativeSumPattern} p2sh + * @property {AverageBlockCumulativeSumPattern} p2wpkh + * @property {AverageBlockCumulativeSumPattern} p2wsh + * @property {AverageBlockCumulativeSumPattern} p2tr + * @property {AverageBlockCumulativeSumPattern} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount + * @property {AverageBlockCumulativeSumPattern} all + * @property {AverageBlockCumulativeSumPattern} p2pk65 + * @property {AverageBlockCumulativeSumPattern} p2pk33 + * @property {AverageBlockCumulativeSumPattern} p2pkh + * @property {AverageBlockCumulativeSumPattern} p2sh + * @property {AverageBlockCumulativeSumPattern} p2wpkh + * @property {AverageBlockCumulativeSumPattern} p2wsh + * @property {AverageBlockCumulativeSumPattern} p2tr + * @property {AverageBlockCumulativeSumPattern} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} cumulative + */ + /** * @typedef {Object} SeriesTree_Addrs_Reused_Supply * @property {BtcCentsSatsUsdPattern} all @@ -6412,27 +6608,90 @@ function createUnspentPattern(client, acc) { * @property {BtcCentsSatsUsdPattern} p2wsh * @property {BtcCentsSatsUsdPattern} p2tr * @property {BtcCentsSatsUsdPattern} p2a - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5} share + * @property {SeriesPattern18<[Sats; 8]>} height + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} share */ /** * @typedef {Object} SeriesTree_Addrs_Respent - * @property {FundedTotalPattern} count + * @property {SeriesTree_Addrs_Respent_Count} count * @property {SeriesTree_Addrs_Respent_Events} events * @property {SeriesTree_Addrs_Respent_Supply} supply */ +/** + * @typedef {Object} SeriesTree_Addrs_Respent_Count + * @property {SeriesTree_Addrs_Respent_Count_Funded} funded + * @property {SeriesTree_Addrs_Respent_Count_Total} total + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Respent_Count_Funded + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Respent_Count_Total + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + /** * @typedef {Object} SeriesTree_Addrs_Respent_Events - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} outputToReusedAddrCount - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} outputToReusedAddrShare + * @property {SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount} outputToReusedAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} outputToReusedAddrShare * @property {_1m1w1y24hPercentPpmRatioPattern} spendableOutputToReusedAddrShare - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} inputFromReusedAddrCount - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7} inputFromReusedAddrShare + * @property {SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount} inputFromReusedAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6} inputFromReusedAddrShare * @property {_1m1w1y24hBlockPattern} activeReusedAddrCount * @property {_1m1w1y24hBlockPattern2} activeReusedAddrShare */ +/** + * @typedef {Object} SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount + * @property {AverageBlockCumulativeSumPattern} all + * @property {AverageBlockCumulativeSumPattern} p2pk65 + * @property {AverageBlockCumulativeSumPattern} p2pk33 + * @property {AverageBlockCumulativeSumPattern} p2pkh + * @property {AverageBlockCumulativeSumPattern} p2sh + * @property {AverageBlockCumulativeSumPattern} p2wpkh + * @property {AverageBlockCumulativeSumPattern} p2wsh + * @property {AverageBlockCumulativeSumPattern} p2tr + * @property {AverageBlockCumulativeSumPattern} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount + * @property {AverageBlockCumulativeSumPattern} all + * @property {AverageBlockCumulativeSumPattern} p2pk65 + * @property {AverageBlockCumulativeSumPattern} p2pk33 + * @property {AverageBlockCumulativeSumPattern} p2pkh + * @property {AverageBlockCumulativeSumPattern} p2sh + * @property {AverageBlockCumulativeSumPattern} p2wpkh + * @property {AverageBlockCumulativeSumPattern} p2wsh + * @property {AverageBlockCumulativeSumPattern} p2tr + * @property {AverageBlockCumulativeSumPattern} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} cumulative + */ + /** * @typedef {Object} SeriesTree_Addrs_Respent_Supply * @property {BtcCentsSatsUsdPattern} all @@ -6444,15 +6703,50 @@ function createUnspentPattern(client, acc) { * @property {BtcCentsSatsUsdPattern} p2wsh * @property {BtcCentsSatsUsdPattern} p2tr * @property {BtcCentsSatsUsdPattern} p2a - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5} share + * @property {SeriesPattern18<[Sats; 8]>} height + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} share */ /** * @typedef {Object} SeriesTree_Addrs_Exposed - * @property {FundedTotalPattern} count + * @property {SeriesTree_Addrs_Exposed_Count} count * @property {SeriesTree_Addrs_Exposed_Supply} supply */ +/** + * @typedef {Object} SeriesTree_Addrs_Exposed_Count + * @property {SeriesTree_Addrs_Exposed_Count_Funded} funded + * @property {SeriesTree_Addrs_Exposed_Count_Total} total + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Exposed_Count_Funded + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Exposed_Count_Total + * @property {SeriesPattern1} all + * @property {SeriesPattern1} p2pk65 + * @property {SeriesPattern1} p2pk33 + * @property {SeriesPattern1} p2pkh + * @property {SeriesPattern1} p2sh + * @property {SeriesPattern1} p2wpkh + * @property {SeriesPattern1} p2wsh + * @property {SeriesPattern1} p2tr + * @property {SeriesPattern1} p2a + * @property {SeriesPattern18<[StoredU64; 8]>} height + */ + /** * @typedef {Object} SeriesTree_Addrs_Exposed_Supply * @property {BtcCentsSatsUsdPattern} all @@ -6464,7 +6758,8 @@ function createUnspentPattern(client, acc) { * @property {BtcCentsSatsUsdPattern} p2wsh * @property {BtcCentsSatsUsdPattern} p2tr * @property {BtcCentsSatsUsdPattern} p2a - * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5} share + * @property {SeriesPattern18<[Sats; 8]>} height + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4} share */ /** @@ -6543,7 +6838,7 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_OpReturn_Total - * @property {AverageBlockCumulativeSumPattern} dataBytes + * @property {AverageBlockCumulativeSumPattern} dataBytes * @property {AverageBlockCumulativeSumPattern} txCount * @property {AverageBlockCumulativeSumPattern} txVsize * @property {AverageBlockCumulativeSumPattern} fees @@ -6553,37 +6848,205 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_OpReturn_ByKind - * @property {ChainDataFeeFeesOutputTxPattern} runes - * @property {ChainDataFeeFeesOutputTxPattern} veriBlock - * @property {ChainDataFeeFeesOutputTxPattern} omni - * @property {ChainDataFeeFeesOutputTxPattern} stacks - * @property {ChainDataFeeFeesOutputTxPattern} blockstack - * @property {ChainDataFeeFeesOutputTxPattern} colu - * @property {ChainDataFeeFeesOutputTxPattern} openAssets - * @property {ChainDataFeeFeesOutputTxPattern} komodo - * @property {ChainDataFeeFeesOutputTxPattern} coinSpark - * @property {ChainDataFeeFeesOutputTxPattern} poet - * @property {ChainDataFeeFeesOutputTxPattern} docproof - * @property {ChainDataFeeFeesOutputTxPattern} openTimestamps - * @property {ChainDataFeeFeesOutputTxPattern} factom - * @property {ChainDataFeeFeesOutputTxPattern} eternityWall - * @property {ChainDataFeeFeesOutputTxPattern} memo - * @property {ChainDataFeeFeesOutputTxPattern} bitproof - * @property {ChainDataFeeFeesOutputTxPattern} ascribe - * @property {ChainDataFeeFeesOutputTxPattern} stampery - * @property {ChainDataFeeFeesOutputTxPattern} epobc - * @property {ChainDataFeeFeesOutputTxPattern} bareHash - * @property {ChainDataFeeFeesOutputTxPattern} text - * @property {ChainDataFeeFeesOutputTxPattern} empty - * @property {ChainDataFeeFeesOutputTxPattern} unknown + * @property {SeriesTree_OpReturn_ByKind_OutputCount} outputCount + * @property {SeriesTree_OpReturn_ByKind_DataBytes} dataBytes + * @property {SeriesTree_OpReturn_ByKind_TxCount} txCount + * @property {SeriesTree_OpReturn_ByKind_TxVsize} txVsize + * @property {SeriesTree_OpReturn_ByKind_Fees} fees + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_ByKind_OutputCount + * @property {AverageBlockCumulativeSumPattern} runes + * @property {AverageBlockCumulativeSumPattern} veriBlock + * @property {AverageBlockCumulativeSumPattern} omni + * @property {AverageBlockCumulativeSumPattern} stacks + * @property {AverageBlockCumulativeSumPattern} blockstack + * @property {AverageBlockCumulativeSumPattern} colu + * @property {AverageBlockCumulativeSumPattern} openAssets + * @property {AverageBlockCumulativeSumPattern} komodo + * @property {AverageBlockCumulativeSumPattern} coinSpark + * @property {AverageBlockCumulativeSumPattern} poet + * @property {AverageBlockCumulativeSumPattern} docproof + * @property {AverageBlockCumulativeSumPattern} openTimestamps + * @property {AverageBlockCumulativeSumPattern} factom + * @property {AverageBlockCumulativeSumPattern} eternityWall + * @property {AverageBlockCumulativeSumPattern} memo + * @property {AverageBlockCumulativeSumPattern} bitproof + * @property {AverageBlockCumulativeSumPattern} ascribe + * @property {AverageBlockCumulativeSumPattern} stampery + * @property {AverageBlockCumulativeSumPattern} epobc + * @property {AverageBlockCumulativeSumPattern} bareHash + * @property {AverageBlockCumulativeSumPattern} text + * @property {AverageBlockCumulativeSumPattern} empty + * @property {AverageBlockCumulativeSumPattern} unknown + * @property {SeriesPattern18<[StoredU64; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_ByKind_DataBytes + * @property {AverageBlockChainCumulativeDataSumPattern} runes + * @property {AverageBlockChainCumulativeDataSumPattern} veriBlock + * @property {AverageBlockChainCumulativeDataSumPattern} omni + * @property {AverageBlockChainCumulativeDataSumPattern} stacks + * @property {AverageBlockChainCumulativeDataSumPattern} blockstack + * @property {AverageBlockChainCumulativeDataSumPattern} colu + * @property {AverageBlockChainCumulativeDataSumPattern} openAssets + * @property {AverageBlockChainCumulativeDataSumPattern} komodo + * @property {AverageBlockChainCumulativeDataSumPattern} coinSpark + * @property {AverageBlockChainCumulativeDataSumPattern} poet + * @property {AverageBlockChainCumulativeDataSumPattern} docproof + * @property {AverageBlockChainCumulativeDataSumPattern} openTimestamps + * @property {AverageBlockChainCumulativeDataSumPattern} factom + * @property {AverageBlockChainCumulativeDataSumPattern} eternityWall + * @property {AverageBlockChainCumulativeDataSumPattern} memo + * @property {AverageBlockChainCumulativeDataSumPattern} bitproof + * @property {AverageBlockChainCumulativeDataSumPattern} ascribe + * @property {AverageBlockChainCumulativeDataSumPattern} stampery + * @property {AverageBlockChainCumulativeDataSumPattern} epobc + * @property {AverageBlockChainCumulativeDataSumPattern} bareHash + * @property {AverageBlockChainCumulativeDataSumPattern} text + * @property {AverageBlockChainCumulativeDataSumPattern} empty + * @property {AverageBlockChainCumulativeDataSumPattern} unknown + * @property {SeriesPattern18<[Bytes; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_ByKind_TxCount + * @property {AverageBlockCumulativeSumPattern} runes + * @property {AverageBlockCumulativeSumPattern} veriBlock + * @property {AverageBlockCumulativeSumPattern} omni + * @property {AverageBlockCumulativeSumPattern} stacks + * @property {AverageBlockCumulativeSumPattern} blockstack + * @property {AverageBlockCumulativeSumPattern} colu + * @property {AverageBlockCumulativeSumPattern} openAssets + * @property {AverageBlockCumulativeSumPattern} komodo + * @property {AverageBlockCumulativeSumPattern} coinSpark + * @property {AverageBlockCumulativeSumPattern} poet + * @property {AverageBlockCumulativeSumPattern} docproof + * @property {AverageBlockCumulativeSumPattern} openTimestamps + * @property {AverageBlockCumulativeSumPattern} factom + * @property {AverageBlockCumulativeSumPattern} eternityWall + * @property {AverageBlockCumulativeSumPattern} memo + * @property {AverageBlockCumulativeSumPattern} bitproof + * @property {AverageBlockCumulativeSumPattern} ascribe + * @property {AverageBlockCumulativeSumPattern} stampery + * @property {AverageBlockCumulativeSumPattern} epobc + * @property {AverageBlockCumulativeSumPattern} bareHash + * @property {AverageBlockCumulativeSumPattern} text + * @property {AverageBlockCumulativeSumPattern} empty + * @property {AverageBlockCumulativeSumPattern} unknown + * @property {SeriesPattern18<[StoredU64; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_ByKind_TxVsize + * @property {AverageBlockCumulativeSumPattern} runes + * @property {AverageBlockCumulativeSumPattern} veriBlock + * @property {AverageBlockCumulativeSumPattern} omni + * @property {AverageBlockCumulativeSumPattern} stacks + * @property {AverageBlockCumulativeSumPattern} blockstack + * @property {AverageBlockCumulativeSumPattern} colu + * @property {AverageBlockCumulativeSumPattern} openAssets + * @property {AverageBlockCumulativeSumPattern} komodo + * @property {AverageBlockCumulativeSumPattern} coinSpark + * @property {AverageBlockCumulativeSumPattern} poet + * @property {AverageBlockCumulativeSumPattern} docproof + * @property {AverageBlockCumulativeSumPattern} openTimestamps + * @property {AverageBlockCumulativeSumPattern} factom + * @property {AverageBlockCumulativeSumPattern} eternityWall + * @property {AverageBlockCumulativeSumPattern} memo + * @property {AverageBlockCumulativeSumPattern} bitproof + * @property {AverageBlockCumulativeSumPattern} ascribe + * @property {AverageBlockCumulativeSumPattern} stampery + * @property {AverageBlockCumulativeSumPattern} epobc + * @property {AverageBlockCumulativeSumPattern} bareHash + * @property {AverageBlockCumulativeSumPattern} text + * @property {AverageBlockCumulativeSumPattern} empty + * @property {AverageBlockCumulativeSumPattern} unknown + * @property {SeriesPattern18<[VSize; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_ByKind_Fees + * @property {AverageBlockCumulativeFeeSumPattern} runes + * @property {AverageBlockCumulativeFeeSumPattern} veriBlock + * @property {AverageBlockCumulativeFeeSumPattern} omni + * @property {AverageBlockCumulativeFeeSumPattern} stacks + * @property {AverageBlockCumulativeFeeSumPattern} blockstack + * @property {AverageBlockCumulativeFeeSumPattern} colu + * @property {AverageBlockCumulativeFeeSumPattern} openAssets + * @property {AverageBlockCumulativeFeeSumPattern} komodo + * @property {AverageBlockCumulativeFeeSumPattern} coinSpark + * @property {AverageBlockCumulativeFeeSumPattern} poet + * @property {AverageBlockCumulativeFeeSumPattern} docproof + * @property {AverageBlockCumulativeFeeSumPattern} openTimestamps + * @property {AverageBlockCumulativeFeeSumPattern} factom + * @property {AverageBlockCumulativeFeeSumPattern} eternityWall + * @property {AverageBlockCumulativeFeeSumPattern} memo + * @property {AverageBlockCumulativeFeeSumPattern} bitproof + * @property {AverageBlockCumulativeFeeSumPattern} ascribe + * @property {AverageBlockCumulativeFeeSumPattern} stampery + * @property {AverageBlockCumulativeFeeSumPattern} epobc + * @property {AverageBlockCumulativeFeeSumPattern} bareHash + * @property {AverageBlockCumulativeFeeSumPattern} text + * @property {AverageBlockCumulativeFeeSumPattern} empty + * @property {AverageBlockCumulativeFeeSumPattern} unknown + * @property {SeriesPattern18<[Sats; 23]>} cumulative */ /** * @typedef {Object} SeriesTree_OpReturn_Policy - * @property {ChainDataFeeFeesOutputTxPattern} preV30Standard - * @property {ChainDataFeeFeesOutputTxPattern} preV30Nonstandard - * @property {ChainDataFeeFeesOutputTxPattern} oversized - * @property {ChainDataFeeFeesOutputTxPattern} multiple + * @property {SeriesTree_OpReturn_Policy_OutputCount} outputCount + * @property {SeriesTree_OpReturn_Policy_DataBytes} dataBytes + * @property {SeriesTree_OpReturn_Policy_TxCount} txCount + * @property {SeriesTree_OpReturn_Policy_TxVsize} txVsize + * @property {SeriesTree_OpReturn_Policy_Fees} fees + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_Policy_OutputCount + * @property {AverageBlockCumulativeSumPattern} preV30Standard + * @property {AverageBlockCumulativeSumPattern} preV30Nonstandard + * @property {AverageBlockCumulativeSumPattern} oversized + * @property {AverageBlockCumulativeSumPattern} multiple + * @property {SeriesPattern18<[StoredU64; 4]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_Policy_DataBytes + * @property {AverageBlockChainCumulativeDataSumPattern} preV30Standard + * @property {AverageBlockChainCumulativeDataSumPattern} preV30Nonstandard + * @property {AverageBlockChainCumulativeDataSumPattern} oversized + * @property {AverageBlockChainCumulativeDataSumPattern} multiple + * @property {SeriesPattern18<[Bytes; 4]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_Policy_TxCount + * @property {AverageBlockCumulativeSumPattern} preV30Standard + * @property {AverageBlockCumulativeSumPattern} preV30Nonstandard + * @property {AverageBlockCumulativeSumPattern} oversized + * @property {AverageBlockCumulativeSumPattern} multiple + * @property {SeriesPattern18<[StoredU64; 4]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_Policy_TxVsize + * @property {AverageBlockCumulativeSumPattern} preV30Standard + * @property {AverageBlockCumulativeSumPattern} preV30Nonstandard + * @property {AverageBlockCumulativeSumPattern} oversized + * @property {AverageBlockCumulativeSumPattern} multiple + * @property {SeriesPattern18<[VSize; 4]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_OpReturn_Policy_Fees + * @property {AverageBlockCumulativeFeeSumPattern} preV30Standard + * @property {AverageBlockCumulativeFeeSumPattern} preV30Nonstandard + * @property {AverageBlockCumulativeFeeSumPattern} oversized + * @property {AverageBlockCumulativeFeeSumPattern} multiple + * @property {SeriesPattern18<[Sats; 4]>} cumulative */ /** @@ -6691,29 +7154,246 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange - * @property {CoindaysDormancySupplyWakefulnessPattern} under1h - * @property {CoindaysDormancySupplyWakefulnessPattern} _1hTo1d - * @property {CoindaysDormancySupplyWakefulnessPattern} _1dTo1w - * @property {CoindaysDormancySupplyWakefulnessPattern} _1wTo1m - * @property {CoindaysDormancySupplyWakefulnessPattern} _1mTo2m - * @property {CoindaysDormancySupplyWakefulnessPattern} _2mTo3m - * @property {CoindaysDormancySupplyWakefulnessPattern} _3mTo4m - * @property {CoindaysDormancySupplyWakefulnessPattern} _4mTo5m - * @property {CoindaysDormancySupplyWakefulnessPattern} _5mTo6m - * @property {CoindaysDormancySupplyWakefulnessPattern} _6mTo9m - * @property {CoindaysDormancySupplyWakefulnessPattern} _9mTo1y - * @property {CoindaysDormancySupplyWakefulnessPattern} _1yTo18m - * @property {CoindaysDormancySupplyWakefulnessPattern} _18mTo2y - * @property {CoindaysDormancySupplyWakefulnessPattern} _2yTo3y - * @property {CoindaysDormancySupplyWakefulnessPattern} _3yTo4y - * @property {CoindaysDormancySupplyWakefulnessPattern} _4yTo5y - * @property {CoindaysDormancySupplyWakefulnessPattern} _5yTo6y - * @property {CoindaysDormancySupplyWakefulnessPattern} _6yTo7y - * @property {CoindaysDormancySupplyWakefulnessPattern} _7yTo8y - * @property {CoindaysDormancySupplyWakefulnessPattern} _8yTo10y - * @property {CoindaysDormancySupplyWakefulnessPattern} _10yTo12y - * @property {CoindaysDormancySupplyWakefulnessPattern} _12yTo15y - * @property {CoindaysDormancySupplyWakefulnessPattern} over15y + * @property {SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated} coindaysCreated + * @property {SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed} coindaysConsumed + * @property {SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored} coindaysStored + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Activity} activity + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Supply} supply + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated + * @property {AverageBlockCumulativeSumPattern} under1h + * @property {AverageBlockCumulativeSumPattern} _1hTo1d + * @property {AverageBlockCumulativeSumPattern} _1dTo1w + * @property {AverageBlockCumulativeSumPattern} _1wTo1m + * @property {AverageBlockCumulativeSumPattern} _1mTo2m + * @property {AverageBlockCumulativeSumPattern} _2mTo3m + * @property {AverageBlockCumulativeSumPattern} _3mTo4m + * @property {AverageBlockCumulativeSumPattern} _4mTo5m + * @property {AverageBlockCumulativeSumPattern} _5mTo6m + * @property {AverageBlockCumulativeSumPattern} _6mTo9m + * @property {AverageBlockCumulativeSumPattern} _9mTo1y + * @property {AverageBlockCumulativeSumPattern} _1yTo18m + * @property {AverageBlockCumulativeSumPattern} _18mTo2y + * @property {AverageBlockCumulativeSumPattern} _2yTo3y + * @property {AverageBlockCumulativeSumPattern} _3yTo4y + * @property {AverageBlockCumulativeSumPattern} _4yTo5y + * @property {AverageBlockCumulativeSumPattern} _5yTo6y + * @property {AverageBlockCumulativeSumPattern} _6yTo7y + * @property {AverageBlockCumulativeSumPattern} _7yTo8y + * @property {AverageBlockCumulativeSumPattern} _8yTo10y + * @property {AverageBlockCumulativeSumPattern} _10yTo12y + * @property {AverageBlockCumulativeSumPattern} _12yTo15y + * @property {AverageBlockCumulativeSumPattern} over15y + * @property {SeriesPattern18<[StoredF64; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed + * @property {AverageBlockCumulativeSumPattern} under1h + * @property {AverageBlockCumulativeSumPattern} _1hTo1d + * @property {AverageBlockCumulativeSumPattern} _1dTo1w + * @property {AverageBlockCumulativeSumPattern} _1wTo1m + * @property {AverageBlockCumulativeSumPattern} _1mTo2m + * @property {AverageBlockCumulativeSumPattern} _2mTo3m + * @property {AverageBlockCumulativeSumPattern} _3mTo4m + * @property {AverageBlockCumulativeSumPattern} _4mTo5m + * @property {AverageBlockCumulativeSumPattern} _5mTo6m + * @property {AverageBlockCumulativeSumPattern} _6mTo9m + * @property {AverageBlockCumulativeSumPattern} _9mTo1y + * @property {AverageBlockCumulativeSumPattern} _1yTo18m + * @property {AverageBlockCumulativeSumPattern} _18mTo2y + * @property {AverageBlockCumulativeSumPattern} _2yTo3y + * @property {AverageBlockCumulativeSumPattern} _3yTo4y + * @property {AverageBlockCumulativeSumPattern} _4yTo5y + * @property {AverageBlockCumulativeSumPattern} _5yTo6y + * @property {AverageBlockCumulativeSumPattern} _6yTo7y + * @property {AverageBlockCumulativeSumPattern} _7yTo8y + * @property {AverageBlockCumulativeSumPattern} _8yTo10y + * @property {AverageBlockCumulativeSumPattern} _10yTo12y + * @property {AverageBlockCumulativeSumPattern} _12yTo15y + * @property {AverageBlockCumulativeSumPattern} over15y + * @property {SeriesPattern18<[StoredF64; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored + * @property {AverageBlockCumulativeSumPattern} under1h + * @property {AverageBlockCumulativeSumPattern} _1hTo1d + * @property {AverageBlockCumulativeSumPattern} _1dTo1w + * @property {AverageBlockCumulativeSumPattern} _1wTo1m + * @property {AverageBlockCumulativeSumPattern} _1mTo2m + * @property {AverageBlockCumulativeSumPattern} _2mTo3m + * @property {AverageBlockCumulativeSumPattern} _3mTo4m + * @property {AverageBlockCumulativeSumPattern} _4mTo5m + * @property {AverageBlockCumulativeSumPattern} _5mTo6m + * @property {AverageBlockCumulativeSumPattern} _6mTo9m + * @property {AverageBlockCumulativeSumPattern} _9mTo1y + * @property {AverageBlockCumulativeSumPattern} _1yTo18m + * @property {AverageBlockCumulativeSumPattern} _18mTo2y + * @property {AverageBlockCumulativeSumPattern} _2yTo3y + * @property {AverageBlockCumulativeSumPattern} _3yTo4y + * @property {AverageBlockCumulativeSumPattern} _4yTo5y + * @property {AverageBlockCumulativeSumPattern} _5yTo6y + * @property {AverageBlockCumulativeSumPattern} _6yTo7y + * @property {AverageBlockCumulativeSumPattern} _7yTo8y + * @property {AverageBlockCumulativeSumPattern} _8yTo10y + * @property {AverageBlockCumulativeSumPattern} _10yTo12y + * @property {AverageBlockCumulativeSumPattern} _12yTo15y + * @property {AverageBlockCumulativeSumPattern} over15y + * @property {SeriesPattern18<[StoredF64; 23]>} cumulative + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Activity + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness} wakefulness + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy} dormancy + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy} wakefulnessToDormancy + * @property {SeriesPattern18<[StoredF64; 23]>} height + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness + * @property {SeriesPattern1} under1h + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} over15y + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy + * @property {SeriesPattern1} under1h + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} over15y + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy + * @property {SeriesPattern1} under1h + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} over15y + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Supply + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake} awake + * @property {SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant} dormant + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake + * @property {BtcCentsSatsUsdPattern} under1h + * @property {BtcCentsSatsUsdPattern} _1hTo1d + * @property {BtcCentsSatsUsdPattern} _1dTo1w + * @property {BtcCentsSatsUsdPattern} _1wTo1m + * @property {BtcCentsSatsUsdPattern} _1mTo2m + * @property {BtcCentsSatsUsdPattern} _2mTo3m + * @property {BtcCentsSatsUsdPattern} _3mTo4m + * @property {BtcCentsSatsUsdPattern} _4mTo5m + * @property {BtcCentsSatsUsdPattern} _5mTo6m + * @property {BtcCentsSatsUsdPattern} _6mTo9m + * @property {BtcCentsSatsUsdPattern} _9mTo1y + * @property {BtcCentsSatsUsdPattern} _1yTo18m + * @property {BtcCentsSatsUsdPattern} _18mTo2y + * @property {BtcCentsSatsUsdPattern} _2yTo3y + * @property {BtcCentsSatsUsdPattern} _3yTo4y + * @property {BtcCentsSatsUsdPattern} _4yTo5y + * @property {BtcCentsSatsUsdPattern} _5yTo6y + * @property {BtcCentsSatsUsdPattern} _6yTo7y + * @property {BtcCentsSatsUsdPattern} _7yTo8y + * @property {BtcCentsSatsUsdPattern} _8yTo10y + * @property {BtcCentsSatsUsdPattern} _10yTo12y + * @property {BtcCentsSatsUsdPattern} _12yTo15y + * @property {BtcCentsSatsUsdPattern} over15y + * @property {SeriesPattern18<[Sats; 23]>} height + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant + * @property {BtcCentsSatsUsdPattern} under1h + * @property {BtcCentsSatsUsdPattern} _1hTo1d + * @property {BtcCentsSatsUsdPattern} _1dTo1w + * @property {BtcCentsSatsUsdPattern} _1wTo1m + * @property {BtcCentsSatsUsdPattern} _1mTo2m + * @property {BtcCentsSatsUsdPattern} _2mTo3m + * @property {BtcCentsSatsUsdPattern} _3mTo4m + * @property {BtcCentsSatsUsdPattern} _4mTo5m + * @property {BtcCentsSatsUsdPattern} _5mTo6m + * @property {BtcCentsSatsUsdPattern} _6mTo9m + * @property {BtcCentsSatsUsdPattern} _9mTo1y + * @property {BtcCentsSatsUsdPattern} _1yTo18m + * @property {BtcCentsSatsUsdPattern} _18mTo2y + * @property {BtcCentsSatsUsdPattern} _2yTo3y + * @property {BtcCentsSatsUsdPattern} _3yTo4y + * @property {BtcCentsSatsUsdPattern} _4yTo5y + * @property {BtcCentsSatsUsdPattern} _5yTo6y + * @property {BtcCentsSatsUsdPattern} _6yTo7y + * @property {BtcCentsSatsUsdPattern} _7yTo8y + * @property {BtcCentsSatsUsdPattern} _8yTo10y + * @property {BtcCentsSatsUsdPattern} _10yTo12y + * @property {BtcCentsSatsUsdPattern} _12yTo15y + * @property {BtcCentsSatsUsdPattern} over15y + * @property {SeriesPattern18<[Sats; 23]>} height */ /** @@ -6844,29 +7524,155 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange - * @property {MobilitySpendingSupplyPattern} under1h - * @property {MobilitySpendingSupplyPattern} _1hTo1d - * @property {MobilitySpendingSupplyPattern} _1dTo1w - * @property {MobilitySpendingSupplyPattern} _1wTo1m - * @property {MobilitySpendingSupplyPattern} _1mTo2m - * @property {MobilitySpendingSupplyPattern} _2mTo3m - * @property {MobilitySpendingSupplyPattern} _3mTo4m - * @property {MobilitySpendingSupplyPattern} _4mTo5m - * @property {MobilitySpendingSupplyPattern} _5mTo6m - * @property {MobilitySpendingSupplyPattern} _6mTo9m - * @property {MobilitySpendingSupplyPattern} _9mTo1y - * @property {MobilitySpendingSupplyPattern} _1yTo18m - * @property {MobilitySpendingSupplyPattern} _18mTo2y - * @property {MobilitySpendingSupplyPattern} _2yTo3y - * @property {MobilitySpendingSupplyPattern} _3yTo4y - * @property {MobilitySpendingSupplyPattern} _4yTo5y - * @property {MobilitySpendingSupplyPattern} _5yTo6y - * @property {MobilitySpendingSupplyPattern} _6yTo7y - * @property {MobilitySpendingSupplyPattern} _7yTo8y - * @property {MobilitySpendingSupplyPattern} _8yTo10y - * @property {MobilitySpendingSupplyPattern} _10yTo12y - * @property {MobilitySpendingSupplyPattern} _12yTo15y - * @property {MobilitySpendingSupplyPattern} over15y + * @property {SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate} spendingRate + * @property {SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure} spendingExposure + * @property {SeriesTree_Frameworks_Coinflow_AgeRange_Supply} supply + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate + * @property {SeriesPattern1} under1h + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} over15y + * @property {SeriesPattern18<[StoredF64; 23]>} height + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure + * @property {SeriesPattern1} under1h + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} over15y + * @property {SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility} mobility + * @property {SeriesPattern18<[StoredF64; 23]>} height + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility + * @property {SeriesPattern1} under1h + * @property {SeriesPattern1} _1hTo1d + * @property {SeriesPattern1} _1dTo1w + * @property {SeriesPattern1} _1wTo1m + * @property {SeriesPattern1} _1mTo2m + * @property {SeriesPattern1} _2mTo3m + * @property {SeriesPattern1} _3mTo4m + * @property {SeriesPattern1} _4mTo5m + * @property {SeriesPattern1} _5mTo6m + * @property {SeriesPattern1} _6mTo9m + * @property {SeriesPattern1} _9mTo1y + * @property {SeriesPattern1} _1yTo18m + * @property {SeriesPattern1} _18mTo2y + * @property {SeriesPattern1} _2yTo3y + * @property {SeriesPattern1} _3yTo4y + * @property {SeriesPattern1} _4yTo5y + * @property {SeriesPattern1} _5yTo6y + * @property {SeriesPattern1} _6yTo7y + * @property {SeriesPattern1} _7yTo8y + * @property {SeriesPattern1} _8yTo10y + * @property {SeriesPattern1} _10yTo12y + * @property {SeriesPattern1} _12yTo15y + * @property {SeriesPattern1} over15y + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange_Supply + * @property {SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile} mobile + * @property {SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile} immobile + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile + * @property {BtcCentsSatsUsdPattern} under1h + * @property {BtcCentsSatsUsdPattern} _1hTo1d + * @property {BtcCentsSatsUsdPattern} _1dTo1w + * @property {BtcCentsSatsUsdPattern} _1wTo1m + * @property {BtcCentsSatsUsdPattern} _1mTo2m + * @property {BtcCentsSatsUsdPattern} _2mTo3m + * @property {BtcCentsSatsUsdPattern} _3mTo4m + * @property {BtcCentsSatsUsdPattern} _4mTo5m + * @property {BtcCentsSatsUsdPattern} _5mTo6m + * @property {BtcCentsSatsUsdPattern} _6mTo9m + * @property {BtcCentsSatsUsdPattern} _9mTo1y + * @property {BtcCentsSatsUsdPattern} _1yTo18m + * @property {BtcCentsSatsUsdPattern} _18mTo2y + * @property {BtcCentsSatsUsdPattern} _2yTo3y + * @property {BtcCentsSatsUsdPattern} _3yTo4y + * @property {BtcCentsSatsUsdPattern} _4yTo5y + * @property {BtcCentsSatsUsdPattern} _5yTo6y + * @property {BtcCentsSatsUsdPattern} _6yTo7y + * @property {BtcCentsSatsUsdPattern} _7yTo8y + * @property {BtcCentsSatsUsdPattern} _8yTo10y + * @property {BtcCentsSatsUsdPattern} _10yTo12y + * @property {BtcCentsSatsUsdPattern} _12yTo15y + * @property {BtcCentsSatsUsdPattern} over15y + * @property {SeriesPattern18<[Sats; 23]>} height + */ + +/** + * @typedef {Object} SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile + * @property {BtcCentsSatsUsdPattern} under1h + * @property {BtcCentsSatsUsdPattern} _1hTo1d + * @property {BtcCentsSatsUsdPattern} _1dTo1w + * @property {BtcCentsSatsUsdPattern} _1wTo1m + * @property {BtcCentsSatsUsdPattern} _1mTo2m + * @property {BtcCentsSatsUsdPattern} _2mTo3m + * @property {BtcCentsSatsUsdPattern} _3mTo4m + * @property {BtcCentsSatsUsdPattern} _4mTo5m + * @property {BtcCentsSatsUsdPattern} _5mTo6m + * @property {BtcCentsSatsUsdPattern} _6mTo9m + * @property {BtcCentsSatsUsdPattern} _9mTo1y + * @property {BtcCentsSatsUsdPattern} _1yTo18m + * @property {BtcCentsSatsUsdPattern} _18mTo2y + * @property {BtcCentsSatsUsdPattern} _2yTo3y + * @property {BtcCentsSatsUsdPattern} _3yTo4y + * @property {BtcCentsSatsUsdPattern} _4yTo5y + * @property {BtcCentsSatsUsdPattern} _5yTo6y + * @property {BtcCentsSatsUsdPattern} _6yTo7y + * @property {BtcCentsSatsUsdPattern} _7yTo8y + * @property {BtcCentsSatsUsdPattern} _8yTo10y + * @property {BtcCentsSatsUsdPattern} _10yTo12y + * @property {BtcCentsSatsUsdPattern} _12yTo15y + * @property {BtcCentsSatsUsdPattern} over15y + * @property {SeriesPattern18<[Sats; 23]>} height */ /** @@ -6963,36 +7769,36 @@ function createUnspentPattern(client, acc) { * @typedef {Object} SeriesTree_Models_RarityMeter * @property {SeriesTree_Models_RarityMeter_Components} components * @property {SeriesTree_Models_RarityMeter_Extremes} extremes - * @property {IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} full - * @property {IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} local - * @property {IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} cycle + * @property {HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} full + * @property {HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} local + * @property {HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern} cycle */ /** * @typedef {Object} SeriesTree_Models_RarityMeter_Components - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} realizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} capitalizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} sthRealizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} sthCapitalizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} lthRealizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} lthCapitalizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} over6mRealizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} over4mRealizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} under4mRealizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} under6mRealizedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} vaultedPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} activePrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} trueMarketMeanPrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} cointimePrice - * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern} coinflowPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} realizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} capitalizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} sthRealizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} sthCapitalizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} lthRealizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} lthCapitalizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} over6mRealizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} over4mRealizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} under4mRealizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} under6mRealizedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} vaultedPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} activePrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} trueMarketMeanPrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} cointimePrice + * @property {Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern} coinflowPrice */ /** * @typedef {Object} SeriesTree_Models_RarityMeter_Extremes * @property {SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss} coinsInLoss - * @property {RankTailThresholdPattern} profitTaking - * @property {RankTailThresholdPattern} capitulation - * @property {RankTailThresholdPattern} peakRegret + * @property {HeightRankTailThresholdPattern} profitTaking + * @property {HeightRankTailThresholdPattern} capitulation + * @property {HeightRankTailThresholdPattern} peakRegret * @property {SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion} sellerExhaustion */ @@ -7001,6 +7807,7 @@ function createUnspentPattern(client, acc) { * @property {SeriesPattern1} thresholdPct01 * @property {SeriesPattern1} thresholdPct005 * @property {SeriesPattern1} thresholdPct0025 + * @property {SeriesPattern18<[Bitcoin; 3]>} height * @property {PercentPpmRatioPattern2} tail * @property {SeriesPattern1} rank */ @@ -7010,6 +7817,7 @@ function createUnspentPattern(client, acc) { * @property {SeriesPattern1} thresholdPct01 * @property {SeriesPattern1} thresholdPct005 * @property {SeriesPattern1} thresholdPct0025 + * @property {SeriesPattern18<[StoredF32; 3]>} height * @property {PercentPpmRatioPattern2} tail * @property {SeriesPattern1} rank */ @@ -7547,6 +8355,7 @@ function createUnspentPattern(client, acc) { * @property {CentsPpmRatioSatsUsdPattern} _2y * @property {CentsPpmRatioSatsUsdPattern} _200w * @property {CentsPpmRatioSatsUsdPattern} _4y + * @property {SeriesPattern18<[Cents; 16]>} height */ /** @@ -7848,6 +8657,8 @@ function createUnspentPattern(client, acc) { * @property {SeriesTree_Cohorts_Utxo_Type} type * @property {SeriesTree_Cohorts_Utxo_Profitability} profitability * @property {SeriesTree_Cohorts_Utxo_Matured} matured + * @property {SeriesPattern18<[Sats; 23]>} cumulativeMaturedSats + * @property {SeriesPattern18<[Cents; 23]>} cumulativeMaturedCents */ /** @@ -7872,7 +8683,7 @@ function createUnspentPattern(client, acc) { * @property {AverageBlockCumulativeInSumPattern} transferVolume * @property {AverageBlockCumulativeSumPattern} coindaysDestroyed * @property {SeriesPattern1} coinyearsDestroyed - * @property {_1m1w1y24hPattern} dormancy + * @property {_1m1w1y24hHeightPattern} dormancy */ /** @@ -7885,22 +8696,22 @@ function createUnspentPattern(client, acc) { * @property {BlockChangeCumulativeDeltaSumPattern} netPnl * @property {SeriesTree_Cohorts_Utxo_All_Realized_Sopr} sopr * @property {BlockCumulativeSumPattern} grossPnl - * @property {_1m1w1y24hPattern8} sellSideRiskRatio + * @property {_1m1w1y24hHeightPattern3} sellSideRiskRatio * @property {BlockCumulativeSumPattern} peakRegret * @property {PricePattern} capitalized - * @property {_1m1w1y24hPattern} profitToLossRatio + * @property {_1m1w1y24hHeightPattern2} profitToLossRatio */ /** * @typedef {Object} SeriesTree_Cohorts_Utxo_All_Realized_Sopr * @property {AverageBlockCumulativeSumPattern} valueDestroyed - * @property {_1m1w1y24hPattern} ratio + * @property {_1m1w1y24hHeightPattern4} ratio * @property {SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted} adjusted */ /** * @typedef {Object} SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted - * @property {_1m1w1y24hPattern} ratio + * @property {_1m1w1y24hHeightPattern2} ratio * @property {AverageBlockCumulativeSumPattern} transferVolume * @property {AverageBlockCumulativeSumPattern} valueDestroyed */ @@ -7911,8 +8722,8 @@ function createUnspentPattern(client, acc) { * @property {PerPattern} inLoss * @property {CentsSatsUsdPattern} min * @property {CentsSatsUsdPattern} max - * @property {Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perCoin - * @property {Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perDollar + * @property {HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perCoin + * @property {HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} perDollar * @property {PercentPpmRatioPattern2} supplyDensity */ @@ -7992,16 +8803,16 @@ function createUnspentPattern(client, acc) { * @property {BlockChangeCumulativeDeltaSumPattern} netPnl * @property {SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr} sopr * @property {BlockCumulativeSumPattern} grossPnl - * @property {_1m1w1y24hPattern8} sellSideRiskRatio + * @property {_1m1w1y24hHeightPattern3} sellSideRiskRatio * @property {BlockCumulativeSumPattern} peakRegret * @property {PricePattern} capitalized - * @property {_1m1w1y24hPattern} profitToLossRatio + * @property {_1m1w1y24hHeightPattern2} profitToLossRatio */ /** * @typedef {Object} SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr * @property {AverageBlockCumulativeSumPattern} valueDestroyed - * @property {_1m1w1y24hPattern} ratio + * @property {_1m1w1y24hHeightPattern4} ratio */ /** @@ -8189,6 +9000,13 @@ function createUnspentPattern(client, acc) { * @property {SeriesTree_Cohorts_Utxo_Profitability_Range} range * @property {SeriesTree_Cohorts_Utxo_Profitability_Profit} profit * @property {SeriesTree_Cohorts_Utxo_Profitability_Loss} loss + * @property {SeriesPattern18} allSupplySats + * @property {SeriesPattern18} sthSupplySats + * @property {SeriesPattern18} allRealizedCap + * @property {SeriesPattern18} sthRealizedCap + * @property {SeriesPattern18} allUnrealizedPnl + * @property {SeriesPattern18} sthUnrealizedPnl + * @property {SeriesPattern18} nupl */ /** @@ -9860,6 +10678,7 @@ class BrkClient extends BrkClientBase { p2a: createAverageBlockCumulativeSumPattern(this, 'p2a_prevout_count'), unknown: createAverageBlockCumulativeSumPattern(this, 'unknown_outputs_prevout_count'), empty: createAverageBlockCumulativeSumPattern(this, 'empty_outputs_prevout_count'), + height: createSeriesPattern18(this, 'prevout_count_by_type'), }, inputShare: { p2pk65: create_1m1w1y24hPercentPpmRatioPattern(this, 'p2pk65_prevout_share'), @@ -9887,6 +10706,7 @@ class BrkClient extends BrkClientBase { p2a: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2a_prevout'), unknown: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_unknown_outputs_prevout'), empty: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_empty_outputs_prevout'), + cumulative: createSeriesPattern18(this, 'tx_count_with_prevout_by_type_cumulative'), }, txShare: createEmptyP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2(this, 'tx_share_with'), }, @@ -9923,6 +10743,7 @@ class BrkClient extends BrkClientBase { unknown: createAverageBlockCumulativeSumPattern(this, 'unknown_outputs_output_count'), empty: createAverageBlockCumulativeSumPattern(this, 'empty_outputs_output_count'), opReturn: createAverageBlockCumulativeSumPattern(this, 'op_return_output_count'), + height: createSeriesPattern18(this, 'output_count_by_type'), }, spendableOutputCount: createAverageBlockCumulativeSumPattern(this, 'spendable_output_count'), outputShare: { @@ -9939,7 +10760,22 @@ class BrkClient extends BrkClientBase { empty: create_1m1w1y24hPercentPpmRatioPattern(this, 'empty_outputs_output_share'), opReturn: create_1m1w1y24hPercentPpmRatioPattern(this, 'op_return_output_share'), }, - txCount: createAllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern(this, 'tx_count'), + txCount: { + all: createAverageBlockCumulativeSumPattern(this, 'tx_count_bis'), + p2pk65: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2pk65_output'), + p2pk33: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2pk33_output'), + p2pkh: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2pkh_output'), + p2ms: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2ms_output'), + p2sh: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2sh_output'), + p2wpkh: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2wpkh_output'), + p2wsh: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2wsh_output'), + p2tr: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2tr_output'), + p2a: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_p2a_output'), + unknown: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_unknown_outputs_output'), + empty: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_empty_outputs_output'), + opReturn: createAverageBlockCumulativeSumPattern(this, 'tx_count_with_op_return_output'), + cumulative: createSeriesPattern18(this, 'tx_count_with_output_by_type_cumulative'), + }, txShare: createEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2(this, 'tx_share_with'), }, value: { @@ -9997,8 +10833,30 @@ class BrkClient extends BrkClientBase { funded: createSeriesPattern34(this, 'funded_addr_data'), empty: createSeriesPattern35(this, 'empty_addr_data'), }, - funded: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(this, 'addr_count'), - empty: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(this, 'empty_addr_count'), + funded: { + all: createSeriesPattern1(this, 'addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_addr_count'), + height: createSeriesPattern18(this, 'addr_count_by_type'), + }, + empty: { + all: createSeriesPattern1(this, 'empty_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_empty_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_empty_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_empty_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_empty_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_empty_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_empty_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_empty_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_empty_addr_count'), + height: createSeriesPattern18(this, 'empty_addr_count_by_type'), + }, activity: { all: { reactivated: create_1m1w1y24hBlockPattern(this, 'reactivated_addrs'), @@ -10016,16 +10874,84 @@ class BrkClient extends BrkClientBase { p2tr: createActiveBidirectionalReactivatedReceivingSendingPattern(this, 'p2tr'), p2a: createActiveBidirectionalReactivatedReceivingSendingPattern(this, 'p2a'), }, - total: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(this, 'total_addr_count'), - new: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'new_addr_count'), + total: { + all: createSeriesPattern1(this, 'total_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_total_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_total_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_total_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_total_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_total_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_total_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_total_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_total_addr_count'), + height: createSeriesPattern18(this, 'total_addr_count_by_type'), + }, + new: { + all: createAverageBlockCumulativeSumPattern(this, 'new_addr_count'), + p2pk65: createAverageBlockCumulativeSumPattern(this, 'p2pk65_new_addr_count'), + p2pk33: createAverageBlockCumulativeSumPattern(this, 'p2pk33_new_addr_count'), + p2pkh: createAverageBlockCumulativeSumPattern(this, 'p2pkh_new_addr_count'), + p2sh: createAverageBlockCumulativeSumPattern(this, 'p2sh_new_addr_count'), + p2wpkh: createAverageBlockCumulativeSumPattern(this, 'p2wpkh_new_addr_count'), + p2wsh: createAverageBlockCumulativeSumPattern(this, 'p2wsh_new_addr_count'), + p2tr: createAverageBlockCumulativeSumPattern(this, 'p2tr_new_addr_count'), + p2a: createAverageBlockCumulativeSumPattern(this, 'p2a_new_addr_count'), + }, reused: { - count: createFundedTotalPattern(this, 'reused_addr_count'), + count: { + funded: { + all: createSeriesPattern1(this, 'reused_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_reused_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_reused_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_reused_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_reused_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_reused_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_reused_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_reused_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_reused_addr_count'), + height: createSeriesPattern18(this, 'reused_addr_count_by_type'), + }, + total: { + all: createSeriesPattern1(this, 'total_reused_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_total_reused_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_total_reused_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_total_reused_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_total_reused_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_total_reused_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_total_reused_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_total_reused_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_total_reused_addr_count'), + height: createSeriesPattern18(this, 'total_reused_addr_count_by_type'), + }, + }, events: { - outputToReusedAddrCount: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'output_to_reused_addr_count'), - outputToReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(this, 'output_to_reused_addr_share'), + outputToReusedAddrCount: { + all: createAverageBlockCumulativeSumPattern(this, 'output_to_reused_addr_count'), + p2pk65: createAverageBlockCumulativeSumPattern(this, 'p2pk65_output_to_reused_addr_count'), + p2pk33: createAverageBlockCumulativeSumPattern(this, 'p2pk33_output_to_reused_addr_count'), + p2pkh: createAverageBlockCumulativeSumPattern(this, 'p2pkh_output_to_reused_addr_count'), + p2sh: createAverageBlockCumulativeSumPattern(this, 'p2sh_output_to_reused_addr_count'), + p2wpkh: createAverageBlockCumulativeSumPattern(this, 'p2wpkh_output_to_reused_addr_count'), + p2wsh: createAverageBlockCumulativeSumPattern(this, 'p2wsh_output_to_reused_addr_count'), + p2tr: createAverageBlockCumulativeSumPattern(this, 'p2tr_output_to_reused_addr_count'), + p2a: createAverageBlockCumulativeSumPattern(this, 'p2a_output_to_reused_addr_count'), + cumulative: createSeriesPattern18(this, 'output_to_reused_addr_count_by_type_cumulative'), + }, + outputToReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'output_to_reused_addr_share'), spendableOutputToReusedAddrShare: create_1m1w1y24hPercentPpmRatioPattern(this, 'spendable_output_to_reused_addr_share'), - inputFromReusedAddrCount: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'input_from_reused_addr_count'), - inputFromReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(this, 'input_from_reused_addr_share'), + inputFromReusedAddrCount: { + all: createAverageBlockCumulativeSumPattern(this, 'input_from_reused_addr_count'), + p2pk65: createAverageBlockCumulativeSumPattern(this, 'p2pk65_input_from_reused_addr_count'), + p2pk33: createAverageBlockCumulativeSumPattern(this, 'p2pk33_input_from_reused_addr_count'), + p2pkh: createAverageBlockCumulativeSumPattern(this, 'p2pkh_input_from_reused_addr_count'), + p2sh: createAverageBlockCumulativeSumPattern(this, 'p2sh_input_from_reused_addr_count'), + p2wpkh: createAverageBlockCumulativeSumPattern(this, 'p2wpkh_input_from_reused_addr_count'), + p2wsh: createAverageBlockCumulativeSumPattern(this, 'p2wsh_input_from_reused_addr_count'), + p2tr: createAverageBlockCumulativeSumPattern(this, 'p2tr_input_from_reused_addr_count'), + p2a: createAverageBlockCumulativeSumPattern(this, 'p2a_input_from_reused_addr_count'), + cumulative: createSeriesPattern18(this, 'input_from_reused_addr_count_by_type_cumulative'), + }, + inputFromReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'input_from_reused_addr_share'), activeReusedAddrCount: create_1m1w1y24hBlockPattern(this, 'active_reused_addr_count'), activeReusedAddrShare: create_1m1w1y24hBlockPattern2(this, 'active_reused_addr_share'), }, @@ -10039,17 +10965,65 @@ class BrkClient extends BrkClientBase { p2wsh: createBtcCentsSatsUsdPattern(this, 'p2wsh_reused_addr_supply'), p2tr: createBtcCentsSatsUsdPattern(this, 'p2tr_reused_addr_supply'), p2a: createBtcCentsSatsUsdPattern(this, 'p2a_reused_addr_supply'), - share: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(this, 'reused_addr_supply_share'), + height: createSeriesPattern18(this, 'reused_addr_supply_sats_by_type'), + share: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(this, 'reused_addr_supply_share'), }, }, respent: { - count: createFundedTotalPattern(this, 'respent_addr_count'), + count: { + funded: { + all: createSeriesPattern1(this, 'respent_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_respent_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_respent_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_respent_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_respent_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_respent_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_respent_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_respent_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_respent_addr_count'), + height: createSeriesPattern18(this, 'respent_addr_count_by_type'), + }, + total: { + all: createSeriesPattern1(this, 'total_respent_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_total_respent_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_total_respent_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_total_respent_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_total_respent_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_total_respent_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_total_respent_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_total_respent_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_total_respent_addr_count'), + height: createSeriesPattern18(this, 'total_respent_addr_count_by_type'), + }, + }, events: { - outputToReusedAddrCount: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'output_to_respent_addr_count'), - outputToReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(this, 'output_to_respent_addr_share'), + outputToReusedAddrCount: { + all: createAverageBlockCumulativeSumPattern(this, 'output_to_respent_addr_count'), + p2pk65: createAverageBlockCumulativeSumPattern(this, 'p2pk65_output_to_respent_addr_count'), + p2pk33: createAverageBlockCumulativeSumPattern(this, 'p2pk33_output_to_respent_addr_count'), + p2pkh: createAverageBlockCumulativeSumPattern(this, 'p2pkh_output_to_respent_addr_count'), + p2sh: createAverageBlockCumulativeSumPattern(this, 'p2sh_output_to_respent_addr_count'), + p2wpkh: createAverageBlockCumulativeSumPattern(this, 'p2wpkh_output_to_respent_addr_count'), + p2wsh: createAverageBlockCumulativeSumPattern(this, 'p2wsh_output_to_respent_addr_count'), + p2tr: createAverageBlockCumulativeSumPattern(this, 'p2tr_output_to_respent_addr_count'), + p2a: createAverageBlockCumulativeSumPattern(this, 'p2a_output_to_respent_addr_count'), + cumulative: createSeriesPattern18(this, 'output_to_respent_addr_count_by_type_cumulative'), + }, + outputToReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'output_to_respent_addr_share'), spendableOutputToReusedAddrShare: create_1m1w1y24hPercentPpmRatioPattern(this, 'spendable_output_to_respent_addr_share'), - inputFromReusedAddrCount: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'input_from_respent_addr_count'), - inputFromReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(this, 'input_from_respent_addr_share'), + inputFromReusedAddrCount: { + all: createAverageBlockCumulativeSumPattern(this, 'input_from_respent_addr_count'), + p2pk65: createAverageBlockCumulativeSumPattern(this, 'p2pk65_input_from_respent_addr_count'), + p2pk33: createAverageBlockCumulativeSumPattern(this, 'p2pk33_input_from_respent_addr_count'), + p2pkh: createAverageBlockCumulativeSumPattern(this, 'p2pkh_input_from_respent_addr_count'), + p2sh: createAverageBlockCumulativeSumPattern(this, 'p2sh_input_from_respent_addr_count'), + p2wpkh: createAverageBlockCumulativeSumPattern(this, 'p2wpkh_input_from_respent_addr_count'), + p2wsh: createAverageBlockCumulativeSumPattern(this, 'p2wsh_input_from_respent_addr_count'), + p2tr: createAverageBlockCumulativeSumPattern(this, 'p2tr_input_from_respent_addr_count'), + p2a: createAverageBlockCumulativeSumPattern(this, 'p2a_input_from_respent_addr_count'), + cumulative: createSeriesPattern18(this, 'input_from_respent_addr_count_by_type_cumulative'), + }, + inputFromReusedAddrShare: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(this, 'input_from_respent_addr_share'), activeReusedAddrCount: create_1m1w1y24hBlockPattern(this, 'active_respent_addr_count'), activeReusedAddrShare: create_1m1w1y24hBlockPattern2(this, 'active_respent_addr_share'), }, @@ -10063,11 +11037,37 @@ class BrkClient extends BrkClientBase { p2wsh: createBtcCentsSatsUsdPattern(this, 'p2wsh_respent_addr_supply'), p2tr: createBtcCentsSatsUsdPattern(this, 'p2tr_respent_addr_supply'), p2a: createBtcCentsSatsUsdPattern(this, 'p2a_respent_addr_supply'), - share: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(this, 'respent_addr_supply_share'), + height: createSeriesPattern18(this, 'respent_addr_supply_sats_by_type'), + share: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(this, 'respent_addr_supply_share'), }, }, exposed: { - count: createFundedTotalPattern(this, 'exposed_addr_count'), + count: { + funded: { + all: createSeriesPattern1(this, 'exposed_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_exposed_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_exposed_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_exposed_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_exposed_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_exposed_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_exposed_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_exposed_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_exposed_addr_count'), + height: createSeriesPattern18(this, 'exposed_addr_count_by_type'), + }, + total: { + all: createSeriesPattern1(this, 'total_exposed_addr_count'), + p2pk65: createSeriesPattern1(this, 'p2pk65_total_exposed_addr_count'), + p2pk33: createSeriesPattern1(this, 'p2pk33_total_exposed_addr_count'), + p2pkh: createSeriesPattern1(this, 'p2pkh_total_exposed_addr_count'), + p2sh: createSeriesPattern1(this, 'p2sh_total_exposed_addr_count'), + p2wpkh: createSeriesPattern1(this, 'p2wpkh_total_exposed_addr_count'), + p2wsh: createSeriesPattern1(this, 'p2wsh_total_exposed_addr_count'), + p2tr: createSeriesPattern1(this, 'p2tr_total_exposed_addr_count'), + p2a: createSeriesPattern1(this, 'p2a_total_exposed_addr_count'), + height: createSeriesPattern18(this, 'total_exposed_addr_count_by_type'), + }, + }, supply: { all: createBtcCentsSatsUsdPattern(this, 'exposed_addr_supply'), p2pk65: createBtcCentsSatsUsdPattern(this, 'p2pk65_exposed_addr_supply'), @@ -10078,7 +11078,8 @@ class BrkClient extends BrkClientBase { p2wsh: createBtcCentsSatsUsdPattern(this, 'p2wsh_exposed_addr_supply'), p2tr: createBtcCentsSatsUsdPattern(this, 'p2tr_exposed_addr_supply'), p2a: createBtcCentsSatsUsdPattern(this, 'p2a_exposed_addr_supply'), - share: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(this, 'exposed_addr_supply_share'), + height: createSeriesPattern18(this, 'exposed_addr_supply_sats_by_type'), + share: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(this, 'exposed_addr_supply_share'), }, }, delta: { @@ -10138,35 +11139,173 @@ class BrkClient extends BrkClientBase { feeShare: create_1m1w1y24hPercentPpmRatioPattern(this, 'op_return_fee_share'), }, byKind: { - runes: createChainDataFeeFeesOutputTxPattern(this, 'op_return_runes'), - veriBlock: createChainDataFeeFeesOutputTxPattern(this, 'op_return_veri_block'), - omni: createChainDataFeeFeesOutputTxPattern(this, 'op_return_omni'), - stacks: createChainDataFeeFeesOutputTxPattern(this, 'op_return_stacks'), - blockstack: createChainDataFeeFeesOutputTxPattern(this, 'op_return_blockstack'), - colu: createChainDataFeeFeesOutputTxPattern(this, 'op_return_colu'), - openAssets: createChainDataFeeFeesOutputTxPattern(this, 'op_return_open_assets'), - komodo: createChainDataFeeFeesOutputTxPattern(this, 'op_return_komodo'), - coinSpark: createChainDataFeeFeesOutputTxPattern(this, 'op_return_coin_spark'), - poet: createChainDataFeeFeesOutputTxPattern(this, 'op_return_poet'), - docproof: createChainDataFeeFeesOutputTxPattern(this, 'op_return_docproof'), - openTimestamps: createChainDataFeeFeesOutputTxPattern(this, 'op_return_open_timestamps'), - factom: createChainDataFeeFeesOutputTxPattern(this, 'op_return_factom'), - eternityWall: createChainDataFeeFeesOutputTxPattern(this, 'op_return_eternity_wall'), - memo: createChainDataFeeFeesOutputTxPattern(this, 'op_return_memo'), - bitproof: createChainDataFeeFeesOutputTxPattern(this, 'op_return_bitproof'), - ascribe: createChainDataFeeFeesOutputTxPattern(this, 'op_return_ascribe'), - stampery: createChainDataFeeFeesOutputTxPattern(this, 'op_return_stampery'), - epobc: createChainDataFeeFeesOutputTxPattern(this, 'op_return_epobc'), - bareHash: createChainDataFeeFeesOutputTxPattern(this, 'op_return_bare_hash'), - text: createChainDataFeeFeesOutputTxPattern(this, 'op_return_text'), - empty: createChainDataFeeFeesOutputTxPattern(this, 'op_return_empty'), - unknown: createChainDataFeeFeesOutputTxPattern(this, 'op_return_unknown'), + outputCount: { + runes: createAverageBlockCumulativeSumPattern(this, 'op_return_runes_output_count'), + veriBlock: createAverageBlockCumulativeSumPattern(this, 'op_return_veri_block_output_count'), + omni: createAverageBlockCumulativeSumPattern(this, 'op_return_omni_output_count'), + stacks: createAverageBlockCumulativeSumPattern(this, 'op_return_stacks_output_count'), + blockstack: createAverageBlockCumulativeSumPattern(this, 'op_return_blockstack_output_count'), + colu: createAverageBlockCumulativeSumPattern(this, 'op_return_colu_output_count'), + openAssets: createAverageBlockCumulativeSumPattern(this, 'op_return_open_assets_output_count'), + komodo: createAverageBlockCumulativeSumPattern(this, 'op_return_komodo_output_count'), + coinSpark: createAverageBlockCumulativeSumPattern(this, 'op_return_coin_spark_output_count'), + poet: createAverageBlockCumulativeSumPattern(this, 'op_return_poet_output_count'), + docproof: createAverageBlockCumulativeSumPattern(this, 'op_return_docproof_output_count'), + openTimestamps: createAverageBlockCumulativeSumPattern(this, 'op_return_open_timestamps_output_count'), + factom: createAverageBlockCumulativeSumPattern(this, 'op_return_factom_output_count'), + eternityWall: createAverageBlockCumulativeSumPattern(this, 'op_return_eternity_wall_output_count'), + memo: createAverageBlockCumulativeSumPattern(this, 'op_return_memo_output_count'), + bitproof: createAverageBlockCumulativeSumPattern(this, 'op_return_bitproof_output_count'), + ascribe: createAverageBlockCumulativeSumPattern(this, 'op_return_ascribe_output_count'), + stampery: createAverageBlockCumulativeSumPattern(this, 'op_return_stampery_output_count'), + epobc: createAverageBlockCumulativeSumPattern(this, 'op_return_epobc_output_count'), + bareHash: createAverageBlockCumulativeSumPattern(this, 'op_return_bare_hash_output_count'), + text: createAverageBlockCumulativeSumPattern(this, 'op_return_text_output_count'), + empty: createAverageBlockCumulativeSumPattern(this, 'op_return_empty_output_count'), + unknown: createAverageBlockCumulativeSumPattern(this, 'op_return_unknown_output_count'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_by_kind_output_count'), + }, + dataBytes: { + runes: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_runes'), + veriBlock: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_veri_block'), + omni: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_omni'), + stacks: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_stacks'), + blockstack: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_blockstack'), + colu: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_colu'), + openAssets: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_open_assets'), + komodo: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_komodo'), + coinSpark: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_coin_spark'), + poet: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_poet'), + docproof: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_docproof'), + openTimestamps: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_open_timestamps'), + factom: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_factom'), + eternityWall: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_eternity_wall'), + memo: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_memo'), + bitproof: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_bitproof'), + ascribe: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_ascribe'), + stampery: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_stampery'), + epobc: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_epobc'), + bareHash: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_bare_hash'), + text: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_text'), + empty: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_empty'), + unknown: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_unknown'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_by_kind_data_bytes'), + }, + txCount: { + runes: createAverageBlockCumulativeSumPattern(this, 'op_return_runes_tx_count'), + veriBlock: createAverageBlockCumulativeSumPattern(this, 'op_return_veri_block_tx_count'), + omni: createAverageBlockCumulativeSumPattern(this, 'op_return_omni_tx_count'), + stacks: createAverageBlockCumulativeSumPattern(this, 'op_return_stacks_tx_count'), + blockstack: createAverageBlockCumulativeSumPattern(this, 'op_return_blockstack_tx_count'), + colu: createAverageBlockCumulativeSumPattern(this, 'op_return_colu_tx_count'), + openAssets: createAverageBlockCumulativeSumPattern(this, 'op_return_open_assets_tx_count'), + komodo: createAverageBlockCumulativeSumPattern(this, 'op_return_komodo_tx_count'), + coinSpark: createAverageBlockCumulativeSumPattern(this, 'op_return_coin_spark_tx_count'), + poet: createAverageBlockCumulativeSumPattern(this, 'op_return_poet_tx_count'), + docproof: createAverageBlockCumulativeSumPattern(this, 'op_return_docproof_tx_count'), + openTimestamps: createAverageBlockCumulativeSumPattern(this, 'op_return_open_timestamps_tx_count'), + factom: createAverageBlockCumulativeSumPattern(this, 'op_return_factom_tx_count'), + eternityWall: createAverageBlockCumulativeSumPattern(this, 'op_return_eternity_wall_tx_count'), + memo: createAverageBlockCumulativeSumPattern(this, 'op_return_memo_tx_count'), + bitproof: createAverageBlockCumulativeSumPattern(this, 'op_return_bitproof_tx_count'), + ascribe: createAverageBlockCumulativeSumPattern(this, 'op_return_ascribe_tx_count'), + stampery: createAverageBlockCumulativeSumPattern(this, 'op_return_stampery_tx_count'), + epobc: createAverageBlockCumulativeSumPattern(this, 'op_return_epobc_tx_count'), + bareHash: createAverageBlockCumulativeSumPattern(this, 'op_return_bare_hash_tx_count'), + text: createAverageBlockCumulativeSumPattern(this, 'op_return_text_tx_count'), + empty: createAverageBlockCumulativeSumPattern(this, 'op_return_empty_tx_count'), + unknown: createAverageBlockCumulativeSumPattern(this, 'op_return_unknown_tx_count'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_by_kind_tx_count'), + }, + txVsize: { + runes: createAverageBlockCumulativeSumPattern(this, 'op_return_runes_tx_vsize'), + veriBlock: createAverageBlockCumulativeSumPattern(this, 'op_return_veri_block_tx_vsize'), + omni: createAverageBlockCumulativeSumPattern(this, 'op_return_omni_tx_vsize'), + stacks: createAverageBlockCumulativeSumPattern(this, 'op_return_stacks_tx_vsize'), + blockstack: createAverageBlockCumulativeSumPattern(this, 'op_return_blockstack_tx_vsize'), + colu: createAverageBlockCumulativeSumPattern(this, 'op_return_colu_tx_vsize'), + openAssets: createAverageBlockCumulativeSumPattern(this, 'op_return_open_assets_tx_vsize'), + komodo: createAverageBlockCumulativeSumPattern(this, 'op_return_komodo_tx_vsize'), + coinSpark: createAverageBlockCumulativeSumPattern(this, 'op_return_coin_spark_tx_vsize'), + poet: createAverageBlockCumulativeSumPattern(this, 'op_return_poet_tx_vsize'), + docproof: createAverageBlockCumulativeSumPattern(this, 'op_return_docproof_tx_vsize'), + openTimestamps: createAverageBlockCumulativeSumPattern(this, 'op_return_open_timestamps_tx_vsize'), + factom: createAverageBlockCumulativeSumPattern(this, 'op_return_factom_tx_vsize'), + eternityWall: createAverageBlockCumulativeSumPattern(this, 'op_return_eternity_wall_tx_vsize'), + memo: createAverageBlockCumulativeSumPattern(this, 'op_return_memo_tx_vsize'), + bitproof: createAverageBlockCumulativeSumPattern(this, 'op_return_bitproof_tx_vsize'), + ascribe: createAverageBlockCumulativeSumPattern(this, 'op_return_ascribe_tx_vsize'), + stampery: createAverageBlockCumulativeSumPattern(this, 'op_return_stampery_tx_vsize'), + epobc: createAverageBlockCumulativeSumPattern(this, 'op_return_epobc_tx_vsize'), + bareHash: createAverageBlockCumulativeSumPattern(this, 'op_return_bare_hash_tx_vsize'), + text: createAverageBlockCumulativeSumPattern(this, 'op_return_text_tx_vsize'), + empty: createAverageBlockCumulativeSumPattern(this, 'op_return_empty_tx_vsize'), + unknown: createAverageBlockCumulativeSumPattern(this, 'op_return_unknown_tx_vsize'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_by_kind_tx_vsize'), + }, + fees: { + runes: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_runes'), + veriBlock: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_veri_block'), + omni: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_omni'), + stacks: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_stacks'), + blockstack: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_blockstack'), + colu: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_colu'), + openAssets: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_open_assets'), + komodo: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_komodo'), + coinSpark: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_coin_spark'), + poet: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_poet'), + docproof: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_docproof'), + openTimestamps: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_open_timestamps'), + factom: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_factom'), + eternityWall: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_eternity_wall'), + memo: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_memo'), + bitproof: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_bitproof'), + ascribe: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_ascribe'), + stampery: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_stampery'), + epobc: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_epobc'), + bareHash: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_bare_hash'), + text: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_text'), + empty: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_empty'), + unknown: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_unknown'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_by_kind_fees'), + }, }, policy: { - preV30Standard: createChainDataFeeFeesOutputTxPattern(this, 'op_return_policy_pre_v30_standard'), - preV30Nonstandard: createChainDataFeeFeesOutputTxPattern(this, 'op_return_policy_pre_v30_nonstandard'), - oversized: createChainDataFeeFeesOutputTxPattern(this, 'op_return_policy_oversized'), - multiple: createChainDataFeeFeesOutputTxPattern(this, 'op_return_policy_multiple'), + outputCount: { + preV30Standard: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_pre_v30_standard_output_count'), + preV30Nonstandard: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_pre_v30_nonstandard_output_count'), + oversized: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_oversized_output_count'), + multiple: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_multiple_output_count'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_policy_output_count'), + }, + dataBytes: { + preV30Standard: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_policy_pre_v30_standard'), + preV30Nonstandard: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_policy_pre_v30_nonstandard'), + oversized: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_policy_oversized'), + multiple: createAverageBlockChainCumulativeDataSumPattern(this, 'op_return_policy_multiple'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_policy_data_bytes'), + }, + txCount: { + preV30Standard: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_pre_v30_standard_tx_count'), + preV30Nonstandard: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_pre_v30_nonstandard_tx_count'), + oversized: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_oversized_tx_count'), + multiple: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_multiple_tx_count'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_policy_tx_count'), + }, + txVsize: { + preV30Standard: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_pre_v30_standard_tx_vsize'), + preV30Nonstandard: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_pre_v30_nonstandard_tx_vsize'), + oversized: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_oversized_tx_vsize'), + multiple: createAverageBlockCumulativeSumPattern(this, 'op_return_policy_multiple_tx_vsize'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_policy_tx_vsize'), + }, + fees: { + preV30Standard: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_policy_pre_v30_standard'), + preV30Nonstandard: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_policy_pre_v30_nonstandard'), + oversized: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_policy_oversized'), + multiple: createAverageBlockCumulativeFeeSumPattern(this, 'op_return_policy_multiple'), + cumulative: createSeriesPattern18(this, 'op_return_cumulative_policy_fees'), + }, }, }, mining: { @@ -10228,29 +11367,216 @@ class BrkClient extends BrkClientBase { ratio: createSeriesPattern1(this, 'activity_to_vaultedness'), }, ageRange: { - under1h: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_under_1h_old'), - _1hTo1d: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_1h_to_1d_old'), - _1dTo1w: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_1d_to_1w_old'), - _1wTo1m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_1w_to_1m_old'), - _1mTo2m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_1m_to_2m_old'), - _2mTo3m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_2m_to_3m_old'), - _3mTo4m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_3m_to_4m_old'), - _4mTo5m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_4m_to_5m_old'), - _5mTo6m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_5m_to_6m_old'), - _6mTo9m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_6m_to_9m_old'), - _9mTo1y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_9m_to_1y_old'), - _1yTo18m: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_1y_to_18m_old'), - _18mTo2y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_18m_to_2y_old'), - _2yTo3y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_2y_to_3y_old'), - _3yTo4y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_3y_to_4y_old'), - _4yTo5y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_4y_to_5y_old'), - _5yTo6y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_5y_to_6y_old'), - _6yTo7y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_6y_to_7y_old'), - _7yTo8y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_7y_to_8y_old'), - _8yTo10y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_8y_to_10y_old'), - _10yTo12y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_10y_to_12y_old'), - _12yTo15y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_12y_to_15y_old'), - over15y: createCoindaysDormancySupplyWakefulnessPattern(this, 'utxos_over_15y_old'), + coindaysCreated: { + under1h: createAverageBlockCumulativeSumPattern(this, 'utxos_under_1h_old_coindays_created'), + _1hTo1d: createAverageBlockCumulativeSumPattern(this, 'utxos_1h_to_1d_old_coindays_created'), + _1dTo1w: createAverageBlockCumulativeSumPattern(this, 'utxos_1d_to_1w_old_coindays_created'), + _1wTo1m: createAverageBlockCumulativeSumPattern(this, 'utxos_1w_to_1m_old_coindays_created'), + _1mTo2m: createAverageBlockCumulativeSumPattern(this, 'utxos_1m_to_2m_old_coindays_created'), + _2mTo3m: createAverageBlockCumulativeSumPattern(this, 'utxos_2m_to_3m_old_coindays_created'), + _3mTo4m: createAverageBlockCumulativeSumPattern(this, 'utxos_3m_to_4m_old_coindays_created'), + _4mTo5m: createAverageBlockCumulativeSumPattern(this, 'utxos_4m_to_5m_old_coindays_created'), + _5mTo6m: createAverageBlockCumulativeSumPattern(this, 'utxos_5m_to_6m_old_coindays_created'), + _6mTo9m: createAverageBlockCumulativeSumPattern(this, 'utxos_6m_to_9m_old_coindays_created'), + _9mTo1y: createAverageBlockCumulativeSumPattern(this, 'utxos_9m_to_1y_old_coindays_created'), + _1yTo18m: createAverageBlockCumulativeSumPattern(this, 'utxos_1y_to_18m_old_coindays_created'), + _18mTo2y: createAverageBlockCumulativeSumPattern(this, 'utxos_18m_to_2y_old_coindays_created'), + _2yTo3y: createAverageBlockCumulativeSumPattern(this, 'utxos_2y_to_3y_old_coindays_created'), + _3yTo4y: createAverageBlockCumulativeSumPattern(this, 'utxos_3y_to_4y_old_coindays_created'), + _4yTo5y: createAverageBlockCumulativeSumPattern(this, 'utxos_4y_to_5y_old_coindays_created'), + _5yTo6y: createAverageBlockCumulativeSumPattern(this, 'utxos_5y_to_6y_old_coindays_created'), + _6yTo7y: createAverageBlockCumulativeSumPattern(this, 'utxos_6y_to_7y_old_coindays_created'), + _7yTo8y: createAverageBlockCumulativeSumPattern(this, 'utxos_7y_to_8y_old_coindays_created'), + _8yTo10y: createAverageBlockCumulativeSumPattern(this, 'utxos_8y_to_10y_old_coindays_created'), + _10yTo12y: createAverageBlockCumulativeSumPattern(this, 'utxos_10y_to_12y_old_coindays_created'), + _12yTo15y: createAverageBlockCumulativeSumPattern(this, 'utxos_12y_to_15y_old_coindays_created'), + over15y: createAverageBlockCumulativeSumPattern(this, 'utxos_over_15y_old_coindays_created'), + cumulative: createSeriesPattern18(this, 'utxos_age_range_coindays_created_cumulative'), + }, + coindaysConsumed: { + under1h: createAverageBlockCumulativeSumPattern(this, 'utxos_under_1h_old_coindays_consumed'), + _1hTo1d: createAverageBlockCumulativeSumPattern(this, 'utxos_1h_to_1d_old_coindays_consumed'), + _1dTo1w: createAverageBlockCumulativeSumPattern(this, 'utxos_1d_to_1w_old_coindays_consumed'), + _1wTo1m: createAverageBlockCumulativeSumPattern(this, 'utxos_1w_to_1m_old_coindays_consumed'), + _1mTo2m: createAverageBlockCumulativeSumPattern(this, 'utxos_1m_to_2m_old_coindays_consumed'), + _2mTo3m: createAverageBlockCumulativeSumPattern(this, 'utxos_2m_to_3m_old_coindays_consumed'), + _3mTo4m: createAverageBlockCumulativeSumPattern(this, 'utxos_3m_to_4m_old_coindays_consumed'), + _4mTo5m: createAverageBlockCumulativeSumPattern(this, 'utxos_4m_to_5m_old_coindays_consumed'), + _5mTo6m: createAverageBlockCumulativeSumPattern(this, 'utxos_5m_to_6m_old_coindays_consumed'), + _6mTo9m: createAverageBlockCumulativeSumPattern(this, 'utxos_6m_to_9m_old_coindays_consumed'), + _9mTo1y: createAverageBlockCumulativeSumPattern(this, 'utxos_9m_to_1y_old_coindays_consumed'), + _1yTo18m: createAverageBlockCumulativeSumPattern(this, 'utxos_1y_to_18m_old_coindays_consumed'), + _18mTo2y: createAverageBlockCumulativeSumPattern(this, 'utxos_18m_to_2y_old_coindays_consumed'), + _2yTo3y: createAverageBlockCumulativeSumPattern(this, 'utxos_2y_to_3y_old_coindays_consumed'), + _3yTo4y: createAverageBlockCumulativeSumPattern(this, 'utxos_3y_to_4y_old_coindays_consumed'), + _4yTo5y: createAverageBlockCumulativeSumPattern(this, 'utxos_4y_to_5y_old_coindays_consumed'), + _5yTo6y: createAverageBlockCumulativeSumPattern(this, 'utxos_5y_to_6y_old_coindays_consumed'), + _6yTo7y: createAverageBlockCumulativeSumPattern(this, 'utxos_6y_to_7y_old_coindays_consumed'), + _7yTo8y: createAverageBlockCumulativeSumPattern(this, 'utxos_7y_to_8y_old_coindays_consumed'), + _8yTo10y: createAverageBlockCumulativeSumPattern(this, 'utxos_8y_to_10y_old_coindays_consumed'), + _10yTo12y: createAverageBlockCumulativeSumPattern(this, 'utxos_10y_to_12y_old_coindays_consumed'), + _12yTo15y: createAverageBlockCumulativeSumPattern(this, 'utxos_12y_to_15y_old_coindays_consumed'), + over15y: createAverageBlockCumulativeSumPattern(this, 'utxos_over_15y_old_coindays_consumed'), + cumulative: createSeriesPattern18(this, 'utxos_age_range_coindays_consumed_cumulative'), + }, + coindaysStored: { + under1h: createAverageBlockCumulativeSumPattern(this, 'utxos_under_1h_old_coindays_stored'), + _1hTo1d: createAverageBlockCumulativeSumPattern(this, 'utxos_1h_to_1d_old_coindays_stored'), + _1dTo1w: createAverageBlockCumulativeSumPattern(this, 'utxos_1d_to_1w_old_coindays_stored'), + _1wTo1m: createAverageBlockCumulativeSumPattern(this, 'utxos_1w_to_1m_old_coindays_stored'), + _1mTo2m: createAverageBlockCumulativeSumPattern(this, 'utxos_1m_to_2m_old_coindays_stored'), + _2mTo3m: createAverageBlockCumulativeSumPattern(this, 'utxos_2m_to_3m_old_coindays_stored'), + _3mTo4m: createAverageBlockCumulativeSumPattern(this, 'utxos_3m_to_4m_old_coindays_stored'), + _4mTo5m: createAverageBlockCumulativeSumPattern(this, 'utxos_4m_to_5m_old_coindays_stored'), + _5mTo6m: createAverageBlockCumulativeSumPattern(this, 'utxos_5m_to_6m_old_coindays_stored'), + _6mTo9m: createAverageBlockCumulativeSumPattern(this, 'utxos_6m_to_9m_old_coindays_stored'), + _9mTo1y: createAverageBlockCumulativeSumPattern(this, 'utxos_9m_to_1y_old_coindays_stored'), + _1yTo18m: createAverageBlockCumulativeSumPattern(this, 'utxos_1y_to_18m_old_coindays_stored'), + _18mTo2y: createAverageBlockCumulativeSumPattern(this, 'utxos_18m_to_2y_old_coindays_stored'), + _2yTo3y: createAverageBlockCumulativeSumPattern(this, 'utxos_2y_to_3y_old_coindays_stored'), + _3yTo4y: createAverageBlockCumulativeSumPattern(this, 'utxos_3y_to_4y_old_coindays_stored'), + _4yTo5y: createAverageBlockCumulativeSumPattern(this, 'utxos_4y_to_5y_old_coindays_stored'), + _5yTo6y: createAverageBlockCumulativeSumPattern(this, 'utxos_5y_to_6y_old_coindays_stored'), + _6yTo7y: createAverageBlockCumulativeSumPattern(this, 'utxos_6y_to_7y_old_coindays_stored'), + _7yTo8y: createAverageBlockCumulativeSumPattern(this, 'utxos_7y_to_8y_old_coindays_stored'), + _8yTo10y: createAverageBlockCumulativeSumPattern(this, 'utxos_8y_to_10y_old_coindays_stored'), + _10yTo12y: createAverageBlockCumulativeSumPattern(this, 'utxos_10y_to_12y_old_coindays_stored'), + _12yTo15y: createAverageBlockCumulativeSumPattern(this, 'utxos_12y_to_15y_old_coindays_stored'), + over15y: createAverageBlockCumulativeSumPattern(this, 'utxos_over_15y_old_coindays_stored'), + cumulative: createSeriesPattern18(this, 'utxos_age_range_coindays_stored_cumulative'), + }, + activity: { + wakefulness: { + under1h: createSeriesPattern1(this, 'utxos_under_1h_old_wakefulness'), + _1hTo1d: createSeriesPattern1(this, 'utxos_1h_to_1d_old_wakefulness'), + _1dTo1w: createSeriesPattern1(this, 'utxos_1d_to_1w_old_wakefulness'), + _1wTo1m: createSeriesPattern1(this, 'utxos_1w_to_1m_old_wakefulness'), + _1mTo2m: createSeriesPattern1(this, 'utxos_1m_to_2m_old_wakefulness'), + _2mTo3m: createSeriesPattern1(this, 'utxos_2m_to_3m_old_wakefulness'), + _3mTo4m: createSeriesPattern1(this, 'utxos_3m_to_4m_old_wakefulness'), + _4mTo5m: createSeriesPattern1(this, 'utxos_4m_to_5m_old_wakefulness'), + _5mTo6m: createSeriesPattern1(this, 'utxos_5m_to_6m_old_wakefulness'), + _6mTo9m: createSeriesPattern1(this, 'utxos_6m_to_9m_old_wakefulness'), + _9mTo1y: createSeriesPattern1(this, 'utxos_9m_to_1y_old_wakefulness'), + _1yTo18m: createSeriesPattern1(this, 'utxos_1y_to_18m_old_wakefulness'), + _18mTo2y: createSeriesPattern1(this, 'utxos_18m_to_2y_old_wakefulness'), + _2yTo3y: createSeriesPattern1(this, 'utxos_2y_to_3y_old_wakefulness'), + _3yTo4y: createSeriesPattern1(this, 'utxos_3y_to_4y_old_wakefulness'), + _4yTo5y: createSeriesPattern1(this, 'utxos_4y_to_5y_old_wakefulness'), + _5yTo6y: createSeriesPattern1(this, 'utxos_5y_to_6y_old_wakefulness'), + _6yTo7y: createSeriesPattern1(this, 'utxos_6y_to_7y_old_wakefulness'), + _7yTo8y: createSeriesPattern1(this, 'utxos_7y_to_8y_old_wakefulness'), + _8yTo10y: createSeriesPattern1(this, 'utxos_8y_to_10y_old_wakefulness'), + _10yTo12y: createSeriesPattern1(this, 'utxos_10y_to_12y_old_wakefulness'), + _12yTo15y: createSeriesPattern1(this, 'utxos_12y_to_15y_old_wakefulness'), + over15y: createSeriesPattern1(this, 'utxos_over_15y_old_wakefulness'), + }, + dormancy: { + under1h: createSeriesPattern1(this, 'utxos_under_1h_old_dormancy'), + _1hTo1d: createSeriesPattern1(this, 'utxos_1h_to_1d_old_dormancy'), + _1dTo1w: createSeriesPattern1(this, 'utxos_1d_to_1w_old_dormancy'), + _1wTo1m: createSeriesPattern1(this, 'utxos_1w_to_1m_old_dormancy'), + _1mTo2m: createSeriesPattern1(this, 'utxos_1m_to_2m_old_dormancy'), + _2mTo3m: createSeriesPattern1(this, 'utxos_2m_to_3m_old_dormancy'), + _3mTo4m: createSeriesPattern1(this, 'utxos_3m_to_4m_old_dormancy'), + _4mTo5m: createSeriesPattern1(this, 'utxos_4m_to_5m_old_dormancy'), + _5mTo6m: createSeriesPattern1(this, 'utxos_5m_to_6m_old_dormancy'), + _6mTo9m: createSeriesPattern1(this, 'utxos_6m_to_9m_old_dormancy'), + _9mTo1y: createSeriesPattern1(this, 'utxos_9m_to_1y_old_dormancy'), + _1yTo18m: createSeriesPattern1(this, 'utxos_1y_to_18m_old_dormancy'), + _18mTo2y: createSeriesPattern1(this, 'utxos_18m_to_2y_old_dormancy'), + _2yTo3y: createSeriesPattern1(this, 'utxos_2y_to_3y_old_dormancy'), + _3yTo4y: createSeriesPattern1(this, 'utxos_3y_to_4y_old_dormancy'), + _4yTo5y: createSeriesPattern1(this, 'utxos_4y_to_5y_old_dormancy'), + _5yTo6y: createSeriesPattern1(this, 'utxos_5y_to_6y_old_dormancy'), + _6yTo7y: createSeriesPattern1(this, 'utxos_6y_to_7y_old_dormancy'), + _7yTo8y: createSeriesPattern1(this, 'utxos_7y_to_8y_old_dormancy'), + _8yTo10y: createSeriesPattern1(this, 'utxos_8y_to_10y_old_dormancy'), + _10yTo12y: createSeriesPattern1(this, 'utxos_10y_to_12y_old_dormancy'), + _12yTo15y: createSeriesPattern1(this, 'utxos_12y_to_15y_old_dormancy'), + over15y: createSeriesPattern1(this, 'utxos_over_15y_old_dormancy'), + }, + wakefulnessToDormancy: { + under1h: createSeriesPattern1(this, 'utxos_under_1h_old_wakefulness_to_dormancy'), + _1hTo1d: createSeriesPattern1(this, 'utxos_1h_to_1d_old_wakefulness_to_dormancy'), + _1dTo1w: createSeriesPattern1(this, 'utxos_1d_to_1w_old_wakefulness_to_dormancy'), + _1wTo1m: createSeriesPattern1(this, 'utxos_1w_to_1m_old_wakefulness_to_dormancy'), + _1mTo2m: createSeriesPattern1(this, 'utxos_1m_to_2m_old_wakefulness_to_dormancy'), + _2mTo3m: createSeriesPattern1(this, 'utxos_2m_to_3m_old_wakefulness_to_dormancy'), + _3mTo4m: createSeriesPattern1(this, 'utxos_3m_to_4m_old_wakefulness_to_dormancy'), + _4mTo5m: createSeriesPattern1(this, 'utxos_4m_to_5m_old_wakefulness_to_dormancy'), + _5mTo6m: createSeriesPattern1(this, 'utxos_5m_to_6m_old_wakefulness_to_dormancy'), + _6mTo9m: createSeriesPattern1(this, 'utxos_6m_to_9m_old_wakefulness_to_dormancy'), + _9mTo1y: createSeriesPattern1(this, 'utxos_9m_to_1y_old_wakefulness_to_dormancy'), + _1yTo18m: createSeriesPattern1(this, 'utxos_1y_to_18m_old_wakefulness_to_dormancy'), + _18mTo2y: createSeriesPattern1(this, 'utxos_18m_to_2y_old_wakefulness_to_dormancy'), + _2yTo3y: createSeriesPattern1(this, 'utxos_2y_to_3y_old_wakefulness_to_dormancy'), + _3yTo4y: createSeriesPattern1(this, 'utxos_3y_to_4y_old_wakefulness_to_dormancy'), + _4yTo5y: createSeriesPattern1(this, 'utxos_4y_to_5y_old_wakefulness_to_dormancy'), + _5yTo6y: createSeriesPattern1(this, 'utxos_5y_to_6y_old_wakefulness_to_dormancy'), + _6yTo7y: createSeriesPattern1(this, 'utxos_6y_to_7y_old_wakefulness_to_dormancy'), + _7yTo8y: createSeriesPattern1(this, 'utxos_7y_to_8y_old_wakefulness_to_dormancy'), + _8yTo10y: createSeriesPattern1(this, 'utxos_8y_to_10y_old_wakefulness_to_dormancy'), + _10yTo12y: createSeriesPattern1(this, 'utxos_10y_to_12y_old_wakefulness_to_dormancy'), + _12yTo15y: createSeriesPattern1(this, 'utxos_12y_to_15y_old_wakefulness_to_dormancy'), + over15y: createSeriesPattern1(this, 'utxos_over_15y_old_wakefulness_to_dormancy'), + }, + height: createSeriesPattern18(this, 'utxos_age_range_wakefulness'), + }, + supply: { + awake: { + under1h: createBtcCentsSatsUsdPattern(this, 'utxos_under_1h_old_awake_supply'), + _1hTo1d: createBtcCentsSatsUsdPattern(this, 'utxos_1h_to_1d_old_awake_supply'), + _1dTo1w: createBtcCentsSatsUsdPattern(this, 'utxos_1d_to_1w_old_awake_supply'), + _1wTo1m: createBtcCentsSatsUsdPattern(this, 'utxos_1w_to_1m_old_awake_supply'), + _1mTo2m: createBtcCentsSatsUsdPattern(this, 'utxos_1m_to_2m_old_awake_supply'), + _2mTo3m: createBtcCentsSatsUsdPattern(this, 'utxos_2m_to_3m_old_awake_supply'), + _3mTo4m: createBtcCentsSatsUsdPattern(this, 'utxos_3m_to_4m_old_awake_supply'), + _4mTo5m: createBtcCentsSatsUsdPattern(this, 'utxos_4m_to_5m_old_awake_supply'), + _5mTo6m: createBtcCentsSatsUsdPattern(this, 'utxos_5m_to_6m_old_awake_supply'), + _6mTo9m: createBtcCentsSatsUsdPattern(this, 'utxos_6m_to_9m_old_awake_supply'), + _9mTo1y: createBtcCentsSatsUsdPattern(this, 'utxos_9m_to_1y_old_awake_supply'), + _1yTo18m: createBtcCentsSatsUsdPattern(this, 'utxos_1y_to_18m_old_awake_supply'), + _18mTo2y: createBtcCentsSatsUsdPattern(this, 'utxos_18m_to_2y_old_awake_supply'), + _2yTo3y: createBtcCentsSatsUsdPattern(this, 'utxos_2y_to_3y_old_awake_supply'), + _3yTo4y: createBtcCentsSatsUsdPattern(this, 'utxos_3y_to_4y_old_awake_supply'), + _4yTo5y: createBtcCentsSatsUsdPattern(this, 'utxos_4y_to_5y_old_awake_supply'), + _5yTo6y: createBtcCentsSatsUsdPattern(this, 'utxos_5y_to_6y_old_awake_supply'), + _6yTo7y: createBtcCentsSatsUsdPattern(this, 'utxos_6y_to_7y_old_awake_supply'), + _7yTo8y: createBtcCentsSatsUsdPattern(this, 'utxos_7y_to_8y_old_awake_supply'), + _8yTo10y: createBtcCentsSatsUsdPattern(this, 'utxos_8y_to_10y_old_awake_supply'), + _10yTo12y: createBtcCentsSatsUsdPattern(this, 'utxos_10y_to_12y_old_awake_supply'), + _12yTo15y: createBtcCentsSatsUsdPattern(this, 'utxos_12y_to_15y_old_awake_supply'), + over15y: createBtcCentsSatsUsdPattern(this, 'utxos_over_15y_old_awake_supply'), + height: createSeriesPattern18(this, 'utxos_age_range_awake_supply_sats'), + }, + dormant: { + under1h: createBtcCentsSatsUsdPattern(this, 'utxos_under_1h_old_dormant_supply'), + _1hTo1d: createBtcCentsSatsUsdPattern(this, 'utxos_1h_to_1d_old_dormant_supply'), + _1dTo1w: createBtcCentsSatsUsdPattern(this, 'utxos_1d_to_1w_old_dormant_supply'), + _1wTo1m: createBtcCentsSatsUsdPattern(this, 'utxos_1w_to_1m_old_dormant_supply'), + _1mTo2m: createBtcCentsSatsUsdPattern(this, 'utxos_1m_to_2m_old_dormant_supply'), + _2mTo3m: createBtcCentsSatsUsdPattern(this, 'utxos_2m_to_3m_old_dormant_supply'), + _3mTo4m: createBtcCentsSatsUsdPattern(this, 'utxos_3m_to_4m_old_dormant_supply'), + _4mTo5m: createBtcCentsSatsUsdPattern(this, 'utxos_4m_to_5m_old_dormant_supply'), + _5mTo6m: createBtcCentsSatsUsdPattern(this, 'utxos_5m_to_6m_old_dormant_supply'), + _6mTo9m: createBtcCentsSatsUsdPattern(this, 'utxos_6m_to_9m_old_dormant_supply'), + _9mTo1y: createBtcCentsSatsUsdPattern(this, 'utxos_9m_to_1y_old_dormant_supply'), + _1yTo18m: createBtcCentsSatsUsdPattern(this, 'utxos_1y_to_18m_old_dormant_supply'), + _18mTo2y: createBtcCentsSatsUsdPattern(this, 'utxos_18m_to_2y_old_dormant_supply'), + _2yTo3y: createBtcCentsSatsUsdPattern(this, 'utxos_2y_to_3y_old_dormant_supply'), + _3yTo4y: createBtcCentsSatsUsdPattern(this, 'utxos_3y_to_4y_old_dormant_supply'), + _4yTo5y: createBtcCentsSatsUsdPattern(this, 'utxos_4y_to_5y_old_dormant_supply'), + _5yTo6y: createBtcCentsSatsUsdPattern(this, 'utxos_5y_to_6y_old_dormant_supply'), + _6yTo7y: createBtcCentsSatsUsdPattern(this, 'utxos_6y_to_7y_old_dormant_supply'), + _7yTo8y: createBtcCentsSatsUsdPattern(this, 'utxos_7y_to_8y_old_dormant_supply'), + _8yTo10y: createBtcCentsSatsUsdPattern(this, 'utxos_8y_to_10y_old_dormant_supply'), + _10yTo12y: createBtcCentsSatsUsdPattern(this, 'utxos_10y_to_12y_old_dormant_supply'), + _12yTo15y: createBtcCentsSatsUsdPattern(this, 'utxos_12y_to_15y_old_dormant_supply'), + over15y: createBtcCentsSatsUsdPattern(this, 'utxos_over_15y_old_dormant_supply'), + height: createSeriesPattern18(this, 'utxos_age_range_dormant_supply_sats'), + }, + }, }, awake: { supply: { @@ -10335,29 +11661,137 @@ class BrkClient extends BrkClientBase { }, coinflow: { ageRange: { - under1h: createMobilitySpendingSupplyPattern(this, 'utxos_under_1h_old'), - _1hTo1d: createMobilitySpendingSupplyPattern(this, 'utxos_1h_to_1d_old'), - _1dTo1w: createMobilitySpendingSupplyPattern(this, 'utxos_1d_to_1w_old'), - _1wTo1m: createMobilitySpendingSupplyPattern(this, 'utxos_1w_to_1m_old'), - _1mTo2m: createMobilitySpendingSupplyPattern(this, 'utxos_1m_to_2m_old'), - _2mTo3m: createMobilitySpendingSupplyPattern(this, 'utxos_2m_to_3m_old'), - _3mTo4m: createMobilitySpendingSupplyPattern(this, 'utxos_3m_to_4m_old'), - _4mTo5m: createMobilitySpendingSupplyPattern(this, 'utxos_4m_to_5m_old'), - _5mTo6m: createMobilitySpendingSupplyPattern(this, 'utxos_5m_to_6m_old'), - _6mTo9m: createMobilitySpendingSupplyPattern(this, 'utxos_6m_to_9m_old'), - _9mTo1y: createMobilitySpendingSupplyPattern(this, 'utxos_9m_to_1y_old'), - _1yTo18m: createMobilitySpendingSupplyPattern(this, 'utxos_1y_to_18m_old'), - _18mTo2y: createMobilitySpendingSupplyPattern(this, 'utxos_18m_to_2y_old'), - _2yTo3y: createMobilitySpendingSupplyPattern(this, 'utxos_2y_to_3y_old'), - _3yTo4y: createMobilitySpendingSupplyPattern(this, 'utxos_3y_to_4y_old'), - _4yTo5y: createMobilitySpendingSupplyPattern(this, 'utxos_4y_to_5y_old'), - _5yTo6y: createMobilitySpendingSupplyPattern(this, 'utxos_5y_to_6y_old'), - _6yTo7y: createMobilitySpendingSupplyPattern(this, 'utxos_6y_to_7y_old'), - _7yTo8y: createMobilitySpendingSupplyPattern(this, 'utxos_7y_to_8y_old'), - _8yTo10y: createMobilitySpendingSupplyPattern(this, 'utxos_8y_to_10y_old'), - _10yTo12y: createMobilitySpendingSupplyPattern(this, 'utxos_10y_to_12y_old'), - _12yTo15y: createMobilitySpendingSupplyPattern(this, 'utxos_12y_to_15y_old'), - over15y: createMobilitySpendingSupplyPattern(this, 'utxos_over_15y_old'), + spendingRate: { + under1h: createSeriesPattern1(this, 'utxos_under_1h_old_spending_rate'), + _1hTo1d: createSeriesPattern1(this, 'utxos_1h_to_1d_old_spending_rate'), + _1dTo1w: createSeriesPattern1(this, 'utxos_1d_to_1w_old_spending_rate'), + _1wTo1m: createSeriesPattern1(this, 'utxos_1w_to_1m_old_spending_rate'), + _1mTo2m: createSeriesPattern1(this, 'utxos_1m_to_2m_old_spending_rate'), + _2mTo3m: createSeriesPattern1(this, 'utxos_2m_to_3m_old_spending_rate'), + _3mTo4m: createSeriesPattern1(this, 'utxos_3m_to_4m_old_spending_rate'), + _4mTo5m: createSeriesPattern1(this, 'utxos_4m_to_5m_old_spending_rate'), + _5mTo6m: createSeriesPattern1(this, 'utxos_5m_to_6m_old_spending_rate'), + _6mTo9m: createSeriesPattern1(this, 'utxos_6m_to_9m_old_spending_rate'), + _9mTo1y: createSeriesPattern1(this, 'utxos_9m_to_1y_old_spending_rate'), + _1yTo18m: createSeriesPattern1(this, 'utxos_1y_to_18m_old_spending_rate'), + _18mTo2y: createSeriesPattern1(this, 'utxos_18m_to_2y_old_spending_rate'), + _2yTo3y: createSeriesPattern1(this, 'utxos_2y_to_3y_old_spending_rate'), + _3yTo4y: createSeriesPattern1(this, 'utxos_3y_to_4y_old_spending_rate'), + _4yTo5y: createSeriesPattern1(this, 'utxos_4y_to_5y_old_spending_rate'), + _5yTo6y: createSeriesPattern1(this, 'utxos_5y_to_6y_old_spending_rate'), + _6yTo7y: createSeriesPattern1(this, 'utxos_6y_to_7y_old_spending_rate'), + _7yTo8y: createSeriesPattern1(this, 'utxos_7y_to_8y_old_spending_rate'), + _8yTo10y: createSeriesPattern1(this, 'utxos_8y_to_10y_old_spending_rate'), + _10yTo12y: createSeriesPattern1(this, 'utxos_10y_to_12y_old_spending_rate'), + _12yTo15y: createSeriesPattern1(this, 'utxos_12y_to_15y_old_spending_rate'), + over15y: createSeriesPattern1(this, 'utxos_over_15y_old_spending_rate'), + height: createSeriesPattern18(this, 'utxos_age_range_spending_rate'), + }, + spendingExposure: { + under1h: createSeriesPattern1(this, 'utxos_under_1h_old_spending_exposure'), + _1hTo1d: createSeriesPattern1(this, 'utxos_1h_to_1d_old_spending_exposure'), + _1dTo1w: createSeriesPattern1(this, 'utxos_1d_to_1w_old_spending_exposure'), + _1wTo1m: createSeriesPattern1(this, 'utxos_1w_to_1m_old_spending_exposure'), + _1mTo2m: createSeriesPattern1(this, 'utxos_1m_to_2m_old_spending_exposure'), + _2mTo3m: createSeriesPattern1(this, 'utxos_2m_to_3m_old_spending_exposure'), + _3mTo4m: createSeriesPattern1(this, 'utxos_3m_to_4m_old_spending_exposure'), + _4mTo5m: createSeriesPattern1(this, 'utxos_4m_to_5m_old_spending_exposure'), + _5mTo6m: createSeriesPattern1(this, 'utxos_5m_to_6m_old_spending_exposure'), + _6mTo9m: createSeriesPattern1(this, 'utxos_6m_to_9m_old_spending_exposure'), + _9mTo1y: createSeriesPattern1(this, 'utxos_9m_to_1y_old_spending_exposure'), + _1yTo18m: createSeriesPattern1(this, 'utxos_1y_to_18m_old_spending_exposure'), + _18mTo2y: createSeriesPattern1(this, 'utxos_18m_to_2y_old_spending_exposure'), + _2yTo3y: createSeriesPattern1(this, 'utxos_2y_to_3y_old_spending_exposure'), + _3yTo4y: createSeriesPattern1(this, 'utxos_3y_to_4y_old_spending_exposure'), + _4yTo5y: createSeriesPattern1(this, 'utxos_4y_to_5y_old_spending_exposure'), + _5yTo6y: createSeriesPattern1(this, 'utxos_5y_to_6y_old_spending_exposure'), + _6yTo7y: createSeriesPattern1(this, 'utxos_6y_to_7y_old_spending_exposure'), + _7yTo8y: createSeriesPattern1(this, 'utxos_7y_to_8y_old_spending_exposure'), + _8yTo10y: createSeriesPattern1(this, 'utxos_8y_to_10y_old_spending_exposure'), + _10yTo12y: createSeriesPattern1(this, 'utxos_10y_to_12y_old_spending_exposure'), + _12yTo15y: createSeriesPattern1(this, 'utxos_12y_to_15y_old_spending_exposure'), + over15y: createSeriesPattern1(this, 'utxos_over_15y_old_spending_exposure'), + mobility: { + under1h: createSeriesPattern1(this, 'utxos_under_1h_old_mobility'), + _1hTo1d: createSeriesPattern1(this, 'utxos_1h_to_1d_old_mobility'), + _1dTo1w: createSeriesPattern1(this, 'utxos_1d_to_1w_old_mobility'), + _1wTo1m: createSeriesPattern1(this, 'utxos_1w_to_1m_old_mobility'), + _1mTo2m: createSeriesPattern1(this, 'utxos_1m_to_2m_old_mobility'), + _2mTo3m: createSeriesPattern1(this, 'utxos_2m_to_3m_old_mobility'), + _3mTo4m: createSeriesPattern1(this, 'utxos_3m_to_4m_old_mobility'), + _4mTo5m: createSeriesPattern1(this, 'utxos_4m_to_5m_old_mobility'), + _5mTo6m: createSeriesPattern1(this, 'utxos_5m_to_6m_old_mobility'), + _6mTo9m: createSeriesPattern1(this, 'utxos_6m_to_9m_old_mobility'), + _9mTo1y: createSeriesPattern1(this, 'utxos_9m_to_1y_old_mobility'), + _1yTo18m: createSeriesPattern1(this, 'utxos_1y_to_18m_old_mobility'), + _18mTo2y: createSeriesPattern1(this, 'utxos_18m_to_2y_old_mobility'), + _2yTo3y: createSeriesPattern1(this, 'utxos_2y_to_3y_old_mobility'), + _3yTo4y: createSeriesPattern1(this, 'utxos_3y_to_4y_old_mobility'), + _4yTo5y: createSeriesPattern1(this, 'utxos_4y_to_5y_old_mobility'), + _5yTo6y: createSeriesPattern1(this, 'utxos_5y_to_6y_old_mobility'), + _6yTo7y: createSeriesPattern1(this, 'utxos_6y_to_7y_old_mobility'), + _7yTo8y: createSeriesPattern1(this, 'utxos_7y_to_8y_old_mobility'), + _8yTo10y: createSeriesPattern1(this, 'utxos_8y_to_10y_old_mobility'), + _10yTo12y: createSeriesPattern1(this, 'utxos_10y_to_12y_old_mobility'), + _12yTo15y: createSeriesPattern1(this, 'utxos_12y_to_15y_old_mobility'), + over15y: createSeriesPattern1(this, 'utxos_over_15y_old_mobility'), + }, + height: createSeriesPattern18(this, 'utxos_age_range_spending_exposure'), + }, + supply: { + mobile: { + under1h: createBtcCentsSatsUsdPattern(this, 'utxos_under_1h_old_mobile_supply'), + _1hTo1d: createBtcCentsSatsUsdPattern(this, 'utxos_1h_to_1d_old_mobile_supply'), + _1dTo1w: createBtcCentsSatsUsdPattern(this, 'utxos_1d_to_1w_old_mobile_supply'), + _1wTo1m: createBtcCentsSatsUsdPattern(this, 'utxos_1w_to_1m_old_mobile_supply'), + _1mTo2m: createBtcCentsSatsUsdPattern(this, 'utxos_1m_to_2m_old_mobile_supply'), + _2mTo3m: createBtcCentsSatsUsdPattern(this, 'utxos_2m_to_3m_old_mobile_supply'), + _3mTo4m: createBtcCentsSatsUsdPattern(this, 'utxos_3m_to_4m_old_mobile_supply'), + _4mTo5m: createBtcCentsSatsUsdPattern(this, 'utxos_4m_to_5m_old_mobile_supply'), + _5mTo6m: createBtcCentsSatsUsdPattern(this, 'utxos_5m_to_6m_old_mobile_supply'), + _6mTo9m: createBtcCentsSatsUsdPattern(this, 'utxos_6m_to_9m_old_mobile_supply'), + _9mTo1y: createBtcCentsSatsUsdPattern(this, 'utxos_9m_to_1y_old_mobile_supply'), + _1yTo18m: createBtcCentsSatsUsdPattern(this, 'utxos_1y_to_18m_old_mobile_supply'), + _18mTo2y: createBtcCentsSatsUsdPattern(this, 'utxos_18m_to_2y_old_mobile_supply'), + _2yTo3y: createBtcCentsSatsUsdPattern(this, 'utxos_2y_to_3y_old_mobile_supply'), + _3yTo4y: createBtcCentsSatsUsdPattern(this, 'utxos_3y_to_4y_old_mobile_supply'), + _4yTo5y: createBtcCentsSatsUsdPattern(this, 'utxos_4y_to_5y_old_mobile_supply'), + _5yTo6y: createBtcCentsSatsUsdPattern(this, 'utxos_5y_to_6y_old_mobile_supply'), + _6yTo7y: createBtcCentsSatsUsdPattern(this, 'utxos_6y_to_7y_old_mobile_supply'), + _7yTo8y: createBtcCentsSatsUsdPattern(this, 'utxos_7y_to_8y_old_mobile_supply'), + _8yTo10y: createBtcCentsSatsUsdPattern(this, 'utxos_8y_to_10y_old_mobile_supply'), + _10yTo12y: createBtcCentsSatsUsdPattern(this, 'utxos_10y_to_12y_old_mobile_supply'), + _12yTo15y: createBtcCentsSatsUsdPattern(this, 'utxos_12y_to_15y_old_mobile_supply'), + over15y: createBtcCentsSatsUsdPattern(this, 'utxos_over_15y_old_mobile_supply'), + height: createSeriesPattern18(this, 'utxos_age_range_mobile_supply_sats'), + }, + immobile: { + under1h: createBtcCentsSatsUsdPattern(this, 'utxos_under_1h_old_immobile_supply'), + _1hTo1d: createBtcCentsSatsUsdPattern(this, 'utxos_1h_to_1d_old_immobile_supply'), + _1dTo1w: createBtcCentsSatsUsdPattern(this, 'utxos_1d_to_1w_old_immobile_supply'), + _1wTo1m: createBtcCentsSatsUsdPattern(this, 'utxos_1w_to_1m_old_immobile_supply'), + _1mTo2m: createBtcCentsSatsUsdPattern(this, 'utxos_1m_to_2m_old_immobile_supply'), + _2mTo3m: createBtcCentsSatsUsdPattern(this, 'utxos_2m_to_3m_old_immobile_supply'), + _3mTo4m: createBtcCentsSatsUsdPattern(this, 'utxos_3m_to_4m_old_immobile_supply'), + _4mTo5m: createBtcCentsSatsUsdPattern(this, 'utxos_4m_to_5m_old_immobile_supply'), + _5mTo6m: createBtcCentsSatsUsdPattern(this, 'utxos_5m_to_6m_old_immobile_supply'), + _6mTo9m: createBtcCentsSatsUsdPattern(this, 'utxos_6m_to_9m_old_immobile_supply'), + _9mTo1y: createBtcCentsSatsUsdPattern(this, 'utxos_9m_to_1y_old_immobile_supply'), + _1yTo18m: createBtcCentsSatsUsdPattern(this, 'utxos_1y_to_18m_old_immobile_supply'), + _18mTo2y: createBtcCentsSatsUsdPattern(this, 'utxos_18m_to_2y_old_immobile_supply'), + _2yTo3y: createBtcCentsSatsUsdPattern(this, 'utxos_2y_to_3y_old_immobile_supply'), + _3yTo4y: createBtcCentsSatsUsdPattern(this, 'utxos_3y_to_4y_old_immobile_supply'), + _4yTo5y: createBtcCentsSatsUsdPattern(this, 'utxos_4y_to_5y_old_immobile_supply'), + _5yTo6y: createBtcCentsSatsUsdPattern(this, 'utxos_5y_to_6y_old_immobile_supply'), + _6yTo7y: createBtcCentsSatsUsdPattern(this, 'utxos_6y_to_7y_old_immobile_supply'), + _7yTo8y: createBtcCentsSatsUsdPattern(this, 'utxos_7y_to_8y_old_immobile_supply'), + _8yTo10y: createBtcCentsSatsUsdPattern(this, 'utxos_8y_to_10y_old_immobile_supply'), + _10yTo12y: createBtcCentsSatsUsdPattern(this, 'utxos_10y_to_12y_old_immobile_supply'), + _12yTo15y: createBtcCentsSatsUsdPattern(this, 'utxos_12y_to_15y_old_immobile_supply'), + over15y: createBtcCentsSatsUsdPattern(this, 'utxos_over_15y_old_immobile_supply'), + height: createSeriesPattern18(this, 'utxos_age_range_immobile_supply_sats'), + }, + }, }, supply: { mobile: { @@ -10425,44 +11859,46 @@ class BrkClient extends BrkClientBase { }, rarityMeter: { components: { - realizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'realized_price'), - capitalizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'capitalized_price'), - sthRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'sth_realized_price'), - sthCapitalizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'sth_capitalized_price'), - lthRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'lth_realized_price'), - lthCapitalizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'lth_capitalized_price'), - over6mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'over_6m_realized_price'), - over4mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'over_4m_realized_price'), - under4mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'under_4m_realized_price'), - under6mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'under_6m_realized_price'), - vaultedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'vaulted_price'), - activePrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'active_price'), - trueMarketMeanPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'true_market_mean_price'), - cointimePrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'cointime_price'), - coinflowPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(this, 'coinflow_price'), + realizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'realized_price'), + capitalizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'capitalized_price'), + sthRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'sth_realized_price'), + sthCapitalizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'sth_capitalized_price'), + lthRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'lth_realized_price'), + lthCapitalizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'lth_capitalized_price'), + over6mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'over_6m_realized_price'), + over4mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'over_4m_realized_price'), + under4mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'under_4m_realized_price'), + under6mRealizedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'under_6m_realized_price'), + vaultedPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'vaulted_price'), + activePrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'active_price'), + trueMarketMeanPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'true_market_mean_price'), + cointimePrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'cointime_price'), + coinflowPrice: createPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(this, 'coinflow_price'), }, extremes: { coinsInLoss: { thresholdPct01: createSeriesPattern1(this, 'rarity_meter_coins_in_loss_threshold_pct0_1'), thresholdPct005: createSeriesPattern1(this, 'rarity_meter_coins_in_loss_threshold_pct0_05'), thresholdPct0025: createSeriesPattern1(this, 'rarity_meter_coins_in_loss_threshold'), + height: createSeriesPattern18(this, 'rarity_meter_coins_in_loss_thresholds'), tail: createPercentPpmRatioPattern2(this, 'rarity_meter_coins_in_loss_tail'), rank: createSeriesPattern1(this, 'rarity_meter_coins_in_loss_rank'), }, - profitTaking: createRankTailThresholdPattern(this, 'rarity_meter_profit_taking'), - capitulation: createRankTailThresholdPattern(this, 'rarity_meter_capitulation'), - peakRegret: createRankTailThresholdPattern(this, 'rarity_meter_peak_regret'), + profitTaking: createHeightRankTailThresholdPattern(this, 'rarity_meter_profit_taking'), + capitulation: createHeightRankTailThresholdPattern(this, 'rarity_meter_capitulation'), + peakRegret: createHeightRankTailThresholdPattern(this, 'rarity_meter_peak_regret'), sellerExhaustion: { thresholdPct01: createSeriesPattern1(this, 'rarity_meter_seller_exhaustion_threshold_pct0_1'), thresholdPct005: createSeriesPattern1(this, 'rarity_meter_seller_exhaustion_threshold_pct0_05'), thresholdPct0025: createSeriesPattern1(this, 'rarity_meter_seller_exhaustion_threshold'), + height: createSeriesPattern18(this, 'rarity_meter_seller_exhaustion_thresholds'), tail: createPercentPpmRatioPattern2(this, 'rarity_meter_seller_exhaustion_tail'), rank: createSeriesPattern1(this, 'rarity_meter_seller_exhaustion_rank'), }, }, - full: createIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(this, 'rarity_meter'), - local: createIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(this, 'local_rarity_meter'), - cycle: createIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(this, 'cycle_rarity_meter'), + full: createHeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(this, 'rarity_meter'), + local: createHeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(this, 'local_rarity_meter'), + cycle: createHeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(this, 'cycle_rarity_meter'), }, }, constants: { @@ -10821,6 +12257,7 @@ class BrkClient extends BrkClientBase { _2y: createCentsPpmRatioSatsUsdPattern(this, 'price_ema_2y'), _200w: createCentsPpmRatioSatsUsdPattern(this, 'price_ema_200w'), _4y: createCentsPpmRatioSatsUsdPattern(this, 'price_ema_4y'), + height: createSeriesPattern18(this, 'price_ema_cents'), }, }, technical: { @@ -11071,7 +12508,7 @@ class BrkClient extends BrkClientBase { transferVolume: createAverageBlockCumulativeInSumPattern(this, 'transfer_volume'), coindaysDestroyed: createAverageBlockCumulativeSumPattern(this, 'coindays_destroyed'), coinyearsDestroyed: createSeriesPattern1(this, 'coinyears_destroyed'), - dormancy: create_1m1w1y24hPattern(this, 'dormancy'), + dormancy: create_1m1w1y24hHeightPattern(this, 'dormancy'), }, realized: { cap: createCentsDeltaToUsdPattern(this, 'realized_cap'), @@ -11082,26 +12519,26 @@ class BrkClient extends BrkClientBase { netPnl: createBlockChangeCumulativeDeltaSumPattern(this, 'net'), sopr: { valueDestroyed: createAverageBlockCumulativeSumPattern(this, 'value_destroyed'), - ratio: create_1m1w1y24hPattern(this, 'sopr'), + ratio: create_1m1w1y24hHeightPattern4(this, 'sopr'), adjusted: { - ratio: create_1m1w1y24hPattern(this, 'asopr'), + ratio: create_1m1w1y24hHeightPattern2(this, 'asopr'), transferVolume: createAverageBlockCumulativeSumPattern(this, 'adj_value_created'), valueDestroyed: createAverageBlockCumulativeSumPattern(this, 'adj_value_destroyed'), }, }, grossPnl: createBlockCumulativeSumPattern(this, 'realized_gross_pnl'), - sellSideRiskRatio: create_1m1w1y24hPattern8(this, 'sell_side_risk_ratio'), + sellSideRiskRatio: create_1m1w1y24hHeightPattern3(this, 'sell_side_risk_ratio'), peakRegret: createBlockCumulativeSumPattern(this, 'realized_peak_regret'), capitalized: createPricePattern(this, 'capitalized_price'), - profitToLossRatio: create_1m1w1y24hPattern(this, 'realized_profit_to_loss_ratio'), + profitToLossRatio: create_1m1w1y24hHeightPattern2(this, 'realized_profit_to_loss_ratio'), }, costBasis: { inProfit: createPerPattern(this, 'cost_basis_in_profit_per'), inLoss: createPerPattern(this, 'cost_basis_in_loss_per'), min: createCentsSatsUsdPattern(this, 'cost_basis_min'), max: createCentsSatsUsdPattern(this, 'cost_basis_max'), - perCoin: createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(this, 'cost_basis_per_coin'), - perDollar: createPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(this, 'cost_basis_per_dollar'), + perCoin: createHeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(this, 'cost_basis_per_coin'), + perDollar: createHeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(this, 'cost_basis_per_dollar'), supplyDensity: createPercentPpmRatioPattern2(this, 'supply_density'), }, unrealized: { @@ -11158,13 +12595,13 @@ class BrkClient extends BrkClientBase { netPnl: createBlockChangeCumulativeDeltaSumPattern(this, 'lth_net'), sopr: { valueDestroyed: createAverageBlockCumulativeSumPattern(this, 'lth_value_destroyed'), - ratio: create_1m1w1y24hPattern(this, 'lth_sopr'), + ratio: create_1m1w1y24hHeightPattern4(this, 'lth_sopr'), }, grossPnl: createBlockCumulativeSumPattern(this, 'lth_realized_gross_pnl'), - sellSideRiskRatio: create_1m1w1y24hPattern8(this, 'lth_sell_side_risk_ratio'), + sellSideRiskRatio: create_1m1w1y24hHeightPattern3(this, 'lth_sell_side_risk_ratio'), peakRegret: createBlockCumulativeSumPattern(this, 'lth_realized_peak_regret'), capitalized: createPricePattern(this, 'lth_capitalized_price'), - profitToLossRatio: create_1m1w1y24hPattern(this, 'lth_realized_profit_to_loss_ratio'), + profitToLossRatio: create_1m1w1y24hHeightPattern2(this, 'lth_realized_profit_to_loss_ratio'), }, costBasis: createInMaxMinPerSupplyPattern(this, 'lth'), unrealized: createCapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(this, 'lth'), @@ -11385,6 +12822,13 @@ class BrkClient extends BrkClientBase { _70pct: createNuplRealizedSupplyUnrealizedPattern(this, 'utxos_over_70pct_in_loss'), _80pct: createNuplRealizedSupplyUnrealizedPattern(this, 'utxos_over_80pct_in_loss'), }, + allSupplySats: createSeriesPattern18(this, 'profitability_all_supply_sats'), + sthSupplySats: createSeriesPattern18(this, 'profitability_sth_supply_sats'), + allRealizedCap: createSeriesPattern18(this, 'profitability_all_realized_cap'), + sthRealizedCap: createSeriesPattern18(this, 'profitability_sth_realized_cap'), + allUnrealizedPnl: createSeriesPattern18(this, 'profitability_all_unrealized_pnl'), + sthUnrealizedPnl: createSeriesPattern18(this, 'profitability_sth_unrealized_pnl'), + nupl: createSeriesPattern18(this, 'profitability_nupl_ppm'), }, matured: { under1h: createAverageBlockCumulativeSumPattern2(this, 'utxos_under_1h_old_matured_supply'), @@ -11411,6 +12855,8 @@ class BrkClient extends BrkClientBase { _12yTo15y: createAverageBlockCumulativeSumPattern2(this, 'utxos_12y_to_15y_old_matured_supply'), over15y: createAverageBlockCumulativeSumPattern2(this, 'utxos_over_15y_old_matured_supply'), }, + cumulativeMaturedSats: createSeriesPattern18(this, 'utxos_age_range_matured_supply_cumulative_sats'), + cumulativeMaturedCents: createSeriesPattern18(this, 'utxos_age_range_matured_supply_cumulative_cents'), }, addr: { overAmount: { diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index f1f650e51..b242d2bfd 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -93,6 +93,7 @@ TxVersionRaw = int # Reconstruction is a single pass: for each entry, either copy # `prior[idx]` or append the inline body. BlockTemplateDiffEntry = Union[int, "Transaction"] +Bytes = int # Investor phase from the Capital Sentiment model. # # Codes are explicit because phase values are persisted. Code `0` represents @@ -274,6 +275,32 @@ UrpdWeight = Literal["raw", "cointime", "coinflow"] Week1 = int Year1 = int Year10 = int +[Bitcoin; 3] = List[Bitcoin] +[Bytes; 23] = List[Bytes] +[Bytes; 4] = List[Bytes] +[Cents; 16] = List[Cents] +[Cents; 19] = List[Cents] +[Cents; 23] = List[Cents] +[Dollars; 3] = List[Dollars] +[PartsPerMillion32; 19] = List[PartsPerMillion32] +[PartsPerMillion32; 4] = List[PartsPerMillion32] +[Sats; 23] = List[Sats] +[Sats; 4] = List[Sats] +[Sats; 8] = List[Sats] +[StoredF32; 3] = List[StoredF32] +[StoredF32; 4] = List[StoredF32] +[StoredF64; 23] = List[StoredF64] +[StoredF64; 3] = List[StoredF64] +[StoredF64; 4] = List[StoredF64] +[StoredU16; 11] = List[StoredU16] +[StoredU16; 12] = List[StoredU16] +[StoredU64; 11] = List[StoredU64] +[StoredU64; 12] = List[StoredU64] +[StoredU64; 23] = List[StoredU64] +[StoredU64; 4] = List[StoredU64] +[StoredU64; 8] = List[StoredU64] +[VSize; 23] = List[VSize] +[VSize; 4] = List[VSize] class AddrAfterTxidParam(TypedDict): """ Bitcoin address + last-seen txid path parameters (Esplora-style pagination) @@ -3033,11 +3060,28 @@ class SeriesPattern35(Generic[T]): # Reusable structural pattern classes -class IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern: +class _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mCumulativeOverUnderPattern: + """Pattern struct for repeated tree structure.""" + pass + +class _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mHeightOverUnderPattern2: + """Pattern struct for repeated tree structure.""" + pass + +class AscribeBareBitproofBlockstackCoinColuCumulativeDocproofEmptyEpobcEternityFactomKomodoMemoOmniOpenPoetRunesStacksStamperyTextUnknownVeriPattern3(Generic[T]): + """Pattern struct for repeated tree structure.""" + pass + +class _10y12y18m1d1h1m1w1y2m2y3m3y4m4y5m5y6m6y7y8y9mOverUnderPattern3: + """Pattern struct for repeated tree structure.""" + pass + +class HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClient, acc: str): """Create pattern node with accumulated series name.""" + self.height: SeriesPattern18[[Cents; 19]] = SeriesPattern18(client, _m(acc, 'percentiles_cents')) self.index: SeriesPattern1[StoredI8] = SeriesPattern1(client, _m(acc, 'index')) self.pct0_1: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct0_1')) self.pct0_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct0_5')) @@ -3060,11 +3104,12 @@ class IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct self.pct99_9: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct99_9')) self.score: SeriesPattern1[StoredI8] = SeriesPattern1(client, _m(acc, 'score')) -class Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern: +class HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClient, acc: str): """Create pattern node with accumulated series name.""" + self.height: SeriesPattern18[[Cents; 19]] = SeriesPattern18(client, _m(acc, 'cents')) self.pct05: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct05')) self.pct10: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct10')) self.pct15: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct15')) @@ -3085,7 +3130,7 @@ class Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct7 self.pct90: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct90')) self.pct95: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct95')) -class Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern: +class Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClient, acc: str): @@ -3109,25 +3154,7 @@ class Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct self.pct99: PpmPriceRatioPattern = PpmPriceRatioPattern(client, acc, 'pct99') self.pct99_5: PpmPriceRatioPattern = PpmPriceRatioPattern(client, acc, 'pct99_5') self.pct99_9: PpmPriceRatioPattern = PpmPriceRatioPattern(client, acc, 'pct99_9') - -class AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'bis')) - self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_empty_outputs_output')) - self.op_return: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_op_return_output')) - self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2a_output')) - self.p2ms: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2ms_output')) - self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2pk33_output')) - self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2pk65_output')) - self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2pkh_output')) - self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2sh_output')) - self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2tr_output')) - self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2wpkh_output')) - self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_p2wsh_output')) - self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'with_unknown_outputs_output')) + self.ratios: SeriesPattern18[[PartsPerMillion32; 19]] = SeriesPattern18(client, _m(acc, 'ratios_ppm')) class _10y1m1w1y2y3m3y4y5y6m6y8yPattern3: """Pattern struct for repeated tree structure.""" @@ -3165,10 +3192,6 @@ class _10y1m1w1y2y3m3y4y5y6m6y8yPattern2: self._6y: PercentPpmRatioPattern = PercentPpmRatioPattern(client, _m(acc, '6y')) self._8y: PercentPpmRatioPattern = PercentPpmRatioPattern(client, _m(acc, '8y')) -class AllEmptyP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern: - """Pattern struct for repeated tree structure.""" - pass - class CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern: """Pattern struct for repeated tree structure.""" @@ -3183,8 +3206,8 @@ class CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern: self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, _m(acc, 'realized_peak_regret')) self.price: CentsPpmRatioSatsUsdPattern = CentsPpmRatioSatsUsdPattern(client, _m(acc, 'realized_price')) self.profit: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, _m(acc, 'realized_profit')) - self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, _m(acc, 'realized_profit_to_loss_ratio')) - self.sell_side_risk_ratio: _1m1w1y24hPattern8 = _1m1w1y24hPattern8(client, _m(acc, 'sell_side_risk_ratio')) + self.profit_to_loss_ratio: _1m1w1y24hHeightPattern2 = _1m1w1y24hHeightPattern2(client, _m(acc, 'realized_profit_to_loss_ratio')) + self.sell_side_risk_ratio: _1m1w1y24hHeightPattern3 = _1m1w1y24hHeightPattern3(client, _m(acc, 'sell_side_risk_ratio')) self.sopr: AdjustedRatioValuePattern = AdjustedRatioValuePattern(client, acc) class EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2: @@ -3205,6 +3228,10 @@ class EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2: self.p2wsh: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, _m(acc, 'p2wsh_output')) self.unknown: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, _m(acc, 'unknown_outputs_output')) +class AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern: + """Pattern struct for repeated tree structure.""" + pass + class AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern: """Pattern struct for repeated tree structure.""" @@ -3256,26 +3283,15 @@ class AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern(Generic[T] self.pct90: _1m1w1y24hPattern[T] = _1m1w1y24hPattern(client, _m(acc, 'pct90')) self.sum: _1m1w1y24hPattern[T] = _1m1w1y24hPattern(client, _m(acc, 'sum')) -class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshSharePattern: +class AllCumulativeP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern: """Pattern struct for repeated tree structure.""" pass -class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6: +class AllHeightP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern: """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, acc) - self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2a', acc)) - self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2pk33', acc)) - self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2pk65', acc)) - self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2pkh', acc)) - self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2sh', acc)) - self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2tr', acc)) - self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2wpkh', acc)) - self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _p('p2wsh', acc)) + pass -class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5: +class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClient, acc: str): @@ -3290,22 +3306,7 @@ class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5: self.p2wpkh: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _p('p2wpkh', acc)) self.p2wsh: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _p('p2wsh', acc)) -class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, acc) - self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2a', acc)) - self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2pk33', acc)) - self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2pk65', acc)) - self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2pkh', acc)) - self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2sh', acc)) - self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2tr', acc)) - self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2wpkh', acc)) - self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, _p('p2wsh', acc)) - -class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7: +class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClient, acc: str): @@ -3365,20 +3366,6 @@ class Pct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern: self.pct80: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct80')) self.pct90: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct90')) -class ChainDataFeeFeesOutputTxPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.chain_share: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'chain_share')) - self.data_bytes: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'data_bytes')) - self.data_share: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'data_share')) - self.fee_share: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, _m(acc, 'fee_share')) - self.fees: AverageBlockCumulativeSumPattern[Sats] = AverageBlockCumulativeSumPattern(client, _m(acc, 'fees')) - self.output_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'output_count')) - self.tx_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'tx_count')) - self.tx_vsize: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, _m(acc, 'tx_vsize')) - class _10y2y3y4y5y6y8yPattern: """Pattern struct for repeated tree structure.""" @@ -3435,19 +3422,6 @@ class CapLossMvrvNetPriceProfitSoprPattern: self.profit: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, _m(acc, 'realized_profit')) self.sopr: RatioValuePattern = RatioValuePattern(client, acc) -class CoindaysDormancySupplyWakefulnessPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.coindays_consumed: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_consumed')) - self.coindays_created: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_created')) - self.coindays_stored: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_stored')) - self.dormancy: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'dormancy')) - self.supply: AwakeDormantPattern = AwakeDormantPattern(client, acc) - self.wakefulness: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'wakefulness')) - self.wakefulness_to_dormancy: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'wakefulness_to_dormancy')) - class InMaxMinPerSupplyPattern: """Pattern struct for repeated tree structure.""" @@ -3457,8 +3431,8 @@ class InMaxMinPerSupplyPattern: self.in_profit: PerPattern = PerPattern(client, _m(acc, 'cost_basis_in_profit_per')) self.max: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'cost_basis_max')) self.min: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'cost_basis_min')) - self.per_coin: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_coin')) - self.per_dollar: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_dollar')) + self.per_coin: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_coin')) + self.per_dollar: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis_per_dollar')) self.supply_density: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'supply_density')) class MaxMedianMinPct10Pct25Pct75Pct90Pattern2: @@ -3487,6 +3461,18 @@ class MaxMedianMinPct10Pct25Pct75Pct90Pattern(Generic[T]): self.pct75: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'pct75')) self.pct90: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'pct90')) +class AverageBlockChainCumulativeDataSumPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self.average: _1m1w1y24hPattern[StoredF32] = _1m1w1y24hPattern(client, _m(acc, 'data_bytes_average')) + self.block: SeriesPattern18[Bytes] = SeriesPattern18(client, _m(acc, 'data_bytes')) + self.chain_share: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'chain_share')) + self.cumulative: SeriesPattern1[Bytes] = SeriesPattern1(client, _m(acc, 'data_bytes_cumulative')) + self.data_share: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'data_share')) + self.sum: _1m1w1y24hPattern[Bytes] = _1m1w1y24hPattern(client, _m(acc, 'data_bytes_sum')) + class AverageBlockCumulativeInSumPattern: """Pattern struct for repeated tree structure.""" @@ -3535,6 +3521,29 @@ class DeltaDominanceHalfInTotalPattern: self.in_profit: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'in_profit')) self.total: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, acc) +class HeightRankTailThresholdPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self.height: SeriesPattern18[[Dollars; 3]] = SeriesPattern18(client, _m(acc, 'thresholds')) + self.rank: SeriesPattern1[StoredU8] = SeriesPattern1(client, _m(acc, 'rank')) + self.tail: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'tail')) + self.threshold_pct0_025: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'threshold')) + self.threshold_pct0_05: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'threshold_pct0_05')) + self.threshold_pct0_1: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'threshold_pct0_1')) + +class _1m1w1y24hHeightPattern3: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self._1m: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '1m')) + self._1w: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '1w')) + self._1y: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '1y')) + self._24h: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '24h')) + self.height: SeriesPattern18[[PartsPerMillion32; 4]] = SeriesPattern18(client, _m(acc, 'ppm')) + class _1m1w1y24hBlockPattern2: """Pattern struct for repeated tree structure.""" @@ -3557,6 +3566,39 @@ class _1m1w1y24hBlockPattern: self._24h: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, 'average_24h')) self.block: SeriesPattern18[StoredU32] = SeriesPattern18(client, acc) +class _1m1w1y24hHeightPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self._1m: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, '1m')) + self._1w: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, '1w')) + self._1y: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, '1y')) + self._24h: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, '24h')) + self.height: SeriesPattern18[[StoredF32; 4]] = SeriesPattern18(client, acc) + +class _1m1w1y24hHeightPattern4: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self._1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '1m')) + self._1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '1w')) + self._1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '1y')) + self._24h: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '24h')) + self.height: SeriesPattern18[[StoredF64; 3]] = SeriesPattern18(client, acc) + +class _1m1w1y24hHeightPattern2: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self._1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '1m')) + self._1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '1w')) + self._1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '1y')) + self._24h: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, '24h')) + self.height: SeriesPattern18[[StoredF64; 4]] = SeriesPattern18(client, acc) + class ActiveBidirectionalReactivatedReceivingSendingPattern: """Pattern struct for repeated tree structure.""" @@ -3612,6 +3654,17 @@ class ActivityOutputsRealizedSupplyUnrealizedPattern2: self.supply: DeltaDominanceTotalPattern = DeltaDominanceTotalPattern(client, _m(acc, 'supply')) self.unrealized: NuplPattern = NuplPattern(client, _m(acc, 'nupl')) +class AverageBlockCumulativeFeeSumPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClient, acc: str): + """Create pattern node with accumulated series name.""" + self.average: _1m1w1y24hPattern[StoredF32] = _1m1w1y24hPattern(client, _m(acc, 'fees_average')) + self.block: SeriesPattern18[Sats] = SeriesPattern18(client, _m(acc, 'fees')) + self.cumulative: SeriesPattern1[Sats] = SeriesPattern1(client, _m(acc, 'fees_cumulative')) + self.fee_share: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, _m(acc, 'fee_share')) + self.sum: _1m1w1y24hPattern[Sats] = _1m1w1y24hPattern(client, _m(acc, 'fees_sum')) + class BlockChangeCumulativeDeltaSumPattern: """Pattern struct for repeated tree structure.""" @@ -3719,16 +3772,9 @@ class PhsReboundThsPattern: self.ths: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, 'ths')) self.ths_min: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, 'ths_min')) -class RankTailThresholdPattern: +class CumulativeMultipleOversizedPrePattern3(Generic[T]): """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.rank: SeriesPattern1[StoredU8] = SeriesPattern1(client, _m(acc, 'rank')) - self.tail: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, 'tail')) - self.threshold_pct0_025: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'threshold')) - self.threshold_pct0_05: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'threshold_pct0_05')) - self.threshold_pct0_1: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'threshold_pct0_1')) + pass class _1m1w1y24hPattern4: """Pattern struct for repeated tree structure.""" @@ -3800,16 +3846,6 @@ class _1m1w1y24hPattern2: self._1y: PercentPpmRatioPattern = PercentPpmRatioPattern(client, _m(acc, '1y_rate')) self._24h: PercentPpmRatioPattern = PercentPpmRatioPattern(client, _m(acc, '24h_rate')) -class _1m1w1y24hPattern8: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self._1m: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '1m')) - self._1w: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '1w')) - self._1y: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '1y')) - self._24h: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, _m(acc, '24h')) - class AverageBlockCumulativeSumPattern2: """Pattern struct for repeated tree structure.""" @@ -3901,7 +3937,7 @@ class CoindaysCoinyearsDormancyTransferPattern: """Create pattern node with accumulated series name.""" self.coindays_destroyed: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, _m(acc, 'coindays_destroyed')) self.coinyears_destroyed: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'coinyears_destroyed')) - self.dormancy: _1m1w1y24hPattern[StoredF32] = _1m1w1y24hPattern(client, _m(acc, 'dormancy')) + self.dormancy: _1m1w1y24hHeightPattern = _1m1w1y24hHeightPattern(client, _m(acc, 'dormancy')) self.transfer_volume: AverageBlockCumulativeInSumPattern = AverageBlockCumulativeInSumPattern(client, _m(acc, 'transfer_volume')) class LossNetNuplProfitPattern: @@ -3914,16 +3950,6 @@ class LossNetNuplProfitPattern: self.nupl: PpmRatioPattern = PpmRatioPattern(client, _m(acc, 'nupl')) self.profit: CentsUsdPattern3 = CentsUsdPattern3(client, _m(acc, 'unrealized_profit')) -class MobilitySpendingSupplyPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.mobility: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'mobility')) - self.spending_exposure: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'spending_exposure')) - self.spending_rate: SeriesPattern1[StoredF64] = SeriesPattern1(client, _m(acc, 'spending_rate')) - self.supply: ImmobileMobilePattern = ImmobileMobilePattern(client, acc) - class NuplRealizedSupplyUnrealizedPattern: """Pattern struct for repeated tree structure.""" @@ -3960,7 +3986,7 @@ class AdjustedRatioValuePattern: def __init__(self, client: BrkClient, acc: str): """Create pattern node with accumulated series name.""" self.adjusted: RatioTransferValuePattern = RatioTransferValuePattern(client, acc) - self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, _m(acc, 'sopr')) + self.ratio: _1m1w1y24hHeightPattern4 = _1m1w1y24hHeightPattern4(client, _m(acc, 'sopr')) self.value_destroyed: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, _m(acc, 'value_destroyed')) class BlockCumulativeSumPattern: @@ -4138,7 +4164,7 @@ class RatioTransferValuePattern: def __init__(self, client: BrkClient, acc: str): """Create pattern node with accumulated series name.""" - self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, _m(acc, 'asopr')) + self.ratio: _1m1w1y24hHeightPattern2 = _1m1w1y24hHeightPattern2(client, _m(acc, 'asopr')) self.transfer_volume: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, _m(acc, 'adj_value_created')) self.value_destroyed: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, _m(acc, 'adj_value_destroyed')) @@ -4208,14 +4234,6 @@ class AllSthPattern: self.all: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, disc)) self.sth: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, f'sth_{disc}')) -class AwakeDormantPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.awake: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'awake_supply')) - self.dormant: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'dormant_supply')) - class AwakeDormantPattern2: """Pattern struct for repeated tree structure.""" pass @@ -4302,24 +4320,12 @@ class CoindaysTransferPattern: class FundedTotalPattern: """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.funded: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, acc) - self.total: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, _p('total', acc)) + pass class ImmobileMobilePattern2: """Pattern struct for repeated tree structure.""" pass -class ImmobileMobilePattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClient, acc: str): - """Create pattern node with accumulated series name.""" - self.immobile: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'immobile_supply')) - self.mobile: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'mobile_supply')) - class InPattern2: """Pattern struct for repeated tree structure.""" @@ -4819,6 +4825,7 @@ class SeriesTree_Inputs_ByType_InputCount: self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2a_prevout_count') self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'unknown_outputs_prevout_count') self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'empty_outputs_prevout_count') + self.height: SeriesPattern18[[StoredU16; 11]] = SeriesPattern18(client, 'prevout_count_by_type') class SeriesTree_Inputs_ByType_InputShare: """Series tree node.""" @@ -4852,6 +4859,7 @@ class SeriesTree_Inputs_ByType_TxCount: self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2a_prevout') self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_unknown_outputs_prevout') self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_empty_outputs_prevout') + self.cumulative: SeriesPattern18[[StoredU64; 11]] = SeriesPattern18(client, 'tx_count_with_prevout_by_type_cumulative') class SeriesTree_Inputs_ByType: """Series tree node.""" @@ -4916,6 +4924,7 @@ class SeriesTree_Outputs_ByType_OutputCount: self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'unknown_outputs_output_count') self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'empty_outputs_output_count') self.op_return: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_output_count') + self.height: SeriesPattern18[[StoredU16; 12]] = SeriesPattern18(client, 'output_count_by_type') class SeriesTree_Outputs_ByType_OutputShare: """Series tree node.""" @@ -4934,6 +4943,25 @@ class SeriesTree_Outputs_ByType_OutputShare: self.empty: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, 'empty_outputs_output_share') self.op_return: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, 'op_return_output_share') +class SeriesTree_Outputs_ByType_TxCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_bis') + self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2pk65_output') + self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2pk33_output') + self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2pkh_output') + self.p2ms: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2ms_output') + self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2sh_output') + self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2wpkh_output') + self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2wsh_output') + self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2tr_output') + self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_p2a_output') + self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_unknown_outputs_output') + self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_empty_outputs_output') + self.op_return: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'tx_count_with_op_return_output') + self.cumulative: SeriesPattern18[[StoredU64; 12]] = SeriesPattern18(client, 'tx_count_with_output_by_type_cumulative') + class SeriesTree_Outputs_ByType: """Series tree node.""" @@ -4941,7 +4969,7 @@ class SeriesTree_Outputs_ByType: self.output_count: SeriesTree_Outputs_ByType_OutputCount = SeriesTree_Outputs_ByType_OutputCount(client) self.spendable_output_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'spendable_output_count') self.output_share: SeriesTree_Outputs_ByType_OutputShare = SeriesTree_Outputs_ByType_OutputShare(client) - self.tx_count: AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern = AllEmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern(client, 'tx_count') + self.tx_count: SeriesTree_Outputs_ByType_TxCount = SeriesTree_Outputs_ByType_TxCount(client) self.tx_share: EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2 = EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2(client, 'tx_share_with') class SeriesTree_Outputs_Value: @@ -5053,6 +5081,36 @@ class SeriesTree_Addrs_Data: self.funded: SeriesPattern34[FundedAddrData] = SeriesPattern34(client, 'funded_addr_data') self.empty: SeriesPattern35[EmptyAddrData] = SeriesPattern35(client, 'empty_addr_data') +class SeriesTree_Addrs_Funded: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'addr_count_by_type') + +class SeriesTree_Addrs_Empty: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'empty_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_empty_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_empty_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_empty_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_empty_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_empty_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_empty_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_empty_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_empty_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'empty_addr_count_by_type') + class SeriesTree_Addrs_Activity_All: """Series tree node.""" @@ -5077,15 +5135,111 @@ class SeriesTree_Addrs_Activity: self.p2tr: ActiveBidirectionalReactivatedReceivingSendingPattern = ActiveBidirectionalReactivatedReceivingSendingPattern(client, 'p2tr') self.p2a: ActiveBidirectionalReactivatedReceivingSendingPattern = ActiveBidirectionalReactivatedReceivingSendingPattern(client, 'p2a') +class SeriesTree_Addrs_Total: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'total_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_total_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_total_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_total_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_total_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_total_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_total_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_total_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_total_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'total_addr_count_by_type') + +class SeriesTree_Addrs_New: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'new_addr_count') + self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk65_new_addr_count') + self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk33_new_addr_count') + self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pkh_new_addr_count') + self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2sh_new_addr_count') + self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wpkh_new_addr_count') + self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wsh_new_addr_count') + self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2tr_new_addr_count') + self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2a_new_addr_count') + +class SeriesTree_Addrs_Reused_Count_Funded: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'reused_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_reused_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_reused_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_reused_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_reused_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_reused_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_reused_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_reused_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_reused_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'reused_addr_count_by_type') + +class SeriesTree_Addrs_Reused_Count_Total: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'total_reused_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_total_reused_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_total_reused_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_total_reused_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_total_reused_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_total_reused_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_total_reused_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_total_reused_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_total_reused_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'total_reused_addr_count_by_type') + +class SeriesTree_Addrs_Reused_Count: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.funded: SeriesTree_Addrs_Reused_Count_Funded = SeriesTree_Addrs_Reused_Count_Funded(client) + self.total: SeriesTree_Addrs_Reused_Count_Total = SeriesTree_Addrs_Reused_Count_Total(client) + +class SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'output_to_reused_addr_count') + self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk65_output_to_reused_addr_count') + self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk33_output_to_reused_addr_count') + self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pkh_output_to_reused_addr_count') + self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2sh_output_to_reused_addr_count') + self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wpkh_output_to_reused_addr_count') + self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wsh_output_to_reused_addr_count') + self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2tr_output_to_reused_addr_count') + self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2a_output_to_reused_addr_count') + self.cumulative: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'output_to_reused_addr_count_by_type_cumulative') + +class SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'input_from_reused_addr_count') + self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk65_input_from_reused_addr_count') + self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk33_input_from_reused_addr_count') + self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pkh_input_from_reused_addr_count') + self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2sh_input_from_reused_addr_count') + self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wpkh_input_from_reused_addr_count') + self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wsh_input_from_reused_addr_count') + self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2tr_input_from_reused_addr_count') + self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2a_input_from_reused_addr_count') + self.cumulative: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'input_from_reused_addr_count_by_type_cumulative') + class SeriesTree_Addrs_Reused_Events: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'output_to_reused_addr_count') - self.output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(client, 'output_to_reused_addr_share') + self.output_to_reused_addr_count: SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount = SeriesTree_Addrs_Reused_Events_OutputToReusedAddrCount(client) + self.output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'output_to_reused_addr_share') self.spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, 'spendable_output_to_reused_addr_share') - self.input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'input_from_reused_addr_count') - self.input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(client, 'input_from_reused_addr_share') + self.input_from_reused_addr_count: SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount = SeriesTree_Addrs_Reused_Events_InputFromReusedAddrCount(client) + self.input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'input_from_reused_addr_share') self.active_reused_addr_count: _1m1w1y24hBlockPattern = _1m1w1y24hBlockPattern(client, 'active_reused_addr_count') self.active_reused_addr_share: _1m1w1y24hBlockPattern2 = _1m1w1y24hBlockPattern2(client, 'active_reused_addr_share') @@ -5102,25 +5256,93 @@ class SeriesTree_Addrs_Reused_Supply: self.p2wsh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wsh_reused_addr_supply') self.p2tr: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2tr_reused_addr_supply') self.p2a: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2a_reused_addr_supply') - self.share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(client, 'reused_addr_supply_share') + self.height: SeriesPattern18[[Sats; 8]] = SeriesPattern18(client, 'reused_addr_supply_sats_by_type') + self.share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, 'reused_addr_supply_share') class SeriesTree_Addrs_Reused: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.count: FundedTotalPattern = FundedTotalPattern(client, 'reused_addr_count') + self.count: SeriesTree_Addrs_Reused_Count = SeriesTree_Addrs_Reused_Count(client) self.events: SeriesTree_Addrs_Reused_Events = SeriesTree_Addrs_Reused_Events(client) self.supply: SeriesTree_Addrs_Reused_Supply = SeriesTree_Addrs_Reused_Supply(client) +class SeriesTree_Addrs_Respent_Count_Funded: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'respent_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_respent_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_respent_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_respent_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_respent_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_respent_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_respent_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_respent_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_respent_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'respent_addr_count_by_type') + +class SeriesTree_Addrs_Respent_Count_Total: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'total_respent_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_total_respent_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_total_respent_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_total_respent_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_total_respent_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_total_respent_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_total_respent_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_total_respent_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_total_respent_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'total_respent_addr_count_by_type') + +class SeriesTree_Addrs_Respent_Count: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.funded: SeriesTree_Addrs_Respent_Count_Funded = SeriesTree_Addrs_Respent_Count_Funded(client) + self.total: SeriesTree_Addrs_Respent_Count_Total = SeriesTree_Addrs_Respent_Count_Total(client) + +class SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'output_to_respent_addr_count') + self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk65_output_to_respent_addr_count') + self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk33_output_to_respent_addr_count') + self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pkh_output_to_respent_addr_count') + self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2sh_output_to_respent_addr_count') + self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wpkh_output_to_respent_addr_count') + self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wsh_output_to_respent_addr_count') + self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2tr_output_to_respent_addr_count') + self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2a_output_to_respent_addr_count') + self.cumulative: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'output_to_respent_addr_count_by_type_cumulative') + +class SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'input_from_respent_addr_count') + self.p2pk65: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk65_input_from_respent_addr_count') + self.p2pk33: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pk33_input_from_respent_addr_count') + self.p2pkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2pkh_input_from_respent_addr_count') + self.p2sh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2sh_input_from_respent_addr_count') + self.p2wpkh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wpkh_input_from_respent_addr_count') + self.p2wsh: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2wsh_input_from_respent_addr_count') + self.p2tr: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2tr_input_from_respent_addr_count') + self.p2a: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'p2a_input_from_respent_addr_count') + self.cumulative: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'input_from_respent_addr_count_by_type_cumulative') + class SeriesTree_Addrs_Respent_Events: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.output_to_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'output_to_respent_addr_count') - self.output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(client, 'output_to_respent_addr_share') + self.output_to_reused_addr_count: SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount = SeriesTree_Addrs_Respent_Events_OutputToReusedAddrCount(client) + self.output_to_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'output_to_respent_addr_share') self.spendable_output_to_reused_addr_share: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, 'spendable_output_to_respent_addr_share') - self.input_from_reused_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'input_from_respent_addr_count') - self.input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern7(client, 'input_from_respent_addr_share') + self.input_from_reused_addr_count: SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount = SeriesTree_Addrs_Respent_Events_InputFromReusedAddrCount(client) + self.input_from_reused_addr_share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'input_from_respent_addr_share') self.active_reused_addr_count: _1m1w1y24hBlockPattern = _1m1w1y24hBlockPattern(client, 'active_respent_addr_count') self.active_reused_addr_share: _1m1w1y24hBlockPattern2 = _1m1w1y24hBlockPattern2(client, 'active_respent_addr_share') @@ -5137,16 +5359,54 @@ class SeriesTree_Addrs_Respent_Supply: self.p2wsh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wsh_respent_addr_supply') self.p2tr: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2tr_respent_addr_supply') self.p2a: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2a_respent_addr_supply') - self.share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(client, 'respent_addr_supply_share') + self.height: SeriesPattern18[[Sats; 8]] = SeriesPattern18(client, 'respent_addr_supply_sats_by_type') + self.share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, 'respent_addr_supply_share') class SeriesTree_Addrs_Respent: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.count: FundedTotalPattern = FundedTotalPattern(client, 'respent_addr_count') + self.count: SeriesTree_Addrs_Respent_Count = SeriesTree_Addrs_Respent_Count(client) self.events: SeriesTree_Addrs_Respent_Events = SeriesTree_Addrs_Respent_Events(client) self.supply: SeriesTree_Addrs_Respent_Supply = SeriesTree_Addrs_Respent_Supply(client) +class SeriesTree_Addrs_Exposed_Count_Funded: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'exposed_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_exposed_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_exposed_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_exposed_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_exposed_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_exposed_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_exposed_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_exposed_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_exposed_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'exposed_addr_count_by_type') + +class SeriesTree_Addrs_Exposed_Count_Total: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.all: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'total_exposed_addr_count') + self.p2pk65: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk65_total_exposed_addr_count') + self.p2pk33: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pk33_total_exposed_addr_count') + self.p2pkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2pkh_total_exposed_addr_count') + self.p2sh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2sh_total_exposed_addr_count') + self.p2wpkh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wpkh_total_exposed_addr_count') + self.p2wsh: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2wsh_total_exposed_addr_count') + self.p2tr: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2tr_total_exposed_addr_count') + self.p2a: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'p2a_total_exposed_addr_count') + self.height: SeriesPattern18[[StoredU64; 8]] = SeriesPattern18(client, 'total_exposed_addr_count_by_type') + +class SeriesTree_Addrs_Exposed_Count: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.funded: SeriesTree_Addrs_Exposed_Count_Funded = SeriesTree_Addrs_Exposed_Count_Funded(client) + self.total: SeriesTree_Addrs_Exposed_Count_Total = SeriesTree_Addrs_Exposed_Count_Total(client) + class SeriesTree_Addrs_Exposed_Supply: """Series tree node.""" @@ -5160,13 +5420,14 @@ class SeriesTree_Addrs_Exposed_Supply: self.p2wsh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wsh_exposed_addr_supply') self.p2tr: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2tr_exposed_addr_supply') self.p2a: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2a_exposed_addr_supply') - self.share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5(client, 'exposed_addr_supply_share') + self.height: SeriesPattern18[[Sats; 8]] = SeriesPattern18(client, 'exposed_addr_supply_sats_by_type') + self.share: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, 'exposed_addr_supply_share') class SeriesTree_Addrs_Exposed: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.count: FundedTotalPattern = FundedTotalPattern(client, 'exposed_addr_count') + self.count: SeriesTree_Addrs_Exposed_Count = SeriesTree_Addrs_Exposed_Count(client) self.supply: SeriesTree_Addrs_Exposed_Supply = SeriesTree_Addrs_Exposed_Supply(client) class SeriesTree_Addrs_Delta: @@ -5204,11 +5465,11 @@ class SeriesTree_Addrs: self.raw: SeriesTree_Addrs_Raw = SeriesTree_Addrs_Raw(client) self.indexes: SeriesTree_Addrs_Indexes = SeriesTree_Addrs_Indexes(client) self.data: SeriesTree_Addrs_Data = SeriesTree_Addrs_Data(client) - self.funded: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, 'addr_count') - self.empty: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, 'empty_addr_count') + self.funded: SeriesTree_Addrs_Funded = SeriesTree_Addrs_Funded(client) + self.empty: SeriesTree_Addrs_Empty = SeriesTree_Addrs_Empty(client) self.activity: SeriesTree_Addrs_Activity = SeriesTree_Addrs_Activity(client) - self.total: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern4(client, 'total_addr_count') - self.new: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6 = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern6(client, 'new_addr_count') + self.total: SeriesTree_Addrs_Total = SeriesTree_Addrs_Total(client) + self.new: SeriesTree_Addrs_New = SeriesTree_Addrs_New(client) self.reused: SeriesTree_Addrs_Reused = SeriesTree_Addrs_Reused(client) self.respent: SeriesTree_Addrs_Respent = SeriesTree_Addrs_Respent(client) self.exposed: SeriesTree_Addrs_Exposed = SeriesTree_Addrs_Exposed(client) @@ -5265,49 +5526,227 @@ class SeriesTree_OpReturn_Total: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.data_bytes: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_data_bytes') + self.data_bytes: AverageBlockCumulativeSumPattern[Bytes] = AverageBlockCumulativeSumPattern(client, 'op_return_data_bytes') self.tx_count: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_tx_count') self.tx_vsize: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_tx_vsize') self.fees: AverageBlockCumulativeSumPattern[Sats] = AverageBlockCumulativeSumPattern(client, 'op_return_fees') self.chain_share: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, 'op_return_chain_share') self.fee_share: _1m1w1y24hPercentPpmRatioPattern = _1m1w1y24hPercentPpmRatioPattern(client, 'op_return_fee_share') +class SeriesTree_OpReturn_ByKind_OutputCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.runes: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_runes_output_count') + self.veri_block: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_veri_block_output_count') + self.omni: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_omni_output_count') + self.stacks: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_stacks_output_count') + self.blockstack: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_blockstack_output_count') + self.colu: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_colu_output_count') + self.open_assets: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_open_assets_output_count') + self.komodo: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_komodo_output_count') + self.coin_spark: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_coin_spark_output_count') + self.poet: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_poet_output_count') + self.docproof: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_docproof_output_count') + self.open_timestamps: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_open_timestamps_output_count') + self.factom: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_factom_output_count') + self.eternity_wall: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_eternity_wall_output_count') + self.memo: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_memo_output_count') + self.bitproof: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_bitproof_output_count') + self.ascribe: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_ascribe_output_count') + self.stampery: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_stampery_output_count') + self.epobc: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_epobc_output_count') + self.bare_hash: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_bare_hash_output_count') + self.text: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_text_output_count') + self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_empty_output_count') + self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_unknown_output_count') + self.cumulative: SeriesPattern18[[StoredU64; 23]] = SeriesPattern18(client, 'op_return_cumulative_by_kind_output_count') + +class SeriesTree_OpReturn_ByKind_DataBytes: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.runes: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_runes') + self.veri_block: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_veri_block') + self.omni: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_omni') + self.stacks: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_stacks') + self.blockstack: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_blockstack') + self.colu: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_colu') + self.open_assets: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_open_assets') + self.komodo: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_komodo') + self.coin_spark: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_coin_spark') + self.poet: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_poet') + self.docproof: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_docproof') + self.open_timestamps: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_open_timestamps') + self.factom: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_factom') + self.eternity_wall: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_eternity_wall') + self.memo: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_memo') + self.bitproof: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_bitproof') + self.ascribe: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_ascribe') + self.stampery: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_stampery') + self.epobc: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_epobc') + self.bare_hash: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_bare_hash') + self.text: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_text') + self.empty: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_empty') + self.unknown: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_unknown') + self.cumulative: SeriesPattern18[[Bytes; 23]] = SeriesPattern18(client, 'op_return_cumulative_by_kind_data_bytes') + +class SeriesTree_OpReturn_ByKind_TxCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.runes: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_runes_tx_count') + self.veri_block: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_veri_block_tx_count') + self.omni: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_omni_tx_count') + self.stacks: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_stacks_tx_count') + self.blockstack: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_blockstack_tx_count') + self.colu: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_colu_tx_count') + self.open_assets: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_open_assets_tx_count') + self.komodo: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_komodo_tx_count') + self.coin_spark: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_coin_spark_tx_count') + self.poet: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_poet_tx_count') + self.docproof: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_docproof_tx_count') + self.open_timestamps: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_open_timestamps_tx_count') + self.factom: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_factom_tx_count') + self.eternity_wall: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_eternity_wall_tx_count') + self.memo: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_memo_tx_count') + self.bitproof: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_bitproof_tx_count') + self.ascribe: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_ascribe_tx_count') + self.stampery: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_stampery_tx_count') + self.epobc: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_epobc_tx_count') + self.bare_hash: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_bare_hash_tx_count') + self.text: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_text_tx_count') + self.empty: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_empty_tx_count') + self.unknown: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_unknown_tx_count') + self.cumulative: SeriesPattern18[[StoredU64; 23]] = SeriesPattern18(client, 'op_return_cumulative_by_kind_tx_count') + +class SeriesTree_OpReturn_ByKind_TxVsize: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.runes: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_runes_tx_vsize') + self.veri_block: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_veri_block_tx_vsize') + self.omni: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_omni_tx_vsize') + self.stacks: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_stacks_tx_vsize') + self.blockstack: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_blockstack_tx_vsize') + self.colu: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_colu_tx_vsize') + self.open_assets: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_open_assets_tx_vsize') + self.komodo: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_komodo_tx_vsize') + self.coin_spark: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_coin_spark_tx_vsize') + self.poet: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_poet_tx_vsize') + self.docproof: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_docproof_tx_vsize') + self.open_timestamps: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_open_timestamps_tx_vsize') + self.factom: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_factom_tx_vsize') + self.eternity_wall: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_eternity_wall_tx_vsize') + self.memo: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_memo_tx_vsize') + self.bitproof: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_bitproof_tx_vsize') + self.ascribe: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_ascribe_tx_vsize') + self.stampery: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_stampery_tx_vsize') + self.epobc: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_epobc_tx_vsize') + self.bare_hash: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_bare_hash_tx_vsize') + self.text: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_text_tx_vsize') + self.empty: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_empty_tx_vsize') + self.unknown: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_unknown_tx_vsize') + self.cumulative: SeriesPattern18[[VSize; 23]] = SeriesPattern18(client, 'op_return_cumulative_by_kind_tx_vsize') + +class SeriesTree_OpReturn_ByKind_Fees: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.runes: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_runes') + self.veri_block: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_veri_block') + self.omni: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_omni') + self.stacks: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_stacks') + self.blockstack: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_blockstack') + self.colu: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_colu') + self.open_assets: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_open_assets') + self.komodo: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_komodo') + self.coin_spark: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_coin_spark') + self.poet: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_poet') + self.docproof: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_docproof') + self.open_timestamps: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_open_timestamps') + self.factom: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_factom') + self.eternity_wall: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_eternity_wall') + self.memo: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_memo') + self.bitproof: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_bitproof') + self.ascribe: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_ascribe') + self.stampery: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_stampery') + self.epobc: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_epobc') + self.bare_hash: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_bare_hash') + self.text: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_text') + self.empty: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_empty') + self.unknown: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_unknown') + self.cumulative: SeriesPattern18[[Sats; 23]] = SeriesPattern18(client, 'op_return_cumulative_by_kind_fees') + class SeriesTree_OpReturn_ByKind: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.runes: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_runes') - self.veri_block: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_veri_block') - self.omni: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_omni') - self.stacks: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_stacks') - self.blockstack: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_blockstack') - self.colu: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_colu') - self.open_assets: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_open_assets') - self.komodo: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_komodo') - self.coin_spark: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_coin_spark') - self.poet: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_poet') - self.docproof: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_docproof') - self.open_timestamps: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_open_timestamps') - self.factom: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_factom') - self.eternity_wall: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_eternity_wall') - self.memo: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_memo') - self.bitproof: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_bitproof') - self.ascribe: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_ascribe') - self.stampery: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_stampery') - self.epobc: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_epobc') - self.bare_hash: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_bare_hash') - self.text: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_text') - self.empty: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_empty') - self.unknown: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_unknown') + self.output_count: SeriesTree_OpReturn_ByKind_OutputCount = SeriesTree_OpReturn_ByKind_OutputCount(client) + self.data_bytes: SeriesTree_OpReturn_ByKind_DataBytes = SeriesTree_OpReturn_ByKind_DataBytes(client) + self.tx_count: SeriesTree_OpReturn_ByKind_TxCount = SeriesTree_OpReturn_ByKind_TxCount(client) + self.tx_vsize: SeriesTree_OpReturn_ByKind_TxVsize = SeriesTree_OpReturn_ByKind_TxVsize(client) + self.fees: SeriesTree_OpReturn_ByKind_Fees = SeriesTree_OpReturn_ByKind_Fees(client) + +class SeriesTree_OpReturn_Policy_OutputCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.pre_v30_standard: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_pre_v30_standard_output_count') + self.pre_v30_nonstandard: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_pre_v30_nonstandard_output_count') + self.oversized: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_oversized_output_count') + self.multiple: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_multiple_output_count') + self.cumulative: SeriesPattern18[[StoredU64; 4]] = SeriesPattern18(client, 'op_return_cumulative_policy_output_count') + +class SeriesTree_OpReturn_Policy_DataBytes: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.pre_v30_standard: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_policy_pre_v30_standard') + self.pre_v30_nonstandard: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_policy_pre_v30_nonstandard') + self.oversized: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_policy_oversized') + self.multiple: AverageBlockChainCumulativeDataSumPattern = AverageBlockChainCumulativeDataSumPattern(client, 'op_return_policy_multiple') + self.cumulative: SeriesPattern18[[Bytes; 4]] = SeriesPattern18(client, 'op_return_cumulative_policy_data_bytes') + +class SeriesTree_OpReturn_Policy_TxCount: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.pre_v30_standard: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_pre_v30_standard_tx_count') + self.pre_v30_nonstandard: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_pre_v30_nonstandard_tx_count') + self.oversized: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_oversized_tx_count') + self.multiple: AverageBlockCumulativeSumPattern[StoredU64] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_multiple_tx_count') + self.cumulative: SeriesPattern18[[StoredU64; 4]] = SeriesPattern18(client, 'op_return_cumulative_policy_tx_count') + +class SeriesTree_OpReturn_Policy_TxVsize: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.pre_v30_standard: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_pre_v30_standard_tx_vsize') + self.pre_v30_nonstandard: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_pre_v30_nonstandard_tx_vsize') + self.oversized: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_oversized_tx_vsize') + self.multiple: AverageBlockCumulativeSumPattern[VSize] = AverageBlockCumulativeSumPattern(client, 'op_return_policy_multiple_tx_vsize') + self.cumulative: SeriesPattern18[[VSize; 4]] = SeriesPattern18(client, 'op_return_cumulative_policy_tx_vsize') + +class SeriesTree_OpReturn_Policy_Fees: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.pre_v30_standard: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_policy_pre_v30_standard') + self.pre_v30_nonstandard: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_policy_pre_v30_nonstandard') + self.oversized: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_policy_oversized') + self.multiple: AverageBlockCumulativeFeeSumPattern = AverageBlockCumulativeFeeSumPattern(client, 'op_return_policy_multiple') + self.cumulative: SeriesPattern18[[Sats; 4]] = SeriesPattern18(client, 'op_return_cumulative_policy_fees') class SeriesTree_OpReturn_Policy: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.pre_v30_standard: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_policy_pre_v30_standard') - self.pre_v30_nonstandard: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_policy_pre_v30_nonstandard') - self.oversized: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_policy_oversized') - self.multiple: ChainDataFeeFeesOutputTxPattern = ChainDataFeeFeesOutputTxPattern(client, 'op_return_policy_multiple') + self.output_count: SeriesTree_OpReturn_Policy_OutputCount = SeriesTree_OpReturn_Policy_OutputCount(client) + self.data_bytes: SeriesTree_OpReturn_Policy_DataBytes = SeriesTree_OpReturn_Policy_DataBytes(client) + self.tx_count: SeriesTree_OpReturn_Policy_TxCount = SeriesTree_OpReturn_Policy_TxCount(client) + self.tx_vsize: SeriesTree_OpReturn_Policy_TxVsize = SeriesTree_OpReturn_Policy_TxVsize(client) + self.fees: SeriesTree_OpReturn_Policy_Fees = SeriesTree_OpReturn_Policy_Fees(client) class SeriesTree_OpReturn: """Series tree node.""" @@ -5408,33 +5847,260 @@ class SeriesTree_Frameworks_Cointime_Activity: self.vaultedness: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'vaultedness') self.ratio: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'activity_to_vaultedness') +class SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_under_1h_old_coindays_created') + self._1h_to_1d: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1h_to_1d_old_coindays_created') + self._1d_to_1w: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1d_to_1w_old_coindays_created') + self._1w_to_1m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1w_to_1m_old_coindays_created') + self._1m_to_2m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1m_to_2m_old_coindays_created') + self._2m_to_3m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_2m_to_3m_old_coindays_created') + self._3m_to_4m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_3m_to_4m_old_coindays_created') + self._4m_to_5m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_4m_to_5m_old_coindays_created') + self._5m_to_6m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_5m_to_6m_old_coindays_created') + self._6m_to_9m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_6m_to_9m_old_coindays_created') + self._9m_to_1y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_9m_to_1y_old_coindays_created') + self._1y_to_18m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1y_to_18m_old_coindays_created') + self._18m_to_2y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_18m_to_2y_old_coindays_created') + self._2y_to_3y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_2y_to_3y_old_coindays_created') + self._3y_to_4y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_3y_to_4y_old_coindays_created') + self._4y_to_5y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_4y_to_5y_old_coindays_created') + self._5y_to_6y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_5y_to_6y_old_coindays_created') + self._6y_to_7y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_6y_to_7y_old_coindays_created') + self._7y_to_8y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_7y_to_8y_old_coindays_created') + self._8y_to_10y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_8y_to_10y_old_coindays_created') + self._10y_to_12y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_10y_to_12y_old_coindays_created') + self._12y_to_15y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_12y_to_15y_old_coindays_created') + self.over_15y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_over_15y_old_coindays_created') + self.cumulative: SeriesPattern18[[StoredF64; 23]] = SeriesPattern18(client, 'utxos_age_range_coindays_created_cumulative') + +class SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_under_1h_old_coindays_consumed') + self._1h_to_1d: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1h_to_1d_old_coindays_consumed') + self._1d_to_1w: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1d_to_1w_old_coindays_consumed') + self._1w_to_1m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1w_to_1m_old_coindays_consumed') + self._1m_to_2m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1m_to_2m_old_coindays_consumed') + self._2m_to_3m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_2m_to_3m_old_coindays_consumed') + self._3m_to_4m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_3m_to_4m_old_coindays_consumed') + self._4m_to_5m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_4m_to_5m_old_coindays_consumed') + self._5m_to_6m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_5m_to_6m_old_coindays_consumed') + self._6m_to_9m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_6m_to_9m_old_coindays_consumed') + self._9m_to_1y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_9m_to_1y_old_coindays_consumed') + self._1y_to_18m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1y_to_18m_old_coindays_consumed') + self._18m_to_2y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_18m_to_2y_old_coindays_consumed') + self._2y_to_3y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_2y_to_3y_old_coindays_consumed') + self._3y_to_4y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_3y_to_4y_old_coindays_consumed') + self._4y_to_5y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_4y_to_5y_old_coindays_consumed') + self._5y_to_6y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_5y_to_6y_old_coindays_consumed') + self._6y_to_7y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_6y_to_7y_old_coindays_consumed') + self._7y_to_8y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_7y_to_8y_old_coindays_consumed') + self._8y_to_10y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_8y_to_10y_old_coindays_consumed') + self._10y_to_12y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_10y_to_12y_old_coindays_consumed') + self._12y_to_15y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_12y_to_15y_old_coindays_consumed') + self.over_15y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_over_15y_old_coindays_consumed') + self.cumulative: SeriesPattern18[[StoredF64; 23]] = SeriesPattern18(client, 'utxos_age_range_coindays_consumed_cumulative') + +class SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_under_1h_old_coindays_stored') + self._1h_to_1d: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1h_to_1d_old_coindays_stored') + self._1d_to_1w: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1d_to_1w_old_coindays_stored') + self._1w_to_1m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1w_to_1m_old_coindays_stored') + self._1m_to_2m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1m_to_2m_old_coindays_stored') + self._2m_to_3m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_2m_to_3m_old_coindays_stored') + self._3m_to_4m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_3m_to_4m_old_coindays_stored') + self._4m_to_5m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_4m_to_5m_old_coindays_stored') + self._5m_to_6m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_5m_to_6m_old_coindays_stored') + self._6m_to_9m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_6m_to_9m_old_coindays_stored') + self._9m_to_1y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_9m_to_1y_old_coindays_stored') + self._1y_to_18m: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_1y_to_18m_old_coindays_stored') + self._18m_to_2y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_18m_to_2y_old_coindays_stored') + self._2y_to_3y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_2y_to_3y_old_coindays_stored') + self._3y_to_4y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_3y_to_4y_old_coindays_stored') + self._4y_to_5y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_4y_to_5y_old_coindays_stored') + self._5y_to_6y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_5y_to_6y_old_coindays_stored') + self._6y_to_7y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_6y_to_7y_old_coindays_stored') + self._7y_to_8y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_7y_to_8y_old_coindays_stored') + self._8y_to_10y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_8y_to_10y_old_coindays_stored') + self._10y_to_12y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_10y_to_12y_old_coindays_stored') + self._12y_to_15y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_12y_to_15y_old_coindays_stored') + self.over_15y: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'utxos_over_15y_old_coindays_stored') + self.cumulative: SeriesPattern18[[StoredF64; 23]] = SeriesPattern18(client, 'utxos_age_range_coindays_stored_cumulative') + +class SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_under_1h_old_wakefulness') + self._1h_to_1d: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1h_to_1d_old_wakefulness') + self._1d_to_1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1d_to_1w_old_wakefulness') + self._1w_to_1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1w_to_1m_old_wakefulness') + self._1m_to_2m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1m_to_2m_old_wakefulness') + self._2m_to_3m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2m_to_3m_old_wakefulness') + self._3m_to_4m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3m_to_4m_old_wakefulness') + self._4m_to_5m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4m_to_5m_old_wakefulness') + self._5m_to_6m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5m_to_6m_old_wakefulness') + self._6m_to_9m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6m_to_9m_old_wakefulness') + self._9m_to_1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_9m_to_1y_old_wakefulness') + self._1y_to_18m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1y_to_18m_old_wakefulness') + self._18m_to_2y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_18m_to_2y_old_wakefulness') + self._2y_to_3y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2y_to_3y_old_wakefulness') + self._3y_to_4y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3y_to_4y_old_wakefulness') + self._4y_to_5y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4y_to_5y_old_wakefulness') + self._5y_to_6y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5y_to_6y_old_wakefulness') + self._6y_to_7y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6y_to_7y_old_wakefulness') + self._7y_to_8y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_7y_to_8y_old_wakefulness') + self._8y_to_10y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_8y_to_10y_old_wakefulness') + self._10y_to_12y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_10y_to_12y_old_wakefulness') + self._12y_to_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_12y_to_15y_old_wakefulness') + self.over_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_over_15y_old_wakefulness') + +class SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_under_1h_old_dormancy') + self._1h_to_1d: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1h_to_1d_old_dormancy') + self._1d_to_1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1d_to_1w_old_dormancy') + self._1w_to_1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1w_to_1m_old_dormancy') + self._1m_to_2m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1m_to_2m_old_dormancy') + self._2m_to_3m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2m_to_3m_old_dormancy') + self._3m_to_4m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3m_to_4m_old_dormancy') + self._4m_to_5m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4m_to_5m_old_dormancy') + self._5m_to_6m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5m_to_6m_old_dormancy') + self._6m_to_9m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6m_to_9m_old_dormancy') + self._9m_to_1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_9m_to_1y_old_dormancy') + self._1y_to_18m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1y_to_18m_old_dormancy') + self._18m_to_2y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_18m_to_2y_old_dormancy') + self._2y_to_3y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2y_to_3y_old_dormancy') + self._3y_to_4y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3y_to_4y_old_dormancy') + self._4y_to_5y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4y_to_5y_old_dormancy') + self._5y_to_6y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5y_to_6y_old_dormancy') + self._6y_to_7y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6y_to_7y_old_dormancy') + self._7y_to_8y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_7y_to_8y_old_dormancy') + self._8y_to_10y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_8y_to_10y_old_dormancy') + self._10y_to_12y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_10y_to_12y_old_dormancy') + self._12y_to_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_12y_to_15y_old_dormancy') + self.over_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_over_15y_old_dormancy') + +class SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_under_1h_old_wakefulness_to_dormancy') + self._1h_to_1d: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1h_to_1d_old_wakefulness_to_dormancy') + self._1d_to_1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1d_to_1w_old_wakefulness_to_dormancy') + self._1w_to_1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1w_to_1m_old_wakefulness_to_dormancy') + self._1m_to_2m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1m_to_2m_old_wakefulness_to_dormancy') + self._2m_to_3m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2m_to_3m_old_wakefulness_to_dormancy') + self._3m_to_4m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3m_to_4m_old_wakefulness_to_dormancy') + self._4m_to_5m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4m_to_5m_old_wakefulness_to_dormancy') + self._5m_to_6m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5m_to_6m_old_wakefulness_to_dormancy') + self._6m_to_9m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6m_to_9m_old_wakefulness_to_dormancy') + self._9m_to_1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_9m_to_1y_old_wakefulness_to_dormancy') + self._1y_to_18m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1y_to_18m_old_wakefulness_to_dormancy') + self._18m_to_2y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_18m_to_2y_old_wakefulness_to_dormancy') + self._2y_to_3y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2y_to_3y_old_wakefulness_to_dormancy') + self._3y_to_4y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3y_to_4y_old_wakefulness_to_dormancy') + self._4y_to_5y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4y_to_5y_old_wakefulness_to_dormancy') + self._5y_to_6y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5y_to_6y_old_wakefulness_to_dormancy') + self._6y_to_7y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6y_to_7y_old_wakefulness_to_dormancy') + self._7y_to_8y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_7y_to_8y_old_wakefulness_to_dormancy') + self._8y_to_10y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_8y_to_10y_old_wakefulness_to_dormancy') + self._10y_to_12y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_10y_to_12y_old_wakefulness_to_dormancy') + self._12y_to_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_12y_to_15y_old_wakefulness_to_dormancy') + self.over_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_over_15y_old_wakefulness_to_dormancy') + +class SeriesTree_Frameworks_Cointime_AgeRange_Activity: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.wakefulness: SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness = SeriesTree_Frameworks_Cointime_AgeRange_Activity_Wakefulness(client) + self.dormancy: SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy = SeriesTree_Frameworks_Cointime_AgeRange_Activity_Dormancy(client) + self.wakefulness_to_dormancy: SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy = SeriesTree_Frameworks_Cointime_AgeRange_Activity_WakefulnessToDormancy(client) + self.height: SeriesPattern18[[StoredF64; 23]] = SeriesPattern18(client, 'utxos_age_range_wakefulness') + +class SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_under_1h_old_awake_supply') + self._1h_to_1d: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1h_to_1d_old_awake_supply') + self._1d_to_1w: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1d_to_1w_old_awake_supply') + self._1w_to_1m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1w_to_1m_old_awake_supply') + self._1m_to_2m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1m_to_2m_old_awake_supply') + self._2m_to_3m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2m_to_3m_old_awake_supply') + self._3m_to_4m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3m_to_4m_old_awake_supply') + self._4m_to_5m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4m_to_5m_old_awake_supply') + self._5m_to_6m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5m_to_6m_old_awake_supply') + self._6m_to_9m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6m_to_9m_old_awake_supply') + self._9m_to_1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_9m_to_1y_old_awake_supply') + self._1y_to_18m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1y_to_18m_old_awake_supply') + self._18m_to_2y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_18m_to_2y_old_awake_supply') + self._2y_to_3y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2y_to_3y_old_awake_supply') + self._3y_to_4y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3y_to_4y_old_awake_supply') + self._4y_to_5y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4y_to_5y_old_awake_supply') + self._5y_to_6y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5y_to_6y_old_awake_supply') + self._6y_to_7y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6y_to_7y_old_awake_supply') + self._7y_to_8y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_7y_to_8y_old_awake_supply') + self._8y_to_10y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_8y_to_10y_old_awake_supply') + self._10y_to_12y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_10y_to_12y_old_awake_supply') + self._12y_to_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_12y_to_15y_old_awake_supply') + self.over_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_over_15y_old_awake_supply') + self.height: SeriesPattern18[[Sats; 23]] = SeriesPattern18(client, 'utxos_age_range_awake_supply_sats') + +class SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_under_1h_old_dormant_supply') + self._1h_to_1d: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1h_to_1d_old_dormant_supply') + self._1d_to_1w: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1d_to_1w_old_dormant_supply') + self._1w_to_1m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1w_to_1m_old_dormant_supply') + self._1m_to_2m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1m_to_2m_old_dormant_supply') + self._2m_to_3m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2m_to_3m_old_dormant_supply') + self._3m_to_4m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3m_to_4m_old_dormant_supply') + self._4m_to_5m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4m_to_5m_old_dormant_supply') + self._5m_to_6m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5m_to_6m_old_dormant_supply') + self._6m_to_9m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6m_to_9m_old_dormant_supply') + self._9m_to_1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_9m_to_1y_old_dormant_supply') + self._1y_to_18m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1y_to_18m_old_dormant_supply') + self._18m_to_2y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_18m_to_2y_old_dormant_supply') + self._2y_to_3y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2y_to_3y_old_dormant_supply') + self._3y_to_4y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3y_to_4y_old_dormant_supply') + self._4y_to_5y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4y_to_5y_old_dormant_supply') + self._5y_to_6y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5y_to_6y_old_dormant_supply') + self._6y_to_7y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6y_to_7y_old_dormant_supply') + self._7y_to_8y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_7y_to_8y_old_dormant_supply') + self._8y_to_10y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_8y_to_10y_old_dormant_supply') + self._10y_to_12y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_10y_to_12y_old_dormant_supply') + self._12y_to_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_12y_to_15y_old_dormant_supply') + self.over_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_over_15y_old_dormant_supply') + self.height: SeriesPattern18[[Sats; 23]] = SeriesPattern18(client, 'utxos_age_range_dormant_supply_sats') + +class SeriesTree_Frameworks_Cointime_AgeRange_Supply: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.awake: SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake = SeriesTree_Frameworks_Cointime_AgeRange_Supply_Awake(client) + self.dormant: SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant = SeriesTree_Frameworks_Cointime_AgeRange_Supply_Dormant(client) + class SeriesTree_Frameworks_Cointime_AgeRange: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.under_1h: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_under_1h_old') - self._1h_to_1d: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_1h_to_1d_old') - self._1d_to_1w: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_1d_to_1w_old') - self._1w_to_1m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_1w_to_1m_old') - self._1m_to_2m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_1m_to_2m_old') - self._2m_to_3m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_2m_to_3m_old') - self._3m_to_4m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_3m_to_4m_old') - self._4m_to_5m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_4m_to_5m_old') - self._5m_to_6m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_5m_to_6m_old') - self._6m_to_9m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_6m_to_9m_old') - self._9m_to_1y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_9m_to_1y_old') - self._1y_to_18m: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_1y_to_18m_old') - self._18m_to_2y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_18m_to_2y_old') - self._2y_to_3y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_2y_to_3y_old') - self._3y_to_4y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_3y_to_4y_old') - self._4y_to_5y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_4y_to_5y_old') - self._5y_to_6y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_5y_to_6y_old') - self._6y_to_7y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_6y_to_7y_old') - self._7y_to_8y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_7y_to_8y_old') - self._8y_to_10y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_8y_to_10y_old') - self._10y_to_12y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_10y_to_12y_old') - self._12y_to_15y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_12y_to_15y_old') - self.over_15y: CoindaysDormancySupplyWakefulnessPattern = CoindaysDormancySupplyWakefulnessPattern(client, 'utxos_over_15y_old') + self.coindays_created: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated = SeriesTree_Frameworks_Cointime_AgeRange_CoindaysCreated(client) + self.coindays_consumed: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed = SeriesTree_Frameworks_Cointime_AgeRange_CoindaysConsumed(client) + self.coindays_stored: SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored = SeriesTree_Frameworks_Cointime_AgeRange_CoindaysStored(client) + self.activity: SeriesTree_Frameworks_Cointime_AgeRange_Activity = SeriesTree_Frameworks_Cointime_AgeRange_Activity(client) + self.supply: SeriesTree_Frameworks_Cointime_AgeRange_Supply = SeriesTree_Frameworks_Cointime_AgeRange_Supply(client) class SeriesTree_Frameworks_Cointime_Awake_Supply: """Series tree node.""" @@ -5583,33 +6249,165 @@ class SeriesTree_Frameworks_Cointime: self.adjusted: SeriesTree_Frameworks_Cointime_Adjusted = SeriesTree_Frameworks_Cointime_Adjusted(client) self.reserve_risk: SeriesTree_Frameworks_Cointime_ReserveRisk = SeriesTree_Frameworks_Cointime_ReserveRisk(client) +class SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_under_1h_old_spending_rate') + self._1h_to_1d: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1h_to_1d_old_spending_rate') + self._1d_to_1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1d_to_1w_old_spending_rate') + self._1w_to_1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1w_to_1m_old_spending_rate') + self._1m_to_2m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1m_to_2m_old_spending_rate') + self._2m_to_3m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2m_to_3m_old_spending_rate') + self._3m_to_4m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3m_to_4m_old_spending_rate') + self._4m_to_5m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4m_to_5m_old_spending_rate') + self._5m_to_6m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5m_to_6m_old_spending_rate') + self._6m_to_9m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6m_to_9m_old_spending_rate') + self._9m_to_1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_9m_to_1y_old_spending_rate') + self._1y_to_18m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1y_to_18m_old_spending_rate') + self._18m_to_2y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_18m_to_2y_old_spending_rate') + self._2y_to_3y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2y_to_3y_old_spending_rate') + self._3y_to_4y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3y_to_4y_old_spending_rate') + self._4y_to_5y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4y_to_5y_old_spending_rate') + self._5y_to_6y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5y_to_6y_old_spending_rate') + self._6y_to_7y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6y_to_7y_old_spending_rate') + self._7y_to_8y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_7y_to_8y_old_spending_rate') + self._8y_to_10y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_8y_to_10y_old_spending_rate') + self._10y_to_12y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_10y_to_12y_old_spending_rate') + self._12y_to_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_12y_to_15y_old_spending_rate') + self.over_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_over_15y_old_spending_rate') + self.height: SeriesPattern18[[StoredF64; 23]] = SeriesPattern18(client, 'utxos_age_range_spending_rate') + +class SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_under_1h_old_mobility') + self._1h_to_1d: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1h_to_1d_old_mobility') + self._1d_to_1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1d_to_1w_old_mobility') + self._1w_to_1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1w_to_1m_old_mobility') + self._1m_to_2m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1m_to_2m_old_mobility') + self._2m_to_3m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2m_to_3m_old_mobility') + self._3m_to_4m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3m_to_4m_old_mobility') + self._4m_to_5m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4m_to_5m_old_mobility') + self._5m_to_6m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5m_to_6m_old_mobility') + self._6m_to_9m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6m_to_9m_old_mobility') + self._9m_to_1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_9m_to_1y_old_mobility') + self._1y_to_18m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1y_to_18m_old_mobility') + self._18m_to_2y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_18m_to_2y_old_mobility') + self._2y_to_3y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2y_to_3y_old_mobility') + self._3y_to_4y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3y_to_4y_old_mobility') + self._4y_to_5y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4y_to_5y_old_mobility') + self._5y_to_6y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5y_to_6y_old_mobility') + self._6y_to_7y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6y_to_7y_old_mobility') + self._7y_to_8y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_7y_to_8y_old_mobility') + self._8y_to_10y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_8y_to_10y_old_mobility') + self._10y_to_12y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_10y_to_12y_old_mobility') + self._12y_to_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_12y_to_15y_old_mobility') + self.over_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_over_15y_old_mobility') + +class SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_under_1h_old_spending_exposure') + self._1h_to_1d: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1h_to_1d_old_spending_exposure') + self._1d_to_1w: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1d_to_1w_old_spending_exposure') + self._1w_to_1m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1w_to_1m_old_spending_exposure') + self._1m_to_2m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1m_to_2m_old_spending_exposure') + self._2m_to_3m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2m_to_3m_old_spending_exposure') + self._3m_to_4m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3m_to_4m_old_spending_exposure') + self._4m_to_5m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4m_to_5m_old_spending_exposure') + self._5m_to_6m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5m_to_6m_old_spending_exposure') + self._6m_to_9m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6m_to_9m_old_spending_exposure') + self._9m_to_1y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_9m_to_1y_old_spending_exposure') + self._1y_to_18m: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_1y_to_18m_old_spending_exposure') + self._18m_to_2y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_18m_to_2y_old_spending_exposure') + self._2y_to_3y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_2y_to_3y_old_spending_exposure') + self._3y_to_4y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_3y_to_4y_old_spending_exposure') + self._4y_to_5y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_4y_to_5y_old_spending_exposure') + self._5y_to_6y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_5y_to_6y_old_spending_exposure') + self._6y_to_7y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_6y_to_7y_old_spending_exposure') + self._7y_to_8y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_7y_to_8y_old_spending_exposure') + self._8y_to_10y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_8y_to_10y_old_spending_exposure') + self._10y_to_12y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_10y_to_12y_old_spending_exposure') + self._12y_to_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_12y_to_15y_old_spending_exposure') + self.over_15y: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'utxos_over_15y_old_spending_exposure') + self.mobility: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility = SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure_Mobility(client) + self.height: SeriesPattern18[[StoredF64; 23]] = SeriesPattern18(client, 'utxos_age_range_spending_exposure') + +class SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_under_1h_old_mobile_supply') + self._1h_to_1d: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1h_to_1d_old_mobile_supply') + self._1d_to_1w: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1d_to_1w_old_mobile_supply') + self._1w_to_1m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1w_to_1m_old_mobile_supply') + self._1m_to_2m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1m_to_2m_old_mobile_supply') + self._2m_to_3m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2m_to_3m_old_mobile_supply') + self._3m_to_4m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3m_to_4m_old_mobile_supply') + self._4m_to_5m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4m_to_5m_old_mobile_supply') + self._5m_to_6m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5m_to_6m_old_mobile_supply') + self._6m_to_9m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6m_to_9m_old_mobile_supply') + self._9m_to_1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_9m_to_1y_old_mobile_supply') + self._1y_to_18m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1y_to_18m_old_mobile_supply') + self._18m_to_2y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_18m_to_2y_old_mobile_supply') + self._2y_to_3y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2y_to_3y_old_mobile_supply') + self._3y_to_4y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3y_to_4y_old_mobile_supply') + self._4y_to_5y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4y_to_5y_old_mobile_supply') + self._5y_to_6y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5y_to_6y_old_mobile_supply') + self._6y_to_7y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6y_to_7y_old_mobile_supply') + self._7y_to_8y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_7y_to_8y_old_mobile_supply') + self._8y_to_10y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_8y_to_10y_old_mobile_supply') + self._10y_to_12y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_10y_to_12y_old_mobile_supply') + self._12y_to_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_12y_to_15y_old_mobile_supply') + self.over_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_over_15y_old_mobile_supply') + self.height: SeriesPattern18[[Sats; 23]] = SeriesPattern18(client, 'utxos_age_range_mobile_supply_sats') + +class SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.under_1h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_under_1h_old_immobile_supply') + self._1h_to_1d: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1h_to_1d_old_immobile_supply') + self._1d_to_1w: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1d_to_1w_old_immobile_supply') + self._1w_to_1m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1w_to_1m_old_immobile_supply') + self._1m_to_2m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1m_to_2m_old_immobile_supply') + self._2m_to_3m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2m_to_3m_old_immobile_supply') + self._3m_to_4m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3m_to_4m_old_immobile_supply') + self._4m_to_5m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4m_to_5m_old_immobile_supply') + self._5m_to_6m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5m_to_6m_old_immobile_supply') + self._6m_to_9m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6m_to_9m_old_immobile_supply') + self._9m_to_1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_9m_to_1y_old_immobile_supply') + self._1y_to_18m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_1y_to_18m_old_immobile_supply') + self._18m_to_2y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_18m_to_2y_old_immobile_supply') + self._2y_to_3y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_2y_to_3y_old_immobile_supply') + self._3y_to_4y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_3y_to_4y_old_immobile_supply') + self._4y_to_5y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_4y_to_5y_old_immobile_supply') + self._5y_to_6y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_5y_to_6y_old_immobile_supply') + self._6y_to_7y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_6y_to_7y_old_immobile_supply') + self._7y_to_8y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_7y_to_8y_old_immobile_supply') + self._8y_to_10y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_8y_to_10y_old_immobile_supply') + self._10y_to_12y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_10y_to_12y_old_immobile_supply') + self._12y_to_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_12y_to_15y_old_immobile_supply') + self.over_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxos_over_15y_old_immobile_supply') + self.height: SeriesPattern18[[Sats; 23]] = SeriesPattern18(client, 'utxos_age_range_immobile_supply_sats') + +class SeriesTree_Frameworks_Coinflow_AgeRange_Supply: + """Series tree node.""" + + def __init__(self, client: BrkClient, base_path: str = ''): + self.mobile: SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile = SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Mobile(client) + self.immobile: SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile = SeriesTree_Frameworks_Coinflow_AgeRange_Supply_Immobile(client) + class SeriesTree_Frameworks_Coinflow_AgeRange: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.under_1h: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_under_1h_old') - self._1h_to_1d: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_1h_to_1d_old') - self._1d_to_1w: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_1d_to_1w_old') - self._1w_to_1m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_1w_to_1m_old') - self._1m_to_2m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_1m_to_2m_old') - self._2m_to_3m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_2m_to_3m_old') - self._3m_to_4m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_3m_to_4m_old') - self._4m_to_5m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_4m_to_5m_old') - self._5m_to_6m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_5m_to_6m_old') - self._6m_to_9m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_6m_to_9m_old') - self._9m_to_1y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_9m_to_1y_old') - self._1y_to_18m: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_1y_to_18m_old') - self._18m_to_2y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_18m_to_2y_old') - self._2y_to_3y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_2y_to_3y_old') - self._3y_to_4y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_3y_to_4y_old') - self._4y_to_5y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_4y_to_5y_old') - self._5y_to_6y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_5y_to_6y_old') - self._6y_to_7y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_6y_to_7y_old') - self._7y_to_8y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_7y_to_8y_old') - self._8y_to_10y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_8y_to_10y_old') - self._10y_to_12y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_10y_to_12y_old') - self._12y_to_15y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_12y_to_15y_old') - self.over_15y: MobilitySpendingSupplyPattern = MobilitySpendingSupplyPattern(client, 'utxos_over_15y_old') + self.spending_rate: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate = SeriesTree_Frameworks_Coinflow_AgeRange_SpendingRate(client) + self.spending_exposure: SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure = SeriesTree_Frameworks_Coinflow_AgeRange_SpendingExposure(client) + self.supply: SeriesTree_Frameworks_Coinflow_AgeRange_Supply = SeriesTree_Frameworks_Coinflow_AgeRange_Supply(client) class SeriesTree_Frameworks_Coinflow_Supply_Mobile: """Series tree node.""" @@ -5727,21 +6525,21 @@ class SeriesTree_Models_RarityMeter_Components: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'realized_price') - self.capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'capitalized_price') - self.sth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'sth_realized_price') - self.sth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'sth_capitalized_price') - self.lth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'lth_realized_price') - self.lth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'lth_capitalized_price') - self.over_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'over_6m_realized_price') - self.over_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'over_4m_realized_price') - self.under_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'under_4m_realized_price') - self.under_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'under_6m_realized_price') - self.vaulted_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'vaulted_price') - self.active_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'active_price') - self.true_market_mean_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'true_market_mean_price') - self.cointime_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'cointime_price') - self.coinflow_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99Pattern(client, 'coinflow_price') + self.realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'realized_price') + self.capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'capitalized_price') + self.sth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'sth_realized_price') + self.sth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'sth_capitalized_price') + self.lth_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'lth_realized_price') + self.lth_capitalized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'lth_capitalized_price') + self.over_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'over_6m_realized_price') + self.over_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'over_4m_realized_price') + self.under_4m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'under_4m_realized_price') + self.under_6m_realized_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'under_6m_realized_price') + self.vaulted_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'vaulted_price') + self.active_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'active_price') + self.true_market_mean_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'true_market_mean_price') + self.cointime_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'cointime_price') + self.coinflow_price: Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern = Pct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99RatiosPattern(client, 'coinflow_price') class SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss: """Series tree node.""" @@ -5750,6 +6548,7 @@ class SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss: self.threshold_pct0_1: SeriesPattern1[Bitcoin] = SeriesPattern1(client, 'rarity_meter_coins_in_loss_threshold_pct0_1') self.threshold_pct0_05: SeriesPattern1[Bitcoin] = SeriesPattern1(client, 'rarity_meter_coins_in_loss_threshold_pct0_05') self.threshold_pct0_025: SeriesPattern1[Bitcoin] = SeriesPattern1(client, 'rarity_meter_coins_in_loss_threshold') + self.height: SeriesPattern18[[Bitcoin; 3]] = SeriesPattern18(client, 'rarity_meter_coins_in_loss_thresholds') self.tail: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, 'rarity_meter_coins_in_loss_tail') self.rank: SeriesPattern1[StoredU8] = SeriesPattern1(client, 'rarity_meter_coins_in_loss_rank') @@ -5760,6 +6559,7 @@ class SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion: self.threshold_pct0_1: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rarity_meter_seller_exhaustion_threshold_pct0_1') self.threshold_pct0_05: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rarity_meter_seller_exhaustion_threshold_pct0_05') self.threshold_pct0_025: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rarity_meter_seller_exhaustion_threshold') + self.height: SeriesPattern18[[StoredF32; 3]] = SeriesPattern18(client, 'rarity_meter_seller_exhaustion_thresholds') self.tail: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, 'rarity_meter_seller_exhaustion_tail') self.rank: SeriesPattern1[StoredU8] = SeriesPattern1(client, 'rarity_meter_seller_exhaustion_rank') @@ -5768,9 +6568,9 @@ class SeriesTree_Models_RarityMeter_Extremes: def __init__(self, client: BrkClient, base_path: str = ''): self.coins_in_loss: SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss = SeriesTree_Models_RarityMeter_Extremes_CoinsInLoss(client) - self.profit_taking: RankTailThresholdPattern = RankTailThresholdPattern(client, 'rarity_meter_profit_taking') - self.capitulation: RankTailThresholdPattern = RankTailThresholdPattern(client, 'rarity_meter_capitulation') - self.peak_regret: RankTailThresholdPattern = RankTailThresholdPattern(client, 'rarity_meter_peak_regret') + self.profit_taking: HeightRankTailThresholdPattern = HeightRankTailThresholdPattern(client, 'rarity_meter_profit_taking') + self.capitulation: HeightRankTailThresholdPattern = HeightRankTailThresholdPattern(client, 'rarity_meter_capitulation') + self.peak_regret: HeightRankTailThresholdPattern = HeightRankTailThresholdPattern(client, 'rarity_meter_peak_regret') self.seller_exhaustion: SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion = SeriesTree_Models_RarityMeter_Extremes_SellerExhaustion(client) class SeriesTree_Models_RarityMeter: @@ -5779,9 +6579,9 @@ class SeriesTree_Models_RarityMeter: def __init__(self, client: BrkClient, base_path: str = ''): self.components: SeriesTree_Models_RarityMeter_Components = SeriesTree_Models_RarityMeter_Components(client) self.extremes: SeriesTree_Models_RarityMeter_Extremes = SeriesTree_Models_RarityMeter_Extremes(client) - self.full: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern = IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, 'rarity_meter') - self.local: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern = IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, 'local_rarity_meter') - self.cycle: IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern = IndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, 'cycle_rarity_meter') + self.full: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern = HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, 'rarity_meter') + self.local: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern = HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, 'local_rarity_meter') + self.cycle: HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern = HeightIndexPct0Pct1Pct10Pct2Pct20Pct30Pct40Pct5Pct50Pct60Pct70Pct80Pct90Pct95Pct98Pct99ScorePattern(client, 'cycle_rarity_meter') class SeriesTree_Models: """Series tree node.""" @@ -6366,6 +7166,7 @@ class SeriesTree_Market_MovingAverage_Ema: self._2y: CentsPpmRatioSatsUsdPattern = CentsPpmRatioSatsUsdPattern(client, 'price_ema_2y') self._200w: CentsPpmRatioSatsUsdPattern = CentsPpmRatioSatsUsdPattern(client, 'price_ema_200w') self._4y: CentsPpmRatioSatsUsdPattern = CentsPpmRatioSatsUsdPattern(client, 'price_ema_4y') + self.height: SeriesPattern18[[Cents; 16]] = SeriesPattern18(client, 'price_ema_cents') class SeriesTree_Market_MovingAverage: """Series tree node.""" @@ -6691,13 +7492,13 @@ class SeriesTree_Cohorts_Utxo_All_Activity: self.transfer_volume: AverageBlockCumulativeInSumPattern = AverageBlockCumulativeInSumPattern(client, 'transfer_volume') self.coindays_destroyed: AverageBlockCumulativeSumPattern[StoredF64] = AverageBlockCumulativeSumPattern(client, 'coindays_destroyed') self.coinyears_destroyed: SeriesPattern1[StoredF64] = SeriesPattern1(client, 'coinyears_destroyed') - self.dormancy: _1m1w1y24hPattern[StoredF32] = _1m1w1y24hPattern(client, 'dormancy') + self.dormancy: _1m1w1y24hHeightPattern = _1m1w1y24hHeightPattern(client, 'dormancy') class SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted: """Series tree node.""" def __init__(self, client: BrkClient, base_path: str = ''): - self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'asopr') + self.ratio: _1m1w1y24hHeightPattern2 = _1m1w1y24hHeightPattern2(client, 'asopr') self.transfer_volume: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, 'adj_value_created') self.value_destroyed: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, 'adj_value_destroyed') @@ -6706,7 +7507,7 @@ class SeriesTree_Cohorts_Utxo_All_Realized_Sopr: def __init__(self, client: BrkClient, base_path: str = ''): self.value_destroyed: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, 'value_destroyed') - self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'sopr') + self.ratio: _1m1w1y24hHeightPattern4 = _1m1w1y24hHeightPattern4(client, 'sopr') self.adjusted: SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted = SeriesTree_Cohorts_Utxo_All_Realized_Sopr_Adjusted(client) class SeriesTree_Cohorts_Utxo_All_Realized: @@ -6721,10 +7522,10 @@ class SeriesTree_Cohorts_Utxo_All_Realized: self.net_pnl: BlockChangeCumulativeDeltaSumPattern = BlockChangeCumulativeDeltaSumPattern(client, 'net') self.sopr: SeriesTree_Cohorts_Utxo_All_Realized_Sopr = SeriesTree_Cohorts_Utxo_All_Realized_Sopr(client) self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'realized_gross_pnl') - self.sell_side_risk_ratio: _1m1w1y24hPattern8 = _1m1w1y24hPattern8(client, 'sell_side_risk_ratio') + self.sell_side_risk_ratio: _1m1w1y24hHeightPattern3 = _1m1w1y24hHeightPattern3(client, 'sell_side_risk_ratio') self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'realized_peak_regret') self.capitalized: PricePattern = PricePattern(client, 'capitalized_price') - self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'realized_profit_to_loss_ratio') + self.profit_to_loss_ratio: _1m1w1y24hHeightPattern2 = _1m1w1y24hHeightPattern2(client, 'realized_profit_to_loss_ratio') class SeriesTree_Cohorts_Utxo_All_CostBasis: """Series tree node.""" @@ -6734,8 +7535,8 @@ class SeriesTree_Cohorts_Utxo_All_CostBasis: self.in_loss: PerPattern = PerPattern(client, 'cost_basis_in_loss_per') self.min: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'cost_basis_min') self.max: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'cost_basis_max') - self.per_coin: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, 'cost_basis_per_coin') - self.per_dollar: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, 'cost_basis_per_dollar') + self.per_coin: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, 'cost_basis_per_coin') + self.per_dollar: HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = HeightPct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, 'cost_basis_per_dollar') self.supply_density: PercentPpmRatioPattern2 = PercentPpmRatioPattern2(client, 'supply_density') class SeriesTree_Cohorts_Utxo_All_Unrealized_Profit: @@ -6816,7 +7617,7 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr: def __init__(self, client: BrkClient, base_path: str = ''): self.value_destroyed: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, 'lth_value_destroyed') - self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'lth_sopr') + self.ratio: _1m1w1y24hHeightPattern4 = _1m1w1y24hHeightPattern4(client, 'lth_sopr') class SeriesTree_Cohorts_Utxo_Lth_Realized: """Series tree node.""" @@ -6830,10 +7631,10 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized: self.net_pnl: BlockChangeCumulativeDeltaSumPattern = BlockChangeCumulativeDeltaSumPattern(client, 'lth_net') self.sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr = SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr(client) self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'lth_realized_gross_pnl') - self.sell_side_risk_ratio: _1m1w1y24hPattern8 = _1m1w1y24hPattern8(client, 'lth_sell_side_risk_ratio') + self.sell_side_risk_ratio: _1m1w1y24hHeightPattern3 = _1m1w1y24hHeightPattern3(client, 'lth_sell_side_risk_ratio') self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'lth_realized_peak_regret') self.capitalized: PricePattern = PricePattern(client, 'lth_capitalized_price') - self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'lth_realized_profit_to_loss_ratio') + self.profit_to_loss_ratio: _1m1w1y24hHeightPattern2 = _1m1w1y24hHeightPattern2(client, 'lth_realized_profit_to_loss_ratio') class SeriesTree_Cohorts_Utxo_Lth: """Series tree node.""" @@ -7107,6 +7908,13 @@ class SeriesTree_Cohorts_Utxo_Profitability: self.range: SeriesTree_Cohorts_Utxo_Profitability_Range = SeriesTree_Cohorts_Utxo_Profitability_Range(client) self.profit: SeriesTree_Cohorts_Utxo_Profitability_Profit = SeriesTree_Cohorts_Utxo_Profitability_Profit(client) self.loss: SeriesTree_Cohorts_Utxo_Profitability_Loss = SeriesTree_Cohorts_Utxo_Profitability_Loss(client) + self.all_supply_sats: SeriesPattern18[Sats] = SeriesPattern18(client, 'profitability_all_supply_sats') + self.sth_supply_sats: SeriesPattern18[Sats] = SeriesPattern18(client, 'profitability_sth_supply_sats') + self.all_realized_cap: SeriesPattern18[Dollars] = SeriesPattern18(client, 'profitability_all_realized_cap') + self.sth_realized_cap: SeriesPattern18[Dollars] = SeriesPattern18(client, 'profitability_sth_realized_cap') + self.all_unrealized_pnl: SeriesPattern18[Dollars] = SeriesPattern18(client, 'profitability_all_unrealized_pnl') + self.sth_unrealized_pnl: SeriesPattern18[Dollars] = SeriesPattern18(client, 'profitability_sth_unrealized_pnl') + self.nupl: SeriesPattern18[PartsPerMillionSigned32] = SeriesPattern18(client, 'profitability_nupl_ppm') class SeriesTree_Cohorts_Utxo_Matured: """Series tree node.""" @@ -7155,6 +7963,8 @@ class SeriesTree_Cohorts_Utxo: self.type: SeriesTree_Cohorts_Utxo_Type = SeriesTree_Cohorts_Utxo_Type(client) self.profitability: SeriesTree_Cohorts_Utxo_Profitability = SeriesTree_Cohorts_Utxo_Profitability(client) self.matured: SeriesTree_Cohorts_Utxo_Matured = SeriesTree_Cohorts_Utxo_Matured(client) + self.cumulative_matured_sats: SeriesPattern18[[Sats; 23]] = SeriesPattern18(client, 'utxos_age_range_matured_supply_cumulative_sats') + self.cumulative_matured_cents: SeriesPattern18[[Cents; 23]] = SeriesPattern18(client, 'utxos_age_range_matured_supply_cumulative_cents') class SeriesTree_Cohorts_Addr_OverAmount: """Series tree node.""" diff --git a/website/llms-full.txt b/website/llms-full.txt index 209244f10..3842c779f 100644 --- a/website/llms-full.txt +++ b/website/llms-full.txt @@ -5,7 +5,7 @@ - Version: `v0.11.2` - Base URL: https://bitview.space - MCP endpoint: https://mcp.bitview.space/ -- Metrics: 58022 +- Metrics: 58115 - Operations: 97 For machine-readable tool construction, use [https://bitview.space/openapi.json](https://bitview.space/openapi.json). For the complete source-derived series tree, use [https://bitview.space/api/series](https://bitview.space/api/series). diff --git a/website/llms.txt b/website/llms.txt index 32b5a8d22..eb7d89dc4 100644 --- a/website/llms.txt +++ b/website/llms.txt @@ -1,6 +1,6 @@ # Bitcoin Research Kit (BRK) -> Free, open-source Bitcoin analytics API and block explorer. 58022 on-chain time-series and 97 API operations. No authentication required. +> Free, open-source Bitcoin analytics API and block explorer. 58115 on-chain time-series and 97 API operations. No authentication required. ## API diff --git a/website/scripts/options/frameworks/coinflow.js b/website/scripts/options/frameworks/coinflow.js index cd2984c39..cd26fb7a2 100644 --- a/website/scripts/options/frameworks/coinflow.js +++ b/website/scripts/options/frameworks/coinflow.js @@ -66,7 +66,15 @@ export function createCoinflowSection() { const { coinflow } = brk.series.frameworks; const ranges = ageRanges.map(({ key, ...range }) => ({ ...range, - tree: coinflow.ageRange[key], + tree: { + spendingRate: coinflow.ageRange.spendingRate[key], + spendingExposure: coinflow.ageRange.spendingExposure[key], + mobility: coinflow.ageRange.spendingExposure.mobility[key], + supply: { + mobile: coinflow.ageRange.supply.mobile[key], + immobile: coinflow.ageRange.supply.immobile[key], + }, + }, })); const terms = [ { name: "STH", color: colors.term.short, tree: coinflow.sth }, diff --git a/website/scripts/options/frameworks/cointime/index.js b/website/scripts/options/frameworks/cointime/index.js index e52ea23be..45f23d29a 100644 --- a/website/scripts/options/frameworks/cointime/index.js +++ b/website/scripts/options/frameworks/cointime/index.js @@ -35,7 +35,19 @@ export function createCointimeSection() { const { all } = cohorts.utxo; const cointimeAgeRanges = ageRanges.map(({ key, ...range }) => ({ ...range, - tree: cointime.ageRange[key], + tree: { + coindaysCreated: cointime.ageRange.coindaysCreated[key], + coindaysConsumed: cointime.ageRange.coindaysConsumed[key], + coindaysStored: cointime.ageRange.coindaysStored[key], + wakefulness: cointime.ageRange.activity.wakefulness[key], + dormancy: cointime.ageRange.activity.dormancy[key], + wakefulnessToDormancy: + cointime.ageRange.activity.wakefulnessToDormancy[key], + supply: { + awake: cointime.ageRange.supply.awake[key], + dormant: cointime.ageRange.supply.dormant[key], + }, + }, })); const awakeCohorts = [ { name: "All", color: colors.awake, tree: cointime }, diff --git a/website_next/llms-full.txt b/website_next/llms-full.txt index 209244f10..3842c779f 100644 --- a/website_next/llms-full.txt +++ b/website_next/llms-full.txt @@ -5,7 +5,7 @@ - Version: `v0.11.2` - Base URL: https://bitview.space - MCP endpoint: https://mcp.bitview.space/ -- Metrics: 58022 +- Metrics: 58115 - Operations: 97 For machine-readable tool construction, use [https://bitview.space/openapi.json](https://bitview.space/openapi.json). For the complete source-derived series tree, use [https://bitview.space/api/series](https://bitview.space/api/series). diff --git a/website_next/llms.txt b/website_next/llms.txt index 32b5a8d22..eb7d89dc4 100644 --- a/website_next/llms.txt +++ b/website_next/llms.txt @@ -1,6 +1,6 @@ # Bitcoin Research Kit (BRK) -> Free, open-source Bitcoin analytics API and block explorer. 58022 on-chain time-series and 97 API operations. No authentication required. +> Free, open-source Bitcoin analytics API and block explorer. 58115 on-chain time-series and 97 API operations. No authentication required. ## API