mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snap
This commit is contained in:
95
Cargo.lock
generated
95
Cargo.lock
generated
@@ -41,9 +41,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aide"
|
||||
version = "0.16.0-alpha.3"
|
||||
version = "0.16.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87a769cd3a8984b7236931cd48f4d1f6b99c0475d60987a1f69490b079116306"
|
||||
checksum = "390515b47251185fa076ac92a7a582d9d383b03e13cef0c801e7670cf928229b"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"bytes",
|
||||
@@ -122,9 +122,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.8"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
|
||||
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"bytes",
|
||||
@@ -233,7 +233,7 @@ version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
@@ -332,9 +332,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "brk"
|
||||
@@ -1358,7 +1358,7 @@ version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"byteorder",
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
@@ -1424,21 +1424,6 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
@@ -1446,7 +1431,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1455,34 +1439,6 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.32"
|
||||
@@ -1501,13 +1457,8 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
@@ -2564,9 +2515,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -2588,7 +2539,7 @@ version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2701,7 +2652,7 @@ version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -2734,9 +2685,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.11"
|
||||
version = "0.103.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4"
|
||||
checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -2907,15 +2858,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.14.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6"
|
||||
checksum = "c2316d01592c3382277c5062105510e35e0a6bfb2851e30028485f7af8cf1240"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"futures",
|
||||
"itoa",
|
||||
"percent-encoding",
|
||||
"ryu",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3148,9 +3099,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.51.1"
|
||||
version = "1.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c"
|
||||
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
@@ -3245,7 +3196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -3587,7 +3538,7 @@ version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
@@ -3863,7 +3814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.11.0",
|
||||
"bitflags 2.11.1",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
|
||||
@@ -36,8 +36,8 @@ inherits = "release"
|
||||
debug = true
|
||||
|
||||
[workspace.dependencies]
|
||||
aide = { version = "0.16.0-alpha.3", features = ["axum-json", "axum-query"] }
|
||||
axum = { version = "0.8.8", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] }
|
||||
aide = { version = "0.16.0-alpha.4", features = ["axum-json", "axum-query"] }
|
||||
axum = { version = "0.8.9", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] }
|
||||
bitcoin = { version = "0.32.8", features = ["serde"] }
|
||||
bitcoincore-rpc = "0.19.0"
|
||||
brk_alloc = { version = "0.3.0-beta.1", path = "crates/brk_alloc" }
|
||||
@@ -76,7 +76,7 @@ jiff = { version = "0.2.23", features = ["perf-inline", "tz-system"], default-fe
|
||||
owo-colors = "4.3.0"
|
||||
parking_lot = "0.12.5"
|
||||
pco = "1.0.1"
|
||||
rayon = "1.11.0"
|
||||
rayon = "1.12.0"
|
||||
rustc-hash = "2.1.2"
|
||||
schemars = { version = "1.2.1", features = ["indexmap2"] }
|
||||
serde = "1.0.228"
|
||||
@@ -84,7 +84,7 @@ serde_bytes = "0.11.19"
|
||||
serde_derive = "1.0.228"
|
||||
serde_json = { version = "1.0.149", features = ["float_roundtrip", "preserve_order"] }
|
||||
smallvec = "1.15.1"
|
||||
tokio = { version = "1.51.1", features = ["rt-multi-thread"] }
|
||||
tokio = { version = "1.52.0", features = ["rt-multi-thread"] }
|
||||
tower-http = { version = "0.6.8", 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"] }
|
||||
|
||||
@@ -1277,6 +1277,38 @@ impl<T: DeserializeOwned> AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90S
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern {
|
||||
pub index: SeriesPattern1<StoredI8>,
|
||||
pub pct0_5: CentsSatsUsdPattern,
|
||||
pub pct1: CentsSatsUsdPattern,
|
||||
pub pct2: CentsSatsUsdPattern,
|
||||
pub pct5: CentsSatsUsdPattern,
|
||||
pub pct95: CentsSatsUsdPattern,
|
||||
pub pct98: CentsSatsUsdPattern,
|
||||
pub pct99: CentsSatsUsdPattern,
|
||||
pub pct99_5: CentsSatsUsdPattern,
|
||||
pub score: SeriesPattern1<StoredI8>,
|
||||
}
|
||||
|
||||
impl IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern {
|
||||
/// Create a new pattern node with accumulated series name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
index: SeriesPattern1::new(client.clone(), _m(&acc, "index")),
|
||||
pct0_5: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct0_5")),
|
||||
pct1: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct01")),
|
||||
pct2: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct02")),
|
||||
pct5: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct05")),
|
||||
pct95: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct95")),
|
||||
pct98: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct98")),
|
||||
pct99: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct99")),
|
||||
pct99_5: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct99_5")),
|
||||
score: SeriesPattern1::new(client.clone(), _m(&acc, "score")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5 {
|
||||
pub all: AverageBlockCumulativeSumPattern<StoredU64>,
|
||||
@@ -5619,7 +5651,7 @@ pub struct SeriesTree_Indicators {
|
||||
pub dormancy: SeriesTree_Indicators_Dormancy,
|
||||
pub stock_to_flow: SeriesPattern1<StoredF32>,
|
||||
pub seller_exhaustion: SeriesPattern1<StoredF32>,
|
||||
pub realized_envelope: SeriesTree_Indicators_RealizedEnvelope,
|
||||
pub rarity_meter: SeriesTree_Indicators_RarityMeter,
|
||||
}
|
||||
|
||||
impl SeriesTree_Indicators {
|
||||
@@ -5635,7 +5667,7 @@ impl SeriesTree_Indicators {
|
||||
dormancy: SeriesTree_Indicators_Dormancy::new(client.clone(), format!("{base_path}_dormancy")),
|
||||
stock_to_flow: SeriesPattern1::new(client.clone(), "stock_to_flow".to_string()),
|
||||
seller_exhaustion: SeriesPattern1::new(client.clone(), "seller_exhaustion".to_string()),
|
||||
realized_envelope: SeriesTree_Indicators_RealizedEnvelope::new(client.clone(), format!("{base_path}_realized_envelope")),
|
||||
rarity_meter: SeriesTree_Indicators_RarityMeter::new(client.clone(), format!("{base_path}_rarity_meter")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5656,32 +5688,18 @@ impl SeriesTree_Indicators_Dormancy {
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Indicators_RealizedEnvelope {
|
||||
pub pct0_5: CentsSatsUsdPattern,
|
||||
pub pct1: CentsSatsUsdPattern,
|
||||
pub pct2: CentsSatsUsdPattern,
|
||||
pub pct5: CentsSatsUsdPattern,
|
||||
pub pct95: CentsSatsUsdPattern,
|
||||
pub pct98: CentsSatsUsdPattern,
|
||||
pub pct99: CentsSatsUsdPattern,
|
||||
pub pct99_5: CentsSatsUsdPattern,
|
||||
pub index: SeriesPattern1<StoredI8>,
|
||||
pub score: SeriesPattern1<StoredI8>,
|
||||
pub struct SeriesTree_Indicators_RarityMeter {
|
||||
pub full: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern,
|
||||
pub local: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern,
|
||||
pub cycle: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Indicators_RealizedEnvelope {
|
||||
impl SeriesTree_Indicators_RarityMeter {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
pct0_5: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct0_5".to_string()),
|
||||
pct1: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct01".to_string()),
|
||||
pct2: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct02".to_string()),
|
||||
pct5: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct05".to_string()),
|
||||
pct95: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct95".to_string()),
|
||||
pct98: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct98".to_string()),
|
||||
pct99: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct99".to_string()),
|
||||
pct99_5: CentsSatsUsdPattern::new(client.clone(), "realized_envelope_pct99_5".to_string()),
|
||||
index: SeriesPattern1::new(client.clone(), "realized_envelope_index".to_string()),
|
||||
score: SeriesPattern1::new(client.clone(), "realized_envelope_score".to_string()),
|
||||
full: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern::new(client.clone(), "rarity_meter".to_string()),
|
||||
local: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern::new(client.clone(), "local_rarity_meter".to_string()),
|
||||
cycle: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern::new(client.clone(), "cycle_rarity_meter".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use brk_error::Result;
|
||||
use brk_types::Version;
|
||||
|
||||
use super::{Vecs, realized_envelope::RealizedEnvelope};
|
||||
use super::{Vecs, rarity_meter::RarityMeter};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
@@ -40,7 +40,7 @@ impl Vecs {
|
||||
let stock_to_flow = PerBlock::forced_import(&db, "stock_to_flow", v, indexes)?;
|
||||
let seller_exhaustion = PerBlock::forced_import(&db, "seller_exhaustion", v, indexes)?;
|
||||
|
||||
let realized_envelope = RealizedEnvelope::forced_import(&db, v, indexes)?;
|
||||
let rarity_meter = RarityMeter::forced_import(&db, v, indexes)?;
|
||||
|
||||
let this = Self {
|
||||
db,
|
||||
@@ -54,7 +54,7 @@ impl Vecs {
|
||||
dormancy,
|
||||
stock_to_flow,
|
||||
seller_exhaustion,
|
||||
realized_envelope,
|
||||
rarity_meter,
|
||||
};
|
||||
finalize_db(&this.db, &this)?;
|
||||
Ok(this)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod compute;
|
||||
mod gini;
|
||||
mod import;
|
||||
pub mod realized_envelope;
|
||||
pub mod rarity_meter;
|
||||
mod vecs;
|
||||
|
||||
pub use vecs::Vecs;
|
||||
|
||||
@@ -4,13 +4,12 @@ use brk_types::{Cents, Height, Indexes, StoredI8, Version};
|
||||
use vecdb::{AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
cointime, distribution, indexes,
|
||||
indexes,
|
||||
internal::{PerBlock, Price, RatioPerBlockPercentiles},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct RealizedEnvelope<M: StorageMode = Rw> {
|
||||
pub struct RarityMeterInner<M: StorageMode = Rw> {
|
||||
pub pct0_5: Price<PerBlock<Cents, M>>,
|
||||
pub pct1: Price<PerBlock<Cents, M>>,
|
||||
pub pct2: Price<PerBlock<Cents, M>>,
|
||||
@@ -23,113 +22,85 @@ pub struct RealizedEnvelope<M: StorageMode = Rw> {
|
||||
pub score: PerBlock<StoredI8, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::new(3);
|
||||
|
||||
impl RealizedEnvelope {
|
||||
impl RarityMeterInner {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
prefix: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
pct0_5: Price::forced_import(db, "realized_envelope_pct0_5", v, indexes)?,
|
||||
pct1: Price::forced_import(db, "realized_envelope_pct01", v, indexes)?,
|
||||
pct2: Price::forced_import(db, "realized_envelope_pct02", v, indexes)?,
|
||||
pct5: Price::forced_import(db, "realized_envelope_pct05", v, indexes)?,
|
||||
pct95: Price::forced_import(db, "realized_envelope_pct95", v, indexes)?,
|
||||
pct98: Price::forced_import(db, "realized_envelope_pct98", v, indexes)?,
|
||||
pct99: Price::forced_import(db, "realized_envelope_pct99", v, indexes)?,
|
||||
pct99_5: Price::forced_import(db, "realized_envelope_pct99_5", v, indexes)?,
|
||||
index: PerBlock::forced_import(db, "realized_envelope_index", v, indexes)?,
|
||||
score: PerBlock::forced_import(db, "realized_envelope_score", v, 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)?,
|
||||
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)?,
|
||||
index: PerBlock::forced_import(db, &format!("{prefix}_index"), version, indexes)?,
|
||||
score: PerBlock::forced_import(db, &format!("{prefix}_score"), version, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
pub(super) fn compute(
|
||||
&mut self,
|
||||
distribution: &distribution::Vecs,
|
||||
cointime: &cointime::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
models: &[&RatioPerBlockPercentiles],
|
||||
spot: &impl ReadableVec<Height, Cents>,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let realized = &distribution.utxo_cohorts.all.metrics.realized;
|
||||
let ct = &cointime.prices;
|
||||
|
||||
let sth_realized = &distribution.utxo_cohorts.sth.metrics.realized;
|
||||
let lth_realized = &distribution.utxo_cohorts.lth.metrics.realized;
|
||||
|
||||
let models: [&RatioPerBlockPercentiles; 10] = [
|
||||
&realized.price_ratio_percentiles,
|
||||
&realized.investor.price.percentiles,
|
||||
&sth_realized.price_ratio_percentiles,
|
||||
&sth_realized.investor.price.percentiles,
|
||||
<h_realized.price_ratio_percentiles,
|
||||
<h_realized.investor.price.percentiles,
|
||||
&ct.vaulted.percentiles,
|
||||
&ct.active.percentiles,
|
||||
&ct.true_market_mean.percentiles,
|
||||
&ct.cointime.percentiles,
|
||||
];
|
||||
|
||||
macro_rules! sources {
|
||||
($pct:ident) => {
|
||||
models.each_ref().map(|m| &m.$pct.price.cents.height)
|
||||
};
|
||||
}
|
||||
let gather = |f: fn(&RatioPerBlockPercentiles) -> &_| -> Vec<_> {
|
||||
models.iter().map(|m| f(m)).collect()
|
||||
};
|
||||
|
||||
// Lower percentiles: max across all models (tightest lower bound)
|
||||
self.pct0_5.cents.height.compute_max_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct0_5),
|
||||
&gather(|m| &m.pct0_5.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
self.pct1.cents.height.compute_max_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct1),
|
||||
&gather(|m| &m.pct1.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
self.pct2.cents.height.compute_max_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct2),
|
||||
&gather(|m| &m.pct2.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
self.pct5.cents.height.compute_max_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct5),
|
||||
&gather(|m| &m.pct5.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Upper percentiles: min across all models (tightest upper bound)
|
||||
self.pct95.cents.height.compute_min_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct95),
|
||||
&gather(|m| &m.pct95.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
self.pct98.cents.height.compute_min_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct98),
|
||||
&gather(|m| &m.pct98.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
self.pct99.cents.height.compute_min_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct99),
|
||||
&gather(|m| &m.pct99.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
self.pct99_5.cents.height.compute_min_of_others(
|
||||
starting_indexes.height,
|
||||
&sources!(pct99_5),
|
||||
&gather(|m| &m.pct99_5.price.cents.height),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let spot = &prices.spot.cents.height;
|
||||
|
||||
// Zone: spot vs own envelope bands (-4 to +4)
|
||||
self.compute_index(spot, starting_indexes, exit)?;
|
||||
|
||||
// Temperature: per-model band crossings (-40 to +40)
|
||||
self.compute_score(&models, spot, starting_indexes, exit)?;
|
||||
self.compute_score(models, spot, starting_indexes, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -140,7 +111,7 @@ impl RealizedEnvelope {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let bands: [&_; 8] = [
|
||||
let bands = [
|
||||
&self.pct0_5.cents.height,
|
||||
&self.pct1.cents.height,
|
||||
&self.pct2.cents.height,
|
||||
@@ -213,7 +184,7 @@ impl RealizedEnvelope {
|
||||
|
||||
fn compute_score(
|
||||
&mut self,
|
||||
models: &[&RatioPerBlockPercentiles; 10],
|
||||
models: &[&RatioPerBlockPercentiles],
|
||||
spot: &impl ReadableVec<Height, Cents>,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
88
crates/brk_computer/src/indicators/rarity_meter/mod.rs
Normal file
88
crates/brk_computer/src/indicators/rarity_meter/mod.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
mod inner;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Indexes, Version};
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{distribution, indexes, prices};
|
||||
|
||||
pub use inner::RarityMeterInner;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct RarityMeter<M: StorageMode = Rw> {
|
||||
pub full: RarityMeterInner<M>,
|
||||
pub local: RarityMeterInner<M>,
|
||||
pub cycle: RarityMeterInner<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::new(4);
|
||||
|
||||
impl RarityMeter {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
full: RarityMeterInner::forced_import(db, "rarity_meter", v, indexes)?,
|
||||
local: RarityMeterInner::forced_import(db, "local_rarity_meter", v, indexes)?,
|
||||
cycle: RarityMeterInner::forced_import(db, "cycle_rarity_meter", v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
distribution: &distribution::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let realized = &distribution.utxo_cohorts.all.metrics.realized;
|
||||
let sth_realized = &distribution.utxo_cohorts.sth.metrics.realized;
|
||||
let lth_realized = &distribution.utxo_cohorts.lth.metrics.realized;
|
||||
let spot = &prices.spot.cents.height;
|
||||
|
||||
// Full: all + sth + lth (rp + ip), 6 models
|
||||
self.full.compute(
|
||||
&[
|
||||
&realized.price_ratio_percentiles,
|
||||
&realized.investor.price.percentiles,
|
||||
&sth_realized.price_ratio_percentiles,
|
||||
&sth_realized.investor.price.percentiles,
|
||||
<h_realized.price_ratio_percentiles,
|
||||
<h_realized.investor.price.percentiles,
|
||||
],
|
||||
spot,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Local: sth only, 2 models
|
||||
self.local.compute(
|
||||
&[
|
||||
&sth_realized.price_ratio_percentiles,
|
||||
&sth_realized.investor.price.percentiles,
|
||||
],
|
||||
spot,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Cycle: all + lth, 4 models
|
||||
self.cycle.compute(
|
||||
&[
|
||||
&realized.price_ratio_percentiles,
|
||||
&realized.investor.price.percentiles,
|
||||
<h_realized.price_ratio_percentiles,
|
||||
<h_realized.investor.price.percentiles,
|
||||
],
|
||||
spot,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
use super::realized_envelope::RealizedEnvelope;
|
||||
use super::rarity_meter::RarityMeter;
|
||||
use crate::internal::{PerBlock, PercentPerBlock, RatioPerBlock};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -25,5 +25,5 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub dormancy: DormancyVecs<M>,
|
||||
pub stock_to_flow: PerBlock<StoredF32, M>,
|
||||
pub seller_exhaustion: PerBlock<StoredF32, M>,
|
||||
pub realized_envelope: RealizedEnvelope<M>,
|
||||
pub rarity_meter: RarityMeter<M>,
|
||||
}
|
||||
|
||||
@@ -467,9 +467,8 @@ impl Computer {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
self.indicators.realized_envelope.compute(
|
||||
self.indicators.rarity_meter.compute(
|
||||
&self.distribution,
|
||||
&self.cointime,
|
||||
&self.prices,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
|
||||
52
crates/brk_reader/examples/last_n_bench.rs
Normal file
52
crates/brk_reader/examples/last_n_bench.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! Times `Reader::after` for a handful of tail-clustered catchup
|
||||
//! sizes. `N ≤ ~1024` lands in the tail strategy (chunked reverse
|
||||
//! reader); `N = 10_000` falls through to the forward strategy since
|
||||
//! it's past the 8-newest-files window.
|
||||
//!
|
||||
//! Run with:
|
||||
//! cargo run --release -p brk_reader --example last_n_bench
|
||||
//!
|
||||
//! Requires a running bitcoind with a cookie file at the default path.
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_reader::Reader;
|
||||
use brk_rpc::{Auth, Client};
|
||||
use brk_types::Height;
|
||||
|
||||
const SCENARIOS: &[u32] = &[1, 10, 100, 1_000, 10_000];
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let bitcoin_dir = Client::default_bitcoin_path();
|
||||
let client = Client::new(
|
||||
Client::default_url(),
|
||||
Auth::CookieFile(bitcoin_dir.join(".cookie")),
|
||||
)?;
|
||||
let reader = Reader::new(bitcoin_dir.join("blocks"), &client);
|
||||
|
||||
let tip = client.get_last_height()?;
|
||||
println!("Tip: {tip}");
|
||||
println!();
|
||||
println!("{:>6} {:>14} {:>10}", "blocks", "elapsed", "blk/s");
|
||||
println!("{}", "-".repeat(36));
|
||||
|
||||
for &n in SCENARIOS {
|
||||
let anchor_height = Height::from(tip.saturating_sub(n));
|
||||
let anchor_hash = client.get_block_hash(*anchor_height as u64)?;
|
||||
let anchor = Some(anchor_hash);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut count = 0usize;
|
||||
for block in reader.after(anchor)? {
|
||||
let _ = block?;
|
||||
count += 1;
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
let blk_per_s = count as f64 / elapsed.as_secs_f64().max(f64::EPSILON);
|
||||
println!("{n:>6} {elapsed:>14?} {blk_per_s:>10.0}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -53,7 +53,7 @@ pub(crate) fn first_block_height(
|
||||
}
|
||||
xor_i.bytes(&mut buf[magic_end..header_end], xor_bytes);
|
||||
|
||||
let header = Header::consensus_decode(&mut &buf[magic_end + 4..header_end])?;
|
||||
let header = Header::consensus_decode_from_finite_reader(&mut &buf[magic_end + 4..header_end])?;
|
||||
let height = client.get_block_info(&header.block_hash())?.height as u32;
|
||||
|
||||
Ok(Height::new(height))
|
||||
|
||||
@@ -27,7 +27,7 @@ pub(crate) fn peek_canonical(
|
||||
let mut header_buf = [0u8; HEADER_LEN];
|
||||
header_buf.copy_from_slice(&bytes[..HEADER_LEN]);
|
||||
xor_state.bytes(&mut header_buf, xor_bytes);
|
||||
let header = Header::consensus_decode(&mut &header_buf[..]).ok()?;
|
||||
let header = Header::consensus_decode_from_finite_reader(&mut &header_buf[..]).ok()?;
|
||||
let offset = canonical.offset_of(&BlockHash::from(header.block_hash()))?;
|
||||
Some((offset, header))
|
||||
}
|
||||
@@ -52,14 +52,20 @@ pub(crate) fn parse_canonical_body(
|
||||
let mut cursor = Cursor::new(bytes);
|
||||
cursor.set_position(HEADER_LEN as u64);
|
||||
|
||||
let tx_count = VarInt::consensus_decode(&mut cursor)?.0 as usize;
|
||||
// `consensus_decode_from_finite_reader` skips the `Take<R>` wrap
|
||||
// that `consensus_decode` applies to every nested field for
|
||||
// memory-safety — our cursor is already a bounded `Vec<u8>`, so
|
||||
// the extra wrapping is pure overhead. Per the crate docs it's
|
||||
// "marginally faster", but for a ~2000-tx block the per-field
|
||||
// compounding adds up.
|
||||
let tx_count = VarInt::consensus_decode_from_finite_reader(&mut cursor)?.0 as usize;
|
||||
let mut txdata = Vec::with_capacity(tx_count);
|
||||
let mut tx_metadata = Vec::with_capacity(tx_count);
|
||||
let mut tx_offsets = Vec::with_capacity(tx_count);
|
||||
for _ in 0..tx_count {
|
||||
let tx_start = cursor.position() as u32;
|
||||
tx_offsets.push(tx_start);
|
||||
let tx = Transaction::consensus_decode(&mut cursor)?;
|
||||
let tx = Transaction::consensus_decode_from_finite_reader(&mut cursor)?;
|
||||
let tx_len = cursor.position() as u32 - tx_start;
|
||||
txdata.push(tx);
|
||||
tx_metadata.push(BlkMetadata::new(metadata.position() + tx_start, tx_len));
|
||||
|
||||
@@ -135,6 +135,7 @@ fn read_and_dispatch(
|
||||
scan_bytes(
|
||||
&mut bytes,
|
||||
blk_index,
|
||||
0,
|
||||
xor_bytes,
|
||||
|metadata, block_bytes, xor_state| {
|
||||
if stop.get().is_some() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Tail pipeline: single-threaded reverse scan of the newest blk
|
||||
//! files until every canonical hash is matched, then forward-emit
|
||||
//! with an inline chain check. Avoids the forward pipeline's
|
||||
//! bisection + out-of-order backoff (~2.7 GB of reads) for any
|
||||
//! tip-clustered catchup.
|
||||
//! files, reading each file in `TAIL_CHUNK`-sized slices from tail
|
||||
//! to head so we only touch bytes covering the canonical window.
|
||||
//! Matches fill offset slots and are emitted forward with an inline
|
||||
//! chain check.
|
||||
|
||||
use std::{fs, ops::ControlFlow};
|
||||
use std::{fs::File, ops::ControlFlow, os::unix::fs::FileExt};
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_rpc::Client;
|
||||
@@ -18,6 +18,8 @@ use crate::{
|
||||
scan::scan_bytes,
|
||||
};
|
||||
|
||||
const TAIL_CHUNK: usize = 8 * 1024 * 1024;
|
||||
|
||||
pub(super) fn pipeline_tail(
|
||||
client: &Client,
|
||||
paths: &BlkIndexToBlkPath,
|
||||
@@ -34,7 +36,7 @@ pub(super) fn pipeline_tail(
|
||||
// miss doesn't scan the entire chain in reverse.
|
||||
let mut below_floor_streak: usize = 0;
|
||||
|
||||
for (&blk_index, path) in paths.iter().rev() {
|
||||
'files: for (&blk_index, path) in paths.iter().rev() {
|
||||
// If this file's first block is below the lowest still-missing
|
||||
// canonical height, we've walked past the window.
|
||||
if let Some(missing_idx) = slots.iter().position(Option::is_none)
|
||||
@@ -53,51 +55,85 @@ pub(super) fn pipeline_tail(
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes = fs::read(path)?;
|
||||
scan_bytes(
|
||||
&mut bytes,
|
||||
blk_index,
|
||||
xor_bytes,
|
||||
|metadata, block_bytes, xor_state| {
|
||||
let Some((offset, header)) =
|
||||
peek_canonical(block_bytes, xor_state, xor_bytes, canonical)
|
||||
else {
|
||||
return ControlFlow::Continue(());
|
||||
};
|
||||
if slots[offset as usize].is_some() {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
let height = Height::from(*canonical.start + offset);
|
||||
match parse_canonical_body(
|
||||
block_bytes.to_vec(),
|
||||
metadata,
|
||||
xor_state,
|
||||
xor_bytes,
|
||||
height,
|
||||
header,
|
||||
) {
|
||||
Ok(block) => {
|
||||
slots[offset as usize] = Some(block);
|
||||
remaining -= 1;
|
||||
}
|
||||
Err(e) => {
|
||||
parse_failure = Some(e);
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
if remaining == 0 {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(e) = parse_failure {
|
||||
return Err(e);
|
||||
let file = File::open(path)?;
|
||||
let file_len = file.metadata()?.len() as usize;
|
||||
if file_len == 0 {
|
||||
continue;
|
||||
}
|
||||
if remaining == 0 {
|
||||
break;
|
||||
|
||||
// Chunked reverse read. `end` is the file position we've
|
||||
// already covered (exclusive). Each iteration reads
|
||||
// [end - TAIL_CHUNK..end] and prepends it to any `spillover`
|
||||
// carried from the previous iteration — the pre-first-magic
|
||||
// bytes of that chunk, which must belong to a block that
|
||||
// started in this earlier region.
|
||||
let mut end = file_len;
|
||||
let mut spillover: Vec<u8> = Vec::new();
|
||||
|
||||
while end > 0 && remaining > 0 {
|
||||
let start = end.saturating_sub(TAIL_CHUNK);
|
||||
let chunk_len = end - start;
|
||||
let mut buf = vec![0u8; chunk_len + spillover.len()];
|
||||
file.read_exact_at(&mut buf[..chunk_len], start as u64)?;
|
||||
buf[chunk_len..].copy_from_slice(&spillover);
|
||||
spillover.clear();
|
||||
|
||||
// `buf` now represents file bytes [start..start + buf.len()].
|
||||
let result = scan_bytes(
|
||||
&mut buf,
|
||||
blk_index,
|
||||
start,
|
||||
xor_bytes,
|
||||
|metadata, block_bytes, xor_state| {
|
||||
let Some((offset, header)) =
|
||||
peek_canonical(block_bytes, xor_state, xor_bytes, canonical)
|
||||
else {
|
||||
return ControlFlow::Continue(());
|
||||
};
|
||||
if slots[offset as usize].is_some() {
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
let height = Height::from(*canonical.start + offset);
|
||||
match parse_canonical_body(
|
||||
block_bytes.to_vec(),
|
||||
metadata,
|
||||
xor_state,
|
||||
xor_bytes,
|
||||
height,
|
||||
header,
|
||||
) {
|
||||
Ok(block) => {
|
||||
slots[offset as usize] = Some(block);
|
||||
remaining -= 1;
|
||||
}
|
||||
Err(e) => {
|
||||
parse_failure = Some(e);
|
||||
return ControlFlow::Break(());
|
||||
}
|
||||
}
|
||||
if remaining == 0 {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(e) = parse_failure {
|
||||
return Err(e);
|
||||
}
|
||||
if remaining == 0 {
|
||||
break 'files;
|
||||
}
|
||||
|
||||
// Carry pre-first-magic bytes into the next (earlier)
|
||||
// chunk so a block that straddled this chunk's start is
|
||||
// stitched back together.
|
||||
end = start;
|
||||
if end > 0 {
|
||||
let prefix_len = result.first_magic.unwrap_or(buf.len());
|
||||
spillover.extend_from_slice(&buf[..prefix_len]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ const MAGIC_BYTES: [u8; 4] = [0xF9, 0xBE, 0xB4, 0xD9];
|
||||
|
||||
/// Returns the position **immediately after** the matched magic, or
|
||||
/// `None` if no match. Advances `xor_i` by the bytes consumed either
|
||||
/// way.
|
||||
/// way. First-byte fast-fail keeps the inner loop tight.
|
||||
pub(crate) fn find_magic(bytes: &[u8], xor_i: &mut XORIndex, xor_bytes: XORBytes) -> Option<usize> {
|
||||
let len = bytes.len();
|
||||
if len < MAGIC_BYTES.len() {
|
||||
@@ -42,36 +42,51 @@ pub(crate) fn find_magic(bytes: &[u8], xor_i: &mut XORIndex, xor_bytes: XORBytes
|
||||
None
|
||||
}
|
||||
|
||||
/// Scans `buf` (the full contents of one blk file) for blocks,
|
||||
/// calling `on_block` for each. The block bytes are passed as a
|
||||
/// mutable borrow so the callback can clone (to ship to a parser
|
||||
/// thread) or process in place (to peek the header).
|
||||
/// Position (relative to `buf`) of the first matched magic byte.
|
||||
/// Used by the chunked tail pipeline to carry pre-first-magic bytes
|
||||
/// into the next (earlier) chunk.
|
||||
pub(crate) struct ScanResult {
|
||||
pub first_magic: Option<usize>,
|
||||
}
|
||||
|
||||
/// Scans `buf` for blocks and calls `on_block` for each. `file_offset`
|
||||
/// is the absolute file position of `buf[0]` — used to seed the XOR
|
||||
/// phase and to report absolute `BlkPosition`s so the chunked tail
|
||||
/// pipeline can read mid-file slices.
|
||||
pub(crate) fn scan_bytes(
|
||||
buf: &mut [u8],
|
||||
blk_index: u16,
|
||||
file_offset: usize,
|
||||
xor_bytes: XORBytes,
|
||||
mut on_block: impl FnMut(BlkMetadata, &mut [u8], XORIndex) -> ControlFlow<()>,
|
||||
) {
|
||||
let mut xor_i = XORIndex::default();
|
||||
) -> ScanResult {
|
||||
let mut xor_i = XORIndex::at_offset(file_offset);
|
||||
let mut first_magic: Option<usize> = None;
|
||||
let mut i = 0;
|
||||
|
||||
while let Some(off) = find_magic(&buf[i..], &mut xor_i, xor_bytes) {
|
||||
first_magic.get_or_insert(i + off - MAGIC_BYTES.len());
|
||||
i += off;
|
||||
if i + 4 > buf.len() {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
let mut size_bytes = [buf[i], buf[i + 1], buf[i + 2], buf[i + 3]];
|
||||
xor_i.bytes(&mut size_bytes, xor_bytes);
|
||||
let len = u32::from_le_bytes(size_bytes) as usize;
|
||||
i += 4;
|
||||
if i + len > buf.len() {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
let metadata = BlkMetadata::new(BlkPosition::new(blk_index, i as u32), len as u32);
|
||||
let metadata = BlkMetadata::new(
|
||||
BlkPosition::new(blk_index, (file_offset + i) as u32),
|
||||
len as u32,
|
||||
);
|
||||
if on_block(metadata, &mut buf[i..i + len], xor_i).is_break() {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
i += len;
|
||||
xor_i.add_assign(len);
|
||||
}
|
||||
|
||||
ScanResult { first_magic }
|
||||
}
|
||||
|
||||
@@ -2254,6 +2254,41 @@ function createAverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern(c
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern
|
||||
* @property {SeriesPattern1<StoredI8>} index
|
||||
* @property {CentsSatsUsdPattern} pct05
|
||||
* @property {CentsSatsUsdPattern} pct1
|
||||
* @property {CentsSatsUsdPattern} pct2
|
||||
* @property {CentsSatsUsdPattern} pct5
|
||||
* @property {CentsSatsUsdPattern} pct95
|
||||
* @property {CentsSatsUsdPattern} pct98
|
||||
* @property {CentsSatsUsdPattern} pct99
|
||||
* @property {CentsSatsUsdPattern} pct995
|
||||
* @property {SeriesPattern1<StoredI8>} score
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated series name
|
||||
* @returns {IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern}
|
||||
*/
|
||||
function createIndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(client, acc) {
|
||||
return {
|
||||
index: createSeriesPattern1(client, _m(acc, 'index')),
|
||||
pct05: createCentsSatsUsdPattern(client, _m(acc, 'pct0_5')),
|
||||
pct1: createCentsSatsUsdPattern(client, _m(acc, 'pct01')),
|
||||
pct2: createCentsSatsUsdPattern(client, _m(acc, 'pct02')),
|
||||
pct5: createCentsSatsUsdPattern(client, _m(acc, 'pct05')),
|
||||
pct95: createCentsSatsUsdPattern(client, _m(acc, 'pct95')),
|
||||
pct98: createCentsSatsUsdPattern(client, _m(acc, 'pct98')),
|
||||
pct99: createCentsSatsUsdPattern(client, _m(acc, 'pct99')),
|
||||
pct995: createCentsSatsUsdPattern(client, _m(acc, 'pct99_5')),
|
||||
score: createSeriesPattern1(client, _m(acc, 'score')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5
|
||||
* @property {AverageBlockCumulativeSumPattern<StoredU64>} all
|
||||
@@ -5597,7 +5632,7 @@ function createTransferPattern(client, acc) {
|
||||
* @property {SeriesTree_Indicators_Dormancy} dormancy
|
||||
* @property {SeriesPattern1<StoredF32>} stockToFlow
|
||||
* @property {SeriesPattern1<StoredF32>} sellerExhaustion
|
||||
* @property {SeriesTree_Indicators_RealizedEnvelope} realizedEnvelope
|
||||
* @property {SeriesTree_Indicators_RarityMeter} rarityMeter
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -5607,17 +5642,10 @@ function createTransferPattern(client, acc) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SeriesTree_Indicators_RealizedEnvelope
|
||||
* @property {CentsSatsUsdPattern} pct05
|
||||
* @property {CentsSatsUsdPattern} pct1
|
||||
* @property {CentsSatsUsdPattern} pct2
|
||||
* @property {CentsSatsUsdPattern} pct5
|
||||
* @property {CentsSatsUsdPattern} pct95
|
||||
* @property {CentsSatsUsdPattern} pct98
|
||||
* @property {CentsSatsUsdPattern} pct99
|
||||
* @property {CentsSatsUsdPattern} pct995
|
||||
* @property {SeriesPattern1<StoredI8>} index
|
||||
* @property {SeriesPattern1<StoredI8>} score
|
||||
* @typedef {Object} SeriesTree_Indicators_RarityMeter
|
||||
* @property {IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern} full
|
||||
* @property {IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern} local
|
||||
* @property {IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern} cycle
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -8808,17 +8836,10 @@ class BrkClient extends BrkClientBase {
|
||||
},
|
||||
stockToFlow: createSeriesPattern1(this, 'stock_to_flow'),
|
||||
sellerExhaustion: createSeriesPattern1(this, 'seller_exhaustion'),
|
||||
realizedEnvelope: {
|
||||
pct05: createCentsSatsUsdPattern(this, 'realized_envelope_pct0_5'),
|
||||
pct1: createCentsSatsUsdPattern(this, 'realized_envelope_pct01'),
|
||||
pct2: createCentsSatsUsdPattern(this, 'realized_envelope_pct02'),
|
||||
pct5: createCentsSatsUsdPattern(this, 'realized_envelope_pct05'),
|
||||
pct95: createCentsSatsUsdPattern(this, 'realized_envelope_pct95'),
|
||||
pct98: createCentsSatsUsdPattern(this, 'realized_envelope_pct98'),
|
||||
pct99: createCentsSatsUsdPattern(this, 'realized_envelope_pct99'),
|
||||
pct995: createCentsSatsUsdPattern(this, 'realized_envelope_pct99_5'),
|
||||
index: createSeriesPattern1(this, 'realized_envelope_index'),
|
||||
score: createSeriesPattern1(this, 'realized_envelope_score'),
|
||||
rarityMeter: {
|
||||
full: createIndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(this, 'rarity_meter'),
|
||||
local: createIndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(this, 'local_rarity_meter'),
|
||||
cycle: createIndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(this, 'cycle_rarity_meter'),
|
||||
},
|
||||
},
|
||||
investing: {
|
||||
|
||||
@@ -2722,6 +2722,22 @@ class AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern(Generic[T]
|
||||
self.pct90: _1m1w1y24hPattern[T] = _1m1w1y24hPattern(client, _m(acc, 'pct90'))
|
||||
self.sum: _1m1w1y24hPattern[T] = _1m1w1y24hPattern(client, _m(acc, 'sum'))
|
||||
|
||||
class IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated series name."""
|
||||
self.index: SeriesPattern1[StoredI8] = SeriesPattern1(client, _m(acc, 'index'))
|
||||
self.pct0_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct0_5'))
|
||||
self.pct1: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct01'))
|
||||
self.pct2: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct02'))
|
||||
self.pct5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct05'))
|
||||
self.pct95: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct95'))
|
||||
self.pct98: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct98'))
|
||||
self.pct99: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct99'))
|
||||
self.pct99_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'pct99_5'))
|
||||
self.score: SeriesPattern1[StoredI8] = SeriesPattern1(client, _m(acc, 'score'))
|
||||
|
||||
class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern5:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -4816,20 +4832,13 @@ class SeriesTree_Indicators_Dormancy:
|
||||
self.supply_adj: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'dormancy_supply_adj')
|
||||
self.flow: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'dormancy_flow')
|
||||
|
||||
class SeriesTree_Indicators_RealizedEnvelope:
|
||||
class SeriesTree_Indicators_RarityMeter:
|
||||
"""Series tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.pct0_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct0_5')
|
||||
self.pct1: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct01')
|
||||
self.pct2: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct02')
|
||||
self.pct5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct05')
|
||||
self.pct95: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct95')
|
||||
self.pct98: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct98')
|
||||
self.pct99: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct99')
|
||||
self.pct99_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'realized_envelope_pct99_5')
|
||||
self.index: SeriesPattern1[StoredI8] = SeriesPattern1(client, 'realized_envelope_index')
|
||||
self.score: SeriesPattern1[StoredI8] = SeriesPattern1(client, 'realized_envelope_score')
|
||||
self.full: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern = IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(client, 'rarity_meter')
|
||||
self.local: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern = IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(client, 'local_rarity_meter')
|
||||
self.cycle: IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern = IndexPct0Pct1Pct2Pct5Pct95Pct98Pct99ScorePattern(client, 'cycle_rarity_meter')
|
||||
|
||||
class SeriesTree_Indicators:
|
||||
"""Series tree node."""
|
||||
@@ -4845,7 +4854,7 @@ class SeriesTree_Indicators:
|
||||
self.dormancy: SeriesTree_Indicators_Dormancy = SeriesTree_Indicators_Dormancy(client)
|
||||
self.stock_to_flow: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'stock_to_flow')
|
||||
self.seller_exhaustion: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'seller_exhaustion')
|
||||
self.realized_envelope: SeriesTree_Indicators_RealizedEnvelope = SeriesTree_Indicators_RealizedEnvelope(client)
|
||||
self.rarity_meter: SeriesTree_Indicators_RarityMeter = SeriesTree_Indicators_RarityMeter(client)
|
||||
|
||||
class SeriesTree_Investing_Period_DcaCostBasis:
|
||||
"""Series tree node."""
|
||||
|
||||
@@ -353,6 +353,17 @@ export function createCointimeSection() {
|
||||
{
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
{
|
||||
name: "AVIV",
|
||||
title: "AVIV Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: cap.aviv.ratio,
|
||||
name: "AVIV",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Reserve Risk",
|
||||
title: "Reserve Risk",
|
||||
@@ -365,22 +376,11 @@ export function createCointimeSection() {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AVIV",
|
||||
title: "AVIV Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: cap.aviv.ratio,
|
||||
name: "AVIV",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Cointime-Adjusted",
|
||||
name: "Adjusted",
|
||||
tree: [
|
||||
{
|
||||
name: "Inflation",
|
||||
|
||||
@@ -4,14 +4,14 @@ import { brk } from "../../utils/client.js";
|
||||
|
||||
/** @type {readonly AddressableType[]} */
|
||||
const ADDRESSABLE_TYPES = [
|
||||
"p2pk65",
|
||||
"p2pk33",
|
||||
"p2pkh",
|
||||
"p2sh",
|
||||
"p2wpkh",
|
||||
"p2wsh",
|
||||
"p2tr",
|
||||
"p2a",
|
||||
"p2tr",
|
||||
"p2wsh",
|
||||
"p2wpkh",
|
||||
"p2sh",
|
||||
"p2pkh",
|
||||
"p2pk33",
|
||||
"p2pk65",
|
||||
];
|
||||
|
||||
/** @type {(key: SpendableType) => key is AddressableType} */
|
||||
@@ -162,12 +162,13 @@ export function buildCohortData() {
|
||||
},
|
||||
);
|
||||
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key, i, arr) => {
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key) => {
|
||||
const names = SPENDABLE_TYPE_NAMES[key];
|
||||
return {
|
||||
key,
|
||||
name: names.short,
|
||||
title: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
color: colors.scriptType[key],
|
||||
tree: utxoCohorts.type[key],
|
||||
addressCount: {
|
||||
base: addrs.funded[key],
|
||||
@@ -178,10 +179,11 @@ export function buildCohortData() {
|
||||
|
||||
const typeOther = entries(SPENDABLE_TYPE_NAMES)
|
||||
.filter(([key]) => !isAddressable(key))
|
||||
.map(([key, names], i, arr) => ({
|
||||
.map(([key, names]) => ({
|
||||
key,
|
||||
name: names.short,
|
||||
title: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
color: colors.scriptType[key],
|
||||
tree: utxoCohorts.type[key],
|
||||
}));
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { brk } from "../../utils/client.js";
|
||||
|
||||
// Section builders
|
||||
import {
|
||||
@@ -743,3 +744,19 @@ export function createUtxoProfitabilitySection({ range, profit, loss }) {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gini leaf for Distribution > Address Balance
|
||||
* @returns {AnyPartialOption}
|
||||
*/
|
||||
export function createAddressBalanceGiniLeaf() {
|
||||
return {
|
||||
name: "Gini",
|
||||
title: "Address Balance Gini Coefficient",
|
||||
bottom: percentRatio({
|
||||
pattern: brk.series.indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.loss,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -653,120 +653,117 @@ export function createMarketSection() {
|
||||
],
|
||||
},
|
||||
|
||||
// Momentum
|
||||
// RSI
|
||||
{
|
||||
name: "Momentum",
|
||||
name: "RSI",
|
||||
tree: [
|
||||
{
|
||||
name: "RSI",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "RSI",
|
||||
bottom: [
|
||||
...ROLLING_WINDOWS_TO_1M.flatMap((w) =>
|
||||
indexRatio({
|
||||
pattern: technical.rsi[w.key].rsi,
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
}),
|
||||
),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: "Compare",
|
||||
title: "RSI",
|
||||
bottom: [
|
||||
...ROLLING_WINDOWS_TO_1M.flatMap((w) =>
|
||||
indexRatio({
|
||||
pattern: technical.rsi[w.key].rsi,
|
||||
name: w.name,
|
||||
title: `${w.title} RSI`,
|
||||
bottom: [
|
||||
...indexRatio({
|
||||
pattern: rsi.rsi,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
{
|
||||
name: "Stochastic",
|
||||
tree: ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `${w.title} Stochastic RSI`,
|
||||
bottom: [
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiK,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
}),
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiD,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
}),
|
||||
...priceLines({
|
||||
unit: Unit.index,
|
||||
numbers: [80, 20],
|
||||
}),
|
||||
],
|
||||
};
|
||||
color: w.color,
|
||||
}),
|
||||
},
|
||||
),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `${w.title} RSI`,
|
||||
bottom: [
|
||||
...indexRatio({
|
||||
pattern: rsi.rsi,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
{
|
||||
name: "MACD",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "MACD",
|
||||
bottom: ROLLING_WINDOWS_TO_1M.map((w) =>
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => ({
|
||||
name: "Stochastic",
|
||||
tree: ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `${w.title} MACD`,
|
||||
title: `${w.title} Stochastic RSI`,
|
||||
bottom: [
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: "MACD",
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiK,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
series: technical.macd[w.key].signal,
|
||||
name: "Signal",
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiD,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
histogram({
|
||||
series: technical.macd[w.key].histogram,
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
...priceLines({
|
||||
unit: Unit.index,
|
||||
numbers: [80, 20],
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// MACD
|
||||
{
|
||||
name: "MACD",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "MACD",
|
||||
bottom: ROLLING_WINDOWS_TO_1M.map((w) =>
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} MACD`,
|
||||
bottom: [
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: "MACD",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
series: technical.macd[w.key].signal,
|
||||
name: "Signal",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
histogram({
|
||||
series: technical.macd[w.key].histogram,
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Volatility
|
||||
{
|
||||
name: "Volatility",
|
||||
@@ -925,201 +922,190 @@ export function createMarketSection() {
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
{
|
||||
name: "Envelope",
|
||||
title: "Realized Envelope",
|
||||
top: priceBands(percentileBands(indicators.realizedEnvelope), {
|
||||
defaultActive: true,
|
||||
name: "Rarity Meter",
|
||||
tree: /** @type {const} */ ([
|
||||
{ key: "full", name: "Full", title: "Rarity Meter" },
|
||||
{ key: "local", name: "Local", title: "Local Rarity Meter" },
|
||||
{ key: "cycle", name: "Cycle", title: "Cycle Rarity Meter" },
|
||||
]).map((v) => {
|
||||
const m = indicators.rarityMeter[v.key];
|
||||
return {
|
||||
name: v.name,
|
||||
title: v.title,
|
||||
top: priceBands(percentileBands(m), { defaultActive: true }),
|
||||
bottom: [
|
||||
histogram({
|
||||
series: m.index,
|
||||
name: "Index",
|
||||
unit: Unit.count,
|
||||
colorFn: (v) =>
|
||||
/** @type {const} */ ([
|
||||
colors.ratioPct._0_5,
|
||||
colors.ratioPct._1,
|
||||
colors.ratioPct._2,
|
||||
colors.ratioPct._5,
|
||||
colors.transparent,
|
||||
colors.ratioPct._95,
|
||||
colors.ratioPct._98,
|
||||
colors.ratioPct._99,
|
||||
colors.ratioPct._99_5,
|
||||
])[v + 4],
|
||||
}),
|
||||
baseline({
|
||||
series: m.score,
|
||||
name: "Score",
|
||||
unit: Unit.count,
|
||||
color: [colors.ratioPct._99, colors.ratioPct._1],
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "NVT",
|
||||
title: "NVT Ratio",
|
||||
bottom: [
|
||||
histogram({
|
||||
series: indicators.realizedEnvelope.index,
|
||||
name: "Index",
|
||||
unit: Unit.count,
|
||||
colorFn: (v) =>
|
||||
/** @type {const} */ ([
|
||||
colors.ratioPct._0_5,
|
||||
colors.ratioPct._1,
|
||||
colors.ratioPct._2,
|
||||
colors.ratioPct._5,
|
||||
colors.transparent,
|
||||
colors.ratioPct._95,
|
||||
colors.ratioPct._98,
|
||||
colors.ratioPct._99,
|
||||
colors.ratioPct._99_5,
|
||||
])[v + 4],
|
||||
line({
|
||||
series: indicators.nvt.ratio,
|
||||
name: "NVT",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Thermocap Multiple",
|
||||
title: "Thermocap Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.thermoCapMultiple.ratio,
|
||||
name: "Thermocap",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.puellMultiple.ratio,
|
||||
name: "Puell",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "RHODL Ratio",
|
||||
title: "RHODL Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.rhodlRatio.ratio,
|
||||
name: "RHODL",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stock-to-Flow",
|
||||
title: "Stock-to-Flow",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.stockToFlow,
|
||||
name: "S2F",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
price({
|
||||
series: ma.sma._111d,
|
||||
name: "111d SMA",
|
||||
color: colors.indicator.upper,
|
||||
}),
|
||||
price({
|
||||
series: ma.sma._350d.x2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.indicator.lower,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
baseline({
|
||||
series: indicators.realizedEnvelope.score,
|
||||
name: "Score",
|
||||
unit: Unit.count,
|
||||
color: [colors.ratioPct._99, colors.ratioPct._1],
|
||||
series: technical.piCycle.ratio,
|
||||
name: "Pi Cycle",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Dormancy",
|
||||
title: "Dormancy",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.dormancy.supplyAdj,
|
||||
name: "Supply Adjusted",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: indicators.dormancy.flow,
|
||||
name: "Flow",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
{
|
||||
name: "NVT",
|
||||
title: "NVT Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.nvt.ratio,
|
||||
name: "NVT",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Thermocap Multiple",
|
||||
title: "Thermocap Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.thermoCapMultiple.ratio,
|
||||
name: "Thermocap",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
name: "Seller Exhaustion",
|
||||
title: "Seller Exhaustion Constant",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.sellerExhaustion,
|
||||
name: "SEC",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cycle",
|
||||
name: "Coin Destruction",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
price({
|
||||
series: ma.sma._111d,
|
||||
name: "111d SMA",
|
||||
color: colors.indicator.upper,
|
||||
}),
|
||||
price({
|
||||
series: ma.sma._350d.x2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.indicator.lower,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
baseline({
|
||||
series: technical.piCycle.ratio,
|
||||
name: "Pi Cycle",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stock-to-Flow",
|
||||
title: "Stock-to-Flow",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.stockToFlow,
|
||||
name: "S2F",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.puellMultiple.ratio,
|
||||
name: "Puell",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "RHODL Ratio",
|
||||
title: "RHODL Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.rhodlRatio.ratio,
|
||||
name: "RHODL",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Dormancy",
|
||||
title: "Dormancy",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.dormancy.supplyAdj,
|
||||
name: "Supply Adjusted",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: indicators.dormancy.flow,
|
||||
name: "Flow",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Seller Exhaustion",
|
||||
title: "Seller Exhaustion Constant",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.sellerExhaustion,
|
||||
name: "SEC",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CDD Supply Adjusted",
|
||||
name: "CDD",
|
||||
title: "Coindays Destroyed (Supply Adjusted)",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.coindaysDestroyedSupplyAdj,
|
||||
name: "CDD SA",
|
||||
name: "CDD",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CYD Supply Adjusted",
|
||||
name: "CYD",
|
||||
title: "Coinyears Destroyed (Supply Adjusted)",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.coinyearsDestroyedSupplyAdj,
|
||||
name: "CYD SA",
|
||||
color: colors.bitcoin,
|
||||
name: "CYD",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gini",
|
||||
title: "Gini Coefficient",
|
||||
bottom: percentRatio({
|
||||
pattern: indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.loss,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -190,9 +190,10 @@ export function createMiningSection() {
|
||||
/**
|
||||
* @param {string} groupTitle
|
||||
* @param {typeof majorPoolData} poolList
|
||||
* @param {string} [name]
|
||||
*/
|
||||
const createPoolCompare = (groupTitle, poolList) => ({
|
||||
name: "Compare",
|
||||
const createPoolCompare = (groupTitle, poolList, name = "Compare") => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
@@ -602,7 +603,7 @@ export function createMiningSection() {
|
||||
{
|
||||
name: "Pools",
|
||||
tree: [
|
||||
createPoolCompare("Major Pools", featuredPools),
|
||||
createPoolCompare("Major Pools", featuredPools, "Featured"),
|
||||
{
|
||||
name: "AntPool & Friends",
|
||||
tree: [
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Unit } from "../utils/units.js";
|
||||
import { entries } from "../utils/array.js";
|
||||
import {
|
||||
line,
|
||||
baseline,
|
||||
fromSupplyPattern,
|
||||
chartsFromFull,
|
||||
chartsFromFullPerBlock,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
chartsFromPercentCumulative,
|
||||
chartsFromPercentCumulativeEntries,
|
||||
chartsFromAggregatedPerBlock,
|
||||
distributionWindowsTree,
|
||||
averagesArray,
|
||||
simpleDeltaTree,
|
||||
ROLLING_WINDOWS,
|
||||
@@ -91,39 +93,6 @@ export function createNetworkSection() {
|
||||
{ key: "reactivated", name: "Reactivated" },
|
||||
]);
|
||||
|
||||
const countTypes = /** @type {const} */ ([
|
||||
{
|
||||
name: "Funded",
|
||||
title: "Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.funded[t],
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
title: "Empty Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.empty[t],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.total[t],
|
||||
},
|
||||
{
|
||||
name: "Funded Reused",
|
||||
title: "Funded Reused Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.reused.count.funded[t],
|
||||
},
|
||||
{
|
||||
name: "Total Reused",
|
||||
title: "Total Reused Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.reused.count.total[t],
|
||||
},
|
||||
]);
|
||||
|
||||
const countMetrics = /** @type {const} */ ([
|
||||
{ key: "funded", name: "Funded", color: undefined },
|
||||
{ key: "empty", name: "Empty", color: colors.gray },
|
||||
@@ -543,6 +512,261 @@ export function createNetworkSection() {
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Mirror of the per-type singles tree, but every leaf is a cross-type
|
||||
* comparison chart (same metric, same unit, one line per addr type).
|
||||
* Structure parallels `createAddressSeriesTreeForType` section-by-section
|
||||
* so users can compare anything they can view on a single type.
|
||||
*/
|
||||
const createAddressByTypeCompare = () => {
|
||||
const typeLines =
|
||||
/**
|
||||
* @param {(t: (typeof addressTypes)[number]) => AnySeriesPattern} getSeries
|
||||
* @param {Unit} [unit]
|
||||
*/
|
||||
(getSeries, unit = Unit.count) =>
|
||||
addressTypes.map((t) =>
|
||||
line({
|
||||
series: getSeries(t),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
);
|
||||
|
||||
const typeBaselines =
|
||||
/**
|
||||
* @param {(t: (typeof addressTypes)[number]) => AnySeriesPattern} getSeries
|
||||
* @param {Unit} [unit]
|
||||
*/
|
||||
(getSeries, unit = Unit.count) =>
|
||||
addressTypes.map((t) =>
|
||||
baseline({
|
||||
series: getSeries(t),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
name: "Compare",
|
||||
tree: [
|
||||
// Count (lifetime Funded/Empty/Total)
|
||||
{
|
||||
name: "Count",
|
||||
tree: countMetrics.map((m) => ({
|
||||
name: m.name,
|
||||
title: `${m.name} Address Count by Type`,
|
||||
bottom: typeLines((t) => addrs[m.key][t.key]),
|
||||
})),
|
||||
},
|
||||
|
||||
// New (rolling sums + cumulative)
|
||||
{
|
||||
name: "New",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: addressTypes,
|
||||
title: (s) => s,
|
||||
metricTitle: "New Addresses by Type",
|
||||
getWindowSeries: (t, key) => addrs.new[t.key].sum[key],
|
||||
getCumulativeSeries: (t) => addrs.new[t.key].cumulative,
|
||||
seriesFn: line,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
|
||||
// Change (rolling deltas, signed, baseline)
|
||||
{
|
||||
name: "Change",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Address Count Change by Type`,
|
||||
bottom: typeBaselines(
|
||||
(t) => addrs.delta[t.key].absolute[w.key],
|
||||
),
|
||||
})),
|
||||
},
|
||||
|
||||
// Growth Rate (rolling percent rates)
|
||||
{
|
||||
name: "Growth Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Address Growth Rate by Type`,
|
||||
bottom: typeLines(
|
||||
(t) => addrs.delta[t.key].rate[w.key].percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
})),
|
||||
},
|
||||
|
||||
// Activity (per activity type, per window)
|
||||
{
|
||||
name: "Activity",
|
||||
tree: activityTypes.map((a) => ({
|
||||
name: a.name,
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} ${a.name} Addresses by Type`,
|
||||
bottom: typeLines(
|
||||
(t) => addrs.activity[t.key][a.key][w.key],
|
||||
),
|
||||
})),
|
||||
})),
|
||||
},
|
||||
|
||||
// Reused
|
||||
{
|
||||
name: "Reused",
|
||||
tree: [
|
||||
{
|
||||
name: "Funded",
|
||||
title: "Funded Reused Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.reused.count.funded[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Reused Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.reused.count.total[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: addressTypes,
|
||||
title: (s) => s,
|
||||
metricTitle:
|
||||
"Transaction Outputs to Reused Addresses by Type",
|
||||
getWindowSeries: (t, key) =>
|
||||
addrs.reused.events.outputToReusedAddrCount[t.key].sum[
|
||||
key
|
||||
],
|
||||
getCumulativeSeries: (t) =>
|
||||
addrs.reused.events.outputToReusedAddrCount[t.key]
|
||||
.cumulative,
|
||||
seriesFn: line,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Share of Transaction Outputs to Reused Addresses by Type`,
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.outputToReusedAddrShare[t.key][
|
||||
w.key
|
||||
].percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title:
|
||||
"Cumulative Share of Transaction Outputs to Reused Addresses by Type",
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.outputToReusedAddrShare[t.key]
|
||||
.percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Inputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: addressTypes,
|
||||
title: (s) => s,
|
||||
metricTitle:
|
||||
"Transaction Inputs from Reused Addresses by Type",
|
||||
getWindowSeries: (t, key) =>
|
||||
addrs.reused.events.inputFromReusedAddrCount[t.key].sum[
|
||||
key
|
||||
],
|
||||
getCumulativeSeries: (t) =>
|
||||
addrs.reused.events.inputFromReusedAddrCount[t.key]
|
||||
.cumulative,
|
||||
seriesFn: line,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Share of Transaction Inputs from Reused Addresses by Type`,
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.inputFromReusedAddrShare[t.key][
|
||||
w.key
|
||||
].percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title:
|
||||
"Cumulative Share of Transaction Inputs from Reused Addresses by Type",
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.inputFromReusedAddrShare[t.key]
|
||||
.percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Exposed
|
||||
{
|
||||
name: "Exposed",
|
||||
tree: [
|
||||
{
|
||||
name: "Funded",
|
||||
title: "Funded Exposed Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.exposed.count.funded[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Exposed Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.exposed.count.total[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Supply",
|
||||
title: "Supply in Exposed Addresses by Type",
|
||||
bottom: addressTypes.flatMap((t) =>
|
||||
satsBtcUsd({
|
||||
pattern: addrs.exposed.supply[t.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a "By Type" subtree: Compare (count / tx count / tx %) plus a
|
||||
* per-type drill-down with the same three metrics.
|
||||
@@ -754,7 +978,7 @@ export function createNetworkSection() {
|
||||
name: "Unspendable",
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
name: "All",
|
||||
title: "Unspendable Supply",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: supply.burned,
|
||||
@@ -1002,19 +1226,74 @@ export function createNetworkSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromAggregatedPerBlock({
|
||||
pattern: outputs.count.total,
|
||||
metric: "Output Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Spendable",
|
||||
tree: chartsFromCount({
|
||||
pattern: outputs.byType.spendableOutputCount,
|
||||
metric: "Spendable Output Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Output Count",
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({
|
||||
series: outputs.count.total.rolling.average[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Output Count`,
|
||||
bottom: [
|
||||
line({
|
||||
series: outputs.count.total.rolling.sum[w.key],
|
||||
name: "Total (Sum)",
|
||||
color: w.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: outputs.byType.spendableOutputCount.sum[w.key],
|
||||
name: "Spendable (Sum)",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: outputs.count.total.rolling.average[w.key],
|
||||
name: "Total (Avg)",
|
||||
color: w.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
series: outputs.byType.spendableOutputCount.average[w.key],
|
||||
name: "Spendable (Avg)",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Cumulative Output Count",
|
||||
bottom: [
|
||||
line({
|
||||
series: outputs.count.total.cumulative,
|
||||
name: "Total",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: outputs.byType.spendableOutputCount.cumulative,
|
||||
name: "Spendable",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
distributionWindowsTree({
|
||||
pattern: outputs.count.total.rolling,
|
||||
metric: "Output Count per Block",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Per Second",
|
||||
@@ -1105,22 +1384,7 @@ export function createNetworkSection() {
|
||||
{
|
||||
name: "By Type",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: countTypes.map((c) => ({
|
||||
name: c.name,
|
||||
title: c.title,
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
series: c.getSeries(t.key),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
createAddressByTypeCompare(),
|
||||
...addressTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressSeriesTreeForType(t.key, t.name),
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
createGroupedCohortFolderAddress,
|
||||
createGroupedAddressCohortFolder,
|
||||
createUtxoProfitabilitySection,
|
||||
createAddressBalanceGiniLeaf,
|
||||
} from "./distribution/index.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
@@ -91,7 +92,7 @@ export function createPartialOptions() {
|
||||
name: "UTXO Age",
|
||||
tree: [
|
||||
{
|
||||
name: "Younger Than",
|
||||
name: "Under",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
@@ -103,7 +104,7 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Older Than",
|
||||
name: "Over",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
@@ -133,7 +134,7 @@ export function createPartialOptions() {
|
||||
name: "UTXO Size",
|
||||
tree: [
|
||||
{
|
||||
name: "Less Than",
|
||||
name: "Under",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithMarketCap({
|
||||
name: "Compare",
|
||||
@@ -147,7 +148,7 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "More Than",
|
||||
name: "Over",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithMarketCap({
|
||||
name: "Compare",
|
||||
@@ -187,7 +188,7 @@ export function createPartialOptions() {
|
||||
name: "Address Balance",
|
||||
tree: [
|
||||
{
|
||||
name: "Less Than",
|
||||
name: "Under",
|
||||
tree: [
|
||||
createGroupedAddressCohortFolder({
|
||||
name: "Compare",
|
||||
@@ -199,7 +200,7 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "More Than",
|
||||
name: "Over",
|
||||
tree: [
|
||||
createGroupedAddressCohortFolder({
|
||||
name: "Compare",
|
||||
@@ -222,6 +223,7 @@ export function createPartialOptions() {
|
||||
...addressesAmountRange.map(createAddressCohortFolder),
|
||||
],
|
||||
},
|
||||
createAddressBalanceGiniLeaf(),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -234,8 +236,25 @@ export function createPartialOptions() {
|
||||
list: typeAddressable,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...typeAddressable.map(createCohortFolderAddress),
|
||||
...typeOther.map(createCohortFolderWithoutRelative),
|
||||
.../** @satisfies {readonly SpendableType[]} */ ([
|
||||
"p2a",
|
||||
"p2tr",
|
||||
"p2wsh",
|
||||
"p2wpkh",
|
||||
"p2sh",
|
||||
"p2ms",
|
||||
"p2pkh",
|
||||
"p2pk33",
|
||||
"p2pk65",
|
||||
"empty",
|
||||
"unknown",
|
||||
]).flatMap((key) => {
|
||||
const addr = typeAddressable.find((t) => t.key === key);
|
||||
if (addr) return [createCohortFolderAddress(addr)];
|
||||
const other = typeOther.find((t) => t.key === key);
|
||||
if (other) return [createCohortFolderWithoutRelative(other)];
|
||||
return [];
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user