diff --git a/Cargo.lock b/Cargo.lock index 4a91e89ab..45f903c4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -682,6 +682,7 @@ dependencies = [ "serde_json", "tokio", "tower-http", + "tower-layer", "tracing", "vecdb", ] @@ -1822,9 +1823,8 @@ dependencies = [ [[package]] name = "importmap" version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f650142f4c83e122ae10a49dbaa88593dc4b20158124409856adbae847775938" dependencies = [ + "include_dir", "rapidhash", "serde", "serde_json", diff --git a/crates/brk_cli/README.md b/crates/brk_cli/README.md index 45a787930..3af82ef5e 100644 --- a/crates/brk_cli/README.md +++ b/crates/brk_cli/README.md @@ -1,90 +1,73 @@ # brk_cli -Command-line interface for running the Bitcoin Research Kit. +Command-line interface for running a Bitcoin Research Kit instance. -## What It Enables +## Preview -Run a full BRK instance: index the blockchain, compute metrics, serve the API, and optionally host a web interface. Continuously syncs with new blocks. - -## Key Features - -- **All-in-one**: Single binary runs indexer, computer, mempool monitor, and server -- **Auto-sync**: Waits for new blocks and processes them automatically -- **Web interface**: Downloads and bundles frontend from GitHub releases -- **Configurable**: TOML config for RPC, paths, and features -- **Collision checking**: Optional TXID collision validation mode -- **Memory optimized**: Uses mimalloc allocator, 512MB stack for deep recursion - -## Install - -First, ensure Rust is up to date: - -```bash -rustup update -``` - -Recommended (optimized for your CPU, auto-finds latest version): - -```bash -RUSTFLAGS="-C target-cpu=native" cargo install --locked brk_cli --version "$(cargo search brk_cli | head -1 | awk -F'"' '{print $2}')" -``` - -**Variants:** - -```bash -# Standard install (portable, latest stable only) -cargo install --locked brk_cli - -# Specific version -cargo install --locked brk_cli --version "0.1.0-alpha.2" -``` - -See [crates.io/crates/brk_cli/versions](https://crates.io/crates/brk_cli/versions) for all versions. - -## Update - -Same as install - cargo replaces the existing binary: - -```bash -rustup update -RUSTFLAGS="-C target-cpu=native" cargo install --locked brk_cli --version "$(cargo search brk_cli | head -1 | awk -F'"' '{print $2}')" -``` +- https://bitview.space - web interface +- https://bitview.space/api - API docs ## Requirements -- Bitcoin Core with accessible `blk*.dat` files +- Bitcoin Core running with RPC enabled +- Access to `blk*.dat` files - ~400 GB disk space -- 12+ GB RAM recommended +- 12+ GB RAM -## Usage +## Install ```bash -# See all options -brk --help - -# The CLI will: -# 1. Index new blocks -# 2. Compute derived metrics -# 3. Start mempool monitor -# 4. Launch API server (port 3110) -# 5. Wait for new blocks and repeat +rustup update +RUSTFLAGS="-C target-cpu=native" cargo install --locked brk_cli --version "$(cargo search brk_cli | head -1 | awk -F'"' '{print $2}')" ``` -## Components +Portable build (without native CPU optimizations): -1. **Indexer**: Processes blocks into queryable indexes -2. **Computer**: Derives 1000+ on-chain metrics -3. **Mempool**: Real-time fee estimation -4. **Server**: REST API with OpenAPI docs -5. **Bundler**: JS bundling for web interface (if enabled) +```bash +cargo install --locked brk_cli +``` -## Performance +## Run -See [brk_computer](https://docs.rs/brk_computer) for full pipeline benchmarks. +```bash +brk +``` -## Built On +Indexes the blockchain, computes datasets, starts the server on `localhost:3110`, and waits for new blocks. -- `brk_indexer` for blockchain indexing -- `brk_computer` for metric computation -- `brk_mempool` for mempool monitoring -- `brk_server` for HTTP API +## Help + +``` +brk -h Show all options +brk -V Show version +``` + +## Options + +Options are saved to `~/.brk/config.toml` after first use. + +``` +--bitcoindir Bitcoin data directory +--blocksdir Blocks directory (default: bitcoindir/blocks) +--brkdir Output directory (default: ~/.brk) + +--rpcconnect RPC host (default: localhost) +--rpcport RPC port (default: 8332) +--rpccookiefile RPC cookie file (default: bitcoindir/.cookie) +--rpcuser RPC username +--rpcpassword RPC password + +-F, --fetch Fetch price data (default: true) +--exchanges Fetch from exchange APIs (default: true) +-w, --website Serve web interface (default: true) +``` + +## Files + +``` +~/.brk/ +├── config.toml Configuration +└── log/ Logs + +/ Indexed data (default: ~/.brk) +``` diff --git a/crates/brk_cli/src/config.rs b/crates/brk_cli/src/config.rs index 5eb0bd544..c78fd2606 100644 --- a/crates/brk_cli/src/config.rs +++ b/crates/brk_cli/src/config.rs @@ -9,7 +9,7 @@ use brk_rpc::{Auth, Client}; use clap::Parser; use serde::{Deserialize, Deserializer, Serialize}; -use crate::{default_brk_path, dot_brk_path, fix_user_path, website::Website}; +use crate::{default_brk_path, dot_brk_path, fix_user_path, website::WebsiteArg}; #[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[command(version, about)] @@ -42,7 +42,7 @@ pub struct Config { /// Website served by the server: true (default), false, or PATH, saved #[serde(default, deserialize_with = "default_on_error")] #[arg(short, long, value_name = "BOOL|PATH")] - website: Option, + website: Option, /// Bitcoin RPC ip, default: localhost, saved #[serde(default, deserialize_with = "default_on_error")] @@ -258,7 +258,7 @@ Finally, you can run the program with '-h' for help." ) } - pub fn website(&self) -> Website { + pub fn website(&self) -> WebsiteArg { self.website.clone().unwrap_or_default() } diff --git a/crates/brk_cli/src/main.rs b/crates/brk_cli/src/main.rs index 3f3e32e9f..a136b853e 100644 --- a/crates/brk_cli/src/main.rs +++ b/crates/brk_cli/src/main.rs @@ -14,7 +14,7 @@ use brk_iterator::Blocks; use brk_mempool::Mempool; use brk_query::AsyncQuery; use brk_reader::Reader; -use brk_server::{Server, WebsiteSource}; +use brk_server::{Server, Website}; use tracing::info; use vecdb::Exit; @@ -22,7 +22,7 @@ mod config; mod paths; mod website; -use crate::{config::Config, paths::*, website::Website}; +use crate::{config::Config, paths::*, website::WebsiteArg}; pub fn main() -> color_eyre::Result<()> { // Can't increase main thread's stack size, thus we need to use another thread @@ -66,23 +66,14 @@ pub fn run() -> color_eyre::Result<()> { let data_path = config.brkdir(); - let website_source = match config.website() { - Website::Enabled(false) => { - info!("Website: disabled"); - WebsiteSource::Disabled - } - Website::Path(p) => { - info!("Website: filesystem ({})", p.display()); - WebsiteSource::Filesystem(p) - } - Website::Enabled(true) => { - info!("Website: embedded"); - WebsiteSource::Embedded - } + let website = match config.website() { + WebsiteArg::Enabled(false) => Website::Disabled, + WebsiteArg::Enabled(true) => Website::Default, + WebsiteArg::Path(p) => Website::Filesystem(p), }; let future = async move { - let server = Server::new(&query, data_path, website_source); + let server = Server::new(&query, data_path, website); tokio::spawn(async move { server.serve().await.unwrap(); diff --git a/crates/brk_cli/src/website.rs b/crates/brk_cli/src/website.rs index 9f4e5b1c5..317758cba 100644 --- a/crates/brk_cli/src/website.rs +++ b/crates/brk_cli/src/website.rs @@ -10,18 +10,18 @@ use crate::paths::fix_user_path; /// - `"/path/to/website"`: serve custom website from path #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[serde(untagged)] -pub enum Website { +pub enum WebsiteArg { Enabled(bool), Path(PathBuf), } -impl Default for Website { +impl Default for WebsiteArg { fn default() -> Self { Self::Enabled(true) } } -impl FromStr for Website { +impl FromStr for WebsiteArg { type Err = std::convert::Infallible; fn from_str(s: &str) -> Result { diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 59af7d10c..ea15db385 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1268,56 +1268,6 @@ impl Price111dSmaPattern { } } -/// Pattern struct for repeated tree structure. -pub struct ActivePriceRatioPattern { - pub ratio: MetricPattern4, - pub ratio_1m_sma: MetricPattern4, - pub ratio_1w_sma: MetricPattern4, - pub ratio_1y_sd: Ratio1ySdPattern, - pub ratio_2y_sd: Ratio1ySdPattern, - pub ratio_4y_sd: Ratio1ySdPattern, - pub ratio_pct1: MetricPattern4, - pub ratio_pct1_usd: MetricPattern4, - pub ratio_pct2: MetricPattern4, - pub ratio_pct2_usd: MetricPattern4, - pub ratio_pct5: MetricPattern4, - pub ratio_pct5_usd: MetricPattern4, - pub ratio_pct95: MetricPattern4, - pub ratio_pct95_usd: MetricPattern4, - pub ratio_pct98: MetricPattern4, - pub ratio_pct98_usd: MetricPattern4, - pub ratio_pct99: MetricPattern4, - pub ratio_pct99_usd: MetricPattern4, - pub ratio_sd: Ratio1ySdPattern, -} - -impl ActivePriceRatioPattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - ratio: MetricPattern4::new(client.clone(), acc.clone()), - ratio_1m_sma: MetricPattern4::new(client.clone(), _m(&acc, "1m_sma")), - ratio_1w_sma: MetricPattern4::new(client.clone(), _m(&acc, "1w_sma")), - ratio_1y_sd: Ratio1ySdPattern::new(client.clone(), _m(&acc, "1y")), - ratio_2y_sd: Ratio1ySdPattern::new(client.clone(), _m(&acc, "2y")), - ratio_4y_sd: Ratio1ySdPattern::new(client.clone(), _m(&acc, "4y")), - ratio_pct1: MetricPattern4::new(client.clone(), _m(&acc, "pct1")), - ratio_pct1_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct1_usd")), - ratio_pct2: MetricPattern4::new(client.clone(), _m(&acc, "pct2")), - ratio_pct2_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct2_usd")), - ratio_pct5: MetricPattern4::new(client.clone(), _m(&acc, "pct5")), - ratio_pct5_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct5_usd")), - ratio_pct95: MetricPattern4::new(client.clone(), _m(&acc, "pct95")), - ratio_pct95_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct95_usd")), - ratio_pct98: MetricPattern4::new(client.clone(), _m(&acc, "pct98")), - ratio_pct98_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct98_usd")), - ratio_pct99: MetricPattern4::new(client.clone(), _m(&acc, "pct99")), - ratio_pct99_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct99_usd")), - ratio_sd: Ratio1ySdPattern::new(client.clone(), acc.clone()), - } - } -} - /// Pattern struct for repeated tree structure. pub struct PercentilesPattern { pub pct05: MetricPattern4, @@ -1368,6 +1318,56 @@ impl PercentilesPattern { } } +/// Pattern struct for repeated tree structure. +pub struct ActivePriceRatioPattern { + pub ratio: MetricPattern4, + pub ratio_1m_sma: MetricPattern4, + pub ratio_1w_sma: MetricPattern4, + pub ratio_1y_sd: Ratio1ySdPattern, + pub ratio_2y_sd: Ratio1ySdPattern, + pub ratio_4y_sd: Ratio1ySdPattern, + pub ratio_pct1: MetricPattern4, + pub ratio_pct1_usd: MetricPattern4, + pub ratio_pct2: MetricPattern4, + pub ratio_pct2_usd: MetricPattern4, + pub ratio_pct5: MetricPattern4, + pub ratio_pct5_usd: MetricPattern4, + pub ratio_pct95: MetricPattern4, + pub ratio_pct95_usd: MetricPattern4, + pub ratio_pct98: MetricPattern4, + pub ratio_pct98_usd: MetricPattern4, + pub ratio_pct99: MetricPattern4, + pub ratio_pct99_usd: MetricPattern4, + pub ratio_sd: Ratio1ySdPattern, +} + +impl ActivePriceRatioPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + ratio: MetricPattern4::new(client.clone(), acc.clone()), + ratio_1m_sma: MetricPattern4::new(client.clone(), _m(&acc, "1m_sma")), + ratio_1w_sma: MetricPattern4::new(client.clone(), _m(&acc, "1w_sma")), + ratio_1y_sd: Ratio1ySdPattern::new(client.clone(), _m(&acc, "1y")), + ratio_2y_sd: Ratio1ySdPattern::new(client.clone(), _m(&acc, "2y")), + ratio_4y_sd: Ratio1ySdPattern::new(client.clone(), _m(&acc, "4y")), + ratio_pct1: MetricPattern4::new(client.clone(), _m(&acc, "pct1")), + ratio_pct1_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct1_usd")), + ratio_pct2: MetricPattern4::new(client.clone(), _m(&acc, "pct2")), + ratio_pct2_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct2_usd")), + ratio_pct5: MetricPattern4::new(client.clone(), _m(&acc, "pct5")), + ratio_pct5_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct5_usd")), + ratio_pct95: MetricPattern4::new(client.clone(), _m(&acc, "pct95")), + ratio_pct95_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct95_usd")), + ratio_pct98: MetricPattern4::new(client.clone(), _m(&acc, "pct98")), + ratio_pct98_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct98_usd")), + ratio_pct99: MetricPattern4::new(client.clone(), _m(&acc, "pct99")), + ratio_pct99_usd: MetricPattern4::new(client.clone(), _m(&acc, "pct99_usd")), + ratio_sd: Ratio1ySdPattern::new(client.clone(), acc.clone()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct RelativePattern5 { pub neg_unrealized_loss_rel_to_market_cap: MetricPattern1, @@ -1600,40 +1600,6 @@ impl BitcoinPattern { } } -/// Pattern struct for repeated tree structure. -pub struct ClassAveragePricePattern { - pub _2015: MetricPattern4, - pub _2016: MetricPattern4, - pub _2017: MetricPattern4, - pub _2018: MetricPattern4, - pub _2019: MetricPattern4, - pub _2020: MetricPattern4, - pub _2021: MetricPattern4, - pub _2022: MetricPattern4, - pub _2023: MetricPattern4, - pub _2024: MetricPattern4, - pub _2025: MetricPattern4, -} - -impl ClassAveragePricePattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - _2015: MetricPattern4::new(client.clone(), _m(&acc, "2015_average_price")), - _2016: MetricPattern4::new(client.clone(), _m(&acc, "2016_average_price")), - _2017: MetricPattern4::new(client.clone(), _m(&acc, "2017_average_price")), - _2018: MetricPattern4::new(client.clone(), _m(&acc, "2018_average_price")), - _2019: MetricPattern4::new(client.clone(), _m(&acc, "2019_average_price")), - _2020: MetricPattern4::new(client.clone(), _m(&acc, "2020_average_price")), - _2021: MetricPattern4::new(client.clone(), _m(&acc, "2021_average_price")), - _2022: MetricPattern4::new(client.clone(), _m(&acc, "2022_average_price")), - _2023: MetricPattern4::new(client.clone(), _m(&acc, "2023_average_price")), - _2024: MetricPattern4::new(client.clone(), _m(&acc, "2024_average_price")), - _2025: MetricPattern4::new(client.clone(), _m(&acc, "2025_average_price")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct DollarsPattern { pub average: MetricPattern2, @@ -1669,33 +1635,35 @@ impl DollarsPattern { } /// Pattern struct for repeated tree structure. -pub struct RelativePattern { - pub neg_unrealized_loss_rel_to_market_cap: MetricPattern1, - pub net_unrealized_pnl_rel_to_market_cap: MetricPattern1, - pub nupl: MetricPattern1, - pub supply_in_loss_rel_to_circulating_supply: MetricPattern1, - pub supply_in_loss_rel_to_own_supply: MetricPattern1, - pub supply_in_profit_rel_to_circulating_supply: MetricPattern1, - pub supply_in_profit_rel_to_own_supply: MetricPattern1, - pub supply_rel_to_circulating_supply: MetricPattern4, - pub unrealized_loss_rel_to_market_cap: MetricPattern1, - pub unrealized_profit_rel_to_market_cap: MetricPattern1, +pub struct ClassAveragePricePattern { + pub _2015: MetricPattern4, + pub _2016: MetricPattern4, + pub _2017: MetricPattern4, + pub _2018: MetricPattern4, + pub _2019: MetricPattern4, + pub _2020: MetricPattern4, + pub _2021: MetricPattern4, + pub _2022: MetricPattern4, + pub _2023: MetricPattern4, + pub _2024: MetricPattern4, + pub _2025: MetricPattern4, } -impl RelativePattern { +impl ClassAveragePricePattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - neg_unrealized_loss_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_market_cap")), - net_unrealized_pnl_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_market_cap")), - nupl: MetricPattern1::new(client.clone(), _m(&acc, "nupl")), - supply_in_loss_rel_to_circulating_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_circulating_supply")), - supply_in_loss_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_own_supply")), - supply_in_profit_rel_to_circulating_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_circulating_supply")), - supply_in_profit_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_own_supply")), - supply_rel_to_circulating_supply: MetricPattern4::new(client.clone(), _m(&acc, "supply_rel_to_circulating_supply")), - unrealized_loss_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_market_cap")), - unrealized_profit_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_market_cap")), + _2015: MetricPattern4::new(client.clone(), _m(&acc, "2015_average_price")), + _2016: MetricPattern4::new(client.clone(), _m(&acc, "2016_average_price")), + _2017: MetricPattern4::new(client.clone(), _m(&acc, "2017_average_price")), + _2018: MetricPattern4::new(client.clone(), _m(&acc, "2018_average_price")), + _2019: MetricPattern4::new(client.clone(), _m(&acc, "2019_average_price")), + _2020: MetricPattern4::new(client.clone(), _m(&acc, "2020_average_price")), + _2021: MetricPattern4::new(client.clone(), _m(&acc, "2021_average_price")), + _2022: MetricPattern4::new(client.clone(), _m(&acc, "2022_average_price")), + _2023: MetricPattern4::new(client.clone(), _m(&acc, "2023_average_price")), + _2024: MetricPattern4::new(client.clone(), _m(&acc, "2024_average_price")), + _2025: MetricPattern4::new(client.clone(), _m(&acc, "2025_average_price")), } } } @@ -1732,6 +1700,38 @@ impl RelativePattern2 { } } +/// Pattern struct for repeated tree structure. +pub struct RelativePattern { + pub neg_unrealized_loss_rel_to_market_cap: MetricPattern1, + pub net_unrealized_pnl_rel_to_market_cap: MetricPattern1, + pub nupl: MetricPattern1, + pub supply_in_loss_rel_to_circulating_supply: MetricPattern1, + pub supply_in_loss_rel_to_own_supply: MetricPattern1, + pub supply_in_profit_rel_to_circulating_supply: MetricPattern1, + pub supply_in_profit_rel_to_own_supply: MetricPattern1, + pub supply_rel_to_circulating_supply: MetricPattern4, + pub unrealized_loss_rel_to_market_cap: MetricPattern1, + pub unrealized_profit_rel_to_market_cap: MetricPattern1, +} + +impl RelativePattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + neg_unrealized_loss_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_market_cap")), + net_unrealized_pnl_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_market_cap")), + nupl: MetricPattern1::new(client.clone(), _m(&acc, "nupl")), + supply_in_loss_rel_to_circulating_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_circulating_supply")), + supply_in_loss_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_own_supply")), + supply_in_profit_rel_to_circulating_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_circulating_supply")), + supply_in_profit_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_own_supply")), + supply_rel_to_circulating_supply: MetricPattern4::new(client.clone(), _m(&acc, "supply_rel_to_circulating_supply")), + unrealized_loss_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_market_cap")), + unrealized_profit_rel_to_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_market_cap")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct CountPattern2 { pub average: MetricPattern1, @@ -1911,53 +1911,27 @@ impl PhaseDailyCentsPattern { } /// Pattern struct for repeated tree structure. -pub struct PeriodCagrPattern { - pub _10y: MetricPattern4, - pub _2y: MetricPattern4, - pub _3y: MetricPattern4, - pub _4y: MetricPattern4, - pub _5y: MetricPattern4, - pub _6y: MetricPattern4, - pub _8y: MetricPattern4, +pub struct UnrealizedPattern { + pub neg_unrealized_loss: MetricPattern1, + pub net_unrealized_pnl: MetricPattern1, + pub supply_in_loss: ActiveSupplyPattern, + pub supply_in_profit: ActiveSupplyPattern, + pub total_unrealized_pnl: MetricPattern1, + pub unrealized_loss: MetricPattern1, + pub unrealized_profit: MetricPattern1, } -impl PeriodCagrPattern { +impl UnrealizedPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - _10y: MetricPattern4::new(client.clone(), _p("10y", &acc)), - _2y: MetricPattern4::new(client.clone(), _p("2y", &acc)), - _3y: MetricPattern4::new(client.clone(), _p("3y", &acc)), - _4y: MetricPattern4::new(client.clone(), _p("4y", &acc)), - _5y: MetricPattern4::new(client.clone(), _p("5y", &acc)), - _6y: MetricPattern4::new(client.clone(), _p("6y", &acc)), - _8y: MetricPattern4::new(client.clone(), _p("8y", &acc)), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct _0satsPattern2 { - pub activity: ActivityPattern2, - pub cost_basis: CostBasisPattern, - pub outputs: OutputsPattern, - pub realized: RealizedPattern, - pub relative: RelativePattern4, - pub supply: SupplyPattern2, - pub unrealized: UnrealizedPattern, -} - -impl _0satsPattern2 { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - activity: ActivityPattern2::new(client.clone(), acc.clone()), - cost_basis: CostBasisPattern::new(client.clone(), acc.clone()), - outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")), - realized: RealizedPattern::new(client.clone(), acc.clone()), - relative: RelativePattern4::new(client.clone(), _m(&acc, "supply_in")), - supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")), - unrealized: UnrealizedPattern::new(client.clone(), acc.clone()), + neg_unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss")), + net_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl")), + supply_in_loss: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_loss")), + supply_in_profit: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_profit")), + total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "total_unrealized_pnl")), + unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss")), + unrealized_profit: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit")), } } } @@ -1989,27 +1963,53 @@ impl _100btcPattern { } /// Pattern struct for repeated tree structure. -pub struct UnrealizedPattern { - pub neg_unrealized_loss: MetricPattern1, - pub net_unrealized_pnl: MetricPattern1, - pub supply_in_loss: ActiveSupplyPattern, - pub supply_in_profit: ActiveSupplyPattern, - pub total_unrealized_pnl: MetricPattern1, - pub unrealized_loss: MetricPattern1, - pub unrealized_profit: MetricPattern1, +pub struct _0satsPattern2 { + pub activity: ActivityPattern2, + pub cost_basis: CostBasisPattern, + pub outputs: OutputsPattern, + pub realized: RealizedPattern, + pub relative: RelativePattern4, + pub supply: SupplyPattern2, + pub unrealized: UnrealizedPattern, } -impl UnrealizedPattern { +impl _0satsPattern2 { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - neg_unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss")), - net_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl")), - supply_in_loss: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_loss")), - supply_in_profit: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_profit")), - total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "total_unrealized_pnl")), - unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss")), - unrealized_profit: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit")), + activity: ActivityPattern2::new(client.clone(), acc.clone()), + cost_basis: CostBasisPattern::new(client.clone(), acc.clone()), + outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")), + realized: RealizedPattern::new(client.clone(), acc.clone()), + relative: RelativePattern4::new(client.clone(), _m(&acc, "supply_in")), + supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")), + unrealized: UnrealizedPattern::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct _10yPattern { + pub activity: ActivityPattern2, + pub cost_basis: CostBasisPattern, + pub outputs: OutputsPattern, + pub realized: RealizedPattern4, + pub relative: RelativePattern, + pub supply: SupplyPattern2, + pub unrealized: UnrealizedPattern, +} + +impl _10yPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + activity: ActivityPattern2::new(client.clone(), acc.clone()), + cost_basis: CostBasisPattern::new(client.clone(), acc.clone()), + outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")), + realized: RealizedPattern4::new(client.clone(), acc.clone()), + relative: RelativePattern::new(client.clone(), acc.clone()), + supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")), + unrealized: UnrealizedPattern::new(client.clone(), acc.clone()), } } } @@ -2041,27 +2041,27 @@ impl _10yTo12yPattern { } /// Pattern struct for repeated tree structure. -pub struct _10yPattern { - pub activity: ActivityPattern2, - pub cost_basis: CostBasisPattern, - pub outputs: OutputsPattern, - pub realized: RealizedPattern4, - pub relative: RelativePattern, - pub supply: SupplyPattern2, - pub unrealized: UnrealizedPattern, +pub struct PeriodCagrPattern { + pub _10y: MetricPattern4, + pub _2y: MetricPattern4, + pub _3y: MetricPattern4, + pub _4y: MetricPattern4, + pub _5y: MetricPattern4, + pub _6y: MetricPattern4, + pub _8y: MetricPattern4, } -impl _10yPattern { +impl PeriodCagrPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - activity: ActivityPattern2::new(client.clone(), acc.clone()), - cost_basis: CostBasisPattern::new(client.clone(), acc.clone()), - outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")), - realized: RealizedPattern4::new(client.clone(), acc.clone()), - relative: RelativePattern::new(client.clone(), acc.clone()), - supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")), - unrealized: UnrealizedPattern::new(client.clone(), acc.clone()), + _10y: MetricPattern4::new(client.clone(), _p("10y", &acc)), + _2y: MetricPattern4::new(client.clone(), _p("2y", &acc)), + _3y: MetricPattern4::new(client.clone(), _p("3y", &acc)), + _4y: MetricPattern4::new(client.clone(), _p("4y", &acc)), + _5y: MetricPattern4::new(client.clone(), _p("5y", &acc)), + _6y: MetricPattern4::new(client.clone(), _p("6y", &acc)), + _8y: MetricPattern4::new(client.clone(), _p("8y", &acc)), } } } @@ -2109,19 +2109,55 @@ impl SplitPattern2 { } /// Pattern struct for repeated tree structure. -pub struct ActiveSupplyPattern { - pub bitcoin: MetricPattern1, - pub dollars: MetricPattern1, - pub sats: MetricPattern1, +pub struct UnclaimedRewardsPattern { + pub bitcoin: BitcoinPattern2, + pub dollars: BlockCountPattern, + pub sats: BlockCountPattern, } -impl ActiveSupplyPattern { +impl UnclaimedRewardsPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - bitcoin: MetricPattern1::new(client.clone(), _m(&acc, "btc")), - dollars: MetricPattern1::new(client.clone(), _m(&acc, "usd")), - sats: MetricPattern1::new(client.clone(), acc.clone()), + bitcoin: BitcoinPattern2::new(client.clone(), _m(&acc, "btc")), + dollars: BlockCountPattern::new(client.clone(), _m(&acc, "usd")), + sats: BlockCountPattern::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct CoinbasePattern { + pub bitcoin: BitcoinPattern, + pub dollars: DollarsPattern, + pub sats: DollarsPattern, +} + +impl CoinbasePattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + bitcoin: BitcoinPattern::new(client.clone(), _m(&acc, "btc")), + dollars: DollarsPattern::new(client.clone(), _m(&acc, "usd")), + sats: DollarsPattern::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct SegwitAdoptionPattern { + pub base: MetricPattern11, + pub cumulative: MetricPattern2, + pub sum: MetricPattern2, +} + +impl SegwitAdoptionPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + base: MetricPattern11::new(client.clone(), acc.clone()), + cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")), + sum: MetricPattern2::new(client.clone(), _m(&acc, "sum")), } } } @@ -2144,6 +2180,24 @@ impl _2015Pattern { } } +/// Pattern struct for repeated tree structure. +pub struct ActiveSupplyPattern { + pub bitcoin: MetricPattern1, + pub dollars: MetricPattern1, + pub sats: MetricPattern1, +} + +impl ActiveSupplyPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + bitcoin: MetricPattern1::new(client.clone(), _m(&acc, "btc")), + dollars: MetricPattern1::new(client.clone(), _m(&acc, "usd")), + sats: MetricPattern1::new(client.clone(), acc.clone()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct CoinbasePattern2 { pub bitcoin: BlockCountPattern, @@ -2181,55 +2235,17 @@ impl CostBasisPattern2 { } /// Pattern struct for repeated tree structure. -pub struct SegwitAdoptionPattern { - pub base: MetricPattern11, - pub cumulative: MetricPattern2, - pub sum: MetricPattern2, +pub struct CostBasisPattern { + pub max: MetricPattern1, + pub min: MetricPattern1, } -impl SegwitAdoptionPattern { +impl CostBasisPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - base: MetricPattern11::new(client.clone(), acc.clone()), - cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")), - sum: MetricPattern2::new(client.clone(), _m(&acc, "sum")), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct UnclaimedRewardsPattern { - pub bitcoin: BitcoinPattern2, - pub dollars: BlockCountPattern, - pub sats: BlockCountPattern, -} - -impl UnclaimedRewardsPattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - bitcoin: BitcoinPattern2::new(client.clone(), _m(&acc, "btc")), - dollars: BlockCountPattern::new(client.clone(), _m(&acc, "usd")), - sats: BlockCountPattern::new(client.clone(), acc.clone()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct CoinbasePattern { - pub bitcoin: BitcoinPattern, - pub dollars: DollarsPattern, - pub sats: DollarsPattern, -} - -impl CoinbasePattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - bitcoin: BitcoinPattern::new(client.clone(), _m(&acc, "btc")), - dollars: DollarsPattern::new(client.clone(), _m(&acc, "usd")), - sats: DollarsPattern::new(client.clone(), acc.clone()), + max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")), + min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")), } } } @@ -2266,22 +2282,6 @@ impl _1dReturns1mSdPattern { } } -/// Pattern struct for repeated tree structure. -pub struct CostBasisPattern { - pub max: MetricPattern1, - pub min: MetricPattern1, -} - -impl CostBasisPattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")), - min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct SupplyPattern2 { pub halved: ActiveSupplyPattern, @@ -2299,17 +2299,17 @@ impl SupplyPattern2 { } /// Pattern struct for repeated tree structure. -pub struct BlockCountPattern { - pub cumulative: MetricPattern1, - pub sum: MetricPattern1, +pub struct SatsPattern { + pub ohlc: MetricPattern1, + pub split: SplitPattern2, } -impl BlockCountPattern { +impl SatsPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - cumulative: MetricPattern1::new(client.clone(), _m(&acc, "cumulative")), - sum: MetricPattern1::new(client.clone(), acc.clone()), + ohlc: MetricPattern1::new(client.clone(), _m(&acc, "ohlc")), + split: SplitPattern2::new(client.clone(), acc.clone()), } } } @@ -2331,17 +2331,17 @@ impl BitcoinPattern2 { } /// Pattern struct for repeated tree structure. -pub struct SatsPattern { - pub ohlc: MetricPattern1, - pub split: SplitPattern2, +pub struct BlockCountPattern { + pub cumulative: MetricPattern1, + pub sum: MetricPattern1, } -impl SatsPattern { +impl BlockCountPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - ohlc: MetricPattern1::new(client.clone(), _m(&acc, "ohlc_sats")), - split: SplitPattern2::new(client.clone(), _m(&acc, "sats")), + cumulative: MetricPattern1::new(client.clone(), _m(&acc, "cumulative")), + sum: MetricPattern1::new(client.clone(), acc.clone()), } } } @@ -4942,8 +4942,8 @@ impl MetricsTree_Positions { pub struct MetricsTree_Price { pub cents: MetricsTree_Price_Cents, pub oracle: MetricsTree_Price_Oracle, - pub sats: SatsPattern, - pub usd: MetricsTree_Price_Usd, + pub sats: MetricsTree_Price_Sats, + pub usd: SatsPattern, } impl MetricsTree_Price { @@ -4951,8 +4951,8 @@ impl MetricsTree_Price { Self { cents: MetricsTree_Price_Cents::new(client.clone(), format!("{base_path}_cents")), oracle: MetricsTree_Price_Oracle::new(client.clone(), format!("{base_path}_oracle")), - sats: SatsPattern::new(client.clone(), "price".to_string()), - usd: MetricsTree_Price_Usd::new(client.clone(), format!("{base_path}_usd")), + sats: MetricsTree_Price_Sats::new(client.clone(), format!("{base_path}_sats")), + usd: SatsPattern::new(client.clone(), "price".to_string()), } } } @@ -5055,16 +5055,16 @@ impl MetricsTree_Price_Oracle { } /// Metrics tree node. -pub struct MetricsTree_Price_Usd { - pub ohlc: MetricPattern1, - pub split: SplitPattern2, +pub struct MetricsTree_Price_Sats { + pub ohlc: MetricPattern1, + pub split: SplitPattern2, } -impl MetricsTree_Price_Usd { +impl MetricsTree_Price_Sats { pub fn new(client: Arc, base_path: String) -> Self { Self { - ohlc: MetricPattern1::new(client.clone(), "price_ohlc".to_string()), - split: SplitPattern2::new(client.clone(), "price".to_string()), + ohlc: MetricPattern1::new(client.clone(), "price_ohlc_sats".to_string()), + split: SplitPattern2::new(client.clone(), "price_sats".to_string()), } } } diff --git a/crates/brk_computer/src/price/oracle/compute.rs b/crates/brk_computer/src/price/oracle/compute.rs index 79b222243..edebf470d 100644 --- a/crates/brk_computer/src/price/oracle/compute.rs +++ b/crates/brk_computer/src/price/oracle/compute.rs @@ -1456,9 +1456,9 @@ impl Vecs { let price_cents = if histogram.total_count() >= 10 { // Downsample 200 bins to 100 bins let mut bins100 = [0u32; 100]; - for i in 0..100 { + (0..100).for_each(|i| { bins100[i] = histogram.bins[i * 2] as u32 + histogram.bins[i * 2 + 1] as u32; - } + }); // Find peak bin, skipping bin 0 (round BTC amounts cluster there) let peak_bin = bins100 @@ -1553,10 +1553,10 @@ impl Vecs { Ok(()) } - /// Compute Phase Oracle V3 - Step 1: Per-block histograms with uniqueVal filtering + /// Compute Phase Oracle V3 - Step 1: Per-block histograms with uniqueVal + noP2TR filtering /// - /// Filters: >= 1000 sats, only outputs with unique values within their transaction. - /// This reduces spurious peaks from exchange batched payouts and inscription spam. + /// Filters: >= 1000 sats, no P2TR outputs, only outputs with unique values within their tx. + /// This reduces spurious peaks from inscription spam and exchange batched payouts. fn compute_phase_v3_histograms( &mut self, indexer: &Indexer, @@ -1564,7 +1564,8 @@ impl Vecs { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { - let source_version = indexer.vecs.outputs.value.version(); + let source_version = + indexer.vecs.outputs.value.version() + indexer.vecs.outputs.outputtype.version(); self.phase_v3_histogram .validate_computed_version_or_reset(source_version)?; @@ -1592,12 +1593,13 @@ impl Vecs { indexer.vecs.transactions.first_txoutindex.into_iter(); let mut txindex_to_output_count_iter = indexes.txindex.output_count.iter(); let mut txoutindex_to_value_iter = indexer.vecs.outputs.value.into_iter(); + let mut txoutindex_to_outputtype_iter = indexer.vecs.outputs.outputtype.into_iter(); let total_txs = indexer.vecs.transactions.height.len(); let mut last_progress = (start_height * 100 / total_heights.max(1)) as u8; - // Reusable buffer for collecting output values per transaction - let mut tx_values: Vec = Vec::with_capacity(16); + // Reusable buffer for collecting output values per transaction (sats, is_p2tr) + let mut tx_outputs: Vec<(Sats, bool)> = Vec::with_capacity(16); for height in start_height..total_heights { // Get transaction range for this block @@ -1606,7 +1608,7 @@ impl Vecs { .get_at(height + 1) .unwrap_or(TxIndex::from(total_txs)); - // Build phase histogram with uniqueVal filtering + // Build phase histogram with uniqueVal + noP2TR filtering let mut histogram = OracleBinsV2::ZERO; // Skip coinbase (first tx in block) @@ -1616,17 +1618,25 @@ impl Vecs { let output_count: StoredU64 = txindex_to_output_count_iter.get_unwrap(TxIndex::from(txindex)); - // Collect all output values for this transaction - tx_values.clear(); + // Collect all output values and types for this transaction + tx_outputs.clear(); for i in 0..*output_count as usize { let txoutindex = first_txoutindex.to_usize() + i; let sats: Sats = txoutindex_to_value_iter.get_at_unwrap(txoutindex); - tx_values.push(sats); + let outputtype: OutputType = + txoutindex_to_outputtype_iter.get_at_unwrap(txoutindex); + let is_p2tr = outputtype == OutputType::P2TR; + tx_outputs.push((sats, is_p2tr)); } // Count occurrences of each value to determine uniqueness // For small output counts, simple nested loop is faster than HashMap - for (i, &sats) in tx_values.iter().enumerate() { + for (i, &(sats, is_p2tr)) in tx_outputs.iter().enumerate() { + // Skip P2TR outputs (inscription spam) + if is_p2tr { + continue; + } + // Skip if below minimum (BASE filter: >= 1000 sats) if sats < Sats::_1K { continue; @@ -1634,7 +1644,7 @@ impl Vecs { // Check if this value is unique within the transaction let mut is_unique = true; - for (j, &other_sats) in tx_values.iter().enumerate() { + for (j, &(other_sats, _)) in tx_outputs.iter().enumerate() { if i != j && sats == other_sats { is_unique = false; break; diff --git a/crates/brk_computer/src/price/oracle/import.rs b/crates/brk_computer/src/price/oracle/import.rs index eb4ad6b0e..329086969 100644 --- a/crates/brk_computer/src/price/oracle/import.rs +++ b/crates/brk_computer/src/price/oracle/import.rs @@ -72,9 +72,9 @@ impl Vecs { &phase_v2_peak_daily_cents, ); - // Phase Oracle V3 (BASE + uniqueVal filter) - // v4: Peak prices use 100 bins (downsampled from 200) - let phase_v3_version = version + Version::new(4); + // Phase Oracle V3 (BASE + noP2TR + uniqueVal filter) + // v5: Added noP2TR filter to reduce inscription spam + let phase_v3_version = version + Version::new(5); let phase_v3_histogram = BytesVec::forced_import(db, "phase_v3_histogram", phase_v3_version)?; let phase_v3_price_cents = diff --git a/crates/brk_computer/src/price/oracle/phase_v2.rs b/crates/brk_computer/src/price/oracle/phase_v2.rs index c1814e44a..926402091 100644 --- a/crates/brk_computer/src/price/oracle/phase_v2.rs +++ b/crates/brk_computer/src/price/oracle/phase_v2.rs @@ -67,6 +67,7 @@ impl Default for PhaseHistogramV2 { } } +#[allow(unused)] impl PhaseHistogramV2 { pub fn new() -> Self { Self { @@ -153,9 +154,7 @@ pub fn find_best_phase( (min_bin..=max_bin).collect() } else { // Wraps around - (min_bin..PHASE_BINS_V2) - .chain(0..=max_bin) - .collect() + (min_bin..PHASE_BINS_V2).chain(0..=max_bin).collect() } } else { (0..PHASE_BINS_V2).collect() diff --git a/crates/brk_server/Cargo.toml b/crates/brk_server/Cargo.toml index 5762e6dab..abd0ebb0b 100644 --- a/crates/brk_server/Cargo.toml +++ b/crates/brk_server/Cargo.toml @@ -6,8 +6,7 @@ edition.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true -build = "build.rs" -include = ["src/**/*", "website/**/*", "examples/**/*", "build.rs", "Cargo.toml", "README.md"] +include = ["src/**/*", "website/**/*", "examples/**/*", "Cargo.toml", "README.md"] [dependencies] aide = { workspace = true } @@ -25,6 +24,8 @@ brk_types = { workspace = true } brk_traversable = { workspace = true } derive_more = { workspace = true } include_dir = "0.7" +importmap = { path = "../../../importmap", features = ["embedded"] } +# importmap = { version = "0.3", features = ["embedded"] } vecdb = { workspace = true } jiff = { workspace = true } quick_cache = "0.6.18" @@ -34,13 +35,7 @@ serde_json = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } tower-http = { version = "0.6.8", features = ["catch-panic", "compression-full", "cors", "normalize-path", "timeout", "trace"] } - -[build-dependencies] -# importmap = { path = "../../../importmap" } -importmap = "0.2" +tower-layer = "0.3" [dev-dependencies] brk_mempool = { workspace = true } - -[package.metadata.cargo-machete] -ignored = ["importmap"] diff --git a/crates/brk_server/README.md b/crates/brk_server/README.md index 3463d44f6..860e79a24 100644 --- a/crates/brk_server/README.md +++ b/crates/brk_server/README.md @@ -13,8 +13,8 @@ HTTP API server for Bitcoin on-chain analytics. ## Usage ```rust,ignore -let server = Server::new(&async_query, data_path, WebsiteSource::Filesystem(files_path)); -// Or WebsiteSource::Embedded, or WebsiteSource::Disabled +let server = Server::new(&async_query, data_path, Website::Filesystem(files_path)); +// Or Website::Default, or Website::Disabled server.serve().await?; ``` diff --git a/crates/brk_server/build.rs b/crates/brk_server/build.rs deleted file mode 100644 index 889caf7f0..000000000 --- a/crates/brk_server/build.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::{env, path::Path}; - -fn main() { - let is_dev = env::var("PROFILE").is_ok_and(|p| p == "debug"); - - // Generate importmap for website (updates index.html in place) - let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - // Use ./website (symlink in repo, real dir in published crate) - let website_path = Path::new(&manifest_dir).join("website"); - - println!("cargo:rerun-if-changed=website"); - println!("cargo::warning=build.rs: website_path={website_path:?}, exists={}", website_path.exists()); - - if website_path.exists() { - // Skip importmap hashing in dev mode (files change often) - let map = if is_dev { - println!("cargo::warning=build.rs: dev mode, skipping importmap"); - importmap::ImportMap::empty() - } else { - match importmap::ImportMap::scan(&website_path, "") { - Ok(map) => { - println!("cargo::warning=build.rs: importmap scanned {} entries", map.imports.len()); - map - } - Err(e) => { - println!("cargo::warning=build.rs: importmap scan failed: {e}"); - importmap::ImportMap::empty() - } - } - }; - - let index_path = website_path.join("index.html"); - if let Err(e) = map.update_html_file(&index_path) { - println!("cargo::warning=build.rs: failed to update index.html: {e}"); - } - } else { - println!("cargo::warning=build.rs: website path does not exist!"); - } -} diff --git a/crates/brk_server/examples/server.rs b/crates/brk_server/examples/server.rs index 33c79bc25..8743473d0 100644 --- a/crates/brk_server/examples/server.rs +++ b/crates/brk_server/examples/server.rs @@ -8,7 +8,7 @@ use brk_mempool::Mempool; use brk_query::AsyncQuery; use brk_reader::Reader; use brk_rpc::{Auth, Client}; -use brk_server::{Server, WebsiteSource}; +use brk_server::{Server, Website}; use tracing::info; use vecdb::Exit; @@ -54,7 +54,7 @@ fn run() -> Result<()> { // Option 1: block_on to run and properly propagate errors runtime.block_on(async move { - let server = Server::new(&query, outputs_dir, WebsiteSource::Disabled); + let server = Server::new(&query, outputs_dir, Website::Disabled); let handle = tokio::spawn(async move { server.serve().await }); diff --git a/crates/brk_server/src/extended/header_map.rs b/crates/brk_server/src/extended/header_map.rs index 271709c95..1d312e4cc 100644 --- a/crates/brk_server/src/extended/header_map.rs +++ b/crates/brk_server/src/extended/header_map.rs @@ -1,35 +1,17 @@ -use std::{ - path::Path, - time::{self, Duration}, -}; +use std::path::Path; use axum::http::{ HeaderMap, - header::{self, IF_MODIFIED_SINCE, IF_NONE_MATCH}, + header::{self, IF_NONE_MATCH}, }; -use brk_error::Result; -use jiff::{Timestamp, civil::DateTime, fmt::strtime, tz::TimeZone}; - -const MODIFIED_SINCE_FORMAT: &str = "%a, %d %b %Y %H:%M:%S GMT"; - -#[derive(PartialEq, Eq)] -pub enum ModifiedState { - ModifiedSince, - NotModifiedSince, -} pub trait HeaderMapExtended { fn has_etag(&self, etag: &str) -> bool; - fn get_if_modified_since(&self) -> Option; - fn check_if_modified_since(&self, path: &Path) -> Result<(ModifiedState, DateTime)>; - fn check_if_modified_since_(&self, duration: Duration) -> Result<(ModifiedState, DateTime)>; - fn insert_cache_control(&mut self, value: &str); fn insert_cache_control_must_revalidate(&mut self); fn insert_cache_control_immutable(&mut self); fn insert_etag(&mut self, etag: &str); - fn insert_last_modified(&mut self, date: DateTime); fn insert_content_disposition_attachment(&mut self); @@ -66,56 +48,10 @@ impl HeaderMapExtended for HeaderMap { self.insert(header::CONTENT_DISPOSITION, "attachment".parse().unwrap()); } - fn insert_last_modified(&mut self, date: DateTime) { - let formatted = date - .to_zoned(TimeZone::system()) - .unwrap() - .strftime(MODIFIED_SINCE_FORMAT) - .to_string(); - - self.insert(header::LAST_MODIFIED, formatted.parse().unwrap()); - } - fn insert_etag(&mut self, etag: &str) { self.insert(header::ETAG, etag.parse().unwrap()); } - fn check_if_modified_since(&self, path: &Path) -> Result<(ModifiedState, DateTime)> { - self.check_if_modified_since_( - path.metadata()? - .modified()? - .duration_since(time::UNIX_EPOCH)?, - ) - } - - fn check_if_modified_since_(&self, duration: Duration) -> Result<(ModifiedState, DateTime)> { - let date = Timestamp::new(duration.as_secs() as i64, 0) - .unwrap() - .to_zoned(TimeZone::UTC) - .datetime(); - - if let Some(if_modified_since) = self.get_if_modified_since() - && if_modified_since == date - { - return Ok((ModifiedState::NotModifiedSince, date)); - } - - Ok((ModifiedState::ModifiedSince, date)) - } - - fn get_if_modified_since(&self) -> Option { - if let Some(modified_since) = self.get(IF_MODIFIED_SINCE) - && let Ok(modified_since) = modified_since.to_str() - { - return strtime::parse(MODIFIED_SINCE_FORMAT, modified_since) - .unwrap() - .to_datetime() - .ok(); - } - - None - } - fn has_etag(&self, etag: &str) -> bool { self.get(IF_NONE_MATCH) .is_some_and(|prev_etag| etag == prev_etag) diff --git a/crates/brk_server/src/files/file.rs b/crates/brk_server/src/files/file.rs index 5ac3cd360..b62130520 100644 --- a/crates/brk_server/src/files/file.rs +++ b/crates/brk_server/src/files/file.rs @@ -1,217 +1,47 @@ -use std::{ - fs, - path::{Path, PathBuf}, - time::Duration, -}; +use std::path::Path; -use axum::{ - body::Body, - extract::{self, State}, - http::HeaderMap, - response::Response, -}; -use quick_cache::sync::GuardResult; -use tracing::{error, info}; +use axum::{body::Body, extract::State, response::Response}; -use crate::{ - AppState, EMBEDDED_WEBSITE, Error, HeaderMapExtended, ModifiedState, ResponseExtended, Result, - WebsiteSource, -}; +use crate::{AppState, HeaderMapExtended, Result}; pub async fn file_handler( - headers: HeaderMap, State(state): State, - path: extract::Path, + path: axum::extract::Path, ) -> Result { - any_handler(headers, state, Some(path.0)) + serve(&state, &path.0) } -pub async fn index_handler(headers: HeaderMap, State(state): State) -> Result { - any_handler(headers, state, None) +pub async fn index_handler(State(state): State) -> Result { + serve(&state, "") } -fn any_handler(headers: HeaderMap, state: AppState, path: Option) -> Result { - match &state.website { - WebsiteSource::Disabled => unreachable!("routes not added when disabled"), - WebsiteSource::Embedded => embedded_handler(&state, path), - WebsiteSource::Filesystem(files_path) => { - filesystem_handler(headers, &state, files_path, path) - } - } -} - -/// Sanitize path to prevent traversal attacks -fn sanitize_path(path: &str) -> String { - path.split('/') - .filter(|c| !c.is_empty() && *c != "." && *c != "..") - .collect::>() - .join("/") -} - -/// Check if path requires revalidation (HTML files, service worker) -fn must_revalidate(path: &Path) -> bool { - path.extension().is_some_and(|ext| ext == "html") - || path - .to_str() - .is_some_and(|p| p.ends_with("service-worker.js")) -} - -/// Build response with proper headers and caching -fn build_response(state: &AppState, path: &Path, content: Vec, cache_key: &str) -> Response { - let must_revalidate = must_revalidate(path); - - // Use cache for non-HTML files in release mode - let guard_res = if !cfg!(debug_assertions) && !must_revalidate { - Some( - state - .cache - .get_value_or_guard(&cache_key.to_owned(), Some(Duration::from_millis(50))), - ) - } else { - None - }; - - let mut response = if let Some(GuardResult::Value(v)) = guard_res { - Response::new(Body::from(v)) - } else { - if let Some(GuardResult::Guard(g)) = guard_res { - let _ = g.insert(content.clone().into()); - } - Response::new(Body::from(content)) - }; +fn serve(state: &AppState, path: &str) -> Result { + let path = sanitize(path); + let content = state.website.get_file(&path)?; + let mut response = Response::new(Body::from(content)); let headers = response.headers_mut(); - headers.insert_content_type(path); - if cfg!(debug_assertions) || must_revalidate { + // Empty path or no extension = index.html (SPA fallback) + if path.is_empty() || Path::new(&path).extension().is_none() { + headers.insert_content_type_text_html(); + } else { + headers.insert_content_type(Path::new(&path)); + } + + if cfg!(debug_assertions) { headers.insert_cache_control_must_revalidate(); } else { headers.insert_cache_control_immutable(); } - response -} - -fn embedded_handler(state: &AppState, path: Option) -> Result { - let path = path.unwrap_or_else(|| "index.html".to_string()); - let sanitized = sanitize_path(&path); - - // Try to get file, with importmap hash stripping and SPA fallback - let file = EMBEDDED_WEBSITE - .get_file(&sanitized) - .or_else(|| { - strip_importmap_hash(Path::new(&sanitized)) - .and_then(|unhashed| EMBEDDED_WEBSITE.get_file(unhashed.to_str()?)) - }) - .or_else(|| { - // If no extension, serve index.html (SPA routing) - if Path::new(&sanitized).extension().is_none() { - EMBEDDED_WEBSITE.get_file("index.html") - } else { - None - } - }); - - let Some(file) = file else { - return Err(Error::not_found("File not found")); - }; - - Ok(build_response( - state, - Path::new(file.path()), - file.contents().to_vec(), - &file.path().to_string_lossy(), - )) -} - -fn filesystem_handler( - headers: HeaderMap, - state: &AppState, - files_path: &Path, - path: Option, -) -> Result { - let path = if let Some(path) = path { - let sanitized = sanitize_path(&path); - let mut path = files_path.join(&sanitized); - - // Canonicalize and verify the path stays within the project root - // (allows symlinks to modules/ which is outside the website directory) - if let Ok(canonical) = path.canonicalize() - && let Ok(canonical_base) = files_path.canonicalize() - { - let project_root = canonical_base.parent().and_then(|p| p.parent()); - let allowed = canonical.starts_with(&canonical_base) - || project_root.is_some_and(|root| canonical.starts_with(root)); - if !allowed { - return Err(Error::forbidden("Access denied")); - } - } - - // Strip hash from import-mapped URLs - if !path.exists() - && let Some(unhashed) = strip_importmap_hash(&path) - && unhashed.exists() - { - path = unhashed; - } - - // SPA fallback - if !path.exists() || path.is_dir() { - if path.extension().is_some() { - return Err(Error::not_found("File doesn't exist")); - } else { - path = files_path.join("index.html"); - } - } - - path - } else { - files_path.join("index.html") - }; - - path_to_response(&headers, state, &path) -} - -fn path_to_response(headers: &HeaderMap, state: &AppState, path: &Path) -> Result { - let (modified, date) = headers.check_if_modified_since(path)?; - if !cfg!(debug_assertions) && modified == ModifiedState::NotModifiedSince { - return Ok(Response::new_not_modified()); - } - - let content = fs::read(path).unwrap_or_else(|error| { - error!("{error}"); - let path = path.to_str().unwrap(); - info!("Can't read file {path}"); - panic!("") - }); - - let cache_key = path.to_str().unwrap(); - let mut response = build_response(state, path, content, cache_key); - response.headers_mut().insert_last_modified(date); - Ok(response) } -/// Strip importmap hash from filename: `foo.abc12345.js` -> `foo.js` -/// Hash is 8 hex characters between the name and extension. -fn strip_importmap_hash(path: &Path) -> Option { - let stem = path.file_stem()?.to_str()?; - let ext = path.extension()?.to_str()?; - - // Only process js/mjs/css files - if !matches!(ext, "js" | "mjs" | "css") { - return None; - } - - // Look for pattern: name.HASH where HASH is 8 hex chars - let dot_pos = stem.rfind('.')?; - let hash = &stem[dot_pos + 1..]; - - if hash.len() == 8 && hash.chars().all(|c| c.is_ascii_hexdigit()) { - let name = &stem[..dot_pos]; - let new_name = format!("{}.{}", name, ext); - Some(path.with_file_name(new_name)) - } else { - None - } +/// Sanitize path to prevent directory traversal attacks +fn sanitize(path: &str) -> String { + path.split('/') + .filter(|s| !s.is_empty() && *s != "." && *s != "..") + .collect::>() + .join("/") } diff --git a/crates/brk_server/src/files/mod.rs b/crates/brk_server/src/files/mod.rs index 70ec24206..f550c3f06 100644 --- a/crates/brk_server/src/files/mod.rs +++ b/crates/brk_server/src/files/mod.rs @@ -1,18 +1,20 @@ use aide::axum::ApiRouter; use axum::{response::Redirect, routing::get}; -use super::{AppState, WebsiteSource}; +use super::AppState; mod file; +mod website; use file::{file_handler, index_handler}; +pub use website::*; pub trait FilesRoutes { - fn add_files_routes(self, website: &WebsiteSource) -> Self; + fn add_files_routes(self, website: &Website) -> Self; } impl FilesRoutes for ApiRouter { - fn add_files_routes(self, website: &WebsiteSource) -> Self { + fn add_files_routes(self, website: &Website) -> Self { if website.is_enabled() { self.route("/{*path}", get(file_handler)) .route("/", get(index_handler)) diff --git a/crates/brk_server/src/files/website.rs b/crates/brk_server/src/files/website.rs new file mode 100644 index 000000000..4b2eb19ff --- /dev/null +++ b/crates/brk_server/src/files/website.rs @@ -0,0 +1,193 @@ +use std::{ + fs, + path::{Path, PathBuf}, + sync::OnceLock, +}; + +use importmap::ImportMap; +use tracing::{error, info}; + +use crate::{EMBEDDED_WEBSITE, Error, Result}; + +/// Cached index.html with importmap injected +static INDEX_HTML: OnceLock = OnceLock::new(); + +/// Source for serving the website +#[derive(Debug, Clone)] +pub enum Website { + Disabled, + Default, + Filesystem(PathBuf), +} + +impl Website { + pub fn is_enabled(&self) -> bool { + !matches!(self, Self::Disabled) + } + + /// Returns the filesystem path if available, None means use embedded + pub fn filesystem_path(&self) -> Option { + match self { + Self::Disabled => None, + Self::Default => { + if cfg!(debug_assertions) { + let local = PathBuf::from("./website"); + local.exists().then_some(local) + } else { + None + } + } + Self::Filesystem(p) => Some(p.clone()), + } + } + + /// Get file content by path (handles hash-stripping, SPA fallback, importmap) + pub fn get_file(&self, path: &str) -> Result> { + match self.filesystem_path() { + None => self.get_embedded(path), + Some(base) => self.get_filesystem(&base, path), + } + } + + /// Log which website source is being used (call once at startup) + pub fn log(&self) { + match self { + Self::Disabled => info!("Website: disabled"), + Self::Default => { + if let Some(p) = self.filesystem_path() { + info!("Website: filesystem ({})", p.display()); + } else { + info!("Website: embedded"); + } + } + Self::Filesystem(p) => info!("Website: filesystem ({})", p.display()), + } + } + + fn get_index(&self) -> Result> { + // Debug mode: no importmap, no cache + if cfg!(debug_assertions) { + return match self.filesystem_path() { + Some(base) => { + fs::read(base.join("index.html")).map_err(|e| Error::not_found(e.to_string())) + } + None => { + let file = EMBEDDED_WEBSITE + .get_file("index.html") + .expect("index.html must exist in embedded website"); + Ok(file.contents().to_vec()) + } + }; + } + + // Release mode: cache with importmap + let html = INDEX_HTML.get_or_init(|| match self.filesystem_path() { + None => { + let file = EMBEDDED_WEBSITE + .get_file("index.html") + .expect("index.html must exist in embedded website"); + + let html = + std::str::from_utf8(file.contents()).expect("index.html must be valid UTF-8"); + + let importmap = ImportMap::scan_embedded(&EMBEDDED_WEBSITE, ""); + importmap + .transform_html(html) + .unwrap_or_else(|| html.to_string()) + } + Some(base) => { + let html = + fs::read_to_string(base.join("index.html")).expect("index.html must exist"); + + match ImportMap::scan(&base, "") { + Ok(importmap) => importmap.transform_html(&html).unwrap_or(html), + Err(e) => { + error!("Failed to scan for importmap: {e}"); + html + } + } + } + }); + + Ok(html.as_bytes().to_vec()) + } + + fn get_embedded(&self, path: &str) -> Result> { + // Index.html + if path.is_empty() || path == "index.html" { + return self.get_index(); + } + + // Try direct lookup, then with hash stripped + let file = EMBEDDED_WEBSITE.get_file(path).or_else(|| { + strip_importmap_hash(Path::new(path)) + .and_then(|unhashed| EMBEDDED_WEBSITE.get_file(unhashed.to_str()?)) + }); + + if let Some(file) = file { + return Ok(file.contents().to_vec()); + } + + // SPA fallback: no extension -> index.html + if Path::new(path).extension().is_none() { + return self.get_index(); + } + + Err(Error::not_found("File not found")) + } + + fn get_filesystem(&self, base: &Path, path: &str) -> Result> { + // Index.html + if path.is_empty() { + return self.get_index(); + } + + let mut file_path = base.join(path); + + // Try with hash stripped + if !file_path.exists() + && let Some(unhashed) = strip_importmap_hash(&file_path) + && unhashed.exists() + { + file_path = unhashed; + } + + // SPA fallback or missing file + if !file_path.exists() || file_path.is_dir() { + if file_path.extension().is_some() { + return Err(Error::not_found("File not found")); + } + return self.get_index(); + } + + // Explicit index.html request + if file_path.file_name().is_some_and(|n| n == "index.html") { + return self.get_index(); + } + + fs::read(&file_path).map_err(|e| { + error!("{e}"); + Error::not_found("File not found") + }) + } +} + +/// Strip importmap hash from filename: `foo.abc12345.js` -> `foo.js` +fn strip_importmap_hash(path: &Path) -> Option { + let stem = path.file_stem()?.to_str()?; + let ext = path.extension()?.to_str()?; + + if !matches!(ext, "js" | "mjs" | "css") { + return None; + } + + let dot_pos = stem.rfind('.')?; + let hash = &stem[dot_pos + 1..]; + + if hash.len() == 8 && hash.chars().all(|c| c.is_ascii_hexdigit()) { + let name = &stem[..dot_pos]; + Some(path.with_file_name(format!("{}.{}", name, ext))) + } else { + None + } +} diff --git a/crates/brk_server/src/lib.rs b/crates/brk_server/src/lib.rs index cb76b206f..612c3fc76 100644 --- a/crates/brk_server/src/lib.rs +++ b/crates/brk_server/src/lib.rs @@ -9,7 +9,7 @@ use std::{ use aide::axum::ApiRouter; use axum::{ - Extension, + Extension, ServiceExt, body::Body, http::{Request, Response, StatusCode, Uri}, middleware::Next, @@ -26,25 +26,12 @@ use tower_http::{ compression::CompressionLayer, cors::CorsLayer, normalize_path::NormalizePathLayer, timeout::TimeoutLayer, trace::TraceLayer, }; +use tower_layer::Layer; use tracing::{error, info}; /// Embedded website assets pub static EMBEDDED_WEBSITE: Dir = include_dir!("$CARGO_MANIFEST_DIR/website"); -/// Source for serving the website -#[derive(Debug, Clone)] -pub enum WebsiteSource { - Disabled, - Embedded, - Filesystem(PathBuf), -} - -impl WebsiteSource { - pub fn is_enabled(&self) -> bool { - !matches!(self, Self::Disabled) - } -} - mod api; pub mod cache; mod error; @@ -57,6 +44,7 @@ pub use cache::{CacheParams, CacheStrategy}; pub use error::{Error, Result}; use extended::*; use files::FilesRoutes; +pub use files::Website; use state::*; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -64,7 +52,8 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub struct Server(AppState); impl Server { - pub fn new(query: &AsyncQuery, data_path: PathBuf, website: WebsiteSource) -> Self { + pub fn new(query: &AsyncQuery, data_path: PathBuf, website: Website) -> Self { + website.log(); Self(AppState { client: query.client().clone(), query: query.clone(), @@ -147,8 +136,7 @@ impl Server { .layer(response_uri_layer) .layer(trace_layer) .layer(TimeoutLayer::with_status_code(StatusCode::GATEWAY_TIMEOUT, Duration::from_secs(5))) - .layer(CorsLayer::permissive()) - .layer(NormalizePathLayer::trim_trailing_slash()); + .layer(CorsLayer::permissive()); const BASE_PORT: u16 = 3110; const MAX_PORT: u16 = BASE_PORT + 100; @@ -190,12 +178,15 @@ impl Server { Err(_) => error!("Client generation panicked"), } + let router = router + .layer(Extension(Arc::new(openapi))) + .layer(Extension(openapi_trimmed)); + + let service = NormalizePathLayer::trim_trailing_slash().layer(router); + serve( listener, - router - .layer(Extension(Arc::new(openapi))) - .layer(Extension(openapi_trimmed)) - .into_make_service(), + ServiceExt::>::into_make_service(service), ) .await?; diff --git a/crates/brk_server/src/state.rs b/crates/brk_server/src/state.rs index 889abad48..3460b91c2 100644 --- a/crates/brk_server/src/state.rs +++ b/crates/brk_server/src/state.rs @@ -13,7 +13,7 @@ use quick_cache::sync::Cache; use serde::Serialize; use crate::{ - CacheParams, CacheStrategy, WebsiteSource, + CacheParams, CacheStrategy, Website, extended::{ResponseExtended, ResultExtended}, }; @@ -22,7 +22,7 @@ pub struct AppState { #[deref] pub query: AsyncQuery, pub data_path: PathBuf, - pub website: WebsiteSource, + pub website: Website, pub cache: Arc>, pub client: Client, pub started_at: Timestamp, diff --git a/crates/brk_types/src/metricdata.rs b/crates/brk_types/src/metricdata.rs index 59f81eae0..579fb283d 100644 --- a/crates/brk_types/src/metricdata.rs +++ b/crates/brk_types/src/metricdata.rs @@ -5,6 +5,8 @@ use serde::Deserialize; use serde_json::Value; use vecdb::AnySerializableVec; +use super::Timestamp; + /// Metric data with range information. /// /// All metric data endpoints return this structure when format is JSON. @@ -19,12 +21,14 @@ pub struct MetricData { pub start: usize, /// End index (exclusive) of the returned range pub end: usize, + /// ISO 8601 timestamp of when the response was generated + pub stamp: String, /// The metric data pub data: Vec, } impl MetricData { - /// Write metric data as JSON to buffer: `{"version":N,"total":N,"start":N,"end":N,"data":[...]}` + /// Write metric data as JSON to buffer: `{"version":N,"total":N,"start":N,"end":N,"stamp":"...","data":[...]}` pub fn serialize( vec: &dyn AnySerializableVec, start: usize, @@ -35,10 +39,11 @@ impl MetricData { let total = vec.len(); let end = end.min(total); let start = start.min(end); + let stamp = Timestamp::now().to_iso8601(); write!( buf, - r#"{{"version":{version},"total":{total},"start":{start},"end":{end},"data":"#, + r#"{{"version":{version},"total":{total},"start":{start},"end":{end},"stamp":"{stamp}","data":"#, )?; vec.write_json(Some(start), Some(end), buf)?; buf.push(b'}'); diff --git a/docs/HOSTING.md b/docs/HOSTING.md index 468ff3df2..a1664d9d7 100644 --- a/docs/HOSTING.md +++ b/docs/HOSTING.md @@ -2,7 +2,7 @@ ## Self-Hosting -BRK is designed to be self-hosted. See the [brk_cli documentation](https://docs.rs/brk_cli) for installation, requirements, and configuration options. +BRK is designed to be self-hosted. See the [brk_cli documentation](https://github.com/bitcoinresearchkit/brk/blob/main/crates/brk_cli/README.md#brk_cli) for installation, requirements, and configuration options. ## Professional Hosting diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 7d0624d41..9f8508104 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1645,59 +1645,6 @@ function createPrice111dSmaPattern(client, acc) { }; } -/** - * @typedef {Object} ActivePriceRatioPattern - * @property {MetricPattern4} ratio - * @property {MetricPattern4} ratio1mSma - * @property {MetricPattern4} ratio1wSma - * @property {Ratio1ySdPattern} ratio1ySd - * @property {Ratio1ySdPattern} ratio2ySd - * @property {Ratio1ySdPattern} ratio4ySd - * @property {MetricPattern4} ratioPct1 - * @property {MetricPattern4} ratioPct1Usd - * @property {MetricPattern4} ratioPct2 - * @property {MetricPattern4} ratioPct2Usd - * @property {MetricPattern4} ratioPct5 - * @property {MetricPattern4} ratioPct5Usd - * @property {MetricPattern4} ratioPct95 - * @property {MetricPattern4} ratioPct95Usd - * @property {MetricPattern4} ratioPct98 - * @property {MetricPattern4} ratioPct98Usd - * @property {MetricPattern4} ratioPct99 - * @property {MetricPattern4} ratioPct99Usd - * @property {Ratio1ySdPattern} ratioSd - */ - -/** - * Create a ActivePriceRatioPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {ActivePriceRatioPattern} - */ -function createActivePriceRatioPattern(client, acc) { - return { - ratio: createMetricPattern4(client, acc), - ratio1mSma: createMetricPattern4(client, _m(acc, '1m_sma')), - ratio1wSma: createMetricPattern4(client, _m(acc, '1w_sma')), - ratio1ySd: createRatio1ySdPattern(client, _m(acc, '1y')), - ratio2ySd: createRatio1ySdPattern(client, _m(acc, '2y')), - ratio4ySd: createRatio1ySdPattern(client, _m(acc, '4y')), - ratioPct1: createMetricPattern4(client, _m(acc, 'pct1')), - ratioPct1Usd: createMetricPattern4(client, _m(acc, 'pct1_usd')), - ratioPct2: createMetricPattern4(client, _m(acc, 'pct2')), - ratioPct2Usd: createMetricPattern4(client, _m(acc, 'pct2_usd')), - ratioPct5: createMetricPattern4(client, _m(acc, 'pct5')), - ratioPct5Usd: createMetricPattern4(client, _m(acc, 'pct5_usd')), - ratioPct95: createMetricPattern4(client, _m(acc, 'pct95')), - ratioPct95Usd: createMetricPattern4(client, _m(acc, 'pct95_usd')), - ratioPct98: createMetricPattern4(client, _m(acc, 'pct98')), - ratioPct98Usd: createMetricPattern4(client, _m(acc, 'pct98_usd')), - ratioPct99: createMetricPattern4(client, _m(acc, 'pct99')), - ratioPct99Usd: createMetricPattern4(client, _m(acc, 'pct99_usd')), - ratioSd: createRatio1ySdPattern(client, acc), - }; -} - /** * @typedef {Object} PercentilesPattern * @property {MetricPattern4} pct05 @@ -1751,6 +1698,59 @@ function createPercentilesPattern(client, acc) { }; } +/** + * @typedef {Object} ActivePriceRatioPattern + * @property {MetricPattern4} ratio + * @property {MetricPattern4} ratio1mSma + * @property {MetricPattern4} ratio1wSma + * @property {Ratio1ySdPattern} ratio1ySd + * @property {Ratio1ySdPattern} ratio2ySd + * @property {Ratio1ySdPattern} ratio4ySd + * @property {MetricPattern4} ratioPct1 + * @property {MetricPattern4} ratioPct1Usd + * @property {MetricPattern4} ratioPct2 + * @property {MetricPattern4} ratioPct2Usd + * @property {MetricPattern4} ratioPct5 + * @property {MetricPattern4} ratioPct5Usd + * @property {MetricPattern4} ratioPct95 + * @property {MetricPattern4} ratioPct95Usd + * @property {MetricPattern4} ratioPct98 + * @property {MetricPattern4} ratioPct98Usd + * @property {MetricPattern4} ratioPct99 + * @property {MetricPattern4} ratioPct99Usd + * @property {Ratio1ySdPattern} ratioSd + */ + +/** + * Create a ActivePriceRatioPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {ActivePriceRatioPattern} + */ +function createActivePriceRatioPattern(client, acc) { + return { + ratio: createMetricPattern4(client, acc), + ratio1mSma: createMetricPattern4(client, _m(acc, '1m_sma')), + ratio1wSma: createMetricPattern4(client, _m(acc, '1w_sma')), + ratio1ySd: createRatio1ySdPattern(client, _m(acc, '1y')), + ratio2ySd: createRatio1ySdPattern(client, _m(acc, '2y')), + ratio4ySd: createRatio1ySdPattern(client, _m(acc, '4y')), + ratioPct1: createMetricPattern4(client, _m(acc, 'pct1')), + ratioPct1Usd: createMetricPattern4(client, _m(acc, 'pct1_usd')), + ratioPct2: createMetricPattern4(client, _m(acc, 'pct2')), + ratioPct2Usd: createMetricPattern4(client, _m(acc, 'pct2_usd')), + ratioPct5: createMetricPattern4(client, _m(acc, 'pct5')), + ratioPct5Usd: createMetricPattern4(client, _m(acc, 'pct5_usd')), + ratioPct95: createMetricPattern4(client, _m(acc, 'pct95')), + ratioPct95Usd: createMetricPattern4(client, _m(acc, 'pct95_usd')), + ratioPct98: createMetricPattern4(client, _m(acc, 'pct98')), + ratioPct98Usd: createMetricPattern4(client, _m(acc, 'pct98_usd')), + ratioPct99: createMetricPattern4(client, _m(acc, 'pct99')), + ratioPct99Usd: createMetricPattern4(client, _m(acc, 'pct99_usd')), + ratioSd: createRatio1ySdPattern(client, acc), + }; +} + /** * @typedef {Object} RelativePattern5 * @property {MetricPattern1} negUnrealizedLossRelToMarketCap @@ -2005,45 +2005,6 @@ function createBitcoinPattern(client, acc) { }; } -/** - * @template T - * @typedef {Object} ClassAveragePricePattern - * @property {MetricPattern4} _2015 - * @property {MetricPattern4} _2016 - * @property {MetricPattern4} _2017 - * @property {MetricPattern4} _2018 - * @property {MetricPattern4} _2019 - * @property {MetricPattern4} _2020 - * @property {MetricPattern4} _2021 - * @property {MetricPattern4} _2022 - * @property {MetricPattern4} _2023 - * @property {MetricPattern4} _2024 - * @property {MetricPattern4} _2025 - */ - -/** - * Create a ClassAveragePricePattern pattern node - * @template T - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {ClassAveragePricePattern} - */ -function createClassAveragePricePattern(client, acc) { - return { - _2015: createMetricPattern4(client, _m(acc, '2015_average_price')), - _2016: createMetricPattern4(client, _m(acc, '2016_average_price')), - _2017: createMetricPattern4(client, _m(acc, '2017_average_price')), - _2018: createMetricPattern4(client, _m(acc, '2018_average_price')), - _2019: createMetricPattern4(client, _m(acc, '2019_average_price')), - _2020: createMetricPattern4(client, _m(acc, '2020_average_price')), - _2021: createMetricPattern4(client, _m(acc, '2021_average_price')), - _2022: createMetricPattern4(client, _m(acc, '2022_average_price')), - _2023: createMetricPattern4(client, _m(acc, '2023_average_price')), - _2024: createMetricPattern4(client, _m(acc, '2024_average_price')), - _2025: createMetricPattern4(client, _m(acc, '2025_average_price')), - }; -} - /** * @template T * @typedef {Object} DollarsPattern @@ -2084,37 +2045,41 @@ function createDollarsPattern(client, acc) { } /** - * @typedef {Object} RelativePattern - * @property {MetricPattern1} negUnrealizedLossRelToMarketCap - * @property {MetricPattern1} netUnrealizedPnlRelToMarketCap - * @property {MetricPattern1} nupl - * @property {MetricPattern1} supplyInLossRelToCirculatingSupply - * @property {MetricPattern1} supplyInLossRelToOwnSupply - * @property {MetricPattern1} supplyInProfitRelToCirculatingSupply - * @property {MetricPattern1} supplyInProfitRelToOwnSupply - * @property {MetricPattern4} supplyRelToCirculatingSupply - * @property {MetricPattern1} unrealizedLossRelToMarketCap - * @property {MetricPattern1} unrealizedProfitRelToMarketCap + * @template T + * @typedef {Object} ClassAveragePricePattern + * @property {MetricPattern4} _2015 + * @property {MetricPattern4} _2016 + * @property {MetricPattern4} _2017 + * @property {MetricPattern4} _2018 + * @property {MetricPattern4} _2019 + * @property {MetricPattern4} _2020 + * @property {MetricPattern4} _2021 + * @property {MetricPattern4} _2022 + * @property {MetricPattern4} _2023 + * @property {MetricPattern4} _2024 + * @property {MetricPattern4} _2025 */ /** - * Create a RelativePattern pattern node + * Create a ClassAveragePricePattern pattern node + * @template T * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {RelativePattern} + * @returns {ClassAveragePricePattern} */ -function createRelativePattern(client, acc) { +function createClassAveragePricePattern(client, acc) { return { - negUnrealizedLossRelToMarketCap: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_market_cap')), - netUnrealizedPnlRelToMarketCap: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_market_cap')), - nupl: createMetricPattern1(client, _m(acc, 'nupl')), - supplyInLossRelToCirculatingSupply: createMetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_circulating_supply')), - supplyInLossRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')), - supplyInProfitRelToCirculatingSupply: createMetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_circulating_supply')), - supplyInProfitRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')), - supplyRelToCirculatingSupply: createMetricPattern4(client, _m(acc, 'supply_rel_to_circulating_supply')), - unrealizedLossRelToMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap')), - unrealizedProfitRelToMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap')), + _2015: createMetricPattern4(client, _m(acc, '2015_average_price')), + _2016: createMetricPattern4(client, _m(acc, '2016_average_price')), + _2017: createMetricPattern4(client, _m(acc, '2017_average_price')), + _2018: createMetricPattern4(client, _m(acc, '2018_average_price')), + _2019: createMetricPattern4(client, _m(acc, '2019_average_price')), + _2020: createMetricPattern4(client, _m(acc, '2020_average_price')), + _2021: createMetricPattern4(client, _m(acc, '2021_average_price')), + _2022: createMetricPattern4(client, _m(acc, '2022_average_price')), + _2023: createMetricPattern4(client, _m(acc, '2023_average_price')), + _2024: createMetricPattern4(client, _m(acc, '2024_average_price')), + _2025: createMetricPattern4(client, _m(acc, '2025_average_price')), }; } @@ -2153,6 +2118,41 @@ function createRelativePattern2(client, acc) { }; } +/** + * @typedef {Object} RelativePattern + * @property {MetricPattern1} negUnrealizedLossRelToMarketCap + * @property {MetricPattern1} netUnrealizedPnlRelToMarketCap + * @property {MetricPattern1} nupl + * @property {MetricPattern1} supplyInLossRelToCirculatingSupply + * @property {MetricPattern1} supplyInLossRelToOwnSupply + * @property {MetricPattern1} supplyInProfitRelToCirculatingSupply + * @property {MetricPattern1} supplyInProfitRelToOwnSupply + * @property {MetricPattern4} supplyRelToCirculatingSupply + * @property {MetricPattern1} unrealizedLossRelToMarketCap + * @property {MetricPattern1} unrealizedProfitRelToMarketCap + */ + +/** + * Create a RelativePattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {RelativePattern} + */ +function createRelativePattern(client, acc) { + return { + negUnrealizedLossRelToMarketCap: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_market_cap')), + netUnrealizedPnlRelToMarketCap: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_market_cap')), + nupl: createMetricPattern1(client, _m(acc, 'nupl')), + supplyInLossRelToCirculatingSupply: createMetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_circulating_supply')), + supplyInLossRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')), + supplyInProfitRelToCirculatingSupply: createMetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_circulating_supply')), + supplyInProfitRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')), + supplyRelToCirculatingSupply: createMetricPattern4(client, _m(acc, 'supply_rel_to_circulating_supply')), + unrealizedLossRelToMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap')), + unrealizedProfitRelToMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap')), + }; +} + /** * @template T * @typedef {Object} CountPattern2 @@ -2358,60 +2358,31 @@ function createPhaseDailyCentsPattern(client, acc) { } /** - * @typedef {Object} PeriodCagrPattern - * @property {MetricPattern4} _10y - * @property {MetricPattern4} _2y - * @property {MetricPattern4} _3y - * @property {MetricPattern4} _4y - * @property {MetricPattern4} _5y - * @property {MetricPattern4} _6y - * @property {MetricPattern4} _8y + * @typedef {Object} UnrealizedPattern + * @property {MetricPattern1} negUnrealizedLoss + * @property {MetricPattern1} netUnrealizedPnl + * @property {ActiveSupplyPattern} supplyInLoss + * @property {ActiveSupplyPattern} supplyInProfit + * @property {MetricPattern1} totalUnrealizedPnl + * @property {MetricPattern1} unrealizedLoss + * @property {MetricPattern1} unrealizedProfit */ /** - * Create a PeriodCagrPattern pattern node + * Create a UnrealizedPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {PeriodCagrPattern} + * @returns {UnrealizedPattern} */ -function createPeriodCagrPattern(client, acc) { +function createUnrealizedPattern(client, acc) { return { - _10y: createMetricPattern4(client, _p('10y', acc)), - _2y: createMetricPattern4(client, _p('2y', acc)), - _3y: createMetricPattern4(client, _p('3y', acc)), - _4y: createMetricPattern4(client, _p('4y', acc)), - _5y: createMetricPattern4(client, _p('5y', acc)), - _6y: createMetricPattern4(client, _p('6y', acc)), - _8y: createMetricPattern4(client, _p('8y', acc)), - }; -} - -/** - * @typedef {Object} _0satsPattern2 - * @property {ActivityPattern2} activity - * @property {CostBasisPattern} costBasis - * @property {OutputsPattern} outputs - * @property {RealizedPattern} realized - * @property {RelativePattern4} relative - * @property {SupplyPattern2} supply - * @property {UnrealizedPattern} unrealized - */ - -/** - * Create a _0satsPattern2 pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {_0satsPattern2} - */ -function create_0satsPattern2(client, acc) { - return { - activity: createActivityPattern2(client, acc), - costBasis: createCostBasisPattern(client, acc), - outputs: createOutputsPattern(client, _m(acc, 'utxo_count')), - realized: createRealizedPattern(client, acc), - relative: createRelativePattern4(client, _m(acc, 'supply_in')), - supply: createSupplyPattern2(client, _m(acc, 'supply')), - unrealized: createUnrealizedPattern(client, acc), + negUnrealizedLoss: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss')), + netUnrealizedPnl: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl')), + supplyInLoss: createActiveSupplyPattern(client, _m(acc, 'supply_in_loss')), + supplyInProfit: createActiveSupplyPattern(client, _m(acc, 'supply_in_profit')), + totalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'total_unrealized_pnl')), + unrealizedLoss: createMetricPattern1(client, _m(acc, 'unrealized_loss')), + unrealizedProfit: createMetricPattern1(client, _m(acc, 'unrealized_profit')), }; } @@ -2445,31 +2416,60 @@ function create_100btcPattern(client, acc) { } /** - * @typedef {Object} UnrealizedPattern - * @property {MetricPattern1} negUnrealizedLoss - * @property {MetricPattern1} netUnrealizedPnl - * @property {ActiveSupplyPattern} supplyInLoss - * @property {ActiveSupplyPattern} supplyInProfit - * @property {MetricPattern1} totalUnrealizedPnl - * @property {MetricPattern1} unrealizedLoss - * @property {MetricPattern1} unrealizedProfit + * @typedef {Object} _0satsPattern2 + * @property {ActivityPattern2} activity + * @property {CostBasisPattern} costBasis + * @property {OutputsPattern} outputs + * @property {RealizedPattern} realized + * @property {RelativePattern4} relative + * @property {SupplyPattern2} supply + * @property {UnrealizedPattern} unrealized */ /** - * Create a UnrealizedPattern pattern node + * Create a _0satsPattern2 pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {UnrealizedPattern} + * @returns {_0satsPattern2} */ -function createUnrealizedPattern(client, acc) { +function create_0satsPattern2(client, acc) { return { - negUnrealizedLoss: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss')), - netUnrealizedPnl: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl')), - supplyInLoss: createActiveSupplyPattern(client, _m(acc, 'supply_in_loss')), - supplyInProfit: createActiveSupplyPattern(client, _m(acc, 'supply_in_profit')), - totalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'total_unrealized_pnl')), - unrealizedLoss: createMetricPattern1(client, _m(acc, 'unrealized_loss')), - unrealizedProfit: createMetricPattern1(client, _m(acc, 'unrealized_profit')), + activity: createActivityPattern2(client, acc), + costBasis: createCostBasisPattern(client, acc), + outputs: createOutputsPattern(client, _m(acc, 'utxo_count')), + realized: createRealizedPattern(client, acc), + relative: createRelativePattern4(client, _m(acc, 'supply_in')), + supply: createSupplyPattern2(client, _m(acc, 'supply')), + unrealized: createUnrealizedPattern(client, acc), + }; +} + +/** + * @typedef {Object} _10yPattern + * @property {ActivityPattern2} activity + * @property {CostBasisPattern} costBasis + * @property {OutputsPattern} outputs + * @property {RealizedPattern4} realized + * @property {RelativePattern} relative + * @property {SupplyPattern2} supply + * @property {UnrealizedPattern} unrealized + */ + +/** + * Create a _10yPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {_10yPattern} + */ +function create_10yPattern(client, acc) { + return { + activity: createActivityPattern2(client, acc), + costBasis: createCostBasisPattern(client, acc), + outputs: createOutputsPattern(client, _m(acc, 'utxo_count')), + realized: createRealizedPattern4(client, acc), + relative: createRelativePattern(client, acc), + supply: createSupplyPattern2(client, _m(acc, 'supply')), + unrealized: createUnrealizedPattern(client, acc), }; } @@ -2503,31 +2503,31 @@ function create_10yTo12yPattern(client, acc) { } /** - * @typedef {Object} _10yPattern - * @property {ActivityPattern2} activity - * @property {CostBasisPattern} costBasis - * @property {OutputsPattern} outputs - * @property {RealizedPattern4} realized - * @property {RelativePattern} relative - * @property {SupplyPattern2} supply - * @property {UnrealizedPattern} unrealized + * @typedef {Object} PeriodCagrPattern + * @property {MetricPattern4} _10y + * @property {MetricPattern4} _2y + * @property {MetricPattern4} _3y + * @property {MetricPattern4} _4y + * @property {MetricPattern4} _5y + * @property {MetricPattern4} _6y + * @property {MetricPattern4} _8y */ /** - * Create a _10yPattern pattern node + * Create a PeriodCagrPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {_10yPattern} + * @returns {PeriodCagrPattern} */ -function create_10yPattern(client, acc) { +function createPeriodCagrPattern(client, acc) { return { - activity: createActivityPattern2(client, acc), - costBasis: createCostBasisPattern(client, acc), - outputs: createOutputsPattern(client, _m(acc, 'utxo_count')), - realized: createRealizedPattern4(client, acc), - relative: createRelativePattern(client, acc), - supply: createSupplyPattern2(client, _m(acc, 'supply')), - unrealized: createUnrealizedPattern(client, acc), + _10y: createMetricPattern4(client, _p('10y', acc)), + _2y: createMetricPattern4(client, _p('2y', acc)), + _3y: createMetricPattern4(client, _p('3y', acc)), + _4y: createMetricPattern4(client, _p('4y', acc)), + _5y: createMetricPattern4(client, _p('5y', acc)), + _6y: createMetricPattern4(client, _p('6y', acc)), + _8y: createMetricPattern4(client, _p('8y', acc)), }; } @@ -2582,23 +2582,65 @@ function createSplitPattern2(client, acc) { } /** - * @typedef {Object} ActiveSupplyPattern - * @property {MetricPattern1} bitcoin - * @property {MetricPattern1} dollars - * @property {MetricPattern1} sats + * @typedef {Object} UnclaimedRewardsPattern + * @property {BitcoinPattern2} bitcoin + * @property {BlockCountPattern} dollars + * @property {BlockCountPattern} sats */ /** - * Create a ActiveSupplyPattern pattern node + * Create a UnclaimedRewardsPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {ActiveSupplyPattern} + * @returns {UnclaimedRewardsPattern} */ -function createActiveSupplyPattern(client, acc) { +function createUnclaimedRewardsPattern(client, acc) { return { - bitcoin: createMetricPattern1(client, _m(acc, 'btc')), - dollars: createMetricPattern1(client, _m(acc, 'usd')), - sats: createMetricPattern1(client, acc), + bitcoin: createBitcoinPattern2(client, _m(acc, 'btc')), + dollars: createBlockCountPattern(client, _m(acc, 'usd')), + sats: createBlockCountPattern(client, acc), + }; +} + +/** + * @typedef {Object} CoinbasePattern + * @property {BitcoinPattern} bitcoin + * @property {DollarsPattern} dollars + * @property {DollarsPattern} sats + */ + +/** + * Create a CoinbasePattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {CoinbasePattern} + */ +function createCoinbasePattern(client, acc) { + return { + bitcoin: createBitcoinPattern(client, _m(acc, 'btc')), + dollars: createDollarsPattern(client, _m(acc, 'usd')), + sats: createDollarsPattern(client, acc), + }; +} + +/** + * @typedef {Object} SegwitAdoptionPattern + * @property {MetricPattern11} base + * @property {MetricPattern2} cumulative + * @property {MetricPattern2} sum + */ + +/** + * Create a SegwitAdoptionPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {SegwitAdoptionPattern} + */ +function createSegwitAdoptionPattern(client, acc) { + return { + base: createMetricPattern11(client, acc), + cumulative: createMetricPattern2(client, _m(acc, 'cumulative')), + sum: createMetricPattern2(client, _m(acc, 'sum')), }; } @@ -2623,6 +2665,27 @@ function create_2015Pattern(client, acc) { }; } +/** + * @typedef {Object} ActiveSupplyPattern + * @property {MetricPattern1} bitcoin + * @property {MetricPattern1} dollars + * @property {MetricPattern1} sats + */ + +/** + * Create a ActiveSupplyPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {ActiveSupplyPattern} + */ +function createActiveSupplyPattern(client, acc) { + return { + bitcoin: createMetricPattern1(client, _m(acc, 'btc')), + dollars: createMetricPattern1(client, _m(acc, 'usd')), + sats: createMetricPattern1(client, acc), + }; +} + /** * @typedef {Object} CoinbasePattern2 * @property {BlockCountPattern} bitcoin @@ -2666,65 +2729,21 @@ function createCostBasisPattern2(client, acc) { } /** - * @typedef {Object} SegwitAdoptionPattern - * @property {MetricPattern11} base - * @property {MetricPattern2} cumulative - * @property {MetricPattern2} sum + * @typedef {Object} CostBasisPattern + * @property {MetricPattern1} max + * @property {MetricPattern1} min */ /** - * Create a SegwitAdoptionPattern pattern node + * Create a CostBasisPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {SegwitAdoptionPattern} + * @returns {CostBasisPattern} */ -function createSegwitAdoptionPattern(client, acc) { +function createCostBasisPattern(client, acc) { return { - base: createMetricPattern11(client, acc), - cumulative: createMetricPattern2(client, _m(acc, 'cumulative')), - sum: createMetricPattern2(client, _m(acc, 'sum')), - }; -} - -/** - * @typedef {Object} UnclaimedRewardsPattern - * @property {BitcoinPattern2} bitcoin - * @property {BlockCountPattern} dollars - * @property {BlockCountPattern} sats - */ - -/** - * Create a UnclaimedRewardsPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {UnclaimedRewardsPattern} - */ -function createUnclaimedRewardsPattern(client, acc) { - return { - bitcoin: createBitcoinPattern2(client, _m(acc, 'btc')), - dollars: createBlockCountPattern(client, _m(acc, 'usd')), - sats: createBlockCountPattern(client, acc), - }; -} - -/** - * @typedef {Object} CoinbasePattern - * @property {BitcoinPattern} bitcoin - * @property {DollarsPattern} dollars - * @property {DollarsPattern} sats - */ - -/** - * Create a CoinbasePattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {CoinbasePattern} - */ -function createCoinbasePattern(client, acc) { - return { - bitcoin: createBitcoinPattern(client, _m(acc, 'btc')), - dollars: createDollarsPattern(client, _m(acc, 'usd')), - sats: createDollarsPattern(client, acc), + max: createMetricPattern1(client, _m(acc, 'max_cost_basis')), + min: createMetricPattern1(client, _m(acc, 'min_cost_basis')), }; } @@ -2766,25 +2785,6 @@ function create_1dReturns1mSdPattern(client, acc) { }; } -/** - * @typedef {Object} CostBasisPattern - * @property {MetricPattern1} max - * @property {MetricPattern1} min - */ - -/** - * Create a CostBasisPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {CostBasisPattern} - */ -function createCostBasisPattern(client, acc) { - return { - max: createMetricPattern1(client, _m(acc, 'max_cost_basis')), - min: createMetricPattern1(client, _m(acc, 'min_cost_basis')), - }; -} - /** * @typedef {Object} SupplyPattern2 * @property {ActiveSupplyPattern} halved @@ -2806,22 +2806,22 @@ function createSupplyPattern2(client, acc) { /** * @template T - * @typedef {Object} BlockCountPattern - * @property {MetricPattern1} cumulative - * @property {MetricPattern1} sum + * @typedef {Object} SatsPattern + * @property {MetricPattern1} ohlc + * @property {SplitPattern2} split */ /** - * Create a BlockCountPattern pattern node + * Create a SatsPattern pattern node * @template T * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {BlockCountPattern} + * @returns {SatsPattern} */ -function createBlockCountPattern(client, acc) { +function createSatsPattern(client, acc) { return { - cumulative: createMetricPattern1(client, _m(acc, 'cumulative')), - sum: createMetricPattern1(client, acc), + ohlc: createMetricPattern1(client, _m(acc, 'ohlc')), + split: createSplitPattern2(client, acc), }; } @@ -2848,22 +2848,22 @@ function createBitcoinPattern2(client, acc) { /** * @template T - * @typedef {Object} SatsPattern - * @property {MetricPattern1} ohlc - * @property {SplitPattern2} split + * @typedef {Object} BlockCountPattern + * @property {MetricPattern1} cumulative + * @property {MetricPattern1} sum */ /** - * Create a SatsPattern pattern node + * Create a BlockCountPattern pattern node * @template T * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {SatsPattern} + * @returns {BlockCountPattern} */ -function createSatsPattern(client, acc) { +function createBlockCountPattern(client, acc) { return { - ohlc: createMetricPattern1(client, _m(acc, 'ohlc_sats')), - split: createSplitPattern2(client, _m(acc, 'sats')), + cumulative: createMetricPattern1(client, _m(acc, 'cumulative')), + sum: createMetricPattern1(client, acc), }; } @@ -4056,8 +4056,8 @@ function createRealizedPriceExtraPattern(client, acc) { * @typedef {Object} MetricsTree_Price * @property {MetricsTree_Price_Cents} cents * @property {MetricsTree_Price_Oracle} oracle - * @property {SatsPattern} sats - * @property {MetricsTree_Price_Usd} usd + * @property {MetricsTree_Price_Sats} sats + * @property {SatsPattern} usd */ /** @@ -4105,9 +4105,9 @@ function createRealizedPriceExtraPattern(client, acc) { */ /** - * @typedef {Object} MetricsTree_Price_Usd - * @property {MetricPattern1} ohlc - * @property {SplitPattern2} split + * @typedef {Object} MetricsTree_Price_Sats + * @property {MetricPattern1} ohlc + * @property {SplitPattern2} split */ /** @@ -6087,11 +6087,11 @@ class BrkClient extends BrkClientBase { priceCents: createMetricPattern11(this, 'oracle_price_cents'), txCount: createMetricPattern6(this, 'oracle_tx_count'), }, - sats: createSatsPattern(this, 'price'), - usd: { - ohlc: createMetricPattern1(this, 'price_ohlc'), - split: createSplitPattern2(this, 'price'), + sats: { + ohlc: createMetricPattern1(this, 'price_ohlc_sats'), + split: createSplitPattern2(this, 'price_sats'), }, + usd: createSatsPattern(this, 'price'), }, scripts: { count: { diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 40fd92e9d..cb91960cc 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -1883,31 +1883,6 @@ class Price111dSmaPattern: self.ratio_pct99_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'ratio_pct99_usd')) self.ratio_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, 'ratio')) -class ActivePriceRatioPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.ratio: MetricPattern4[StoredF32] = MetricPattern4(client, acc) - self.ratio_1m_sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, '1m_sma')) - self.ratio_1w_sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, '1w_sma')) - self.ratio_1y_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, '1y')) - self.ratio_2y_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, '2y')) - self.ratio_4y_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, '4y')) - self.ratio_pct1: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct1')) - self.ratio_pct1_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct1_usd')) - self.ratio_pct2: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct2')) - self.ratio_pct2_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct2_usd')) - self.ratio_pct5: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct5')) - self.ratio_pct5_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct5_usd')) - self.ratio_pct95: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct95')) - self.ratio_pct95_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct95_usd')) - self.ratio_pct98: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct98')) - self.ratio_pct98_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct98_usd')) - self.ratio_pct99: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct99')) - self.ratio_pct99_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct99_usd')) - self.ratio_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, acc) - class PercentilesPattern: """Pattern struct for repeated tree structure.""" @@ -1933,6 +1908,31 @@ class PercentilesPattern: self.pct90: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct90')) self.pct95: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct95')) +class ActivePriceRatioPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.ratio: MetricPattern4[StoredF32] = MetricPattern4(client, acc) + self.ratio_1m_sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, '1m_sma')) + self.ratio_1w_sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, '1w_sma')) + self.ratio_1y_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, '1y')) + self.ratio_2y_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, '2y')) + self.ratio_4y_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, _m(acc, '4y')) + self.ratio_pct1: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct1')) + self.ratio_pct1_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct1_usd')) + self.ratio_pct2: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct2')) + self.ratio_pct2_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct2_usd')) + self.ratio_pct5: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct5')) + self.ratio_pct5_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct5_usd')) + self.ratio_pct95: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct95')) + self.ratio_pct95_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct95_usd')) + self.ratio_pct98: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct98')) + self.ratio_pct98_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct98_usd')) + self.ratio_pct99: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'pct99')) + self.ratio_pct99_usd: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'pct99_usd')) + self.ratio_sd: Ratio1ySdPattern = Ratio1ySdPattern(client, acc) + class RelativePattern5: """Pattern struct for repeated tree structure.""" @@ -2049,23 +2049,6 @@ class BitcoinPattern: self.pct90: MetricPattern6[Bitcoin] = MetricPattern6(client, _m(acc, 'pct90')) self.sum: MetricPattern2[Bitcoin] = MetricPattern2(client, _m(acc, 'sum')) -class ClassAveragePricePattern(Generic[T]): - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_average_price')) - self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_average_price')) - self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_average_price')) - self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_average_price')) - self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_average_price')) - self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_average_price')) - self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_average_price')) - self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_average_price')) - self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_average_price')) - self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_average_price')) - self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_average_price')) - class DollarsPattern(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2083,21 +2066,22 @@ class DollarsPattern(Generic[T]): self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90')) self.sum: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'sum')) -class RelativePattern: +class ClassAveragePricePattern(Generic[T]): """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.neg_unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_market_cap')) - self.net_unrealized_pnl_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_market_cap')) - self.nupl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'nupl')) - self.supply_in_loss_rel_to_circulating_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_circulating_supply')) - self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')) - self.supply_in_profit_rel_to_circulating_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_circulating_supply')) - self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')) - self.supply_rel_to_circulating_supply: MetricPattern4[StoredF64] = MetricPattern4(client, _m(acc, 'supply_rel_to_circulating_supply')) - self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap')) - self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap')) + self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_average_price')) + self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_average_price')) + self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_average_price')) + self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_average_price')) + self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_average_price')) + self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_average_price')) + self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_average_price')) + self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_average_price')) + self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_average_price')) + self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_average_price')) + self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_average_price')) class RelativePattern2: """Pattern struct for repeated tree structure.""" @@ -2115,6 +2099,22 @@ class RelativePattern2: self.unrealized_profit_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap')) self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl')) +class RelativePattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.neg_unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_market_cap')) + self.net_unrealized_pnl_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_market_cap')) + self.nupl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'nupl')) + self.supply_in_loss_rel_to_circulating_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_circulating_supply')) + self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')) + self.supply_in_profit_rel_to_circulating_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_circulating_supply')) + self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')) + self.supply_rel_to_circulating_supply: MetricPattern4[StoredF64] = MetricPattern4(client, _m(acc, 'supply_rel_to_circulating_supply')) + self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap')) + self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap')) + class CountPattern2(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2204,31 +2204,18 @@ class PhaseDailyCentsPattern(Generic[T]): self.pct75: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct75')) self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90')) -class PeriodCagrPattern: +class UnrealizedPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self._10y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('10y', acc)) - self._2y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('2y', acc)) - self._3y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('3y', acc)) - self._4y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('4y', acc)) - self._5y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('5y', acc)) - self._6y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('6y', acc)) - self._8y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('8y', acc)) - -class _0satsPattern2: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.activity: ActivityPattern2 = ActivityPattern2(client, acc) - self.cost_basis: CostBasisPattern = CostBasisPattern(client, acc) - self.outputs: OutputsPattern = OutputsPattern(client, _m(acc, 'utxo_count')) - self.realized: RealizedPattern = RealizedPattern(client, acc) - self.relative: RelativePattern4 = RelativePattern4(client, _m(acc, 'supply_in')) - self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) - self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) + self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss')) + self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl')) + self.supply_in_loss: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_loss')) + self.supply_in_profit: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_profit')) + self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl')) + self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss')) + self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit')) class _100btcPattern: """Pattern struct for repeated tree structure.""" @@ -2243,29 +2230,16 @@ class _100btcPattern: self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) -class UnrealizedPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss')) - self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl')) - self.supply_in_loss: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_loss')) - self.supply_in_profit: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_profit')) - self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl')) - self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss')) - self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit')) - -class _10yTo12yPattern: +class _0satsPattern2: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" self.activity: ActivityPattern2 = ActivityPattern2(client, acc) - self.cost_basis: CostBasisPattern2 = CostBasisPattern2(client, acc) + self.cost_basis: CostBasisPattern = CostBasisPattern(client, acc) self.outputs: OutputsPattern = OutputsPattern(client, _m(acc, 'utxo_count')) - self.realized: RealizedPattern2 = RealizedPattern2(client, acc) - self.relative: RelativePattern2 = RelativePattern2(client, acc) + self.realized: RealizedPattern = RealizedPattern(client, acc) + self.relative: RelativePattern4 = RelativePattern4(client, _m(acc, 'supply_in')) self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) @@ -2282,6 +2256,32 @@ class _10yPattern: self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) +class _10yTo12yPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.activity: ActivityPattern2 = ActivityPattern2(client, acc) + self.cost_basis: CostBasisPattern2 = CostBasisPattern2(client, acc) + self.outputs: OutputsPattern = OutputsPattern(client, _m(acc, 'utxo_count')) + self.realized: RealizedPattern2 = RealizedPattern2(client, acc) + self.relative: RelativePattern2 = RelativePattern2(client, acc) + self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) + self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) + +class PeriodCagrPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self._10y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('10y', acc)) + self._2y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('2y', acc)) + self._3y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('3y', acc)) + self._4y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('4y', acc)) + self._5y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('5y', acc)) + self._6y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('6y', acc)) + self._8y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('8y', acc)) + class ActivityPattern2: """Pattern struct for repeated tree structure.""" @@ -2303,14 +2303,32 @@ class SplitPattern2(Generic[T]): self.low: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'low')) self.open: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'open')) -class ActiveSupplyPattern: +class UnclaimedRewardsPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.bitcoin: MetricPattern1[Bitcoin] = MetricPattern1(client, _m(acc, 'btc')) - self.dollars: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd')) - self.sats: MetricPattern1[Sats] = MetricPattern1(client, acc) + self.bitcoin: BitcoinPattern2[Bitcoin] = BitcoinPattern2(client, _m(acc, 'btc')) + self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd')) + self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc) + +class CoinbasePattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.bitcoin: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc')) + self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd')) + self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc) + +class SegwitAdoptionPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.base: MetricPattern11[StoredF32] = MetricPattern11(client, acc) + self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative')) + self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum')) class _2015Pattern: """Pattern struct for repeated tree structure.""" @@ -2321,6 +2339,15 @@ class _2015Pattern: self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'usd')) self.sats: MetricPattern4[Sats] = MetricPattern4(client, acc) +class ActiveSupplyPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.bitcoin: MetricPattern1[Bitcoin] = MetricPattern1(client, _m(acc, 'btc')) + self.dollars: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd')) + self.sats: MetricPattern1[Sats] = MetricPattern1(client, acc) + class CoinbasePattern2: """Pattern struct for repeated tree structure.""" @@ -2339,32 +2366,13 @@ class CostBasisPattern2: self.min: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'min_cost_basis')) self.percentiles: PercentilesPattern = PercentilesPattern(client, _m(acc, 'cost_basis')) -class SegwitAdoptionPattern: +class CostBasisPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.base: MetricPattern11[StoredF32] = MetricPattern11(client, acc) - self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative')) - self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum')) - -class UnclaimedRewardsPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.bitcoin: BitcoinPattern2[Bitcoin] = BitcoinPattern2(client, _m(acc, 'btc')) - self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd')) - self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc) - -class CoinbasePattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.bitcoin: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc')) - self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd')) - self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc) + self.max: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'max_cost_basis')) + self.min: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'min_cost_basis')) class RelativePattern4: """Pattern struct for repeated tree structure.""" @@ -2382,14 +2390,6 @@ class _1dReturns1mSdPattern: self.sd: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sd')) self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma')) -class CostBasisPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.max: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'max_cost_basis')) - self.min: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'min_cost_basis')) - class SupplyPattern2: """Pattern struct for repeated tree structure.""" @@ -2398,13 +2398,13 @@ class SupplyPattern2: self.halved: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'halved')) self.total: ActiveSupplyPattern = ActiveSupplyPattern(client, acc) -class BlockCountPattern(Generic[T]): +class SatsPattern(Generic[T]): """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative')) - self.sum: MetricPattern1[T] = MetricPattern1(client, acc) + self.ohlc: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'ohlc')) + self.split: SplitPattern2[T] = SplitPattern2(client, acc) class BitcoinPattern2(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2414,13 +2414,13 @@ class BitcoinPattern2(Generic[T]): self.cumulative: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'cumulative')) self.sum: MetricPattern1[T] = MetricPattern1(client, acc) -class SatsPattern(Generic[T]): +class BlockCountPattern(Generic[T]): """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.ohlc: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'ohlc_sats')) - self.split: SplitPattern2[T] = SplitPattern2(client, _m(acc, 'sats')) + self.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative')) + self.sum: MetricPattern1[T] = MetricPattern1(client, acc) class OutputsPattern: """Pattern struct for repeated tree structure.""" @@ -3702,12 +3702,12 @@ class MetricsTree_Price_Oracle: self.price_cents: MetricPattern11[Cents] = MetricPattern11(client, 'oracle_price_cents') self.tx_count: MetricPattern6[StoredU32] = MetricPattern6(client, 'oracle_tx_count') -class MetricsTree_Price_Usd: +class MetricsTree_Price_Sats: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.ohlc: MetricPattern1[OHLCDollars] = MetricPattern1(client, 'price_ohlc') - self.split: SplitPattern2[Dollars] = SplitPattern2(client, 'price') + self.ohlc: MetricPattern1[OHLCSats] = MetricPattern1(client, 'price_ohlc_sats') + self.split: SplitPattern2[Sats] = SplitPattern2(client, 'price_sats') class MetricsTree_Price: """Metrics tree node.""" @@ -3715,8 +3715,8 @@ class MetricsTree_Price: def __init__(self, client: BrkClientBase, base_path: str = ''): self.cents: MetricsTree_Price_Cents = MetricsTree_Price_Cents(client) self.oracle: MetricsTree_Price_Oracle = MetricsTree_Price_Oracle(client) - self.sats: SatsPattern[OHLCSats] = SatsPattern(client, 'price') - self.usd: MetricsTree_Price_Usd = MetricsTree_Price_Usd(client) + self.sats: MetricsTree_Price_Sats = MetricsTree_Price_Sats(client) + self.usd: SatsPattern[OHLCDollars] = SatsPattern(client, 'price') class MetricsTree_Scripts_Count: """Metrics tree node.""" diff --git a/website/index.html b/website/index.html index 9bab8fc55..7397d9ba5 100644 --- a/website/index.html +++ b/website/index.html @@ -1519,9 +1519,10 @@ } + - + diff --git a/website/scripts/chart/index.js b/website/scripts/chart/index.js index 68a307901..6203a7ca3 100644 --- a/website/scripts/chart/index.js +++ b/website/scripts/chart/index.js @@ -8,7 +8,6 @@ import { } from "../modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.mjs"; const createChart = /** @type {CreateChart} */ (_createChart); - import { createChoiceField, createLabeledInput, @@ -502,7 +501,8 @@ export function createChartElement({ if (!hasData()) { setData(data); hasData.set(true); - lastTime = /** @type {number} */ (data.at(-1)?.time) ?? -Infinity; + lastTime = + /** @type {number} */ (data.at(-1)?.time) ?? -Infinity; if (fitContent) { ichart.timeScale().fitContent();