global: add cohorts by entry

This commit is contained in:
nym21
2026-06-14 00:40:18 +02:00
parent 297fc3b855
commit c85da92cbc
44 changed files with 1927 additions and 19567 deletions
Generated
+49 -50
View File
@@ -213,7 +213,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags 2.12.1",
"bitflags 2.13.0",
"cexpr",
"clang-sys",
"itertools",
@@ -277,9 +277,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.12.1"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "blk"
@@ -757,9 +757,9 @@ checksum = "1c53ba0f290bfc610084c05582d9c5d421662128fc69f4bf236707af6fd321b9"
[[package]]
name = "cc"
version = "1.2.63"
version = "1.2.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1240,9 +1240,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fjall"
version = "3.1.4"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62b25b4d815ae178d7d9e4aa32ee59f072efd5431c736abede1e6ee13c8c453"
checksum = "038acd422d607e0eca09e093f299f9eccf9bd097554343d93746afff81a45113"
dependencies = [
"byteorder-lite",
"byteview",
@@ -1292,7 +1292,7 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
dependencies = [
"bitflags 2.12.1",
"bitflags 2.13.0",
"byteorder",
"core-foundation",
"core-graphics",
@@ -1522,9 +1522,9 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
[[package]]
name = "http"
version = "1.4.1"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
dependencies = [
"bytes",
"itoa",
@@ -1879,13 +1879,12 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
[[package]]
name = "js-sys"
version = "0.3.99"
version = "0.3.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
@@ -1999,9 +1998,9 @@ checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]]
name = "lsm-tree"
version = "3.1.4"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e447ac67ff6aef4ec07fc19e507b219336cbba90a697c0dbeb1bf51b91536b67"
checksum = "8ef86c3c797c10eefcc73407c43ae48c19d4df686131a8334b2895a513e91df4"
dependencies = [
"byteorder-lite",
"byteview",
@@ -2036,9 +2035,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.8.1"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "memmap2"
@@ -2360,9 +2359,9 @@ dependencies = [
[[package]]
name = "quick_cache"
version = "0.6.22"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1c821816e9b928e20e92ed59bb3ac4aab321d16ca2316871c9fe7ca739cd477"
checksum = "3a3db184a8b66cfe87f0263a1de147a6b554c864d1767c6f7fa4eb0e5497b565"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
@@ -2463,7 +2462,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.12.1",
"bitflags 2.13.0",
]
[[package]]
@@ -2499,9 +2498,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr",
@@ -2522,9 +2521,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "ring"
@@ -2576,7 +2575,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags 2.12.1",
"bitflags 2.13.0",
"errno",
"libc",
"linux-raw-sys",
@@ -2859,9 +2858,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "socket2"
@@ -3144,7 +3143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"async-compression",
"bitflags 2.12.1",
"bitflags 2.13.0",
"bytes",
"futures-core",
"futures-util",
@@ -3336,9 +3335,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "varint-rs"
version = "2.2.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
checksum = "bfa6c38708f6257f1ec2ca7e5a11f9bbf58a27d7060078b6b333624968183d96"
[[package]]
name = "vecdb"
@@ -3397,9 +3396,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen 0.57.1",
]
@@ -3415,9 +3414,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.122"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
dependencies = [
"cfg-if",
"once_cell",
@@ -3428,9 +3427,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.122"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -3438,9 +3437,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.122"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -3451,9 +3450,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.122"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
dependencies = [
"unicode-ident",
]
@@ -3486,7 +3485,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags 2.12.1",
"bitflags 2.13.0",
"hashbrown 0.15.5",
"indexmap",
"semver",
@@ -3494,9 +3493,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.99"
version = "0.3.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3768,7 +3767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags 2.12.1",
"bitflags 2.13.0",
"indexmap",
"log",
"serde",
@@ -3846,18 +3845,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.50"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.50"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
dependencies = [
"proc-macro2",
"quote",
@@ -3887,9 +3886,9 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.8.2"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
[[package]]
name = "zerotrie"
+2 -2
View File
@@ -64,7 +64,7 @@ color-eyre = "0.6.5"
corepc-jsonrpc = { package = "jsonrpc", version = "0.19.0", features = ["simple_http"], default-features = false }
corepc-types = { version = "0.14.0", features = ["std"], default-features = false }
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
fjall = "3.1.4"
fjall = "3.1.5"
indexmap = { version = "2.14.0", features = ["serde"] }
jiff = { version = "0.2.28", features = ["perf-inline", "tz-system"], default-features = false }
owo-colors = "4.3.0"
@@ -77,7 +77,7 @@ serde = "1.0.228"
serde_bytes = "0.11.19"
serde_derive = "1.0.228"
serde_json = { version = "1.0.150", features = ["float_roundtrip", "preserve_order"] }
smallvec = "1.15.1"
smallvec = "1.15.2"
tokio = { version = "1.52.3", features = ["rt-multi-thread"] }
tower-http = { version = "0.6.11", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
tower-layer = "0.3"
+4 -3
View File
@@ -6,9 +6,9 @@
use std::collections::BTreeMap;
use brk_cohort::{
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, EPOCH_NAMES, LOSS_NAMES, OVER_AGE_NAMES,
OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES,
UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, ENTRY_NAMES, EPOCH_NAMES, LOSS_NAMES,
OVER_AGE_NAMES, OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES,
SPENDABLE_TYPE_NAMES, TERM_NAMES, UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
};
use brk_types::{Index, pools};
use serde::Serialize;
@@ -59,6 +59,7 @@ impl CohortConstants {
("TERM_NAMES", to_value(&TERM_NAMES)),
("EPOCH_NAMES", to_value(&EPOCH_NAMES)),
("CLASS_NAMES", to_value(&CLASS_NAMES)),
("ENTRY_NAMES", to_value(&ENTRY_NAMES)),
("SPENDABLE_TYPE_NAMES", to_value(&SPENDABLE_TYPE_NAMES)),
("AGE_RANGE_NAMES", to_value(&AGE_RANGE_NAMES)),
("UNDER_AGE_NAMES", to_value(&UNDER_AGE_NAMES)),
+603 -18
View File
@@ -1191,6 +1191,22 @@ pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern {
pub sopr: AdjustedRatioValuePattern,
}
/// Pattern struct for repeated tree structure.
pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2 {
pub cap: CentsDeltaToUsdPattern,
pub capitalized: PricePattern,
pub gross_pnl: BlockCumulativeSumPattern,
pub loss: BlockCumulativeNegativeSumPattern,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub peak_regret: BlockCumulativeSumPattern,
pub price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern,
pub profit: BlockCumulativeSumPattern,
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub sopr: RatioValuePattern2,
}
/// Pattern struct for repeated tree structure.
pub struct EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2 {
pub empty: _1m1w1y24hBpsPercentRatioPattern,
@@ -1658,6 +1674,17 @@ pub struct ActiveInputOutputSpendablePattern {
pub spendable_output_to_reused_addr_share: _1m1w1y24hBpsPercentRatioPattern,
}
/// Pattern struct for repeated tree structure.
pub struct ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2 {
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub cost_basis: InMaxMinPerSupplyPattern,
pub invested_capital: InPattern,
pub outputs: SpendingSpentUnspentPattern,
pub realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2,
pub supply: DeltaDominanceHalfInTotalPattern2,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
}
/// Pattern struct for repeated tree structure.
pub struct CapLossMvrvNetPriceProfitSoprPattern {
pub cap: CentsDeltaUsdPattern,
@@ -3408,6 +3435,22 @@ impl PriceRatioPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct RatioValuePattern2 {
pub ratio: _1m1w1y24hPattern<StoredF64>,
pub value_destroyed: AverageBlockCumulativeSumPattern<Cents>,
}
impl RatioValuePattern2 {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "sopr")),
value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "value_destroyed")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct RatioValuePattern {
pub ratio: _24hPattern,
@@ -7199,6 +7242,7 @@ pub struct SeriesTree_Cohorts_Utxo {
pub over_age: SeriesTree_Cohorts_Utxo_OverAge,
pub epoch: SeriesTree_Cohorts_Utxo_Epoch,
pub class: SeriesTree_Cohorts_Utxo_Class,
pub entry: SeriesTree_Cohorts_Utxo_Entry,
pub over_amount: SeriesTree_Cohorts_Utxo_OverAmount,
pub amount_range: SeriesTree_Cohorts_Utxo_AmountRange,
pub under_amount: SeriesTree_Cohorts_Utxo_UnderAmount,
@@ -7218,6 +7262,7 @@ impl SeriesTree_Cohorts_Utxo {
over_age: SeriesTree_Cohorts_Utxo_OverAge::new(client.clone(), format!("{base_path}_over_age")),
epoch: SeriesTree_Cohorts_Utxo_Epoch::new(client.clone(), format!("{base_path}_epoch")),
class: SeriesTree_Cohorts_Utxo_Class::new(client.clone(), format!("{base_path}_class")),
entry: SeriesTree_Cohorts_Utxo_Entry::new(client.clone(), format!("{base_path}_entry")),
over_amount: SeriesTree_Cohorts_Utxo_OverAmount::new(client.clone(), format!("{base_path}_over_amount")),
amount_range: SeriesTree_Cohorts_Utxo_AmountRange::new(client.clone(), format!("{base_path}_amount_range")),
under_amount: SeriesTree_Cohorts_Utxo_UnderAmount::new(client.clone(), format!("{base_path}_under_amount")),
@@ -7999,7 +8044,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized {
pub price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr,
pub sopr: RatioValuePattern2,
pub gross_pnl: BlockCumulativeSumPattern,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub peak_regret: BlockCumulativeSumPattern,
@@ -8016,7 +8061,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized {
price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price::new(client.clone(), format!("{base_path}_price")),
mvrv: SeriesPattern1::new(client.clone(), "lth_mvrv".to_string()),
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "lth_net".to_string()),
sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr::new(client.clone(), format!("{base_path}_sopr")),
sopr: RatioValuePattern2::new(client.clone(), "lth".to_string()),
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()),
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_peak_regret".to_string()),
@@ -8236,21 +8281,6 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev_1y {
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr {
pub value_destroyed: AverageBlockCumulativeSumPattern<Cents>,
pub ratio: _1m1w1y24hPattern<StoredF64>,
}
impl SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr {
pub fn new(client: Arc<BrkClientBase>, 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()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_AgeRange {
pub under_1h: ActivityOutputsRealizedSupplyUnrealizedPattern,
@@ -8466,6 +8496,561 @@ impl SeriesTree_Cohorts_Utxo_Class {
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry {
pub discount: SeriesTree_Cohorts_Utxo_Entry_Discount,
pub premium: SeriesTree_Cohorts_Utxo_Entry_Premium,
}
impl SeriesTree_Cohorts_Utxo_Entry {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
discount: SeriesTree_Cohorts_Utxo_Entry_Discount::new(client.clone(), format!("{base_path}_discount")),
premium: SeriesTree_Cohorts_Utxo_Entry_Premium::new(client.clone(), format!("{base_path}_premium")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount {
pub supply: DeltaDominanceHalfInTotalPattern2,
pub outputs: SpendingSpentUnspentPattern,
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized,
pub cost_basis: InMaxMinPerSupplyPattern,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
pub invested_capital: InPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "veteran_supply".to_string()),
outputs: SpendingSpentUnspentPattern::new(client.clone(), "veteran".to_string()),
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "veteran".to_string()),
realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized::new(client.clone(), format!("{base_path}_realized")),
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "veteran".to_string()),
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "veteran".to_string()),
invested_capital: InPattern::new(client.clone(), "veteran_invested_capital_in".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized {
pub cap: CentsDeltaToUsdPattern,
pub profit: BlockCumulativeSumPattern,
pub loss: BlockCumulativeNegativeSumPattern,
pub price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub sopr: RatioValuePattern2,
pub gross_pnl: BlockCumulativeSumPattern,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub peak_regret: BlockCumulativeSumPattern,
pub capitalized: PricePattern,
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cap: CentsDeltaToUsdPattern::new(client.clone(), "veteran_realized_cap".to_string()),
profit: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_profit".to_string()),
loss: BlockCumulativeNegativeSumPattern::new(client.clone(), "veteran_realized_loss".to_string()),
price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price::new(client.clone(), format!("{base_path}_price")),
mvrv: SeriesPattern1::new(client.clone(), "veteran_mvrv".to_string()),
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "veteran_net".to_string()),
sopr: RatioValuePattern2::new(client.clone(), "veteran".to_string()),
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_gross_pnl".to_string()),
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "veteran_sell_side_risk_ratio".to_string()),
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_peak_regret".to_string()),
capitalized: PricePattern::new(client.clone(), "veteran_capitalized_price".to_string()),
profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "veteran_realized_profit_to_loss_ratio".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price {
pub usd: SeriesPattern1<Dollars>,
pub cents: SeriesPattern1<Cents>,
pub sats: SeriesPattern1<SatsFract>,
pub bps: SeriesPattern1<BasisPoints32>,
pub ratio: SeriesPattern1<StoredF32>,
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
pub sma: _1m1w1y2y4yAllPattern,
pub std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
usd: SeriesPattern1::new(client.clone(), "veteran_realized_price".to_string()),
cents: SeriesPattern1::new(client.clone(), "veteran_realized_price_cents".to_string()),
sats: SeriesPattern1::new(client.clone(), "veteran_realized_price_sats".to_string()),
bps: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_bps".to_string()),
ratio: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio".to_string()),
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "veteran_realized_price".to_string()),
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "veteran_realized_price_ratio_sma".to_string()),
std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev {
pub all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All,
pub _4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y,
pub _2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y,
pub _1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All::new(client.clone(), format!("{base_path}_all")),
_4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y::new(client.clone(), format!("{base_path}_4y")),
_2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y::new(client.clone(), format!("{base_path}_2y")),
_1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y::new(client.clone(), format!("{base_path}_1y")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_4y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_4y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_4y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_4y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_4y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_4y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_4y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_4y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_4y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_4y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_4y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_4y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_4y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_4y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_4y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_2y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_2y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_2y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_2y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_2y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_2y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_2y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_2y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_2y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_2y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_2y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_2y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_2y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_2y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_2y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_1y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_1y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_1y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_1y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_1y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_1y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_1y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_1y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_1y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_1y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_1y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_1y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_1y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_1y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_1y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium {
pub supply: DeltaDominanceHalfInTotalPattern2,
pub outputs: SpendingSpentUnspentPattern,
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized,
pub cost_basis: InMaxMinPerSupplyPattern,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
pub invested_capital: InPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "rookie_supply".to_string()),
outputs: SpendingSpentUnspentPattern::new(client.clone(), "rookie".to_string()),
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "rookie".to_string()),
realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized::new(client.clone(), format!("{base_path}_realized")),
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "rookie".to_string()),
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "rookie".to_string()),
invested_capital: InPattern::new(client.clone(), "rookie_invested_capital_in".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized {
pub cap: CentsDeltaToUsdPattern,
pub profit: BlockCumulativeSumPattern,
pub loss: BlockCumulativeNegativeSumPattern,
pub price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub sopr: RatioValuePattern2,
pub gross_pnl: BlockCumulativeSumPattern,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub peak_regret: BlockCumulativeSumPattern,
pub capitalized: PricePattern,
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cap: CentsDeltaToUsdPattern::new(client.clone(), "rookie_realized_cap".to_string()),
profit: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_profit".to_string()),
loss: BlockCumulativeNegativeSumPattern::new(client.clone(), "rookie_realized_loss".to_string()),
price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price::new(client.clone(), format!("{base_path}_price")),
mvrv: SeriesPattern1::new(client.clone(), "rookie_mvrv".to_string()),
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "rookie_net".to_string()),
sopr: RatioValuePattern2::new(client.clone(), "rookie".to_string()),
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_gross_pnl".to_string()),
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "rookie_sell_side_risk_ratio".to_string()),
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_peak_regret".to_string()),
capitalized: PricePattern::new(client.clone(), "rookie_capitalized_price".to_string()),
profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "rookie_realized_profit_to_loss_ratio".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price {
pub usd: SeriesPattern1<Dollars>,
pub cents: SeriesPattern1<Cents>,
pub sats: SeriesPattern1<SatsFract>,
pub bps: SeriesPattern1<BasisPoints32>,
pub ratio: SeriesPattern1<StoredF32>,
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
pub sma: _1m1w1y2y4yAllPattern,
pub std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
usd: SeriesPattern1::new(client.clone(), "rookie_realized_price".to_string()),
cents: SeriesPattern1::new(client.clone(), "rookie_realized_price_cents".to_string()),
sats: SeriesPattern1::new(client.clone(), "rookie_realized_price_sats".to_string()),
bps: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_bps".to_string()),
ratio: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio".to_string()),
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "rookie_realized_price".to_string()),
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "rookie_realized_price_ratio_sma".to_string()),
std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev {
pub all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All,
pub _4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y,
pub _2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y,
pub _1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All::new(client.clone(), format!("{base_path}_all")),
_4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y::new(client.clone(), format!("{base_path}_4y")),
_2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y::new(client.clone(), format!("{base_path}_2y")),
_1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y::new(client.clone(), format!("{base_path}_1y")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_4y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_4y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_4y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_4y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_4y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_4y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_4y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_4y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_4y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_4y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_4y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_4y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_4y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_4y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_4y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_2y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_2y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_2y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_2y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_2y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_2y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_2y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_2y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_2y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_2y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_2y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_2y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_2y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_2y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_2y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_1y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_1y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_1y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_1y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_1y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_1y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_1y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_1y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_1y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_1y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_1y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_1y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_1y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_1y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_1y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_OverAmount {
pub _1sat: ActivityOutputsRealizedSupplyUnrealizedPattern2,
@@ -8953,7 +9538,7 @@ pub struct BrkClient {
impl BrkClient {
/// Client version.
pub const VERSION: &'static str = "v0.3.2";
pub const VERSION: &'static str = "v0.3.3";
/// Create a new client with the given base URL.
pub fn new(base_url: impl Into<String>) -> Self {
+104
View File
@@ -0,0 +1,104 @@
use brk_traversable::Traversable;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Serialize;
use super::{CohortName, Filter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryPrice {
Discount,
Premium,
}
impl EntryPrice {
#[inline]
pub const fn from_is_discount(is_discount: bool) -> Self {
if is_discount {
Self::Discount
} else {
Self::Premium
}
}
#[inline]
pub const fn is_discount(self) -> bool {
matches!(self, Self::Discount)
}
}
pub const ENTRY_FILTERS: ByEntry<Filter> = ByEntry {
discount: Filter::Entry(EntryPrice::Discount),
premium: Filter::Entry(EntryPrice::Premium),
};
pub const ENTRY_NAMES: ByEntry<CohortName> = ByEntry {
discount: CohortName::new("veteran", "Veteran", "Veteran Coins"),
premium: CohortName::new("rookie", "Rookie", "Rookie Coins"),
};
#[derive(Default, Clone, Traversable, Serialize)]
pub struct ByEntry<T> {
pub discount: T,
pub premium: T,
}
impl ByEntry<CohortName> {
pub const fn names() -> &'static Self {
&ENTRY_NAMES
}
}
impl<T> ByEntry<T> {
pub fn new<F>(mut create: F) -> Self
where
F: FnMut(Filter, &'static str) -> T,
{
let f = ENTRY_FILTERS;
let n = ENTRY_NAMES;
Self {
discount: create(f.discount, n.discount.id),
premium: create(f.premium, n.premium.id),
}
}
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
where
F: FnMut(Filter, &'static str) -> Result<T, E>,
{
let f = ENTRY_FILTERS;
let n = ENTRY_NAMES;
Ok(Self {
discount: create(f.discount, n.discount.id)?,
premium: create(f.premium, n.premium.id)?,
})
}
pub fn get(&self, entry: EntryPrice) -> &T {
match entry {
EntryPrice::Discount => &self.discount,
EntryPrice::Premium => &self.premium,
}
}
pub fn get_mut(&mut self, entry: EntryPrice) -> &mut T {
match entry {
EntryPrice::Discount => &mut self.discount,
EntryPrice::Premium => &mut self.premium,
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
[&self.discount, &self.premium].into_iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
[&mut self.discount, &mut self.premium].into_iter()
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut T>
where
T: Send + Sync,
{
[&mut self.discount, &mut self.premium].into_par_iter()
}
}
+2 -1
View File
@@ -24,7 +24,7 @@ impl CohortContext {
/// Build full name for a filter, adding prefix only for Time/Amount filters.
///
/// Prefix rules:
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Type`
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Entry`, `Type`
/// - Context prefix: `Time`, `Amount`
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
match filter {
@@ -32,6 +32,7 @@ impl CohortContext {
| Filter::Term(_)
| Filter::Epoch(_)
| Filter::Class(_)
| Filter::Entry(_)
| Filter::Type(_) => name.to_string(),
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
}
+8 -3
View File
@@ -1,6 +1,6 @@
use brk_types::{Halving, OutputType, Sats, Year};
use super::{AmountFilter, CohortContext, Term, TimeFilter};
use super::{AmountFilter, CohortContext, EntryPrice, Term, TimeFilter};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Filter {
@@ -10,6 +10,7 @@ pub enum Filter {
Amount(AmountFilter),
Epoch(Halving),
Class(Year),
Entry(EntryPrice),
Type(OutputType),
}
@@ -68,7 +69,8 @@ impl Filter {
}
/// Whether to compute extended metrics (realized cap ratios, profit/loss ratios, percentiles)
/// For UTXO context: true only for age range cohorts (Range) and aggregate cohorts (All, Term)
/// For UTXO context: true for age range cohorts (Range), aggregate cohorts (All, Term),
/// and immutable entry valuation cohorts.
/// For address context: always false
pub fn is_extended(&self, context: CohortContext) -> bool {
match context {
@@ -76,7 +78,10 @@ impl Filter {
CohortContext::Utxo => {
matches!(
self,
Filter::All | Filter::Term(_) | Filter::Time(TimeFilter::Range(_))
Filter::All
| Filter::Term(_)
| Filter::Time(TimeFilter::Range(_))
| Filter::Entry(_)
)
}
}
+2
View File
@@ -7,6 +7,7 @@ mod amount_range;
mod by_addr_type;
mod by_any_addr;
mod by_epoch;
mod by_entry;
mod by_term;
mod by_type;
mod class;
@@ -36,6 +37,7 @@ pub use amount_range::*;
pub use by_addr_type::*;
pub use by_any_addr::*;
pub use by_epoch::*;
pub use by_entry::*;
pub use by_term::*;
pub use by_type::*;
pub use class::*;
+10 -2
View File
@@ -2,8 +2,8 @@ use brk_traversable::Traversable;
use rayon::prelude::*;
use crate::{
AgeRange, AmountRange, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount, SpendableType,
UnderAge, UnderAmount,
AgeRange, AmountRange, ByEntry, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount,
SpendableType, UnderAge, UnderAmount,
};
#[derive(Default, Clone, Traversable)]
@@ -12,6 +12,7 @@ pub struct UTXOGroups<T> {
pub age_range: AgeRange<T>,
pub epoch: ByEpoch<T>,
pub class: Class<T>,
pub entry: ByEntry<T>,
pub over_age: OverAge<T>,
pub over_amount: OverAmount<T>,
pub amount_range: AmountRange<T>,
@@ -31,6 +32,7 @@ impl<T> UTXOGroups<T> {
age_range: AgeRange::new(&mut create),
epoch: ByEpoch::new(&mut create),
class: Class::new(&mut create),
entry: ByEntry::new(&mut create),
over_age: OverAge::new(&mut create),
over_amount: OverAmount::new(&mut create),
amount_range: AmountRange::new(&mut create),
@@ -51,6 +53,7 @@ impl<T> UTXOGroups<T> {
.chain(self.age_range.iter())
.chain(self.epoch.iter())
.chain(self.class.iter())
.chain(self.entry.iter())
.chain(self.amount_range.iter())
.chain(self.under_amount.iter())
.chain(self.type_.iter())
@@ -66,6 +69,7 @@ impl<T> UTXOGroups<T> {
.chain(self.age_range.iter_mut())
.chain(self.epoch.iter_mut())
.chain(self.class.iter_mut())
.chain(self.entry.iter_mut())
.chain(self.amount_range.iter_mut())
.chain(self.under_amount.iter_mut())
.chain(self.type_.iter_mut())
@@ -84,6 +88,7 @@ impl<T> UTXOGroups<T> {
.chain(self.age_range.par_iter_mut())
.chain(self.epoch.par_iter_mut())
.chain(self.class.par_iter_mut())
.chain(self.entry.par_iter_mut())
.chain(self.amount_range.par_iter_mut())
.chain(self.under_amount.par_iter_mut())
.chain(self.type_.par_iter_mut())
@@ -94,6 +99,7 @@ impl<T> UTXOGroups<T> {
.iter()
.chain(self.epoch.iter())
.chain(self.class.iter())
.chain(self.entry.iter())
.chain(self.amount_range.iter())
.chain(self.type_.iter())
}
@@ -103,6 +109,7 @@ impl<T> UTXOGroups<T> {
.iter_mut()
.chain(self.epoch.iter_mut())
.chain(self.class.iter_mut())
.chain(self.entry.iter_mut())
.chain(self.amount_range.iter_mut())
.chain(self.type_.iter_mut())
}
@@ -115,6 +122,7 @@ impl<T> UTXOGroups<T> {
.par_iter_mut()
.chain(self.epoch.par_iter_mut())
.chain(self.class.par_iter_mut())
.chain(self.entry.par_iter_mut())
.chain(self.amount_range.par_iter_mut())
.chain(self.type_.par_iter_mut())
}
@@ -30,18 +30,34 @@ const TREE_SIZE: usize = TIER0_COUNT + TIER1_COUNT + OVERFLOW; // 190,001
pub(super) struct CostBasisNode {
all_sats: i64,
sth_sats: i64,
discount_sats: i64,
all_usd: i128,
sth_usd: i128,
discount_usd: i128,
}
impl CostBasisNode {
#[inline(always)]
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
fn new_supply(sats: i64, usd: i128, is_sth: bool) -> Self {
Self {
all_sats: sats,
sth_sats: if is_sth { sats } else { 0 },
discount_sats: 0,
all_usd: usd,
sth_usd: if is_sth { usd } else { 0 },
discount_usd: 0,
}
}
#[inline(always)]
fn new_discount(sats: i64, usd: i128) -> Self {
Self {
all_sats: 0,
sth_sats: 0,
discount_sats: sats,
all_usd: 0,
sth_usd: 0,
discount_usd: usd,
}
}
}
@@ -51,8 +67,10 @@ impl FenwickNode for CostBasisNode {
fn add_assign(&mut self, other: &Self) {
self.all_sats += other.all_sats;
self.sth_sats += other.sth_sats;
self.discount_sats += other.discount_sats;
self.all_usd += other.all_usd;
self.sth_usd += other.sth_usd;
self.discount_usd += other.discount_usd;
}
}
@@ -151,16 +169,34 @@ impl CostBasisFenwick {
}
let bucket = price_to_bucket(price);
let delta =
CostBasisNode::new(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
CostBasisNode::new_supply(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
self.tree.add(bucket, &delta);
self.totals.add_assign(&delta);
}
/// Bulk-initialize from BTreeMaps (one per age-range cohort).
/// Call after state import when all pending maps have been drained.
pub(super) fn bulk_init<'a>(
/// Apply a net delta from the discount-entry cohort.
///
/// Supply totals are maintained from the age-range cohorts; this updates
/// only the discount-entry partition so premium can be derived as all - discount.
pub(super) fn apply_discount_delta(&mut self, price: CentsCompact, pending: &PendingDelta) {
let net_sats = u64::from(pending.inc) as i64 - u64::from(pending.dec) as i64;
if net_sats == 0 {
return;
}
let bucket = price_to_bucket(price);
let delta =
CostBasisNode::new_discount(net_sats, price.as_u128() as i128 * net_sats as i128);
self.tree.add(bucket, &delta);
self.totals.add_assign(&delta);
}
/// Bulk-initialize from age-range maps plus the discount-entry map.
/// Age-range maps maintain all/STH/LTH totals; the discount-entry map
/// maintains only the discount partition used to derive premium.
pub(super) fn bulk_init_with_discount<'a>(
&mut self,
maps: impl Iterator<Item = (&'a std::collections::BTreeMap<CentsCompact, Sats>, bool)>,
discount_maps: impl Iterator<Item = &'a std::collections::BTreeMap<CentsCompact, Sats>>,
) {
self.tree.reset();
self.totals = CostBasisNode::default();
@@ -169,7 +205,18 @@ impl CostBasisFenwick {
for (&price, &sats) in map.iter() {
let bucket = price_to_bucket(price);
let s = u64::from(sats) as i64;
let node = CostBasisNode::new(s, price.as_u128() as i128 * s as i128, is_sth);
let node =
CostBasisNode::new_supply(s, price.as_u128() as i128 * s as i128, is_sth);
self.tree.add_raw(bucket, &node);
self.totals.add_assign(&node);
}
}
for map in discount_maps {
for (&price, &sats) in map.iter() {
let bucket = price_to_bucket(price);
let s = u64::from(sats) as i64;
let node = CostBasisNode::new_discount(s, price.as_u128() as i128 * s as i128);
self.tree.add_raw(bucket, &node);
self.totals.add_assign(&node);
}
@@ -212,6 +259,26 @@ impl CostBasisFenwick {
)
}
/// Compute percentile prices for discount-entry cohort.
pub(super) fn percentiles_discount_entry(&self) -> PercentileResult {
self.compute_percentiles(
self.totals.discount_sats,
self.totals.discount_usd,
|n| n.discount_sats,
|n| n.discount_usd,
)
}
/// Compute percentile prices for premium-entry cohort (all - discount).
pub(super) fn percentiles_premium_entry(&self) -> PercentileResult {
self.compute_percentiles(
self.totals.all_sats - self.totals.discount_sats,
self.totals.all_usd - self.totals.discount_usd,
|n| n.all_sats - n.discount_sats,
|n| n.all_usd - n.discount_usd,
)
}
fn compute_percentiles(
&self,
total_sats: i64,
@@ -271,6 +338,37 @@ impl CostBasisFenwick {
return (0, 0, 0);
}
let range = self.density_range(spot_price);
let all_range = range.all_sats.max(0);
let sth_range = range.sth_sats.max(0);
let lth_range = all_range - sth_range;
let lth_total = self.totals.all_sats - self.totals.sth_sats;
(
Self::to_bps(all_range, self.totals.all_sats),
Self::to_bps(sth_range, self.totals.sth_sats),
Self::to_bps(lth_range, lth_total),
)
}
/// Compute supply density for entry cohorts: (discount_bps, premium_bps).
pub(super) fn entry_density(&self, spot_price: Cents) -> (u16, u16) {
if self.totals.all_sats <= 0 {
return (0, 0);
}
let range = self.density_range(spot_price);
let discount_range = range.discount_sats.max(0);
let premium_range = range.all_sats.max(0) - discount_range;
let premium_total = self.totals.all_sats - self.totals.discount_sats;
(
Self::to_bps(discount_range, self.totals.discount_sats),
Self::to_bps(premium_range, premium_total),
)
}
fn density_range(&self, spot_price: Cents) -> CostBasisNode {
let spot_f64 = u64::from(spot_price) as f64;
let low = Cents::from((spot_f64 * 0.95) as u64);
let high = Cents::from((spot_f64 * 1.05) as u64);
@@ -285,24 +383,23 @@ impl CostBasisFenwick {
CostBasisNode::default()
};
let all_range = (cum_high.all_sats - cum_low.all_sats).max(0);
let sth_range = (cum_high.sth_sats - cum_low.sth_sats).max(0);
let lth_range = all_range - sth_range;
CostBasisNode {
all_sats: cum_high.all_sats - cum_low.all_sats,
sth_sats: cum_high.sth_sats - cum_low.sth_sats,
discount_sats: cum_high.discount_sats - cum_low.discount_sats,
all_usd: cum_high.all_usd - cum_low.all_usd,
sth_usd: cum_high.sth_usd - cum_low.sth_usd,
discount_usd: cum_high.discount_usd - cum_low.discount_usd,
}
}
let to_bps = |range: i64, total: i64| -> u16 {
if total <= 0 {
0
} else {
(range as f64 / total as f64 * 10000.0).round() as u16
}
};
let lth_total = self.totals.all_sats - self.totals.sth_sats;
(
to_bps(all_range, self.totals.all_sats),
to_bps(sth_range, self.totals.sth_sats),
to_bps(lth_range, lth_total),
)
#[inline(always)]
fn to_bps(range: i64, total: i64) -> u16 {
if total <= 0 {
0
} else {
(range as f64 / total as f64 * 10000.0).round() as u16
}
}
// -----------------------------------------------------------------------
@@ -1,8 +1,8 @@
use std::path::Path;
use brk_cohort::{
AgeRange, AmountRange, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge, OverAmount,
SpendableType, Term, UnderAge, UnderAmount,
AgeRange, AmountRange, ByEntry, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge,
OverAmount, SpendableType, Term, UnderAge, UnderAmount,
};
use brk_error::Result;
use brk_indexer::Lengths;
@@ -16,7 +16,6 @@ use vecdb::{
use crate::{
blocks,
distribution::{
DynCohortVecs,
metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
@@ -24,6 +23,7 @@ use crate::{
TypeCohortMetrics,
},
state::UTXOCohortState,
DynCohortVecs,
},
indexes,
internal::{ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
@@ -45,6 +45,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub over_age: OverAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>>>,
pub class: Class<UTXOCohortVecs<CoreCohortMetrics<M>>>,
pub entry: ByEntry<UTXOCohortVecs<ExtendedCohortMetrics<M>>>,
pub over_amount: OverAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub amount_range: AmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub under_amount: UnderAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
@@ -67,8 +68,10 @@ pub(crate) struct UTXOCohortsTransientState {
}
impl UTXOCohorts<Rw> {
/// ~71 separate cohorts (21 age + 5 epoch + 18 class + 15 amount + 12 type)
const SEPARATE_COHORT_CAPACITY: usize = 80;
/// Separate cohorts currently total 72:
/// 21 age + 5 epoch + 18 class + 2 entry + 15 amount + 11 spendable type.
/// Keep small headroom because this is only Vec allocation capacity.
const SEPARATE_COHORT_CAPACITY: usize = 82;
/// Import all UTXO cohorts from database.
pub(crate) fn forced_import(
@@ -136,6 +139,26 @@ impl UTXOCohorts<Rw> {
let epoch = ByEpoch::try_new(&core_separate)?;
let class = Class::try_new(&core_separate)?;
let extended_separate =
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<ExtendedCohortMetrics>> {
let full_name = CohortContext::Utxo.full_name(&f, name);
let cfg = ImportConfig {
db,
filter: &f,
full_name: &full_name,
version: v,
indexes,
cached_starts,
};
let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name)));
Ok(UTXOCohortVecs::new(
state,
ExtendedCohortMetrics::forced_import(&cfg)?,
))
};
let entry = ByEntry::try_new(&extended_separate)?;
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
let minimal_separate =
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
@@ -281,6 +304,7 @@ impl UTXOCohorts<Rw> {
lth,
epoch,
class,
entry,
type_,
under_age,
over_age,
@@ -309,6 +333,7 @@ impl UTXOCohorts<Rw> {
sth,
caches,
age_range,
entry,
..
} = self;
caches
@@ -327,7 +352,15 @@ impl UTXOCohorts<Rw> {
Some((map, caches.fenwick.is_sth_at(i)))
})
.collect();
caches.fenwick.bulk_init(maps.into_iter());
let discount_maps = entry
.discount
.state
.as_ref()
.map(|state| state.cost_basis_map())
.into_iter();
caches
.fenwick
.bulk_init_with_discount(maps.into_iter(), discount_maps);
}
/// Apply pending deltas from all age-range cohorts to the Fenwick tree.
@@ -338,7 +371,10 @@ impl UTXOCohorts<Rw> {
}
// Destructure to get separate borrows on caches and age_range
let Self {
caches, age_range, ..
caches,
age_range,
entry,
..
} = self;
for (i, sub) in age_range.iter().enumerate() {
if let Some(state) = sub.state.as_ref() {
@@ -348,6 +384,11 @@ impl UTXOCohorts<Rw> {
});
}
}
if let Some(state) = entry.discount.state.as_ref() {
state.for_each_cost_basis_pending(|&price, delta| {
caches.fenwick.apply_discount_delta(price, delta);
});
}
}
/// Push maturation sats to the matured vecs for the given height.
@@ -365,6 +406,7 @@ impl UTXOCohorts<Rw> {
age_range,
epoch,
class,
entry,
amount_range,
type_,
..
@@ -374,6 +416,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &mut dyn DynCohortVecs)
.chain(epoch.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(class.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(entry.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(
amount_range
.par_iter_mut()
@@ -389,6 +432,7 @@ impl UTXOCohorts<Rw> {
age_range,
epoch,
class,
entry,
amount_range,
type_,
..
@@ -398,6 +442,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &mut dyn DynCohortVecs)
.chain(epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(class.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(amount_range.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(type_.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
}
@@ -409,6 +454,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &dyn DynCohortVecs)
.chain(self.epoch.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.class.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.entry.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.amount_range.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.type_.iter().map(|x| x as &dyn DynCohortVecs))
}
@@ -516,6 +562,7 @@ impl UTXOCohorts<Rw> {
);
all.extend(self.epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(self.class.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(self.entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(
self.amount_range
.iter_mut()
@@ -604,6 +651,7 @@ impl UTXOCohorts<Rw> {
under_amount,
epoch,
class,
entry,
type_,
..
} = self;
@@ -676,6 +724,19 @@ impl UTXOCohorts<Rw> {
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
})
}),
Box::new(|| {
entry.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_lengths,
height_to_market_cap,
ss,
au,
exit,
)
})
}),
Box::new(|| {
amount_range.par_iter_mut().try_for_each(|v| {
v.metrics
@@ -730,6 +791,9 @@ impl UTXOCohorts<Rw> {
for v in self.class.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
for v in self.entry.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
for v in self.amount_range.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
@@ -813,7 +877,7 @@ impl UTXOCohorts<Rw> {
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
/// Called during the block loop after separate cohorts' push_state but before reset.
pub(crate) fn push_overlapping(&mut self, height_price: Cents) {
pub(crate) fn push_overlapping(&mut self, height_price: Cents) -> Cents {
let Self {
all,
sth,
@@ -852,7 +916,7 @@ impl UTXOCohorts<Rw> {
}
}
all.metrics.realized.push_accum(&all_acc);
let all_capitalized_price = all.metrics.realized.push_accum(&all_acc);
sth.metrics.realized.push_accum(&sth_acc);
lth.metrics.realized.push_accum(&lth_acc);
@@ -880,6 +944,8 @@ impl UTXOCohorts<Rw> {
.unrealized
.capitalized_cap_in_loss_raw
.push(CentsSquaredSats::new(lth_ccap.1));
all_capitalized_price
}
}
@@ -50,6 +50,22 @@ impl UTXOCohorts {
let lth = self.caches.fenwick.percentiles_lth();
push_cost_basis(&lth, lth_d, &mut self.lth.metrics.cost_basis);
let (discount_d, premium_d) = self.caches.fenwick.entry_density(spot_price);
let discount = self.caches.fenwick.percentiles_discount_entry();
push_cost_basis(
&discount,
discount_d,
&mut self.entry.discount.metrics.cost_basis,
);
let premium = self.caches.fenwick.percentiles_premium_entry();
push_cost_basis(
&premium,
premium_d,
&mut self.entry.premium.metrics.cost_basis,
);
let prof = self.caches.fenwick.profitability(spot_price);
push_profitability(&prof, &mut self.profitability);
}
@@ -1,3 +1,4 @@
use brk_cohort::EntryPrice;
use brk_types::{Cents, CostBasisSnapshot, Height, Timestamp};
use vecdb::Rw;
@@ -12,6 +13,7 @@ impl UTXOCohorts<Rw> {
/// - The "under_1h" age cohort (all new UTXOs start at 0 hours old)
/// - The appropriate epoch cohort based on block height
/// - The appropriate class cohort based on block timestamp
/// - The immutable entry valuation cohort based on creation price versus anchor
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
/// - The appropriate amount range cohort based on value
pub(crate) fn receive(
@@ -20,13 +22,14 @@ impl UTXOCohorts<Rw> {
height: Height,
timestamp: Timestamp,
price: Cents,
entry: EntryPrice,
) {
let supply_state = received.spendable_supply;
// Pre-compute snapshot once for the 3 cohorts sharing the same supply_state
// Pre-compute snapshot once for cohorts sharing the block-level supply_state
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
// New UTXOs go into under_1h, current epoch, and current class
// New UTXOs go into under_1h plus immutable creation cohorts
self.age_range
.under_1h
.state
@@ -45,6 +48,12 @@ impl UTXOCohorts<Rw> {
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
}
self.entry
.get_mut(entry)
.state
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
// Update output type cohorts (skip types with no outputs this block)
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
@@ -49,7 +49,7 @@ impl UTXOCohorts<Rw> {
// This is the max price between receive and send heights
let peak_price = price_range_max.max_between(receive_height, send_height);
// Pre-compute once for age_range, epoch, year (all share sent.spendable_supply)
// Pre-compute once for cohorts sharing the sent supply.
if let Some(pre) = SendPrecomputed::new(
&sent.spendable_supply,
current_price,
@@ -75,6 +75,12 @@ impl UTXOCohorts<Rw> {
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
}
self.entry
.get_mut(block_state.entry)
.state
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
} else if sent.spendable_supply.utxo_count > 0 {
// Zero-value UTXOs: just subtract supply
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
@@ -85,6 +91,12 @@ impl UTXOCohorts<Rw> {
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
}
self.entry
.get_mut(block_state.entry)
.state
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
}
// Update output type cohorts (skip zero-supply entries)
@@ -1,4 +1,4 @@
use brk_cohort::ByAddrType;
use brk_cohort::{ByAddrType, EntryPrice};
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{
@@ -46,6 +46,7 @@ pub(crate) fn process_blocks(
last_height: Height,
chain_state: &mut Vec<BlockState>,
tx_index_to_height: &mut RangeMap<TxIndex, Height>,
mut entry_anchor: Cents,
cached_prices: &[Cents],
cached_timestamps: &[Timestamp],
cached_price_range_max: &PriceRangeMax,
@@ -370,9 +371,14 @@ pub(crate) fn process_blocks(
.iterate(Sats::FIFTY_BTC, OutputType::P2PK65);
}
let entry = EntryPrice::from_is_discount(
entry_anchor == Cents::ZERO || block_price <= entry_anchor,
);
// Push current block state before processing cohort updates
chain_state.push(BlockState {
supply: transacted.spendable_supply,
entry,
price: block_price,
timestamp,
});
@@ -411,7 +417,7 @@ pub(crate) fn process_blocks(
|| {
// UTXO cohorts receive/send
vecs.utxo_cohorts
.receive(transacted, height, timestamp, block_price);
.receive(transacted, height, timestamp, block_price, entry);
if let Some(min_h) =
vecs.utxo_cohorts
.send(height_to_sent, chain_state, ctx.price_range_max)
@@ -460,7 +466,7 @@ pub(crate) fn process_blocks(
let is_last_of_day = is_last_of_day[offset];
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
push_cohort_states(
entry_anchor = push_cohort_states(
&mut vecs.utxo_cohorts,
&mut vecs.addr_cohorts,
height,
@@ -527,7 +533,7 @@ fn push_cohort_states(
addr_cohorts: &mut AddrCohorts,
height: Height,
height_price: Cents,
) {
) -> Cents {
// Phase 1: push + unrealized (no reset yet, states still needed for aggregation)
rayon::join(
|| {
@@ -545,7 +551,7 @@ fn push_cohort_states(
);
// Phase 2: aggregate age_range states → push to overlapping cohorts
utxo_cohorts.push_overlapping(height_price);
let all_capitalized_price = utxo_cohorts.push_overlapping(height_price);
// Phase 3: reset per-block values
utxo_cohorts
@@ -554,4 +560,6 @@ fn push_cohort_states(
addr_cohorts
.iter_separate_mut()
.for_each(|v| v.reset_single_iteration_values());
all_capitalized_price
}
@@ -206,7 +206,7 @@ impl RealizedFull {
}
#[inline(always)]
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) {
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) -> Cents {
self.cap_raw.push(accum.cap_raw);
self.capitalized.cap_raw.push(accum.capitalized_cap_raw);
@@ -221,6 +221,8 @@ impl RealizedFull {
self.capitalized.price.cents.height.push(capitalized_price);
self.peak_regret.value.block.cents.push(accum.peak_regret());
capitalized_price
}
pub(crate) fn compute_rest_part1(
@@ -1,5 +1,6 @@
use std::ops::{Add, AddAssign, SubAssign};
use brk_cohort::EntryPrice;
use brk_types::{Cents, SupplyState, Timestamp};
use serde::Serialize;
@@ -8,6 +9,8 @@ pub struct BlockState {
#[serde(flatten)]
pub supply: SupplyState,
#[serde(skip)]
pub entry: EntryPrice,
#[serde(skip)]
pub price: Cents,
#[serde(skip)]
pub timestamp: Timestamp,
+41 -5
View File
@@ -1,6 +1,6 @@
use std::path::{Path, PathBuf};
use brk_cohort::{ByAddrType, Filter};
use brk_cohort::{ByAddrType, EntryPrice, Filter};
use brk_error::Result;
use brk_indexer::Indexer;
use brk_traversable::Traversable;
@@ -436,13 +436,34 @@ impl Vecs {
let end = usize::from(recovered_height);
debug!("building supply_state vec for {} heights", recovered_height);
let supply_state_data: Vec<_> = self.supply_state.collect_range_at(0, end);
let capitalized_price_data: Vec<_> = self
.utxo_cohorts
.all
.metrics
.realized
.capitalized
.price
.cents
.height
.collect_range_at(0, end);
let mut entry_anchor = Cents::ZERO;
chain_state = supply_state_data
.into_iter()
.enumerate()
.map(|(h, supply)| BlockState {
supply,
price: self.caches.prices[h],
timestamp: self.caches.timestamps[h],
.map(|(h, supply)| {
let price = self.caches.prices[h];
let entry = EntryPrice::from_is_discount(
entry_anchor == Cents::ZERO || price <= entry_anchor,
);
entry_anchor = capitalized_price_data[h];
BlockState {
supply,
entry,
price,
timestamp: self.caches.timestamps[h],
}
})
.collect();
debug!("chain_state rebuilt");
@@ -474,6 +495,20 @@ impl Vecs {
let prices = std::mem::take(&mut self.caches.prices);
let timestamps = std::mem::take(&mut self.caches.timestamps);
let price_range_max = std::mem::take(&mut self.caches.price_range_max);
let entry_anchor = starting_height
.decremented()
.and_then(|height| {
self.utxo_cohorts
.all
.metrics
.realized
.capitalized
.price
.cents
.height
.collect_one(height)
})
.unwrap_or(Cents::ZERO);
process_blocks(
self,
@@ -486,6 +521,7 @@ impl Vecs {
last_height,
&mut chain_state,
&mut tx_index_to_height,
entry_anchor,
&prices,
&timestamps,
&price_range_max,
+516 -11
View File
@@ -2506,6 +2506,22 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, acc) {
* @property {AdjustedRatioValuePattern} sopr
*/
/**
* @typedef {Object} CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2
* @property {CentsDeltaToUsdPattern} cap
* @property {PricePattern} capitalized
* @property {BlockCumulativeSumPattern} grossPnl
* @property {BlockCumulativeNegativeSumPattern} loss
* @property {SeriesPattern1<StoredF32>} mvrv
* @property {BlockChangeCumulativeDeltaSumPattern} netPnl
* @property {BlockCumulativeSumPattern} peakRegret
* @property {BpsCentsPercentilesRatioSatsSmaStdUsdPattern} price
* @property {BlockCumulativeSumPattern} profit
* @property {_1m1w1y24hPattern<StoredF64>} profitToLossRatio
* @property {_1m1w1y24hPattern8} sellSideRiskRatio
* @property {RatioValuePattern2} sopr
*/
/**
* @typedef {Object} EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2
* @property {_1m1w1y24hBpsPercentRatioPattern} empty
@@ -3017,6 +3033,17 @@ function create_1m1w1y24hBpsPercentRatioPattern(client, acc) {
* @property {_1m1w1y24hBpsPercentRatioPattern} spendableOutputToReusedAddrShare
*/
/**
* @typedef {Object} ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2
* @property {CoindaysCoinyearsDormancyTransferPattern} activity
* @property {InMaxMinPerSupplyPattern} costBasis
* @property {InPattern} investedCapital
* @property {SpendingSpentUnspentPattern} outputs
* @property {CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2} realized
* @property {DeltaDominanceHalfInTotalPattern2} supply
* @property {CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2} unrealized
*/
/**
* @typedef {Object} CapLossMvrvNetPriceProfitSoprPattern
* @property {CentsDeltaUsdPattern} cap
@@ -5046,6 +5073,25 @@ function createPriceRatioPattern(client, acc, disc) {
};
}
/**
* @typedef {Object} RatioValuePattern2
* @property {_1m1w1y24hPattern<StoredF64>} ratio
* @property {AverageBlockCumulativeSumPattern<Cents>} valueDestroyed
*/
/**
* Create a RatioValuePattern2 pattern node
* @param {BrkClient} client
* @param {string} acc - Accumulated series name
* @returns {RatioValuePattern2}
*/
function createRatioValuePattern2(client, acc) {
return {
ratio: create_1m1w1y24hPattern(client, _m(acc, 'sopr')),
valueDestroyed: createAverageBlockCumulativeSumPattern(client, _m(acc, 'value_destroyed')),
};
}
/**
* @typedef {Object} RatioValuePattern
* @property {_24hPattern} ratio
@@ -6787,6 +6833,7 @@ function createTransferPattern(client, acc) {
* @property {SeriesTree_Cohorts_Utxo_OverAge} overAge
* @property {SeriesTree_Cohorts_Utxo_Epoch} epoch
* @property {SeriesTree_Cohorts_Utxo_Class} class
* @property {SeriesTree_Cohorts_Utxo_Entry} entry
* @property {SeriesTree_Cohorts_Utxo_OverAmount} overAmount
* @property {SeriesTree_Cohorts_Utxo_AmountRange} amountRange
* @property {SeriesTree_Cohorts_Utxo_UnderAmount} underAmount
@@ -7144,7 +7191,7 @@ function createTransferPattern(client, acc) {
* @property {SeriesTree_Cohorts_Utxo_Lth_Realized_Price} price
* @property {SeriesPattern1<StoredF32>} mvrv
* @property {BlockChangeCumulativeDeltaSumPattern} netPnl
* @property {SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr} sopr
* @property {RatioValuePattern2} sopr
* @property {BlockCumulativeSumPattern} grossPnl
* @property {_1m1w1y24hPattern8} sellSideRiskRatio
* @property {BlockCumulativeSumPattern} peakRegret
@@ -7248,12 +7295,6 @@ function createTransferPattern(client, acc) {
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr
* @property {AverageBlockCumulativeSumPattern<Cents>} valueDestroyed
* @property {_1m1w1y24hPattern<StoredF64>} ratio
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_AgeRange
* @property {ActivityOutputsRealizedSupplyUnrealizedPattern} under1h
@@ -7354,6 +7395,258 @@ function createTransferPattern(client, acc) {
* @property {ActivityOutputsRealizedSupplyUnrealizedPattern} _2026
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount} discount
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium} premium
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount
* @property {DeltaDominanceHalfInTotalPattern2} supply
* @property {SpendingSpentUnspentPattern} outputs
* @property {CoindaysCoinyearsDormancyTransferPattern} activity
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized} realized
* @property {InMaxMinPerSupplyPattern} costBasis
* @property {CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2} unrealized
* @property {InPattern} investedCapital
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized
* @property {CentsDeltaToUsdPattern} cap
* @property {BlockCumulativeSumPattern} profit
* @property {BlockCumulativeNegativeSumPattern} loss
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price} price
* @property {SeriesPattern1<StoredF32>} mvrv
* @property {BlockChangeCumulativeDeltaSumPattern} netPnl
* @property {RatioValuePattern2} sopr
* @property {BlockCumulativeSumPattern} grossPnl
* @property {_1m1w1y24hPattern8} sellSideRiskRatio
* @property {BlockCumulativeSumPattern} peakRegret
* @property {PricePattern} capitalized
* @property {_1m1w1y24hPattern<StoredF64>} profitToLossRatio
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price
* @property {SeriesPattern1<Dollars>} usd
* @property {SeriesPattern1<Cents>} cents
* @property {SeriesPattern1<SatsFract>} sats
* @property {SeriesPattern1<BasisPoints32>} bps
* @property {SeriesPattern1<StoredF32>} ratio
* @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles
* @property {_1m1w1y2y4yAllPattern} sma
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev} stdDev
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All} all
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y} _4y
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y} _2y
* @property {SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y} _1y
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium
* @property {DeltaDominanceHalfInTotalPattern2} supply
* @property {SpendingSpentUnspentPattern} outputs
* @property {CoindaysCoinyearsDormancyTransferPattern} activity
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized} realized
* @property {InMaxMinPerSupplyPattern} costBasis
* @property {CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2} unrealized
* @property {InPattern} investedCapital
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized
* @property {CentsDeltaToUsdPattern} cap
* @property {BlockCumulativeSumPattern} profit
* @property {BlockCumulativeNegativeSumPattern} loss
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price} price
* @property {SeriesPattern1<StoredF32>} mvrv
* @property {BlockChangeCumulativeDeltaSumPattern} netPnl
* @property {RatioValuePattern2} sopr
* @property {BlockCumulativeSumPattern} grossPnl
* @property {_1m1w1y24hPattern8} sellSideRiskRatio
* @property {BlockCumulativeSumPattern} peakRegret
* @property {PricePattern} capitalized
* @property {_1m1w1y24hPattern<StoredF64>} profitToLossRatio
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price
* @property {SeriesPattern1<Dollars>} usd
* @property {SeriesPattern1<Cents>} cents
* @property {SeriesPattern1<SatsFract>} sats
* @property {SeriesPattern1<BasisPoints32>} bps
* @property {SeriesPattern1<StoredF32>} ratio
* @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles
* @property {_1m1w1y2y4yAllPattern} sma
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev} stdDev
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All} all
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y} _4y
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y} _2y
* @property {SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y} _1y
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y
* @property {SeriesPattern1<StoredF32>} sd
* @property {SeriesPattern1<StoredF32>} zscore
* @property {CentsSatsUsdPattern} _0sd
* @property {PriceRatioPattern} p05sd
* @property {PriceRatioPattern} p1sd
* @property {PriceRatioPattern} p15sd
* @property {PriceRatioPattern} p2sd
* @property {PriceRatioPattern} p25sd
* @property {PriceRatioPattern} p3sd
* @property {PriceRatioPattern} m05sd
* @property {PriceRatioPattern} m1sd
* @property {PriceRatioPattern} m15sd
* @property {PriceRatioPattern} m2sd
* @property {PriceRatioPattern} m25sd
* @property {PriceRatioPattern} m3sd
*/
/**
* @typedef {Object} SeriesTree_Cohorts_Utxo_OverAmount
* @property {ActivityOutputsRealizedSupplyUnrealizedPattern2} _1sat
@@ -7919,6 +8212,19 @@ class BrkClient extends BrkClientBase {
}
});
ENTRY_NAMES = /** @type {const} */ ({
"discount": {
"id": "veteran",
"short": "Veteran",
"long": "Veteran Coins"
},
"premium": {
"id": "rookie",
"short": "Rookie",
"long": "Rookie Coins"
}
});
SPENDABLE_TYPE_NAMES = /** @type {const} */ ({
"p2pk65": {
"id": "p2pk65",
@@ -10239,10 +10545,7 @@ class BrkClient extends BrkClientBase {
},
mvrv: createSeriesPattern1(this, 'lth_mvrv'),
netPnl: createBlockChangeCumulativeDeltaSumPattern(this, 'lth_net'),
sopr: {
valueDestroyed: createAverageBlockCumulativeSumPattern(this, 'lth_value_destroyed'),
ratio: create_1m1w1y24hPattern(this, 'lth_sopr'),
},
sopr: createRatioValuePattern2(this, 'lth'),
grossPnl: createBlockCumulativeSumPattern(this, 'lth_realized_gross_pnl'),
sellSideRiskRatio: create_1m1w1y24hPattern8(this, 'lth_sell_side_risk_ratio'),
peakRegret: createBlockCumulativeSumPattern(this, 'lth_realized_peak_regret'),
@@ -10343,6 +10646,208 @@ class BrkClient extends BrkClientBase {
_2025: createActivityOutputsRealizedSupplyUnrealizedPattern(this, 'class_2025'),
_2026: createActivityOutputsRealizedSupplyUnrealizedPattern(this, 'class_2026'),
},
entry: {
discount: {
supply: createDeltaDominanceHalfInTotalPattern2(this, 'veteran_supply'),
outputs: createSpendingSpentUnspentPattern(this, 'veteran'),
activity: createCoindaysCoinyearsDormancyTransferPattern(this, 'veteran'),
realized: {
cap: createCentsDeltaToUsdPattern(this, 'veteran_realized_cap'),
profit: createBlockCumulativeSumPattern(this, 'veteran_realized_profit'),
loss: createBlockCumulativeNegativeSumPattern(this, 'veteran_realized_loss'),
price: {
usd: createSeriesPattern1(this, 'veteran_realized_price'),
cents: createSeriesPattern1(this, 'veteran_realized_price_cents'),
sats: createSeriesPattern1(this, 'veteran_realized_price_sats'),
bps: createSeriesPattern1(this, 'veteran_realized_price_ratio_bps'),
ratio: createSeriesPattern1(this, 'veteran_realized_price_ratio'),
percentiles: createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'veteran_realized_price'),
sma: create_1m1w1y2y4yAllPattern(this, 'veteran_realized_price_ratio_sma'),
stdDev: {
all: {
sd: createSeriesPattern1(this, 'veteran_realized_price_ratio_sd'),
zscore: createSeriesPattern1(this, 'veteran_realized_price_ratio_zscore'),
_0sd: createCentsSatsUsdPattern(this, 'veteran_realized_price_0sd'),
p05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p0_5sd'),
p1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1sd'),
p15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1_5sd'),
p2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2sd'),
p25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2_5sd'),
p3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p3sd'),
m05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm0_5sd'),
m1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1sd'),
m15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1_5sd'),
m2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2sd'),
m25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2_5sd'),
m3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm3sd'),
},
_4y: {
sd: createSeriesPattern1(this, 'veteran_realized_price_ratio_sd_4y'),
zscore: createSeriesPattern1(this, 'veteran_realized_price_ratio_zscore_4y'),
_0sd: createCentsSatsUsdPattern(this, 'veteran_realized_price_0sd_4y'),
p05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p0_5sd_4y'),
p1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1sd_4y'),
p15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1_5sd_4y'),
p2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2sd_4y'),
p25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2_5sd_4y'),
p3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p3sd_4y'),
m05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm0_5sd_4y'),
m1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1sd_4y'),
m15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1_5sd_4y'),
m2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2sd_4y'),
m25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2_5sd_4y'),
m3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm3sd_4y'),
},
_2y: {
sd: createSeriesPattern1(this, 'veteran_realized_price_ratio_sd_2y'),
zscore: createSeriesPattern1(this, 'veteran_realized_price_ratio_zscore_2y'),
_0sd: createCentsSatsUsdPattern(this, 'veteran_realized_price_0sd_2y'),
p05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p0_5sd_2y'),
p1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1sd_2y'),
p15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1_5sd_2y'),
p2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2sd_2y'),
p25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2_5sd_2y'),
p3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p3sd_2y'),
m05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm0_5sd_2y'),
m1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1sd_2y'),
m15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1_5sd_2y'),
m2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2sd_2y'),
m25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2_5sd_2y'),
m3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm3sd_2y'),
},
_1y: {
sd: createSeriesPattern1(this, 'veteran_realized_price_ratio_sd_1y'),
zscore: createSeriesPattern1(this, 'veteran_realized_price_ratio_zscore_1y'),
_0sd: createCentsSatsUsdPattern(this, 'veteran_realized_price_0sd_1y'),
p05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p0_5sd_1y'),
p1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1sd_1y'),
p15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p1_5sd_1y'),
p2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2sd_1y'),
p25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p2_5sd_1y'),
p3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'p3sd_1y'),
m05sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm0_5sd_1y'),
m1sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1sd_1y'),
m15sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm1_5sd_1y'),
m2sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2sd_1y'),
m25sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm2_5sd_1y'),
m3sd: createPriceRatioPattern(this, 'veteran_realized_price', 'm3sd_1y'),
},
},
},
mvrv: createSeriesPattern1(this, 'veteran_mvrv'),
netPnl: createBlockChangeCumulativeDeltaSumPattern(this, 'veteran_net'),
sopr: createRatioValuePattern2(this, 'veteran'),
grossPnl: createBlockCumulativeSumPattern(this, 'veteran_realized_gross_pnl'),
sellSideRiskRatio: create_1m1w1y24hPattern8(this, 'veteran_sell_side_risk_ratio'),
peakRegret: createBlockCumulativeSumPattern(this, 'veteran_realized_peak_regret'),
capitalized: createPricePattern(this, 'veteran_capitalized_price'),
profitToLossRatio: create_1m1w1y24hPattern(this, 'veteran_realized_profit_to_loss_ratio'),
},
costBasis: createInMaxMinPerSupplyPattern(this, 'veteran'),
unrealized: createCapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(this, 'veteran'),
investedCapital: createInPattern(this, 'veteran_invested_capital_in'),
},
premium: {
supply: createDeltaDominanceHalfInTotalPattern2(this, 'rookie_supply'),
outputs: createSpendingSpentUnspentPattern(this, 'rookie'),
activity: createCoindaysCoinyearsDormancyTransferPattern(this, 'rookie'),
realized: {
cap: createCentsDeltaToUsdPattern(this, 'rookie_realized_cap'),
profit: createBlockCumulativeSumPattern(this, 'rookie_realized_profit'),
loss: createBlockCumulativeNegativeSumPattern(this, 'rookie_realized_loss'),
price: {
usd: createSeriesPattern1(this, 'rookie_realized_price'),
cents: createSeriesPattern1(this, 'rookie_realized_price_cents'),
sats: createSeriesPattern1(this, 'rookie_realized_price_sats'),
bps: createSeriesPattern1(this, 'rookie_realized_price_ratio_bps'),
ratio: createSeriesPattern1(this, 'rookie_realized_price_ratio'),
percentiles: createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'rookie_realized_price'),
sma: create_1m1w1y2y4yAllPattern(this, 'rookie_realized_price_ratio_sma'),
stdDev: {
all: {
sd: createSeriesPattern1(this, 'rookie_realized_price_ratio_sd'),
zscore: createSeriesPattern1(this, 'rookie_realized_price_ratio_zscore'),
_0sd: createCentsSatsUsdPattern(this, 'rookie_realized_price_0sd'),
p05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p0_5sd'),
p1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1sd'),
p15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1_5sd'),
p2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2sd'),
p25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2_5sd'),
p3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p3sd'),
m05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm0_5sd'),
m1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1sd'),
m15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1_5sd'),
m2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2sd'),
m25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2_5sd'),
m3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm3sd'),
},
_4y: {
sd: createSeriesPattern1(this, 'rookie_realized_price_ratio_sd_4y'),
zscore: createSeriesPattern1(this, 'rookie_realized_price_ratio_zscore_4y'),
_0sd: createCentsSatsUsdPattern(this, 'rookie_realized_price_0sd_4y'),
p05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p0_5sd_4y'),
p1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1sd_4y'),
p15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1_5sd_4y'),
p2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2sd_4y'),
p25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2_5sd_4y'),
p3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p3sd_4y'),
m05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm0_5sd_4y'),
m1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1sd_4y'),
m15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1_5sd_4y'),
m2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2sd_4y'),
m25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2_5sd_4y'),
m3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm3sd_4y'),
},
_2y: {
sd: createSeriesPattern1(this, 'rookie_realized_price_ratio_sd_2y'),
zscore: createSeriesPattern1(this, 'rookie_realized_price_ratio_zscore_2y'),
_0sd: createCentsSatsUsdPattern(this, 'rookie_realized_price_0sd_2y'),
p05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p0_5sd_2y'),
p1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1sd_2y'),
p15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1_5sd_2y'),
p2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2sd_2y'),
p25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2_5sd_2y'),
p3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p3sd_2y'),
m05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm0_5sd_2y'),
m1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1sd_2y'),
m15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1_5sd_2y'),
m2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2sd_2y'),
m25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2_5sd_2y'),
m3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm3sd_2y'),
},
_1y: {
sd: createSeriesPattern1(this, 'rookie_realized_price_ratio_sd_1y'),
zscore: createSeriesPattern1(this, 'rookie_realized_price_ratio_zscore_1y'),
_0sd: createCentsSatsUsdPattern(this, 'rookie_realized_price_0sd_1y'),
p05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p0_5sd_1y'),
p1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1sd_1y'),
p15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p1_5sd_1y'),
p2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2sd_1y'),
p25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p2_5sd_1y'),
p3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'p3sd_1y'),
m05sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm0_5sd_1y'),
m1sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1sd_1y'),
m15sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm1_5sd_1y'),
m2sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2sd_1y'),
m25sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm2_5sd_1y'),
m3sd: createPriceRatioPattern(this, 'rookie_realized_price', 'm3sd_1y'),
},
},
},
mvrv: createSeriesPattern1(this, 'rookie_mvrv'),
netPnl: createBlockChangeCumulativeDeltaSumPattern(this, 'rookie_net'),
sopr: createRatioValuePattern2(this, 'rookie'),
grossPnl: createBlockCumulativeSumPattern(this, 'rookie_realized_gross_pnl'),
sellSideRiskRatio: create_1m1w1y24hPattern8(this, 'rookie_sell_side_risk_ratio'),
peakRegret: createBlockCumulativeSumPattern(this, 'rookie_realized_peak_regret'),
capitalized: createPricePattern(this, 'rookie_capitalized_price'),
profitToLossRatio: create_1m1w1y24hPattern(this, 'rookie_realized_profit_to_loss_ratio'),
},
costBasis: createInMaxMinPerSupplyPattern(this, 'rookie'),
unrealized: createCapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(this, 'rookie'),
investedCapital: createInPattern(this, 'rookie_invested_capital_in'),
},
},
overAmount: {
_1sat: createActivityOutputsRealizedSupplyUnrealizedPattern2(this, 'utxos_over_1sat'),
_10sats: createActivityOutputsRealizedSupplyUnrealizedPattern2(this, 'utxos_over_10sats'),
+300 -8
View File
@@ -2954,6 +2954,10 @@ class CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern:
"""Pattern struct for repeated tree structure."""
pass
class CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2:
"""Pattern struct for repeated tree structure."""
pass
class EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2:
"""Pattern struct for repeated tree structure."""
@@ -3181,6 +3185,10 @@ class ActiveInputOutputSpendablePattern:
"""Pattern struct for repeated tree structure."""
pass
class ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2:
"""Pattern struct for repeated tree structure."""
pass
class CapLossMvrvNetPriceProfitSoprPattern:
"""Pattern struct for repeated tree structure."""
@@ -4056,6 +4064,14 @@ class PriceRatioPattern:
self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, disc))
self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, f'ratio_{disc}'))
class RatioValuePattern2:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClient, acc: str):
"""Create pattern node with accumulated series name."""
self.ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, _m(acc, 'sopr'))
self.value_destroyed: AverageBlockCumulativeSumPattern[Cents] = AverageBlockCumulativeSumPattern(client, _m(acc, 'value_destroyed'))
class RatioValuePattern:
"""Pattern struct for repeated tree structure."""
@@ -6299,13 +6315,6 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized_Price:
self.sma: _1m1w1y2y4yAllPattern = _1m1w1y2y4yAllPattern(client, 'lth_realized_price_ratio_sma')
self.std_dev: SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev = SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev(client)
class SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr:
"""Series tree node."""
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')
class SeriesTree_Cohorts_Utxo_Lth_Realized:
"""Series tree node."""
@@ -6316,7 +6325,7 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized:
self.price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price = SeriesTree_Cohorts_Utxo_Lth_Realized_Price(client)
self.mvrv: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'lth_mvrv')
self.net_pnl: BlockChangeCumulativeDeltaSumPattern = BlockChangeCumulativeDeltaSumPattern(client, 'lth_net')
self.sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr = SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr(client)
self.sopr: RatioValuePattern2 = RatioValuePattern2(client, 'lth')
self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'lth_realized_gross_pnl')
self.sell_side_risk_ratio: _1m1w1y24hPattern8 = _1m1w1y24hPattern8(client, 'lth_sell_side_risk_ratio')
self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'lth_realized_peak_regret')
@@ -6440,6 +6449,275 @@ class SeriesTree_Cohorts_Utxo_Class:
self._2025: ActivityOutputsRealizedSupplyUnrealizedPattern = ActivityOutputsRealizedSupplyUnrealizedPattern(client, 'class_2025')
self._2026: ActivityOutputsRealizedSupplyUnrealizedPattern = ActivityOutputsRealizedSupplyUnrealizedPattern(client, 'class_2026')
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_sd')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_zscore')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'veteran_realized_price_0sd')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p0_5sd')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1sd')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1_5sd')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2sd')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2_5sd')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p3sd')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm0_5sd')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1sd')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1_5sd')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2sd')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2_5sd')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm3sd')
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_sd_4y')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_zscore_4y')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'veteran_realized_price_0sd_4y')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p0_5sd_4y')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1sd_4y')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1_5sd_4y')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2sd_4y')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2_5sd_4y')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p3sd_4y')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm0_5sd_4y')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1sd_4y')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1_5sd_4y')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2sd_4y')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2_5sd_4y')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm3sd_4y')
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_sd_2y')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_zscore_2y')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'veteran_realized_price_0sd_2y')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p0_5sd_2y')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1sd_2y')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1_5sd_2y')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2sd_2y')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2_5sd_2y')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p3sd_2y')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm0_5sd_2y')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1sd_2y')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1_5sd_2y')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2sd_2y')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2_5sd_2y')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm3sd_2y')
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_sd_1y')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio_zscore_1y')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'veteran_realized_price_0sd_1y')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p0_5sd_1y')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1sd_1y')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p1_5sd_1y')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2sd_1y')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p2_5sd_1y')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'p3sd_1y')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm0_5sd_1y')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1sd_1y')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm1_5sd_1y')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2sd_1y')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm2_5sd_1y')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'veteran_realized_price', 'm3sd_1y')
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All(client)
self._4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y(client)
self._2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y(client)
self._1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y(client)
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.usd: SeriesPattern1[Dollars] = SeriesPattern1(client, 'veteran_realized_price')
self.cents: SeriesPattern1[Cents] = SeriesPattern1(client, 'veteran_realized_price_cents')
self.sats: SeriesPattern1[SatsFract] = SeriesPattern1(client, 'veteran_realized_price_sats')
self.bps: SeriesPattern1[BasisPoints32] = SeriesPattern1(client, 'veteran_realized_price_ratio_bps')
self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_realized_price_ratio')
self.percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'veteran_realized_price')
self.sma: _1m1w1y2y4yAllPattern = _1m1w1y2y4yAllPattern(client, 'veteran_realized_price_ratio_sma')
self.std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev(client)
class SeriesTree_Cohorts_Utxo_Entry_Discount_Realized:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.cap: CentsDeltaToUsdPattern = CentsDeltaToUsdPattern(client, 'veteran_realized_cap')
self.profit: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'veteran_realized_profit')
self.loss: BlockCumulativeNegativeSumPattern = BlockCumulativeNegativeSumPattern(client, 'veteran_realized_loss')
self.price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price(client)
self.mvrv: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'veteran_mvrv')
self.net_pnl: BlockChangeCumulativeDeltaSumPattern = BlockChangeCumulativeDeltaSumPattern(client, 'veteran_net')
self.sopr: RatioValuePattern2 = RatioValuePattern2(client, 'veteran')
self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'veteran_realized_gross_pnl')
self.sell_side_risk_ratio: _1m1w1y24hPattern8 = _1m1w1y24hPattern8(client, 'veteran_sell_side_risk_ratio')
self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'veteran_realized_peak_regret')
self.capitalized: PricePattern = PricePattern(client, 'veteran_capitalized_price')
self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'veteran_realized_profit_to_loss_ratio')
class SeriesTree_Cohorts_Utxo_Entry_Discount:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.supply: DeltaDominanceHalfInTotalPattern2 = DeltaDominanceHalfInTotalPattern2(client, 'veteran_supply')
self.outputs: SpendingSpentUnspentPattern = SpendingSpentUnspentPattern(client, 'veteran')
self.activity: CoindaysCoinyearsDormancyTransferPattern = CoindaysCoinyearsDormancyTransferPattern(client, 'veteran')
self.realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized = SeriesTree_Cohorts_Utxo_Entry_Discount_Realized(client)
self.cost_basis: InMaxMinPerSupplyPattern = InMaxMinPerSupplyPattern(client, 'veteran')
self.unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 = CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(client, 'veteran')
self.invested_capital: InPattern = InPattern(client, 'veteran_invested_capital_in')
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_sd')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_zscore')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'rookie_realized_price_0sd')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p0_5sd')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1sd')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1_5sd')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2sd')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2_5sd')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p3sd')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm0_5sd')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1sd')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1_5sd')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2sd')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2_5sd')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm3sd')
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_sd_4y')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_zscore_4y')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'rookie_realized_price_0sd_4y')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p0_5sd_4y')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1sd_4y')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1_5sd_4y')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2sd_4y')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2_5sd_4y')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p3sd_4y')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm0_5sd_4y')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1sd_4y')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1_5sd_4y')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2sd_4y')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2_5sd_4y')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm3sd_4y')
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_sd_2y')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_zscore_2y')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'rookie_realized_price_0sd_2y')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p0_5sd_2y')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1sd_2y')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1_5sd_2y')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2sd_2y')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2_5sd_2y')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p3sd_2y')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm0_5sd_2y')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1sd_2y')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1_5sd_2y')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2sd_2y')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2_5sd_2y')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm3sd_2y')
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.sd: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_sd_1y')
self.zscore: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio_zscore_1y')
self._0sd: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'rookie_realized_price_0sd_1y')
self.p0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p0_5sd_1y')
self.p1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1sd_1y')
self.p1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p1_5sd_1y')
self.p2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2sd_1y')
self.p2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p2_5sd_1y')
self.p3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'p3sd_1y')
self.m0_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm0_5sd_1y')
self.m1sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1sd_1y')
self.m1_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm1_5sd_1y')
self.m2sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2sd_1y')
self.m2_5sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm2_5sd_1y')
self.m3sd: PriceRatioPattern = PriceRatioPattern(client, 'rookie_realized_price', 'm3sd_1y')
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All(client)
self._4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y(client)
self._2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y(client)
self._1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y(client)
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.usd: SeriesPattern1[Dollars] = SeriesPattern1(client, 'rookie_realized_price')
self.cents: SeriesPattern1[Cents] = SeriesPattern1(client, 'rookie_realized_price_cents')
self.sats: SeriesPattern1[SatsFract] = SeriesPattern1(client, 'rookie_realized_price_sats')
self.bps: SeriesPattern1[BasisPoints32] = SeriesPattern1(client, 'rookie_realized_price_ratio_bps')
self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_realized_price_ratio')
self.percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'rookie_realized_price')
self.sma: _1m1w1y2y4yAllPattern = _1m1w1y2y4yAllPattern(client, 'rookie_realized_price_ratio_sma')
self.std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev(client)
class SeriesTree_Cohorts_Utxo_Entry_Premium_Realized:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.cap: CentsDeltaToUsdPattern = CentsDeltaToUsdPattern(client, 'rookie_realized_cap')
self.profit: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'rookie_realized_profit')
self.loss: BlockCumulativeNegativeSumPattern = BlockCumulativeNegativeSumPattern(client, 'rookie_realized_loss')
self.price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price(client)
self.mvrv: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'rookie_mvrv')
self.net_pnl: BlockChangeCumulativeDeltaSumPattern = BlockChangeCumulativeDeltaSumPattern(client, 'rookie_net')
self.sopr: RatioValuePattern2 = RatioValuePattern2(client, 'rookie')
self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'rookie_realized_gross_pnl')
self.sell_side_risk_ratio: _1m1w1y24hPattern8 = _1m1w1y24hPattern8(client, 'rookie_sell_side_risk_ratio')
self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'rookie_realized_peak_regret')
self.capitalized: PricePattern = PricePattern(client, 'rookie_capitalized_price')
self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'rookie_realized_profit_to_loss_ratio')
class SeriesTree_Cohorts_Utxo_Entry_Premium:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.supply: DeltaDominanceHalfInTotalPattern2 = DeltaDominanceHalfInTotalPattern2(client, 'rookie_supply')
self.outputs: SpendingSpentUnspentPattern = SpendingSpentUnspentPattern(client, 'rookie')
self.activity: CoindaysCoinyearsDormancyTransferPattern = CoindaysCoinyearsDormancyTransferPattern(client, 'rookie')
self.realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized = SeriesTree_Cohorts_Utxo_Entry_Premium_Realized(client)
self.cost_basis: InMaxMinPerSupplyPattern = InMaxMinPerSupplyPattern(client, 'rookie')
self.unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 = CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(client, 'rookie')
self.invested_capital: InPattern = InPattern(client, 'rookie_invested_capital_in')
class SeriesTree_Cohorts_Utxo_Entry:
"""Series tree node."""
def __init__(self, client: BrkClient, base_path: str = ''):
self.discount: SeriesTree_Cohorts_Utxo_Entry_Discount = SeriesTree_Cohorts_Utxo_Entry_Discount(client)
self.premium: SeriesTree_Cohorts_Utxo_Entry_Premium = SeriesTree_Cohorts_Utxo_Entry_Premium(client)
class SeriesTree_Cohorts_Utxo_OverAmount:
"""Series tree node."""
@@ -6621,6 +6899,7 @@ class SeriesTree_Cohorts_Utxo:
self.over_age: SeriesTree_Cohorts_Utxo_OverAge = SeriesTree_Cohorts_Utxo_OverAge(client)
self.epoch: SeriesTree_Cohorts_Utxo_Epoch = SeriesTree_Cohorts_Utxo_Epoch(client)
self.class_: SeriesTree_Cohorts_Utxo_Class = SeriesTree_Cohorts_Utxo_Class(client)
self.entry: SeriesTree_Cohorts_Utxo_Entry = SeriesTree_Cohorts_Utxo_Entry(client)
self.over_amount: SeriesTree_Cohorts_Utxo_OverAmount = SeriesTree_Cohorts_Utxo_OverAmount(client)
self.amount_range: SeriesTree_Cohorts_Utxo_AmountRange = SeriesTree_Cohorts_Utxo_AmountRange(client)
self.under_amount: SeriesTree_Cohorts_Utxo_UnderAmount = SeriesTree_Cohorts_Utxo_UnderAmount(client)
@@ -7064,6 +7343,19 @@ class BrkClient(BrkClientBase):
}
}
ENTRY_NAMES = {
"discount": {
"id": "veteran",
"short": "Veteran",
"long": "Veteran Coins"
},
"premium": {
"id": "rookie",
"short": "Rookie",
"long": "Rookie Coins"
}
}
SPENDABLE_TYPE_NAMES = {
"p2pk65": {
"id": "p2pk65",
+1
View File
@@ -0,0 +1 @@
../../modules
-14
View File
@@ -1,14 +0,0 @@
LICENSE
**/*.*.*/*.json
*webcomponent*
cli*
extras/
*.cjs
dev.js
*.development*
*.iife.*
nano.*
worker.*
*.mts
*.cts
*.rs
@@ -1 +0,0 @@
generated
File diff suppressed because it is too large Load Diff
@@ -1,10 +0,0 @@
{
"compilerOptions": {
"checkJs": true,
"strict": true,
"target": "ESNext",
"module": "ESNext",
"skipLibCheck": true
},
"exclude": ["dist"]
}
@@ -1,44 +0,0 @@
{
"bugs": {
"url": "https://github.com/bitcoinresearchkit/brk/issues"
},
"scripts": {
"test": "node tests/basic.js && node tests/tree.js",
"test:basic": "node tests/basic.js",
"test:tree": "node tests/tree.js"
},
"description": "Bitcoin on-chain analytics client — thousands of metrics, block explorer, and address index",
"engines": {
"node": ">=18"
},
"exports": {
".": "./index.js"
},
"files": [
"index.js",
"generated"
],
"homepage": "https://github.com/bitcoinresearchkit/brk/tree/main/modules/brk-client",
"keywords": [
"brk",
"bitcoin",
"blockchain",
"research",
"on-chain",
"analytics",
"metrics",
"api",
"data",
"cryptocurrency"
],
"license": "MIT",
"main": "index.js",
"name": "brk-client",
"repository": {
"directory": "modules/brk-client",
"type": "git",
"url": "git+https://github.com/bitcoinresearchkit/brk.git"
},
"type": "module",
"version": "0.3.1"
}
@@ -1,66 +0,0 @@
import { BrkClient } from "../index.js";
const client = new BrkClient("http://localhost:3110");
console.log("Testing idiomatic API...\n");
// Test getter access (property)
console.log("1. Getter access (.by.dateindex):");
const all = await client.series.prices.split.close.usd.by.day1;
console.log(` Got: ${all.data.length} items\n`);
// Test dynamic access (bracket notation)
console.log("2. Dynamic access (.by['dateindex']):");
const allDynamic = await client.series.prices.split.close.usd.by.day1;
console.log(` Got: ${allDynamic.data.length} items\n`);
// Test fetch all (explicit .fetch())
console.log("3. Explicit .fetch():");
const allExplicit = await client.series.prices.split.close.usd.by.day1.fetch();
console.log(` Got: ${allExplicit.data.length} items\n`);
// Test first(n)
console.log("4. First 5 items (.first(5)):");
const first5 = await client.series.prices.split.close.usd.by.day1.first(5);
console.log(
` Start: ${first5.start}, End: ${first5.end}, Got: ${first5.data.length} items\n`,
);
// Test last(n)
console.log("5. Last 5 items (.last(5)):");
const last5 = await client.series.prices.split.close.usd.by.day1.last(5);
console.log(
` Start: ${last5.start}, End: ${last5.end}, Got: ${last5.data.length} items\n`,
);
// Test slice(start, end)
console.log("6. Slice 10-20 (.slice(10, 20)):");
const sliced = await client.series.prices.split.close.usd.by.day1.slice(10, 20);
console.log(
` Start: ${sliced.start}, End: ${sliced.end}, Got: ${sliced.data.length} items\n`,
);
// Test get(index) - single item
console.log("7. Single item (.get(100)):");
const single = await client.series.prices.split.close.usd.by.day1.get(100);
console.log(
` Start: ${single.start}, End: ${single.end}, Got: ${single.data.length} item(s)\n`,
);
// Test skip(n).take(m) chaining
console.log("8. Skip and take (.skip(100).take(10)):");
const skipTake = await client.series.prices.split.close.usd.by.day1
.skip(100)
.take(10);
console.log(
` Start: ${skipTake.start}, End: ${skipTake.end}, Got: ${skipTake.data.length} items\n`,
);
// Test fetchCsv
console.log("9. Fetch as CSV (.last(3).fetchCsv()):");
const csv = await client.series.prices.split.close.usd.by.day1
.last(3)
.fetchCsv();
console.log(` CSV preview: ${csv.substring(0, 100)}...\n`);
console.log("All tests passed!");
@@ -1,132 +0,0 @@
/**
* Consistency test: verifies that all series sharing the same index have the same length.
* Useful for catching stale/inconsistent state after a reorg rollback.
*/
import { BrkClient } from "../index.js";
/**
* @typedef {import('../index.js').AnySeriesPattern} AnyMetricPattern
*/
/**
* @param {any} obj
* @returns {obj is AnyMetricPattern}
*/
function isMetricPattern(obj) {
return (
obj &&
typeof obj === "object" &&
typeof obj.indexes === "function" &&
obj.by &&
typeof obj.by === "object"
);
}
/**
* Recursively collect all metric patterns from the tree.
* @param {Record<string, any>} obj
* @param {string} path
* @returns {Array<{path: string, metric: AnyMetricPattern}>}
*/
function getAllMetrics(obj, path = "") {
/** @type {Array<{path: string, metric: AnyMetricPattern}>} */
const metrics = [];
for (const key of Object.keys(obj)) {
const attr = obj[key];
if (!attr || typeof attr !== "object") continue;
const currentPath = path ? `${path}.${key}` : key;
if (isMetricPattern(attr)) {
metrics.push({ path: currentPath, metric: attr });
}
if (typeof attr === "object" && !Array.isArray(attr)) {
metrics.push(...getAllMetrics(attr, currentPath));
}
}
return metrics;
}
async function testConsistency() {
const client = new BrkClient({
baseUrl: "http://localhost:3110",
timeout: 15000,
});
const metrics = getAllMetrics(client.series);
console.log(`\nFound ${metrics.length} metrics`);
/** @type {Map<string, Array<{path: string, total: number}>>} */
const byIndex = new Map();
for (const { path, metric } of metrics) {
const indexes = metric.indexes();
for (const idxName of indexes) {
const fullPath = `${path}.by.${idxName}`;
const endpoint = metric.by[idxName];
if (!endpoint) {
console.log(`SKIP: ${fullPath} (undefined endpoint)`);
continue;
}
try {
const result = await endpoint.last(0);
const total = result.end;
if (!byIndex.has(idxName)) {
byIndex.set(idxName, []);
}
/** @type {Array<{path: string, total: number}>} */ (byIndex.get(idxName)).push({ path: fullPath, total });
} catch (e) {
console.log(
`FAIL: ${fullPath} -> ${e instanceof Error ? e.message : e}`,
);
return;
}
}
}
let failed = false;
for (const [index, entries] of byIndex) {
const totals = new Set(entries.map((e) => e.total));
if (totals.size === 1) {
const [total] = totals;
console.log(`OK: ${index}${entries.length} series, all length ${total}`);
continue;
}
failed = true;
console.log(`\nMISMATCH: ${index}${entries.length} series with ${totals.size} different lengths:`);
/** @type {Map<number, string[]>} */
const grouped = new Map();
for (const { path, total } of entries) {
if (!grouped.has(total)) grouped.set(total, []);
/** @type {string[]} */ (grouped.get(total)).push(path);
}
for (const [total, paths] of [...grouped].sort((a, b) => b[0] - a[0])) {
console.log(` length ${total}: (${paths.length} series)`);
for (const p of paths) {
console.log(` ${p}`);
}
}
}
if (failed) {
console.log("\nFAILED: length mismatches detected");
throw new Error("length mismatches detected");
} else {
console.log("\nPASSED: all indexes consistent");
}
}
testConsistency();
@@ -1,248 +0,0 @@
/**
* Tests for MetricData helper methods and date conversion functions.
* Run: node tests/metric_data.js
*/
import { BrkClient } from "../index.js";
const client = new BrkClient("http://localhost:3110");
console.log("Testing MetricData helpers...\n");
// Fetch a date-based metric
console.log("1. Fetching price data (day1):");
const price = await client.series.prices.split.close.usd.by.day1.first(5);
console.log(` Start: ${price.start}, End: ${price.end}`);
// Test isDateBased
console.log("\n2. isDateBased:");
if (!price.isDateBased) throw new Error("day1 should be date-based");
console.log(` day1: ${price.isDateBased}`);
// Test indexes() - always returns numbers
console.log("\n3. indexes():");
const indexes = price.indexes();
console.log(` ${JSON.stringify(indexes)}`);
if (indexes.length !== 5) throw new Error("Expected 5 indexes");
if (indexes[0] !== price.start)
throw new Error("First index should equal start");
// Test dates() - DateMetricData method
console.log("\n4. dates():");
const dates = price.dates();
console.log(
` First: ${dates[0].toISOString()}, Last: ${dates[dates.length - 1].toISOString()}`,
);
if (dates.length !== 5) throw new Error("Expected 5 dates");
// DateIndex 0 = Jan 3, 2009 (genesis)
if (
dates[0].getFullYear() !== 2009 ||
dates[0].getMonth() !== 0 ||
dates[0].getDate() !== 3
) {
throw new Error(
`Expected genesis date (2009-01-03), got ${dates[0].toISOString()}`,
);
}
// Test keys() - always returns numbers (alias for indexes)
console.log("\n5. keys():");
const keys = price.keys();
if (keys.length !== 5) throw new Error("Expected 5 keys");
if (typeof keys[0] !== "number") throw new Error("Expected number keys");
console.log(` Length: ${keys.length}, First: ${keys[0]}`);
// Test entries() - returns [number, value] pairs
console.log("\n6. entries():");
const entries = price.entries();
if (typeof entries[0][0] !== "number")
throw new Error("Expected number entry key");
console.log(` First: [${entries[0][0]}, ${entries[0][1]}]`);
if (entries[0][1] !== price.data[0])
throw new Error("First entry value mismatch");
// Test dateEntries() - DateMetricData method, returns [Date, value] pairs
console.log("\n7. dateEntries():");
const dateEntries = price.dateEntries();
if (!(dateEntries[0][0] instanceof Date))
throw new Error("Expected Date entry key");
console.log(
` First: [${dateEntries[0][0].toISOString()}, ${dateEntries[0][1]}]`,
);
// Test toMap() - returns Map<number, value>
console.log("\n8. toMap():");
const map = price.toMap();
console.log(` Size: ${map.size}`);
if (map.size !== 5) throw new Error("Expected map size 5");
// Test toDateMap() - DateMetricData method
console.log("\n9. toDateMap():");
const dateMap = price.toDateMap();
console.log(` Size: ${dateMap.size}`);
if (dateMap.size !== 5) throw new Error("Expected date map size 5");
// Test Symbol.iterator (for...of) - yields [number, value]
console.log("\n10. for...of iteration:");
let count = 0;
for (const [key, _val] of price) {
if (count === 0 && typeof key !== "number")
throw new Error("Expected number keys in iteration");
count++;
}
console.log(` Iterated ${count} items`);
if (count !== 5) throw new Error("Expected 5 iterations");
// Test with non-date-based index (height)
console.log("\n11. Testing height-based metric:");
const heightMetric = await client.series.prices.spot.usd.by.height.last(3);
console.log(` Start: ${heightMetric.start}, End: ${heightMetric.end}`);
if (heightMetric.isDateBased)
throw new Error("height should not be date-based");
// Test keys() - always numbers
const heightKeys = heightMetric.keys();
console.log(` keys(): ${JSON.stringify(heightKeys)}`);
if (typeof heightKeys[0] !== "number")
throw new Error("Expected number keys for height");
// Test entries() - [number, value]
const heightEntries = heightMetric.entries();
console.log(
` entries()[0]: [${heightEntries[0][0]}, ${heightEntries[0][1]}]`,
);
if (heightEntries[0][0] !== heightMetric.start)
throw new Error("First entry index mismatch");
// Test toMap() - Map<number, value>
const heightMap = heightMetric.toMap();
if (heightMap.size !== 3) throw new Error("Expected map size 3");
if (heightMap.get(heightMetric.start) !== heightMetric.data[0])
throw new Error("First value mismatch");
// Test for...of on non-date metric
console.log("\n12. for...of iteration (height):");
let heightCount = 0;
for (const [key, _val] of heightMetric) {
if (heightCount === 0 && typeof key !== "number")
throw new Error("Expected number keys for height iteration");
heightCount++;
}
console.log(` Iterated ${heightCount} items`);
// Test different date indexes
console.log("\n13. Testing month1:");
const monthMetric =
await client.series.prices.split.close.usd.by.month1.first(3);
const monthDates = monthMetric.dates();
console.log(` First month: ${monthDates[0].toISOString()}`);
// MonthIndex 0 = Jan 1, 2009
if (
monthDates[0].getFullYear() !== 2009 ||
monthDates[0].getMonth() !== 0 ||
monthDates[0].getDate() !== 1
) {
throw new Error(`Expected 2009-01-01, got ${monthDates[0].toISOString()}`);
}
// Test indexToDate directly
console.log("\n14. Testing indexToDate():");
const genesis = client.indexToDate("day1", 0);
if (
genesis.getFullYear() !== 2009 ||
genesis.getMonth() !== 0 ||
genesis.getDate() !== 3
) {
throw new Error(`Expected genesis 2009-01-03, got ${genesis.toISOString()}`);
}
const dayOne = client.indexToDate("day1", 1);
if (
dayOne.getFullYear() !== 2009 ||
dayOne.getMonth() !== 0 ||
dayOne.getDate() !== 9
) {
throw new Error(`Expected day one 2009-01-09, got ${dayOne.toISOString()}`);
}
console.log(` day1 0: ${genesis.toISOString()}`);
console.log(` day1 1: ${dayOne.toISOString()}`);
// Test week1
const week0 = client.indexToDate("week1", 0);
const week1 = client.indexToDate("week1", 1);
if (week0.getTime() !== genesis.getTime())
throw new Error("week1 0 should equal genesis");
console.log(` week1 0: ${week0.toISOString()}`);
console.log(` week1 1: ${week1.toISOString()}`);
// Test year1
const year0 = client.indexToDate("year1", 0);
const year1 = client.indexToDate("year1", 1);
if (
year0.getFullYear() !== 2009 ||
year0.getMonth() !== 0 ||
year0.getDate() !== 1
) {
throw new Error(`Expected 2009-01-01, got ${year0.toISOString()}`);
}
if (year1.getFullYear() !== 2010) throw new Error("year1 1 should be 2010");
console.log(` year1 0: ${year0.toISOString()}`);
console.log(` year1 1: ${year1.toISOString()}`);
// Test month3
const q0 = client.indexToDate("month3", 0);
const q1 = client.indexToDate("month3", 1);
if (q0.getMonth() !== 0) throw new Error("month3 0 should be January");
if (q1.getMonth() !== 3) throw new Error("month3 1 should be April");
console.log(` month3 0: ${q0.toISOString()}`);
console.log(` month3 1: ${q1.toISOString()}`);
// Test month6
const s0 = client.indexToDate("month6", 0);
const s1 = client.indexToDate("month6", 1);
if (s0.getMonth() !== 0) throw new Error("month6 0 should be January");
if (s1.getMonth() !== 6) throw new Error("month6 1 should be July");
console.log(` month6 0: ${s0.toISOString()}`);
console.log(` month6 1: ${s1.toISOString()}`);
// Test year10
const d0 = client.indexToDate("year10", 0);
const d1 = client.indexToDate("year10", 1);
if (d0.getFullYear() !== 2009) throw new Error("year10 0 should be 2009");
if (d1.getFullYear() !== 2019) throw new Error("year10 1 should be 2019");
console.log(` year10 0: ${d0.toISOString()}`);
console.log(` year10 1: ${d1.toISOString()}`);
// Test dateToIndex
console.log("\n15. Testing dateToIndex():");
const idx = client.dateToIndex("day1", new Date(Date.UTC(2009, 0, 9)));
if (idx !== 1) throw new Error(`Expected day1 index 1, got ${idx}`);
console.log(` day1 2009-01-09: ${idx}`);
const monthIdx = client.dateToIndex("month1", new Date(Date.UTC(2010, 0, 1)));
if (monthIdx !== 12)
throw new Error(`Expected month1 index 12, got ${monthIdx}`);
console.log(` month1 2010-01-01: ${monthIdx}`);
const yearIdx = client.dateToIndex("year1", new Date(Date.UTC(2019, 0, 1)));
if (yearIdx !== 10) throw new Error(`Expected year1 index 10, got ${yearIdx}`);
console.log(` year1 2019-01-01: ${yearIdx}`);
// Test roundtrip: indexToDate -> dateToIndex
const testDate = client.indexToDate("day1", 100);
const roundtrip = client.dateToIndex("day1", testDate);
if (roundtrip !== 100)
throw new Error(`Roundtrip failed: expected 100, got ${roundtrip}`);
console.log(` Roundtrip day1 100: ${testDate.toISOString()} -> ${roundtrip}`);
// Test slice with Date
console.log("\n16. Testing slice with Date:");
const dateSlice = await client.series.prices.split.close.usd.by.day1
.slice(new Date(Date.UTC(2020, 0, 1)), new Date(Date.UTC(2020, 0, 4)))
.fetch();
console.log(
` Slice start: ${dateSlice.start}, end: ${dateSlice.end}, items: ${dateSlice.data.length}`,
);
if (dateSlice.data.length !== dateSlice.end - dateSlice.start)
throw new Error("Slice data length mismatch");
console.log("\nAll MetricData tests passed!");
@@ -1,102 +0,0 @@
/**
* Comprehensive test that fetches all endpoints in the tree.
*/
import { BrkClient } from "../index.js";
/**
* @typedef {import('../index.js').AnySeriesPattern} AnyMetricPattern
*/
/**
* Check if an object is a metric pattern (has indexes() method and by object).
* @param {any} obj
* @returns {obj is AnyMetricPattern}
*/
function isMetricPattern(obj) {
return (
obj &&
typeof obj === "object" &&
typeof obj.indexes === "function" &&
obj.by &&
typeof obj.by === "object"
);
}
/**
* Recursively collect all metric patterns from the tree.
* @param {Record<string, any>} obj
* @param {string} path
* @returns {Array<{path: string, metric: AnyMetricPattern}>}
*/
function getAllMetrics(obj, path = "") {
/** @type {Array<{path: string, metric: AnyMetricPattern}>} */
const metrics = [];
for (const key of Object.keys(obj)) {
const attr = obj[key];
if (!attr || typeof attr !== "object") continue;
const currentPath = path ? `${path}.${key}` : key;
// Check if this is a metric pattern using the indexes() method
if (isMetricPattern(attr)) {
metrics.push({ path: currentPath, metric: attr });
}
// Recurse into nested tree nodes
if (typeof attr === "object" && !Array.isArray(attr)) {
metrics.push(...getAllMetrics(attr, currentPath));
}
}
return metrics;
}
async function testAllEndpoints() {
const client = new BrkClient({
baseUrl: "http://localhost:3110",
timeout: 15000,
});
const metrics = getAllMetrics(client.series);
console.log(`\nFound ${metrics.length} metrics`);
let success = 0;
for (const { path, metric } of metrics) {
// Use the indexes() method to get all available indexes
const indexes = metric.indexes();
for (const idxName of indexes) {
const fullPath = `${path}.by.${idxName}`;
try {
// Verify both access methods work: .by[index] and .get(index)
const endpointByProperty = metric.by[idxName];
const endpointByGet = metric.get(idxName);
if (!endpointByProperty) {
throw new Error(`metric.by.${idxName} is undefined`);
}
if (!endpointByGet) {
throw new Error(`metric.get('${idxName}') returned undefined`);
}
await endpointByProperty.last(0);
success++;
console.log(`OK: ${fullPath}`);
} catch (e) {
console.log(
`FAIL: ${fullPath} -> ${e instanceof Error ? e.message : e}`,
);
return;
}
}
}
console.log(`\n=== Results ===`);
console.log(`Success: ${success}`);
}
testAllEndpoints();
@@ -1,13 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": true,
"target": "ESNext",
"module": "ESNext",
"outDir": "/tmp/brk",
"lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"],
"skipLibCheck": true
},
"exclude": ["dist", "tests"]
}
@@ -1 +0,0 @@
*.js
-653
View File
@@ -1,653 +0,0 @@
declare module 'lean-qr' {
interface ImageDataLike {
readonly data: Uint8ClampedArray;
}
interface Context2DLike<DataT extends ImageDataLike> {
createImageData(width: number, height: number): DataT;
putImageData(data: DataT, x: number, y: number): void;
}
interface CanvasLike<DataT extends ImageDataLike> {
width: number;
height: number;
getContext(type: '2d'): Context2DLike<DataT> | null;
}
/**
* A colour in `[red, green, blue, alpha]` format (all values from 0 to 255).
* If alpha is omitted, it is assumed to be 255 (opaque).
*/
export type RGBA = readonly [number, number, number, number?];
export interface Bitmap1D {
/**
* Appends a sequence of bits.
*
* @param value an integer containing the bits to append (big endian).
* @param bits the number of bits to read from `value`. Must be between 1 and 24.
*/
push(value: number, bits: number): void;
}
export interface StringOptions {
/** the text to use for modules which are 'on' (typically black) */
on?: string;
/** the text to use for modules which are 'off' (typically white) */
off?: string;
/** the text to use for linefeeds between rows */
lf?: string;
/** the padding to apply around the output (populated with 'off' modules) */
pad?: number;
/**
* the padding to apply on the left and right of the output (populated with 'off' modules)
* @deprecated use `pad` instead
*/
padX?: number;
/**
* the padding to apply on the top and bottom of the output (populated with 'off' modules)
* @deprecated use `pad` instead
*/
padY?: number;
}
export interface ImageDataOptions {
/** the colour to use for modules which are 'on' (typically black) */
on?: RGBA;
/** the colour to use for modules which are 'off' (typically white) */
off?: RGBA;
/** the padding to apply around the output (filled with 'off') */
pad?: number;
/**
* the padding to apply on the left and right of the output (filled with 'off')
* @deprecated use `pad` instead
*/
padX?: number;
/**
* the padding to apply on the top and bottom of the output (filled with 'off')
* @deprecated use `pad` instead
*/
padY?: number;
}
export interface Bitmap2D {
/** the width / height of the QR code in modules (excluding any padding) */
readonly size: number;
/**
* Read the state of a module from the QR code.
*
* @param x the x coordinate to read. Can be negative / out of bounds.
* @param y the y coordinate to read. Can be negative / out of bounds.
* @returns true if the requested module is set (i.e. typically black)
*/
get(x: number, y: number): boolean;
/**
* Generate a string containing the QR code, suitable for displaying in a
* terminal environment. Generally, you should customise on and off to use
* the ANSI escapes of your target terminal for better rendering.
*
* @param options optional configuration for the display.
*/
toString(options?: Readonly<StringOptions>): string;
/**
* Generate image data containing the QR code, at a scale of 1 pixel per
* module. Use this if you need more control than toCanvas allows.
*
* @param context a context to use for creating the image data.
* @param options optional configuration for the display.
*/
toImageData<DataT extends ImageDataLike>(
context: Context2DLike<DataT>,
options?: Readonly<ImageDataOptions>,
): DataT;
/**
* Generate a `data:image/*` URL for the QR code.
*
* @param options optional configuration for the output.
* @returns a string suitable for use as the `src` of an `img` tag.
*/
toDataURL(
options?: Readonly<
ImageDataOptions & {
type?: `image/${string}`;
scale?: number;
}
>,
): string;
/**
* Populate a given canvas with the QR code, at a scale of 1 pixel per
* module. Set image-rendering: pixelated and scale the canvas using CSS
* for a large image. Automatically resizes the canvas to fit the QR code
* if necessary.
*
* @param canvas the canvas to populate.
* @param options optional configuration for the display.
*/
toCanvas(
canvas: CanvasLike<ImageDataLike>,
options?: Readonly<ImageDataOptions>,
): void;
}
export type Mask = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
export type Mode = (data: Bitmap1D, version: number) => void;
export interface ModeFactory {
(value: string): Mode;
/** a function which returns true when given a character which the current mode can represent */
test(string: string): boolean;
/** a function which returns an estimate of the number of bits required to encode a given value */
est(value: string, version: number): number;
/** an optional ECI which must be active for this mode to be interpreted correctly by a reader */
eci?: number;
}
interface ModeAutoOptions {
/** a list of modes which can be considered when encoding a message */
modes?: ReadonlyArray<ModeFactory>;
}
export const mode: Readonly<{
/** automatically picks the most optimal combination of modes for the requested message */
auto(value: string, options?: Readonly<ModeAutoOptions>): Mode;
/** concatenates multiple modes together */
multi(...modes: ReadonlyArray<Mode>): Mode;
/** sets the Extended Channel Interpretation for the message from this point onwards */
eci(id: number): Mode;
/** supports `0-9` and stores 3 characters per 10 bits */
numeric: ModeFactory;
/** supports `0-9A-Z $%*+-./:` and stores 2 characters per 11 bits */
alphaNumeric: ModeFactory;
/** arbitrary byte data, typically combined with `eci` */
bytes(data: Uint8Array | ReadonlyArray<number>): Mode;
/** supports 7-bit ASCII and stores 1 character per 8 bits with no ECI */
ascii: ModeFactory;
/** supports 8-bit ISO-8859-1 and stores 1 character per 8 bits with ECI 3 */
iso8859_1: ModeFactory;
/** supports double-byte Shift-JIS characters stores 1 character per 13 bits */
shift_jis: ModeFactory;
/** supports variable length UTF-8 with ECI 26 */
utf8: ModeFactory;
}>;
export type Correction = number & { readonly _: unique symbol };
export const correction: Readonly<{
/**
* minimum possible correction level (same as L)
* @deprecated use correction.L
*/
min: Correction;
/** ~7.5% error tolerance, ~25% data overhead */
L: Correction;
/** ~15% error tolerance, ~60% data overhead */
M: Correction;
/** ~22.5% error tolerance, ~120% data overhead */
Q: Correction;
/** ~30% error tolerance, ~190% data overhead */
H: Correction;
/**
* maximum possible correction level (same as H)
* @deprecated use correction.H
*/
max: Correction;
}>;
export interface GenerateOptions extends ModeAutoOptions {
/** the minimum correction level to use (higher levels may still be used if the chosen version has space) */
minCorrectionLevel?: Correction;
/** the maximum correction level to use */
maxCorrectionLevel?: Correction;
/** the minimum version (size) of code to generate (must be between 1 and 40) */
minVersion?: number;
/** the maximum version (size) of code to generate (must be between 1 and 40) */
maxVersion?: number;
/** a mask to use on the QR code (should be left as `null` for ISO compliance but may be changed for artistic effect) */
mask?: null | Mask;
/** padding bits to use for extra space in the QR code (should be left as the default for ISO compliance but may be changed for artistic effect) */
trailer?: number;
}
/**
* Generate a QR code.
*
* @param data either a string, or a pre-encoded mode.
* @param options optional configuration for the QR code.
* @returns the requested QR code.
*/
export type GenerateFn = (
data: Mode | string,
options?: Readonly<GenerateOptions>,
) => Bitmap2D;
interface Generate extends GenerateFn {
/**
* Creates a scoped `generate` function which considers additional modes
* when using auto encoding.
*
* @param modes the modes to add.
* @returns a `generate` function which will additionally consider the
* given modes when using auto encoding.
*
* @deprecated this will be removed in version 3. Prefer passing an explicit list of modes when calling `generate`.
*/
with(...modes: ReadonlyArray<ModeFactory>): GenerateFn;
}
export const generate: Generate;
}
declare module 'lean-qr/nano' {
import type {
Correction,
Bitmap2D as FullBitmap2D,
GenerateOptions as FullGenerateOptions,
} from 'lean-qr';
import { correction as fullCorrection } from 'lean-qr';
export type { Correction };
export const correction: Pick<typeof fullCorrection, 'L' | 'M' | 'Q' | 'H'>;
export type Bitmap2D = Pick<FullBitmap2D, 'size' | 'get' | 'toCanvas'>;
export type GenerateOptions = Pick<
FullGenerateOptions,
'minCorrectionLevel' | 'minVersion'
>;
/**
* Generate a QR code.
*
* @param data either a string, or a pre-encoded mode.
* @param options optional configuration for the QR code.
* @returns the requested QR code.
*/
export function generate(
data: string,
options?: Readonly<GenerateOptions>,
): Bitmap2D;
}
declare module 'lean-qr/extras/svg' {
import type { Bitmap2D as FullBitmap2D } from 'lean-qr';
type Bitmap2D = Pick<FullBitmap2D, 'size' | 'get'>;
export interface SVGOptions {
/** the colour to use for modules which are 'on' (typically black) */
on?: string;
/** the colour to use for modules which are 'off' (typically white) */
off?: string;
/** the padding to apply around the output (filled with 'off') */
pad?: number;
/**
* the padding to apply on the left and right of the output (filled with 'off')
* @deprecated use `pad` instead
*/
padX?: number;
/**
* the padding to apply on the top and bottom of the output (filled with 'off')
* @deprecated use `pad` instead
*/
padY?: number;
/** a width to apply to the resulting image (overrides `scale`) */
width?: number | null;
/** a height to apply to the resulting image (overrides `scale`) */
height?: number | null;
/** a scale to apply to the resulting image (`scale` pixels = 1 module) */
scale?: number;
}
/**
* Generate the raw outline of the QR code for use in an existing SVG.
*
* @param code the QR code to convert.
* @returns a string suitable for passing to the `d` attribute of a `path`.
*/
export function toSvgPath(code: Bitmap2D): string;
/**
* Generate an SVG element which can be added to the DOM.
*
* @param code the QR code to convert.
* @param options optional configuration for the display.
* @returns an SVG element.
*/
export function toSvg(
code: Bitmap2D,
target: Document | SVGElement,
options?: Readonly<SVGOptions>,
): SVGElement;
/**
* Generate an SVG document which can be exported to a file or served from a
* web server.
*
* @param code the QR code to convert.
* @param options optional configuration for the display.
* @returns an SVG document.
*/
export function toSvgSource(
code: Bitmap2D,
options?: Readonly<
SVGOptions & {
/** `true` to include an XML declaration at the start of the source (for standalone documents which will not be embedded inside another document) */
xmlDeclaration?: boolean;
}
>,
): string;
/**
* Generate a `data:image/svg+xml` URL.
*
* @param code the QR code to convert.
* @param options optional configuration for the display.
* @returns a string suitable for use as the `src` of an `img` tag.
*/
export function toSvgDataURL(
code: Bitmap2D,
options?: Readonly<SVGOptions>,
): string;
}
declare module 'lean-qr/extras/node_export' {
import type { RGBA, Bitmap2D as FullBitmap2D } from 'lean-qr';
type Bitmap2D = Pick<FullBitmap2D, 'size' | 'get'>;
export interface PNGOptions {
/** the colour to use for modules which are 'on' (typically black) */
on?: RGBA;
/** the colour to use for modules which are 'off' (typically white) */
off?: RGBA;
/** the padding to apply around the output (filled with 'off') */
pad?: number;
/**
* the padding to apply on the left and right of the output (filled with 'off')
* @deprecated use `pad` instead
*/
padX?: number;
/**
* the padding to apply on the top and bottom of the output (filled with 'off')
* @deprecated use `pad` instead
*/
padY?: number;
/** a scale to apply to the resulting image (`scale` pixels = 1 module) */
scale?: number;
}
/**
* Generate a PNG document which can be exported to a file or served from a
* web server.
*
* @param code the QR code to convert.
* @param options optional configuration for the display.
* @returns a PNG document.
*/
export function toPngBuffer(
code: Bitmap2D,
options?: Readonly<PNGOptions>,
): Uint8Array;
/**
* Generate a `data:image/png` URL.
*
* @param code the QR code to convert.
* @param options optional configuration for the display.
* @returns a string suitable for use as the `src` of an `img` tag.
*/
export function toPngDataURL(
code: Bitmap2D,
options?: Readonly<PNGOptions>,
): string;
}
declare module 'lean-qr/extras/react' {
import type {
Bitmap2D as FullBitmap2D,
GenerateOptions,
ImageDataOptions,
} from 'lean-qr';
import type {
SVGOptions,
toSvgDataURL as toSvgDataURLFn,
} from 'lean-qr/extras/svg';
export interface AsyncFramework<T> {
createElement: (
type: 'canvas',
props: {
ref: any;
style: { imageRendering: 'pixelated' };
className: string;
},
) => T;
useRef<T>(initialValue: T | null): { readonly current: T | null };
useEffect(fn: () => void | (() => void), deps: unknown[]): void;
}
interface QRComponentProps {
content: string;
className?: string;
}
export interface AsyncQRComponentProps
extends ImageDataOptions,
GenerateOptions,
QRComponentProps {}
export type AsyncQRComponent<T> = (
props: Readonly<AsyncQRComponentProps>,
) => T;
/**
* Generate an asynchronous QR component (rendering to a `canvas`).
* You should call this just once, in the global scope.
*
* ```js
* import * as React from 'react';
* import { generate } from 'lean-qr';
* import { makeAsyncComponent } from 'lean-qr/extras/react';
* const QR = makeAsyncComponent(React, generate);
* ```
*
* This is not suitable for server-side rendering (use `makeSyncComponent`
* instead).
*
* @param framework the framework to use (e.g. `React`).
* @param generate the `generate` function to use
* (from `lean-qr` or `lean-qr/nano`).
* @param defaultProps optional default properties to apply when the
* component is used (overridden by properties set on use).
* @returns a component which can be rendered elsewhere.
*/
export function makeAsyncComponent<T>(
framework: Readonly<AsyncFramework<T>>,
generate: (
data: string,
options?: Readonly<GenerateOptions>,
) => Pick<FullBitmap2D, 'toCanvas'>,
defaultProps?: Readonly<Partial<AsyncQRComponentProps>>,
): AsyncQRComponent<T>;
export interface SyncFramework<T> {
createElement: (
type: 'img',
props: {
src: string;
style: { imageRendering: 'pixelated' };
className: string;
},
) => T;
useMemo<T>(fn: () => T, deps: unknown[]): T;
}
export interface SyncQRComponentProps
extends SVGOptions,
GenerateOptions,
QRComponentProps {}
export type SyncQRComponent<T> = (props: Readonly<SyncQRComponentProps>) => T;
/**
* Generate a synchronous QR component (rendering to an SVG).
* You should call this just once, in the global scope.
*
* ```js
* import * as React from 'react';
* import { generate } from 'lean-qr';
* import { toSvgDataURL } from 'lean-qr/extras/svg';
* import { makeSyncComponent } from 'lean-qr/extras/react';
* const QR = makeSyncComponent(React, generate, toSvgDataURL);
* ```
*
* This is best suited for server-side rendering (prefer
* `makeAsyncComponent` if you only need client-side rendering).
*
* @param framework the framework to use (e.g. `React`).
* @param generate the `generate` function to use
* (from `lean-qr` or `lean-qr/nano`).
* @param toSvgDataURL the `toSvgDataURL` function to use
* (from `lean-qr/extras/svg`).
* @param defaultProps optional default properties to apply when the
* component is used (overridden by properties set on use).
* @returns a component which can be rendered elsewhere.
*/
export function makeSyncComponent<T>(
framework: Readonly<SyncFramework<T>>,
generate: (
data: string,
options?: Readonly<GenerateOptions>,
) => Pick<FullBitmap2D, 'size' | 'get'>,
toSvgDataURL: typeof toSvgDataURLFn,
defaultProps?: Readonly<Partial<SyncQRComponentProps>>,
): SyncQRComponent<T>;
}
declare module 'lean-qr/extras/vue' {
import type {
Bitmap2D as FullBitmap2D,
GenerateOptions,
ImageDataOptions,
} from 'lean-qr';
import type {
SVGOptions,
toSvgDataURL as toSvgDataURLFn,
} from 'lean-qr/extras/svg';
export interface Framework {
h:
| ((type: 'canvas', props: { ref: string; style: string }) => unknown)
| ((type: 'img', props: { src: string; style: string }) => unknown);
}
interface QRComponentProps {
content: string;
}
export interface VueCanvasComponentProps
extends ImageDataOptions,
GenerateOptions,
QRComponentProps {}
type VueComponentDefinition<Props> = {
props: {
[k in keyof Props]-?: {
type: { (): Required<Props>[k] };
required: undefined extends Props[k] ? false : true;
};
};
} & ThisType<unknown>;
/**
* Generate a QR component which renders to a `canvas`.
* You should call this just once, in the global scope.
*
* ```js
* import { h, defineComponent } from 'vue';
* import { generate } from 'lean-qr';
* import { makeVueCanvasComponent } from 'lean-qr/extras/vue';
* export const QR = defineComponent(makeVueCanvasComponent({ h }, generate));
* ```
*
* This is not suitable for server-side rendering (use `makeSyncComponent`
* instead).
*
* @param framework the framework to use (e.g. `{ h }`).
* @param generate the `generate` function to use
* (from `lean-qr` or `lean-qr/nano`).
* @param defaultProps optional default properties to apply when the
* component is used (overridden by properties set on use).
* @returns a component which can be rendered elsewhere.
*/
export function makeVueCanvasComponent(
framework: Readonly<Framework>,
generate: (
data: string,
options?: Readonly<GenerateOptions>,
) => Pick<FullBitmap2D, 'toCanvas'>,
defaultProps?: Readonly<Partial<VueCanvasComponentProps>>,
): VueComponentDefinition<VueCanvasComponentProps>;
export interface VueSVGComponentProps
extends SVGOptions,
GenerateOptions,
QRComponentProps {}
/**
* Generate a QR component which renders to an SVG.
* You should call this just once, in the global scope:
*
* ```js
* import { h, defineComponent } from 'vue';
* import { generate } from 'lean-qr';
* import { toSvgDataURL } from 'lean-qr/extras/svg';
* import { makeVueSvgComponent } from 'lean-qr/extras/vue';
* export const QR = defineComponent(makeVueSvgComponent({ h }, generate, toSvgDataURL));
* ```
*
* This is best suited for server-side rendering (prefer
* `makeAsyncComponent` if you only need client-side rendering).
*
* @param framework the framework to use (e.g. `{ h }`).
* @param generate the `generate` function to use
* (from `lean-qr` or `lean-qr/nano`).
* @param toSvgDataURL the `toSvgDataURL` function to use
* (from `lean-qr/extras/svg`).
* @param defaultProps optional default properties to apply when the
* component is used (overridden by properties set on use).
* @returns a component which can be rendered elsewhere.
*/
export function makeVueSvgComponent(
framework: Readonly<Framework>,
generate: (
data: string,
options?: Readonly<GenerateOptions>,
) => Pick<FullBitmap2D, 'size' | 'get'>,
toSvgDataURL: typeof toSvgDataURLFn,
defaultProps?: Readonly<Partial<VueSVGComponentProps>>,
): VueComponentDefinition<VueSVGComponentProps>;
}
declare module 'lean-qr/extras/errors' {
/**
* Convert an error into a human-readable message. This is intended for use
* with Lean QR errors, but will return somewhat meaningful messages for
* other errors too.
*
* @param error the error to convert.
* @returns a human-readable message explaining the error.
*/
export function readError(error: unknown): string;
}
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
*charts.pro*
light*.js
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -1,433 +0,0 @@
const DEFAULT_SEPARATORS = "_- :/";
const DEFAULT_TRIGRAM_BUDGET = 6;
const DEFAULT_LIMIT = 100;
const DEFAULT_MIN_SCORE = 2;
/**
* Search configuration.
*
* Defaults work well for most use cases.
* Tweak `trigramBudget` to trade speed for typo tolerance.
*/
export class QuickMatchConfig {
/** Characters that separate words in items (e.g. "hash_rate" ["hash", "rate"]).
* @type {string} */
separators = DEFAULT_SEPARATORS;
/** Max results returned per query.
* @type {number} */
limit = DEFAULT_LIMIT;
/** How hard to try matching typos (0 = off, 36 = fast, 915 = thorough, max 20).
* @type {number} */
trigramBudget = DEFAULT_TRIGRAM_BUDGET;
/** Min overlap required for a typo match. Higher = fewer false positives.
* @type {number} */
minScore = DEFAULT_MIN_SCORE;
/** @param {number} n - Max results (default: 100, min: 1) */
withLimit(n) {
this.limit = Math.max(1, n);
return this;
}
/** @param {number} n - Trigram budget (0-20, default: 6) */
withTrigramBudget(n) {
this.trigramBudget = Math.max(0, Math.min(20, n));
return this;
}
/** @param {string} s - Separator characters (default: '_- :/') */
withSeparators(s) {
this.separators = s;
return this;
}
/** @param {number} n - Min trigram score (default: 2, min: 1) */
withMinScore(n) {
this.minScore = Math.max(1, n);
return this;
}
}
/**
* Instant search over a list of strings.
*
* Supports exact words, prefixes ("dom" "dominance"), joined words
* ("hashrate" "hash_rate"), and typo tolerance ("suply" "supply").
* Results are ranked: exact matches first, then by specificity.
*/
export class QuickMatch {
/** @param {string[]} items - Searchable items (lowercase) @param {QuickMatchConfig} [config] */
constructor(items, config = new QuickMatchConfig()) {
this.config = config;
this.items = items;
/** @type {Map<string, number[]>} */
this.wordIndex = new Map();
/** @type {Map<string, number[]>} */
this.trigramIndex = new Map();
this._sepLookup = sepLookup(config.separators);
this._scores = new Uint32Array(items.length);
/** @type {number[]} */
this._dirty = [];
let maxWordLen = 0;
let maxQueryLen = 0;
let maxWords = 0;
const sep = this._sepLookup;
for (let idx = 0; idx < items.length; idx++) {
const item = items[idx];
if (item.length > maxQueryLen) maxQueryLen = item.length;
const words = [];
let start = 0;
for (let i = 0; i <= item.length; i++) {
if (i < item.length && !sep[item.charCodeAt(i)]) continue;
if (i > start) {
const word = item.slice(start, i);
words.push(word);
if (word.length > maxWordLen) maxWordLen = word.length;
for (let len = 1; len <= word.length; len++) {
addToIndex(this.wordIndex, word.slice(0, len), idx);
}
for (let k = 0; k <= word.length - 3; k++) {
addToIndex(this.trigramIndex, word[k] + word[k + 1] + word[k + 2], idx);
}
}
start = i + 1;
}
for (let i = 0; i < words.length - 1; i++) {
const compound = words[i] + words[i + 1];
// A joined-word query ("hashrate") can be longer than any single
// word. Capping at the longest index key keeps the DDoS guard
// data-bounded while still letting it match.
if (compound.length > maxWordLen) maxWordLen = compound.length;
const from = words[i].length + 1;
for (let len = from; len <= compound.length; len++) {
addToIndex(this.wordIndex, compound.slice(0, len), idx);
}
}
if (words.length > maxWords) maxWords = words.length;
}
this.maxWordLen = maxWordLen + 4;
this.maxQueryLen = maxQueryLen + 6;
this.maxWords = maxWords + 2;
}
/** @param {string} query */
matches(query) {
return this.matchesWith(query, this.config);
}
/**
* @param {string} query
* @param {QuickMatchConfig} config
*/
matchesWith(query, config) {
const { limit, trigramBudget } = config;
const sep =
config.separators === this.config.separators
? this._sepLookup
: sepLookup(config.separators);
const q = normalize(query);
if (!q || q.length > this.maxQueryLen) return [];
const qwords = splitWords(q, sep, this.maxWordLen);
if (!qwords.length || qwords.length > this.maxWords) return [];
const known = [];
const unknown = [];
for (const w of qwords) {
const hits = this.wordIndex.get(w);
if (hits) {
known.push(hits);
} else if (w.length >= 3 && unknown.length < trigramBudget) {
unknown.push(w);
}
}
const pool = intersect(known);
// Try typo matching for unknown words
if (unknown.length && trigramBudget) {
const { _scores: scores, _dirty: dirty } = this;
if (pool) {
for (const i of pool) {
scores[i] = 1;
dirty.push(i);
}
}
const hitCount = this._scoreTrigrams(
unknown,
trigramBudget,
pool !== null,
Math.max(0, q.length - 3),
);
const minScore = Math.max(config.minScore, Math.ceil(hitCount / 2));
const result = this._rank(dirty, minScore, qwords, sep, limit);
for (const i of dirty) scores[i] = 0;
dirty.length = 0;
if (result.length > 0) return result;
}
// Rank known candidates (intersection, or union as fallback)
const candidates = pool || union(known);
return candidates.length > 0
? this._rank(candidates, null, qwords, sep, limit)
: [];
}
/** @private @param {string[]} unknown @param {number} budget @param {boolean} poolOnly @param {number} minLen */
_scoreTrigrams(unknown, budget, poolOnly, minLen) {
const { _scores: scores, _dirty: dirty, items } = this;
const visited = new Set();
const maxRounds = budget;
let hits = 0;
outer: for (let round = 0; round < maxRounds; round++) {
for (const word of unknown) {
if (budget <= 0) break outer;
const pos = trigramPosition(word.length, round);
if (pos < 0) continue;
const tri = word[pos] + word[pos + 1] + word[pos + 2];
if (visited.has(tri)) continue;
visited.add(tri);
budget--;
const matched = this.trigramIndex.get(tri);
if (!matched) continue;
hits++;
if (poolOnly) {
for (let j = 0; j < matched.length; j++) {
const i = matched[j];
if (scores[i] > 0) scores[i]++;
}
} else {
for (let j = 0; j < matched.length; j++) {
const i = matched[j];
if (items[i].length >= minLen) {
if (scores[i] === 0) dirty.push(i);
scores[i]++;
}
}
}
}
}
return hits;
}
/**
* @private
* @param {number[]} indices
* @param {number|null} minScore
* @param {string[]} qwords
* @param {Uint8Array} sep
* @param {number} limit
*/
_rank(indices, minScore, qwords, sep, limit) {
const { items, _scores: scores } = this;
/** @type {[number, number][][]} */
const buckets = Array.from({ length: qwords.length + 1 }, () => []);
for (let i = 0; i < indices.length; i++) {
const idx = indices[i];
if (minScore !== null && scores[idx] < minScore) continue;
const [matched, position] = wordMatch(items[idx], qwords, sep);
buckets[matched].push([idx, position]);
}
const results = [];
for (let ps = buckets.length - 1; ps >= 0 && results.length < limit; ps--) {
const bucket = buckets[ps];
if (!bucket.length) continue;
bucket.sort(
([a, pa], [b, pb]) =>
scores[b] - scores[a] ||
pa - pb ||
items[a].length - items[b].length ||
(items[a] < items[b] ? -1 : 1), // item text, asc (total order)
);
const take = Math.min(bucket.length, limit - results.length);
for (let i = 0; i < take; i++) results.push(items[bucket[i][0]]);
}
return results;
}
}
// --- Helpers ---
/** @param {string} query */
function normalize(query) {
let out = "";
let start = 0;
let end = query.length;
while (start < end && query.charCodeAt(start) <= 32) start++;
while (end > start && query.charCodeAt(end - 1) <= 32) end--;
for (let i = start; i < end; i++) {
const c = query.charCodeAt(i);
if (c >= 128) continue;
out += c >= 65 && c <= 90 ? String.fromCharCode(c + 32) : query[i];
}
return out;
}
/** @param {string} separators */
function sepLookup(separators) {
const t = new Uint8Array(128);
for (let i = 0; i < separators.length; i++) {
const c = separators.charCodeAt(i);
if (c < 128) t[c] = 1;
}
return t;
}
/**
* @param {string} text
* @param {Uint8Array} sep
* @param {number} maxLen
*/
function splitWords(text, sep, maxLen) {
/** @type {string[]} */
const words = [];
let start = 0;
for (let i = 0; i <= text.length; i++) {
if (i < text.length && !sep[text.charCodeAt(i)]) continue;
if (i > start) {
const w = text.slice(start, i);
if (w.length <= maxLen && !words.includes(w)) words.push(w);
}
start = i + 1;
}
return words;
}
/**
* @param {Map<string, number[]>} index
* @param {string} key
* @param {number} value
*/
function addToIndex(index, key, value) {
const arr = index.get(key);
if (arr) {
if (arr[arr.length - 1] !== value) arr.push(value);
} else {
index.set(key, [value]);
}
}
/** @param {number[][]} arrays */
function union(arrays) {
if (arrays.length <= 1) return arrays[0] || [];
const seen = new Set();
const result = [];
for (const arr of arrays) {
for (const idx of arr) {
if (!seen.has(idx)) {
seen.add(idx);
result.push(idx);
}
}
}
return result;
}
/** @param {number[][]} arrays @returns {number[]|null} */
function intersect(arrays) {
if (arrays.length <= 1) return arrays[0] || null;
let si = 0;
for (let i = 1; i < arrays.length; i++) {
if (arrays[i].length < arrays[si].length) si = i;
}
const result = arrays[si].slice();
for (let i = 0; i < arrays.length; i++) {
if (i === si) continue;
let w = 0;
for (let j = 0; j < result.length; j++) {
if (bsearch(arrays[i], result[j])) result[w++] = result[j];
}
result.length = w;
if (!w) return null;
}
return result;
}
/**
* @param {number[]} arr
* @param {number} val
*/
function bsearch(arr, val) {
let lo = 0,
hi = arr.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
if (arr[mid] === val) return true;
if (arr[mid] < val) lo = mid + 1;
else hi = mid - 1;
}
return false;
}
/**
* Aligns query words against the item's words, in order.
* @param {string} item @param {string[]} qwords @param {Uint8Array} sep
* @returns {[number, number]} `[matched, position]` - query words matched as
* an in-order subsequence, and the item-word index where that run starts
* (or the item's word count when nothing matched).
*/
function wordMatch(item, qwords, sep) {
const len = item.length;
let matched = 0;
let position = 0;
let pos = 0;
while (pos < len) {
while (pos < len && sep[item.charCodeAt(pos)]) pos++;
if (pos >= len) break;
const ws = pos;
while (pos < len && !sep[item.charCodeAt(pos)]) pos++;
const qw = qwords[matched];
if (qw !== undefined && pos - ws >= qw.length && item.startsWith(qw, ws)) {
matched++;
} else if (matched === 0) {
position++;
}
}
return [matched, position];
}
/** @param {number} len @param {number} round */
function trigramPosition(len, round) {
const max = len - 3;
if (max < 0) return -1;
if (round === 0) return 0;
if (round === 1 && max > 0) return max;
if (round === 2 && max > 1) return max >> 1;
if (max <= 2) return -1;
const mid = max >> 1;
const off = (round - 2) >> 1;
const pos = round & 1 ? Math.max(0, mid - off) : mid + off;
return pos === 0 || pos >= max || pos === mid ? -1 : pos;
}
-15
View File
@@ -1,15 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"target": "ESNext",
"module": "ESNext",
"outDir": "/tmp/brk",
"lib": ["DOM", "DOM.Iterable", "ESNext", "WebWorker"],
"skipLibCheck": true,
},
"exclude": ["assets", "./scripts/modules"],
}
-410
View File
@@ -1,410 +0,0 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_debug() {
if [[ "${DEBUG:-}" == "1" ]]; then
echo -e "${YELLOW}[DEBUG]${NC} $1"
fi
}
# Check if required tools are installed
check_dependencies() {
local missing_deps=()
if ! command -v curl &> /dev/null; then
missing_deps+=("curl")
fi
if ! command -v grep &> /dev/null; then
missing_deps+=("grep")
fi
if ! command -v sed &> /dev/null; then
missing_deps+=("sed")
fi
if [ ${#missing_deps[@]} -ne 0 ]; then
print_error "Missing required dependencies: ${missing_deps[*]}"
print_error "Please install them and try again."
exit 1
fi
}
# Function to URL encode a string
url_encode() {
local string="${1}"
local strlen=${#string}
local encoded=""
local pos c o
for ((pos=0; pos<strlen; pos++)); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
echo "${encoded}"
}
# Function to create directory if it doesn't exist
create_dir() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
print_status "Created directory: $dir"
fi
}
# Function to resolve "latest" version to actual version number
resolve_latest_version() {
local package_name="$1"
local version="$2"
# If version is not "latest", return as-is
if [[ "$version" != "latest" ]]; then
echo "$version"
return
fi
print_status "Resolving 'latest' version for $package_name..." >&2
# URL encode the package name
local encoded_package=$(url_encode "$package_name")
local latest_url="https://app.unpkg.com/${encoded_package}@latest"
# Use curl to follow redirects and get the final URL
local final_url
final_url=$(curl -L -s -o /dev/null -w '%{url_effective}' "$latest_url")
if [[ -z "$final_url" ]]; then
print_error "Failed to resolve latest version for $package_name" >&2
return 1
fi
# Extract version from the final URL
# Format: https://app.unpkg.com/@solidjs/signals@0.4.1
local resolved_version
# Use a different delimiter (#) to avoid issues with / in package names
resolved_version=$(echo "$final_url" | sed -n "s#.*${package_name}@\([^\/]*\).*#\1#p")
if [[ -z "$resolved_version" ]]; then
print_error "Could not extract version from URL: $final_url" >&2
return 1
fi
print_success "Resolved 'latest' to version: $resolved_version" >&2
echo "$resolved_version"
}
# Function to download a file
download_file() {
local file_url="$1"
local local_path="$2"
local dir=$(dirname "$local_path")
create_dir "$dir"
print_status "Downloading: $file_url"
if curl -L -s -f "$file_url" -o "$local_path"; then
print_success "Downloaded: $local_path"
return 0
else
print_error "Failed to download: $file_url"
return 1
fi
}
# Function to parse HTML and extract file/folder links using a much simpler approach
parse_directory() {
local html_content="$1"
local current_path="$2"
local package_name="$3"
local version="$4"
print_debug "Parsing directory for path: '$current_path'"
# The HTML contains the original (unencoded) package name in URLs
# So we search for the original package name, not the encoded version
# Find all links that point to files/folders in this package
# Look for the pattern: <a href="...">filename</a> or <a href="...">foldername/</a>
local links=$(echo "$html_content" | grep -o '<a href="[^"]*"[^>]*>[^<]*</a>' | grep "${package_name}@${version}/files")
print_debug "Found $(echo "$links" | wc -l) total links"
if [[ "${DEBUG:-}" == "1" ]]; then
print_debug "All found links:"
echo "$links" | while read -r link; do
[[ -n "$link" ]] && print_debug " $link"
done
fi
echo "$links" | while read -r link_line; do
if [[ -z "$link_line" ]]; then
continue
fi
# Extract the href URL
local href=$(echo "$link_line" | sed -n 's/.*href="\([^"]*\)".*/\1/p')
# Extract the link text (what's between <a> and </a>)
local link_text=$(echo "$link_line" | sed -n 's/.*<a[^>]*>\([^<]*\)<\/a>.*/\1/p')
print_debug "Processing link: href='$href' text='$link_text'"
# Skip if we couldn't extract both parts
if [[ -z "$href" ]] || [[ -z "$link_text" ]]; then
continue
fi
# Skip parent directory links
if [[ "$link_text" == "../" ]] || [[ "$link_text" == ".." ]]; then
continue
fi
# Extract the file/folder path from the URL
# URL format: https://app.unpkg.com/@solidjs/signals@0.4.1/files/path/to/item
# Note: href contains the original (unencoded) package name
local url_path="${href#*${package_name}@${version}/files}"
url_path="${url_path#/}" # Remove leading slash
print_debug "URL path extracted: '$url_path'"
# Skip if this is not a direct child of current directory
if [[ -n "$current_path" ]]; then
# We're in a subdirectory, so the URL path should start with current_path
local current_clean="${current_path#/}"
if [[ "$url_path" != "${current_clean}"* ]]; then
print_debug "Skipping - not in current path"
continue
fi
# Get the relative path from current directory
local relative_path="${url_path#${current_clean}}"
relative_path="${relative_path#/}"
else
# We're in root, so relative_path is the same as url_path
local relative_path="$url_path"
fi
print_debug "Relative path: '$relative_path'"
# Skip if this contains subdirectories (we only want direct children)
if [[ "$relative_path" == *"/"* ]]; then
print_debug "Skipping - contains subdirectories"
continue
fi
# Skip empty paths
if [[ -z "$relative_path" ]]; then
continue
fi
# Determine if it's a folder or file based on the link text
if [[ "$link_text" == *"/" ]]; then
# It's a folder
local folder_name="${relative_path%/}"
if [[ -n "$current_path" ]]; then
echo "FOLDER:${current_path}/${folder_name}"
else
echo "FOLDER:/${folder_name}"
fi
else
# It's a file
if [[ -n "$current_path" ]]; then
echo "FILE:${current_path}/${relative_path}"
else
echo "FILE:/${relative_path}"
fi
fi
done
}
# Function to check if item was already processed
is_processed() {
local item="$1"
local processed_list="$2"
[[ "$processed_list" == *"|${item}|"* ]]
}
# Function to add item to processed list
add_processed() {
local item="$1"
local processed_list="$2"
echo "${processed_list}|${item}|"
}
# Function to process a directory (download files and recurse into subdirectories)
process_directory() {
local package_name="$1"
local version="$2"
local dir_path="$3"
local output_dir="$4"
local processed_dirs="$5"
# Encode the package name for URL
local encoded_package=$(url_encode "$package_name")
local app_url="https://app.unpkg.com/${encoded_package}@${version}/files${dir_path}"
local download_base_url="https://unpkg.com/${encoded_package}@${version}"
# Check if we've already processed this directory
if is_processed "$dir_path" "$processed_dirs"; then
print_warning "Already processed directory: $dir_path"
return
fi
print_status "Processing directory: ${dir_path:-'(root)'}"
print_status "Fetching: $app_url"
# Download the directory listing HTML
local html_content
if ! html_content=$(curl -L -s -f "$app_url"); then
print_error "Failed to fetch directory listing: $app_url"
return
fi
print_status "Fetched HTML content (${#html_content} characters)"
# Mark this directory as processed
processed_dirs=$(add_processed "$dir_path" "$processed_dirs")
# Parse the HTML to find files and folders
local items
items=$(parse_directory "$html_content" "$dir_path" "$package_name" "$version")
print_debug "Parsed items:"
print_debug "$items"
# Collect unique files and folders
local files=()
local folders=()
local seen_files=""
local seen_folders=""
while IFS= read -r item; do
[[ -z "$item" ]] && continue
if [[ "$item" == FILE:* ]]; then
local file_path="${item#FILE:}"
if ! is_processed "$file_path" "$seen_files"; then
files+=("$file_path")
seen_files=$(add_processed "$file_path" "$seen_files")
print_debug "Added file: $file_path"
fi
elif [[ "$item" == FOLDER:* ]]; then
local folder_path="${item#FOLDER:}"
if ! is_processed "$folder_path" "$seen_folders"; then
folders+=("$folder_path")
seen_folders=$(add_processed "$folder_path" "$seen_folders")
print_debug "Added folder: $folder_path"
fi
fi
done <<< "$items"
print_status "Found ${#files[@]} files and ${#folders[@]} folders"
# Download all files in this directory
for file_path in "${files[@]}"; do
if [[ -n "$file_path" ]]; then
local file_url="${download_base_url}${file_path}"
local local_path="${output_dir}${file_path}"
download_file "$file_url" "$local_path"
fi
done
# Recursively process all folders
for folder_path in "${folders[@]}"; do
if [[ -n "$folder_path" ]]; then
process_directory "$package_name" "$version" "$folder_path" "$output_dir" "$processed_dirs"
fi
done
}
# Main function
main() {
# Check dependencies
check_dependencies
# Parse command line arguments
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
echo "Usage: $0 <package-name> [version] [output-dir]"
echo "Example: $0 \"@solidjs/signals\""
echo "Example: $0 \"@solidjs/signals\" \"0.4.1\""
echo "Example: $0 \"@solidjs/signals\" \"latest\""
echo "Example: $0 \"@solidjs/signals\" \"latest\" \"./downloads\""
echo "Example: $0 \"lodash\" \"4.17.21\" \"./downloads\""
echo ""
echo "Version defaults to 'latest' if not specified"
echo "Set DEBUG=1 environment variable for verbose output"
exit 1
fi
local package_name="$1"
local version="${2:-latest}"
# Resolve latest version if needed (do this once at the start)
local resolved_version
if ! resolved_version=$(resolve_latest_version "$package_name" "$version"); then
exit 1
fi
# Use resolved version for output directory
local output_dir="${3:-./$(echo "${package_name}" | sed 's/@//g' | sed 's/\//-/g')/${resolved_version}}"
print_status "Starting download of package: $package_name@$version"
if [[ "$version" == "latest" ]]; then
print_status "Resolved to actual version: $resolved_version"
fi
print_status "Output directory: $output_dir"
# Check if the directory already exists and has content
if [[ -d "$output_dir" ]] && [[ -n "$(ls -A "$output_dir" 2>/dev/null)" ]]; then
print_warning "Directory already exists and is not empty: $output_dir"
print_warning "Package $package_name@$resolved_version appears to already be downloaded."
print_warning "Remove the directory or choose a different output location to proceed."
return
fi
# Create the base output directory
create_dir "$output_dir"
# Start processing from the root directory using the resolved version
process_directory "$package_name" "$resolved_version" "" "$output_dir" ""
print_success "Package download completed!"
print_status "Files downloaded to: $output_dir"
}
# Run the main function with all arguments
# main "$@"
main "quickmatch-js"
main "lean-qr"
main "lightweight-charts"
@@ -33,6 +33,7 @@ export function buildCohortData() {
AMOUNT_RANGE_NAMES,
SPENDABLE_TYPE_NAMES,
CLASS_NAMES,
ENTRY_NAMES,
PROFITABILITY_RANGE_NAMES,
PROFIT_NAMES,
LOSS_NAMES,
@@ -201,6 +202,18 @@ export function buildCohortData() {
tree: utxoCohorts.class[key],
}));
const entryColors = {
discount: colors.arr[11],
premium: colors.arr[0],
};
const entry = entries(ENTRY_NAMES).map(([key, names]) => ({
name: names.short,
title: `UTXOs ${names.long}`,
color: entryColors[key],
tree: utxoCohorts.entry[key],
}));
const { range, profit, loss } = utxoCohorts.profitability;
const profitabilityRange = entries(PROFITABILITY_RANGE_NAMES).map(
@@ -242,6 +255,7 @@ export function buildCohortData() {
typeAddressable,
typeOther,
class: class_,
entry,
profitabilityRange,
profitabilityProfit,
profitabilityLoss,
+1 -1
View File
@@ -226,7 +226,7 @@ function historicalSubSection(name, periods) {
* @returns {PartialOptionsGroup}
*/
export function createMarketSection() {
const { market, supply, cohorts, prices, indicators } = brk.series;
const { market, supply, cohorts, price: prices, indicators } = brk.series;
const {
movingAverage: ma,
ath,
+15
View File
@@ -5,6 +5,7 @@ import {
createCohortFolderAll,
createCohortFolderFull,
createCohortFolderWithAdjusted,
createCohortFolderWithNupl,
createCohortFolderLongTerm,
createCohortFolderAgeRangeWithMatured,
createCohortFolderBasicWithMarketCap,
@@ -53,6 +54,7 @@ export function createPartialOptions() {
overAge,
ageRange,
epoch,
entry,
utxosOverAmount,
addressesOverAmount,
utxosUnderAmount,
@@ -99,6 +101,19 @@ export function createPartialOptions() {
createCohortFolderLongTerm(termLong),
{
name: "Entry",
tree: [
createGroupedCohortFolderWithNupl({
name: "Compare",
title: "Veteran vs Rookie",
list: entry,
all: cohortAll,
}),
...entry.map(createCohortFolderWithNupl),
],
},
{
name: "UTXO Age",
tree: [
+1 -1
View File
@@ -40,7 +40,7 @@ export function init() {
/** @type {Map<Unit, AnyFetchedSeriesBlueprint[]>} */
const result = new Map();
const { ohlc, spot } = brk.series.prices;
const { ohlc, spot } = brk.series.price;
result.set(Unit.usd, [
/** @type {AnyFetchedSeriesBlueprint} */ ({