From 18bb4186a82bc485029374a284cfaef348b420d1 Mon Sep 17 00:00:00 2001 From: nym21 Date: Thu, 26 Mar 2026 15:57:22 +0100 Subject: [PATCH] global: snapshot --- Cargo.lock | 38 +- Cargo.toml | 4 +- crates/brk_cli/Cargo.toml | 2 +- crates/brk_client/src/lib.rs | 528 ++++++++++-------- crates/brk_computer/src/blocks/compute.rs | 17 +- crates/brk_computer/src/blocks/import.rs | 4 +- crates/brk_computer/src/blocks/lookback.rs | 109 ++-- crates/brk_computer/src/blocks/mod.rs | 5 +- .../brk_computer/src/blocks/time/compute.rs | 34 -- crates/brk_computer/src/blocks/time/import.rs | 66 --- crates/brk_computer/src/blocks/time/mod.rs | 5 - crates/brk_computer/src/blocks/time/vecs.rs | 80 --- crates/brk_computer/src/cointime/compute.rs | 9 +- crates/brk_computer/src/distribution/vecs.rs | 17 +- crates/brk_computer/src/indexes/mod.rs | 85 +-- crates/brk_computer/src/indexes/timestamp.rs | 150 +++++ crates/brk_computer/src/indicators/compute.rs | 9 +- crates/brk_computer/src/indicators/import.rs | 5 +- crates/brk_computer/src/indicators/mod.rs | 1 + .../src/indicators/thermometer.rs | 254 +++++++++ crates/brk_computer/src/indicators/vecs.rs | 2 + crates/brk_computer/src/inputs/compute.rs | 9 +- .../internal/algo/expanding_percentiles.rs | 14 +- .../internal/per_block/ratio/percentiles.rs | 18 +- .../src/{market/dca => investing}/by_class.rs | 0 .../{market/dca => investing}/by_period.rs | 22 + .../src/{market/dca => investing}/compute.rs | 48 +- .../src/{market/dca => investing}/import.rs | 42 +- .../src/{market/dca => investing}/mod.rs | 2 + .../src/{market/dca => investing}/vecs.rs | 4 +- crates/brk_computer/src/lib.rs | 47 +- crates/brk_computer/src/market/ath/compute.rs | 6 +- crates/brk_computer/src/market/compute.rs | 33 +- crates/brk_computer/src/market/import.rs | 4 +- .../src/market/lookback/by_period.rs | 20 - crates/brk_computer/src/market/mod.rs | 3 - .../src/market/returns/compute.rs | 4 +- .../brk_computer/src/market/returns/import.rs | 2 +- .../brk_computer/src/market/returns/vecs.rs | 3 +- crates/brk_computer/src/mining/compute.rs | 9 +- crates/brk_computer/src/outputs/compute.rs | 9 +- .../brk_computer/src/outputs/spent/compute.rs | 6 +- crates/brk_computer/src/pools/mod.rs | 9 +- crates/brk_computer/src/positions.rs | 9 +- crates/brk_computer/src/prices/compute.rs | 9 +- crates/brk_computer/src/prices/mod.rs | 5 +- crates/brk_computer/src/scripts/compute.rs | 9 +- crates/brk_computer/src/supply/compute.rs | 9 +- .../brk_computer/src/transactions/compute.rs | 9 +- crates/brk_indexer/src/lib.rs | 32 +- crates/brk_indexer/src/stores.rs | 46 +- crates/brk_indexer/src/vecs/mod.rs | 14 +- crates/brk_query/src/impl/mining/day1_iter.rs | 2 +- .../brk_query/src/impl/mining/difficulty.rs | 3 +- crates/brk_query/src/impl/mining/epochs.rs | 2 +- crates/brk_query/src/impl/mining/hashrate.rs | 2 +- crates/brk_query/src/impl/series.rs | 2 +- crates/brk_store/src/lib.rs | 28 + modules/brk-client/index.js | 436 ++++++++------- .../{0.4.0 => 0.4.1}/src/index.js | 1 + packages/brk_client/brk_client/__init__.py | 254 +++++---- website/scripts/chart/index.js | 179 +++++- website/scripts/options/investing.js | 74 +-- website/scripts/options/market.js | 47 +- website/scripts/options/series.js | 6 + website/scripts/options/shared.js | 132 ++++- website/scripts/options/types.js | 2 + website/scripts/panes/search.js | 2 +- website/scripts/types.js | 4 +- website/scripts/utils/colors.js | 14 +- website/styles/chart.css | 78 ++- website/styles/variables.css | 15 +- 72 files changed, 2013 insertions(+), 1150 deletions(-) delete mode 100644 crates/brk_computer/src/blocks/time/compute.rs delete mode 100644 crates/brk_computer/src/blocks/time/import.rs delete mode 100644 crates/brk_computer/src/blocks/time/mod.rs delete mode 100644 crates/brk_computer/src/blocks/time/vecs.rs create mode 100644 crates/brk_computer/src/indexes/timestamp.rs create mode 100644 crates/brk_computer/src/indicators/thermometer.rs rename crates/brk_computer/src/{market/dca => investing}/by_class.rs (100%) rename crates/brk_computer/src/{market/dca => investing}/by_period.rs (90%) rename crates/brk_computer/src/{market/dca => investing}/compute.rs (87%) rename crates/brk_computer/src/{market/dca => investing}/import.rs (53%) rename crates/brk_computer/src/{market/dca => investing}/mod.rs (76%) rename crates/brk_computer/src/{market/dca => investing}/vecs.rs (90%) rename modules/quickmatch-js/{0.4.0 => 0.4.1}/src/index.js (99%) diff --git a/Cargo.lock b/Cargo.lock index 38ed81635..42d8cdfba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2045,9 +2045,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "libc", ] @@ -2201,9 +2201,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-traits" @@ -2546,8 +2546,6 @@ dependencies = [ [[package]] name = "rawdb" version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912a9c6f76a5f141057139d510b969b082ff74f39a72a1c27178d8e1eeec95dc" dependencies = [ "libc", "log", @@ -2916,9 +2914,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -3182,9 +3180,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" +checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" dependencies = [ "indexmap", "serde_core", @@ -3197,27 +3195,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "tower" @@ -3352,9 +3350,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" [[package]] name = "unicode-xid" @@ -3440,8 +3438,6 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" [[package]] name = "vecdb" version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f89be182f86511ee28832cc04039d818564b88c63b14093fcf1871c732c0dd" dependencies = [ "itoa", "libc", @@ -3463,8 +3459,6 @@ dependencies = [ [[package]] name = "vecdb_derive" version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42915a5ca404d941e3e6d024f794403c481e5aeb96b1867d12d1f1e972d1433f" dependencies = [ "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index 513363ee1..947038c13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,8 +87,8 @@ tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", " tower-layer = "0.3" tracing = { version = "0.1", default-features = false, features = ["std"] } ureq = { version = "3.3.0", features = ["json"] } -vecdb = { version = "0.7.2", features = ["derive", "serde_json", "pco", "schemars"] } -# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } +# vecdb = { version = "0.7.2", features = ["derive", "serde_json", "pco", "schemars"] } +vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } [workspace.metadata.release] shared-version = true diff --git a/crates/brk_cli/Cargo.toml b/crates/brk_cli/Cargo.toml index 8b0db03fc..3868f873c 100644 --- a/crates/brk_cli/Cargo.toml +++ b/crates/brk_cli/Cargo.toml @@ -26,7 +26,7 @@ owo-colors = { workspace = true } tracing = { workspace = true } serde = { workspace = true } tokio = { workspace = true } -toml = "1.0.7" +toml = "1.1.0" vecdb = { workspace = true } [[bin]] diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 413df287a..2c352260e 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1247,7 +1247,7 @@ impl GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 { pub struct BpsCentsPercentilesRatioSatsSmaStdUsdPattern { pub bps: SeriesPattern1, pub cents: SeriesPattern1, - pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern, + pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern, pub ratio: SeriesPattern1, pub sats: SeriesPattern1, pub sma: _1m1w1y2y4yAllPattern, @@ -1255,6 +1255,34 @@ pub struct BpsCentsPercentilesRatioSatsSmaStdUsdPattern { pub usd: SeriesPattern1, } +/// Pattern struct for repeated tree structure. +pub struct Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern { + pub pct0_5: BpsPriceRatioPattern, + pub pct1: BpsPriceRatioPattern, + pub pct2: BpsPriceRatioPattern, + pub pct5: BpsPriceRatioPattern, + pub pct95: BpsPriceRatioPattern, + pub pct98: BpsPriceRatioPattern, + pub pct99: BpsPriceRatioPattern, + pub pct99_5: BpsPriceRatioPattern, +} + +impl Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + pct0_5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct0_5".to_string()), + pct1: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct1".to_string()), + pct2: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct2".to_string()), + pct5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct5".to_string()), + pct95: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct95".to_string()), + pct98: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct98".to_string()), + pct99: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99".to_string()), + pct99_5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99_5".to_string()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct _10y2y3y4y5y6y8yPattern { pub _10y: BpsPercentRatioPattern, @@ -1487,7 +1515,7 @@ impl AverageBlockCumulativeInSumPattern { pub struct BpsCentsPercentilesRatioSatsUsdPattern { pub bps: SeriesPattern1, pub cents: SeriesPattern1, - pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern, + pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern, pub ratio: SeriesPattern1, pub sats: SeriesPattern1, pub usd: SeriesPattern1, @@ -1499,7 +1527,7 @@ impl BpsCentsPercentilesRatioSatsUsdPattern { Self { bps: SeriesPattern1::new(client.clone(), _m(&acc, "ratio_bps")), cents: SeriesPattern1::new(client.clone(), _m(&acc, "cents")), - percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), acc.clone()), + percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), acc.clone()), ratio: SeriesPattern1::new(client.clone(), _m(&acc, "ratio")), sats: SeriesPattern1::new(client.clone(), _m(&acc, "sats")), usd: SeriesPattern1::new(client.clone(), acc.clone()), @@ -1603,30 +1631,6 @@ impl DeltaHalfInToTotalPattern2 { } } -/// Pattern struct for repeated tree structure. -pub struct Pct1Pct2Pct5Pct95Pct98Pct99Pattern { - pub pct1: BpsPriceRatioPattern, - pub pct2: BpsPriceRatioPattern, - pub pct5: BpsPriceRatioPattern, - pub pct95: BpsPriceRatioPattern, - pub pct98: BpsPriceRatioPattern, - pub pct99: BpsPriceRatioPattern, -} - -impl Pct1Pct2Pct5Pct95Pct98Pct99Pattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - pct1: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct1".to_string()), - pct2: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct2".to_string()), - pct5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct5".to_string()), - pct95: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct95".to_string()), - pct98: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct98".to_string()), - pct99: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99".to_string()), - } - } -} - /// Pattern struct for repeated tree structure. pub struct _1m1w1y24hBlockPattern { pub _1m: SeriesPattern1, @@ -3126,6 +3130,7 @@ pub struct SeriesTree { pub constants: SeriesTree_Constants, pub indexes: SeriesTree_Indexes, pub indicators: SeriesTree_Indicators, + pub investing: SeriesTree_Investing, pub market: SeriesTree_Market, pub pools: SeriesTree_Pools, pub prices: SeriesTree_Prices, @@ -3148,6 +3153,7 @@ impl SeriesTree { constants: SeriesTree_Constants::new(client.clone(), format!("{base_path}_constants")), indexes: SeriesTree_Indexes::new(client.clone(), format!("{base_path}_indexes")), indicators: SeriesTree_Indicators::new(client.clone(), format!("{base_path}_indicators")), + investing: SeriesTree_Investing::new(client.clone(), format!("{base_path}_investing")), market: SeriesTree_Market::new(client.clone(), format!("{base_path}_market")), pools: SeriesTree_Pools::new(client.clone(), format!("{base_path}_pools")), prices: SeriesTree_Prices::new(client.clone(), format!("{base_path}_prices")), @@ -3215,17 +3221,13 @@ impl SeriesTree_Blocks_Difficulty { /// Series tree node. pub struct SeriesTree_Blocks_Time { - pub timestamp: SeriesPattern1, - pub date: SeriesPattern18, - pub timestamp_monotonic: SeriesPattern18, + pub timestamp: SeriesPattern18, } impl SeriesTree_Blocks_Time { pub fn new(client: Arc, base_path: String) -> Self { Self { - timestamp: SeriesPattern1::new(client.clone(), "timestamp".to_string()), - date: SeriesPattern18::new(client.clone(), "date".to_string()), - timestamp_monotonic: SeriesPattern18::new(client.clone(), "timestamp_monotonic".to_string()), + timestamp: SeriesPattern18::new(client.clone(), "timestamp".to_string()), } } } @@ -4562,6 +4564,7 @@ pub struct SeriesTree_Indexes { pub tx_index: SeriesTree_Indexes_TxIndex, pub txin_index: SeriesTree_Indexes_TxinIndex, pub txout_index: SeriesTree_Indexes_TxoutIndex, + pub timestamp: SeriesTree_Indexes_Timestamp, } impl SeriesTree_Indexes { @@ -4587,6 +4590,7 @@ impl SeriesTree_Indexes { tx_index: SeriesTree_Indexes_TxIndex::new(client.clone(), format!("{base_path}_tx_index")), txin_index: SeriesTree_Indexes_TxinIndex::new(client.clone(), format!("{base_path}_txin_index")), txout_index: SeriesTree_Indexes_TxoutIndex::new(client.clone(), format!("{base_path}_txout_index")), + timestamp: SeriesTree_Indexes_Timestamp::new(client.clone(), format!("{base_path}_timestamp")), } } } @@ -5129,6 +5133,21 @@ impl SeriesTree_Indexes_TxoutIndex { } } +/// Series tree node. +pub struct SeriesTree_Indexes_Timestamp { + pub monotonic: SeriesPattern18, + pub resolutions: SeriesPattern2, +} + +impl SeriesTree_Indexes_Timestamp { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + monotonic: SeriesPattern18::new(client.clone(), "timestamp_monotonic".to_string()), + resolutions: SeriesPattern2::new(client.clone(), "timestamp".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Indicators { pub puell_multiple: BpsRatioPattern2, @@ -5141,6 +5160,7 @@ pub struct SeriesTree_Indicators { pub dormancy: SeriesTree_Indicators_Dormancy, pub stock_to_flow: SeriesPattern1, pub seller_exhaustion: SeriesPattern1, + pub thermometer: SeriesTree_Indicators_Thermometer, } impl SeriesTree_Indicators { @@ -5156,6 +5176,7 @@ impl SeriesTree_Indicators { dormancy: SeriesTree_Indicators_Dormancy::new(client.clone(), format!("{base_path}_dormancy")), stock_to_flow: SeriesPattern1::new(client.clone(), "stock_to_flow".to_string()), seller_exhaustion: SeriesPattern1::new(client.clone(), "seller_exhaustion".to_string()), + thermometer: SeriesTree_Indicators_Thermometer::new(client.clone(), format!("{base_path}_thermometer")), } } } @@ -5175,6 +5196,234 @@ impl SeriesTree_Indicators_Dormancy { } } +/// Series tree node. +pub struct SeriesTree_Indicators_Thermometer { + pub pct0_5: CentsSatsUsdPattern, + pub pct1: CentsSatsUsdPattern, + pub pct2: CentsSatsUsdPattern, + pub pct5: CentsSatsUsdPattern, + pub pct95: CentsSatsUsdPattern, + pub pct98: CentsSatsUsdPattern, + pub pct99: CentsSatsUsdPattern, + pub pct99_5: CentsSatsUsdPattern, + pub zone: SeriesPattern1, + pub score: SeriesPattern1, +} + +impl SeriesTree_Indicators_Thermometer { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + pct0_5: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct0_5".to_string()), + pct1: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct01".to_string()), + pct2: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct02".to_string()), + pct5: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct05".to_string()), + pct95: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct95".to_string()), + pct98: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct98".to_string()), + pct99: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct99".to_string()), + pct99_5: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct99_5".to_string()), + zone: SeriesPattern1::new(client.clone(), "thermometer_zone".to_string()), + score: SeriesPattern1::new(client.clone(), "thermometer_score".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing { + pub sats_per_day: SeriesPattern18, + pub period: SeriesTree_Investing_Period, + pub class: SeriesTree_Investing_Class, +} + +impl SeriesTree_Investing { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + sats_per_day: SeriesPattern18::new(client.clone(), "dca_sats_per_day".to_string()), + period: SeriesTree_Investing_Period::new(client.clone(), format!("{base_path}_period")), + class: SeriesTree_Investing_Class::new(client.clone(), format!("{base_path}_class")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing_Period { + pub stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3, + pub cost_basis: SeriesTree_Investing_Period_CostBasis, + pub return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2, + pub cagr: _10y2y3y4y5y6y8yPattern, + pub lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3, + pub lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2, +} + +impl SeriesTree_Investing_Period { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "dca_stack".to_string()), + cost_basis: SeriesTree_Investing_Period_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")), + return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_return".to_string()), + cagr: _10y2y3y4y5y6y8yPattern::new(client.clone(), "dca_cagr".to_string()), + lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "lump_sum_stack".to_string()), + lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_return".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing_Period_CostBasis { + pub _1w: CentsSatsUsdPattern, + pub _1m: CentsSatsUsdPattern, + pub _3m: CentsSatsUsdPattern, + pub _6m: CentsSatsUsdPattern, + pub _1y: CentsSatsUsdPattern, + pub _2y: CentsSatsUsdPattern, + pub _3y: CentsSatsUsdPattern, + pub _4y: CentsSatsUsdPattern, + pub _5y: CentsSatsUsdPattern, + pub _6y: CentsSatsUsdPattern, + pub _8y: CentsSatsUsdPattern, + pub _10y: CentsSatsUsdPattern, +} + +impl SeriesTree_Investing_Period_CostBasis { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + _1w: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1w".to_string()), + _1m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1m".to_string()), + _3m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3m".to_string()), + _6m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6m".to_string()), + _1y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1y".to_string()), + _2y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_2y".to_string()), + _3y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3y".to_string()), + _4y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_4y".to_string()), + _5y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_5y".to_string()), + _6y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6y".to_string()), + _8y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_8y".to_string()), + _10y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_10y".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing_Class { + pub stack: SeriesTree_Investing_Class_Stack, + pub cost_basis: SeriesTree_Investing_Class_CostBasis, + pub return_: SeriesTree_Investing_Class_Return, +} + +impl SeriesTree_Investing_Class { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + stack: SeriesTree_Investing_Class_Stack::new(client.clone(), format!("{base_path}_stack")), + cost_basis: SeriesTree_Investing_Class_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")), + return_: SeriesTree_Investing_Class_Return::new(client.clone(), format!("{base_path}_return")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing_Class_Stack { + pub from_2015: BtcCentsSatsUsdPattern3, + pub from_2016: BtcCentsSatsUsdPattern3, + pub from_2017: BtcCentsSatsUsdPattern3, + pub from_2018: BtcCentsSatsUsdPattern3, + pub from_2019: BtcCentsSatsUsdPattern3, + pub from_2020: BtcCentsSatsUsdPattern3, + pub from_2021: BtcCentsSatsUsdPattern3, + pub from_2022: BtcCentsSatsUsdPattern3, + pub from_2023: BtcCentsSatsUsdPattern3, + pub from_2024: BtcCentsSatsUsdPattern3, + pub from_2025: BtcCentsSatsUsdPattern3, + pub from_2026: BtcCentsSatsUsdPattern3, +} + +impl SeriesTree_Investing_Class_Stack { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + from_2015: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2015".to_string()), + from_2016: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2016".to_string()), + from_2017: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2017".to_string()), + from_2018: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2018".to_string()), + from_2019: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2019".to_string()), + from_2020: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2020".to_string()), + from_2021: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2021".to_string()), + from_2022: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2022".to_string()), + from_2023: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2023".to_string()), + from_2024: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2024".to_string()), + from_2025: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2025".to_string()), + from_2026: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2026".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing_Class_CostBasis { + pub from_2015: CentsSatsUsdPattern, + pub from_2016: CentsSatsUsdPattern, + pub from_2017: CentsSatsUsdPattern, + pub from_2018: CentsSatsUsdPattern, + pub from_2019: CentsSatsUsdPattern, + pub from_2020: CentsSatsUsdPattern, + pub from_2021: CentsSatsUsdPattern, + pub from_2022: CentsSatsUsdPattern, + pub from_2023: CentsSatsUsdPattern, + pub from_2024: CentsSatsUsdPattern, + pub from_2025: CentsSatsUsdPattern, + pub from_2026: CentsSatsUsdPattern, +} + +impl SeriesTree_Investing_Class_CostBasis { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + from_2015: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2015".to_string()), + from_2016: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2016".to_string()), + from_2017: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2017".to_string()), + from_2018: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2018".to_string()), + from_2019: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2019".to_string()), + from_2020: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2020".to_string()), + from_2021: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2021".to_string()), + from_2022: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2022".to_string()), + from_2023: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2023".to_string()), + from_2024: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2024".to_string()), + from_2025: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2025".to_string()), + from_2026: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2026".to_string()), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Investing_Class_Return { + pub from_2015: BpsPercentRatioPattern, + pub from_2016: BpsPercentRatioPattern, + pub from_2017: BpsPercentRatioPattern, + pub from_2018: BpsPercentRatioPattern, + pub from_2019: BpsPercentRatioPattern, + pub from_2020: BpsPercentRatioPattern, + pub from_2021: BpsPercentRatioPattern, + pub from_2022: BpsPercentRatioPattern, + pub from_2023: BpsPercentRatioPattern, + pub from_2024: BpsPercentRatioPattern, + pub from_2025: BpsPercentRatioPattern, + pub from_2026: BpsPercentRatioPattern, +} + +impl SeriesTree_Investing_Class_Return { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + from_2015: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2015".to_string()), + from_2016: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2016".to_string()), + from_2017: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2017".to_string()), + from_2018: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2018".to_string()), + from_2019: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2019".to_string()), + from_2020: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2020".to_string()), + from_2021: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2021".to_string()), + from_2022: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2022".to_string()), + from_2023: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2023".to_string()), + from_2024: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2024".to_string()), + from_2025: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2025".to_string()), + from_2026: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2026".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Market { pub ath: SeriesTree_Market_Ath, @@ -5183,7 +5432,6 @@ pub struct SeriesTree_Market { pub volatility: _1m1w1y24hPattern, pub range: SeriesTree_Market_Range, pub moving_average: SeriesTree_Market_MovingAverage, - pub dca: SeriesTree_Market_Dca, pub technical: SeriesTree_Market_Technical, } @@ -5196,7 +5444,6 @@ impl SeriesTree_Market { volatility: _1m1w1y24hPattern::new(client.clone(), "price_volatility".to_string()), range: SeriesTree_Market_Range::new(client.clone(), format!("{base_path}_range")), moving_average: SeriesTree_Market_MovingAverage::new(client.clone(), format!("{base_path}_moving_average")), - dca: SeriesTree_Market_Dca::new(client.clone(), format!("{base_path}_dca")), technical: SeriesTree_Market_Technical::new(client.clone(), format!("{base_path}_technical")), } } @@ -5565,203 +5812,6 @@ impl SeriesTree_Market_MovingAverage_Ema { } } -/// Series tree node. -pub struct SeriesTree_Market_Dca { - pub sats_per_day: SeriesPattern18, - pub period: SeriesTree_Market_Dca_Period, - pub class: SeriesTree_Market_Dca_Class, -} - -impl SeriesTree_Market_Dca { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - sats_per_day: SeriesPattern18::new(client.clone(), "dca_sats_per_day".to_string()), - period: SeriesTree_Market_Dca_Period::new(client.clone(), format!("{base_path}_period")), - class: SeriesTree_Market_Dca_Class::new(client.clone(), format!("{base_path}_class")), - } - } -} - -/// Series tree node. -pub struct SeriesTree_Market_Dca_Period { - pub stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3, - pub cost_basis: SeriesTree_Market_Dca_Period_CostBasis, - pub return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2, - pub cagr: _10y2y3y4y5y6y8yPattern, - pub lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3, - pub lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2, -} - -impl SeriesTree_Market_Dca_Period { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "dca_stack".to_string()), - cost_basis: SeriesTree_Market_Dca_Period_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")), - return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_return".to_string()), - cagr: _10y2y3y4y5y6y8yPattern::new(client.clone(), "dca_cagr".to_string()), - lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "lump_sum_stack".to_string()), - lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_return".to_string()), - } - } -} - -/// Series tree node. -pub struct SeriesTree_Market_Dca_Period_CostBasis { - pub _1w: CentsSatsUsdPattern, - pub _1m: CentsSatsUsdPattern, - pub _3m: CentsSatsUsdPattern, - pub _6m: CentsSatsUsdPattern, - pub _1y: CentsSatsUsdPattern, - pub _2y: CentsSatsUsdPattern, - pub _3y: CentsSatsUsdPattern, - pub _4y: CentsSatsUsdPattern, - pub _5y: CentsSatsUsdPattern, - pub _6y: CentsSatsUsdPattern, - pub _8y: CentsSatsUsdPattern, - pub _10y: CentsSatsUsdPattern, -} - -impl SeriesTree_Market_Dca_Period_CostBasis { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - _1w: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1w".to_string()), - _1m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1m".to_string()), - _3m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3m".to_string()), - _6m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6m".to_string()), - _1y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1y".to_string()), - _2y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_2y".to_string()), - _3y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3y".to_string()), - _4y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_4y".to_string()), - _5y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_5y".to_string()), - _6y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6y".to_string()), - _8y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_8y".to_string()), - _10y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_10y".to_string()), - } - } -} - -/// Series tree node. -pub struct SeriesTree_Market_Dca_Class { - pub stack: SeriesTree_Market_Dca_Class_Stack, - pub cost_basis: SeriesTree_Market_Dca_Class_CostBasis, - pub return_: SeriesTree_Market_Dca_Class_Return, -} - -impl SeriesTree_Market_Dca_Class { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - stack: SeriesTree_Market_Dca_Class_Stack::new(client.clone(), format!("{base_path}_stack")), - cost_basis: SeriesTree_Market_Dca_Class_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")), - return_: SeriesTree_Market_Dca_Class_Return::new(client.clone(), format!("{base_path}_return")), - } - } -} - -/// Series tree node. -pub struct SeriesTree_Market_Dca_Class_Stack { - pub from_2015: BtcCentsSatsUsdPattern3, - pub from_2016: BtcCentsSatsUsdPattern3, - pub from_2017: BtcCentsSatsUsdPattern3, - pub from_2018: BtcCentsSatsUsdPattern3, - pub from_2019: BtcCentsSatsUsdPattern3, - pub from_2020: BtcCentsSatsUsdPattern3, - pub from_2021: BtcCentsSatsUsdPattern3, - pub from_2022: BtcCentsSatsUsdPattern3, - pub from_2023: BtcCentsSatsUsdPattern3, - pub from_2024: BtcCentsSatsUsdPattern3, - pub from_2025: BtcCentsSatsUsdPattern3, - pub from_2026: BtcCentsSatsUsdPattern3, -} - -impl SeriesTree_Market_Dca_Class_Stack { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - from_2015: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2015".to_string()), - from_2016: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2016".to_string()), - from_2017: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2017".to_string()), - from_2018: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2018".to_string()), - from_2019: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2019".to_string()), - from_2020: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2020".to_string()), - from_2021: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2021".to_string()), - from_2022: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2022".to_string()), - from_2023: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2023".to_string()), - from_2024: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2024".to_string()), - from_2025: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2025".to_string()), - from_2026: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2026".to_string()), - } - } -} - -/// Series tree node. -pub struct SeriesTree_Market_Dca_Class_CostBasis { - pub from_2015: CentsSatsUsdPattern, - pub from_2016: CentsSatsUsdPattern, - pub from_2017: CentsSatsUsdPattern, - pub from_2018: CentsSatsUsdPattern, - pub from_2019: CentsSatsUsdPattern, - pub from_2020: CentsSatsUsdPattern, - pub from_2021: CentsSatsUsdPattern, - pub from_2022: CentsSatsUsdPattern, - pub from_2023: CentsSatsUsdPattern, - pub from_2024: CentsSatsUsdPattern, - pub from_2025: CentsSatsUsdPattern, - pub from_2026: CentsSatsUsdPattern, -} - -impl SeriesTree_Market_Dca_Class_CostBasis { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - from_2015: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2015".to_string()), - from_2016: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2016".to_string()), - from_2017: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2017".to_string()), - from_2018: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2018".to_string()), - from_2019: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2019".to_string()), - from_2020: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2020".to_string()), - from_2021: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2021".to_string()), - from_2022: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2022".to_string()), - from_2023: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2023".to_string()), - from_2024: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2024".to_string()), - from_2025: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2025".to_string()), - from_2026: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2026".to_string()), - } - } -} - -/// Series tree node. -pub struct SeriesTree_Market_Dca_Class_Return { - pub from_2015: BpsPercentRatioPattern, - pub from_2016: BpsPercentRatioPattern, - pub from_2017: BpsPercentRatioPattern, - pub from_2018: BpsPercentRatioPattern, - pub from_2019: BpsPercentRatioPattern, - pub from_2020: BpsPercentRatioPattern, - pub from_2021: BpsPercentRatioPattern, - pub from_2022: BpsPercentRatioPattern, - pub from_2023: BpsPercentRatioPattern, - pub from_2024: BpsPercentRatioPattern, - pub from_2025: BpsPercentRatioPattern, - pub from_2026: BpsPercentRatioPattern, -} - -impl SeriesTree_Market_Dca_Class_Return { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - from_2015: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2015".to_string()), - from_2016: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2016".to_string()), - from_2017: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2017".to_string()), - from_2018: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2018".to_string()), - from_2019: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2019".to_string()), - from_2020: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2020".to_string()), - from_2021: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2021".to_string()), - from_2022: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2022".to_string()), - from_2023: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2023".to_string()), - from_2024: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2024".to_string()), - from_2025: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2025".to_string()), - from_2026: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2026".to_string()), - } - } -} - /// Series tree node. pub struct SeriesTree_Market_Technical { pub rsi: SeriesTree_Market_Technical_Rsi, @@ -6527,7 +6577,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized_Price { pub sats: SeriesPattern1, pub bps: SeriesPattern1, pub ratio: SeriesPattern1, - pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern, + pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern, pub sma: _1m1w1y2y4yAllPattern, pub std_dev: SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev, } @@ -6540,7 +6590,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized_Price { sats: SeriesPattern1::new(client.clone(), "realized_price_sats".to_string()), bps: SeriesPattern1::new(client.clone(), "realized_price_ratio_bps".to_string()), ratio: SeriesPattern1::new(client.clone(), "realized_price_ratio".to_string()), - percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "realized_price".to_string()), + percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "realized_price".to_string()), sma: _1m1w1y2y4yAllPattern::new(client.clone(), "realized_price_ratio_sma".to_string()), std_dev: SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")), } @@ -6957,7 +7007,7 @@ pub struct SeriesTree_Cohorts_Utxo_Sth_Realized_Price { pub sats: SeriesPattern1, pub bps: SeriesPattern1, pub ratio: SeriesPattern1, - pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern, + pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern, pub sma: _1m1w1y2y4yAllPattern, pub std_dev: SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev, } @@ -6970,7 +7020,7 @@ impl SeriesTree_Cohorts_Utxo_Sth_Realized_Price { sats: SeriesPattern1::new(client.clone(), "sth_realized_price_sats".to_string()), bps: SeriesPattern1::new(client.clone(), "sth_realized_price_ratio_bps".to_string()), ratio: SeriesPattern1::new(client.clone(), "sth_realized_price_ratio".to_string()), - percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "sth_realized_price".to_string()), + percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "sth_realized_price".to_string()), sma: _1m1w1y2y4yAllPattern::new(client.clone(), "sth_realized_price_ratio_sma".to_string()), std_dev: SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")), } @@ -7225,7 +7275,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Price { pub sats: SeriesPattern1, pub bps: SeriesPattern1, pub ratio: SeriesPattern1, - pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern, + pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern, pub sma: _1m1w1y2y4yAllPattern, pub std_dev: SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev, } @@ -7238,7 +7288,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized_Price { sats: SeriesPattern1::new(client.clone(), "lth_realized_price_sats".to_string()), bps: SeriesPattern1::new(client.clone(), "lth_realized_price_ratio_bps".to_string()), ratio: SeriesPattern1::new(client.clone(), "lth_realized_price_ratio".to_string()), - percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "lth_realized_price".to_string()), + percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "lth_realized_price".to_string()), sma: _1m1w1y2y4yAllPattern::new(client.clone(), "lth_realized_price_ratio_sma".to_string()), std_dev: SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")), } @@ -8145,7 +8195,7 @@ pub struct BrkClient { impl BrkClient { /// Client version. - pub const VERSION: &'static str = "v0.1.9"; + pub const VERSION: &'static str = "v0.2.2"; /// Create a new client with the given base URL. pub fn new(base_url: impl Into) -> Self { diff --git a/crates/brk_computer/src/blocks/compute.rs b/crates/brk_computer/src/blocks/compute.rs index 6a0b411d4..b0621db77 100644 --- a/crates/brk_computer/src/blocks/compute.rs +++ b/crates/brk_computer/src/blocks/compute.rs @@ -17,12 +17,10 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - // Sequential: time → lookback (dependency chain) - self.time - .timestamp - .compute(indexer, indexes, starting_indexes, exit)?; - self.lookback - .compute(&self.time, starting_indexes, exit)?; + self.db.sync_bg_tasks()?; + + // lookback depends on indexes.timestamp.monotonic + self.lookback.compute(indexes, starting_indexes, exit)?; // Parallel: remaining sub-modules are independent of each other. // size depends on lookback (already computed above). @@ -52,8 +50,11 @@ impl Vecs { Ok(()) })?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/blocks/import.rs b/crates/brk_computer/src/blocks/import.rs index 004859ffa..7cc84a49b 100644 --- a/crates/brk_computer/src/blocks/import.rs +++ b/crates/brk_computer/src/blocks/import.rs @@ -10,7 +10,7 @@ use crate::{ }; use super::{ - CountVecs, DifficultyVecs, HalvingVecs, IntervalVecs, LookbackVecs, SizeVecs, TimeVecs, Vecs, + CountVecs, DifficultyVecs, HalvingVecs, IntervalVecs, LookbackVecs, SizeVecs, Vecs, WeightVecs, }; @@ -30,7 +30,6 @@ impl Vecs { let interval = IntervalVecs::forced_import(&db, version, indexes, cached_starts)?; let size = SizeVecs::forced_import(&db, version, indexes, cached_starts)?; let weight = WeightVecs::forced_import(&db, version, indexes, cached_starts, &size)?; - let time = TimeVecs::forced_import(&db, version, indexes)?; let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?; let halving = HalvingVecs::forced_import(&db, version, indexes)?; @@ -41,7 +40,6 @@ impl Vecs { interval, size, weight, - time, difficulty, halving, }; diff --git a/crates/brk_computer/src/blocks/lookback.rs b/crates/brk_computer/src/blocks/lookback.rs index d4c20bc37..07a9eafa4 100644 --- a/crates/brk_computer/src/blocks/lookback.rs +++ b/crates/brk_computer/src/blocks/lookback.rs @@ -3,9 +3,10 @@ use brk_traversable::Traversable; use brk_types::{Height, Indexes, Timestamp, Version}; use vecdb::{AnyVec, CachedVec, Cursor, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode, VecIndex}; -use crate::internal::{CachedWindowStarts, Windows, WindowStarts}; - -use super::time; +use crate::{ + indexes, + internal::{CachedWindowStarts, Windows, WindowStarts}, +}; #[derive(Traversable)] pub struct Vecs { @@ -178,71 +179,71 @@ impl Vecs { pub(crate) fn compute( &mut self, - time: &time::Vecs, + indexes: &indexes::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.compute_rolling_start_hours(time, starting_indexes, exit, 1, |s| { + self.compute_rolling_start_hours(indexes, starting_indexes, exit, 1, |s| { &mut s._1h })?; - self.compute_rolling_start(time, starting_indexes, exit, 1, |s| &mut s._24h)?; - self.compute_rolling_start(time, starting_indexes, exit, 3, |s| &mut s._3d)?; - self.compute_rolling_start(time, starting_indexes, exit, 7, |s| &mut s._1w)?; - self.compute_rolling_start(time, starting_indexes, exit, 8, |s| &mut s._8d)?; - self.compute_rolling_start(time, starting_indexes, exit, 9, |s| &mut s._9d)?; - self.compute_rolling_start(time, starting_indexes, exit, 12, |s| &mut s._12d)?; - self.compute_rolling_start(time, starting_indexes, exit, 13, |s| &mut s._13d)?; - self.compute_rolling_start(time, starting_indexes, exit, 14, |s| &mut s._2w)?; - self.compute_rolling_start(time, starting_indexes, exit, 21, |s| &mut s._21d)?; - self.compute_rolling_start(time, starting_indexes, exit, 26, |s| &mut s._26d)?; - self.compute_rolling_start(time, starting_indexes, exit, 30, |s| &mut s._1m)?; - self.compute_rolling_start(time, starting_indexes, exit, 34, |s| &mut s._34d)?; - self.compute_rolling_start(time, starting_indexes, exit, 55, |s| &mut s._55d)?; - self.compute_rolling_start(time, starting_indexes, exit, 60, |s| &mut s._2m)?; - self.compute_rolling_start(time, starting_indexes, exit, 63, |s| &mut s._9w)?; - self.compute_rolling_start(time, starting_indexes, exit, 84, |s| &mut s._12w)?; - self.compute_rolling_start(time, starting_indexes, exit, 89, |s| &mut s._89d)?; - self.compute_rolling_start(time, starting_indexes, exit, 90, |s| &mut s._3m)?; - self.compute_rolling_start(time, starting_indexes, exit, 98, |s| &mut s._14w)?; - self.compute_rolling_start(time, starting_indexes, exit, 111, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 1, |s| &mut s._24h)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 3, |s| &mut s._3d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 7, |s| &mut s._1w)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 8, |s| &mut s._8d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 9, |s| &mut s._9d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 12, |s| &mut s._12d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 13, |s| &mut s._13d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 14, |s| &mut s._2w)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 21, |s| &mut s._21d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 26, |s| &mut s._26d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 30, |s| &mut s._1m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 34, |s| &mut s._34d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 55, |s| &mut s._55d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 60, |s| &mut s._2m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 63, |s| &mut s._9w)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 84, |s| &mut s._12w)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 89, |s| &mut s._89d)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 90, |s| &mut s._3m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 98, |s| &mut s._14w)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 111, |s| { &mut s._111d })?; - self.compute_rolling_start(time, starting_indexes, exit, 144, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 144, |s| { &mut s._144d })?; - self.compute_rolling_start(time, starting_indexes, exit, 180, |s| &mut s._6m)?; - self.compute_rolling_start(time, starting_indexes, exit, 182, |s| &mut s._26w)?; - self.compute_rolling_start(time, starting_indexes, exit, 200, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 180, |s| &mut s._6m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 182, |s| &mut s._26w)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 200, |s| { &mut s._200d })?; - self.compute_rolling_start(time, starting_indexes, exit, 270, |s| &mut s._9m)?; - self.compute_rolling_start(time, starting_indexes, exit, 350, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 270, |s| &mut s._9m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 350, |s| { &mut s._350d })?; - self.compute_rolling_start(time, starting_indexes, exit, 360, |s| &mut s._12m)?; - self.compute_rolling_start(time, starting_indexes, exit, 365, |s| &mut s._1y)?; - self.compute_rolling_start(time, starting_indexes, exit, 420, |s| &mut s._14m)?; - self.compute_rolling_start(time, starting_indexes, exit, 730, |s| &mut s._2y)?; - self.compute_rolling_start(time, starting_indexes, exit, 780, |s| &mut s._26m)?; - self.compute_rolling_start(time, starting_indexes, exit, 1095, |s| &mut s._3y)?; - self.compute_rolling_start(time, starting_indexes, exit, 1400, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 360, |s| &mut s._12m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 365, |s| &mut s._1y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 420, |s| &mut s._14m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 730, |s| &mut s._2y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 780, |s| &mut s._26m)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 1095, |s| &mut s._3y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 1400, |s| { &mut s._200w })?; - self.compute_rolling_start(time, starting_indexes, exit, 1460, |s| &mut s._4y)?; - self.compute_rolling_start(time, starting_indexes, exit, 1825, |s| &mut s._5y)?; - self.compute_rolling_start(time, starting_indexes, exit, 2190, |s| &mut s._6y)?; - self.compute_rolling_start(time, starting_indexes, exit, 2920, |s| &mut s._8y)?; - self.compute_rolling_start(time, starting_indexes, exit, 3285, |s| &mut s._9y)?; - self.compute_rolling_start(time, starting_indexes, exit, 3650, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 1460, |s| &mut s._4y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 1825, |s| &mut s._5y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 2190, |s| &mut s._6y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 2920, |s| &mut s._8y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 3285, |s| &mut s._9y)?; + self.compute_rolling_start(indexes, starting_indexes, exit, 3650, |s| { &mut s._10y })?; - self.compute_rolling_start(time, starting_indexes, exit, 4380, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 4380, |s| { &mut s._12y })?; - self.compute_rolling_start(time, starting_indexes, exit, 5110, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 5110, |s| { &mut s._14y })?; - self.compute_rolling_start(time, starting_indexes, exit, 9490, |s| { + self.compute_rolling_start(indexes, starting_indexes, exit, 9490, |s| { &mut s._26y })?; @@ -251,7 +252,7 @@ impl Vecs { fn compute_rolling_start( &mut self, - time: &time::Vecs, + indexes: &indexes::Vecs, starting_indexes: &Indexes, exit: &Exit, days: usize, @@ -260,14 +261,14 @@ impl Vecs { where F: FnOnce(&mut Self) -> &mut EagerVec>, { - self.compute_rolling_start_inner(time, starting_indexes, exit, get_field, |t, prev_ts| { + self.compute_rolling_start_inner(indexes, starting_indexes, exit, get_field, |t, prev_ts| { t.difference_in_days_between(prev_ts) >= days }) } fn compute_rolling_start_hours( &mut self, - time: &time::Vecs, + indexes: &indexes::Vecs, starting_indexes: &Indexes, exit: &Exit, hours: usize, @@ -276,14 +277,14 @@ impl Vecs { where F: FnOnce(&mut Self) -> &mut EagerVec>, { - self.compute_rolling_start_inner(time, starting_indexes, exit, get_field, |t, prev_ts| { + self.compute_rolling_start_inner(indexes, starting_indexes, exit, get_field, |t, prev_ts| { t.difference_in_hours_between(prev_ts) >= hours }) } fn compute_rolling_start_inner( &mut self, - time: &time::Vecs, + indexes: &indexes::Vecs, starting_indexes: &Indexes, exit: &Exit, get_field: F, @@ -300,12 +301,12 @@ impl Vecs { } else { Height::ZERO }; - let mut cursor = Cursor::new(&time.timestamp_monotonic); + let mut cursor = Cursor::new(&indexes.timestamp.monotonic); cursor.advance(prev.to_usize()); let mut prev_ts = cursor.next().unwrap(); Ok(field.compute_transform( starting_indexes.height, - &time.timestamp_monotonic, + &indexes.timestamp.monotonic, |(h, t, ..)| { while expired(t, prev_ts) { prev.increment(); diff --git a/crates/brk_computer/src/blocks/mod.rs b/crates/brk_computer/src/blocks/mod.rs index fc7607043..1e796b038 100644 --- a/crates/brk_computer/src/blocks/mod.rs +++ b/crates/brk_computer/src/blocks/mod.rs @@ -4,7 +4,6 @@ pub mod halving; pub mod interval; pub mod lookback; pub mod size; -pub mod time; pub mod weight; mod compute; @@ -19,7 +18,6 @@ pub use halving::Vecs as HalvingVecs; pub use interval::Vecs as IntervalVecs; pub use lookback::Vecs as LookbackVecs; pub use size::Vecs as SizeVecs; -pub use time::Vecs as TimeVecs; pub use weight::Vecs as WeightVecs; pub const DB_NAME: &str = "blocks"; @@ -37,7 +35,7 @@ pub(crate) const ONE_TERA_HASH: f64 = 1_000_000_000_000.0; #[derive(Traversable)] pub struct Vecs { #[traversable(skip)] - pub(crate) db: Database, + pub db: Database, pub count: CountVecs, pub lookback: LookbackVecs, @@ -46,7 +44,6 @@ pub struct Vecs { pub size: SizeVecs, #[traversable(flatten)] pub weight: WeightVecs, - pub time: TimeVecs, pub difficulty: DifficultyVecs, pub halving: HalvingVecs, } diff --git a/crates/brk_computer/src/blocks/time/compute.rs b/crates/brk_computer/src/blocks/time/compute.rs deleted file mode 100644 index 95b622d45..000000000 --- a/crates/brk_computer/src/blocks/time/compute.rs +++ /dev/null @@ -1,34 +0,0 @@ -use brk_error::Result; -use brk_indexer::Indexer; -use vecdb::{Exit, ReadableVec}; - -use super::Vecs; - -impl Vecs { - pub(crate) fn compute( - &mut self, - indexer: &Indexer, - starting_height: brk_types::Height, - exit: &Exit, - ) -> Result<()> { - let mut prev_timestamp_monotonic = None; - self.timestamp_monotonic.compute_transform( - starting_height, - &indexer.vecs.blocks.timestamp, - |(h, timestamp, this)| { - if prev_timestamp_monotonic.is_none() - && let Some(prev_h) = h.decremented() - { - prev_timestamp_monotonic.replace(this.collect_one(prev_h).unwrap()); - } - let timestamp_monotonic = - prev_timestamp_monotonic.map_or(timestamp, |prev_d| prev_d.max(timestamp)); - prev_timestamp_monotonic.replace(timestamp_monotonic); - (h, timestamp_monotonic) - }, - exit, - )?; - - Ok(()) - } -} diff --git a/crates/brk_computer/src/blocks/time/import.rs b/crates/brk_computer/src/blocks/time/import.rs deleted file mode 100644 index deca6eaf8..000000000 --- a/crates/brk_computer/src/blocks/time/import.rs +++ /dev/null @@ -1,66 +0,0 @@ -use brk_error::Result; -use brk_types::{Date, Height, Version}; -use vecdb::{Database, EagerVec, ImportableVec, LazyVecFrom1, ReadableCloneableVec}; - -use super::{TimestampIndexes, Vecs}; -use crate::indexes; - -impl Vecs { - pub(crate) fn forced_import( - db: &Database, - version: Version, - indexes: &indexes::Vecs, - ) -> Result { - let timestamp_monotonic = EagerVec::forced_import(db, "timestamp_monotonic", version)?; - - Ok(Self { - date: LazyVecFrom1::init( - "date", - version, - timestamp_monotonic.read_only_boxed_clone(), - |_height: Height, timestamp| Date::from(timestamp), - ), - timestamp_monotonic, - timestamp: TimestampIndexes::forced_import(db, version, indexes)?, - }) - } -} - -impl TimestampIndexes { - fn forced_import(db: &Database, version: Version, indexes: &indexes::Vecs) -> Result { - macro_rules! period { - ($field:ident) => { - LazyVecFrom1::init( - "timestamp", - version, - indexes.$field.first_height.read_only_boxed_clone(), - |idx, _: Height| idx.to_timestamp(), - ) - }; - } - - macro_rules! epoch { - ($field:ident) => { - ImportableVec::forced_import(db, "timestamp", version)? - }; - } - - Ok(Self(crate::internal::PerResolution { - minute10: period!(minute10), - minute30: period!(minute30), - hour1: period!(hour1), - hour4: period!(hour4), - hour12: period!(hour12), - day1: period!(day1), - day3: period!(day3), - week1: period!(week1), - month1: period!(month1), - month3: period!(month3), - month6: period!(month6), - year1: period!(year1), - year10: period!(year10), - halving: epoch!(halving), - epoch: epoch!(difficulty), - })) - } -} diff --git a/crates/brk_computer/src/blocks/time/mod.rs b/crates/brk_computer/src/blocks/time/mod.rs deleted file mode 100644 index 546680f72..000000000 --- a/crates/brk_computer/src/blocks/time/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod compute; -mod import; -mod vecs; - -pub use vecs::{TimestampIndexes, Vecs}; diff --git a/crates/brk_computer/src/blocks/time/vecs.rs b/crates/brk_computer/src/blocks/time/vecs.rs deleted file mode 100644 index c81d8ba66..000000000 --- a/crates/brk_computer/src/blocks/time/vecs.rs +++ /dev/null @@ -1,80 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use brk_types::{ - Date, Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Indexes, - Minute10, Minute30, Month1, Month3, Month6, Timestamp, Week1, Year1, Year10, -}; -use derive_more::{Deref, DerefMut}; -use vecdb::{EagerVec, Exit, LazyVecFrom1, PcoVec, ReadableVec, Rw, StorageMode}; - -use crate::{indexes, internal::PerResolution}; -#[derive(Traversable)] -pub struct Vecs { - pub date: LazyVecFrom1, - pub timestamp_monotonic: M::Stored>>, - pub timestamp: TimestampIndexes, -} - -/// Per-period timestamp indexes. -/// -/// Time-based periods (minute10–year10) are lazy: `idx.to_timestamp()` is a pure -/// function of the index, so no storage or decompression is needed. -/// Epoch-based periods (halving, difficulty) are eager: their timestamps -/// come from block data via `compute_indirect_sequential`. -#[derive(Deref, DerefMut, Traversable)] -#[traversable(transparent)] -pub struct TimestampIndexes( - #[allow(clippy::type_complexity)] - pub PerResolution< - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - LazyVecFrom1, - M::Stored>>, - M::Stored>>, - >, -); - -impl TimestampIndexes { - /// Compute epoch timestamps via indirect lookup from block timestamps. - /// Time-based periods are lazy (idx.to_timestamp()) and need no compute. - pub(crate) fn compute( - &mut self, - indexer: &brk_indexer::Indexer, - indexes: &indexes::Vecs, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - let prev_height = starting_indexes.height.decremented().unwrap_or_default(); - self.halving.compute_indirect_sequential( - indexes - .height - .halving - .collect_one(prev_height) - .unwrap_or_default(), - &indexes.halving.first_height, - &indexer.vecs.blocks.timestamp, - exit, - )?; - self.epoch.compute_indirect_sequential( - indexes - .height - .epoch - .collect_one(prev_height) - .unwrap_or_default(), - &indexes.epoch.first_height, - &indexer.vecs.blocks.timestamp, - exit, - )?; - Ok(()) - } -} diff --git a/crates/brk_computer/src/cointime/compute.rs b/crates/brk_computer/src/cointime/compute.rs index b8b240df6..9f13364cc 100644 --- a/crates/brk_computer/src/cointime/compute.rs +++ b/crates/brk_computer/src/cointime/compute.rs @@ -17,6 +17,8 @@ impl Vecs { distribution: &distribution::Vecs, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + // Activity computes first (liveliness, vaultedness, etc.) self.activity .compute(starting_indexes, distribution, exit)?; @@ -80,8 +82,11 @@ impl Vecs { r3?; r4?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index 22bef272e..ad830acd8 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -227,6 +227,8 @@ impl Vecs { starting_indexes: &mut Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + // 1. Find minimum height we have data for across stateful vecs let current_height = Height::from(self.supply_state.len()); let min_stateful = self.min_stateful_len(); @@ -300,7 +302,7 @@ impl Vecs { .cents .height .len() - .min(blocks.time.timestamp_monotonic.len()); + .min(indexes.timestamp.monotonic.len()); let cache_current_len = self.caches.prices.len(); if cache_target_len < cache_current_len { self.caches.prices.truncate(cache_target_len); @@ -312,9 +314,9 @@ impl Vecs { .cents .height .collect_range_at(cache_current_len, cache_target_len); - let new_timestamps = blocks - .time - .timestamp_monotonic + let new_timestamps = indexes + .timestamp + .monotonic .collect_range_at(cache_current_len, cache_target_len); self.caches.prices.extend(new_prices); self.caches.timestamps.extend(new_timestamps); @@ -499,8 +501,11 @@ impl Vecs { self.addr_cohorts .compute_rest_part2(prices, starting_indexes, &all_utxo_count, exit)?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } diff --git a/crates/brk_computer/src/indexes/mod.rs b/crates/brk_computer/src/indexes/mod.rs index 303be424a..c0a8e59ef 100644 --- a/crates/brk_computer/src/indexes/mod.rs +++ b/crates/brk_computer/src/indexes/mod.rs @@ -13,6 +13,7 @@ mod minute30; mod month1; mod month3; mod month6; +pub mod timestamp; mod tx_index; mod txin_index; mod txout_index; @@ -31,13 +32,11 @@ use brk_types::{ }; use vecdb::{CachedVec, Database, Exit, ReadableVec, Rw, StorageMode}; -use crate::{ - blocks, - internal::db_utils::{finalize_db, open_db}, -}; +use crate::internal::db_utils::{finalize_db, open_db}; -pub use cached_mappings::CachedMappings; pub use addr::Vecs as AddrVecs; +pub use timestamp::Timestamps; +pub use cached_mappings::CachedMappings; pub use day1::Vecs as Day1Vecs; pub use day3::Vecs as Day3Vecs; pub use epoch::Vecs as EpochVecs; @@ -85,6 +84,7 @@ pub struct Vecs { pub tx_index: TxIndexVecs, pub txin_index: TxInIndexVecs, pub txout_index: TxOutIndexVecs, + pub timestamp: Timestamps, } impl Vecs { @@ -136,6 +136,11 @@ impl Vecs { epoch_identity: CachedVec::new(&epoch.identity), }; + let timestamp = Timestamps::forced_import_from_locals( + &db, version, &minute10, &minute30, &hour1, &hour4, &hour12, &day1, &day3, &week1, + &month1, &month3, &month6, &year1, &year10, + )?; + let this = Self { cached_mappings, addr, @@ -158,6 +163,7 @@ impl Vecs { tx_index, txin_index, txout_index, + timestamp, db, }; @@ -168,36 +174,24 @@ impl Vecs { pub(crate) fn compute( &mut self, indexer: &Indexer, - blocks: &mut blocks::Vecs, starting_indexes: Indexes, exit: &Exit, ) -> Result { - blocks - .time - .compute(indexer, starting_indexes.height, exit)?; - let indexes = self.compute_(indexer, &blocks.time, starting_indexes, exit)?; - let _lock = exit.lock(); - self.db.compact()?; - Ok(indexes) - } + self.db.sync_bg_tasks()?; + + // timestamp_monotonic must be computed first — other mappings read it + self.timestamp + .compute_monotonic(indexer, starting_indexes.height, exit)?; - fn compute_( - &mut self, - indexer: &Indexer, - blocks_time: &blocks::time::Vecs, - starting_indexes: Indexes, - exit: &Exit, - ) -> Result { self.compute_tx_indexes(indexer, &starting_indexes, exit)?; self.compute_height_indexes(indexer, &starting_indexes, exit)?; let prev_height = starting_indexes.height.decremented().unwrap_or_default(); - self.compute_timestamp_mappings(blocks_time, &starting_indexes, exit)?; + self.compute_timestamp_mappings(&starting_indexes, exit)?; let starting_day1 = self.compute_calendar_mappings( indexer, - blocks_time, &starting_indexes, prev_height, exit, @@ -205,13 +199,26 @@ impl Vecs { self.compute_period_vecs( indexer, - blocks_time, &starting_indexes, prev_height, starting_day1, exit, )?; + self.timestamp.compute_per_resolution( + indexer, + &self.height, + &self.halving, + &self.epoch, + &starting_indexes, + exit, + )?; + + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(starting_indexes) } @@ -266,7 +273,6 @@ impl Vecs { fn compute_timestamp_mappings( &mut self, - blocks_time: &blocks::time::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { @@ -274,7 +280,7 @@ impl Vecs { ($field:ident, $period:ty) => { self.height.$field.compute_transform( starting_indexes.height, - &blocks_time.timestamp_monotonic, + &self.timestamp.monotonic, |(h, ts, _)| (h, <$period>::from_timestamp(ts)), exit, )?; @@ -294,7 +300,6 @@ impl Vecs { fn compute_calendar_mappings( &mut self, indexer: &Indexer, - blocks_time: &blocks::time::Vecs, starting_indexes: &Indexes, prev_height: Height, exit: &Exit, @@ -307,8 +312,8 @@ impl Vecs { self.height.day1.compute_transform( starting_indexes.height, - &blocks_time.date, - |(h, d, ..)| (h, Day1::try_from(d).unwrap()), + &self.timestamp.monotonic, + |(h, ts, ..)| (h, Day1::try_from(Date::from(ts)).unwrap()), exit, )?; @@ -318,7 +323,7 @@ impl Vecs { starting_day1 }; - self.compute_epoch(indexer, blocks_time, starting_indexes, prev_height, exit)?; + self.compute_epoch(indexer, starting_indexes, prev_height, exit)?; self.height.week1.compute_transform( starting_indexes.height, @@ -363,7 +368,6 @@ impl Vecs { fn compute_epoch( &mut self, indexer: &Indexer, - blocks_time: &blocks::time::Vecs, starting_indexes: &Indexes, prev_height: Height, exit: &Exit, @@ -389,14 +393,12 @@ impl Vecs { &self.epoch.first_height, exit, )?; - self.epoch - .height_count - .compute_count_from_indexes( - starting_difficulty, - &self.epoch.first_height, - &blocks_time.date, - exit, - )?; + self.epoch.height_count.compute_count_from_indexes( + starting_difficulty, + &self.epoch.first_height, + &self.timestamp.monotonic, + exit, + )?; let starting_halving = self .height @@ -426,7 +428,6 @@ impl Vecs { fn compute_period_vecs( &mut self, indexer: &Indexer, - blocks_time: &blocks::time::Vecs, starting_indexes: &Indexes, prev_height: Height, starting_day1: Day1, @@ -478,7 +479,7 @@ impl Vecs { exit, )?; - let date = &blocks_time.date; + let ts = &self.timestamp.monotonic; macro_rules! dated_period { ($period:ident) => {{ @@ -500,7 +501,7 @@ impl Vecs { self.$period.date.compute_transform( start, &self.$period.first_height, - |(idx, first_h, _)| (idx, date.collect_one(first_h).unwrap()), + |(idx, first_h, _)| (idx, Date::from(ts.collect_one(first_h).unwrap())), exit, )?; }}; diff --git a/crates/brk_computer/src/indexes/timestamp.rs b/crates/brk_computer/src/indexes/timestamp.rs new file mode 100644 index 000000000..1ad227abb --- /dev/null +++ b/crates/brk_computer/src/indexes/timestamp.rs @@ -0,0 +1,150 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{ + Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Indexes, Minute10, Minute30, Month1, + Month3, Month6, Timestamp, Week1, Year1, Year10, +}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + Database, EagerVec, Exit, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, + ReadableVec, Rw, StorageMode, Version, +}; + +use crate::internal::PerResolution; + +/// Timestamps: monotonic height→timestamp + per-period timestamp lookups. +/// +/// Time-based periods (minute10–year10) are lazy: `idx.to_timestamp()` is a pure +/// function of the index, so no storage or decompression is needed. +/// Epoch-based periods (halving, difficulty) are eager: their timestamps +/// come from block data via `compute_indirect_sequential`. +#[derive(Deref, DerefMut, Traversable)] +pub struct Timestamps { + pub monotonic: M::Stored>>, + #[deref] + #[deref_mut] + #[traversable(flatten)] + #[allow(clippy::type_complexity)] + pub resolutions: PerResolution< + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + M::Stored>>, + M::Stored>>, + >, +} + +impl Timestamps { + #[allow(clippy::too_many_arguments)] + pub(crate) fn forced_import_from_locals( + db: &Database, + version: Version, + minute10: &super::Minute10Vecs, + minute30: &super::Minute30Vecs, + hour1: &super::Hour1Vecs, + hour4: &super::Hour4Vecs, + hour12: &super::Hour12Vecs, + day1: &super::Day1Vecs, + day3: &super::Day3Vecs, + week1: &super::Week1Vecs, + month1: &super::Month1Vecs, + month3: &super::Month3Vecs, + month6: &super::Month6Vecs, + year1: &super::Year1Vecs, + year10: &super::Year10Vecs, + ) -> Result { + let monotonic = EagerVec::forced_import(db, "timestamp_monotonic", version)?; + + macro_rules! period { + ($field:ident) => { + LazyVecFrom1::init( + "timestamp", + version, + $field.first_height.read_only_boxed_clone(), + |idx, _: Height| idx.to_timestamp(), + ) + }; + } + + Ok(Self { + monotonic, + resolutions: PerResolution { + minute10: period!(minute10), + minute30: period!(minute30), + hour1: period!(hour1), + hour4: period!(hour4), + hour12: period!(hour12), + day1: period!(day1), + day3: period!(day3), + week1: period!(week1), + month1: period!(month1), + month3: period!(month3), + month6: period!(month6), + year1: period!(year1), + year10: period!(year10), + halving: ImportableVec::forced_import(db, "timestamp", version)?, + epoch: ImportableVec::forced_import(db, "timestamp", version)?, + }, + }) + } + + pub(crate) fn compute_monotonic( + &mut self, + indexer: &brk_indexer::Indexer, + starting_height: Height, + exit: &Exit, + ) -> Result<()> { + let mut prev = None; + self.monotonic.compute_transform( + starting_height, + &indexer.vecs.blocks.timestamp, + |(h, timestamp, this)| { + if prev.is_none() + && let Some(prev_h) = h.decremented() + { + prev.replace(this.collect_one(prev_h).unwrap()); + } + let monotonic = prev.map_or(timestamp, |p| p.max(timestamp)); + prev.replace(monotonic); + (h, monotonic) + }, + exit, + )?; + Ok(()) + } + + pub(crate) fn compute_per_resolution( + &mut self, + indexer: &brk_indexer::Indexer, + height: &super::HeightVecs, + halving_vecs: &super::HalvingVecs, + epoch_vecs: &super::EpochVecs, + starting_indexes: &Indexes, + exit: &Exit, + ) -> Result<()> { + let prev_height = starting_indexes.height.decremented().unwrap_or_default(); + self.resolutions.halving.compute_indirect_sequential( + height.halving.collect_one(prev_height).unwrap_or_default(), + &halving_vecs.first_height, + &indexer.vecs.blocks.timestamp, + exit, + )?; + self.resolutions.epoch.compute_indirect_sequential( + height.epoch.collect_one(prev_height).unwrap_or_default(), + &epoch_vecs.first_height, + &indexer.vecs.blocks.timestamp, + exit, + )?; + Ok(()) + } +} diff --git a/crates/brk_computer/src/indicators/compute.rs b/crates/brk_computer/src/indicators/compute.rs index e540db7e0..9bcdb5849 100644 --- a/crates/brk_computer/src/indicators/compute.rs +++ b/crates/brk_computer/src/indicators/compute.rs @@ -16,6 +16,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + // Puell Multiple: daily_subsidy_usd / sma_365d_subsidy_usd self.puell_multiple .bps @@ -199,8 +201,11 @@ impl Vecs { exit, )?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/indicators/import.rs b/crates/brk_computer/src/indicators/import.rs index 1af5a2f25..509a8af9d 100644 --- a/crates/brk_computer/src/indicators/import.rs +++ b/crates/brk_computer/src/indicators/import.rs @@ -3,7 +3,7 @@ use std::path::Path; use brk_error::Result; use brk_types::Version; -use super::Vecs; +use super::{Vecs, thermometer::Thermometer}; use crate::{ indexes, internal::{PerBlock, PercentPerBlock, RatioPerBlock, db_utils::{finalize_db, open_db}}, @@ -38,6 +38,8 @@ impl Vecs { let seller_exhaustion = PerBlock::forced_import(&db, "seller_exhaustion", v, indexes)?; + let thermometer = Thermometer::forced_import(&db, v, indexes)?; + let this = Self { db, puell_multiple, @@ -50,6 +52,7 @@ impl Vecs { dormancy, stock_to_flow, seller_exhaustion, + thermometer, }; finalize_db(&this.db, &this)?; Ok(this) diff --git a/crates/brk_computer/src/indicators/mod.rs b/crates/brk_computer/src/indicators/mod.rs index 17adb2bbb..aa0174d18 100644 --- a/crates/brk_computer/src/indicators/mod.rs +++ b/crates/brk_computer/src/indicators/mod.rs @@ -1,6 +1,7 @@ mod compute; mod gini; mod import; +pub mod thermometer; mod vecs; pub use vecs::Vecs; diff --git a/crates/brk_computer/src/indicators/thermometer.rs b/crates/brk_computer/src/indicators/thermometer.rs new file mode 100644 index 000000000..dbdb4420c --- /dev/null +++ b/crates/brk_computer/src/indicators/thermometer.rs @@ -0,0 +1,254 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{Cents, Height, Indexes, StoredI8, Version}; +use vecdb::{AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, WritableVec}; + +use crate::{ + cointime, + distribution, + indexes, + internal::{PerBlock, Price, RatioPerBlockPercentiles}, + prices, +}; + +#[derive(Traversable)] +pub struct Thermometer { + pub pct0_5: Price>, + pub pct1: Price>, + pub pct2: Price>, + pub pct5: Price>, + pub pct95: Price>, + pub pct98: Price>, + pub pct99: Price>, + pub pct99_5: Price>, + pub zone: PerBlock, + pub score: PerBlock, +} + +const VERSION: Version = Version::new(2); + +impl Thermometer { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + let v = version + VERSION; + Ok(Self { + pct0_5: Price::forced_import(db, "thermometer_pct0_5", v, indexes)?, + pct1: Price::forced_import(db, "thermometer_pct01", v, indexes)?, + pct2: Price::forced_import(db, "thermometer_pct02", v, indexes)?, + pct5: Price::forced_import(db, "thermometer_pct05", v, indexes)?, + pct95: Price::forced_import(db, "thermometer_pct95", v, indexes)?, + pct98: Price::forced_import(db, "thermometer_pct98", v, indexes)?, + pct99: Price::forced_import(db, "thermometer_pct99", v, indexes)?, + pct99_5: Price::forced_import(db, "thermometer_pct99_5", v, indexes)?, + zone: PerBlock::forced_import(db, "thermometer_zone", v, indexes)?, + score: PerBlock::forced_import(db, "thermometer_score", v, indexes)?, + }) + } + + pub(crate) fn compute( + &mut self, + distribution: &distribution::Vecs, + cointime: &cointime::Vecs, + prices: &prices::Vecs, + starting_indexes: &Indexes, + exit: &Exit, + ) -> Result<()> { + let realized = &distribution.utxo_cohorts.all.metrics.realized; + let ct = &cointime.prices; + + let sth_realized = &distribution.utxo_cohorts.sth.metrics.realized; + let lth_realized = &distribution.utxo_cohorts.lth.metrics.realized; + + let models: [&RatioPerBlockPercentiles; 10] = [ + &realized.price_ratio_percentiles, + &realized.investor.price.percentiles, + &sth_realized.price_ratio_percentiles, + &sth_realized.investor.price.percentiles, + <h_realized.price_ratio_percentiles, + <h_realized.investor.price.percentiles, + &ct.vaulted.percentiles, + &ct.active.percentiles, + &ct.true_market_mean.percentiles, + &ct.cointime.percentiles, + ]; + + macro_rules! sources { + ($pct:ident) => { + models.each_ref().map(|m| &m.$pct.price.cents.height) + }; + } + + // Lower percentiles: max across all models (tightest lower bound) + self.pct0_5.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct0_5), exit)?; + self.pct1.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct1), exit)?; + self.pct2.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct2), exit)?; + self.pct5.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct5), exit)?; + + // Upper percentiles: min across all models (tightest upper bound) + self.pct95.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct95), exit)?; + self.pct98.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct98), exit)?; + self.pct99.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct99), exit)?; + self.pct99_5.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct99_5), exit)?; + + let spot = &prices.spot.cents.height; + + // Zone: spot vs own envelope bands (-4 to +4) + self.compute_zone(spot, starting_indexes, exit)?; + + // Temperature: per-model band crossings (-40 to +40) + self.compute_score(&models, spot, starting_indexes, exit)?; + + Ok(()) + } + + fn compute_zone( + &mut self, + spot: &EagerVec>, + starting_indexes: &Indexes, + exit: &Exit, + ) -> Result<()> { + let bands: [&_; 8] = [ + &self.pct0_5.cents.height, + &self.pct1.cents.height, + &self.pct2.cents.height, + &self.pct5.cents.height, + &self.pct95.cents.height, + &self.pct98.cents.height, + &self.pct99.cents.height, + &self.pct99_5.cents.height, + ]; + + let dep_version: Version = bands.iter().map(|b| b.version()).sum::() + spot.version(); + + self.zone.height.validate_computed_version_or_reset(dep_version)?; + self.zone.height.truncate_if_needed(starting_indexes.height)?; + + self.zone.height.repeat_until_complete(exit, |vec| { + let skip = vec.len(); + let source_end = bands.iter().map(|b| b.len()).min().unwrap().min(spot.len()); + let end = vec.batch_end(source_end); + + if skip >= end { + return Ok(()); + } + + let spot_batch = spot.collect_range_at(skip, end); + let b: [Vec; 8] = bands.each_ref().map(|v| v.collect_range_at(skip, end)); + + for j in 0..(end - skip) { + let price = spot_batch[j]; + let mut score: i8 = 0; + + if price < b[3][j] { score -= 1; } + if price < b[2][j] { score -= 1; } + if price < b[1][j] { score -= 1; } + if price < b[0][j] { score -= 1; } + if price > b[4][j] { score += 1; } + if price > b[5][j] { score += 1; } + if price > b[6][j] { score += 1; } + if price > b[7][j] { score += 1; } + + vec.push(StoredI8::new(score)); + } + + Ok(()) + })?; + + Ok(()) + } + + fn compute_score( + &mut self, + models: &[&RatioPerBlockPercentiles; 10], + spot: &EagerVec>, + starting_indexes: &Indexes, + exit: &Exit, + ) -> Result<()> { + let dep_version: Version = models + .iter() + .map(|p| { + p.pct0_5.price.cents.height.version() + + p.pct1.price.cents.height.version() + + p.pct2.price.cents.height.version() + + p.pct5.price.cents.height.version() + + p.pct95.price.cents.height.version() + + p.pct98.price.cents.height.version() + + p.pct99.price.cents.height.version() + + p.pct99_5.price.cents.height.version() + }) + .sum::() + + spot.version(); + + self.score.height.validate_computed_version_or_reset(dep_version)?; + self.score.height.truncate_if_needed(starting_indexes.height)?; + + self.score.height.repeat_until_complete(exit, |vec| { + let skip = vec.len(); + let source_end = models + .iter() + .flat_map(|p| { + [ + p.pct0_5.price.cents.height.len(), + p.pct1.price.cents.height.len(), + p.pct2.price.cents.height.len(), + p.pct5.price.cents.height.len(), + p.pct95.price.cents.height.len(), + p.pct98.price.cents.height.len(), + p.pct99.price.cents.height.len(), + p.pct99_5.price.cents.height.len(), + ] + }) + .min() + .unwrap() + .min(spot.len()); + let end = vec.batch_end(source_end); + + if skip >= end { + return Ok(()); + } + + let spot_batch = spot.collect_range_at(skip, end); + + let bands: Vec<[Vec; 8]> = models + .iter() + .map(|p| { + [ + p.pct0_5.price.cents.height.collect_range_at(skip, end), + p.pct1.price.cents.height.collect_range_at(skip, end), + p.pct2.price.cents.height.collect_range_at(skip, end), + p.pct5.price.cents.height.collect_range_at(skip, end), + p.pct95.price.cents.height.collect_range_at(skip, end), + p.pct98.price.cents.height.collect_range_at(skip, end), + p.pct99.price.cents.height.collect_range_at(skip, end), + p.pct99_5.price.cents.height.collect_range_at(skip, end), + ] + }) + .collect(); + + for j in 0..(end - skip) { + let price = spot_batch[j]; + let mut total: i8 = 0; + + for model in &bands { + if price < model[3][j] { total -= 1; } + if price < model[2][j] { total -= 1; } + if price < model[1][j] { total -= 1; } + if price < model[0][j] { total -= 1; } + if price > model[4][j] { total += 1; } + if price > model[5][j] { total += 1; } + if price > model[6][j] { total += 1; } + if price > model[7][j] { total += 1; } + } + + vec.push(StoredI8::new(total)); + } + + Ok(()) + })?; + + Ok(()) + } +} diff --git a/crates/brk_computer/src/indicators/vecs.rs b/crates/brk_computer/src/indicators/vecs.rs index 9e27b4e56..071eda62c 100644 --- a/crates/brk_computer/src/indicators/vecs.rs +++ b/crates/brk_computer/src/indicators/vecs.rs @@ -2,6 +2,7 @@ use brk_traversable::Traversable; use brk_types::{BasisPoints16, BasisPoints32, StoredF32}; use vecdb::{Database, Rw, StorageMode}; +use super::thermometer::Thermometer; use crate::internal::{PerBlock, PercentPerBlock, RatioPerBlock}; #[derive(Traversable)] @@ -24,4 +25,5 @@ pub struct Vecs { pub dormancy: DormancyVecs, pub stock_to_flow: PerBlock, pub seller_exhaustion: PerBlock, + pub thermometer: Thermometer, } diff --git a/crates/brk_computer/src/inputs/compute.rs b/crates/brk_computer/src/inputs/compute.rs index 32c61d2c4..f005d72bd 100644 --- a/crates/brk_computer/src/inputs/compute.rs +++ b/crates/brk_computer/src/inputs/compute.rs @@ -15,13 +15,18 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + self.spent .compute(indexer, starting_indexes, exit)?; self.count .compute(indexer, indexes, blocks, starting_indexes, exit)?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/internal/algo/expanding_percentiles.rs b/crates/brk_computer/src/internal/algo/expanding_percentiles.rs index d89a25874..eec37544d 100644 --- a/crates/brk_computer/src/internal/algo/expanding_percentiles.rs +++ b/crates/brk_computer/src/internal/algo/expanding_percentiles.rs @@ -71,20 +71,20 @@ impl ExpandingPercentiles { self.tree.add(Self::to_bucket(value), &1); } - /// Compute 6 percentiles in one call via kth. O(6 × log N) but with - /// shared tree traversal across all 6 targets for better cache locality. + /// Compute 8 percentiles in one call via kth. O(8 × log N) but with + /// shared tree traversal across all 8 targets for better cache locality. /// Quantiles q must be sorted ascending in (0, 1). Output is in BPS. - pub fn quantiles(&self, qs: &[f64; 6], out: &mut [u32; 6]) { + pub fn quantiles(&self, qs: &[f64; 8], out: &mut [u32; 8]) { if self.count == 0 { out.iter_mut().for_each(|o| *o = 0); return; } - let mut targets = [0u32; 6]; + let mut targets = [0u32; 8]; for (i, &q) in qs.iter().enumerate() { let k = ((q * self.count as f64).ceil() as u32).clamp(1, self.count); targets[i] = k - 1; // 0-indexed } - let mut buckets = [0usize; 6]; + let mut buckets = [0usize; 8]; self.tree.kth(&targets, &|n: &u32| *n, &mut buckets); for (i, bucket) in buckets.iter().enumerate() { out[i] = *bucket as u32 * BUCKET_BPS as u32; @@ -97,8 +97,8 @@ mod tests { use super::*; fn quantile(ep: &ExpandingPercentiles, q: f64) -> u32 { - let mut out = [0u32; 6]; - ep.quantiles(&[q, q, q, q, q, q], &mut out); + let mut out = [0u32; 8]; + ep.quantiles(&[q, q, q, q, q, q, q, q], &mut out); out[0] } diff --git a/crates/brk_computer/src/internal/per_block/ratio/percentiles.rs b/crates/brk_computer/src/internal/per_block/ratio/percentiles.rs index b2dda8139..98e638025 100644 --- a/crates/brk_computer/src/internal/per_block/ratio/percentiles.rs +++ b/crates/brk_computer/src/internal/per_block/ratio/percentiles.rs @@ -22,18 +22,20 @@ pub struct RatioBand { #[derive(Traversable)] pub struct RatioPerBlockPercentiles { + pub pct99_5: RatioBand, pub pct99: RatioBand, pub pct98: RatioBand, pub pct95: RatioBand, pub pct5: RatioBand, pub pct2: RatioBand, pub pct1: RatioBand, + pub pct0_5: RatioBand, #[traversable(skip)] expanding_pct: ExpandingPercentiles, } -const VERSION: Version = Version::new(5); +const VERSION: Version = Version::new(6); /// First height included in ratio percentile computation (first halving). /// Earlier blocks lack meaningful market data and pollute the distribution. @@ -70,12 +72,14 @@ impl RatioPerBlockPercentiles { } Ok(Self { + pct99_5: import_band!("pct99_5"), pct99: import_band!("pct99"), pct98: import_band!("pct98"), pct95: import_band!("pct95"), pct5: import_band!("pct5"), pct2: import_band!("pct2"), pct1: import_band!("pct1"), + pct0_5: import_band!("pct0_5"), expanding_pct: ExpandingPercentiles::default(), }) } @@ -114,16 +118,18 @@ impl RatioPerBlockPercentiles { } let new_ratios = ratio_source.collect_range_at(start, ratio_len); - let mut pct_vecs: [&mut EagerVec>; 6] = [ + let mut pct_vecs: [&mut EagerVec>; 8] = [ + &mut self.pct0_5.ratio.bps.height, &mut self.pct1.ratio.bps.height, &mut self.pct2.ratio.bps.height, &mut self.pct5.ratio.bps.height, &mut self.pct95.ratio.bps.height, &mut self.pct98.ratio.bps.height, &mut self.pct99.ratio.bps.height, + &mut self.pct99_5.ratio.bps.height, ]; - const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99]; - let mut out = [0u32; 6]; + const PCTS: [f64; 8] = [0.005, 0.01, 0.02, 0.05, 0.95, 0.98, 0.99, 0.995]; + let mut out = [0u32; 8]; for vec in pct_vecs.iter_mut() { vec.truncate_if_needed_at(start)?; @@ -160,12 +166,14 @@ impl RatioPerBlockPercentiles { }; } + compute_band!(pct99_5); compute_band!(pct99); compute_band!(pct98); compute_band!(pct95); compute_band!(pct5); compute_band!(pct2); compute_band!(pct1); + compute_band!(pct0_5); Ok(()) } @@ -174,12 +182,14 @@ impl RatioPerBlockPercentiles { &mut self, ) -> impl Iterator>> { [ + &mut self.pct0_5.ratio.bps.height, &mut self.pct1.ratio.bps.height, &mut self.pct2.ratio.bps.height, &mut self.pct5.ratio.bps.height, &mut self.pct95.ratio.bps.height, &mut self.pct98.ratio.bps.height, &mut self.pct99.ratio.bps.height, + &mut self.pct99_5.ratio.bps.height, ] .into_iter() } diff --git a/crates/brk_computer/src/market/dca/by_class.rs b/crates/brk_computer/src/investing/by_class.rs similarity index 100% rename from crates/brk_computer/src/market/dca/by_class.rs rename to crates/brk_computer/src/investing/by_class.rs diff --git a/crates/brk_computer/src/market/dca/by_period.rs b/crates/brk_computer/src/investing/by_period.rs similarity index 90% rename from crates/brk_computer/src/market/dca/by_period.rs rename to crates/brk_computer/src/investing/by_period.rs index 01462f2d0..04c9b2cb8 100644 --- a/crates/brk_computer/src/market/dca/by_period.rs +++ b/crates/brk_computer/src/investing/by_period.rs @@ -1,5 +1,7 @@ use brk_traversable::Traversable; +use crate::market::lookback::ByLookbackPeriod; + /// DCA period identifiers with their day counts pub const DCA_PERIOD_DAYS: ByDcaPeriod = ByDcaPeriod { _1w: 7, @@ -173,6 +175,26 @@ impl ByDcaPeriod { } } +impl ByDcaPeriod<&T> { + /// Get the DCA-matching subset from lookback (excludes 24h) + pub(crate) fn from_lookback(lookback: &ByLookbackPeriod) -> ByDcaPeriod<&T> { + ByDcaPeriod { + _1w: &lookback._1w, + _1m: &lookback._1m, + _3m: &lookback._3m, + _6m: &lookback._6m, + _1y: &lookback._1y, + _2y: &lookback._2y, + _3y: &lookback._3y, + _4y: &lookback._4y, + _5y: &lookback._5y, + _6y: &lookback._6y, + _8y: &lookback._8y, + _10y: &lookback._10y, + } + } +} + /// Generic wrapper for DCA CAGR data (periods >= 2 years) #[derive(Clone, Default, Traversable)] pub struct ByDcaCagr { diff --git a/crates/brk_computer/src/market/dca/compute.rs b/crates/brk_computer/src/investing/compute.rs similarity index 87% rename from crates/brk_computer/src/market/dca/compute.rs rename to crates/brk_computer/src/investing/compute.rs index c8cf5c5f1..40b0982ff 100644 --- a/crates/brk_computer/src/market/dca/compute.rs +++ b/crates/brk_computer/src/investing/compute.rs @@ -2,8 +2,8 @@ use brk_error::Result; use brk_types::{BasisPointsSigned32, Bitcoin, Cents, Date, Day1, Dollars, Indexes, Sats}; use vecdb::{AnyVec, Exit, ReadableOptionVec, ReadableVec, VecIndex}; -use super::Vecs; -use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market::lookback, prices}; +use super::{ByDcaPeriod, Vecs}; +use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market, prices}; const DCA_AMOUNT: Dollars = Dollars::mint(100.0); @@ -13,10 +13,12 @@ impl Vecs { indexes: &indexes::Vecs, prices: &prices::Vecs, blocks: &blocks::Vecs, - lookback: &lookback::Vecs, + lookback: &market::lookback::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + let h2d = &indexes.height.day1; let close = &prices.split.close.usd.day1; @@ -71,17 +73,12 @@ impl Vecs { self.period.cost_basis.zip_mut_with_days(&self.period.stack) { let days = days as usize; - let start = average_price.cents.height.len().min(starting_height); - let stack_data = stack - .sats - .height - .collect_range_at(start, stack.sats.height.len()); - average_price.cents.height.compute_transform( + average_price.cents.height.compute_transform2( starting_indexes.height, h2d, - |(h, di, _)| { + &stack.sats.height, + |(h, di, stack_sats, ..)| { let di_usize = di.to_usize(); - let stack_sats = stack_data[h.to_usize() - start]; let avg = if di_usize > first_price_di { let num_days = days.min(di_usize + 1 - first_price_di); Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats)) @@ -125,21 +122,16 @@ impl Vecs { } // Lump sum by period - stack - let lookback_dca = lookback.price_past.as_dca_period(); + let lookback_dca = ByDcaPeriod::from_lookback(&lookback.price_past); for (stack, lookback_price, days) in self.period.lump_sum_stack.zip_mut_with_days(&lookback_dca) { let total_invested = DCA_AMOUNT * days as usize; - let ls_start = stack.sats.height.len().min(starting_height); - let lookback_data = lookback_price - .cents - .height - .collect_range_at(ls_start, lookback_price.cents.height.len()); - stack.sats.height.compute_transform( + stack.sats.height.compute_transform2( starting_indexes.height, h2d, - |(h, _di, _)| { - let lp = lookback_data[h.to_usize() - ls_start]; + &lookback_price.cents.height, + |(h, _di, lp, ..)| { let sats = if lp == Cents::ZERO { Sats::ZERO } else { @@ -238,20 +230,15 @@ impl Vecs { .zip(start_days) { let from_usize = from.to_usize(); - let cls_start = average_price.cents.height.len().min(starting_height); - let stack_data = stack - .sats - .height - .collect_range_at(cls_start, stack.sats.height.len()); - average_price.cents.height.compute_transform( + average_price.cents.height.compute_transform2( starting_indexes.height, h2d, - |(h, di, _)| { + &stack.sats.height, + |(h, di, stack_sats, ..)| { let di_usize = di.to_usize(); if di_usize < from_usize { return (h, Cents::ZERO); } - let stack_sats = stack_data[h.to_usize() - cls_start]; let num_days = di_usize + 1 - from_usize; let avg = Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats)); (h, avg) @@ -275,6 +262,11 @@ impl Vecs { )?; } + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/market/dca/import.rs b/crates/brk_computer/src/investing/import.rs similarity index 53% rename from crates/brk_computer/src/market/dca/import.rs rename to crates/brk_computer/src/investing/import.rs index 340b2d8a7..4b3a69518 100644 --- a/crates/brk_computer/src/market/dca/import.rs +++ b/crates/brk_computer/src/investing/import.rs @@ -1,43 +1,50 @@ +use std::path::Path; + use brk_error::Result; use brk_types::Version; -use vecdb::{Database, ImportableVec}; +use vecdb::ImportableVec; use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod, Vecs}; use super::vecs::{ClassVecs, PeriodVecs}; use crate::{ indexes, - internal::{AmountPerBlock, PercentPerBlock, Price}, + internal::{ + db_utils::{finalize_db, open_db}, + AmountPerBlock, PercentPerBlock, Price, + }, }; impl Vecs { pub(crate) fn forced_import( - db: &Database, - version: Version, + parent_path: &Path, + parent_version: Version, indexes: &indexes::Vecs, ) -> Result { + let db = open_db(parent_path, super::DB_NAME, 50_000)?; + let version = parent_version; let stack = ByDcaPeriod::try_new(|name, _days| { - AmountPerBlock::forced_import(db, &format!("dca_stack_{name}"), version, indexes) + AmountPerBlock::forced_import(&db, &format!("dca_stack_{name}"), version, indexes) })?; let cost_basis = ByDcaPeriod::try_new(|name, _days| { - Price::forced_import(db, &format!("dca_cost_basis_{name}"), version, indexes) + Price::forced_import(&db, &format!("dca_cost_basis_{name}"), version, indexes) })?; let r#return = ByDcaPeriod::try_new(|name, _days| { - PercentPerBlock::forced_import(db, &format!("dca_return_{name}"), version, indexes) + PercentPerBlock::forced_import(&db, &format!("dca_return_{name}"), version, indexes) })?; let cagr = ByDcaCagr::try_new(|name, _days| { - PercentPerBlock::forced_import(db, &format!("dca_cagr_{name}"), version, indexes) + PercentPerBlock::forced_import(&db, &format!("dca_cagr_{name}"), version, indexes) })?; let lump_sum_stack = ByDcaPeriod::try_new(|name, _days| { - AmountPerBlock::forced_import(db, &format!("lump_sum_stack_{name}"), version, indexes) + AmountPerBlock::forced_import(&db, &format!("lump_sum_stack_{name}"), version, indexes) })?; let lump_sum_return = ByDcaPeriod::try_new(|name, _days| { PercentPerBlock::forced_import( - db, + &db, &format!("lump_sum_return_{name}"), version, indexes, @@ -45,19 +52,19 @@ impl Vecs { })?; let class_stack = ByDcaClass::try_new(|name, _year, _day1| { - AmountPerBlock::forced_import(db, &format!("dca_stack_{name}"), version, indexes) + AmountPerBlock::forced_import(&db, &format!("dca_stack_{name}"), version, indexes) })?; let class_cost_basis = ByDcaClass::try_new(|name, _year, _day1| { - Price::forced_import(db, &format!("dca_cost_basis_{name}"), version, indexes) + Price::forced_import(&db, &format!("dca_cost_basis_{name}"), version, indexes) })?; let class_return = ByDcaClass::try_new(|name, _year, _day1| { - PercentPerBlock::forced_import(db, &format!("dca_return_{name}"), version, indexes) + PercentPerBlock::forced_import(&db, &format!("dca_return_{name}"), version, indexes) })?; - Ok(Self { - sats_per_day: ImportableVec::forced_import(db, "dca_sats_per_day", version)?, + let this = Self { + sats_per_day: ImportableVec::forced_import(&db, "dca_sats_per_day", version)?, period: PeriodVecs { stack, cost_basis, @@ -71,6 +78,9 @@ impl Vecs { cost_basis: class_cost_basis, r#return: class_return, }, - }) + db, + }; + finalize_db(&this.db, &this)?; + Ok(this) } } diff --git a/crates/brk_computer/src/market/dca/mod.rs b/crates/brk_computer/src/investing/mod.rs similarity index 76% rename from crates/brk_computer/src/market/dca/mod.rs rename to crates/brk_computer/src/investing/mod.rs index fcb7904d3..cac6e47bc 100644 --- a/crates/brk_computer/src/market/dca/mod.rs +++ b/crates/brk_computer/src/investing/mod.rs @@ -7,3 +7,5 @@ mod vecs; pub use by_class::*; pub use by_period::*; pub use vecs::Vecs; + +pub const DB_NAME: &str = "investing"; diff --git a/crates/brk_computer/src/market/dca/vecs.rs b/crates/brk_computer/src/investing/vecs.rs similarity index 90% rename from crates/brk_computer/src/market/dca/vecs.rs rename to crates/brk_computer/src/investing/vecs.rs index 9320f41ef..072913b9b 100644 --- a/crates/brk_computer/src/market/dca/vecs.rs +++ b/crates/brk_computer/src/investing/vecs.rs @@ -1,6 +1,6 @@ use brk_traversable::Traversable; use brk_types::{BasisPointsSigned32, Cents, Height, Sats}; -use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; +use vecdb::{Database, EagerVec, PcoVec, Rw, StorageMode}; use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod}; use crate::internal::{AmountPerBlock, PerBlock, PercentPerBlock, Price}; @@ -24,6 +24,8 @@ pub struct ClassVecs { #[derive(Traversable)] pub struct Vecs { + #[traversable(skip)] + pub(crate) db: Database, pub sats_per_day: M::Stored>>, pub period: PeriodVecs, pub class: ClassVecs, diff --git a/crates/brk_computer/src/lib.rs b/crates/brk_computer/src/lib.rs index 5c86b8b69..f15a467b3 100644 --- a/crates/brk_computer/src/lib.rs +++ b/crates/brk_computer/src/lib.rs @@ -18,6 +18,7 @@ mod indicators; pub mod indexes; mod inputs; mod internal; +mod investing; mod market; mod mining; mod outputs; @@ -39,6 +40,7 @@ pub struct Computer { pub constants: Box, pub indexes: Box>, pub indicators: Box>, + pub investing: Box>, pub market: Box>, pub pools: Box>, pub prices: Box>, @@ -180,8 +182,8 @@ impl Computer { // Market, indicators, and distribution are independent; import in parallel. // Supply depends on distribution so it runs after. - let (distribution, market, indicators) = - timed("Imported distribution/market/indicators", || { + let (distribution, market, indicators, investing) = + timed("Imported distribution/market/indicators/investing", || { thread::scope(|s| -> Result<_> { let market_handle = big_thread().spawn_scoped(s, || -> Result<_> { Ok(Box::new(market::Vecs::forced_import( @@ -199,6 +201,14 @@ impl Computer { )?)) })?; + let investing_handle = big_thread().spawn_scoped(s, || -> Result<_> { + Ok(Box::new(investing::Vecs::forced_import( + &computed_path, + VERSION, + &indexes, + )?)) + })?; + let distribution = Box::new(distribution::Vecs::forced_import( &computed_path, VERSION, @@ -208,7 +218,8 @@ impl Computer { let market = market_handle.join().unwrap()?; let indicators = indicators_handle.join().unwrap()?; - Ok((distribution, market, indicators)) + let investing = investing_handle.join().unwrap()?; + Ok((distribution, market, indicators, investing)) }) })?; @@ -232,6 +243,7 @@ impl Computer { scripts, constants, indicators, + investing, market, distribution, supply, @@ -260,6 +272,7 @@ impl Computer { cointime::DB_NAME, indicators::DB_NAME, indexes::DB_NAME, + investing::DB_NAME, market::DB_NAME, pools::DB_NAME, prices::DB_NAME, @@ -305,7 +318,7 @@ impl Computer { let mut starting_indexes = timed("Computed indexes", || { self.indexes - .compute(indexer, &mut self.blocks, starting_indexes, exit) + .compute(indexer, starting_indexes, exit) })?; thread::scope(|scope| -> Result<()> { @@ -339,8 +352,8 @@ impl Computer { let market = scope.spawn(|| { timed("Computed market", || { self.market.compute( - &self.indexes, &self.prices, + &self.indexes, &self.blocks, &starting_indexes, exit, @@ -422,6 +435,19 @@ impl Computer { }) }); + let investing = scope.spawn(|| { + timed("Computed investing", || { + self.investing.compute( + &self.indexes, + &self.prices, + &self.blocks, + &self.market.lookback, + &starting_indexes_clone, + exit, + ) + }) + }); + timed("Computed distribution", || { self.distribution.compute( indexer, @@ -437,6 +463,7 @@ impl Computer { })?; pools.join().unwrap()?; + investing.join().unwrap()?; Ok(()) })?; @@ -485,6 +512,14 @@ impl Computer { Ok(()) })?; + self.indicators.thermometer.compute( + &self.distribution, + &self.cointime, + &self.prices, + &starting_indexes, + exit, + )?; + info!("Total compute time: {:?}", compute_start.elapsed()); Ok(()) } @@ -517,7 +552,7 @@ macro_rules! impl_iter_named { } impl_iter_named!(blocks, mining, transactions, scripts, positions, cointime, - constants, indicators, indexes, market, pools, prices, distribution, supply, inputs, outputs); + constants, indicators, indexes, investing, market, pools, prices, distribution, supply, inputs, outputs); fn timed(label: &str, f: impl FnOnce() -> T) -> T { let start = Instant::now(); diff --git a/crates/brk_computer/src/market/ath/compute.rs b/crates/brk_computer/src/market/ath/compute.rs index d6afa9394..6b03d2152 100644 --- a/crates/brk_computer/src/market/ath/compute.rs +++ b/crates/brk_computer/src/market/ath/compute.rs @@ -3,13 +3,13 @@ use brk_types::{Indexes, StoredF32, Timestamp}; use vecdb::{Exit, ReadableVec, VecIndex}; use super::Vecs; -use crate::{blocks, prices}; +use crate::{indexes, prices}; impl Vecs { pub(crate) fn compute( &mut self, prices: &prices::Vecs, - blocks: &blocks::Vecs, + indexes: &indexes::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { @@ -24,7 +24,7 @@ impl Vecs { starting_indexes.height, &self.high.cents.height, &prices.spot.cents.height, - &blocks.time.timestamp_monotonic, + &indexes.timestamp.monotonic, |(i, ath, price, ts, slf)| { if ath_ts.is_none() { let idx = i.to_usize(); diff --git a/crates/brk_computer/src/market/compute.rs b/crates/brk_computer/src/market/compute.rs index bfb333e0b..d67e57776 100644 --- a/crates/brk_computer/src/market/compute.rs +++ b/crates/brk_computer/src/market/compute.rs @@ -9,17 +9,19 @@ use super::Vecs; impl Vecs { pub(crate) fn compute( &mut self, - indexes: &indexes::Vecs, prices: &prices::Vecs, + indexes: &indexes::Vecs, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + // Phase 1: Independent sub-modules in parallel let (r1, r2) = rayon::join( || { rayon::join( - || self.ath.compute(prices, blocks, starting_indexes, exit), + || self.ath.compute(prices, indexes, starting_indexes, exit), || self.lookback.compute(blocks, prices, starting_indexes, exit), ) }, @@ -39,24 +41,8 @@ impl Vecs { r2.1?; // Phase 2: Depend on lookback - let (r3, r4) = rayon::join( - || { - self.returns - .compute(prices, blocks, &self.lookback, starting_indexes, exit) - }, - || { - self.dca.compute( - indexes, - prices, - blocks, - &self.lookback, - starting_indexes, - exit, - ) - }, - ); - r3?; - r4?; + self.returns + .compute(prices, blocks, &self.lookback, starting_indexes, exit)?; // Phase 3: Depends on returns, moving_average self.technical.compute( @@ -68,8 +54,11 @@ impl Vecs { exit, )?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/market/import.rs b/crates/brk_computer/src/market/import.rs index 24091ae21..10526ae6e 100644 --- a/crates/brk_computer/src/market/import.rs +++ b/crates/brk_computer/src/market/import.rs @@ -9,7 +9,7 @@ use crate::{ }; use super::{ - AthVecs, DcaVecs, TechnicalVecs, LookbackVecs, MovingAverageVecs, RangeVecs, ReturnsVecs, + AthVecs, TechnicalVecs, LookbackVecs, MovingAverageVecs, RangeVecs, ReturnsVecs, Vecs, VolatilityVecs, }; @@ -28,7 +28,6 @@ impl Vecs { let volatility = VolatilityVecs::forced_import(version, &returns)?; let range = RangeVecs::forced_import(&db, version, indexes)?; let moving_average = MovingAverageVecs::forced_import(&db, version, indexes)?; - let dca = DcaVecs::forced_import(&db, version, indexes)?; let technical = TechnicalVecs::forced_import(&db, version, indexes)?; let this = Self { @@ -39,7 +38,6 @@ impl Vecs { volatility, range, moving_average, - dca, technical, }; finalize_db(&this.db, &this)?; diff --git a/crates/brk_computer/src/market/lookback/by_period.rs b/crates/brk_computer/src/market/lookback/by_period.rs index f2fe102f4..f5eb411f8 100644 --- a/crates/brk_computer/src/market/lookback/by_period.rs +++ b/crates/brk_computer/src/market/lookback/by_period.rs @@ -1,7 +1,5 @@ use brk_traversable::Traversable; -use crate::market::dca::ByDcaPeriod; - /// Lookback period days (includes 24h, unlike DCA) pub const LOOKBACK_PERIOD_DAYS: ByLookbackPeriod = ByLookbackPeriod { _24h: 1, @@ -117,22 +115,4 @@ impl ByLookbackPeriod { ] .into_iter() } - - /// Get the DCA-matching subset (excludes 24h) - pub(crate) fn as_dca_period(&self) -> ByDcaPeriod<&T> { - ByDcaPeriod { - _1w: &self._1w, - _1m: &self._1m, - _3m: &self._3m, - _6m: &self._6m, - _1y: &self._1y, - _2y: &self._2y, - _3y: &self._3y, - _4y: &self._4y, - _5y: &self._5y, - _6y: &self._6y, - _8y: &self._8y, - _10y: &self._10y, - } - } } diff --git a/crates/brk_computer/src/market/mod.rs b/crates/brk_computer/src/market/mod.rs index ca0ae850e..738e570f7 100644 --- a/crates/brk_computer/src/market/mod.rs +++ b/crates/brk_computer/src/market/mod.rs @@ -1,6 +1,5 @@ pub mod ath; mod compute; -pub mod dca; mod import; pub mod technical; pub mod lookback; @@ -13,7 +12,6 @@ use brk_traversable::Traversable; use vecdb::{Database, Rw, StorageMode}; pub use ath::Vecs as AthVecs; -pub use dca::Vecs as DcaVecs; pub use technical::Vecs as TechnicalVecs; pub use lookback::Vecs as LookbackVecs; pub use moving_average::Vecs as MovingAverageVecs; @@ -32,6 +30,5 @@ pub struct Vecs { pub volatility: VolatilityVecs, pub range: RangeVecs, pub moving_average: MovingAverageVecs, - pub dca: DcaVecs, pub technical: TechnicalVecs, } diff --git a/crates/brk_computer/src/market/returns/compute.rs b/crates/brk_computer/src/market/returns/compute.rs index 6e9db7eb1..51333dc45 100644 --- a/crates/brk_computer/src/market/returns/compute.rs +++ b/crates/brk_computer/src/market/returns/compute.rs @@ -3,7 +3,7 @@ use brk_types::{BasisPointsSigned32, Dollars, Indexes}; use vecdb::Exit; use super::Vecs; -use crate::{blocks, internal::RatioDiffDollarsBps32, market::lookback, prices}; +use crate::{blocks, internal::RatioDiffDollarsBps32, investing::ByDcaPeriod, market::lookback, prices}; impl Vecs { pub(crate) fn compute( @@ -29,7 +29,7 @@ impl Vecs { } // CAGR computed from returns at height level (2y+ periods only) - let price_return_dca = self.periods.as_dca_period(); + let price_return_dca = ByDcaPeriod::from_lookback(&self.periods); for (cagr, returns, days) in self.cagr.zip_mut_with_period(&price_return_dca) { let years = days as f64 / 365.0; cagr.bps.height.compute_transform( diff --git a/crates/brk_computer/src/market/returns/import.rs b/crates/brk_computer/src/market/returns/import.rs index 421bb7ff1..ab475ce0a 100644 --- a/crates/brk_computer/src/market/returns/import.rs +++ b/crates/brk_computer/src/market/returns/import.rs @@ -7,7 +7,7 @@ use super::Vecs; use crate::{ indexes, internal::{StdDevPerBlock, PercentPerBlock, Windows}, - market::dca::ByDcaCagr, + investing::ByDcaCagr, }; impl Vecs { diff --git a/crates/brk_computer/src/market/returns/vecs.rs b/crates/brk_computer/src/market/returns/vecs.rs index 12d782941..18e3423a3 100644 --- a/crates/brk_computer/src/market/returns/vecs.rs +++ b/crates/brk_computer/src/market/returns/vecs.rs @@ -4,7 +4,8 @@ use vecdb::{Rw, StorageMode}; use crate::{ internal::{PercentPerBlock, StdDevPerBlock, Windows}, - market::{dca::ByDcaCagr, lookback::ByLookbackPeriod}, + investing::ByDcaCagr, + market::lookback::ByLookbackPeriod, }; #[derive(Traversable)] diff --git a/crates/brk_computer/src/mining/compute.rs b/crates/brk_computer/src/mining/compute.rs index 76e5114ba..2d21d2ea7 100644 --- a/crates/brk_computer/src/mining/compute.rs +++ b/crates/brk_computer/src/mining/compute.rs @@ -18,6 +18,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + // Block rewards (coinbase, subsidy, fee_dominance, etc.) self.rewards.compute( indexer, @@ -39,8 +41,11 @@ impl Vecs { exit, )?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/outputs/compute.rs b/crates/brk_computer/src/outputs/compute.rs index 778f8be4a..6a15aaeda 100644 --- a/crates/brk_computer/src/outputs/compute.rs +++ b/crates/brk_computer/src/outputs/compute.rs @@ -18,6 +18,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + self.count.compute( indexer, indexes, @@ -27,10 +29,13 @@ impl Vecs { starting_indexes, exit, )?; - let _lock = self + let lock = self .spent .compute(indexer, inputs, starting_indexes, exit)?; - self.db.compact()?; + self.db.run_bg(move |db| { + let _lock = lock; + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/outputs/spent/compute.rs b/crates/brk_computer/src/outputs/spent/compute.rs index b89cff778..97e30c47b 100644 --- a/crates/brk_computer/src/outputs/spent/compute.rs +++ b/crates/brk_computer/src/outputs/spent/compute.rs @@ -10,13 +10,13 @@ use crate::inputs; const HEIGHT_BATCH: u32 = 10_000; impl Vecs { - pub(crate) fn compute<'a>( + pub(crate) fn compute( &mut self, indexer: &Indexer, inputs: &inputs::Vecs, starting_indexes: &Indexes, - exit: &'a Exit, - ) -> Result> { + exit: &Exit, + ) -> Result { let target_height = indexer.vecs.blocks.blockhash.len(); if target_height == 0 { return Ok(exit.lock()); diff --git a/crates/brk_computer/src/pools/mod.rs b/crates/brk_computer/src/pools/mod.rs index 7612551c0..84e3c5604 100644 --- a/crates/brk_computer/src/pools/mod.rs +++ b/crates/brk_computer/src/pools/mod.rs @@ -86,6 +86,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + self.compute_pool(indexer, indexes, starting_indexes, exit)?; self.major.par_iter_mut().try_for_each(|(_, vecs)| { @@ -103,8 +105,11 @@ impl Vecs { vecs.compute(starting_indexes, &self.pool, blocks, exit) })?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } diff --git a/crates/brk_computer/src/positions.rs b/crates/brk_computer/src/positions.rs index 5468bcb61..3686a8997 100644 --- a/crates/brk_computer/src/positions.rs +++ b/crates/brk_computer/src/positions.rs @@ -45,9 +45,14 @@ impl Vecs { reader: &Reader, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + self.compute_(indexer, starting_indexes, reader, exit)?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } diff --git a/crates/brk_computer/src/prices/compute.rs b/crates/brk_computer/src/prices/compute.rs index 6594e7cc3..af51b57f2 100644 --- a/crates/brk_computer/src/prices/compute.rs +++ b/crates/brk_computer/src/prices/compute.rs @@ -18,6 +18,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + self.compute_prices(indexer, starting_indexes, exit)?; self.split.open.cents.compute_first( starting_indexes, @@ -47,8 +49,11 @@ impl Vecs { exit, )?; - let _lock = exit.lock(); - self.db().compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } diff --git a/crates/brk_computer/src/prices/mod.rs b/crates/brk_computer/src/prices/mod.rs index 7b8dc2de3..d58ba7163 100644 --- a/crates/brk_computer/src/prices/mod.rs +++ b/crates/brk_computer/src/prices/mod.rs @@ -25,7 +25,7 @@ pub const DB_NAME: &str = "prices"; #[derive(Traversable)] pub struct Vecs { #[traversable(skip)] - pub(crate) db: Database, + pub db: Database, pub split: SplitByUnit, pub ohlc: OhlcByUnit, @@ -183,7 +183,4 @@ impl Vecs { }) } - pub(crate) fn db(&self) -> &Database { - &self.db - } } diff --git a/crates/brk_computer/src/scripts/compute.rs b/crates/brk_computer/src/scripts/compute.rs index fdf3e9d83..f2a98b8da 100644 --- a/crates/brk_computer/src/scripts/compute.rs +++ b/crates/brk_computer/src/scripts/compute.rs @@ -15,13 +15,18 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + self.count.compute(indexer, starting_indexes, exit)?; self.value .compute(indexer, prices, starting_indexes, exit)?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_computer/src/supply/compute.rs b/crates/brk_computer/src/supply/compute.rs index 56a1394c7..feaf800c7 100644 --- a/crates/brk_computer/src/supply/compute.rs +++ b/crates/brk_computer/src/supply/compute.rs @@ -21,6 +21,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + // 1. Compute burned/unspendable supply self.burned.compute( scripts, @@ -76,8 +78,11 @@ impl Vecs { )?; } - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } diff --git a/crates/brk_computer/src/transactions/compute.rs b/crates/brk_computer/src/transactions/compute.rs index 258c9b8ac..5768e31a1 100644 --- a/crates/brk_computer/src/transactions/compute.rs +++ b/crates/brk_computer/src/transactions/compute.rs @@ -20,6 +20,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { + self.db.sync_bg_tasks()?; + let (r1, (r2, r3)) = rayon::join( || { self.count @@ -57,8 +59,11 @@ impl Vecs { exit, )?; - let _lock = exit.lock(); - self.db.compact()?; + let exit = exit.clone(); + self.db.run_bg(move |db| { + let _lock = exit.lock(); + db.compact() + }); Ok(()) } } diff --git a/crates/brk_indexer/src/lib.rs b/crates/brk_indexer/src/lib.rs index 3fdd6faf3..0c714cff1 100644 --- a/crates/brk_indexer/src/lib.rs +++ b/crates/brk_indexer/src/lib.rs @@ -6,6 +6,7 @@ use brk_error::Result; use brk_iterator::Blocks; use brk_rpc::Client; use brk_types::Height; +use fjall::PersistMode; use tracing::{debug, info}; use vecdb::{Exit, ReadOnlyClone, ReadableVec, Ro, Rw, StorageMode}; mod constants; @@ -107,6 +108,8 @@ impl Indexer { exit: &Exit, check_collisions: bool, ) -> Result { + self.vecs.db.sync_bg_tasks()?; + debug!("Starting indexing..."); let last_blockhash = self.vecs.blocks.blockhash.collect_last(); @@ -248,11 +251,32 @@ impl Indexer { drop(readers); - if !is_export_height(indexes.height) { - export(stores, vecs, indexes.height)?; - } + let lock = exit.lock(); + let tasks = self.stores.take_all_pending_ingests(indexes.height)?; + self.vecs.stamped_write(indexes.height)?; + let fjall_db = self.stores.db.clone(); - self.vecs.compact()?; + self.vecs.db.run_bg(move |db| { + let _lock = lock; + + if !tasks.is_empty() { + let i = Instant::now(); + for task in tasks { + task().map_err(vecdb::RawDBError::other)?; + } + info!("Stores committed in {:?}", i.elapsed()); + + let i = Instant::now(); + fjall_db + .persist(PersistMode::SyncData) + .map_err(vecdb::RawDBError::other)?; + info!("Stores persisted in {:?}", i.elapsed()); + } + + db.flush()?; + db.compact()?; + Ok(()) + }); Ok(starting_indexes) } diff --git a/crates/brk_indexer/src/stores.rs b/crates/brk_indexer/src/stores.rs index acf21fea5..a6d5b725d 100644 --- a/crates/brk_indexer/src/stores.rs +++ b/crates/brk_indexer/src/stores.rs @@ -6,8 +6,8 @@ use brk_cohort::ByAddrType; use brk_error::Result; use brk_store::{AnyStore, Kind, Mode, Store}; use brk_types::{ - AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, BlockHashPrefix, Height, OutPoint, - OutputType, StoredString, TxIndex, TxOutIndex, TxidPrefix, TypeIndex, Unit, Version, Vout, + AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, BlockHashPrefix, Height, OutPoint, OutputType, + StoredString, TxIndex, TxOutIndex, TxidPrefix, TypeIndex, Unit, Version, Vout, }; use fjall::{Database, PersistMode}; use rayon::prelude::*; @@ -24,8 +24,7 @@ pub struct Stores { pub addr_type_to_addr_hash_to_addr_index: ByAddrType>, pub addr_type_to_addr_index_and_tx_index: ByAddrType>, - pub addr_type_to_addr_index_and_unspent_outpoint: - ByAddrType>, + pub addr_type_to_addr_index_and_unspent_outpoint: ByAddrType>, pub blockhash_prefix_to_height: Store, pub height_to_coinbase_tag: Store, pub txid_prefix_to_tx_index: Store, @@ -194,6 +193,39 @@ impl Stores { Ok(()) } + /// Takes all pending puts/dels from every store and returns closures + /// that can ingest them on a background thread. + #[allow(clippy::type_complexity)] + pub fn take_all_pending_ingests( + &mut self, + height: Height, + ) -> Result Result<()> + Send>>> { + let h = height; + let mut tasks = Vec::new(); + + macro_rules! take { + ($store:expr) => { + tasks.extend($store.take_pending_ingest(h)?); + }; + } + + take!(self.blockhash_prefix_to_height); + take!(self.height_to_coinbase_tag); + take!(self.txid_prefix_to_tx_index); + + for store in self.addr_type_to_addr_hash_to_addr_index.values_mut() { + take!(store); + } + for store in self.addr_type_to_addr_index_and_tx_index.values_mut() { + take!(store); + } + for store in self.addr_type_to_addr_index_and_unspent_outpoint.values_mut() { + take!(store); + } + + Ok(tasks) + } + pub fn rollback_if_needed( &mut self, vecs: &mut Vecs, @@ -368,11 +400,7 @@ impl Stores { let addr_type = output_type; let addr_index = type_index; - addr_index_tx_index_to_remove.insert(( - addr_type, - addr_index, - spending_tx_index, - )); + addr_index_tx_index_to_remove.insert((addr_type, addr_index, spending_tx_index)); self.addr_type_to_addr_index_and_unspent_outpoint .get_mut_unwrap(addr_type) diff --git a/crates/brk_indexer/src/vecs/mod.rs b/crates/brk_indexer/src/vecs/mod.rs index 2a95af6fb..00a6571c1 100644 --- a/crates/brk_indexer/src/vecs/mod.rs +++ b/crates/brk_indexer/src/vecs/mod.rs @@ -30,7 +30,7 @@ use crate::Indexes; #[derive(Traversable)] pub struct Vecs { #[traversable(skip)] - db: Database, + pub db: Database, pub blocks: BlocksVecs, #[traversable(wrap = "transactions", rename = "raw")] pub transactions: TransactionsVecs, @@ -121,8 +121,7 @@ impl Vecs { } pub fn flush(&mut self, height: Height) -> Result<()> { - self.par_iter_mut_any_stored_vec() - .try_for_each(|vec| vec.stamped_write(Stamp::from(height)))?; + self.stamped_write(height)?; self.db.flush()?; Ok(()) } @@ -137,6 +136,12 @@ impl Vecs { .unwrap() } + pub fn stamped_write(&mut self, height: Height) -> Result<()> { + self.par_iter_mut_any_stored_vec() + .try_for_each(|vec| vec.stamped_write(Stamp::from(height)))?; + Ok(()) + } + pub fn compact(&self) -> Result<()> { self.db.compact()?; Ok(()) @@ -168,7 +173,4 @@ impl Vecs { .chain(self.scripts.par_iter_mut_any()) } - pub fn db(&self) -> &Database { - &self.db - } } diff --git a/crates/brk_query/src/impl/mining/day1_iter.rs b/crates/brk_query/src/impl/mining/day1_iter.rs index 118cb2ef1..c760a51d4 100644 --- a/crates/brk_query/src/impl/mining/day1_iter.rs +++ b/crates/brk_query/src/impl/mining/day1_iter.rs @@ -46,7 +46,7 @@ impl<'a> Day1Iter<'a> { .to_usize() .saturating_sub(self.start_di.to_usize()) + 1; - let timestamps = &self.computer.blocks.time.timestamp.day1; + let timestamps = &self.computer.indexes.timestamp.day1; let heights = &self.computer.indexes.day1.first_height; let mut entries = Vec::with_capacity(total / self.step + 1); diff --git a/crates/brk_query/src/impl/mining/difficulty.rs b/crates/brk_query/src/impl/mining/difficulty.rs index e04058510..3f353e82c 100644 --- a/crates/brk_query/src/impl/mining/difficulty.rs +++ b/crates/brk_query/src/impl/mining/difficulty.rs @@ -45,8 +45,7 @@ impl Query { // Get timestamps using difficulty_to_timestamp for epoch start let epoch_start_timestamp = computer - .blocks - .time + .indexes .timestamp .epoch .collect_one(current_epoch) diff --git a/crates/brk_query/src/impl/mining/epochs.rs b/crates/brk_query/src/impl/mining/epochs.rs index c0376cfb6..4d58078ca 100644 --- a/crates/brk_query/src/impl/mining/epochs.rs +++ b/crates/brk_query/src/impl/mining/epochs.rs @@ -22,7 +22,7 @@ pub fn iter_difficulty_epochs( .unwrap_or_default(); let epoch_to_height = &computer.indexes.epoch.first_height; - let epoch_to_timestamp = &computer.blocks.time.timestamp.epoch; + let epoch_to_timestamp = &computer.indexes.timestamp.epoch; let epoch_to_difficulty = &computer.blocks.difficulty.value.epoch; let mut results = Vec::with_capacity(end_epoch.to_usize() - start_epoch.to_usize() + 1); diff --git a/crates/brk_query/src/impl/mining/hashrate.rs b/crates/brk_query/src/impl/mining/hashrate.rs index 34f025738..acb042b0c 100644 --- a/crates/brk_query/src/impl/mining/hashrate.rs +++ b/crates/brk_query/src/impl/mining/hashrate.rs @@ -56,7 +56,7 @@ impl Query { let step = (total_days / 200).max(1); // Max ~200 data points let hashrate_vec = &computer.mining.hashrate.rate.base.day1; - let timestamp_vec = &computer.blocks.time.timestamp.day1; + let timestamp_vec = &computer.indexes.timestamp.day1; let mut hashrates = Vec::with_capacity(total_days / step + 1); let mut di = start_day1.to_usize(); diff --git a/crates/brk_query/src/impl/series.rs b/crates/brk_query/src/impl/series.rs index 9ad10f3fd..185216c42 100644 --- a/crates/brk_query/src/impl/series.rs +++ b/crates/brk_query/src/impl/series.rs @@ -373,7 +373,7 @@ impl Query { // Slow path: rebuild from computer's precomputed monotonic timestamps let mut map = HEIGHT_BY_MONOTONIC_TIMESTAMP.write(); if map.len() <= current_height { - *map = RangeMap::from(self.computer().blocks.time.timestamp_monotonic.collect()); + *map = RangeMap::from(self.computer().indexes.timestamp.monotonic.collect()); } map.ceil(ts).map(usize::from).unwrap_or(current_height) } diff --git a/crates/brk_store/src/lib.rs b/crates/brk_store/src/lib.rs index f7427b62d..c41f2022d 100644 --- a/crates/brk_store/src/lib.rs +++ b/crates/brk_store/src/lib.rs @@ -206,6 +206,34 @@ where } } + /// Takes buffered puts/dels and returns a closure that ingests them into the keyspace. + /// The store is left with empty buffers, ready for the next batch. + #[allow(clippy::type_complexity)] + pub fn take_pending_ingest( + &mut self, + height: Height, + ) -> Result Result<()> + Send>>> + where + K: Send + 'static, + V: Send + 'static, + for<'a> ByteView: From<&'a K> + From<&'a V>, + { + self.export_meta_if_needed(height)?; + + let puts = mem::take(&mut self.puts); + let dels = mem::take(&mut self.dels); + + if puts.is_empty() && dels.is_empty() { + return Ok(None); + } + + let keyspace = self.keyspace.clone(); + + Ok(Some(Box::new(move || { + Self::ingest(&keyspace, puts.iter(), dels.iter()) + }))) + } + #[inline] pub fn iter(&self) -> impl Iterator { self.keyspace diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 9b68564da..88718b1bb 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1973,7 +1973,7 @@ function createGrossInvestedInvestorLossNetNuplProfitSentimentPattern2(client, a * @typedef {Object} BpsCentsPercentilesRatioSatsSmaStdUsdPattern * @property {SeriesPattern1} bps * @property {SeriesPattern1} cents - * @property {Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles + * @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles * @property {SeriesPattern1} ratio * @property {SeriesPattern1} sats * @property {_1m1w1y2y4yAllPattern} sma @@ -1981,6 +1981,37 @@ function createGrossInvestedInvestorLossNetNuplProfitSentimentPattern2(client, a * @property {SeriesPattern1} usd */ +/** + * @typedef {Object} Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern + * @property {BpsPriceRatioPattern} pct05 + * @property {BpsPriceRatioPattern} pct1 + * @property {BpsPriceRatioPattern} pct2 + * @property {BpsPriceRatioPattern} pct5 + * @property {BpsPriceRatioPattern} pct95 + * @property {BpsPriceRatioPattern} pct98 + * @property {BpsPriceRatioPattern} pct99 + * @property {BpsPriceRatioPattern} pct995 + */ + +/** + * Create a Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated series name + * @returns {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} + */ +function createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc) { + return { + pct05: createBpsPriceRatioPattern(client, acc, 'pct0_5'), + pct1: createBpsPriceRatioPattern(client, acc, 'pct1'), + pct2: createBpsPriceRatioPattern(client, acc, 'pct2'), + pct5: createBpsPriceRatioPattern(client, acc, 'pct5'), + pct95: createBpsPriceRatioPattern(client, acc, 'pct95'), + pct98: createBpsPriceRatioPattern(client, acc, 'pct98'), + pct99: createBpsPriceRatioPattern(client, acc, 'pct99'), + pct995: createBpsPriceRatioPattern(client, acc, 'pct99_5'), + }; +} + /** * @typedef {Object} _10y2y3y4y5y6y8yPattern * @property {BpsPercentRatioPattern} _10y @@ -2242,7 +2273,7 @@ function createAverageBlockCumulativeInSumPattern(client, acc) { * @typedef {Object} BpsCentsPercentilesRatioSatsUsdPattern * @property {SeriesPattern1} bps * @property {SeriesPattern1} cents - * @property {Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles + * @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles * @property {SeriesPattern1} ratio * @property {SeriesPattern1} sats * @property {SeriesPattern1} usd @@ -2258,7 +2289,7 @@ function createBpsCentsPercentilesRatioSatsUsdPattern(client, acc) { return { bps: createSeriesPattern1(client, _m(acc, 'ratio_bps')), cents: createSeriesPattern1(client, _m(acc, 'cents')), - percentiles: createPct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc), + percentiles: createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc), ratio: createSeriesPattern1(client, _m(acc, 'ratio')), sats: createSeriesPattern1(client, _m(acc, 'sats')), usd: createSeriesPattern1(client, acc), @@ -2373,33 +2404,6 @@ function createDeltaHalfInToTotalPattern2(client, acc) { }; } -/** - * @typedef {Object} Pct1Pct2Pct5Pct95Pct98Pct99Pattern - * @property {BpsPriceRatioPattern} pct1 - * @property {BpsPriceRatioPattern} pct2 - * @property {BpsPriceRatioPattern} pct5 - * @property {BpsPriceRatioPattern} pct95 - * @property {BpsPriceRatioPattern} pct98 - * @property {BpsPriceRatioPattern} pct99 - */ - -/** - * Create a Pct1Pct2Pct5Pct95Pct98Pct99Pattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated series name - * @returns {Pct1Pct2Pct5Pct95Pct98Pct99Pattern} - */ -function createPct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc) { - return { - pct1: createBpsPriceRatioPattern(client, acc, 'pct1'), - pct2: createBpsPriceRatioPattern(client, acc, 'pct2'), - pct5: createBpsPriceRatioPattern(client, acc, 'pct5'), - pct95: createBpsPriceRatioPattern(client, acc, 'pct95'), - pct98: createBpsPriceRatioPattern(client, acc, 'pct98'), - pct99: createBpsPriceRatioPattern(client, acc, 'pct99'), - }; -} - /** * @typedef {Object} _1m1w1y24hBlockPattern * @property {SeriesPattern1} _1m @@ -4146,6 +4150,7 @@ function createTransferPattern(client, acc) { * @property {SeriesTree_Constants} constants * @property {SeriesTree_Indexes} indexes * @property {SeriesTree_Indicators} indicators + * @property {SeriesTree_Investing} investing * @property {SeriesTree_Market} market * @property {SeriesTree_Pools} pools * @property {SeriesTree_Prices} prices @@ -4180,9 +4185,7 @@ function createTransferPattern(client, acc) { /** * @typedef {Object} SeriesTree_Blocks_Time - * @property {SeriesPattern1} timestamp - * @property {SeriesPattern18} date - * @property {SeriesPattern18} timestampMonotonic + * @property {SeriesPattern18} timestamp */ /** @@ -4769,6 +4772,7 @@ function createTransferPattern(client, acc) { * @property {SeriesTree_Indexes_TxIndex} txIndex * @property {SeriesTree_Indexes_TxinIndex} txinIndex * @property {SeriesTree_Indexes_TxoutIndex} txoutIndex + * @property {SeriesTree_Indexes_Timestamp} timestamp */ /** @@ -4992,6 +4996,12 @@ function createTransferPattern(client, acc) { * @property {SeriesPattern21} identity */ +/** + * @typedef {Object} SeriesTree_Indexes_Timestamp + * @property {SeriesPattern18} monotonic + * @property {SeriesPattern2} resolutions + */ + /** * @typedef {Object} SeriesTree_Indicators * @property {BpsRatioPattern2} puellMultiple @@ -5004,6 +5014,7 @@ function createTransferPattern(client, acc) { * @property {SeriesTree_Indicators_Dormancy} dormancy * @property {SeriesPattern1} stockToFlow * @property {SeriesPattern1} sellerExhaustion + * @property {SeriesTree_Indicators_Thermometer} thermometer */ /** @@ -5012,6 +5023,108 @@ function createTransferPattern(client, acc) { * @property {SeriesPattern1} flow */ +/** + * @typedef {Object} SeriesTree_Indicators_Thermometer + * @property {CentsSatsUsdPattern} pct05 + * @property {CentsSatsUsdPattern} pct1 + * @property {CentsSatsUsdPattern} pct2 + * @property {CentsSatsUsdPattern} pct5 + * @property {CentsSatsUsdPattern} pct95 + * @property {CentsSatsUsdPattern} pct98 + * @property {CentsSatsUsdPattern} pct99 + * @property {CentsSatsUsdPattern} pct995 + * @property {SeriesPattern1} zone + * @property {SeriesPattern1} score + */ + +/** + * @typedef {Object} SeriesTree_Investing + * @property {SeriesPattern18} satsPerDay + * @property {SeriesTree_Investing_Period} period + * @property {SeriesTree_Investing_Class} class + */ + +/** + * @typedef {Object} SeriesTree_Investing_Period + * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern3} stack + * @property {SeriesTree_Investing_Period_CostBasis} costBasis + * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2} return + * @property {_10y2y3y4y5y6y8yPattern} cagr + * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern3} lumpSumStack + * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2} lumpSumReturn + */ + +/** + * @typedef {Object} SeriesTree_Investing_Period_CostBasis + * @property {CentsSatsUsdPattern} _1w + * @property {CentsSatsUsdPattern} _1m + * @property {CentsSatsUsdPattern} _3m + * @property {CentsSatsUsdPattern} _6m + * @property {CentsSatsUsdPattern} _1y + * @property {CentsSatsUsdPattern} _2y + * @property {CentsSatsUsdPattern} _3y + * @property {CentsSatsUsdPattern} _4y + * @property {CentsSatsUsdPattern} _5y + * @property {CentsSatsUsdPattern} _6y + * @property {CentsSatsUsdPattern} _8y + * @property {CentsSatsUsdPattern} _10y + */ + +/** + * @typedef {Object} SeriesTree_Investing_Class + * @property {SeriesTree_Investing_Class_Stack} stack + * @property {SeriesTree_Investing_Class_CostBasis} costBasis + * @property {SeriesTree_Investing_Class_Return} return + */ + +/** + * @typedef {Object} SeriesTree_Investing_Class_Stack + * @property {BtcCentsSatsUsdPattern3} from2015 + * @property {BtcCentsSatsUsdPattern3} from2016 + * @property {BtcCentsSatsUsdPattern3} from2017 + * @property {BtcCentsSatsUsdPattern3} from2018 + * @property {BtcCentsSatsUsdPattern3} from2019 + * @property {BtcCentsSatsUsdPattern3} from2020 + * @property {BtcCentsSatsUsdPattern3} from2021 + * @property {BtcCentsSatsUsdPattern3} from2022 + * @property {BtcCentsSatsUsdPattern3} from2023 + * @property {BtcCentsSatsUsdPattern3} from2024 + * @property {BtcCentsSatsUsdPattern3} from2025 + * @property {BtcCentsSatsUsdPattern3} from2026 + */ + +/** + * @typedef {Object} SeriesTree_Investing_Class_CostBasis + * @property {CentsSatsUsdPattern} from2015 + * @property {CentsSatsUsdPattern} from2016 + * @property {CentsSatsUsdPattern} from2017 + * @property {CentsSatsUsdPattern} from2018 + * @property {CentsSatsUsdPattern} from2019 + * @property {CentsSatsUsdPattern} from2020 + * @property {CentsSatsUsdPattern} from2021 + * @property {CentsSatsUsdPattern} from2022 + * @property {CentsSatsUsdPattern} from2023 + * @property {CentsSatsUsdPattern} from2024 + * @property {CentsSatsUsdPattern} from2025 + * @property {CentsSatsUsdPattern} from2026 + */ + +/** + * @typedef {Object} SeriesTree_Investing_Class_Return + * @property {BpsPercentRatioPattern} from2015 + * @property {BpsPercentRatioPattern} from2016 + * @property {BpsPercentRatioPattern} from2017 + * @property {BpsPercentRatioPattern} from2018 + * @property {BpsPercentRatioPattern} from2019 + * @property {BpsPercentRatioPattern} from2020 + * @property {BpsPercentRatioPattern} from2021 + * @property {BpsPercentRatioPattern} from2022 + * @property {BpsPercentRatioPattern} from2023 + * @property {BpsPercentRatioPattern} from2024 + * @property {BpsPercentRatioPattern} from2025 + * @property {BpsPercentRatioPattern} from2026 + */ + /** * @typedef {Object} SeriesTree_Market * @property {SeriesTree_Market_Ath} ath @@ -5020,7 +5133,6 @@ function createTransferPattern(client, acc) { * @property {_1m1w1y24hPattern} volatility * @property {SeriesTree_Market_Range} range * @property {SeriesTree_Market_MovingAverage} movingAverage - * @property {SeriesTree_Market_Dca} dca * @property {SeriesTree_Market_Technical} technical */ @@ -5183,94 +5295,6 @@ function createTransferPattern(client, acc) { * @property {BpsCentsRatioSatsUsdPattern} _4y */ -/** - * @typedef {Object} SeriesTree_Market_Dca - * @property {SeriesPattern18} satsPerDay - * @property {SeriesTree_Market_Dca_Period} period - * @property {SeriesTree_Market_Dca_Class} class - */ - -/** - * @typedef {Object} SeriesTree_Market_Dca_Period - * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern3} stack - * @property {SeriesTree_Market_Dca_Period_CostBasis} costBasis - * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2} return - * @property {_10y2y3y4y5y6y8yPattern} cagr - * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern3} lumpSumStack - * @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2} lumpSumReturn - */ - -/** - * @typedef {Object} SeriesTree_Market_Dca_Period_CostBasis - * @property {CentsSatsUsdPattern} _1w - * @property {CentsSatsUsdPattern} _1m - * @property {CentsSatsUsdPattern} _3m - * @property {CentsSatsUsdPattern} _6m - * @property {CentsSatsUsdPattern} _1y - * @property {CentsSatsUsdPattern} _2y - * @property {CentsSatsUsdPattern} _3y - * @property {CentsSatsUsdPattern} _4y - * @property {CentsSatsUsdPattern} _5y - * @property {CentsSatsUsdPattern} _6y - * @property {CentsSatsUsdPattern} _8y - * @property {CentsSatsUsdPattern} _10y - */ - -/** - * @typedef {Object} SeriesTree_Market_Dca_Class - * @property {SeriesTree_Market_Dca_Class_Stack} stack - * @property {SeriesTree_Market_Dca_Class_CostBasis} costBasis - * @property {SeriesTree_Market_Dca_Class_Return} return - */ - -/** - * @typedef {Object} SeriesTree_Market_Dca_Class_Stack - * @property {BtcCentsSatsUsdPattern3} from2015 - * @property {BtcCentsSatsUsdPattern3} from2016 - * @property {BtcCentsSatsUsdPattern3} from2017 - * @property {BtcCentsSatsUsdPattern3} from2018 - * @property {BtcCentsSatsUsdPattern3} from2019 - * @property {BtcCentsSatsUsdPattern3} from2020 - * @property {BtcCentsSatsUsdPattern3} from2021 - * @property {BtcCentsSatsUsdPattern3} from2022 - * @property {BtcCentsSatsUsdPattern3} from2023 - * @property {BtcCentsSatsUsdPattern3} from2024 - * @property {BtcCentsSatsUsdPattern3} from2025 - * @property {BtcCentsSatsUsdPattern3} from2026 - */ - -/** - * @typedef {Object} SeriesTree_Market_Dca_Class_CostBasis - * @property {CentsSatsUsdPattern} from2015 - * @property {CentsSatsUsdPattern} from2016 - * @property {CentsSatsUsdPattern} from2017 - * @property {CentsSatsUsdPattern} from2018 - * @property {CentsSatsUsdPattern} from2019 - * @property {CentsSatsUsdPattern} from2020 - * @property {CentsSatsUsdPattern} from2021 - * @property {CentsSatsUsdPattern} from2022 - * @property {CentsSatsUsdPattern} from2023 - * @property {CentsSatsUsdPattern} from2024 - * @property {CentsSatsUsdPattern} from2025 - * @property {CentsSatsUsdPattern} from2026 - */ - -/** - * @typedef {Object} SeriesTree_Market_Dca_Class_Return - * @property {BpsPercentRatioPattern} from2015 - * @property {BpsPercentRatioPattern} from2016 - * @property {BpsPercentRatioPattern} from2017 - * @property {BpsPercentRatioPattern} from2018 - * @property {BpsPercentRatioPattern} from2019 - * @property {BpsPercentRatioPattern} from2020 - * @property {BpsPercentRatioPattern} from2021 - * @property {BpsPercentRatioPattern} from2022 - * @property {BpsPercentRatioPattern} from2023 - * @property {BpsPercentRatioPattern} from2024 - * @property {BpsPercentRatioPattern} from2025 - * @property {BpsPercentRatioPattern} from2026 - */ - /** * @typedef {Object} SeriesTree_Market_Technical * @property {SeriesTree_Market_Technical_Rsi} rsi @@ -5624,7 +5648,7 @@ function createTransferPattern(client, acc) { * @property {SeriesPattern1} sats * @property {SeriesPattern1} bps * @property {SeriesPattern1} ratio - * @property {Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles + * @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles * @property {_1m1w1y2y4yAllPattern} sma * @property {SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev} stdDev */ @@ -5815,7 +5839,7 @@ function createTransferPattern(client, acc) { * @property {SeriesPattern1} sats * @property {SeriesPattern1} bps * @property {SeriesPattern1} ratio - * @property {Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles + * @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles * @property {_1m1w1y2y4yAllPattern} sma * @property {SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev} stdDev */ @@ -5937,7 +5961,7 @@ function createTransferPattern(client, acc) { * @property {SeriesPattern1} sats * @property {SeriesPattern1} bps * @property {SeriesPattern1} ratio - * @property {Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles + * @property {Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} percentiles * @property {_1m1w1y2y4yAllPattern} sma * @property {SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev} stdDev */ @@ -7557,9 +7581,7 @@ class BrkClient extends BrkClientBase { daysToRetarget: createSeriesPattern1(this, 'days_to_retarget'), }, time: { - timestamp: createSeriesPattern1(this, 'timestamp'), - date: createSeriesPattern18(this, 'date'), - timestampMonotonic: createSeriesPattern18(this, 'timestamp_monotonic'), + timestamp: createSeriesPattern18(this, 'timestamp'), }, size: { base: createSeriesPattern18(this, 'total_size'), @@ -8101,6 +8123,10 @@ class BrkClient extends BrkClientBase { txoutIndex: { identity: createSeriesPattern21(this, 'txout_index'), }, + timestamp: { + monotonic: createSeriesPattern18(this, 'timestamp_monotonic'), + resolutions: createSeriesPattern2(this, 'timestamp'), + }, }, indicators: { puellMultiple: createBpsRatioPattern2(this, 'puell_multiple'), @@ -8116,6 +8142,86 @@ class BrkClient extends BrkClientBase { }, stockToFlow: createSeriesPattern1(this, 'stock_to_flow'), sellerExhaustion: createSeriesPattern1(this, 'seller_exhaustion'), + thermometer: { + pct05: createCentsSatsUsdPattern(this, 'thermometer_pct0_5'), + pct1: createCentsSatsUsdPattern(this, 'thermometer_pct01'), + pct2: createCentsSatsUsdPattern(this, 'thermometer_pct02'), + pct5: createCentsSatsUsdPattern(this, 'thermometer_pct05'), + pct95: createCentsSatsUsdPattern(this, 'thermometer_pct95'), + pct98: createCentsSatsUsdPattern(this, 'thermometer_pct98'), + pct99: createCentsSatsUsdPattern(this, 'thermometer_pct99'), + pct995: createCentsSatsUsdPattern(this, 'thermometer_pct99_5'), + zone: createSeriesPattern1(this, 'thermometer_zone'), + score: createSeriesPattern1(this, 'thermometer_score'), + }, + }, + investing: { + satsPerDay: createSeriesPattern18(this, 'dca_sats_per_day'), + period: { + stack: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(this, 'dca_stack'), + costBasis: { + _1w: createCentsSatsUsdPattern(this, 'dca_cost_basis_1w'), + _1m: createCentsSatsUsdPattern(this, 'dca_cost_basis_1m'), + _3m: createCentsSatsUsdPattern(this, 'dca_cost_basis_3m'), + _6m: createCentsSatsUsdPattern(this, 'dca_cost_basis_6m'), + _1y: createCentsSatsUsdPattern(this, 'dca_cost_basis_1y'), + _2y: createCentsSatsUsdPattern(this, 'dca_cost_basis_2y'), + _3y: createCentsSatsUsdPattern(this, 'dca_cost_basis_3y'), + _4y: createCentsSatsUsdPattern(this, 'dca_cost_basis_4y'), + _5y: createCentsSatsUsdPattern(this, 'dca_cost_basis_5y'), + _6y: createCentsSatsUsdPattern(this, 'dca_cost_basis_6y'), + _8y: createCentsSatsUsdPattern(this, 'dca_cost_basis_8y'), + _10y: createCentsSatsUsdPattern(this, 'dca_cost_basis_10y'), + }, + return: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_return'), + cagr: create_10y2y3y4y5y6y8yPattern(this, 'dca_cagr'), + lumpSumStack: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(this, 'lump_sum_stack'), + lumpSumReturn: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_return'), + }, + class: { + stack: { + from2015: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2015'), + from2016: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2016'), + from2017: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2017'), + from2018: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2018'), + from2019: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2019'), + from2020: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2020'), + from2021: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2021'), + from2022: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2022'), + from2023: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2023'), + from2024: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2024'), + from2025: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2025'), + from2026: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2026'), + }, + costBasis: { + from2015: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2015'), + from2016: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2016'), + from2017: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2017'), + from2018: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2018'), + from2019: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2019'), + from2020: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2020'), + from2021: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2021'), + from2022: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2022'), + from2023: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2023'), + from2024: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2024'), + from2025: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2025'), + from2026: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2026'), + }, + return: { + from2015: createBpsPercentRatioPattern(this, 'dca_return_from_2015'), + from2016: createBpsPercentRatioPattern(this, 'dca_return_from_2016'), + from2017: createBpsPercentRatioPattern(this, 'dca_return_from_2017'), + from2018: createBpsPercentRatioPattern(this, 'dca_return_from_2018'), + from2019: createBpsPercentRatioPattern(this, 'dca_return_from_2019'), + from2020: createBpsPercentRatioPattern(this, 'dca_return_from_2020'), + from2021: createBpsPercentRatioPattern(this, 'dca_return_from_2021'), + from2022: createBpsPercentRatioPattern(this, 'dca_return_from_2022'), + from2023: createBpsPercentRatioPattern(this, 'dca_return_from_2023'), + from2024: createBpsPercentRatioPattern(this, 'dca_return_from_2024'), + from2025: createBpsPercentRatioPattern(this, 'dca_return_from_2025'), + from2026: createBpsPercentRatioPattern(this, 'dca_return_from_2026'), + }, + }, }, market: { ath: { @@ -8238,74 +8344,6 @@ class BrkClient extends BrkClientBase { _4y: createBpsCentsRatioSatsUsdPattern(this, 'price_ema_4y'), }, }, - dca: { - satsPerDay: createSeriesPattern18(this, 'dca_sats_per_day'), - period: { - stack: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(this, 'dca_stack'), - costBasis: { - _1w: createCentsSatsUsdPattern(this, 'dca_cost_basis_1w'), - _1m: createCentsSatsUsdPattern(this, 'dca_cost_basis_1m'), - _3m: createCentsSatsUsdPattern(this, 'dca_cost_basis_3m'), - _6m: createCentsSatsUsdPattern(this, 'dca_cost_basis_6m'), - _1y: createCentsSatsUsdPattern(this, 'dca_cost_basis_1y'), - _2y: createCentsSatsUsdPattern(this, 'dca_cost_basis_2y'), - _3y: createCentsSatsUsdPattern(this, 'dca_cost_basis_3y'), - _4y: createCentsSatsUsdPattern(this, 'dca_cost_basis_4y'), - _5y: createCentsSatsUsdPattern(this, 'dca_cost_basis_5y'), - _6y: createCentsSatsUsdPattern(this, 'dca_cost_basis_6y'), - _8y: createCentsSatsUsdPattern(this, 'dca_cost_basis_8y'), - _10y: createCentsSatsUsdPattern(this, 'dca_cost_basis_10y'), - }, - return: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_return'), - cagr: create_10y2y3y4y5y6y8yPattern(this, 'dca_cagr'), - lumpSumStack: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(this, 'lump_sum_stack'), - lumpSumReturn: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_return'), - }, - class: { - stack: { - from2015: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2015'), - from2016: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2016'), - from2017: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2017'), - from2018: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2018'), - from2019: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2019'), - from2020: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2020'), - from2021: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2021'), - from2022: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2022'), - from2023: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2023'), - from2024: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2024'), - from2025: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2025'), - from2026: createBtcCentsSatsUsdPattern3(this, 'dca_stack_from_2026'), - }, - costBasis: { - from2015: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2015'), - from2016: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2016'), - from2017: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2017'), - from2018: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2018'), - from2019: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2019'), - from2020: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2020'), - from2021: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2021'), - from2022: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2022'), - from2023: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2023'), - from2024: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2024'), - from2025: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2025'), - from2026: createCentsSatsUsdPattern(this, 'dca_cost_basis_from_2026'), - }, - return: { - from2015: createBpsPercentRatioPattern(this, 'dca_return_from_2015'), - from2016: createBpsPercentRatioPattern(this, 'dca_return_from_2016'), - from2017: createBpsPercentRatioPattern(this, 'dca_return_from_2017'), - from2018: createBpsPercentRatioPattern(this, 'dca_return_from_2018'), - from2019: createBpsPercentRatioPattern(this, 'dca_return_from_2019'), - from2020: createBpsPercentRatioPattern(this, 'dca_return_from_2020'), - from2021: createBpsPercentRatioPattern(this, 'dca_return_from_2021'), - from2022: createBpsPercentRatioPattern(this, 'dca_return_from_2022'), - from2023: createBpsPercentRatioPattern(this, 'dca_return_from_2023'), - from2024: createBpsPercentRatioPattern(this, 'dca_return_from_2024'), - from2025: createBpsPercentRatioPattern(this, 'dca_return_from_2025'), - from2026: createBpsPercentRatioPattern(this, 'dca_return_from_2026'), - }, - }, - }, technical: { rsi: { _24h: createRsiStochPattern(this, 'rsi', '24h'), @@ -8569,7 +8607,7 @@ class BrkClient extends BrkClientBase { sats: createSeriesPattern1(this, 'realized_price_sats'), bps: createSeriesPattern1(this, 'realized_price_ratio_bps'), ratio: createSeriesPattern1(this, 'realized_price_ratio'), - percentiles: createPct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'realized_price'), + percentiles: createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'realized_price'), sma: create_1m1w1y2y4yAllPattern(this, 'realized_price_ratio_sma'), stdDev: { all: { @@ -8713,7 +8751,7 @@ class BrkClient extends BrkClientBase { sats: createSeriesPattern1(this, 'sth_realized_price_sats'), bps: createSeriesPattern1(this, 'sth_realized_price_ratio_bps'), ratio: createSeriesPattern1(this, 'sth_realized_price_ratio'), - percentiles: createPct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'sth_realized_price'), + percentiles: createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'sth_realized_price'), sma: create_1m1w1y2y4yAllPattern(this, 'sth_realized_price_ratio_sma'), stdDev: { all: { @@ -8812,7 +8850,7 @@ class BrkClient extends BrkClientBase { sats: createSeriesPattern1(this, 'lth_realized_price_sats'), bps: createSeriesPattern1(this, 'lth_realized_price_ratio_bps'), ratio: createSeriesPattern1(this, 'lth_realized_price_ratio'), - percentiles: createPct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'lth_realized_price'), + percentiles: createPct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(this, 'lth_realized_price'), sma: create_1m1w1y2y4yAllPattern(this, 'lth_realized_price_ratio_sma'), stdDev: { all: { diff --git a/modules/quickmatch-js/0.4.0/src/index.js b/modules/quickmatch-js/0.4.1/src/index.js similarity index 99% rename from modules/quickmatch-js/0.4.0/src/index.js rename to modules/quickmatch-js/0.4.1/src/index.js index d981b2993..b238ee553 100644 --- a/modules/quickmatch-js/0.4.0/src/index.js +++ b/modules/quickmatch-js/0.4.1/src/index.js @@ -238,6 +238,7 @@ export class QuickMatch { */ _rank(indices, minScore, qwords, sep, limit) { const { items, _scores: scores } = this; + /** @type {[number[], number[], number[]]} */ const buckets = [[], [], []]; // ps=0, ps=1, ps=2 for (let i = 0; i < indices.length; i++) { diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 897b6a965..7316e47ea 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -2306,6 +2306,20 @@ class BpsCentsPercentilesRatioSatsSmaStdUsdPattern: """Pattern struct for repeated tree structure.""" pass +class Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated series name.""" + self.pct0_5: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct0_5') + self.pct1: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct1') + self.pct2: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct2') + self.pct5: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct5') + self.pct95: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct95') + self.pct98: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct98') + self.pct99: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct99') + self.pct99_5: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct99_5') + class _10y2y3y4y5y6y8yPattern: """Pattern struct for repeated tree structure.""" @@ -2427,7 +2441,7 @@ class BpsCentsPercentilesRatioSatsUsdPattern: """Create pattern node with accumulated series name.""" self.bps: SeriesPattern1[BasisPoints32] = SeriesPattern1(client, _m(acc, 'ratio_bps')) self.cents: SeriesPattern1[Cents] = SeriesPattern1(client, _m(acc, 'cents')) - self.percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc) + self.percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, acc) self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, _m(acc, 'ratio')) self.sats: SeriesPattern1[SatsFract] = SeriesPattern1(client, _m(acc, 'sats')) self.usd: SeriesPattern1[Dollars] = SeriesPattern1(client, acc) @@ -2480,18 +2494,6 @@ class DeltaHalfInToTotalPattern2: self.to_circulating: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'to_circulating')) self.total: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, acc) -class Pct1Pct2Pct5Pct95Pct98Pct99Pattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated series name.""" - self.pct1: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct1') - self.pct2: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct2') - self.pct5: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct5') - self.pct95: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct95') - self.pct98: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct98') - self.pct99: BpsPriceRatioPattern = BpsPriceRatioPattern(client, acc, 'pct99') - class _1m1w1y24hBlockPattern: """Pattern struct for repeated tree structure.""" @@ -3251,9 +3253,7 @@ class SeriesTree_Blocks_Time: """Series tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.timestamp: SeriesPattern1[Timestamp] = SeriesPattern1(client, 'timestamp') - self.date: SeriesPattern18[Date] = SeriesPattern18(client, 'date') - self.timestamp_monotonic: SeriesPattern18[Timestamp] = SeriesPattern18(client, 'timestamp_monotonic') + self.timestamp: SeriesPattern18[Timestamp] = SeriesPattern18(client, 'timestamp') class SeriesTree_Blocks_Size: """Series tree node.""" @@ -4149,6 +4149,13 @@ class SeriesTree_Indexes_TxoutIndex: def __init__(self, client: BrkClientBase, base_path: str = ''): self.identity: SeriesPattern21[TxOutIndex] = SeriesPattern21(client, 'txout_index') +class SeriesTree_Indexes_Timestamp: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.monotonic: SeriesPattern18[Timestamp] = SeriesPattern18(client, 'timestamp_monotonic') + self.resolutions: SeriesPattern2[Timestamp] = SeriesPattern2(client, 'timestamp') + class SeriesTree_Indexes: """Series tree node.""" @@ -4173,6 +4180,7 @@ class SeriesTree_Indexes: self.tx_index: SeriesTree_Indexes_TxIndex = SeriesTree_Indexes_TxIndex(client) self.txin_index: SeriesTree_Indexes_TxinIndex = SeriesTree_Indexes_TxinIndex(client) self.txout_index: SeriesTree_Indexes_TxoutIndex = SeriesTree_Indexes_TxoutIndex(client) + self.timestamp: SeriesTree_Indexes_Timestamp = SeriesTree_Indexes_Timestamp(client) class SeriesTree_Indicators_Dormancy: """Series tree node.""" @@ -4181,6 +4189,21 @@ class SeriesTree_Indicators_Dormancy: self.supply_adjusted: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'dormancy_supply_adjusted') self.flow: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'dormancy_flow') +class SeriesTree_Indicators_Thermometer: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.pct0_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct0_5') + self.pct1: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct01') + self.pct2: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct02') + self.pct5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct05') + self.pct95: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct95') + self.pct98: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct98') + self.pct99: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct99') + self.pct99_5: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'thermometer_pct99_5') + self.zone: SeriesPattern1[StoredI8] = SeriesPattern1(client, 'thermometer_zone') + self.score: SeriesPattern1[StoredI8] = SeriesPattern1(client, 'thermometer_score') + class SeriesTree_Indicators: """Series tree node.""" @@ -4195,6 +4218,102 @@ class SeriesTree_Indicators: self.dormancy: SeriesTree_Indicators_Dormancy = SeriesTree_Indicators_Dormancy(client) self.stock_to_flow: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'stock_to_flow') self.seller_exhaustion: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'seller_exhaustion') + self.thermometer: SeriesTree_Indicators_Thermometer = SeriesTree_Indicators_Thermometer(client) + +class SeriesTree_Investing_Period_CostBasis: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self._1w: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_1w') + self._1m: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_1m') + self._3m: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_3m') + self._6m: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_6m') + self._1y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_1y') + self._2y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_2y') + self._3y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_3y') + self._4y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_4y') + self._5y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_5y') + self._6y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_6y') + self._8y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_8y') + self._10y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_10y') + +class SeriesTree_Investing_Period: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, 'dca_stack') + self.cost_basis: SeriesTree_Investing_Period_CostBasis = SeriesTree_Investing_Period_CostBasis(client) + self.return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_return') + self.cagr: _10y2y3y4y5y6y8yPattern = _10y2y3y4y5y6y8yPattern(client, 'dca_cagr') + self.lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, 'lump_sum_stack') + self.lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_return') + +class SeriesTree_Investing_Class_Stack: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.from_2015: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2015') + self.from_2016: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2016') + self.from_2017: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2017') + self.from_2018: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2018') + self.from_2019: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2019') + self.from_2020: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2020') + self.from_2021: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2021') + self.from_2022: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2022') + self.from_2023: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2023') + self.from_2024: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2024') + self.from_2025: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2025') + self.from_2026: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2026') + +class SeriesTree_Investing_Class_CostBasis: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.from_2015: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2015') + self.from_2016: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2016') + self.from_2017: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2017') + self.from_2018: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2018') + self.from_2019: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2019') + self.from_2020: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2020') + self.from_2021: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2021') + self.from_2022: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2022') + self.from_2023: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2023') + self.from_2024: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2024') + self.from_2025: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2025') + self.from_2026: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2026') + +class SeriesTree_Investing_Class_Return: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.from_2015: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2015') + self.from_2016: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2016') + self.from_2017: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2017') + self.from_2018: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2018') + self.from_2019: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2019') + self.from_2020: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2020') + self.from_2021: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2021') + self.from_2022: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2022') + self.from_2023: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2023') + self.from_2024: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2024') + self.from_2025: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2025') + self.from_2026: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2026') + +class SeriesTree_Investing_Class: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.stack: SeriesTree_Investing_Class_Stack = SeriesTree_Investing_Class_Stack(client) + self.cost_basis: SeriesTree_Investing_Class_CostBasis = SeriesTree_Investing_Class_CostBasis(client) + self.return_: SeriesTree_Investing_Class_Return = SeriesTree_Investing_Class_Return(client) + +class SeriesTree_Investing: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.sats_per_day: SeriesPattern18[Sats] = SeriesPattern18(client, 'dca_sats_per_day') + self.period: SeriesTree_Investing_Period = SeriesTree_Investing_Period(client) + self.class_: SeriesTree_Investing_Class = SeriesTree_Investing_Class(client) class SeriesTree_Market_Ath: """Series tree node.""" @@ -4370,101 +4489,6 @@ class SeriesTree_Market_MovingAverage: self.sma: SeriesTree_Market_MovingAverage_Sma = SeriesTree_Market_MovingAverage_Sma(client) self.ema: SeriesTree_Market_MovingAverage_Ema = SeriesTree_Market_MovingAverage_Ema(client) -class SeriesTree_Market_Dca_Period_CostBasis: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self._1w: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_1w') - self._1m: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_1m') - self._3m: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_3m') - self._6m: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_6m') - self._1y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_1y') - self._2y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_2y') - self._3y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_3y') - self._4y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_4y') - self._5y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_5y') - self._6y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_6y') - self._8y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_8y') - self._10y: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_10y') - -class SeriesTree_Market_Dca_Period: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, 'dca_stack') - self.cost_basis: SeriesTree_Market_Dca_Period_CostBasis = SeriesTree_Market_Dca_Period_CostBasis(client) - self.return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_return') - self.cagr: _10y2y3y4y5y6y8yPattern = _10y2y3y4y5y6y8yPattern(client, 'dca_cagr') - self.lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, 'lump_sum_stack') - self.lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_return') - -class SeriesTree_Market_Dca_Class_Stack: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.from_2015: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2015') - self.from_2016: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2016') - self.from_2017: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2017') - self.from_2018: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2018') - self.from_2019: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2019') - self.from_2020: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2020') - self.from_2021: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2021') - self.from_2022: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2022') - self.from_2023: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2023') - self.from_2024: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2024') - self.from_2025: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2025') - self.from_2026: BtcCentsSatsUsdPattern3 = BtcCentsSatsUsdPattern3(client, 'dca_stack_from_2026') - -class SeriesTree_Market_Dca_Class_CostBasis: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.from_2015: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2015') - self.from_2016: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2016') - self.from_2017: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2017') - self.from_2018: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2018') - self.from_2019: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2019') - self.from_2020: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2020') - self.from_2021: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2021') - self.from_2022: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2022') - self.from_2023: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2023') - self.from_2024: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2024') - self.from_2025: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2025') - self.from_2026: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'dca_cost_basis_from_2026') - -class SeriesTree_Market_Dca_Class_Return: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.from_2015: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2015') - self.from_2016: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2016') - self.from_2017: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2017') - self.from_2018: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2018') - self.from_2019: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2019') - self.from_2020: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2020') - self.from_2021: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2021') - self.from_2022: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2022') - self.from_2023: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2023') - self.from_2024: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2024') - self.from_2025: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2025') - self.from_2026: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'dca_return_from_2026') - -class SeriesTree_Market_Dca_Class: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.stack: SeriesTree_Market_Dca_Class_Stack = SeriesTree_Market_Dca_Class_Stack(client) - self.cost_basis: SeriesTree_Market_Dca_Class_CostBasis = SeriesTree_Market_Dca_Class_CostBasis(client) - self.return_: SeriesTree_Market_Dca_Class_Return = SeriesTree_Market_Dca_Class_Return(client) - -class SeriesTree_Market_Dca: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.sats_per_day: SeriesPattern18[Sats] = SeriesPattern18(client, 'dca_sats_per_day') - self.period: SeriesTree_Market_Dca_Period = SeriesTree_Market_Dca_Period(client) - self.class_: SeriesTree_Market_Dca_Class = SeriesTree_Market_Dca_Class(client) - class SeriesTree_Market_Technical_Rsi: """Series tree node.""" @@ -4529,7 +4553,6 @@ class SeriesTree_Market: self.volatility: _1m1w1y24hPattern[StoredF32] = _1m1w1y24hPattern(client, 'price_volatility') self.range: SeriesTree_Market_Range = SeriesTree_Market_Range(client) self.moving_average: SeriesTree_Market_MovingAverage = SeriesTree_Market_MovingAverage(client) - self.dca: SeriesTree_Market_Dca = SeriesTree_Market_Dca(client) self.technical: SeriesTree_Market_Technical = SeriesTree_Market_Technical(client) class SeriesTree_Pools_Major: @@ -4890,7 +4913,7 @@ class SeriesTree_Cohorts_Utxo_All_Realized_Price: self.sats: SeriesPattern1[SatsFract] = SeriesPattern1(client, 'realized_price_sats') self.bps: SeriesPattern1[BasisPoints32] = SeriesPattern1(client, 'realized_price_ratio_bps') self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'realized_price_ratio') - self.percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'realized_price') + self.percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'realized_price') self.sma: _1m1w1y2y4yAllPattern = _1m1w1y2y4yAllPattern(client, 'realized_price_ratio_sma') self.std_dev: SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev = SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev(client) @@ -5097,7 +5120,7 @@ class SeriesTree_Cohorts_Utxo_Sth_Realized_Price: self.sats: SeriesPattern1[SatsFract] = SeriesPattern1(client, 'sth_realized_price_sats') self.bps: SeriesPattern1[BasisPoints32] = SeriesPattern1(client, 'sth_realized_price_ratio_bps') self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'sth_realized_price_ratio') - self.percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'sth_realized_price') + self.percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'sth_realized_price') self.sma: _1m1w1y2y4yAllPattern = _1m1w1y2y4yAllPattern(client, 'sth_realized_price_ratio_sma') self.std_dev: SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev = SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev(client) @@ -5227,7 +5250,7 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized_Price: self.sats: SeriesPattern1[SatsFract] = SeriesPattern1(client, 'lth_realized_price_sats') self.bps: SeriesPattern1[BasisPoints32] = SeriesPattern1(client, 'lth_realized_price_ratio_bps') self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'lth_realized_price_ratio') - self.percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, 'lth_realized_price') + self.percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern = Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern(client, '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) @@ -5646,6 +5669,7 @@ class SeriesTree: self.constants: SeriesTree_Constants = SeriesTree_Constants(client) self.indexes: SeriesTree_Indexes = SeriesTree_Indexes(client) self.indicators: SeriesTree_Indicators = SeriesTree_Indicators(client) + self.investing: SeriesTree_Investing = SeriesTree_Investing(client) self.market: SeriesTree_Market = SeriesTree_Market(client) self.pools: SeriesTree_Pools = SeriesTree_Pools(client) self.prices: SeriesTree_Prices = SeriesTree_Prices(client) diff --git a/website/scripts/chart/index.js b/website/scripts/chart/index.js index 5b603df3f..8de1fb1bb 100644 --- a/website/scripts/chart/index.js +++ b/website/scripts/chart/index.js @@ -9,7 +9,7 @@ import { import { createLegend } from "./legend.js"; import { capture } from "./capture.js"; import { colors } from "../utils/colors.js"; -import { createRadios, createSelect } from "../utils/dom.js"; +import { createRadios, createSelect, getElementById } from "../utils/dom.js"; import { createPersistedValue } from "../utils/persisted.js"; import { onChange as onThemeChange } from "../utils/theme.js"; import { throttle, debounce } from "../utils/timing.js"; @@ -74,6 +74,71 @@ const lineWidth = /** @type {1} */ (/** @type {unknown} */ (1.5)); const MAX_SIZE = 10_000; +/** @typedef {{ label: string, index: IndexLabel, from: number }} RangePreset */ + +/** @returns {RangePreset[]} */ +function getRangePresets() { + const nowSec = Math.floor(Date.now() / 1000); + const now = new Date(); + const y = now.getUTCFullYear(); + const m = now.getUTCMonth(); + const d = now.getUTCDate(); + /** @param {number} n */ + const monthsAgo = (n) => Math.floor(Date.UTC(y, m - n, d) / 1000); + + /** @type {RangePreset[]} */ + const presets = [ + { + label: "1w", + index: /** @type {IndexLabel} */ ("30mn"), + from: nowSec - 7 * 86_400, + }, + { + label: "1m", + index: /** @type {IndexLabel} */ ("1h"), + from: monthsAgo(1), + }, + { + label: "3m", + index: /** @type {IndexLabel} */ ("4h"), + from: monthsAgo(3), + }, + { + label: "6m", + index: /** @type {IndexLabel} */ ("12h"), + from: monthsAgo(6), + }, + { + label: "1y", + index: /** @type {IndexLabel} */ ("1d"), + from: monthsAgo(12), + }, + { + label: "4y", + index: /** @type {IndexLabel} */ ("3d"), + from: monthsAgo(48), + }, + ]; + + // Insert ytd at the right position + const ytdFrom = Math.floor(Date.UTC(y, 0, 1) / 1000); + const ri = presets.findIndex((e) => e.from <= ytdFrom); + const insertAt = ri === -1 ? presets.length : ri; + presets.splice(insertAt, 0, { + label: "ytd", + index: presets[ri === -1 ? presets.length - 1 : ri].index, + from: ytdFrom, + }); + + presets.push({ + label: "all", + index: /** @type {IndexLabel} */ ("1w"), + from: -Infinity, + }); + + return presets; +} + /** * @param {Object} args * @param {HTMLElement} args.parent @@ -89,8 +154,8 @@ export function createChart({ parent, brk, fitContent }) { /** @param {ChartableIndex} idx */ const getTimeEndpoint = (idx) => idx === "height" - ? brk.series.blocks.time.timestampMonotonic.by[idx] - : brk.series.blocks.time.timestamp.by[idx]; + ? brk.series.indexes.timestamp.monotonic.by[idx] + : brk.series.indexes.timestamp.resolutions.by[idx]; const index = { /** @type {Set<(index: ChartableIndex) => void>} */ @@ -204,10 +269,6 @@ export function createChart({ parent, brk, fitContent }) { enableResize: false, }, }, - grid: { - vertLines: { visible: false }, - horzLines: { visible: false }, - }, rightPriceScale: { borderVisible: false, }, @@ -290,6 +351,8 @@ export function createChart({ parent, brk, fitContent }) { const defaultColor = colors.default(); const offColor = colors.gray(); const borderColor = colors.border(); + const offBorderColor = colors.offBorder(); + console.log(borderColor); ichart.applyOptions({ layout: { textColor: offColor, @@ -307,6 +370,14 @@ export function createChart({ parent, brk, fitContent }) { labelBackgroundColor: defaultColor, }, }, + grid: { + horzLines: { + color: offBorderColor, + }, + vertLines: { + color: offBorderColor, + }, + }, }); } applyColors(); @@ -402,8 +473,9 @@ export function createChart({ parent, brk, fitContent }) { const pane = ichart.panes().at(paneIndex); if (!pane) return; if (this.isAllHidden(paneIndex)) { + const collapsedHeight = paneIndex === 0 ? 32 : 64; const chartHeight = ichart.chartElement().clientHeight; - pane.setStretchFactor(chartHeight > 0 ? 48 / (chartHeight - 48) : 0); + pane.setStretchFactor(chartHeight > 0 ? collapsedHeight / (chartHeight - collapsedHeight) : 0); } else { pane.setStretchFactor(1); } @@ -885,6 +957,7 @@ export function createChart({ parent, brk, fitContent }) { * @param {Unit} args.unit * @param {string} [args.key] - Optional key for persistence (derived from name if not provided) * @param {Color | [Color, Color]} [args.color] - Single color or [positive, negative] colors + * @param {(value: number) => Color} [args.colorFn] * @param {number} [args.paneIndex] * @param {boolean} [args.defaultActive] * @param {HistogramSeriesPartialOptions} [args.options] @@ -894,6 +967,7 @@ export function createChart({ parent, brk, fitContent }) { name, key, color = colors.bi.p1, + colorFn, order, unit, paneIndex = 0, @@ -930,7 +1004,17 @@ export function createChart({ parent, brk, fitContent }) { }); }, setData: (data) => { - if (isDualColor) { + if (colorFn) { + iseries.setData( + data.map((d) => ({ + ...d, + color: + "value" in d + ? (colorFn(d.value) ?? (() => "transparent"))() + : "transparent", + })), + ); + } else if (isDualColor) { iseries.setData( data.map((d) => ({ ...d, @@ -957,6 +1041,7 @@ export function createChart({ parent, brk, fitContent }) { * @param {Unit} args.unit * @param {string} [args.key] - Optional key for persistence (derived from name if not provided) * @param {Color} args.color + * @param {(value: number) => Color} [args.colorFn] * @param {number} [args.paneIndex] * @param {boolean} [args.defaultActive] * @param {LineSeriesPartialOptions} [args.options] @@ -967,6 +1052,7 @@ export function createChart({ parent, brk, fitContent }) { key, order, color, + colorFn, unit, paneIndex = 0, defaultActive, @@ -999,7 +1085,18 @@ export function createChart({ parent, brk, fitContent }) { color: color.highlight(highlighted), }); }, - setData: (data) => iseries.setData(data), + setData: (data) => { + if (colorFn) { + iseries.setData( + data.map((d) => ({ + ...d, + color: "value" in d ? (colorFn(d.value) ?? color)() : color(), + })), + ); + } else { + iseries.setData(data); + } + }, update: (data) => iseries.update(data), getData: () => iseries.data(), onRemove: () => ichart.removeSeries(iseries), @@ -1373,7 +1470,11 @@ export function createChart({ parent, brk, fitContent }) { break; case "Histogram": pane.series.push( - serieses.addHistogram({ ...common, color: blueprint.color }), + serieses.addHistogram({ + ...common, + color: blueprint.color, + colorFn: blueprint.colorFn, + }), ); break; case "Candlestick": @@ -1414,6 +1515,7 @@ export function createChart({ parent, brk, fitContent }) { serieses.addLine({ ...common, color: blueprint.color ?? defaultColor, + colorFn: blueprint.colorFn, }), ); } @@ -1443,9 +1545,35 @@ export function createChart({ parent, brk, fitContent }) { /** @type {HTMLElement | null} */ let indexField = null; - const lastTd = ichart - .chartElement() - .querySelector("table > tr:last-child > td:last-child"); + /** @param {RangePreset} preset */ + function applyPreset(preset) { + preferredIndex = preset.index; + /** @type {HTMLSelectElement} */ (getElementById("index")).value = + preset.index; + index.name.set(preset.index); + + const targetGen = generation; + const waitAndApply = () => { + if (generation !== targetGen) return; + if (!initialLoadComplete) { + requestAnimationFrame(waitAndApply); + return; + } + const data = blueprints.panes[0].series[0]?.getData(); + if (!data?.length) return; + const from = isFinite(preset.from) + ? (data.findIndex( + (d) => /** @type {number} */ (d.time) >= preset.from, + ) ?? 0) + : 0; + const padding = Math.round((data.length - from) * 0.025); + ichart.timeScale().setVisibleLogicalRange({ + from: from - padding, + to: data.length + padding, + }); + }; + requestAnimationFrame(waitAndApply); + } const chart = { get panes() { @@ -1464,7 +1592,13 @@ export function createChart({ parent, brk, fitContent }) { index.name.set(currentValue); } - indexField = createSelect({ + indexField = window.document.createElement("div"); + indexField.classList.add("index-bar"); + + const scroller = window.document.createElement("div"); + indexField.append(scroller); + + const selectField = createSelect({ initialValue: currentValue, onChange: (v) => { preferredIndex = v; @@ -1474,7 +1608,20 @@ export function createChart({ parent, brk, fitContent }) { groups, id: "index", }); - if (lastTd) lastTd.append(indexField); + scroller.append(selectField); + + const sep = window.document.createElement("span"); + sep.textContent = "|"; + scroller.append(sep); + + for (const preset of getRangePresets()) { + const btn = window.document.createElement("button"); + btn.textContent = preset.label; + btn.addEventListener("click", () => applyPreset(preset)); + scroller.append(btn); + } + + chartEl.append(indexField); }, /** diff --git a/website/scripts/options/investing.js b/website/scripts/options/investing.js index f5d4591c4..fa9efb57a 100644 --- a/website/scripts/options/investing.js +++ b/website/scripts/options/investing.js @@ -69,19 +69,19 @@ const ALL_YEARS = /** @type {const} */ ([...YEARS_2020S, ...YEARS_2010S]); /** * Build DCA class entry from year - * @param {MarketDca} dca + * @param {Investing} investing * @param {DcaYear} year * @param {number} i * @returns {BaseEntryItem} */ -function buildYearEntry(dca, year, i) { +function buildYearEntry(investing, year, i) { const key = /** @type {DcaYearKey} */ (`from${year}`); return { name: `${year}`, color: colors.at(i, ALL_YEARS.length), - costBasis: dca.class.costBasis[key], - returns: dca.class.return[key], - stack: dca.class.stack[key], + costBasis: investing.class.costBasis[key], + returns: investing.class.return[key], + stack: investing.class.stack[key], }; } @@ -90,16 +90,16 @@ function buildYearEntry(dca, year, i) { * @returns {PartialOptionsGroup} */ export function createInvestingSection() { - const { market } = brk.series; - const { dca, lookback, returns } = market; + const { market, investing } = brk.series; + const { lookback, returns } = market; return { name: "Investing", tree: [ - createDcaVsLumpSumSection({ dca, lookback, returns }), - createDcaByPeriodSection({ dca, returns }), - createLumpSumByPeriodSection({ dca, lookback, returns }), - createDcaByStartYearSection({ dca }), + createDcaVsLumpSumSection({ investing, lookback, returns }), + createDcaByPeriodSection({ investing, returns }), + createLumpSumByPeriodSection({ investing, lookback, returns }), + createDcaByStartYearSection({ investing }), ], }; } @@ -270,15 +270,15 @@ function createLongSingleEntry(item) { /** * Create DCA vs Lump Sum section * @param {Object} args - * @param {Market["dca"]} args.dca + * @param {Investing} args.investing * @param {Market["lookback"]} args.lookback * @param {Market["returns"]} args.returns */ -export function createDcaVsLumpSumSection({ dca, lookback, returns }) { +export function createDcaVsLumpSumSection({ investing, lookback, returns }) { /** @param {AllPeriodKey} key */ const topPane = (key) => [ price({ - series: dca.period.costBasis[key], + series: investing.period.costBasis[key], name: "DCA", color: colors.profit, }), @@ -299,11 +299,11 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) { top: topPane(key), bottom: [ ...percentRatioBaseline({ - pattern: dca.period.return[key], + pattern: investing.period.return[key], name: "DCA", }), ...percentRatioBaseline({ - pattern: dca.period.lumpSumReturn[key], + pattern: investing.period.lumpSumReturn[key], name: "Lump Sum", color: colors.bi.p2, }), @@ -317,7 +317,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) { top: topPane(key), bottom: [ ...percentRatioBaseline({ - pattern: dca.period.cagr[key], + pattern: investing.period.cagr[key], name: "DCA", }), ...percentRatioBaseline({ @@ -335,12 +335,12 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) { top: topPane(key), bottom: [ ...satsBtcUsd({ - pattern: dca.period.stack[key], + pattern: investing.period.stack[key], name: "DCA", color: colors.profit, }), ...satsBtcUsd({ - pattern: dca.period.lumpSumStack[key], + pattern: investing.period.lumpSumStack[key], name: "Lump Sum", color: colors.bitcoin, }), @@ -395,11 +395,11 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) { /** * Create period-based section (DCA or Lump Sum) * @param {Object} args - * @param {Market["dca"]} args.dca + * @param {Investing} args.investing * @param {Market["lookback"]} [args.lookback] * @param {Market["returns"]} args.returns */ -function createPeriodSection({ dca, lookback, returns }) { +function createPeriodSection({ investing, lookback, returns }) { const isLumpSum = !!lookback; const suffix = isLumpSum ? "Lump Sum" : "DCA"; @@ -409,20 +409,20 @@ function createPeriodSection({ dca, lookback, returns }) { const buildBaseEntry = (key, i) => ({ name: periodName(key), color: colors.at(i, allPeriods.length), - costBasis: isLumpSum ? lookback[key] : dca.period.costBasis[key], + costBasis: isLumpSum ? lookback[key] : investing.period.costBasis[key], returns: isLumpSum - ? dca.period.lumpSumReturn[key] - : dca.period.return[key], + ? investing.period.lumpSumReturn[key] + : investing.period.return[key], stack: isLumpSum - ? dca.period.lumpSumStack[key] - : dca.period.stack[key], + ? investing.period.lumpSumStack[key] + : investing.period.stack[key], }); /** @param {LongPeriodKey} key @param {number} i @returns {LongEntryItem} */ const buildLongEntry = (key, i) => withCagr( buildBaseEntry(key, i), - isLumpSum ? returns.cagr[key] : dca.period.cagr[key], + isLumpSum ? returns.cagr[key] : investing.period.cagr[key], ); /** @param {BaseEntryItem} entry */ @@ -471,30 +471,30 @@ function createPeriodSection({ dca, lookback, returns }) { /** * Create DCA by Period section * @param {Object} args - * @param {Market["dca"]} args.dca + * @param {Investing} args.investing * @param {Market["returns"]} args.returns */ -export function createDcaByPeriodSection({ dca, returns }) { - return createPeriodSection({ dca, returns }); +export function createDcaByPeriodSection({ investing, returns }) { + return createPeriodSection({ investing, returns }); } /** * Create Lump Sum by Period section * @param {Object} args - * @param {Market["dca"]} args.dca + * @param {Investing} args.investing * @param {Market["lookback"]} args.lookback * @param {Market["returns"]} args.returns */ -export function createLumpSumByPeriodSection({ dca, lookback, returns }) { - return createPeriodSection({ dca, lookback, returns }); +export function createLumpSumByPeriodSection({ investing, lookback, returns }) { + return createPeriodSection({ investing, lookback, returns }); } /** * Create DCA by Start Year section * @param {Object} args - * @param {Market["dca"]} args.dca + * @param {Investing} args.investing */ -export function createDcaByStartYearSection({ dca }) { +export function createDcaByStartYearSection({ investing }) { /** @param {string} name @param {string} title @param {BaseEntryItem[]} entries */ const createDecadeGroup = (name, title, entries) => ({ name, @@ -511,10 +511,10 @@ export function createDcaByStartYearSection({ dca }) { }); const entries2020s = YEARS_2020S.map((year, i) => - buildYearEntry(dca, year, i), + buildYearEntry(investing, year, i), ); const entries2010s = YEARS_2010S.map((year, i) => - buildYearEntry(dca, year, YEARS_2020S.length + i), + buildYearEntry(investing, year, YEARS_2020S.length + i), ); return { diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js index 6fa5f4881..3739eecfa 100644 --- a/website/scripts/options/market.js +++ b/website/scripts/options/market.js @@ -16,7 +16,7 @@ import { ROLLING_WINDOWS, ROLLING_WINDOWS_TO_1M, } from "./series.js"; -import { simplePriceRatioTree } from "./shared.js"; +import { simplePriceRatioTree, percentileBands, priceBands } from "./shared.js"; import { periodIdToName } from "./utils.js"; /** @@ -165,7 +165,10 @@ function returnsSubSectionWithCagr(name, periods) { ...periods.map((p) => ({ name: periodIdToName(p.id, true), title: `${periodIdToName(p.id, true)} Total Price Returns`, - bottom: percentRatioBaseline({ pattern: p.returns, name: "Return" }), + bottom: percentRatioBaseline({ + pattern: p.returns, + name: "Return", + }), })), ], }, @@ -1084,6 +1087,46 @@ export function createMarketSection() { color: colors.loss, }), }, + { + name: "Thermometer", + tree: [ + { + name: "Bands", + title: "Thermometer", + top: priceBands(percentileBands(indicators.thermometer), { defaultActive: true }), + }, + { + name: "Score", + title: "Thermometer", + top: priceBands(percentileBands(indicators.thermometer)), + bottom: [ + histogram({ + series: indicators.thermometer.zone, + name: "Zone", + unit: Unit.count, + colorFn: (v) => /** @type {const} */ ([ + colors.ratioPct._0_5, + colors.ratioPct._1, + colors.ratioPct._2, + colors.ratioPct._5, + colors.transparent, + colors.ratioPct._95, + colors.ratioPct._98, + colors.ratioPct._99, + colors.ratioPct._99_5, + ])[v + 4], + }), + baseline({ + series: indicators.thermometer.score, + name: "Score", + unit: Unit.count, + color: [colors.ratioPct._99, colors.ratioPct._1], + defaultActive: false, + }), + ], + }, + ], + }, ], }, ], diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 9fc363a37..2a70c41dc 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -147,6 +147,7 @@ function percentileSeries({ pattern, unit, title = "" }) { * @param {string} [args.key] - Optional key for persistence (derived from name if not provided) * @param {LineStyle} [args.style] * @param {Color} [args.color] + * @param {(value: number) => Color} [args.colorFn] * @param {boolean} [args.defaultActive] * @param {LineSeriesPartialOptions} [args.options] * @returns {FetchedLineSeriesBlueprint} @@ -157,6 +158,7 @@ export function line({ key, style, color, + colorFn, defaultActive, unit, options, @@ -166,6 +168,7 @@ export function line({ title: name, key, color, + colorFn, unit, defaultActive, options: { @@ -370,6 +373,7 @@ export function dotsBaseline({ * @param {Unit} args.unit * @param {string} [args.key] - Optional key for persistence (derived from name if not provided) * @param {Color | [Color, Color]} [args.color] + * @param {(value: number) => Color} [args.colorFn] * @param {boolean} [args.defaultActive] * @param {HistogramSeriesPartialOptions} [args.options] * @returns {FetchedHistogramSeriesBlueprint} @@ -379,6 +383,7 @@ export function histogram({ name, key, color, + colorFn, defaultActive, unit, options, @@ -389,6 +394,7 @@ export function histogram({ title: name, key, color, + colorFn, unit, defaultActive, options, diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index c655e3c32..a593229c5 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -1,7 +1,13 @@ /** Shared helpers for options */ import { Unit } from "../utils/units.js"; -import { ROLLING_WINDOWS, line, baseline, price, sumsAndAveragesCumulativeWith } from "./series.js"; +import { + ROLLING_WINDOWS, + line, + baseline, + price, + sumsAndAveragesCumulativeWith, +} from "./series.js"; import { priceLine, priceLines } from "./constants.js"; import { colors } from "../utils/colors.js"; @@ -270,11 +276,19 @@ export function simplePriceRatioTree({ pattern, title, legend, color }) { } /** - * @template T - * @param {InvestorPercentilesPattern} p - * @param {(entry: InvestorPercentileEntry) => T} extract + * @param {{ pct95: AnyPricePattern, pct5: AnyPricePattern, pct98: AnyPricePattern, pct2: AnyPricePattern, pct99: AnyPricePattern, pct1: AnyPricePattern, pct995: AnyPricePattern, pct05: AnyPricePattern }} p */ -function percentileBands(p, extract) { +export function percentileBands(p) { + return percentileBandsWith(p, (e) => e); +} + +/** + * @template E + * @template T + * @param {{ pct95: E, pct5: E, pct98: E, pct2: E, pct99: E, pct1: E, pct995: E, pct05: E }} p + * @param {(entry: E) => T} extract + */ +export function percentileBandsWith(p, extract) { return [ { name: "P95", prop: extract(p.pct95), color: colors.ratioPct._95 }, { name: "P5", prop: extract(p.pct5), color: colors.ratioPct._5 }, @@ -282,20 +296,38 @@ function percentileBands(p, extract) { { name: "P2", prop: extract(p.pct2), color: colors.ratioPct._2 }, { name: "P99", prop: extract(p.pct99), color: colors.ratioPct._99 }, { name: "P1", prop: extract(p.pct1), color: colors.ratioPct._1 }, + { name: "P99.5", prop: extract(p.pct995), color: colors.ratioPct._99_5 }, + { name: "P0.5", prop: extract(p.pct05), color: colors.ratioPct._0_5 }, ]; } -/** @param {{ name: string, prop: AnyPricePattern, color: Color }[]} bands */ -function priceBands(bands) { +/** + * @param {{ name: string, prop: AnyPricePattern, color: Color }[]} bands + * @param {{ defaultActive?: boolean }} [opts] + */ +export function priceBands(bands, opts) { return bands.map(({ name, prop, color }) => - price({ series: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }), + price({ + series: prop, + name, + color, + defaultActive: opts?.defaultActive ?? false, + options: { lineStyle: 1 }, + }), ); } /** @param {{ name: string, prop: AnySeriesPattern, color: Color }[]} bands */ function ratioBands(bands) { return bands.map(({ name, prop, color }) => - line({ series: prop, name, color, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }), + line({ + series: prop, + name, + color, + defaultActive: false, + unit: Unit.ratio, + options: { lineStyle: 1 }, + }), ); } @@ -319,8 +351,8 @@ export function priceRatioPercentilesTree({ priceReferences, }) { const p = pattern.percentiles; - const pctUsd = percentileBands(p, (e) => e.price); - const pctRatio = percentileBands(p, (e) => e.ratio); + const pctUsd = percentileBandsWith(p, (e) => e.price); + const pctRatio = percentileBandsWith(p, (e) => e.ratio); return [ { name: "Price", @@ -500,7 +532,11 @@ export function ratioSmas(ratio) { { name: "1y SMA", series: ratio.sma._1y.ratio }, { name: "2y SMA", series: ratio.sma._2y.ratio }, { name: "4y SMA", series: ratio.sma._4y.ratio }, - { name: "All Time SMA", series: ratio.sma.all.ratio, color: colors.time.all }, + { + name: "All Time SMA", + series: ratio.sma.all.ratio, + color: colors.time.all, + }, ].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s })); } @@ -543,7 +579,14 @@ export function ratioBottomSeries(ratio) { * @param {string} [args.legend] * @returns {PartialChartOption} */ -export function createRatioChart({ title, pricePattern, ratio, color, name, legend }) { +export function createRatioChart({ + title, + pricePattern, + ratio, + color, + name, + legend, +}) { return { name: name ?? "Ratio", title: title(name ?? "Ratio"), @@ -727,7 +770,7 @@ export function createPriceRatioCharts({ priceReferences, }) { const titleFn = formatCohortTitle(context); - const pctUsd = percentileBands(ratio.percentiles, (e) => e.price); + const pctUsd = percentileBandsWith(ratio.percentiles, (e) => e.price); return [ { name: "Price", @@ -775,20 +818,39 @@ export function createPriceRatioCharts({ * @param {Unit} args.unit * @returns {PartialOptionsTree} */ -export function groupedWindowsCumulative({ list, all, title, metricTitle, getWindowSeries, getCumulativeSeries, seriesFn, unit }) { +export function groupedWindowsCumulative({ + list, + all, + title, + metricTitle, + getWindowSeries, + getCumulativeSeries, + seriesFn, + unit, +}) { return [ ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: title(`${w.title} ${metricTitle}`), bottom: mapCohortsWithAll(list, all, (c) => - seriesFn({ series: getWindowSeries(c, w.key), name: c.name, color: c.color, unit }), + seriesFn({ + series: getWindowSeries(c, w.key), + name: c.name, + color: c.color, + unit, + }), ), })), { name: "Cumulative", title: title(`Cumulative ${metricTitle}`), bottom: mapCohortsWithAll(list, all, (c) => - seriesFn({ series: getCumulativeSeries(c), name: c.name, color: c.color, unit }), + seriesFn({ + series: getCumulativeSeries(c), + name: c.name, + color: c.color, + unit, + }), ), }, ]; @@ -807,9 +869,21 @@ export function groupedWindowsCumulative({ list, all, title, metricTitle, getWin * @param {(args: { series: AnySeriesPattern, name: string, color: Color, unit: Unit }) => AnyFetchedSeriesBlueprint} [args.seriesFn] * @returns {PartialOptionsTree} */ -export function groupedWindowsCumulativeUsd({ list, all, title, metricTitle, getMetric, seriesFn = line }) { +export function groupedWindowsCumulativeUsd({ + list, + all, + title, + metricTitle, + getMetric, + seriesFn = line, +}) { return groupedWindowsCumulative({ - list, all, title, metricTitle, seriesFn, unit: Unit.usd, + list, + all, + title, + metricTitle, + seriesFn, + unit: Unit.usd, getWindowSeries: (c, key) => getMetric(c).sum[key].usd, getCumulativeSeries: (c) => getMetric(c).cumulative.usd, }); @@ -827,20 +901,34 @@ export function groupedWindowsCumulativeUsd({ list, all, title, metricTitle, get * @param {(c: T | A) => { sum: Record, cumulative: AnyValuePattern }} args.getMetric * @returns {PartialOptionsTree} */ -export function groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle, getMetric }) { +export function groupedWindowsCumulativeSatsBtcUsd({ + list, + all, + title, + metricTitle, + getMetric, +}) { return [ ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: title(`${w.title} ${metricTitle}`), bottom: flatMapCohortsWithAll(list, all, (c) => - satsBtcUsd({ pattern: getMetric(c).sum[w.key], name: c.name, color: c.color }), + satsBtcUsd({ + pattern: getMetric(c).sum[w.key], + name: c.name, + color: c.color, + }), ), })), { name: "Cumulative", title: title(`Cumulative ${metricTitle}`), bottom: flatMapCohortsWithAll(list, all, (c) => - satsBtcUsd({ pattern: getMetric(c).cumulative, name: c.name, color: c.color }), + satsBtcUsd({ + pattern: getMetric(c).cumulative, + name: c.name, + color: c.color, + }), ), }, ]; diff --git a/website/scripts/options/types.js b/website/scripts/options/types.js index 99969f735..b4d60bd78 100644 --- a/website/scripts/options/types.js +++ b/website/scripts/options/types.js @@ -20,12 +20,14 @@ * @typedef {Object} LineSeriesBlueprintSpecific * @property {"Line"} [type] * @property {Color} [color] + * @property {(value: number) => Color} [colorFn] * @property {LineSeriesPartialOptions} [options] * @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint * * @typedef {Object} HistogramSeriesBlueprintSpecific * @property {"Histogram"} type * @property {Color | [Color, Color]} [color] - Single color or [positive, negative] colors (defaults to green/red) + * @property {(value: number) => Color} [colorFn] * @property {HistogramSeriesPartialOptions} [options] * @typedef {BaseSeriesBlueprint & HistogramSeriesBlueprintSpecific} HistogramSeriesBlueprint * diff --git a/website/scripts/panes/search.js b/website/scripts/panes/search.js index 3fa5b076f..a7be25cd0 100644 --- a/website/scripts/panes/search.js +++ b/website/scripts/panes/search.js @@ -3,7 +3,7 @@ import { searchLabelElement, searchResultsElement, } from "../utils/elements.js"; -import { QuickMatch } from "../modules/quickmatch-js/0.4.0/src/index.js"; +import { QuickMatch } from "../modules/quickmatch-js/0.4.1/src/index.js"; /** * @param {Options} options diff --git a/website/scripts/types.js b/website/scripts/types.js index 1739c36fb..d226815b2 100644 --- a/website/scripts/types.js +++ b/website/scripts/types.js @@ -180,7 +180,7 @@ * Tree branch types * @typedef {Brk.SeriesTree_Market} Market * @typedef {Brk.SeriesTree_Market_MovingAverage} MarketMovingAverage - * @typedef {Brk.SeriesTree_Market_Dca} MarketDca + * @typedef {Brk.SeriesTree_Investing} Investing * @typedef {Brk._10y2y3y4y5y6y8yPattern} PeriodCagrPattern * @typedef {FullStatsPattern} AnyFullStatsPattern * @@ -243,7 +243,7 @@ * @typedef {Brk.AbsoluteRatePattern2} FiatDeltaPattern * * Investor price percentiles (pct1/2/5/95/98/99) - * @typedef {Brk.Pct1Pct2Pct5Pct95Pct98Pct99Pattern} InvestorPercentilesPattern + * @typedef {Brk.Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} InvestorPercentilesPattern * @typedef {Brk.BpsPriceRatioPattern} InvestorPercentileEntry * * Generic tree node type for walking diff --git a/website/scripts/utils/colors.js b/website/scripts/utils/colors.js index 06eeedf04..c0e1a31e1 100644 --- a/website/scripts/utils/colors.js +++ b/website/scripts/utils/colors.js @@ -124,9 +124,11 @@ function seq(keys) { } export const colors = { + transparent: createColor(() => "transparent"), default: createColor(() => getLightDarkValue("--color")), gray: createColor(() => getColor("gray")), border: createColor(() => getLightDarkValue("--border-color")), + offBorder: createColor(() => getLightDarkValue("--off-border-color")), // Directional profit: palette.green, @@ -214,12 +216,14 @@ export const colors = { // Ratio percentile bands (extreme values) ratioPct: { - _99: palette.rose, - _98: palette.pink, - _95: palette.fuchsia, - _5: palette.teal, + _99_5: palette.red, + _99: palette.orange, + _98: palette.amber, + _95: palette.yellow, + _5: palette.cyan, _2: palette.sky, - _1: palette.indigo, + _1: palette.blue, + _0_5: palette.indigo, }, // Standard deviation bands (warm = positive, cool = negative) diff --git a/website/styles/chart.css b/website/styles/chart.css index a4e999847..6863d7f2c 100644 --- a/website/styles/chart.css +++ b/website/styles/chart.css @@ -7,8 +7,8 @@ label, select { - margin: -0.5rem; - padding: 0.5rem; + margin: -0.375rem; + padding: 0.375rem; } select { @@ -67,7 +67,7 @@ overflow-x: auto; scrollbar-width: thin; padding: 0 var(--main-padding); - padding-top: 0.278rem; + padding-top: 0.375rem; padding-bottom: 0.75rem; > * { pointer-events: auto; @@ -210,6 +210,7 @@ display: inline-flex; font-size: var(--font-size-xs); align-items: center; + text-transform: uppercase; } tr:not(:last-child) > td:last-child > .field { @@ -217,7 +218,6 @@ right: 0; gap: 0.375rem; background-color: var(--background-color); - text-transform: uppercase; padding-left: 0.625rem; padding-top: 0.35rem; padding-bottom: 0.125rem; @@ -237,8 +237,74 @@ } } - tr:last-child > td:last-child > .field { - bottom: 2.125rem; + .index-bar { + position: absolute; + bottom: 1.8rem; + left: calc(var(--main-padding) * -1); + right: 50px; + z-index: 20; + font-size: var(--font-size-xs); + line-height: var(--line-height-xs); + text-transform: uppercase; + pointer-events: none; + + &::before, + &::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + width: var(--main-padding); + z-index: 1; + pointer-events: none; + } + + &::before { + left: 0; + background-image: linear-gradient( + to left, + transparent, + var(--background-color) + ); + } + + &::after { + right: 0; + background-image: linear-gradient( + to right, + transparent, + var(--background-color) + ); + } + + > div { + display: flex; + align-items: center; + overflow-x: auto; + scrollbar-width: thin; + padding: 0 var(--main-padding); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + + > * { + pointer-events: auto; + flex-shrink: 0; + } + + > span { + padding: 0 0.75rem; + } + + button { + color: var(--off-color); + padding: 0.375rem; + margin: -0.375rem 0rem; + + &:hover { + color: var(--color); + } + } + } } button.capture { diff --git a/website/styles/variables.css b/website/styles/variables.css index 462c61c1b..7638c7695 100644 --- a/website/styles/variables.css +++ b/website/styles/variables.css @@ -1,13 +1,13 @@ :root { color-scheme: light dark; - /*--white: oklch(90% 0 0);*/ - --white: oklch(93.3% 0.006 75); - /*oklch(0.9333 0.0059 59.65)*/ - --light-gray: oklch(85% 0.01 75); - --gray: oklch(60% 0.01 44); - --dark-gray: oklch(25% 0.006 90); - --black: oklch(16.5% 0.006 90); + --white: oklch(95% 0 0); + --dark-white: oklch(92.5% 0 0); + --light-gray: oklch(90% 0 0); + --gray: oklch(55% 0 0); + --dark-gray: oklch(20% 0 0); + --light-black: oklch(17.5% 0 0); + --black: oklch(15% 0 0); /*oklch(0.2038 0.0076 196.57)*/ --red: oklch(0.607 0.241 26.328); --orange: oklch(67.64% 0.191 44.41); @@ -31,6 +31,7 @@ --color: light-dark(var(--black), var(--white)); --off-color: var(--gray); --border-color: light-dark(var(--light-gray), var(--dark-gray)); + --off-border-color: light-dark(var(--dark-white), var(--light-black)); --font-size-xs: 0.75rem; --line-height-xs: calc(1 / 0.75);