mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 06:01:57 -07:00
global: snapshot
This commit is contained in:
@@ -1,2 +1,15 @@
|
||||
[build]
|
||||
rustflags = ["-C", "target-cpu=native"]
|
||||
|
||||
# pco (pcodec) decompression is significantly faster with these SIMD instructions
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "target-cpu=native", "-C", "target-feature=+bmi1,+bmi2,+avx2"]
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "target-cpu=native", "-C", "target-feature=+bmi1,+bmi2,+avx2"]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-cpu=native", "-C", "target-feature=+bmi1,+bmi2,+avx2"]
|
||||
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
rustflags = ["-C", "target-cpu=native", "-C", "target-feature=+bmi1,+bmi2,+avx2"]
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ _*
|
||||
/*.html
|
||||
/research
|
||||
/filter_*
|
||||
/heatmaps
|
||||
/heatmaps*
|
||||
/oracle*
|
||||
|
||||
# Logs
|
||||
*.log*
|
||||
|
||||
Generated
+18
-24
@@ -54,7 +54,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -483,7 +483,7 @@ dependencies = [
|
||||
"jiff",
|
||||
"minreq",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"vecdb",
|
||||
]
|
||||
@@ -1158,9 +1158,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dtype_dispatch"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3a5ccdfd6c5e7e2fea9c5cf256f2a08216047fab19c621c3da64e9ae4a1462d"
|
||||
checksum = "63cb69518c750a905135325cb009ffb24e1dff48cfc7bee026cd8e7e90d14f26"
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
@@ -2255,9 +2255,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pco"
|
||||
version = "0.4.9"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42382de9fb564e2d10cb4d5ca97cc06d928f0f9667bbef456b57e60827b6548b"
|
||||
checksum = "4acf50a078a796b341d327c18a0d3b2d2ca7dd630f04958558608169e2686e87"
|
||||
dependencies = [
|
||||
"better_io",
|
||||
"dtype_dispatch",
|
||||
@@ -2483,8 +2483,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rawdb"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133cd3a1d92510fe902efcdf70a7f45aa83e1cd47173d8cc013fef82af0b8f8e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
@@ -2492,7 +2490,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"smallvec",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2532,7 +2530,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2845,7 +2843,7 @@ dependencies = [
|
||||
"futures",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3011,11 +3009,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.17",
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3031,9 +3029,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3234,7 +3232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -3366,8 +3364,6 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
|
||||
[[package]]
|
||||
name = "vecdb"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a9b98950cd49f718ec32f3b91282b9d0c033ea15430436ddb0cc286c77aa5bb"
|
||||
dependencies = [
|
||||
"ctrlc",
|
||||
"log",
|
||||
@@ -3378,7 +3374,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.17",
|
||||
"thiserror 2.0.18",
|
||||
"vecdb_derive",
|
||||
"zerocopy",
|
||||
"zstd",
|
||||
@@ -3387,8 +3383,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vecdb_derive"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "043380b6bd5d519b63def192ff310f7a599feaca7f5b12cc86e5d9dd7eabf750"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -3881,9 +3875,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.14"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
|
||||
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
|
||||
+2
-2
@@ -81,8 +81,8 @@ tokio = { version = "1.49.0", features = ["rt-multi-thread"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
|
||||
tower-layer = "0.3"
|
||||
vecdb = { version = "0.5.11", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
# vecdb = { version = "0.5.11", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
|
||||
|
||||
[workspace.metadata.release]
|
||||
shared-version = true
|
||||
|
||||
@@ -18,9 +18,11 @@ Command-line interface for running a Bitcoin Research Kit instance.
|
||||
|
||||
```bash
|
||||
rustup update
|
||||
RUSTFLAGS="-C target-cpu=native" cargo install --locked brk_cli --version "$(cargo search brk_cli | head -1 | awk -F'"' '{print $2}')"
|
||||
RUSTFLAGS="-C target-cpu=native -C target-feature=+bmi1,+bmi2,+avx2" cargo install --locked brk_cli --version "$(cargo search brk_cli | head -1 | awk -F'"' '{print $2}')"
|
||||
```
|
||||
|
||||
The SIMD flags (`bmi1`, `bmi2`, `avx2`) significantly improve pcodec decompression performance.
|
||||
|
||||
Portable build (without native CPU optimizations):
|
||||
|
||||
```bash
|
||||
|
||||
+817
-3111
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ brk_traversable = { workspace = true }
|
||||
brk_types = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
pco = "0.4.9"
|
||||
pco = "1.0.0"
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{StoredF32, StoredI16, StoredU16, Version};
|
||||
use brk_types::{StoredF32, StoredI8, StoredU16, Version};
|
||||
|
||||
use super::{
|
||||
indexes,
|
||||
internal::{ConstantVecs, ReturnF32Tenths, ReturnI16, ReturnU16},
|
||||
internal::{ConstantVecs, ReturnF32Tenths, ReturnI8, ReturnU16},
|
||||
};
|
||||
|
||||
pub const DB_NAME: &str = "constants";
|
||||
@@ -24,10 +24,10 @@ pub struct Vecs {
|
||||
pub constant_80: ConstantVecs<StoredU16>,
|
||||
pub constant_100: ConstantVecs<StoredU16>,
|
||||
pub constant_600: ConstantVecs<StoredU16>,
|
||||
pub constant_minus_1: ConstantVecs<StoredI16>,
|
||||
pub constant_minus_2: ConstantVecs<StoredI16>,
|
||||
pub constant_minus_3: ConstantVecs<StoredI16>,
|
||||
pub constant_minus_4: ConstantVecs<StoredI16>,
|
||||
pub constant_minus_1: ConstantVecs<StoredI8>,
|
||||
pub constant_minus_2: ConstantVecs<StoredI8>,
|
||||
pub constant_minus_3: ConstantVecs<StoredI8>,
|
||||
pub constant_minus_4: ConstantVecs<StoredI8>,
|
||||
}
|
||||
|
||||
impl Vecs {
|
||||
@@ -49,10 +49,10 @@ impl Vecs {
|
||||
constant_80: ConstantVecs::new::<ReturnU16<80>>("constant_80", v, indexes),
|
||||
constant_100: ConstantVecs::new::<ReturnU16<100>>("constant_100", v, indexes),
|
||||
constant_600: ConstantVecs::new::<ReturnU16<600>>("constant_600", v, indexes),
|
||||
constant_minus_1: ConstantVecs::new::<ReturnI16<-1>>("constant_minus_1", v, indexes),
|
||||
constant_minus_2: ConstantVecs::new::<ReturnI16<-2>>("constant_minus_2", v, indexes),
|
||||
constant_minus_3: ConstantVecs::new::<ReturnI16<-3>>("constant_minus_3", v, indexes),
|
||||
constant_minus_4: ConstantVecs::new::<ReturnI16<-4>>("constant_minus_4", v, indexes),
|
||||
constant_minus_1: ConstantVecs::new::<ReturnI8<-1>>("constant_minus_1", v, indexes),
|
||||
constant_minus_2: ConstantVecs::new::<ReturnI8<-2>>("constant_minus_2", v, indexes),
|
||||
constant_minus_3: ConstantVecs::new::<ReturnI8<-3>>("constant_minus_3", v, indexes),
|
||||
constant_minus_4: ConstantVecs::new::<ReturnI8<-4>>("constant_minus_4", v, indexes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
use brk_error::{Error, Result};
|
||||
use brk_types::{CentsCompact, Dollars, Height, Sats, SupplyState};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use pco::standalone::{simple_decompress, simpler_compress};
|
||||
use pco::{standalone::{simple_compress, simple_decompress}, ChunkConfig};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::Bytes;
|
||||
@@ -234,15 +234,14 @@ impl PriceToAmount {
|
||||
#[derive(Clone, Default, Debug, Deref, DerefMut, Serialize, Deserialize)]
|
||||
struct State(BTreeMap<CentsCompact, Sats>);
|
||||
|
||||
const COMPRESSION_LEVEL: usize = 4;
|
||||
|
||||
impl State {
|
||||
fn serialize(&self) -> vecdb::Result<Vec<u8>> {
|
||||
let keys: Vec<i32> = self.keys().map(|k| i32::from(*k)).collect();
|
||||
let values: Vec<u64> = self.values().map(|v| u64::from(*v)).collect();
|
||||
|
||||
let compressed_keys = simpler_compress(&keys, COMPRESSION_LEVEL)?;
|
||||
let compressed_values = simpler_compress(&values, COMPRESSION_LEVEL)?;
|
||||
let config = ChunkConfig::default();
|
||||
let compressed_keys = simple_compress(&keys, &config)?;
|
||||
let compressed_values = simple_compress(&values, &config)?;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
buffer.extend(keys.len().to_bytes());
|
||||
|
||||
@@ -21,7 +21,7 @@ mod ratio32;
|
||||
mod ratio32_neg;
|
||||
mod ratio_f32;
|
||||
mod return_f32_tenths;
|
||||
mod return_i16;
|
||||
mod return_i8;
|
||||
mod return_u16;
|
||||
mod rsi_formula;
|
||||
mod sat_halve;
|
||||
@@ -61,7 +61,7 @@ pub use ratio32::*;
|
||||
pub use ratio32_neg::*;
|
||||
pub use ratio_f32::*;
|
||||
pub use return_f32_tenths::*;
|
||||
pub use return_i16::*;
|
||||
pub use return_i8::*;
|
||||
pub use return_u16::*;
|
||||
pub use rsi_formula::*;
|
||||
pub use sat_halve::*;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
use brk_types::StoredI16;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// Returns a constant i16 value, ignoring the input.
|
||||
pub struct ReturnI16<const V: i16>;
|
||||
|
||||
impl<S, const V: i16> UnaryTransform<S, StoredI16> for ReturnI16<V> {
|
||||
#[inline(always)]
|
||||
fn apply(_: S) -> StoredI16 {
|
||||
StoredI16::new(V)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use brk_types::StoredI8;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// Returns a constant i8 value, ignoring the input.
|
||||
pub struct ReturnI8<const V: i8>;
|
||||
|
||||
impl<S, const V: i8> UnaryTransform<S, StoredI8> for ReturnI8<V> {
|
||||
#[inline(always)]
|
||||
fn apply(_: S) -> StoredI8 {
|
||||
StoredI8::new(V)
|
||||
}
|
||||
}
|
||||
@@ -383,11 +383,9 @@ impl Stores {
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
info!("Resetting stores...");
|
||||
|
||||
// Clear all keyspaces
|
||||
self.iter_any().try_for_each(|store| -> Result<()> {
|
||||
store.keyspace().clear()?;
|
||||
Ok(())
|
||||
})?;
|
||||
// Clear all stores (both in-memory buffers and on-disk keyspaces)
|
||||
self.par_iter_any_mut()
|
||||
.try_for_each(|store| store.reset())?;
|
||||
|
||||
// Persist the cleared state
|
||||
self.db.persist(PersistMode::SyncAll)?;
|
||||
|
||||
@@ -19,3 +19,11 @@ memmap2 = "0.9"
|
||||
plotters = "0.3"
|
||||
tracing = { workspace = true }
|
||||
vecdb = { workspace = true }
|
||||
|
||||
[[example]]
|
||||
name = "heatmap"
|
||||
path = "examples/heatmap.rs"
|
||||
|
||||
[[example]]
|
||||
name = "oracle"
|
||||
path = "examples/oracle.rs"
|
||||
|
||||
@@ -1,4 +1,26 @@
|
||||
//! Experimental playground for brk development.
|
||||
//! Playground library for Bitcoin on-chain analysis
|
||||
//!
|
||||
//! This crate is for experiments and prototypes.
|
||||
//! Most contents are git-ignored.
|
||||
//! This crate provides tools for:
|
||||
//! - Phase histogram analysis of UTXO patterns
|
||||
//! - Filter-based output selection for price signal extraction
|
||||
//! - On-chain OHLC price oracle derivation
|
||||
|
||||
pub mod anchors;
|
||||
pub mod conditions;
|
||||
pub mod constants;
|
||||
pub mod filters;
|
||||
pub mod histogram;
|
||||
pub mod oracle;
|
||||
pub mod render;
|
||||
pub mod signal;
|
||||
|
||||
pub use anchors::{get_anchor_ohlc, get_anchor_range, Ohlc};
|
||||
pub use conditions::{out_bits, tx_bits, MappedOutputConditions};
|
||||
pub use constants::{HeatmapFilter, NUM_BINS, ROUND_USD_AMOUNTS};
|
||||
pub use filters::FILTERS;
|
||||
pub use oracle::{
|
||||
derive_daily_ohlc, derive_daily_ohlc_with_confidence, derive_height_price,
|
||||
derive_ohlc_from_height_prices, derive_price_from_histogram, OracleConfig, OracleResult,
|
||||
};
|
||||
pub use signal::{compute_expected_bins_per_day, usd_to_bin};
|
||||
pub use histogram::load_or_compute_output_conditions;
|
||||
|
||||
@@ -160,6 +160,7 @@ impl Query {
|
||||
total,
|
||||
start,
|
||||
end,
|
||||
height: *self.height(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -173,6 +174,7 @@ impl Query {
|
||||
total,
|
||||
start,
|
||||
end,
|
||||
..
|
||||
} = resolved;
|
||||
|
||||
let output = match format {
|
||||
|
||||
@@ -13,6 +13,7 @@ impl Query {
|
||||
total,
|
||||
start,
|
||||
end,
|
||||
..
|
||||
} = resolved;
|
||||
|
||||
if vecs.is_empty() {
|
||||
|
||||
@@ -10,11 +10,12 @@ pub struct ResolvedQuery {
|
||||
pub(crate) total: usize,
|
||||
pub(crate) start: usize,
|
||||
pub(crate) end: usize,
|
||||
pub(crate) height: u32,
|
||||
}
|
||||
|
||||
impl ResolvedQuery {
|
||||
pub fn etag(&self) -> Etag {
|
||||
Etag::from_metric(self.version, self.total, self.start, self.end)
|
||||
Etag::from_metric(self.version, self.total, self.start, self.end, self.height)
|
||||
}
|
||||
|
||||
pub fn format(&self) -> Format {
|
||||
|
||||
@@ -12,4 +12,5 @@ pub trait AnyStore: Send + Sync {
|
||||
fn export_meta_if_needed(&mut self, height: Height) -> Result<()>;
|
||||
fn keyspace(&self) -> &Keyspace;
|
||||
fn commit(&mut self, height: Height) -> Result<()>;
|
||||
fn reset(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -349,4 +349,15 @@ where
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
self.meta.reset()?;
|
||||
self.puts.clear();
|
||||
self.dels.clear();
|
||||
for cache in &mut self.caches {
|
||||
cache.clear();
|
||||
}
|
||||
self.keyspace.clear()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,14 @@ impl StoreMeta {
|
||||
pub fn has(&self, height: Height) -> bool {
|
||||
!self.needs(height)
|
||||
}
|
||||
pub fn reset(&mut self) -> io::Result<()> {
|
||||
self.height = None;
|
||||
let path = self.path_height();
|
||||
if path.exists() {
|
||||
fs::remove_file(&path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn path_height(&self) -> PathBuf {
|
||||
Self::path_height_(&self.pathbuf)
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ impl FromCoarserIndex<MonthIndex> for DateIndex {
|
||||
|
||||
impl FromCoarserIndex<QuarterIndex> for DateIndex {
|
||||
fn min_from(coarser: QuarterIndex) -> usize {
|
||||
let coarser = u16::from(coarser);
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
@@ -163,7 +163,7 @@ impl FromCoarserIndex<QuarterIndex> for DateIndex {
|
||||
fn max_from_(coarser: QuarterIndex) -> usize {
|
||||
let d = Date::new(2009, 3, 31)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(3 * u16::from(coarser)))
|
||||
.checked_add(Span::new().months(3 * u8::from(coarser)))
|
||||
.unwrap();
|
||||
DateIndex::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
@@ -171,7 +171,7 @@ impl FromCoarserIndex<QuarterIndex> for DateIndex {
|
||||
|
||||
impl FromCoarserIndex<SemesterIndex> for DateIndex {
|
||||
fn min_from(coarser: SemesterIndex) -> usize {
|
||||
let coarser = u16::from(coarser);
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
@@ -186,7 +186,7 @@ impl FromCoarserIndex<SemesterIndex> for DateIndex {
|
||||
fn max_from_(coarser: SemesterIndex) -> usize {
|
||||
let d = Date::new(2009, 5, 31)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(1 + 6 * u16::from(coarser)))
|
||||
.checked_add(Span::new().months(1 + 6 * u8::from(coarser)))
|
||||
.unwrap();
|
||||
DateIndex::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
@@ -194,18 +194,18 @@ impl FromCoarserIndex<SemesterIndex> for DateIndex {
|
||||
|
||||
impl FromCoarserIndex<YearIndex> for DateIndex {
|
||||
fn min_from(coarser: YearIndex) -> usize {
|
||||
let coarser = u16::from(coarser);
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
Self::try_from(Date::new(2009 + coarser, 1, 1))
|
||||
Self::try_from(Date::new(2009 + coarser as u16, 1, 1))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn max_from_(coarser: YearIndex) -> usize {
|
||||
Self::try_from(Date::new(2009 + u16::from(coarser), 12, 31))
|
||||
Self::try_from(Date::new(2009 + u8::from(coarser) as u16, 12, 31))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
@@ -213,19 +213,19 @@ impl FromCoarserIndex<YearIndex> for DateIndex {
|
||||
|
||||
impl FromCoarserIndex<DecadeIndex> for DateIndex {
|
||||
fn min_from(coarser: DecadeIndex) -> usize {
|
||||
let coarser = u16::from(coarser);
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
Self::try_from(Date::new(2000 + 10 * coarser, 1, 1))
|
||||
Self::try_from(Date::new(2000 + 10 * coarser as u16, 1, 1))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn max_from_(coarser: DecadeIndex) -> usize {
|
||||
let coarser = u16::from(coarser);
|
||||
Self::try_from(Date::new(2009 + (10 * coarser), 12, 31))
|
||||
let coarser = u8::from(coarser);
|
||||
Self::try_from(Date::new(2009 + 10 * coarser as u16, 12, 31))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -23,16 +23,16 @@ use super::{Date, DateIndex, YearIndex};
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct DecadeIndex(u16);
|
||||
pub struct DecadeIndex(u8);
|
||||
|
||||
impl From<u16> for DecadeIndex {
|
||||
impl From<u8> for DecadeIndex {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecadeIndex> for u16 {
|
||||
impl From<DecadeIndex> for u8 {
|
||||
#[inline]
|
||||
fn from(value: DecadeIndex) -> Self {
|
||||
value.0
|
||||
@@ -42,7 +42,7 @@ impl From<DecadeIndex> for u16 {
|
||||
impl From<usize> for DecadeIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as u16)
|
||||
Self(value as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Add<usize> for DecadeIndex {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self::from(self.0 + rhs as u16)
|
||||
Self::from(self.0 + rhs as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ impl From<Date> for DecadeIndex {
|
||||
if year < 2000 {
|
||||
panic!("unsupported")
|
||||
}
|
||||
Self((year - 2000) / 10)
|
||||
Self(((year - 2000) / 10) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ impl From<YearIndex> for DecadeIndex {
|
||||
if v == 0 {
|
||||
Self(0)
|
||||
} else {
|
||||
Self((((v - 1) / 10) + 1) as u16)
|
||||
Self((((v - 1) / 10) + 1) as u8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,16 +44,16 @@ impl Etag {
|
||||
///
|
||||
/// Format varies based on whether the slice touches the end:
|
||||
/// - Slice ends before total: `{version:x}-{start}-{end}` (len irrelevant, data won't change if metric grows)
|
||||
/// - Slice reaches the end: `{version:x}-{start}-{total}` (len matters, new data would change results)
|
||||
/// - Slice reaches the end: `{version:x}-{start}-{total}-{height}` (includes height since last value may be recomputed each block)
|
||||
///
|
||||
/// `version` is the metric version for single queries, or the sum of versions for bulk queries.
|
||||
pub fn from_metric(version: u64, total: usize, start: usize, end: usize) -> Self {
|
||||
pub fn from_metric(version: u64, total: usize, start: usize, end: usize, height: u32) -> Self {
|
||||
if end < total {
|
||||
// Fixed window not at the end - len doesn't matter
|
||||
Self(format!("{version:x}-{start}-{end}"))
|
||||
} else {
|
||||
// Fetching up to current end - len matters
|
||||
Self(format!("{version:x}-{start}-{total}"))
|
||||
// Fetching up to current end - include height since last value may change each block
|
||||
Self(format!("{version:x}-{start}-{total}-{height}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,17 +25,17 @@ pub const BLOCKS_PER_HALVING: u32 = 210_000;
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct HalvingEpoch(u16);
|
||||
pub struct HalvingEpoch(u8);
|
||||
|
||||
impl HalvingEpoch {
|
||||
pub const fn new(value: u16) -> Self {
|
||||
pub const fn new(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for HalvingEpoch {
|
||||
impl From<u8> for HalvingEpoch {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ impl From<u16> for HalvingEpoch {
|
||||
impl From<usize> for HalvingEpoch {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as u16)
|
||||
Self(value as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +72,14 @@ impl Add<usize> for HalvingEpoch {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self::from(self.0 + rhs as u16)
|
||||
Self::from(self.0 + rhs as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Height> for HalvingEpoch {
|
||||
#[inline]
|
||||
fn from(value: Height) -> Self {
|
||||
Self((u32::from(value) / BLOCKS_PER_HALVING) as u16)
|
||||
Self((u32::from(value) / BLOCKS_PER_HALVING) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ mod stored_bool;
|
||||
mod stored_f32;
|
||||
mod stored_f64;
|
||||
mod stored_i16;
|
||||
mod stored_i8;
|
||||
mod stored_string;
|
||||
mod stored_u16;
|
||||
mod stored_u32;
|
||||
@@ -302,6 +303,7 @@ pub use stored_bool::*;
|
||||
pub use stored_f32::*;
|
||||
pub use stored_f64::*;
|
||||
pub use stored_i16::*;
|
||||
pub use stored_i8::*;
|
||||
pub use stored_string::*;
|
||||
pub use stored_u8::*;
|
||||
pub use stored_u16::*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Etag, Output, OutputLegacy};
|
||||
use crate::{Output, OutputLegacy};
|
||||
|
||||
/// Metric output with metadata for caching.
|
||||
#[derive(Debug)]
|
||||
@@ -10,12 +10,6 @@ pub struct MetricOutput {
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl MetricOutput {
|
||||
pub fn etag(&self) -> Etag {
|
||||
Etag::from_metric(self.version, self.total, self.start, self.end)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deprecated: Legacy metric output with metadata for caching.
|
||||
#[derive(Debug)]
|
||||
pub struct MetricOutputLegacy {
|
||||
@@ -25,9 +19,3 @@ pub struct MetricOutputLegacy {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl MetricOutputLegacy {
|
||||
pub fn etag(&self) -> Etag {
|
||||
Etag::from_metric(self.version, self.total, self.start, self.end)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ impl From<DateIndex> for MonthIndex {
|
||||
impl From<Date> for MonthIndex {
|
||||
#[inline]
|
||||
fn from(value: Date) -> Self {
|
||||
Self(u16::from(YearIndex::from(value)) * 12 + value.month() as u16 - 1)
|
||||
Self(u8::from(YearIndex::from(value)) as u16 * 12 + value.month() as u16 - 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::AddressBytes;
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[repr(u16)]
|
||||
#[repr(u8)]
|
||||
/// Type (P2PKH, P2WPKH, P2SH, P2TR, etc.)
|
||||
pub enum OutputType {
|
||||
P2PK65,
|
||||
@@ -41,8 +41,8 @@ pub enum OutputType {
|
||||
}
|
||||
|
||||
impl OutputType {
|
||||
fn is_valid(value: u16) -> bool {
|
||||
value <= Self::Unknown as u16
|
||||
fn is_valid(value: u8) -> bool {
|
||||
value <= Self::Unknown as u8
|
||||
}
|
||||
|
||||
pub fn is_spendable(&self) -> bool {
|
||||
@@ -205,7 +205,7 @@ impl Bytes for OutputType {
|
||||
|
||||
#[inline]
|
||||
fn to_bytes(&self) -> Self::Array {
|
||||
(*self as u16).to_le_bytes()
|
||||
[*self as u8]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -216,7 +216,7 @@ impl Bytes for OutputType {
|
||||
received: bytes.len(),
|
||||
});
|
||||
};
|
||||
let value = u16::from_le_bytes([bytes[0], bytes[1]]);
|
||||
let value = bytes[0];
|
||||
if !Self::is_valid(value) {
|
||||
return Err(vecdb::Error::InvalidArgument("invalid OutputType"));
|
||||
}
|
||||
@@ -227,7 +227,7 @@ impl Bytes for OutputType {
|
||||
}
|
||||
|
||||
impl Pco for OutputType {
|
||||
type NumberType = u16;
|
||||
type NumberType = u8;
|
||||
}
|
||||
|
||||
impl TransparentPco<u16> for OutputType {}
|
||||
impl TransparentPco<u8> for OutputType {}
|
||||
|
||||
@@ -23,11 +23,11 @@ use super::MonthIndex;
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct QuarterIndex(u16);
|
||||
pub struct QuarterIndex(u8);
|
||||
|
||||
impl From<u16> for QuarterIndex {
|
||||
impl From<u8> for QuarterIndex {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,11 @@ impl From<u16> for QuarterIndex {
|
||||
impl From<usize> for QuarterIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as u16)
|
||||
Self(value as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QuarterIndex> for u16 {
|
||||
impl From<QuarterIndex> for u8 {
|
||||
#[inline]
|
||||
fn from(value: QuarterIndex) -> Self {
|
||||
value.0
|
||||
@@ -57,7 +57,7 @@ impl Add<usize> for QuarterIndex {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self::from(self.0 + rhs as u16)
|
||||
Self::from(self.0 + rhs as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl Div<usize> for QuarterIndex {
|
||||
impl From<MonthIndex> for QuarterIndex {
|
||||
#[inline]
|
||||
fn from(value: MonthIndex) -> Self {
|
||||
Self((usize::from(value) / 3) as u16)
|
||||
Self((usize::from(value) / 3) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ use super::MonthIndex;
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct SemesterIndex(u16);
|
||||
pub struct SemesterIndex(u8);
|
||||
|
||||
impl From<u16> for SemesterIndex {
|
||||
impl From<u8> for SemesterIndex {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,11 @@ impl From<u16> for SemesterIndex {
|
||||
impl From<usize> for SemesterIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as u16)
|
||||
Self(value as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemesterIndex> for u16 {
|
||||
impl From<SemesterIndex> for u8 {
|
||||
#[inline]
|
||||
fn from(value: SemesterIndex) -> Self {
|
||||
value.0
|
||||
@@ -57,7 +57,7 @@ impl Add<usize> for SemesterIndex {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self::from(self.0 + rhs as u16)
|
||||
Self::from(self.0 + rhs as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl Div<usize> for SemesterIndex {
|
||||
impl From<MonthIndex> for SemesterIndex {
|
||||
#[inline]
|
||||
fn from(value: MonthIndex) -> Self {
|
||||
Self((usize::from(value) / 6) as u16)
|
||||
Self((usize::from(value) / 6) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{Formattable, Pco, PrintableIndex};
|
||||
|
||||
/// Fixed-size boolean value optimized for on-disk storage (stored as u16)
|
||||
/// Fixed-size boolean value optimized for on-disk storage (stored as u8)
|
||||
#[derive(
|
||||
Debug,
|
||||
Deref,
|
||||
@@ -19,7 +19,7 @@ use vecdb::{Formattable, Pco, PrintableIndex};
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct StoredBool(u16);
|
||||
pub struct StoredBool(u8);
|
||||
|
||||
impl StoredBool {
|
||||
pub const FALSE: Self = Self(0);
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::ops::{Add, AddAssign, Div};
|
||||
|
||||
use derive_more::Deref;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Deref,
|
||||
Clone,
|
||||
Default,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct StoredI8(i8);
|
||||
|
||||
impl StoredI8 {
|
||||
pub const ZERO: Self = Self(0);
|
||||
|
||||
pub fn new(v: i8) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for StoredI8 {
|
||||
#[inline]
|
||||
fn from(value: i8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for StoredI8 {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
if value > i8::MAX as usize {
|
||||
panic!("usize too big (value = {value})")
|
||||
}
|
||||
Self(value as i8)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<StoredI8> for StoredI8 {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<usize> for StoredI8 {
|
||||
type Output = Self;
|
||||
fn div(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 / rhs as i8)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for StoredI8 {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for StoredI8 {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
*self = *self + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for StoredI8 {
|
||||
#[inline]
|
||||
fn from(value: f64) -> Self {
|
||||
if value < i8::MIN as f64 || value > i8::MAX as f64 {
|
||||
panic!()
|
||||
}
|
||||
Self(value as i8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredI8> for f64 {
|
||||
#[inline]
|
||||
fn from(value: StoredI8) -> Self {
|
||||
value.0 as f64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredI8> for usize {
|
||||
#[inline]
|
||||
fn from(value: StoredI8) -> Self {
|
||||
value.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl PrintableIndex for StoredI8 {
|
||||
fn to_string() -> &'static str {
|
||||
"i8"
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["i8"]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for StoredI8 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut buf = itoa::Buffer::new();
|
||||
let str = buf.format(self.0);
|
||||
f.write_str(str)
|
||||
}
|
||||
}
|
||||
|
||||
impl Formattable for StoredI8 {
|
||||
#[inline(always)]
|
||||
fn may_need_escaping() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{Formattable, Pco};
|
||||
|
||||
use super::StoredU16;
|
||||
use super::StoredU8;
|
||||
|
||||
/// Transaction version number
|
||||
#[derive(
|
||||
@@ -20,13 +20,13 @@ use super::StoredU16;
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct TxVersion(u16);
|
||||
pub struct TxVersion(u8);
|
||||
|
||||
impl TxVersion {
|
||||
pub const ONE: Self = Self(1);
|
||||
pub const TWO: Self = Self(2);
|
||||
pub const THREE: Self = Self(3);
|
||||
pub const NON_STANDARD: Self = Self(u16::MAX);
|
||||
pub const NON_STANDARD: Self = Self(u8::MAX);
|
||||
}
|
||||
|
||||
impl From<bitcoin::transaction::Version> for TxVersion {
|
||||
@@ -48,7 +48,7 @@ impl From<TxVersion> for bitcoin::transaction::Version {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TxVersion> for StoredU16 {
|
||||
impl From<TxVersion> for StoredU8 {
|
||||
#[inline]
|
||||
fn from(value: TxVersion) -> Self {
|
||||
Self::from(value.0)
|
||||
|
||||
@@ -23,11 +23,11 @@ use super::{Date, DateIndex, MonthIndex};
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct YearIndex(u16);
|
||||
pub struct YearIndex(u8);
|
||||
|
||||
impl From<u16> for YearIndex {
|
||||
impl From<u8> for YearIndex {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
fn from(value: u8) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ impl From<u16> for YearIndex {
|
||||
impl From<usize> for YearIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as u16)
|
||||
Self(value as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Add<usize> for YearIndex {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self::from(self.0 + rhs as u16)
|
||||
Self::from(self.0 + rhs as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +92,11 @@ impl From<DateIndex> for YearIndex {
|
||||
impl From<Date> for YearIndex {
|
||||
#[inline]
|
||||
fn from(value: Date) -> Self {
|
||||
Self(value.year() - 2009)
|
||||
Self((value.year() - 2009) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<YearIndex> for u16 {
|
||||
impl From<YearIndex> for u8 {
|
||||
#[inline]
|
||||
fn from(value: YearIndex) -> Self {
|
||||
value.0
|
||||
@@ -112,7 +112,7 @@ impl CheckedSub for YearIndex {
|
||||
impl From<MonthIndex> for YearIndex {
|
||||
#[inline]
|
||||
fn from(value: MonthIndex) -> Self {
|
||||
Self((usize::from(value) / 12) as u16)
|
||||
Self((usize::from(value) / 12) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+189
-191
@@ -606,7 +606,7 @@
|
||||
*/
|
||||
/** @typedef {number} SemesterIndex */
|
||||
/**
|
||||
* Fixed-size boolean value optimized for on-disk storage (stored as u16)
|
||||
* Fixed-size boolean value optimized for on-disk storage (stored as u8)
|
||||
*
|
||||
* @typedef {number} StoredBool
|
||||
*/
|
||||
@@ -620,7 +620,7 @@
|
||||
*
|
||||
* @typedef {number} StoredF64
|
||||
*/
|
||||
/** @typedef {number} StoredI16 */
|
||||
/** @typedef {number} StoredI8 */
|
||||
/** @typedef {number} StoredU16 */
|
||||
/**
|
||||
* Fixed-size 32-bit unsigned integer optimized for on-disk storage
|
||||
@@ -839,11 +839,9 @@ class BrkError extends Error {
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} MetricData
|
||||
* @property {number} version - Data version number
|
||||
* @property {number} total - Total number of data points
|
||||
* @property {number} start - Start index (inclusive)
|
||||
* @property {number} end - End index (exclusive)
|
||||
* @property {string} stamp - Last update timestamp (ISO 8601)
|
||||
* @property {T[]} data - The metric data
|
||||
*/
|
||||
/** @typedef {MetricData<any>} AnyMetricData */
|
||||
@@ -2032,17 +2030,17 @@ function createBitcoinPattern(client, acc) {
|
||||
*/
|
||||
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')),
|
||||
_2015: createMetricPattern4(client, _m(acc, '2015_returns')),
|
||||
_2016: createMetricPattern4(client, _m(acc, '2016_returns')),
|
||||
_2017: createMetricPattern4(client, _m(acc, '2017_returns')),
|
||||
_2018: createMetricPattern4(client, _m(acc, '2018_returns')),
|
||||
_2019: createMetricPattern4(client, _m(acc, '2019_returns')),
|
||||
_2020: createMetricPattern4(client, _m(acc, '2020_returns')),
|
||||
_2021: createMetricPattern4(client, _m(acc, '2021_returns')),
|
||||
_2022: createMetricPattern4(client, _m(acc, '2022_returns')),
|
||||
_2023: createMetricPattern4(client, _m(acc, '2023_returns')),
|
||||
_2024: createMetricPattern4(client, _m(acc, '2024_returns')),
|
||||
_2025: createMetricPattern4(client, _m(acc, '2025_returns')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2225,41 +2223,6 @@ function createAddrCountPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} FeeRatePattern
|
||||
* @property {MetricPattern1<T>} average
|
||||
* @property {MetricPattern1<T>} max
|
||||
* @property {MetricPattern11<T>} median
|
||||
* @property {MetricPattern1<T>} min
|
||||
* @property {MetricPattern11<T>} pct10
|
||||
* @property {MetricPattern11<T>} pct25
|
||||
* @property {MetricPattern11<T>} pct75
|
||||
* @property {MetricPattern11<T>} pct90
|
||||
* @property {MetricPattern27<T>} txindex
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a FeeRatePattern pattern node
|
||||
* @template T
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {FeeRatePattern<T>}
|
||||
*/
|
||||
function createFeeRatePattern(client, acc) {
|
||||
return {
|
||||
average: createMetricPattern1(client, _m(acc, 'average')),
|
||||
max: createMetricPattern1(client, _m(acc, 'max')),
|
||||
median: createMetricPattern11(client, _m(acc, 'median')),
|
||||
min: createMetricPattern1(client, _m(acc, 'min')),
|
||||
pct10: createMetricPattern11(client, _m(acc, 'pct10')),
|
||||
pct25: createMetricPattern11(client, _m(acc, 'pct25')),
|
||||
pct75: createMetricPattern11(client, _m(acc, 'pct75')),
|
||||
pct90: createMetricPattern11(client, _m(acc, 'pct90')),
|
||||
txindex: createMetricPattern27(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} FullnessPattern
|
||||
@@ -2295,6 +2258,41 @@ function createFullnessPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} FeeRatePattern
|
||||
* @property {MetricPattern1<T>} average
|
||||
* @property {MetricPattern1<T>} max
|
||||
* @property {MetricPattern11<T>} median
|
||||
* @property {MetricPattern1<T>} min
|
||||
* @property {MetricPattern11<T>} pct10
|
||||
* @property {MetricPattern11<T>} pct25
|
||||
* @property {MetricPattern11<T>} pct75
|
||||
* @property {MetricPattern11<T>} pct90
|
||||
* @property {MetricPattern27<T>} txindex
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a FeeRatePattern pattern node
|
||||
* @template T
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {FeeRatePattern<T>}
|
||||
*/
|
||||
function createFeeRatePattern(client, acc) {
|
||||
return {
|
||||
average: createMetricPattern1(client, _m(acc, 'average')),
|
||||
max: createMetricPattern1(client, _m(acc, 'max')),
|
||||
median: createMetricPattern11(client, _m(acc, 'median')),
|
||||
min: createMetricPattern1(client, _m(acc, 'min')),
|
||||
pct10: createMetricPattern11(client, _m(acc, 'pct10')),
|
||||
pct25: createMetricPattern11(client, _m(acc, 'pct25')),
|
||||
pct75: createMetricPattern11(client, _m(acc, 'pct75')),
|
||||
pct90: createMetricPattern11(client, _m(acc, 'pct90')),
|
||||
txindex: createMetricPattern27(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} _0satsPattern
|
||||
* @property {ActivityPattern2} activity
|
||||
@@ -2359,35 +2357,6 @@ function createPhaseDailyCentsPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} PeriodCagrPattern
|
||||
* @property {MetricPattern4<StoredF32>} _10y
|
||||
* @property {MetricPattern4<StoredF32>} _2y
|
||||
* @property {MetricPattern4<StoredF32>} _3y
|
||||
* @property {MetricPattern4<StoredF32>} _4y
|
||||
* @property {MetricPattern4<StoredF32>} _5y
|
||||
* @property {MetricPattern4<StoredF32>} _6y
|
||||
* @property {MetricPattern4<StoredF32>} _8y
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a PeriodCagrPattern pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {PeriodCagrPattern}
|
||||
*/
|
||||
function createPeriodCagrPattern(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} _10yTo12yPattern
|
||||
* @property {ActivityPattern2} activity
|
||||
@@ -2533,6 +2502,35 @@ function createUnrealizedPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} PeriodCagrPattern
|
||||
* @property {MetricPattern4<StoredF32>} _10y
|
||||
* @property {MetricPattern4<StoredF32>} _2y
|
||||
* @property {MetricPattern4<StoredF32>} _3y
|
||||
* @property {MetricPattern4<StoredF32>} _4y
|
||||
* @property {MetricPattern4<StoredF32>} _5y
|
||||
* @property {MetricPattern4<StoredF32>} _6y
|
||||
* @property {MetricPattern4<StoredF32>} _8y
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a PeriodCagrPattern pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {PeriodCagrPattern}
|
||||
*/
|
||||
function createPeriodCagrPattern(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} ActivityPattern2
|
||||
* @property {BlockCountPattern<StoredF64>} coinblocksDestroyed
|
||||
@@ -2584,23 +2582,23 @@ function createSplitPattern2(client, acc) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} CoinbasePattern
|
||||
* @property {BitcoinPattern} bitcoin
|
||||
* @property {DollarsPattern<Dollars>} dollars
|
||||
* @property {DollarsPattern<Sats>} sats
|
||||
* @typedef {Object} CostBasisPattern2
|
||||
* @property {MetricPattern1<Dollars>} max
|
||||
* @property {MetricPattern1<Dollars>} min
|
||||
* @property {PercentilesPattern} percentiles
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a CoinbasePattern pattern node
|
||||
* Create a CostBasisPattern2 pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {CoinbasePattern}
|
||||
* @returns {CostBasisPattern2}
|
||||
*/
|
||||
function createCoinbasePattern(client, acc) {
|
||||
function createCostBasisPattern2(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')),
|
||||
percentiles: createPercentilesPattern(client, _m(acc, 'cost_basis')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2625,6 +2623,27 @@ function createCoinbasePattern2(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ActiveSupplyPattern
|
||||
* @property {MetricPattern1<Bitcoin>} bitcoin
|
||||
* @property {MetricPattern1<Dollars>} dollars
|
||||
* @property {MetricPattern1<Sats>} 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} SegwitAdoptionPattern
|
||||
* @property {MetricPattern11<StoredF32>} base
|
||||
@@ -2646,27 +2665,6 @@ function createSegwitAdoptionPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} CostBasisPattern2
|
||||
* @property {MetricPattern1<Dollars>} max
|
||||
* @property {MetricPattern1<Dollars>} min
|
||||
* @property {PercentilesPattern} percentiles
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a CostBasisPattern2 pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {CostBasisPattern2}
|
||||
*/
|
||||
function createCostBasisPattern2(client, acc) {
|
||||
return {
|
||||
max: createMetricPattern1(client, _m(acc, 'max_cost_basis')),
|
||||
min: createMetricPattern1(client, _m(acc, 'min_cost_basis')),
|
||||
percentiles: createPercentilesPattern(client, _m(acc, 'cost_basis')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} UnclaimedRewardsPattern
|
||||
* @property {BitcoinPattern2<Bitcoin>} bitcoin
|
||||
@@ -2710,23 +2708,23 @@ function create_2015Pattern(client, acc) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ActiveSupplyPattern
|
||||
* @property {MetricPattern1<Bitcoin>} bitcoin
|
||||
* @property {MetricPattern1<Dollars>} dollars
|
||||
* @property {MetricPattern1<Sats>} sats
|
||||
* @typedef {Object} CoinbasePattern
|
||||
* @property {BitcoinPattern} bitcoin
|
||||
* @property {DollarsPattern<Dollars>} dollars
|
||||
* @property {DollarsPattern<Sats>} sats
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a ActiveSupplyPattern pattern node
|
||||
* Create a CoinbasePattern pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {ActiveSupplyPattern}
|
||||
* @returns {CoinbasePattern}
|
||||
*/
|
||||
function createActiveSupplyPattern(client, acc) {
|
||||
function createCoinbasePattern(client, acc) {
|
||||
return {
|
||||
bitcoin: createMetricPattern1(client, _m(acc, 'btc')),
|
||||
dollars: createMetricPattern1(client, _m(acc, 'usd')),
|
||||
sats: createMetricPattern1(client, acc),
|
||||
bitcoin: createBitcoinPattern(client, _m(acc, 'btc')),
|
||||
dollars: createDollarsPattern(client, _m(acc, 'usd')),
|
||||
sats: createDollarsPattern(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2749,25 +2747,6 @@ function create_1dReturns1mSdPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SupplyPattern2
|
||||
* @property {ActiveSupplyPattern} halved
|
||||
* @property {ActiveSupplyPattern} total
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a SupplyPattern2 pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {SupplyPattern2}
|
||||
*/
|
||||
function createSupplyPattern2(client, acc) {
|
||||
return {
|
||||
halved: createActiveSupplyPattern(client, _m(acc, 'halved')),
|
||||
total: createActiveSupplyPattern(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} CostBasisPattern
|
||||
* @property {MetricPattern1<Dollars>} max
|
||||
@@ -2806,6 +2785,25 @@ function createRelativePattern4(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SupplyPattern2
|
||||
* @property {ActiveSupplyPattern} halved
|
||||
* @property {ActiveSupplyPattern} total
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a SupplyPattern2 pattern node
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {SupplyPattern2}
|
||||
*/
|
||||
function createSupplyPattern2(client, acc) {
|
||||
return {
|
||||
halved: createActiveSupplyPattern(client, _m(acc, 'halved')),
|
||||
total: createActiveSupplyPattern(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} BitcoinPattern2
|
||||
@@ -2827,27 +2825,6 @@ function createBitcoinPattern2(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} SatsPattern
|
||||
* @property {MetricPattern1<T>} ohlc
|
||||
* @property {SplitPattern2<T>} split
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a SatsPattern pattern node
|
||||
* @template T
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {SatsPattern<T>}
|
||||
*/
|
||||
function createSatsPattern(client, acc) {
|
||||
return {
|
||||
ohlc: createMetricPattern1(client, _m(acc, 'ohlc_sats')),
|
||||
split: createSplitPattern2(client, _m(acc, 'sats')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} BlockCountPattern
|
||||
@@ -2869,6 +2846,27 @@ function createBlockCountPattern(client, acc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} SatsPattern
|
||||
* @property {MetricPattern1<T>} ohlc
|
||||
* @property {SplitPattern2<T>} split
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a SatsPattern pattern node
|
||||
* @template T
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {SatsPattern<T>}
|
||||
*/
|
||||
function createSatsPattern(client, acc) {
|
||||
return {
|
||||
ohlc: createMetricPattern1(client, _m(acc, 'ohlc_sats')),
|
||||
split: createSplitPattern2(client, _m(acc, 'sats')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} RealizedPriceExtraPattern
|
||||
* @property {MetricPattern4<StoredF32>} ratio
|
||||
@@ -3126,10 +3124,10 @@ function createOutputsPattern(client, acc) {
|
||||
* @property {MetricPattern1<StoredF32>} constant618
|
||||
* @property {MetricPattern1<StoredU16>} constant70
|
||||
* @property {MetricPattern1<StoredU16>} constant80
|
||||
* @property {MetricPattern1<StoredI16>} constantMinus1
|
||||
* @property {MetricPattern1<StoredI16>} constantMinus2
|
||||
* @property {MetricPattern1<StoredI16>} constantMinus3
|
||||
* @property {MetricPattern1<StoredI16>} constantMinus4
|
||||
* @property {MetricPattern1<StoredI8>} constantMinus1
|
||||
* @property {MetricPattern1<StoredI8>} constantMinus2
|
||||
* @property {MetricPattern1<StoredI8>} constantMinus3
|
||||
* @property {MetricPattern1<StoredI8>} constantMinus4
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -3698,8 +3696,8 @@ function createOutputsPattern(client, acc) {
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Market_Dca
|
||||
* @property {ClassAveragePricePattern<Dollars>} classAveragePrice
|
||||
* @property {MetricsTree_Market_Dca_ClassReturns} classReturns
|
||||
* @property {MetricsTree_Market_Dca_ClassAveragePrice} classAveragePrice
|
||||
* @property {ClassAveragePricePattern<StoredF32>} classReturns
|
||||
* @property {MetricsTree_Market_Dca_ClassStack} classStack
|
||||
* @property {PeriodAveragePricePattern<Dollars>} periodAveragePrice
|
||||
* @property {PeriodCagrPattern} periodCagr
|
||||
@@ -3709,18 +3707,18 @@ function createOutputsPattern(client, acc) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Market_Dca_ClassReturns
|
||||
* @property {MetricPattern4<StoredF32>} _2015
|
||||
* @property {MetricPattern4<StoredF32>} _2016
|
||||
* @property {MetricPattern4<StoredF32>} _2017
|
||||
* @property {MetricPattern4<StoredF32>} _2018
|
||||
* @property {MetricPattern4<StoredF32>} _2019
|
||||
* @property {MetricPattern4<StoredF32>} _2020
|
||||
* @property {MetricPattern4<StoredF32>} _2021
|
||||
* @property {MetricPattern4<StoredF32>} _2022
|
||||
* @property {MetricPattern4<StoredF32>} _2023
|
||||
* @property {MetricPattern4<StoredF32>} _2024
|
||||
* @property {MetricPattern4<StoredF32>} _2025
|
||||
* @typedef {Object} MetricsTree_Market_Dca_ClassAveragePrice
|
||||
* @property {MetricPattern4<Dollars>} _2015
|
||||
* @property {MetricPattern4<Dollars>} _2016
|
||||
* @property {MetricPattern4<Dollars>} _2017
|
||||
* @property {MetricPattern4<Dollars>} _2018
|
||||
* @property {MetricPattern4<Dollars>} _2019
|
||||
* @property {MetricPattern4<Dollars>} _2020
|
||||
* @property {MetricPattern4<Dollars>} _2021
|
||||
* @property {MetricPattern4<Dollars>} _2022
|
||||
* @property {MetricPattern4<Dollars>} _2023
|
||||
* @property {MetricPattern4<Dollars>} _2024
|
||||
* @property {MetricPattern4<Dollars>} _2025
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -5728,20 +5726,20 @@ class BrkClient extends BrkClientBase {
|
||||
yearsSincePriceAth: createMetricPattern4(this, 'years_since_price_ath'),
|
||||
},
|
||||
dca: {
|
||||
classAveragePrice: createClassAveragePricePattern(this, 'dca_class'),
|
||||
classReturns: {
|
||||
_2015: createMetricPattern4(this, 'dca_class_2015_returns'),
|
||||
_2016: createMetricPattern4(this, 'dca_class_2016_returns'),
|
||||
_2017: createMetricPattern4(this, 'dca_class_2017_returns'),
|
||||
_2018: createMetricPattern4(this, 'dca_class_2018_returns'),
|
||||
_2019: createMetricPattern4(this, 'dca_class_2019_returns'),
|
||||
_2020: createMetricPattern4(this, 'dca_class_2020_returns'),
|
||||
_2021: createMetricPattern4(this, 'dca_class_2021_returns'),
|
||||
_2022: createMetricPattern4(this, 'dca_class_2022_returns'),
|
||||
_2023: createMetricPattern4(this, 'dca_class_2023_returns'),
|
||||
_2024: createMetricPattern4(this, 'dca_class_2024_returns'),
|
||||
_2025: createMetricPattern4(this, 'dca_class_2025_returns'),
|
||||
classAveragePrice: {
|
||||
_2015: createMetricPattern4(this, 'dca_class_2015_average_price'),
|
||||
_2016: createMetricPattern4(this, 'dca_class_2016_average_price'),
|
||||
_2017: createMetricPattern4(this, 'dca_class_2017_average_price'),
|
||||
_2018: createMetricPattern4(this, 'dca_class_2018_average_price'),
|
||||
_2019: createMetricPattern4(this, 'dca_class_2019_average_price'),
|
||||
_2020: createMetricPattern4(this, 'dca_class_2020_average_price'),
|
||||
_2021: createMetricPattern4(this, 'dca_class_2021_average_price'),
|
||||
_2022: createMetricPattern4(this, 'dca_class_2022_average_price'),
|
||||
_2023: createMetricPattern4(this, 'dca_class_2023_average_price'),
|
||||
_2024: createMetricPattern4(this, 'dca_class_2024_average_price'),
|
||||
_2025: createMetricPattern4(this, 'dca_class_2025_average_price'),
|
||||
},
|
||||
classReturns: createClassAveragePricePattern(this, 'dca_class'),
|
||||
classStack: {
|
||||
_2015: create_2015Pattern(this, 'dca_class_2015_stack'),
|
||||
_2016: create_2015Pattern(this, 'dca_class_2016_stack'),
|
||||
|
||||
@@ -106,13 +106,13 @@ QuarterIndex = int
|
||||
# Transaction locktime
|
||||
RawLockTime = int
|
||||
SemesterIndex = int
|
||||
# Fixed-size boolean value optimized for on-disk storage (stored as u16)
|
||||
# Fixed-size boolean value optimized for on-disk storage (stored as u8)
|
||||
StoredBool = int
|
||||
# Stored 32-bit floating point value
|
||||
StoredF32 = float
|
||||
# Fixed-size 64-bit floating point value optimized for on-disk storage
|
||||
StoredF64 = float
|
||||
StoredI16 = int
|
||||
StoredI8 = int
|
||||
StoredU16 = int
|
||||
# Fixed-size 32-bit unsigned integer optimized for on-disk storage
|
||||
StoredU32 = int
|
||||
@@ -2054,17 +2054,17 @@ class ClassAveragePricePattern(Generic[T]):
|
||||
|
||||
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'))
|
||||
self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_returns'))
|
||||
self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_returns'))
|
||||
self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_returns'))
|
||||
self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_returns'))
|
||||
self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_returns'))
|
||||
self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_returns'))
|
||||
self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_returns'))
|
||||
self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_returns'))
|
||||
self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_returns'))
|
||||
self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_returns'))
|
||||
self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_returns'))
|
||||
|
||||
class DollarsPattern(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
@@ -2146,21 +2146,6 @@ class AddrCountPattern:
|
||||
self.p2wpkh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2wpkh', acc))
|
||||
self.p2wsh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2wsh', acc))
|
||||
|
||||
class FeeRatePattern(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.average: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'average'))
|
||||
self.max: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'max'))
|
||||
self.median: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'median'))
|
||||
self.min: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'min'))
|
||||
self.pct10: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct10'))
|
||||
self.pct25: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct25'))
|
||||
self.pct75: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct75'))
|
||||
self.pct90: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct90'))
|
||||
self.txindex: MetricPattern27[T] = MetricPattern27(client, acc)
|
||||
|
||||
class FullnessPattern(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2176,6 +2161,21 @@ class FullnessPattern(Generic[T]):
|
||||
self.pct75: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct75'))
|
||||
self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90'))
|
||||
|
||||
class FeeRatePattern(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.average: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'average'))
|
||||
self.max: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'max'))
|
||||
self.median: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'median'))
|
||||
self.min: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'min'))
|
||||
self.pct10: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct10'))
|
||||
self.pct25: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct25'))
|
||||
self.pct75: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct75'))
|
||||
self.pct90: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct90'))
|
||||
self.txindex: MetricPattern27[T] = MetricPattern27(client, acc)
|
||||
|
||||
class _0satsPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2204,19 +2204,6 @@ class PhaseDailyCentsPattern(Generic[T]):
|
||||
self.pct75: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct75'))
|
||||
self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90'))
|
||||
|
||||
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 _10yTo12yPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2282,6 +2269,19 @@ class UnrealizedPattern:
|
||||
self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss'))
|
||||
self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit'))
|
||||
|
||||
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,14 @@ class SplitPattern2(Generic[T]):
|
||||
self.low: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'low'))
|
||||
self.open: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'open'))
|
||||
|
||||
class CoinbasePattern:
|
||||
class CostBasisPattern2:
|
||||
"""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'))
|
||||
self.percentiles: PercentilesPattern = PercentilesPattern(client, _m(acc, 'cost_basis'))
|
||||
|
||||
class CoinbasePattern2:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
@@ -2321,6 +2321,15 @@ class CoinbasePattern2:
|
||||
self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd'))
|
||||
self.sats: BlockCountPattern[Sats] = BlockCountPattern(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 SegwitAdoptionPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2330,15 +2339,6 @@ class SegwitAdoptionPattern:
|
||||
self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative'))
|
||||
self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum'))
|
||||
|
||||
class CostBasisPattern2:
|
||||
"""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'))
|
||||
self.percentiles: PercentilesPattern = PercentilesPattern(client, _m(acc, 'cost_basis'))
|
||||
|
||||
class UnclaimedRewardsPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2357,14 +2357,14 @@ class _2015Pattern:
|
||||
self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'usd'))
|
||||
self.sats: MetricPattern4[Sats] = MetricPattern4(client, acc)
|
||||
|
||||
class ActiveSupplyPattern:
|
||||
class CoinbasePattern:
|
||||
"""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: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc'))
|
||||
self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd'))
|
||||
self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc)
|
||||
|
||||
class _1dReturns1mSdPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
@@ -2374,14 +2374,6 @@ class _1dReturns1mSdPattern:
|
||||
self.sd: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sd'))
|
||||
self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma'))
|
||||
|
||||
class SupplyPattern2:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.halved: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'halved'))
|
||||
self.total: ActiveSupplyPattern = ActiveSupplyPattern(client, acc)
|
||||
|
||||
class CostBasisPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2398,6 +2390,14 @@ class RelativePattern4:
|
||||
self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'loss_rel_to_own_supply'))
|
||||
self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'profit_rel_to_own_supply'))
|
||||
|
||||
class SupplyPattern2:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.halved: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'halved'))
|
||||
self.total: ActiveSupplyPattern = ActiveSupplyPattern(client, acc)
|
||||
|
||||
class BitcoinPattern2(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2406,6 +2406,14 @@ class BitcoinPattern2(Generic[T]):
|
||||
self.cumulative: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'cumulative'))
|
||||
self.sum: MetricPattern1[T] = MetricPattern1(client, acc)
|
||||
|
||||
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.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative'))
|
||||
self.sum: MetricPattern1[T] = MetricPattern1(client, acc)
|
||||
|
||||
class SatsPattern(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2414,14 +2422,6 @@ class SatsPattern(Generic[T]):
|
||||
self.ohlc: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'ohlc_sats'))
|
||||
self.split: SplitPattern2[T] = SplitPattern2(client, _m(acc, 'sats'))
|
||||
|
||||
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.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative'))
|
||||
self.sum: MetricPattern1[T] = MetricPattern1(client, acc)
|
||||
|
||||
class RealizedPriceExtraPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2659,10 +2659,10 @@ class MetricsTree_Constants:
|
||||
self.constant_61_8: MetricPattern1[StoredF32] = MetricPattern1(client, 'constant_61_8')
|
||||
self.constant_70: MetricPattern1[StoredU16] = MetricPattern1(client, 'constant_70')
|
||||
self.constant_80: MetricPattern1[StoredU16] = MetricPattern1(client, 'constant_80')
|
||||
self.constant_minus_1: MetricPattern1[StoredI16] = MetricPattern1(client, 'constant_minus_1')
|
||||
self.constant_minus_2: MetricPattern1[StoredI16] = MetricPattern1(client, 'constant_minus_2')
|
||||
self.constant_minus_3: MetricPattern1[StoredI16] = MetricPattern1(client, 'constant_minus_3')
|
||||
self.constant_minus_4: MetricPattern1[StoredI16] = MetricPattern1(client, 'constant_minus_4')
|
||||
self.constant_minus_1: MetricPattern1[StoredI8] = MetricPattern1(client, 'constant_minus_1')
|
||||
self.constant_minus_2: MetricPattern1[StoredI8] = MetricPattern1(client, 'constant_minus_2')
|
||||
self.constant_minus_3: MetricPattern1[StoredI8] = MetricPattern1(client, 'constant_minus_3')
|
||||
self.constant_minus_4: MetricPattern1[StoredI8] = MetricPattern1(client, 'constant_minus_4')
|
||||
|
||||
class MetricsTree_Distribution_AddressCohorts_AmountRange:
|
||||
"""Metrics tree node."""
|
||||
@@ -3269,21 +3269,21 @@ class MetricsTree_Market_Ath:
|
||||
self.price_drawdown: MetricPattern3[StoredF32] = MetricPattern3(client, 'price_drawdown')
|
||||
self.years_since_price_ath: MetricPattern4[StoredF32] = MetricPattern4(client, 'years_since_price_ath')
|
||||
|
||||
class MetricsTree_Market_Dca_ClassReturns:
|
||||
class MetricsTree_Market_Dca_ClassAveragePrice:
|
||||
"""Metrics tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self._2015: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2015_returns')
|
||||
self._2016: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2016_returns')
|
||||
self._2017: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2017_returns')
|
||||
self._2018: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2018_returns')
|
||||
self._2019: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2019_returns')
|
||||
self._2020: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2020_returns')
|
||||
self._2021: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2021_returns')
|
||||
self._2022: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2022_returns')
|
||||
self._2023: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2023_returns')
|
||||
self._2024: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2024_returns')
|
||||
self._2025: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2025_returns')
|
||||
self._2015: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2015_average_price')
|
||||
self._2016: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2016_average_price')
|
||||
self._2017: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2017_average_price')
|
||||
self._2018: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2018_average_price')
|
||||
self._2019: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2019_average_price')
|
||||
self._2020: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2020_average_price')
|
||||
self._2021: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2021_average_price')
|
||||
self._2022: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2022_average_price')
|
||||
self._2023: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2023_average_price')
|
||||
self._2024: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2024_average_price')
|
||||
self._2025: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2025_average_price')
|
||||
|
||||
class MetricsTree_Market_Dca_ClassStack:
|
||||
"""Metrics tree node."""
|
||||
@@ -3305,8 +3305,8 @@ class MetricsTree_Market_Dca:
|
||||
"""Metrics tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.class_average_price: ClassAveragePricePattern[Dollars] = ClassAveragePricePattern(client, 'dca_class')
|
||||
self.class_returns: MetricsTree_Market_Dca_ClassReturns = MetricsTree_Market_Dca_ClassReturns(client)
|
||||
self.class_average_price: MetricsTree_Market_Dca_ClassAveragePrice = MetricsTree_Market_Dca_ClassAveragePrice(client)
|
||||
self.class_returns: ClassAveragePricePattern[StoredF32] = ClassAveragePricePattern(client, 'dca_class')
|
||||
self.class_stack: MetricsTree_Market_Dca_ClassStack = MetricsTree_Market_Dca_ClassStack(client)
|
||||
self.period_average_price: PeriodAveragePricePattern[Dollars] = PeriodAveragePricePattern(client, 'dca_average_price')
|
||||
self.period_cagr: PeriodCagrPattern = PeriodCagrPattern(client, 'dca_cagr')
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
*/**/*.md
|
||||
!scripts/**/_*.js
|
||||
*_old.js
|
||||
|
||||
+133
-67
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
createChart as _createChart,
|
||||
createSeriesMarkers,
|
||||
CandlestickSeries,
|
||||
HistogramSeries,
|
||||
LineSeries,
|
||||
@@ -35,15 +36,15 @@ import { resources } from "../resources.js";
|
||||
* @template T
|
||||
* @typedef {Object} Series
|
||||
* @property {string} id
|
||||
* @property {() => ISeries} inner
|
||||
* @property {number} paneIndex
|
||||
* @property {Signal<boolean>} active
|
||||
* @property {Signal<boolean>} highlighted
|
||||
* @property {Signal<boolean>} hasData
|
||||
* @property {Signal<string | null>} url
|
||||
* @property {() => Record<string, any>} getOptions
|
||||
* @property {(options: Record<string, any>) => void} applyOptions
|
||||
* @property {() => readonly T[]} getData
|
||||
* @property {(data: T) => void} update
|
||||
* @property {(markers: TimeSeriesMarker[]) => void} setMarkers
|
||||
* @property {VoidFunction} clearMarkers
|
||||
* @property {VoidFunction} remove
|
||||
*/
|
||||
|
||||
@@ -97,6 +98,10 @@ export function createChartElement({
|
||||
div.classList.add("chart");
|
||||
parent.append(div);
|
||||
|
||||
// Registry for shared legend signals (same name = linked across panes)
|
||||
/** @type {Map<string, Signal<boolean>>} */
|
||||
const sharedActiveSignals = new Map();
|
||||
|
||||
const legendTop = createLegend(signals);
|
||||
div.append(legendTop.element);
|
||||
|
||||
@@ -165,7 +170,10 @@ export function createChartElement({
|
||||
});
|
||||
};
|
||||
|
||||
const seriesList = signals.createSignal(/** @type {Set<AnySeries>} */ (new Set()), { equals: false });
|
||||
const seriesList = signals.createSignal(
|
||||
/** @type {Set<AnySeries>} */ (new Set()),
|
||||
{ equals: false },
|
||||
);
|
||||
const seriesCount = signals.createMemo(() => seriesList().size);
|
||||
const markers = createMinMaxMarkers({
|
||||
chart: ichart,
|
||||
@@ -186,7 +194,7 @@ export function createChartElement({
|
||||
() => visibleBarsCountBucket() >= 2,
|
||||
);
|
||||
const shouldUpdateMarkers = signals.createMemo(
|
||||
() => visibleBarsCount() * seriesCount() <= 5000,
|
||||
() => visibleBarsCount() * seriesCount() <= 20_000,
|
||||
);
|
||||
|
||||
signals.createEffect(shouldUpdateMarkers, (should) => {
|
||||
@@ -337,12 +345,20 @@ export function createChartElement({
|
||||
paneIndex,
|
||||
position: "sw",
|
||||
createChild(pane) {
|
||||
const { field, selected } = createChoiceField({
|
||||
const defaultValue =
|
||||
unit.id === "usd" && seriesType !== "Baseline" ? "log" : "lin";
|
||||
const selected = signals.createPersistedSignal({
|
||||
defaultValue,
|
||||
storageKey: `${id}-scale-${paneIndex}`,
|
||||
urlKey: paneIndex === 0 ? "price_scale" : "unit_scale",
|
||||
serialize: (v) => v,
|
||||
deserialize: (s) => /** @type {"lin" | "log"} */ (s),
|
||||
});
|
||||
const field = createChoiceField({
|
||||
choices: /** @type {const} */ (["lin", "log"]),
|
||||
id: stringToId(`${id} ${paneIndex} ${unit}`),
|
||||
defaultValue:
|
||||
unit.id === "usd" && seriesType !== "Baseline" ? "log" : "lin",
|
||||
key: `${id}-price-scale-${paneIndex}`,
|
||||
defaultValue,
|
||||
selected,
|
||||
signals,
|
||||
});
|
||||
|
||||
@@ -366,21 +382,19 @@ export function createChartElement({
|
||||
* @param {number} args.order
|
||||
* @param {Color[]} args.colors
|
||||
* @param {LCSeriesType} args.seriesType
|
||||
* @param {() => ISeries} args.inner
|
||||
* @param {AnyMetricPattern} [args.metric]
|
||||
* @param {Accessor<WhitespaceData[]>} [args.data]
|
||||
* @param {number} args.paneIndex
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {(ctx: { active: Signal<boolean> }) => void} args.setup
|
||||
* @param {(ctx: { active: Signal<boolean>, highlighted: Signal<boolean> }) => void} args.setup
|
||||
* @param {() => readonly any[]} args.getData
|
||||
* @param {(data: any[]) => void} args.setData
|
||||
* @param {(data: any) => void} args.update
|
||||
* @param {() => Record<string, any>} args.getOptions
|
||||
* @param {(options: Record<string, any>) => void} args.applyOptions
|
||||
* @param {(markers: TimeSeriesMarker[]) => void} args.setMarkers
|
||||
* @param {VoidFunction} args.clearMarkers
|
||||
* @param {() => void} args.onRemove
|
||||
*/
|
||||
function addSeries({
|
||||
inner,
|
||||
metric,
|
||||
name,
|
||||
unit,
|
||||
@@ -394,22 +408,34 @@ export function createChartElement({
|
||||
getData,
|
||||
setData,
|
||||
update,
|
||||
getOptions,
|
||||
applyOptions,
|
||||
setMarkers,
|
||||
clearMarkers,
|
||||
onRemove,
|
||||
}) {
|
||||
return signals.createRoot((dispose) => {
|
||||
const id = `${stringToId(name)}-${paneIndex}`;
|
||||
const urlId = stringToId(name);
|
||||
|
||||
const active = signals.createSignal(defaultActive ?? true, {
|
||||
save: {
|
||||
keyPrefix: "",
|
||||
key: id,
|
||||
// Reuse existing signal if same name (links legends across panes)
|
||||
let active = sharedActiveSignals.get(urlId);
|
||||
if (!active) {
|
||||
active = signals.createPersistedSignal({
|
||||
defaultValue: defaultActive ?? true,
|
||||
storageKey: id,
|
||||
urlKey: urlId,
|
||||
...serdeBool,
|
||||
},
|
||||
});
|
||||
});
|
||||
sharedActiveSignals.set(urlId, active);
|
||||
}
|
||||
|
||||
setup({ active });
|
||||
const highlighted = signals.createSignal(true);
|
||||
|
||||
setup({ active, highlighted });
|
||||
|
||||
// Update markers when active changes
|
||||
signals.createEffect(active, () => {
|
||||
if (shouldUpdateMarkers()) markers.scheduleUpdate();
|
||||
});
|
||||
|
||||
const hasData = signals.createSignal(false);
|
||||
let lastTime = -Infinity;
|
||||
@@ -420,15 +446,15 @@ export function createChartElement({
|
||||
/** @type {AnySeries} */
|
||||
const series = {
|
||||
active,
|
||||
highlighted,
|
||||
hasData,
|
||||
id,
|
||||
inner,
|
||||
paneIndex,
|
||||
url: signals.createSignal(/** @type {string | null} */ (null)),
|
||||
getOptions,
|
||||
applyOptions,
|
||||
getData,
|
||||
update,
|
||||
setMarkers,
|
||||
clearMarkers,
|
||||
remove() {
|
||||
dispose();
|
||||
onRemove();
|
||||
@@ -705,8 +731,10 @@ export function createChartElement({
|
||||
)
|
||||
);
|
||||
|
||||
// Marker plugin always on candlestick (has true min/max via high/low)
|
||||
const markerPlugin = createSeriesMarkers(candlestickISeries, [], { autoScale: false });
|
||||
|
||||
const series = addSeries({
|
||||
inner: () => (showLine ? lineISeries : candlestickISeries),
|
||||
colors: [upColor, downColor],
|
||||
name,
|
||||
order,
|
||||
@@ -716,22 +744,34 @@ export function createChartElement({
|
||||
data,
|
||||
defaultActive,
|
||||
metric,
|
||||
setup: ({ active }) => {
|
||||
setup: ({ active, highlighted }) => {
|
||||
candlestickISeries.setSeriesOrder(order);
|
||||
lineISeries.setSeriesOrder(order);
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
shouldShow: shouldShowLine(),
|
||||
active: active(),
|
||||
highlighted: highlighted(),
|
||||
barsCount: visibleBarsCount(),
|
||||
}),
|
||||
({ shouldShow, active, barsCount }) => {
|
||||
({ shouldShow, active, highlighted, barsCount }) => {
|
||||
if (barsCount === Infinity) return;
|
||||
const wasLine = showLine;
|
||||
showLine = shouldShow;
|
||||
candlestickISeries.applyOptions({ visible: active && !showLine });
|
||||
// Use transparent when showing the other mode, otherwise use highlight
|
||||
const up = showLine ? "transparent" : upColor.highlight(highlighted);
|
||||
const down = showLine ? "transparent" : downColor.highlight(highlighted);
|
||||
const line = showLine ? colors.default.highlight(highlighted) : "transparent";
|
||||
candlestickISeries.applyOptions({
|
||||
visible: active,
|
||||
upColor: up,
|
||||
downColor: down,
|
||||
wickUpColor: up,
|
||||
wickDownColor: down,
|
||||
});
|
||||
lineISeries.applyOptions({
|
||||
visible: active && showLine,
|
||||
visible: active,
|
||||
color: line,
|
||||
priceLineVisible: active && showLine,
|
||||
});
|
||||
if (wasLine !== showLine && shouldUpdateMarkers())
|
||||
@@ -749,12 +789,8 @@ export function createChartElement({
|
||||
lineISeries.update({ time: data.time, value: data.close });
|
||||
},
|
||||
getData: () => candlestickISeries.data(),
|
||||
getOptions: () =>
|
||||
showLine ? lineISeries.options() : candlestickISeries.options(),
|
||||
applyOptions: (options) =>
|
||||
showLine
|
||||
? lineISeries.applyOptions(options)
|
||||
: candlestickISeries.applyOptions(options),
|
||||
setMarkers: (m) => markerPlugin.setMarkers(m),
|
||||
clearMarkers: () => markerPlugin.setMarkers([]),
|
||||
onRemove: () => {
|
||||
ichart.removeSeries(candlestickISeries);
|
||||
ichart.removeSeries(lineISeries);
|
||||
@@ -803,8 +839,9 @@ export function createChartElement({
|
||||
)
|
||||
);
|
||||
|
||||
const markerPlugin = createSeriesMarkers(iseries, [], { autoScale: false });
|
||||
|
||||
const series = addSeries({
|
||||
inner: () => iseries,
|
||||
colors: isDualColor ? [positiveColor, negativeColor] : [positiveColor],
|
||||
name,
|
||||
order,
|
||||
@@ -814,10 +851,16 @@ export function createChartElement({
|
||||
data,
|
||||
defaultActive,
|
||||
metric,
|
||||
setup: ({ active }) => {
|
||||
setup: ({ active, highlighted }) => {
|
||||
iseries.setSeriesOrder(order);
|
||||
signals.createEffect(active, (active) =>
|
||||
iseries.applyOptions({ visible: active }),
|
||||
signals.createEffect(
|
||||
() => ({ active: active(), highlighted: highlighted() }),
|
||||
({ active, highlighted }) => {
|
||||
iseries.applyOptions({
|
||||
visible: active,
|
||||
color: positiveColor.highlight(highlighted),
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
setData: (data) => {
|
||||
@@ -837,8 +880,8 @@ export function createChartElement({
|
||||
},
|
||||
update: (data) => iseries.update(data),
|
||||
getData: () => iseries.data(),
|
||||
getOptions: () => iseries.options(),
|
||||
applyOptions: (options) => iseries.applyOptions(options),
|
||||
setMarkers: (m) => markerPlugin.setMarkers(m),
|
||||
clearMarkers: () => markerPlugin.setMarkers([]),
|
||||
onRemove: () => ichart.removeSeries(iseries),
|
||||
});
|
||||
return series;
|
||||
@@ -883,8 +926,9 @@ export function createChartElement({
|
||||
)
|
||||
);
|
||||
|
||||
const markerPlugin = createSeriesMarkers(iseries, [], { autoScale: false });
|
||||
|
||||
const series = addSeries({
|
||||
inner: () => iseries,
|
||||
colors: [color],
|
||||
name,
|
||||
order,
|
||||
@@ -894,17 +938,23 @@ export function createChartElement({
|
||||
data,
|
||||
defaultActive,
|
||||
metric,
|
||||
setup: ({ active }) => {
|
||||
setup: ({ active, highlighted }) => {
|
||||
iseries.setSeriesOrder(order);
|
||||
signals.createEffect(active, (active) =>
|
||||
iseries.applyOptions({ visible: active }),
|
||||
signals.createEffect(
|
||||
() => ({ active: active(), highlighted: highlighted() }),
|
||||
({ active, highlighted }) => {
|
||||
iseries.applyOptions({
|
||||
visible: active,
|
||||
color: color.highlight(highlighted),
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
setData: (data) => iseries.setData(data),
|
||||
update: (data) => iseries.update(data),
|
||||
getData: () => iseries.data(),
|
||||
getOptions: () => iseries.options(),
|
||||
applyOptions: (options) => iseries.applyOptions(options),
|
||||
setMarkers: (m) => markerPlugin.setMarkers(m),
|
||||
clearMarkers: () => markerPlugin.setMarkers([]),
|
||||
onRemove: () => ichart.removeSeries(iseries),
|
||||
});
|
||||
return series;
|
||||
@@ -951,8 +1001,9 @@ export function createChartElement({
|
||||
)
|
||||
);
|
||||
|
||||
const markerPlugin = createSeriesMarkers(iseries, [], { autoScale: false });
|
||||
|
||||
const series = addSeries({
|
||||
inner: () => iseries,
|
||||
colors: [color],
|
||||
name,
|
||||
order,
|
||||
@@ -962,10 +1013,16 @@ export function createChartElement({
|
||||
data,
|
||||
defaultActive,
|
||||
metric,
|
||||
setup: ({ active }) => {
|
||||
setup: ({ active, highlighted }) => {
|
||||
iseries.setSeriesOrder(order);
|
||||
signals.createEffect(active, (active) =>
|
||||
iseries.applyOptions({ visible: active }),
|
||||
signals.createEffect(
|
||||
() => ({ active: active(), highlighted: highlighted() }),
|
||||
({ active, highlighted }) => {
|
||||
iseries.applyOptions({
|
||||
visible: active,
|
||||
color: color.highlight(highlighted),
|
||||
});
|
||||
},
|
||||
);
|
||||
signals.createEffect(visibleBarsCountBucket, (bucket) => {
|
||||
const radius = bucket === 3 ? 1 : bucket >= 1 ? 1.5 : 2;
|
||||
@@ -975,8 +1032,8 @@ export function createChartElement({
|
||||
setData: (data) => iseries.setData(data),
|
||||
update: (data) => iseries.update(data),
|
||||
getData: () => iseries.data(),
|
||||
getOptions: () => iseries.options(),
|
||||
applyOptions: (options) => iseries.applyOptions(options),
|
||||
setMarkers: (m) => markerPlugin.setMarkers(m),
|
||||
clearMarkers: () => markerPlugin.setMarkers([]),
|
||||
onRemove: () => ichart.removeSeries(iseries),
|
||||
});
|
||||
return series;
|
||||
@@ -990,6 +1047,8 @@ export function createChartElement({
|
||||
* @param {AnyMetricPattern} [args.metric]
|
||||
* @param {number} [args.paneIndex]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {Color} [args.topColor]
|
||||
* @param {Color} [args.bottomColor]
|
||||
* @param {BaselineSeriesPartialOptions} [args.options]
|
||||
*/
|
||||
addBaselineSeries({
|
||||
@@ -1000,6 +1059,8 @@ export function createChartElement({
|
||||
paneIndex: _paneIndex,
|
||||
defaultActive,
|
||||
data,
|
||||
topColor = colors.green,
|
||||
bottomColor = colors.red,
|
||||
options,
|
||||
}) {
|
||||
const paneIndex = _paneIndex ?? 0;
|
||||
@@ -1015,8 +1076,8 @@ export function createChartElement({
|
||||
price: options?.baseValue?.price ?? 0,
|
||||
},
|
||||
...options,
|
||||
topLineColor: options?.topLineColor ?? colors.green(),
|
||||
bottomLineColor: options?.bottomLineColor ?? colors.red(),
|
||||
topLineColor: topColor(),
|
||||
bottomLineColor: bottomColor(),
|
||||
priceLineVisible: false,
|
||||
bottomFillColor1: "transparent",
|
||||
bottomFillColor2: "transparent",
|
||||
@@ -1028,12 +1089,10 @@ export function createChartElement({
|
||||
)
|
||||
);
|
||||
|
||||
const markerPlugin = createSeriesMarkers(iseries, [], { autoScale: false });
|
||||
|
||||
const series = addSeries({
|
||||
inner: () => iseries,
|
||||
colors: [
|
||||
() => options?.topLineColor ?? colors.green(),
|
||||
() => options?.bottomLineColor ?? colors.red(),
|
||||
],
|
||||
colors: [topColor, bottomColor],
|
||||
name,
|
||||
order,
|
||||
paneIndex,
|
||||
@@ -1042,17 +1101,24 @@ export function createChartElement({
|
||||
data,
|
||||
defaultActive,
|
||||
metric,
|
||||
setup: ({ active }) => {
|
||||
setup: ({ active, highlighted }) => {
|
||||
iseries.setSeriesOrder(order);
|
||||
signals.createEffect(active, (active) =>
|
||||
iseries.applyOptions({ visible: active }),
|
||||
signals.createEffect(
|
||||
() => ({ active: active(), highlighted: highlighted() }),
|
||||
({ active, highlighted }) => {
|
||||
iseries.applyOptions({
|
||||
visible: active,
|
||||
topLineColor: topColor.highlight(highlighted),
|
||||
bottomLineColor: bottomColor.highlight(highlighted),
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
setData: (data) => iseries.setData(data),
|
||||
update: (data) => iseries.update(data),
|
||||
getData: () => iseries.data(),
|
||||
getOptions: () => iseries.options(),
|
||||
applyOptions: (options) => iseries.applyOptions(options),
|
||||
setMarkers: (m) => markerPlugin.setMarkers(m),
|
||||
clearMarkers: () => markerPlugin.setMarkers([]),
|
||||
onRemove: () => ichart.removeSeries(iseries),
|
||||
});
|
||||
return series;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { createLabeledInput, createSpanName } from "../utils/dom.js";
|
||||
import { stringToId } from "../utils/format.js";
|
||||
|
||||
/** @param {string} color */
|
||||
const tameColor = (color) => `${color.slice(0, -1)} / 50%)`;
|
||||
|
||||
/**
|
||||
* @param {Signals} signals
|
||||
*/
|
||||
@@ -52,6 +49,11 @@ export function createLegend(signals) {
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
// Sync checkbox with signal (for shared signals across panes)
|
||||
signals.createEffect(series.active, (active) => {
|
||||
input.checked = active;
|
||||
});
|
||||
|
||||
const spanMain = window.document.createElement("span");
|
||||
spanMain.classList.add("main");
|
||||
label.append(spanMain);
|
||||
@@ -72,6 +74,11 @@ export function createLegend(signals) {
|
||||
|
||||
const shouldHighlight = () => !hovered() || hovered() === series;
|
||||
|
||||
// Update series highlighted state
|
||||
signals.createEffect(shouldHighlight, (shouldHighlight) => {
|
||||
series.highlighted.set(shouldHighlight);
|
||||
});
|
||||
|
||||
const spanColors = window.document.createElement("span");
|
||||
spanColors.classList.add("colors");
|
||||
spanMain.prepend(spanColors);
|
||||
@@ -80,45 +87,13 @@ export function createLegend(signals) {
|
||||
spanColors.append(spanColor);
|
||||
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
color: color(),
|
||||
shouldHighlight: shouldHighlight(),
|
||||
}),
|
||||
({ color, shouldHighlight }) => {
|
||||
if (shouldHighlight) {
|
||||
spanColor.style.backgroundColor = color;
|
||||
} else {
|
||||
spanColor.style.backgroundColor = tameColor(color);
|
||||
}
|
||||
() => color.highlight(shouldHighlight()),
|
||||
(c) => {
|
||||
spanColor.style.backgroundColor = c;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const initialColors = /** @type {Record<string, any>} */ ({});
|
||||
const darkenedColors = /** @type {Record<string, any>} */ ({});
|
||||
|
||||
const seriesOptions = series.getOptions();
|
||||
if (!seriesOptions) return;
|
||||
|
||||
Object.entries(seriesOptions).forEach(([k, v]) => {
|
||||
if (k.toLowerCase().includes("color") && typeof v === "string") {
|
||||
if (!v.startsWith("oklch")) return;
|
||||
initialColors[k] = v;
|
||||
darkenedColors[k] = tameColor(v);
|
||||
} else if (k === "lastValueVisible" && v) {
|
||||
initialColors[k] = true;
|
||||
darkenedColors[k] = false;
|
||||
}
|
||||
});
|
||||
|
||||
signals.createEffect(shouldHighlight, (shouldHighlight) => {
|
||||
if (shouldHighlight) {
|
||||
series.applyOptions(initialColors);
|
||||
} else {
|
||||
series.applyOptions(darkenedColors);
|
||||
}
|
||||
});
|
||||
|
||||
const anchor = window.document.createElement("a");
|
||||
|
||||
signals.createEffect(series.url, (url) => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { createSeriesMarkers } from "../modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.mjs";
|
||||
import { throttle } from "../utils/timing.js";
|
||||
|
||||
/**
|
||||
@@ -9,20 +8,7 @@ import { throttle } from "../utils/timing.js";
|
||||
* @param {(value: number) => string} args.formatValue
|
||||
*/
|
||||
export function createMinMaxMarkers({ chart, seriesList, colors, formatValue }) {
|
||||
/** @type {WeakMap<ISeries, SeriesMarkersPlugin>} */
|
||||
const pluginCache = new WeakMap();
|
||||
|
||||
/** @param {ISeries} iseries */
|
||||
function getOrCreatePlugin(iseries) {
|
||||
let plugin = pluginCache.get(iseries);
|
||||
if (!plugin) {
|
||||
plugin = createSeriesMarkers(iseries, [], { autoScale: false });
|
||||
pluginCache.set(iseries, plugin);
|
||||
}
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/** @type {Set<ISeries>} */
|
||||
/** @type {Set<AnySeries>} */
|
||||
const prevMarkerSeries = new Set();
|
||||
|
||||
function update() {
|
||||
@@ -37,7 +23,7 @@ export function createMinMaxMarkers({ chart, seriesList, colors, formatValue })
|
||||
const t1 = /** @type {number} */ (tRight ?? range.to);
|
||||
const color = colors.gray();
|
||||
|
||||
/** @type {Map<number, { minV: number, minT: Time, minS: ISeries, maxV: number, maxT: Time, maxS: ISeries }>} */
|
||||
/** @type {Map<number, { minV: number, minT: Time, minS: AnySeries, maxV: number, maxT: Time, maxS: AnySeries }>} */
|
||||
const byPane = new Map();
|
||||
|
||||
for (const series of seriesList()) {
|
||||
@@ -57,16 +43,15 @@ export function createMinMaxMarkers({ chart, seriesList, colors, formatValue })
|
||||
if (lo >= len) continue;
|
||||
|
||||
const paneIndex = series.paneIndex;
|
||||
const iseries = series.inner();
|
||||
let pane = byPane.get(paneIndex);
|
||||
if (!pane) {
|
||||
pane = {
|
||||
minV: Infinity,
|
||||
minT: /** @type {Time} */ (0),
|
||||
minS: iseries,
|
||||
minS: series,
|
||||
maxV: -Infinity,
|
||||
maxT: /** @type {Time} */ (0),
|
||||
maxS: iseries,
|
||||
maxS: series,
|
||||
};
|
||||
byPane.set(paneIndex, pane);
|
||||
}
|
||||
@@ -79,17 +64,18 @@ export function createMinMaxMarkers({ chart, seriesList, colors, formatValue })
|
||||
if (v && v < pane.minV) {
|
||||
pane.minV = v;
|
||||
pane.minT = pt.time;
|
||||
pane.minS = iseries;
|
||||
pane.minS = series;
|
||||
}
|
||||
if (h && h > pane.maxV) {
|
||||
pane.maxV = h;
|
||||
pane.maxT = pt.time;
|
||||
pane.maxS = iseries;
|
||||
pane.maxS = series;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set new markers
|
||||
/** @type {Set<AnySeries>} */
|
||||
const used = new Set();
|
||||
for (const { minV, minT, minS, maxV, maxT, maxS } of byPane.values()) {
|
||||
if (!Number.isFinite(minV) || !Number.isFinite(maxV) || minT === maxT)
|
||||
@@ -115,23 +101,23 @@ export function createMinMaxMarkers({ chart, seriesList, colors, formatValue })
|
||||
used.add(minS);
|
||||
used.add(maxS);
|
||||
if (minS === maxS) {
|
||||
getOrCreatePlugin(minS).setMarkers([minM, maxM]);
|
||||
minS.setMarkers([minM, maxM]);
|
||||
} else {
|
||||
getOrCreatePlugin(minS).setMarkers([minM]);
|
||||
getOrCreatePlugin(maxS).setMarkers([maxM]);
|
||||
minS.setMarkers([minM]);
|
||||
maxS.setMarkers([maxM]);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear stale
|
||||
for (const s of prevMarkerSeries) {
|
||||
if (!used.has(s)) getOrCreatePlugin(s).setMarkers([]);
|
||||
if (!used.has(s)) s.clearMarkers();
|
||||
}
|
||||
prevMarkerSeries.clear();
|
||||
for (const s of used) prevMarkerSeries.add(s);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
for (const s of prevMarkerSeries) getOrCreatePlugin(s).setMarkers([]);
|
||||
for (const s of prevMarkerSeries) s.clearMarkers();
|
||||
prevMarkerSeries.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,96 +1,59 @@
|
||||
import {
|
||||
readParam,
|
||||
readNumberParam,
|
||||
writeParam,
|
||||
} from "../utils/url.js";
|
||||
import { readParam, writeParam } from "../utils/url.js";
|
||||
import { readStored, writeToStorage } from "../utils/storage.js";
|
||||
|
||||
/**
|
||||
* @typedef {{ from: number | null, to: number | null }} Range
|
||||
*/
|
||||
|
||||
const INDEX_KEY = "chart-index";
|
||||
const RANGES_KEY = "chart-ranges";
|
||||
const RANGE_SEP = "_";
|
||||
|
||||
/**
|
||||
* @param {Signals} signals
|
||||
*/
|
||||
export function createChartState(signals) {
|
||||
const index = signals.createPersistedSignal({
|
||||
storageKey: "chart-index",
|
||||
urlKey: "index",
|
||||
defaultValue: /** @type {ChartableIndexName} */ ("date"),
|
||||
serialize: (v) => v,
|
||||
deserialize: (s) => /** @type {ChartableIndexName} */ (s),
|
||||
});
|
||||
|
||||
// Ranges stored per-index in localStorage only
|
||||
/** @type {Record<string, Range>} */
|
||||
let ranges = {};
|
||||
try {
|
||||
const stored = localStorage.getItem(RANGES_KEY);
|
||||
const stored = readStored(RANGES_KEY);
|
||||
if (stored) ranges = JSON.parse(stored);
|
||||
} catch {}
|
||||
|
||||
const saveRanges = () => {
|
||||
try {
|
||||
localStorage.setItem(RANGES_KEY, JSON.stringify(ranges));
|
||||
} catch {}
|
||||
};
|
||||
|
||||
// Read index: URL > localStorage > default
|
||||
/** @type {ChartableIndexName} */
|
||||
const defaultIndex = "date";
|
||||
const urlIndex = readParam("index");
|
||||
/** @type {ChartableIndexName} */
|
||||
let initialIndex = defaultIndex;
|
||||
if (urlIndex) {
|
||||
initialIndex = /** @type {ChartableIndexName} */ (urlIndex);
|
||||
} else {
|
||||
try {
|
||||
const stored = localStorage.getItem(INDEX_KEY);
|
||||
if (stored) initialIndex = /** @type {ChartableIndexName} */ (stored);
|
||||
} catch {}
|
||||
// Initialize from URL if present
|
||||
const urlRange = readParam("range");
|
||||
if (urlRange) {
|
||||
const [from, to] = urlRange.split(RANGE_SEP).map(Number);
|
||||
if (!isNaN(from) && !isNaN(to)) {
|
||||
ranges[index()] = { from, to };
|
||||
writeToStorage(RANGES_KEY, JSON.stringify(ranges));
|
||||
}
|
||||
}
|
||||
|
||||
// Read range: URL > localStorage (per index)
|
||||
const urlFrom = readNumberParam("from");
|
||||
const urlTo = readNumberParam("to");
|
||||
const storedRange = ranges[initialIndex] ?? { from: null, to: null };
|
||||
const initialRange = {
|
||||
from: urlFrom ?? storedRange.from,
|
||||
to: urlTo ?? storedRange.to,
|
||||
};
|
||||
// Save URL range to localStorage if present
|
||||
if (urlFrom !== null || urlTo !== null) {
|
||||
ranges[initialIndex] = initialRange;
|
||||
saveRanges();
|
||||
}
|
||||
|
||||
const index = signals.createSignal(/** @type {ChartableIndexName} */ (initialIndex));
|
||||
const currentRange = signals.createSignal(initialRange);
|
||||
|
||||
// Save index changes to localStorage + URL
|
||||
signals.createEffect(index, (value) => {
|
||||
try {
|
||||
localStorage.setItem(INDEX_KEY, value);
|
||||
} catch {}
|
||||
writeParam("index", value !== defaultIndex ? value : null);
|
||||
});
|
||||
|
||||
// When index changes, switch to that index's saved range
|
||||
signals.createEffect(index, (i) => {
|
||||
const range = ranges[i] ?? { from: null, to: null };
|
||||
currentRange.set(range);
|
||||
// Update URL with new range
|
||||
writeParam("from", range.from !== null ? String(range.from) : null);
|
||||
writeParam("to", range.to !== null ? String(range.to) : null);
|
||||
});
|
||||
|
||||
return {
|
||||
index,
|
||||
/** @type {Accessor<Range>} */
|
||||
range: currentRange,
|
||||
/**
|
||||
* @param {Range} value
|
||||
*/
|
||||
/** @returns {Range} */
|
||||
range: () => ranges[index()] ?? { from: null, to: null },
|
||||
/** @param {Range} value */
|
||||
setRange(value) {
|
||||
const i = index();
|
||||
ranges[i] = value;
|
||||
currentRange.set(value);
|
||||
saveRanges();
|
||||
writeParam("from", value.from !== null ? String(value.from) : null);
|
||||
writeParam("to", value.to !== null ? String(value.to) : null);
|
||||
ranges[index()] = value;
|
||||
writeToStorage(RANGES_KEY, JSON.stringify(ranges));
|
||||
if (value.from !== null && value.to !== null) {
|
||||
// Round to 2 decimals for cleaner URLs
|
||||
const f = Math.floor(value.from * 100) / 100;
|
||||
const t = Math.floor(value.to * 100) / 100;
|
||||
writeParam("range", `${f}${RANGE_SEP}${t}`);
|
||||
} else {
|
||||
writeParam("range", null);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { BrkClient } from "./modules/brk-client/index.js";
|
||||
import { initOptions } from "./options/full.js";
|
||||
import ufuzzy from "./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs";
|
||||
import * as leanQr from "./modules/lean-qr/2.7.1/index.mjs";
|
||||
import { init as initExplorer } from "./panes/explorer.js";
|
||||
import { init as initExplorer } from "./panes/_explorer.js";
|
||||
import { init as initChart } from "./panes/chart/index.js";
|
||||
import { init as initTable } from "./panes/table.js";
|
||||
import { init as initSimulation } from "./panes/simulation.js";
|
||||
import { init as initSimulation } from "./panes/_simulation.js";
|
||||
import { next } from "./utils/timing.js";
|
||||
import { replaceHistory } from "./utils/url.js";
|
||||
import { removeStored, writeToStorage } from "./utils/storage.js";
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createChartElement } from "../../chart/index.js";
|
||||
import { createChartState } from "../../chart/state.js";
|
||||
import { webSockets } from "../../utils/ws.js";
|
||||
import { screenshot } from "./screenshot.js";
|
||||
import { debounce } from "../../utils/timing.js";
|
||||
|
||||
const keyPrefix = "chart";
|
||||
const ONE_BTC_IN_SATS = 100_000_000;
|
||||
@@ -89,20 +90,34 @@ export function init({ colors, option, brk }) {
|
||||
});
|
||||
}
|
||||
|
||||
// Sync chart → state.range on user pan/zoom
|
||||
// Debounce to avoid rapid URL updates while panning
|
||||
const debouncedSetRange = debounce(
|
||||
(/** @type {{ from: number, to: number }} */ range) => state.setRange(range),
|
||||
500,
|
||||
);
|
||||
chart.onVisibleLogicalRangeChange((t) => {
|
||||
if (!t) return;
|
||||
state.setRange({ from: t.from, to: t.to });
|
||||
if (!t || t.from >= t.to) return;
|
||||
debouncedSetRange({ from: t.from, to: t.to });
|
||||
});
|
||||
|
||||
chartElement.append(fieldset);
|
||||
|
||||
const { field: topUnitField, selected: topUnit } = createChoiceField({
|
||||
const unitChoices = /** @type {const} */ ([Unit.usd, Unit.sats]);
|
||||
/** @type {Signal<Unit>} */
|
||||
const topUnit = signals.createPersistedSignal({
|
||||
defaultValue: /** @type {Unit} */ (Unit.usd),
|
||||
storageKey: `${keyPrefix}-price`,
|
||||
urlKey: "price",
|
||||
serialize: (u) => u.id,
|
||||
deserialize: (s) => /** @type {Unit} */ (unitChoices.find((u) => u.id === s) ?? Unit.usd),
|
||||
});
|
||||
const topUnitField = createChoiceField({
|
||||
defaultValue: Unit.usd,
|
||||
keyPrefix,
|
||||
key: "unit-0",
|
||||
choices: [Unit.usd, Unit.sats],
|
||||
choices: unitChoices,
|
||||
toKey: (u) => u.id,
|
||||
toLabel: (u) => u.name,
|
||||
selected: topUnit,
|
||||
signals,
|
||||
sorted: true,
|
||||
type: "select",
|
||||
@@ -207,23 +222,28 @@ export function init({ colors, option, brk }) {
|
||||
|
||||
const bottomUnits = Array.from(option.bottom.keys());
|
||||
|
||||
/** @type {{ field: HTMLDivElement, selected: Accessor<Unit> } | undefined} */
|
||||
/** @type {{ field: HTMLDivElement, selected: Signal<Unit> } | undefined} */
|
||||
let bottomUnitSelector;
|
||||
|
||||
if (bottomUnits.length) {
|
||||
bottomUnitSelector = createChoiceField({
|
||||
const selected = signals.createPersistedSignal({
|
||||
defaultValue: bottomUnits[0],
|
||||
storageKey: `${keyPrefix}-unit`,
|
||||
urlKey: "unit",
|
||||
serialize: (u) => u.id,
|
||||
deserialize: (s) => bottomUnits.find((u) => u.id === s) ?? bottomUnits[0],
|
||||
});
|
||||
const field = createChoiceField({
|
||||
defaultValue: bottomUnits[0],
|
||||
keyPrefix,
|
||||
key: "unit-1",
|
||||
choices: bottomUnits,
|
||||
toKey: (u) => u.id,
|
||||
toLabel: (u) => u.name,
|
||||
selected,
|
||||
signals,
|
||||
sorted: true,
|
||||
type: "select",
|
||||
});
|
||||
|
||||
const field = bottomUnitSelector.field;
|
||||
bottomUnitSelector = { field, selected };
|
||||
chart.addFieldsetIfNeeded({
|
||||
id: "charts-unit-1",
|
||||
paneIndex: 1,
|
||||
@@ -471,9 +491,9 @@ function createIndexSelector(option, state) {
|
||||
|
||||
/** @type {ChartableIndexName} */
|
||||
const defaultIndex = "date";
|
||||
const { field } = createChoiceField({
|
||||
const field = createChoiceField({
|
||||
defaultValue: defaultIndex,
|
||||
signal: state.index,
|
||||
selected: state.index,
|
||||
choices,
|
||||
id: "index",
|
||||
signals,
|
||||
|
||||
+64
-143
@@ -24,6 +24,8 @@ import {
|
||||
onCleanup,
|
||||
} from "./modules/solidjs-signals/0.6.3/dist/prod.js";
|
||||
import { debounce } from "./utils/timing.js";
|
||||
import { writeParam, readParam } from "./utils/url.js";
|
||||
import { readStored, writeToStorage } from "./utils/storage.js";
|
||||
|
||||
let effectCount = 0;
|
||||
|
||||
@@ -68,14 +70,11 @@ const signals = {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} initialValue
|
||||
* @param {SignalOptions<T> & {save?: {keyPrefix: string | Accessor<string>; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean; saveDefaultValue?: boolean}}} [options]
|
||||
* @param {SignalOptions<T>} [options]
|
||||
* @returns {Signal<T>}
|
||||
*/
|
||||
createSignal(initialValue, options) {
|
||||
const [get, set] = this.createSolidSignal(
|
||||
/** @type {any} */ (initialValue),
|
||||
options,
|
||||
);
|
||||
const [get, set] = this.createSolidSignal(/** @type {any} */ (initialValue), options);
|
||||
|
||||
// @ts-ignore
|
||||
get.set = set;
|
||||
@@ -83,148 +82,70 @@ const signals = {
|
||||
// @ts-ignore
|
||||
get.reset = () => set(initialValue);
|
||||
|
||||
if (options?.save) {
|
||||
const save = options.save;
|
||||
|
||||
const paramKey = save.key;
|
||||
const storageKey = this.createMemo(
|
||||
() =>
|
||||
`${
|
||||
typeof save.keyPrefix === "string"
|
||||
? save.keyPrefix
|
||||
: save.keyPrefix()
|
||||
}-${paramKey}`,
|
||||
);
|
||||
|
||||
// /** @type { ((this: Window, ev: PopStateEvent) => any) | undefined} */
|
||||
// let popstateCallback;
|
||||
|
||||
let serialized = /** @type {string | null} */ (null);
|
||||
if (options.save.serializeParam !== false) {
|
||||
serialized = new URLSearchParams(window.location.search).get(paramKey);
|
||||
|
||||
// popstateCallback =
|
||||
// /** @type {(this: Window, ev: PopStateEvent) => any} */ (
|
||||
// (_) => {
|
||||
// serialized = new URLSearchParams(window.location.search).get(
|
||||
// paramKey,
|
||||
// );
|
||||
// set(() =>
|
||||
// serialized ? save.deserialize(serialized) : initialValue,
|
||||
// );
|
||||
// }
|
||||
// );
|
||||
// if (!popstateCallback) throw "Unreachable";
|
||||
// window.addEventListener("popstate", popstateCallback);
|
||||
// signals.onCleanup(() => {
|
||||
// if (popstateCallback)
|
||||
// window.removeEventListener("popstate", popstateCallback);
|
||||
// });
|
||||
}
|
||||
if (serialized === null) {
|
||||
try {
|
||||
serialized = localStorage.getItem(storageKey());
|
||||
} catch (_) {}
|
||||
}
|
||||
if (serialized) {
|
||||
set(() => (serialized ? save.deserialize(serialized) : initialValue));
|
||||
}
|
||||
|
||||
let firstRun1 = true;
|
||||
this.createEffect(storageKey, (storageKey) => {
|
||||
if (!firstRun1) {
|
||||
try {
|
||||
serialized = localStorage.getItem(storageKey);
|
||||
set(() =>
|
||||
serialized ? save.deserialize(serialized) : initialValue,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
firstRun1 = false;
|
||||
});
|
||||
|
||||
let firstRun2 = true;
|
||||
|
||||
const debouncedSave = debounce(
|
||||
/** @param {T} value */ (value) => {
|
||||
try {
|
||||
if (
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
(initialValue === undefined ||
|
||||
initialValue === null ||
|
||||
save.saveDefaultValue ||
|
||||
save.serialize(value) !== save.serialize(initialValue))
|
||||
) {
|
||||
localStorage.setItem(storageKey(), save.serialize(value));
|
||||
writeParam(paramKey, save.serialize(value));
|
||||
} else {
|
||||
localStorage.removeItem(storageKey());
|
||||
removeParam(paramKey);
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
250,
|
||||
);
|
||||
|
||||
this.createEffect(get, (value) => {
|
||||
if (!save) return;
|
||||
|
||||
if (firstRun2) {
|
||||
// First run: sync URL params immediately
|
||||
if (
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
(initialValue === undefined ||
|
||||
initialValue === null ||
|
||||
save.saveDefaultValue ||
|
||||
save.serialize(value) !== save.serialize(initialValue))
|
||||
) {
|
||||
writeParam(paramKey, save.serialize(value));
|
||||
} else {
|
||||
removeParam(paramKey);
|
||||
}
|
||||
firstRun2 = false;
|
||||
} else {
|
||||
// Subsequent runs: debounce
|
||||
debouncedSave(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return get;
|
||||
},
|
||||
/**
|
||||
* @template T
|
||||
* @param {Object} args
|
||||
* @param {T} args.defaultValue
|
||||
* @param {string} args.storageKey
|
||||
* @param {string} [args.urlKey]
|
||||
* @param {(v: T) => string} args.serialize
|
||||
* @param {(s: string) => T} args.deserialize
|
||||
* @param {boolean} [args.saveDefaultValue]
|
||||
* @returns {Signal<T>}
|
||||
*/
|
||||
createPersistedSignal({
|
||||
defaultValue,
|
||||
storageKey,
|
||||
urlKey,
|
||||
serialize,
|
||||
deserialize,
|
||||
saveDefaultValue = false,
|
||||
}) {
|
||||
const defaultSerialized = serialize(defaultValue);
|
||||
|
||||
// Read: URL > localStorage > default
|
||||
let serialized = urlKey ? readParam(urlKey) : null;
|
||||
if (serialized === null) {
|
||||
serialized = readStored(storageKey);
|
||||
}
|
||||
const initialValue = serialized !== null ? deserialize(serialized) : defaultValue;
|
||||
|
||||
const signal = this.createSignal(initialValue);
|
||||
|
||||
/** @param {T} value */
|
||||
const write = (value) => {
|
||||
const s = serialize(value);
|
||||
const isDefault = s === defaultSerialized;
|
||||
|
||||
if (!isDefault || saveDefaultValue) {
|
||||
writeToStorage(storageKey, s);
|
||||
} else {
|
||||
writeToStorage(storageKey, null);
|
||||
}
|
||||
|
||||
if (urlKey) {
|
||||
writeParam(urlKey, !isDefault || saveDefaultValue ? s : null);
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedWrite = debounce(write, 250);
|
||||
|
||||
let firstRun = true;
|
||||
this.createEffect(signal, (value) => {
|
||||
if (firstRun) {
|
||||
write(value);
|
||||
firstRun = false;
|
||||
} else {
|
||||
debouncedWrite(value);
|
||||
}
|
||||
});
|
||||
|
||||
return signal;
|
||||
},
|
||||
};
|
||||
/** @typedef {typeof signals} Signals */
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {string | undefined} value
|
||||
*/
|
||||
function writeParam(key, value) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (value !== undefined) {
|
||||
urlParams.set(key, String(value));
|
||||
} else {
|
||||
urlParams.delete(key);
|
||||
}
|
||||
|
||||
try {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
`${window.location.pathname}?${urlParams.toString()}`,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
function removeParam(key) {
|
||||
writeParam(key, undefined);
|
||||
}
|
||||
|
||||
export default signals;
|
||||
|
||||
@@ -1,70 +1,45 @@
|
||||
/**
|
||||
* Reduce color opacity to 50% for dimming effect
|
||||
* @param {string} color - oklch color string
|
||||
*/
|
||||
export function tameColor(color) {
|
||||
if (color === "transparent") return color;
|
||||
return `${color.slice(0, -1)} / 50%)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ColorMethods
|
||||
* @property {() => string} tame - Returns tamed (50% opacity) version
|
||||
* @property {(highlighted: boolean) => string} highlight - Returns normal if highlighted, tamed otherwise
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(() => string) & ColorMethods} Color
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a Color object that is callable and has utility methods
|
||||
* @param {() => string} getter
|
||||
* @returns {Color}
|
||||
*/
|
||||
function createColor(getter) {
|
||||
const color = /** @type {Color} */ (() => getter());
|
||||
color.tame = () => tameColor(getter());
|
||||
color.highlight = (highlighted) => highlighted ? getter() : tameColor(getter());
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Accessor<boolean>} dark
|
||||
*/
|
||||
export function createColors(dark) {
|
||||
const globalComputedStyle = getComputedStyle(window.document.documentElement);
|
||||
|
||||
/**
|
||||
* @param {string} color
|
||||
* @param {string} name
|
||||
*/
|
||||
function getColor(color) {
|
||||
return globalComputedStyle.getPropertyValue(`--${color}`);
|
||||
}
|
||||
function red() {
|
||||
return getColor("red");
|
||||
}
|
||||
function orange() {
|
||||
return getColor("orange");
|
||||
}
|
||||
function amber() {
|
||||
return getColor("amber");
|
||||
}
|
||||
function yellow() {
|
||||
return getColor("yellow");
|
||||
}
|
||||
function avocado() {
|
||||
return getColor("avocado");
|
||||
}
|
||||
function lime() {
|
||||
return getColor("lime");
|
||||
}
|
||||
function green() {
|
||||
return getColor("green");
|
||||
}
|
||||
function emerald() {
|
||||
return getColor("emerald");
|
||||
}
|
||||
function teal() {
|
||||
return getColor("teal");
|
||||
}
|
||||
function cyan() {
|
||||
return getColor("cyan");
|
||||
}
|
||||
function sky() {
|
||||
return getColor("sky");
|
||||
}
|
||||
function blue() {
|
||||
return getColor("blue");
|
||||
}
|
||||
function indigo() {
|
||||
return getColor("indigo");
|
||||
}
|
||||
function violet() {
|
||||
return getColor("violet");
|
||||
}
|
||||
function purple() {
|
||||
return getColor("purple");
|
||||
}
|
||||
function fuchsia() {
|
||||
return getColor("fuchsia");
|
||||
}
|
||||
function pink() {
|
||||
return getColor("pink");
|
||||
}
|
||||
function rose() {
|
||||
return getColor("rose");
|
||||
}
|
||||
function gray() {
|
||||
return getColor("gray");
|
||||
function getColor(name) {
|
||||
return globalComputedStyle.getPropertyValue(`--${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,41 +51,33 @@ export function createColors(dark) {
|
||||
return dark() ? _dark : light;
|
||||
}
|
||||
|
||||
function textColor() {
|
||||
return getLightDarkValue("--color");
|
||||
}
|
||||
function borderColor() {
|
||||
return getLightDarkValue("--border-color");
|
||||
}
|
||||
|
||||
return {
|
||||
default: textColor,
|
||||
gray,
|
||||
border: borderColor,
|
||||
default: createColor(() => getLightDarkValue("--color")),
|
||||
gray: createColor(() => getColor("gray")),
|
||||
border: createColor(() => getLightDarkValue("--border-color")),
|
||||
|
||||
red,
|
||||
orange,
|
||||
amber,
|
||||
yellow,
|
||||
avocado,
|
||||
lime,
|
||||
green,
|
||||
emerald,
|
||||
teal,
|
||||
cyan,
|
||||
sky,
|
||||
blue,
|
||||
indigo,
|
||||
violet,
|
||||
purple,
|
||||
fuchsia,
|
||||
pink,
|
||||
rose,
|
||||
red: createColor(() => getColor("red")),
|
||||
orange: createColor(() => getColor("orange")),
|
||||
amber: createColor(() => getColor("amber")),
|
||||
yellow: createColor(() => getColor("yellow")),
|
||||
avocado: createColor(() => getColor("avocado")),
|
||||
lime: createColor(() => getColor("lime")),
|
||||
green: createColor(() => getColor("green")),
|
||||
emerald: createColor(() => getColor("emerald")),
|
||||
teal: createColor(() => getColor("teal")),
|
||||
cyan: createColor(() => getColor("cyan")),
|
||||
sky: createColor(() => getColor("sky")),
|
||||
blue: createColor(() => getColor("blue")),
|
||||
indigo: createColor(() => getColor("indigo")),
|
||||
violet: createColor(() => getColor("violet")),
|
||||
purple: createColor(() => getColor("purple")),
|
||||
fuchsia: createColor(() => getColor("fuchsia")),
|
||||
pink: createColor(() => getColor("pink")),
|
||||
rose: createColor(() => getColor("rose")),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {ReturnType<typeof createColors>} Colors
|
||||
* @typedef {Colors["orange"]} Color
|
||||
* @typedef {keyof Colors} ColorName
|
||||
*/
|
||||
|
||||
@@ -215,15 +215,13 @@ export function importStyle(href) {
|
||||
/**
|
||||
* @template T
|
||||
* @param {Object} args
|
||||
* @param {T} args.defaultValue
|
||||
* @param {T} args.defaultValue - Fallback when selected value is no longer in choices
|
||||
* @param {string} [args.id]
|
||||
* @param {readonly T[] | Accessor<readonly T[]>} args.choices
|
||||
* @param {string} [args.keyPrefix]
|
||||
* @param {string} [args.key]
|
||||
* @param {boolean} [args.sorted]
|
||||
* @param {Signals} args.signals
|
||||
* @param {Signal<T>} [args.signal] - Optional external signal to use instead of creating one
|
||||
* @param {(choice: T) => string} [args.toKey] - Extract string key for storage (defaults to identity for strings)
|
||||
* @param {Signal<T>} args.selected
|
||||
* @param {(choice: T) => string} [args.toKey] - Extract string key (defaults to identity for strings)
|
||||
* @param {(choice: T) => string} [args.toLabel] - Extract display label (defaults to identity for strings)
|
||||
* @param {"radio" | "select"} [args.type] - Render as radio buttons or select dropdown
|
||||
*/
|
||||
@@ -231,10 +229,8 @@ export function createChoiceField({
|
||||
id,
|
||||
choices: unsortedChoices,
|
||||
defaultValue,
|
||||
keyPrefix,
|
||||
key,
|
||||
signals,
|
||||
signal: externalSignal,
|
||||
selected,
|
||||
sorted,
|
||||
toKey = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
|
||||
toLabel = /** @type {(choice: T) => string} */ ((/** @type {any} */ c) => c),
|
||||
@@ -260,25 +256,8 @@ export function createChoiceField({
|
||||
: c;
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} storedKey
|
||||
* @returns {T}
|
||||
*/
|
||||
function fromKey(storedKey) {
|
||||
const found = choices().find((c) => toKey(c) === storedKey);
|
||||
return found ?? defaultValue;
|
||||
}
|
||||
|
||||
/** @type {Signal<T>} */
|
||||
const selected = externalSignal ?? signals.createSignal(defaultValue, {
|
||||
save: {
|
||||
serialize: (v) => toKey(v),
|
||||
deserialize: (s) => fromKey(s),
|
||||
keyPrefix: keyPrefix ?? "",
|
||||
key: key ?? "",
|
||||
saveDefaultValue: true,
|
||||
},
|
||||
});
|
||||
/** @param {string} key */
|
||||
const fromKey = (key) => choices().find((c) => toKey(c) === key) ?? defaultValue;
|
||||
|
||||
const field = window.document.createElement("div");
|
||||
field.classList.add("field");
|
||||
@@ -317,8 +296,8 @@ export function createChoiceField({
|
||||
}
|
||||
} else if (type === "select") {
|
||||
const select = window.document.createElement("select");
|
||||
select.id = id ?? key ?? "";
|
||||
select.name = id ?? key ?? "";
|
||||
select.id = id ?? "";
|
||||
select.name = id ?? "";
|
||||
|
||||
choices.forEach((choice) => {
|
||||
const option = window.document.createElement("option");
|
||||
@@ -346,7 +325,7 @@ export function createChoiceField({
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const fieldId = id ?? key ?? "";
|
||||
const fieldId = id ?? "";
|
||||
choices.forEach((choice) => {
|
||||
const choiceKey = toKey(choice);
|
||||
const choiceLabel = toLabel(choice);
|
||||
@@ -372,7 +351,7 @@ export function createChoiceField({
|
||||
}
|
||||
});
|
||||
|
||||
return { field, selected };
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,10 +104,8 @@ export const serdeBool = {
|
||||
deserialize(v) {
|
||||
if (v === "true" || v === "1") {
|
||||
return true;
|
||||
} else if (v === "false" || v === "0") {
|
||||
return false;
|
||||
} else {
|
||||
throw "deser bool err";
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
export function readStoredNumber(key) {
|
||||
const saved = readStored(key);
|
||||
if (saved) {
|
||||
return Number(saved);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
export function readStoredBool(key) {
|
||||
const saved = readStored(key);
|
||||
if (saved) {
|
||||
return saved === "true" || saved === "1";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
*/
|
||||
function processPathname(pathname) {
|
||||
pathname ||= window.location.pathname;
|
||||
return Array.isArray(pathname) ? pathname.join("/") : pathname;
|
||||
const result = Array.isArray(pathname) ? pathname.join("/") : pathname;
|
||||
// Strip leading slash to avoid double slashes when prepending /
|
||||
return result.startsWith("/") ? result.slice(1) : result;
|
||||
}
|
||||
|
||||
const chartParamsWhitelist = ["from", "to"];
|
||||
const chartParamsWhitelist = ["range"];
|
||||
|
||||
/**
|
||||
* @param {string | string[]} pathname
|
||||
@@ -16,7 +18,6 @@ export function pushHistory(pathname) {
|
||||
pathname = processPathname(pathname);
|
||||
try {
|
||||
const url = `/${pathname}?${urlParams.toString()}`;
|
||||
console.log(`push history: ${url}`);
|
||||
window.history.pushState(null, "", url);
|
||||
} catch (_) {}
|
||||
}
|
||||
@@ -31,7 +32,6 @@ export function replaceHistory({ urlParams, pathname }) {
|
||||
pathname = processPathname(pathname);
|
||||
try {
|
||||
const url = `/${pathname}?${urlParams.toString()}`;
|
||||
console.log(`replace history: ${url}`);
|
||||
window.history.replaceState(null, "", url);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
+28
-31
@@ -14,6 +14,19 @@ const offline = () =>
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {Request | string} req
|
||||
*/
|
||||
function fetchAndCache(req) {
|
||||
return fetch(req).then((res) => {
|
||||
if (res.ok) {
|
||||
const clone = res.clone();
|
||||
caches.open(CACHE).then((c) => c.put(req, clone));
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
sw.addEventListener("install", (e) => {
|
||||
e.waitUntil(
|
||||
caches
|
||||
@@ -53,12 +66,9 @@ sw.addEventListener("fetch", (event) => {
|
||||
// Navigation: network-first for shell
|
||||
if (req.mode === "navigate") {
|
||||
event.respondWith(
|
||||
fetch(ROOT)
|
||||
.then((res) => {
|
||||
if (res.ok) caches.open(CACHE).then((c) => c.put(ROOT, res.clone()));
|
||||
return res;
|
||||
})
|
||||
.catch(() => caches.match(ROOT).then((c) => c || offline())),
|
||||
fetchAndCache(ROOT).catch(() =>
|
||||
caches.match(ROOT).then((c) => c || offline()),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -68,15 +78,7 @@ sw.addEventListener("fetch", (event) => {
|
||||
event.respondWith(
|
||||
caches
|
||||
.match(req)
|
||||
.then(
|
||||
(cached) =>
|
||||
cached ||
|
||||
fetch(req).then((res) => {
|
||||
if (res.ok)
|
||||
caches.open(CACHE).then((c) => c.put(req, res.clone()));
|
||||
return res;
|
||||
}),
|
||||
)
|
||||
.then((cached) => cached || fetchAndCache(req))
|
||||
.catch(() => offline()),
|
||||
);
|
||||
return;
|
||||
@@ -86,21 +88,16 @@ sw.addEventListener("fetch", (event) => {
|
||||
// SPA routes (no extension) fall back to ROOT, static assets get 503
|
||||
const isStatic = path.includes(".") && !path.endsWith(".html");
|
||||
event.respondWith(
|
||||
fetch(req)
|
||||
.then((res) => {
|
||||
if (res.ok) caches.open(CACHE).then((c) => c.put(req, res.clone()));
|
||||
return res;
|
||||
})
|
||||
.catch(() =>
|
||||
caches
|
||||
.match(req)
|
||||
.then(
|
||||
(cached) =>
|
||||
cached ||
|
||||
(isStatic
|
||||
? offline()
|
||||
: caches.match(ROOT).then((c) => c || offline())),
|
||||
),
|
||||
),
|
||||
fetchAndCache(req).catch(() =>
|
||||
caches
|
||||
.match(req)
|
||||
.then(
|
||||
(cached) =>
|
||||
cached ||
|
||||
(isStatic
|
||||
? offline()
|
||||
: caches.match(ROOT).then((c) => c || offline())),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user