computer + kibo: part 13

This commit is contained in:
nym21
2025-04-18 18:08:11 +02:00
parent ac7c2f3d03
commit 1cfb7b5615
12 changed files with 1184 additions and 474 deletions

View File

@@ -2,7 +2,7 @@ use std::{fs, path::Path};
use brk_core::{
Cents, Close, Dateindex, Decadeindex, Difficultyepoch, Dollars, Height, High, Low, Monthindex,
OHLCCents, OHLCDollars, Open, Quarterindex, Sats, Weekindex, Yearindex,
OHLCCents, OHLCDollars, OHLCSats, Open, Quarterindex, Sats, Weekindex, Yearindex,
};
use brk_exit::Exit;
use brk_fetcher::Fetcher;
@@ -23,31 +23,46 @@ pub struct Vecs {
pub dateindex_to_high_in_cents: ComputedVec<Dateindex, High<Cents>>,
pub dateindex_to_low_in_cents: ComputedVec<Dateindex, Low<Cents>>,
pub dateindex_to_ohlc: ComputedVec<Dateindex, OHLCDollars>,
pub dateindex_to_ohlc_in_sats: ComputedVec<Dateindex, OHLCSats>,
pub dateindex_to_ohlc_in_cents: ComputedVec<Dateindex, OHLCCents>,
pub dateindex_to_open_in_cents: ComputedVec<Dateindex, Open<Cents>>,
pub height_to_close_in_cents: ComputedVec<Height, Close<Cents>>,
pub height_to_high_in_cents: ComputedVec<Height, High<Cents>>,
pub height_to_low_in_cents: ComputedVec<Height, Low<Cents>>,
pub height_to_ohlc: ComputedVec<Height, OHLCDollars>,
pub height_to_ohlc_in_sats: ComputedVec<Height, OHLCSats>,
pub height_to_ohlc_in_cents: ComputedVec<Height, OHLCCents>,
pub height_to_open_in_cents: ComputedVec<Height, Open<Cents>>,
pub timeindexes_to_close: ComputedVecsFromDateindex<Close<Dollars>>,
pub timeindexes_to_high: ComputedVecsFromDateindex<High<Dollars>>,
pub timeindexes_to_low: ComputedVecsFromDateindex<Low<Dollars>>,
pub timeindexes_to_open: ComputedVecsFromDateindex<Open<Dollars>>,
pub timeindexes_to_sats_per_dollar: ComputedVecsFromDateindex<Close<Sats>>,
pub timeindexes_to_open_in_sats: ComputedVecsFromDateindex<Open<Sats>>,
pub timeindexes_to_high_in_sats: ComputedVecsFromDateindex<High<Sats>>,
pub timeindexes_to_low_in_sats: ComputedVecsFromDateindex<Low<Sats>>,
pub timeindexes_to_close_in_sats: ComputedVecsFromDateindex<Close<Sats>>,
pub chainindexes_to_close: ComputedVecsFromHeightStrict<Close<Dollars>>,
pub chainindexes_to_high: ComputedVecsFromHeightStrict<High<Dollars>>,
pub chainindexes_to_low: ComputedVecsFromHeightStrict<Low<Dollars>>,
pub chainindexes_to_open: ComputedVecsFromHeightStrict<Open<Dollars>>,
pub chainindexes_to_sats_per_dollar: ComputedVecsFromHeightStrict<Close<Sats>>,
pub chainindexes_to_open_in_sats: ComputedVecsFromHeightStrict<Open<Sats>>,
pub chainindexes_to_high_in_sats: ComputedVecsFromHeightStrict<High<Sats>>,
pub chainindexes_to_low_in_sats: ComputedVecsFromHeightStrict<Low<Sats>>,
pub chainindexes_to_close_in_sats: ComputedVecsFromHeightStrict<Close<Sats>>,
pub weekindex_to_ohlc: ComputedVec<Weekindex, OHLCDollars>,
pub weekindex_to_ohlc_in_sats: ComputedVec<Weekindex, OHLCSats>,
pub difficultyepoch_to_ohlc: ComputedVec<Difficultyepoch, OHLCDollars>,
pub difficultyepoch_to_ohlc_in_sats: ComputedVec<Difficultyepoch, OHLCSats>,
pub monthindex_to_ohlc: ComputedVec<Monthindex, OHLCDollars>,
pub monthindex_to_ohlc_in_sats: ComputedVec<Monthindex, OHLCSats>,
pub quarterindex_to_ohlc: ComputedVec<Quarterindex, OHLCDollars>,
pub quarterindex_to_ohlc_in_sats: ComputedVec<Quarterindex, OHLCSats>,
pub yearindex_to_ohlc: ComputedVec<Yearindex, OHLCDollars>,
pub yearindex_to_ohlc_in_sats: ComputedVec<Yearindex, OHLCSats>,
// pub halvingepoch_to_ohlc: StorableVec<Halvingepoch, OHLCDollars>,
// pub halvingepoch_to_ohlc_in_sats: StorableVec<Halvingepoch, OHLCSats>,
pub decadeindex_to_ohlc: ComputedVec<Decadeindex, OHLCDollars>,
pub decadeindex_to_ohlc_in_sats: ComputedVec<Decadeindex, OHLCSats>,
}
impl Vecs {
@@ -70,6 +85,11 @@ impl Vecs {
Version::ZERO,
compressed,
)?,
dateindex_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("dateindex_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
dateindex_to_close_in_cents: ComputedVec::forced_import(
&path.join("dateindex_to_close_in_cents"),
Version::ZERO,
@@ -100,6 +120,11 @@ impl Vecs {
Version::ZERO,
compressed,
)?,
height_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("height_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
height_to_close_in_cents: ComputedVec::forced_import(
&path.join("height_to_close_in_cents"),
Version::ZERO,
@@ -148,9 +173,30 @@ impl Vecs {
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
timeindexes_to_sats_per_dollar: ComputedVecsFromDateindex::forced_import(
timeindexes_to_open_in_sats: ComputedVecsFromDateindex::forced_import(
path,
"sats_per_dollar",
"open_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_first(),
)?,
timeindexes_to_high_in_sats: ComputedVecsFromDateindex::forced_import(
path,
"high_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_max(),
)?,
timeindexes_to_low_in_sats: ComputedVecsFromDateindex::forced_import(
path,
"low_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_min(),
)?,
timeindexes_to_close_in_sats: ComputedVecsFromDateindex::forced_import(
path,
"close_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
@@ -183,9 +229,30 @@ impl Vecs {
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
chainindexes_to_sats_per_dollar: ComputedVecsFromHeightStrict::forced_import(
chainindexes_to_open_in_sats: ComputedVecsFromHeightStrict::forced_import(
path,
"sats_per_dollar",
"open_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_first(),
)?,
chainindexes_to_high_in_sats: ComputedVecsFromHeightStrict::forced_import(
path,
"high_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_max(),
)?,
chainindexes_to_low_in_sats: ComputedVecsFromHeightStrict::forced_import(
path,
"low_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_min(),
)?,
chainindexes_to_close_in_sats: ComputedVecsFromHeightStrict::forced_import(
path,
"close_in_sats",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
@@ -195,32 +262,62 @@ impl Vecs {
Version::ZERO,
compressed,
)?,
weekindex_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("weekindex_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
difficultyepoch_to_ohlc: ComputedVec::forced_import(
&path.join("difficultyepoch_to_ohlc"),
Version::ZERO,
compressed,
)?,
difficultyepoch_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("difficultyepoch_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
monthindex_to_ohlc: ComputedVec::forced_import(
&path.join("monthindex_to_ohlc"),
Version::ZERO,
compressed,
)?,
monthindex_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("monthindex_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
quarterindex_to_ohlc: ComputedVec::forced_import(
&path.join("quarterindex_to_ohlc"),
Version::ZERO,
compressed,
)?,
quarterindex_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("quarterindex_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
yearindex_to_ohlc: ComputedVec::forced_import(
&path.join("yearindex_to_ohlc"),
Version::ZERO,
compressed,
)?,
yearindex_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("yearindex_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
// halvingepoch_to_ohlc: StorableVec::forced_import(&path.join("halvingepoch_to_ohlc"), Version::ZERO, compressed)?,
decadeindex_to_ohlc: ComputedVec::forced_import(
&path.join("decadeindex_to_ohlc"),
Version::ZERO,
compressed,
)?,
decadeindex_to_ohlc_in_sats: ComputedVec::forced_import(
&path.join("decadeindex_to_ohlc_in_sats"),
Version::ZERO,
compressed,
)?,
})
}
@@ -637,7 +734,52 @@ impl Vecs {
exit,
)?;
self.chainindexes_to_sats_per_dollar.compute(
self.chainindexes_to_open_in_sats.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.height,
self.chainindexes_to_open.height.mut_vec(),
|(i, open, ..)| (i, Open::new(Sats::ONE_BTC / *open)),
exit,
)
},
)?;
self.chainindexes_to_high_in_sats.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.height,
self.chainindexes_to_low.height.mut_vec(),
|(i, low, ..)| (i, High::new(Sats::ONE_BTC / *low)),
exit,
)
},
)?;
self.chainindexes_to_low_in_sats.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.height,
self.chainindexes_to_high.height.mut_vec(),
|(i, high, ..)| (i, Low::new(Sats::ONE_BTC / *high)),
exit,
)
},
)?;
self.chainindexes_to_close_in_sats.compute(
indexer,
indexes,
starting_indexes,
@@ -646,13 +788,58 @@ impl Vecs {
v.compute_transform(
starting_indexes.height,
self.chainindexes_to_close.height.mut_vec(),
|(i, close, ..)| (i, Close::from(Sats::ONE_BTC / *close)),
|(i, close, ..)| (i, Close::new(Sats::ONE_BTC / *close)),
exit,
)
},
)?;
self.timeindexes_to_sats_per_dollar.compute(
self.timeindexes_to_open_in_sats.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.dateindex,
self.timeindexes_to_open.dateindex.mut_vec(),
|(i, open, ..)| (i, Open::new(Sats::ONE_BTC / *open)),
exit,
)
},
)?;
self.timeindexes_to_high_in_sats.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.dateindex,
self.timeindexes_to_low.dateindex.mut_vec(),
|(i, low, ..)| (i, High::new(Sats::ONE_BTC / *low)),
exit,
)
},
)?;
self.timeindexes_to_low_in_sats.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.dateindex,
self.timeindexes_to_high.dateindex.mut_vec(),
|(i, high, ..)| (i, Low::new(Sats::ONE_BTC / *high)),
exit,
)
},
)?;
self.timeindexes_to_close_in_sats.compute(
indexer,
indexes,
starting_indexes,
@@ -661,12 +848,259 @@ impl Vecs {
v.compute_transform(
starting_indexes.dateindex,
self.timeindexes_to_close.dateindex.mut_vec(),
|(i, close, ..)| (i, Close::from(Sats::ONE_BTC / *close)),
|(i, close, ..)| (i, Close::new(Sats::ONE_BTC / *close)),
exit,
)
},
)?;
self.height_to_ohlc_in_sats.compute_transform(
starting_indexes.height,
self.chainindexes_to_close_in_sats.height.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.chainindexes_to_open_in_sats
.height
.double_unwrap_cached_get(i),
high: self
.chainindexes_to_high_in_sats
.height
.double_unwrap_cached_get(i),
low: self
.chainindexes_to_low_in_sats
.height
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
self.dateindex_to_ohlc_in_sats.compute_transform(
starting_indexes.dateindex,
self.timeindexes_to_close_in_sats.dateindex.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.timeindexes_to_open_in_sats
.dateindex
.double_unwrap_cached_get(i),
high: self
.timeindexes_to_high_in_sats
.dateindex
.double_unwrap_cached_get(i),
low: self
.timeindexes_to_low_in_sats
.dateindex
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
self.weekindex_to_ohlc_in_sats.compute_transform(
starting_indexes.weekindex,
self.timeindexes_to_close_in_sats
.weekindex
.unwrap_last()
.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.timeindexes_to_open_in_sats
.weekindex
.unwrap_first()
.double_unwrap_cached_get(i),
high: self
.timeindexes_to_high_in_sats
.weekindex
.unwrap_max()
.double_unwrap_cached_get(i),
low: self
.timeindexes_to_low_in_sats
.weekindex
.unwrap_min()
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
self.difficultyepoch_to_ohlc_in_sats.compute_transform(
starting_indexes.difficultyepoch,
self.chainindexes_to_close_in_sats
.difficultyepoch
.unwrap_last()
.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.chainindexes_to_open_in_sats
.difficultyepoch
.unwrap_first()
.double_unwrap_cached_get(i),
high: self
.chainindexes_to_high_in_sats
.difficultyepoch
.unwrap_max()
.double_unwrap_cached_get(i),
low: self
.chainindexes_to_low_in_sats
.difficultyepoch
.unwrap_min()
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
self.monthindex_to_ohlc_in_sats.compute_transform(
starting_indexes.monthindex,
self.timeindexes_to_close_in_sats
.monthindex
.unwrap_last()
.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.timeindexes_to_open_in_sats
.monthindex
.unwrap_first()
.double_unwrap_cached_get(i),
high: self
.timeindexes_to_high_in_sats
.monthindex
.unwrap_max()
.double_unwrap_cached_get(i),
low: self
.timeindexes_to_low_in_sats
.monthindex
.unwrap_min()
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
self.quarterindex_to_ohlc_in_sats.compute_transform(
starting_indexes.quarterindex,
self.timeindexes_to_close_in_sats
.quarterindex
.unwrap_last()
.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.timeindexes_to_open_in_sats
.quarterindex
.unwrap_first()
.double_unwrap_cached_get(i),
high: self
.timeindexes_to_high_in_sats
.quarterindex
.unwrap_max()
.double_unwrap_cached_get(i),
low: self
.timeindexes_to_low_in_sats
.quarterindex
.unwrap_min()
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
self.yearindex_to_ohlc_in_sats.compute_transform(
starting_indexes.yearindex,
self.timeindexes_to_close_in_sats
.yearindex
.unwrap_last()
.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.timeindexes_to_open_in_sats
.yearindex
.unwrap_first()
.double_unwrap_cached_get(i),
high: self
.timeindexes_to_high_in_sats
.yearindex
.unwrap_max()
.double_unwrap_cached_get(i),
low: self
.timeindexes_to_low_in_sats
.yearindex
.unwrap_min()
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
// self.halvingepoch_to_ohlc
// _in_sats.compute_transform(starting_indexes.halvingepoch, other, t, exit)?;
self.decadeindex_to_ohlc_in_sats.compute_transform(
starting_indexes.decadeindex,
self.timeindexes_to_close_in_sats
.decadeindex
.unwrap_last()
.mut_vec(),
|(i, close, ..)| {
(
i,
OHLCSats {
open: self
.timeindexes_to_open_in_sats
.decadeindex
.unwrap_first()
.double_unwrap_cached_get(i),
high: self
.timeindexes_to_high_in_sats
.decadeindex
.unwrap_max()
.double_unwrap_cached_get(i),
low: self
.timeindexes_to_low_in_sats
.decadeindex
.unwrap_min()
.double_unwrap_cached_get(i),
close,
},
)
},
exit,
)?;
Ok(())
}
@@ -692,9 +1126,16 @@ impl Vecs {
self.yearindex_to_ohlc.any_vec(),
// self.halvingepoch_to_ohlc.any_vec(),
self.decadeindex_to_ohlc.any_vec(),
self.height_to_ohlc_in_sats.any_vec(),
self.dateindex_to_ohlc_in_sats.any_vec(),
self.weekindex_to_ohlc_in_sats.any_vec(),
self.difficultyepoch_to_ohlc_in_sats.any_vec(),
self.monthindex_to_ohlc_in_sats.any_vec(),
self.quarterindex_to_ohlc_in_sats.any_vec(),
self.yearindex_to_ohlc_in_sats.any_vec(),
// self.halvingepoch_to_ohlc_in_sats.any_vec(),
self.decadeindex_to_ohlc_in_sats.any_vec(),
],
self.chainindexes_to_sats_per_dollar.any_vecs(),
self.timeindexes_to_sats_per_dollar.any_vecs(),
self.timeindexes_to_close.any_vecs(),
self.timeindexes_to_high.any_vecs(),
self.timeindexes_to_low.any_vecs(),
@@ -703,6 +1144,14 @@ impl Vecs {
self.chainindexes_to_high.any_vecs(),
self.chainindexes_to_low.any_vecs(),
self.chainindexes_to_open.any_vecs(),
self.timeindexes_to_close_in_sats.any_vecs(),
self.timeindexes_to_high_in_sats.any_vecs(),
self.timeindexes_to_low_in_sats.any_vecs(),
self.timeindexes_to_open_in_sats.any_vecs(),
self.chainindexes_to_close_in_sats.any_vecs(),
self.chainindexes_to_high_in_sats.any_vecs(),
self.chainindexes_to_low_in_sats.any_vecs(),
self.chainindexes_to_open_in_sats.any_vecs(),
]
.concat()
}

View File

@@ -99,6 +99,51 @@ impl From<&OHLCCents> for OHLCDollars {
}
}
#[derive(Debug, Default, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
#[repr(C)]
pub struct OHLCSats {
pub open: Open<Sats>,
pub high: High<Sats>,
pub low: Low<Sats>,
pub close: Close<Sats>,
}
impl Serialize for OHLCSats {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tup = serializer.serialize_tuple(4)?;
tup.serialize_element(&self.open)?;
tup.serialize_element(&self.high)?;
tup.serialize_element(&self.low)?;
tup.serialize_element(&self.close)?;
tup.end()
}
}
impl From<(Open<Sats>, High<Sats>, Low<Sats>, Close<Sats>)> for OHLCSats {
fn from(value: (Open<Sats>, High<Sats>, Low<Sats>, Close<Sats>)) -> Self {
Self {
open: value.0,
high: value.1,
low: value.2,
close: value.3,
}
}
}
impl From<Close<Sats>> for OHLCSats {
fn from(value: Close<Sats>) -> Self {
Self {
open: Open::from(value),
high: High::from(value),
low: Low::from(value),
close: value,
}
}
}
#[derive(
Debug,
Default,
@@ -117,12 +162,40 @@ impl From<&OHLCCents> for OHLCDollars {
)]
#[repr(C)]
pub struct Open<T>(T);
impl<T> From<T> for Open<T> {
fn from(value: T) -> Self {
impl<T> Open<T> {
pub fn new(value: T) -> Self {
Self(value)
}
}
impl<T> From<usize> for Open<T>
where
T: From<usize>,
{
fn from(value: usize) -> Self {
Self(T::from(value))
}
}
impl<T> From<f64> for Open<T>
where
T: From<f64>,
{
fn from(value: f64) -> Self {
Self(T::from(value))
}
}
impl<T> From<Open<T>> for f64
where
f64: From<T>,
{
fn from(value: Open<T>) -> Self {
Self::from(value.0)
}
}
impl<T> From<Close<T>> for Open<T>
where
T: Copy,
@@ -138,24 +211,6 @@ impl From<Open<Cents>> for Open<Dollars> {
}
}
impl From<usize> for Open<Dollars> {
fn from(value: usize) -> Self {
Self(Dollars::from(value))
}
}
impl From<f64> for Open<Dollars> {
fn from(value: f64) -> Self {
Self(Dollars::from(value))
}
}
impl From<Open<Dollars>> for f64 {
fn from(value: Open<Dollars>) -> Self {
Self::from(value.0)
}
}
impl<T> Add for Open<T>
where
T: Add<Output = T>,
@@ -194,12 +249,40 @@ where
)]
#[repr(C)]
pub struct High<T>(T);
impl<T> From<T> for High<T> {
fn from(value: T) -> Self {
impl<T> High<T> {
pub fn new(value: T) -> Self {
Self(value)
}
}
impl<T> From<usize> for High<T>
where
T: From<usize>,
{
fn from(value: usize) -> Self {
Self(T::from(value))
}
}
impl<T> From<f64> for High<T>
where
T: From<f64>,
{
fn from(value: f64) -> Self {
Self(T::from(value))
}
}
impl<T> From<High<T>> for f64
where
f64: From<T>,
{
fn from(value: High<T>) -> Self {
Self::from(value.0)
}
}
impl<T> From<Close<T>> for High<T>
where
T: Copy,
@@ -215,24 +298,6 @@ impl From<High<Cents>> for High<Dollars> {
}
}
impl From<usize> for High<Dollars> {
fn from(value: usize) -> Self {
Self(Dollars::from(value))
}
}
impl From<f64> for High<Dollars> {
fn from(value: f64) -> Self {
Self(Dollars::from(value))
}
}
impl From<High<Dollars>> for f64 {
fn from(value: High<Dollars>) -> Self {
Self::from(value.0)
}
}
impl<T> Add for High<T>
where
T: Add<Output = T>,
@@ -271,12 +336,40 @@ where
)]
#[repr(C)]
pub struct Low<T>(T);
impl<T> From<T> for Low<T> {
fn from(value: T) -> Self {
impl<T> Low<T> {
pub fn new(value: T) -> Self {
Self(value)
}
}
impl<T> From<usize> for Low<T>
where
T: From<usize>,
{
fn from(value: usize) -> Self {
Self(T::from(value))
}
}
impl<T> From<f64> for Low<T>
where
T: From<f64>,
{
fn from(value: f64) -> Self {
Self(T::from(value))
}
}
impl<T> From<Low<T>> for f64
where
f64: From<T>,
{
fn from(value: Low<T>) -> Self {
Self::from(value.0)
}
}
impl<T> From<Close<T>> for Low<T>
where
T: Copy,
@@ -292,24 +385,6 @@ impl From<Low<Cents>> for Low<Dollars> {
}
}
impl From<usize> for Low<Dollars> {
fn from(value: usize) -> Self {
Self(Dollars::from(value))
}
}
impl From<f64> for Low<Dollars> {
fn from(value: f64) -> Self {
Self(Dollars::from(value))
}
}
impl From<Low<Dollars>> for f64 {
fn from(value: Low<Dollars>) -> Self {
Self::from(value.0)
}
}
impl<T> Add for Low<T>
where
T: Add<Output = T>,
@@ -348,54 +423,52 @@ where
)]
#[repr(C)]
pub struct Close<T>(T);
impl<T> From<T> for Close<T> {
fn from(value: T) -> Self {
impl<T> Close<T> {
pub fn new(value: T) -> Self {
Self(value)
}
}
impl<T> From<usize> for Close<T>
where
T: From<usize>,
{
fn from(value: usize) -> Self {
Self(T::from(value))
}
}
impl<T> From<f64> for Close<T>
where
T: From<f64>,
{
fn from(value: f64) -> Self {
Self(T::from(value))
}
}
impl<T> From<Close<T>> for f64
where
f64: From<T>,
{
fn from(value: Close<T>) -> Self {
Self::from(value.0)
}
}
// impl<A, B> From<Close<A>> for Close<B>
// where
// B: From<A>,
// {
// fn from(value: Close<A>) -> Self {
// Self(B::from(*value))
impl From<Close<Cents>> for Close<Dollars> {
fn from(value: Close<Cents>) -> Self {
Self(Dollars::from(*value))
}
}
impl From<usize> for Close<Dollars> {
fn from(value: usize) -> Self {
Self(Dollars::from(value))
}
}
impl From<usize> for Close<Sats> {
fn from(value: usize) -> Self {
Self(Sats::from(value))
}
}
impl From<f64> for Close<Dollars> {
fn from(value: f64) -> Self {
Self(Dollars::from(value))
}
}
impl From<f64> for Close<Sats> {
fn from(value: f64) -> Self {
Self(Sats::from(value))
}
}
impl From<Close<Dollars>> for f64 {
fn from(value: Close<Dollars>) -> Self {
Self::from(value.0)
}
}
impl From<Close<Sats>> for f64 {
fn from(value: Close<Sats>) -> Self {
Self::from(value.0)
}
}
impl<T> Add for Close<T>
where
T: Add<Output = T>,

View File

@@ -223,10 +223,10 @@ impl Binance {
Ok((
timestamp,
OHLCCents::from((
Open::from(get_cents(1)),
High::from(get_cents(2)),
Low::from(get_cents(3)),
Close::from(get_cents(4)),
Open::new(get_cents(1)),
High::new(get_cents(2)),
Low::new(get_cents(3)),
Close::new(get_cents(4)),
)),
))
}

View File

@@ -141,10 +141,10 @@ impl Kibo {
};
Ok(OHLCCents::from((
Open::from(get_value("open")?),
High::from(get_value("high")?),
Low::from(get_value("low")?),
Close::from(get_value("close")?),
Open::new(get_value("open")?),
High::new(get_value("high")?),
Low::new(get_value("low")?),
Close::new(get_value("close")?),
)))
}
}

View File

@@ -114,10 +114,10 @@ impl Kraken {
Ok((
timestamp,
OHLCCents::from((
Open::from(get_cents(1)),
High::from(get_cents(2)),
Low::from(get_cents(3)),
Close::from(get_cents(4)),
Open::new(get_cents(1)),
High::new(get_cents(2)),
Low::new(get_cents(3)),
Close::new(get_cents(4)),
)),
))
}

View File

@@ -60,9 +60,9 @@ impl Fetcher {
self.binance
.get_from_1mn(timestamp, previous_timestamp)
.unwrap_or_else(|_| {
self.kibo.get_from_height(height).unwrap_or_else(|_| {
self.kibo.get_from_height(height).unwrap_or_else(|e| {
let date = Date::from(timestamp);
eprintln!("{e}");
panic!(
"
Can't find the price for: height: {height} - date: {date}

View File

@@ -1051,12 +1051,32 @@
margin-left: var(--negative-main-padding);
fieldset {
padding-left: var(--main-padding);
padding-top: 0.5rem;
padding: 0.5rem;
z-index: 10;
position: absolute;
left: 0;
top: 0;
&[data-position="nw"] {
padding-left: var(--main-padding);
top: 0;
left: 0;
}
&[data-position="ne"] {
top: 0;
right: 0;
}
&[data-position="se"] {
bottom: 0;
right: 0;
}
&[data-position="sw"] {
padding-left: var(--main-padding);
bottom: 0;
left: 0;
}
gap: 0;
}
}

View File

@@ -1,6 +1,6 @@
// @ts-check
/** @import {IChartApi, ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData, SeriesType} from './v5.0.5-treeshaked/types' */
/** @import {IChartApi, ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData, SeriesType, IPaneApi, LineSeriesOptions} from './v5.0.5-treeshaked/types' */
/**
* @typedef {[number, number, number, number]} OHLCTuple
@@ -47,7 +47,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
autoSize: true,
layout: {
fontFamily: "Geist mono",
fontSize: 13,
// fontSize: 13,
background: { color: "transparent" },
attributionLogo: false,
colorSpace: "display-p3",
@@ -98,6 +98,16 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
index === /** @satisfies {Height} */ (0) ||
index === /** @satisfies {Difficultyepoch} */ (3) ||
index === /** @satisfies {Halvingepoch} */ (8),
minBarSpacing:
index === /** @satisfies {Monthindex} */ (4)
? 1
: index === /** @satisfies {Quarterindex} */ (5)
? 3
: index === /** @satisfies {Yearindex} */ (6)
? 12
: index === /** @satisfies {Decadeindex} */ (7)
? 120
: undefined,
},
crosshair: {
horzLine: {
@@ -291,6 +301,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
* @param {Accessor<CandlestickData[]>} [args.data]
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
* @param {boolean} [args.inverse]
*/
addCandlestickSeries({
vecId,
@@ -299,13 +310,14 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
paneIndex: _paneIndex,
defaultActive,
data,
inverse,
}) {
const paneIndex = _paneIndex ?? 0;
if (!ichart || !timeResource) throw Error("Chart not fully set");
const green = colors.green();
const red = colors.red();
const green = inverse ? colors.red() : colors.green();
const red = inverse ? colors.green() : colors.red();
const series = ichart.addSeries(
/** @type {SeriesDefinition<'Candlestick'>} */ (lc.CandlestickSeries),
{
@@ -351,14 +363,11 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
this.addPriceScaleSelectorIfNeeded({
paneIndex,
seriesType: "Candlestick",
signals,
id,
unit,
utils,
});
return series;
@@ -372,6 +381,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
* @param {Color} [args.color]
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
* @param {DeepPartial<LineStyleOptions & SeriesOptionsCommon>} [args.options]
*/
addLineSeries({
vecId,
@@ -381,12 +391,13 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
paneIndex: _paneIndex,
defaultActive,
data,
options,
}) {
if (!ichart || !timeResource) throw Error("Chart not fully set");
const paneIndex = _paneIndex ?? 0;
color ||= colors.orange;
color ||= unit === "USD" ? colors.dollars : colors.bitcoin;
const series = ichart.addSeries(
/** @type {SeriesDefinition<'Line'>} */ (lc.LineSeries),
@@ -395,6 +406,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
visible: defaultActive !== false,
priceLineVisible: false,
color: color(),
...options,
},
paneIndex,
);
@@ -434,14 +446,11 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
this.addPriceScaleSelectorIfNeeded({
paneIndex,
signals,
seriesType: "Line",
id,
unit,
utils,
});
return series;
@@ -520,18 +529,99 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
this.addPriceScaleSelectorIfNeeded({
paneIndex,
signals,
seriesType: "Baseline",
id,
unit,
utils,
});
return series;
},
/**
* @param {Object} args
* @param {Unit} args.unit
* @param {string} args.id
* @param {SeriesType} args.seriesType
* @param {number} args.paneIndex
*/
addPriceScaleSelectorIfNeeded({ unit, paneIndex, id, seriesType }) {
id = `${id}-scale`;
this.addFieldsetIfNeeded({
id,
paneIndex,
position: "sw",
createChild({ owner, pane }) {
const { field, selected } = utils.dom.createHorizontalChoiceField({
choices: /** @type {const} */ (["lin", "log"]),
id: utils.stringToId(`${id} ${unit}`),
defaultValue:
unit === "USD" && seriesType !== "Baseline" ? "log" : "lin",
key: `${id}-price-scale-${paneIndex}`,
signals,
});
signals.runWithOwner(owner, () => {
signals.createEffect(selected, (selected) => {
try {
pane.priceScale("right").applyOptions({
mode: selected === "lin" ? 0 : 1,
});
} catch {}
});
});
return field;
},
});
},
/**
* @param {Object} args
* @param {string} args.id
* @param {number} args.paneIndex
* @param {"nw" | "ne" | "se" | "sw"} args.position
* @param {number} [args.timeout]
* @param {(args: {owner: Owner | null, pane: IPaneApi<Time>}) => HTMLElement} args.createChild
*/
addFieldsetIfNeeded({ paneIndex, id, position, createChild }) {
const owner = signals.getOwner();
setTimeout(
() => {
const parent = ichart
?.panes()
.at(paneIndex)
?.getHTMLElement()
.children?.item(1)?.firstChild;
if (!parent) throw Error("Parent should exist");
if (
Array.from(parent.childNodes).filter(
(element) =>
/** @type {HTMLElement} */ (element).dataset.position ===
position,
).length
) {
return;
}
const fieldset = window.document.createElement("fieldset");
fieldset.dataset.size = "xs";
fieldset.dataset.position = position;
fieldset.id = `${id}-${paneIndex}`;
const pane = ichart?.panes().at(paneIndex);
if (!pane) throw Error("Expect pane");
pane
.getHTMLElement()
.children?.item(1)
?.firstChild?.appendChild(fieldset);
fieldset.append(createChild({ owner, pane }));
},
paneIndex ? 50 : 0,
);
},
/**
*
* @param {Object} args
@@ -894,106 +984,3 @@ function createPaneHeightObserver({ ichart, paneIndex, signals, utils }) {
callback();
}
/**
* @param {Object} args
* @param {IChartApi} args.ichart
* @param {Unit} args.unit
* @param {string} args.id
* @param {SeriesType} args.seriesType
* @param {number} args.paneIndex
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/
function createPriceScaleSelectorIfNeeded({
ichart,
unit,
paneIndex,
id,
seriesType,
signals,
utils,
}) {
const owner = signals.getOwner();
setTimeout(
() => {
const parent = ichart
?.panes()
.at(paneIndex)
?.getHTMLElement()
.children?.item(1)?.firstChild;
if (!parent) throw Error("Parent should exist");
const tagName = "fieldset";
if (parent.lastChild?.nodeName.toLowerCase() === tagName) {
return;
}
console.log(id);
const choices = /**@type {const} */ (["lin", "log"]);
/** @typedef {(typeof choices)[number]} Choices */
const serializedValue = signals.createSignal(
/** @satisfies {Choices} */ (
unit === "USD" && seriesType !== "Baseline" ? "log" : "lin"
),
{
save: {
...utils.serde.string,
keyPrefix: "",
key: `${id}-price-scale-${paneIndex}`,
},
},
);
const field = utils.dom.createHorizontalChoiceField({
title: unit,
selected: serializedValue(),
choices: choices,
id: `${id}-${unit.replace(" ", "-")}`,
signals,
});
field.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
serializedValue.set(value);
});
const element = window.document.createElement(tagName);
element.dataset.size = "xs";
element.id = `${id}-price-scale-${paneIndex}`;
element.append(field);
const mode = signals.createMemo(() => {
switch (serializedValue()) {
case "lin":
return 0;
case "log":
return 1;
}
});
const pane = ichart?.panes().at(paneIndex);
if (!pane) throw Error("Expect pane");
signals.runWithOwner(owner, () => {
signals.createEffect(mode, (mode) => {
try {
pane.priceScale("right").applyOptions({
mode,
});
} catch {}
});
});
pane.getHTMLElement().children?.item(1)?.firstChild?.appendChild(element);
},
paneIndex ? 10 : 0,
);
}

View File

@@ -40,96 +40,218 @@ export function init({
vecsResources,
});
const index_ = createIndexSelector({ elements, signals, utils });
const index = createIndexSelector({ elements, signals, utils });
let firstRun = true;
signals.createEffect(selected, (option) => {
headingElement.innerHTML = option.title;
signals.createEffect(index_, (index) => {
chart.reset({ owner: signals.getOwner() });
signals.createEffect(index, (index) => {
const { field: topUnitField, selected: topUnit } =
utils.dom.createHorizontalChoiceField({
defaultValue: "USD",
keyPrefix: "charts",
key: "unit-0",
choices: /** @type {const} */ ([
/** @satisfies {Unit} */ ("USD"),
/** @satisfies {Unit} */ ("Sats"),
]),
signals,
});
const TIMERANGE_LS_KEY = `chart-timerange-${index}`;
signals.createEffect(topUnit, (topUnit) => {
const { field: seriesTypeField, selected: topSeriesType } =
utils.dom.createHorizontalChoiceField({
defaultValue: "Candles",
keyPrefix: "charts",
key: "seriestype-0",
choices: /** @type {const} */ (["Candles", "Line"]),
signals,
});
const from = signals.createSignal(/** @type {number | null} */ (null), {
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "from",
serializeParam: firstRun,
},
});
const to = signals.createSignal(/** @type {number | null} */ (null), {
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "to",
serializeParam: firstRun,
},
});
chart.create({
index,
timeScaleSetCallback: (unknownTimeScaleCallback) => {
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
chart.inner()?.timeScale().setVisibleLogicalRange({
from: from_,
to: to_,
});
} else {
unknownTimeScaleCallback();
}
},
});
const candles = chart.addCandlestickSeries({
vecId: "ohlc",
name: "Price",
unit: "USD",
});
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return;
const last = /** @type { CandlestickData | undefined} */ (
candles.data().at(-1)
);
if (!last) return;
candles?.update({ ...last, close: latest.close });
});
[
{ blueprints: option.top, paneIndex: 0 },
{ blueprints: option.bottom, paneIndex: 1 },
].forEach(({ blueprints, paneIndex }) => {
blueprints?.forEach((blueprint) => {
const indexes = /** @type {readonly number[]} */ (
vecIdToIndexes[blueprint.key]
signals.createEffect(topSeriesType, (topSeriesType) => {
const bottomUnits = /** @type {readonly Unit[]} */ (
Object.keys(option.bottom)
);
if (indexes.includes(index)) {
chart.addLineSeries({
vecId: blueprint.key,
color: blueprint.color,
name: blueprint.title,
unit: option.unit,
defaultActive: blueprint.defaultActive,
paneIndex,
const { field: bottomUnitField, selected: bottomUnit } =
utils.dom.createHorizontalChoiceField({
defaultValue: bottomUnits.at(0) || "",
keyPrefix: "charts",
key: "unit-1",
choices: bottomUnits,
signals,
});
}
signals.createEffect(bottomUnit, (bottomUnit) => {
chart.reset({ owner: signals.getOwner() });
chart.addFieldsetIfNeeded({
id: "charts-unit-0",
paneIndex: 0,
position: "nw",
createChild() {
return topUnitField;
},
});
if (bottomUnits.length) {
chart.addFieldsetIfNeeded({
id: "charts-unit-1",
paneIndex: 1,
position: "nw",
createChild() {
return bottomUnitField;
},
});
}
chart.addFieldsetIfNeeded({
id: "charts-seriestype-0",
paneIndex: 0,
position: "ne",
createChild() {
return seriesTypeField;
},
});
const TIMERANGE_LS_KEY = `chart-timerange-${index}`;
const from = signals.createSignal(
/** @type {number | null} */ (null),
{
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "from",
serializeParam: firstRun,
},
},
);
const to = signals.createSignal(
/** @type {number | null} */ (null),
{
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "to",
serializeParam: firstRun,
},
},
);
chart.create({
index,
timeScaleSetCallback: (unknownTimeScaleCallback) => {
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
chart.inner()?.timeScale().setVisibleLogicalRange({
from: from_,
to: to_,
});
} else {
unknownTimeScaleCallback();
}
},
});
switch (topUnit) {
case "USD": {
switch (topSeriesType) {
case "Candles": {
const candles = chart.addCandlestickSeries({
vecId: "ohlc",
name: "Price",
unit: topUnit,
});
break;
}
case "Line": {
const line = chart.addLineSeries({
vecId: "close",
name: "Price",
unit: topUnit,
color: colors.default,
options: {
priceLineVisible: true,
},
});
}
}
// signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
// if (!latest) return;
// const last = /** @type { CandlestickData | undefined} */ (
// candles.data().at(-1)
// );
// if (!last) return;
// candles?.update({ ...last, close: latest.close });
// });
break;
}
case "Sats": {
switch (topSeriesType) {
case "Candles": {
const candles = chart.addCandlestickSeries({
vecId: "ohlc-in-sats",
name: "Price",
unit: topUnit,
inverse: true,
});
break;
}
case "Line": {
const line = chart.addLineSeries({
vecId: "close-in-sats",
name: "Price",
unit: topUnit,
color: colors.default,
options: {
priceLineVisible: true,
},
});
}
}
break;
}
}
[
{ blueprints: option.top, paneIndex: 0 },
{ blueprints: option.bottom, paneIndex: 1 },
].forEach(({ blueprints, paneIndex }) => {
const unit = paneIndex ? bottomUnit : topUnit;
console.log({ unit });
blueprints[unit]?.forEach((blueprint) => {
const indexes = /** @type {readonly number[]} */ (
vecIdToIndexes[blueprint.key]
);
if (indexes.includes(index)) {
chart.addLineSeries({
vecId: blueprint.key,
color: blueprint.color,
name: blueprint.title,
unit,
defaultActive: blueprint.defaultActive,
paneIndex,
});
}
});
});
chart
.inner()
?.timeScale()
.subscribeVisibleLogicalRangeChange(
utils.debounce((t) => {
from.set(t.from);
to.set(t.to);
}),
);
firstRun = false;
});
});
});
chart
.inner()
?.timeScale()
.subscribeVisibleLogicalRangeChange(
utils.debounce((t) => {
from.set(t.from);
to.set(t.to);
}),
);
firstRun = false;
});
});
}
@@ -141,48 +263,34 @@ export function init({
* @param {Utilities} args.utils
*/
function createIndexSelector({ elements, signals, utils }) {
const indexChoices = /**@type {const} */ ([
"timestamp",
"date",
"week",
// "difficulty epoch",
"month",
"quarter",
"year",
// "halving epoch",
"decade",
]);
/** @typedef {(typeof indexChoices)[number]} SerializedIndex */
const serializedIndex = /** @type {Signal<SerializedIndex>} */ (
signals.createSignal("date", {
save: {
keyPrefix: "charts",
key: "index",
...utils.serde.string,
},
})
);
const indexesField = utils.dom.createHorizontalChoiceField({
const { field, selected } = utils.dom.createHorizontalChoiceField({
title: "Index",
selected: serializedIndex(),
choices: indexChoices,
defaultValue: "date",
keyPrefix: "charts",
key: "index",
choices: /**@type {const} */ ([
"timestamp",
"date",
"week",
// "difficulty epoch",
"month",
"quarter",
"year",
// "halving epoch",
"decade",
]),
id: "index",
signals,
});
indexesField.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
serializedIndex.set(value);
});
const fieldset = window.document.createElement("fieldset");
fieldset.append(indexesField);
fieldset.append(field);
fieldset.dataset.size = "sm";
elements.charts.append(fieldset);
const index = signals.createMemo(
/** @returns {ChartableIndex} */ () => {
switch (serializedIndex()) {
switch (selected()) {
case "timestamp":
return /** @satisfies {Height} */ (0);
case "date":

View File

@@ -6,9 +6,9 @@
* @import * as _ from "../packages/ufuzzy/v1.0.14/types"
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.5-treeshaked/types"
* @import { SignalOptions } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo } from "../packages/solid-signals/v0.2.4-treeshaked/types/signals";
* @import {Signal, Signals} from "../packages/solid-signals/types";
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/owner"
* @import { createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo } from "../packages/solid-signals/v0.2.4-treeshaked/types/signals";
* @import {Addressindex, Dateindex, Decadeindex, Difficultyepoch, Index, Halvingepoch, Height, Monthindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, VecId, Weekindex, Yearindex, VecIdToIndexes, Quarterindex} from "./vecid-to-indexes"
*/
@@ -322,40 +322,62 @@ function createUtils() {
this.importStyle(href).addEventListener("load", callback);
},
/**
* @template {Readonly<string[]>} T
* @param {Object} args
* @param {string | Accessor<string>} args.title
* @param {string} args.id
* @param {Readonly<string[]>} args.choices
* @param {string} args.selected
* @param {{createEffect: CreateEffect}} args.signals
* @param {string | Accessor<string>} [args.title]
* @param {T[number]} args.defaultValue
* @param {string} [args.id]
* @param {T} args.choices
* @param {string} [args.keyPrefix]
* @param {string} args.key
* @param {{createEffect: CreateEffect, createSignal: Signals["createSignal"]}} args.signals
*/
createHorizontalChoiceField({ title, id, choices, selected, signals }) {
createHorizontalChoiceField({
title,
id,
choices,
defaultValue,
keyPrefix,
key,
signals,
}) {
/** @type {Signal<T[number]>} */
const selected = signals.createSignal(defaultValue, {
save: {
...serde.string,
keyPrefix: keyPrefix ?? "",
key,
},
});
const field = window.document.createElement("div");
field.classList.add("field");
const legend = window.document.createElement("legend");
if (typeof title === "string") {
legend.innerHTML = title;
} else {
signals.createEffect(title, (title) => {
if (title) {
const legend = window.document.createElement("legend");
if (typeof title === "string") {
legend.innerHTML = title;
});
}
field.append(legend);
} else {
signals.createEffect(title, (title) => {
legend.innerHTML = title;
});
}
field.append(legend);
const hr = window.document.createElement("hr");
field.append(hr);
const hr = window.document.createElement("hr");
field.append(hr);
}
const div = window.document.createElement("div");
field.append(div);
choices.forEach((choice) => {
const inputValue = choice.toLowerCase();
const inputValue = choice;
const { label } = this.createLabeledInput({
inputId: `${id}-${choice.toLowerCase()}`,
inputName: id,
inputId: `${id ?? key}-${choice.toLowerCase()}`,
inputName: id ?? key,
inputValue,
inputChecked: inputValue === selected,
inputChecked: inputValue === selected(),
labelTitle: choice,
type: "radio",
});
@@ -365,7 +387,13 @@ function createUtils() {
div.append(label);
});
return field;
field.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
selected.set(value);
});
return { field, selected };
},
/**
* @param {Object} args

View File

@@ -54,14 +54,14 @@
*
* @typedef {Object} LineSeriesBlueprintSpecific
* @property {"Line"} [type]
* @property {Color} color
* @property {Color} [color]
* @property {DeepPartial<LineStyleOptions & SeriesOptionsCommon>} [options]
* @property {Accessor<LineData[]>} [data]
* @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint
*
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
*
* @typedef {AnySeriesBlueprint & {key: ChartableVecId}} AnyFetchedSeriesBlueprint
* @typedef {AnySeriesBlueprint & { key: ChartableVecId }} AnyFetchedSeriesBlueprint
*
* @typedef {Object} PartialOption
* @property {string} name
@@ -73,27 +73,37 @@
*
* @typedef {Object} PartialChartOptionSpecific
* @property {"chart"} [kind]
* @property {Unit} [unit]
* @property {string} title
* @property {AnyFetchedSeriesBlueprint[]} [top]
* @property {AnyFetchedSeriesBlueprint[]} [bottom]
*
* @typedef {PartialOption & PartialChartOptionSpecific} PartialChartOption
* @typedef {Required<PartialChartOption> & ProcessedOptionAddons} ChartOption
*
* @typedef {Object} ProcessedChartOptionAddons
* @property {Record<Unit, AnyFetchedSeriesBlueprint[]>} top
* @property {Record<Unit, AnyFetchedSeriesBlueprint[]>} bottom
*
* @typedef {Required<Omit<PartialChartOption, "top" | "bottom">> & ProcessedChartOptionAddons & ProcessedOptionAddons} ChartOption
*
* @typedef {Object} PartialSimulationOptionSpecific
* @property {"simulation"} kind
* @property {string} title
*
* @typedef {PartialOption & PartialSimulationOptionSpecific} PartialSimulationOption
*
* @typedef {Required<PartialSimulationOption> & ProcessedOptionAddons} SimulationOption
*
* @typedef {Object} PartialUrlOptionSpecific
* @property {"url"} [kind]
* @property {() => string} url
* @property {boolean} [qrcode]
*
* @typedef {PartialOption & PartialUrlOptionSpecific} PartialUrlOption
*
* @typedef {Required<PartialUrlOption> & ProcessedOptionAddons} UrlOption
*
* @typedef {PartialChartOption | PartialSimulationOption | PartialUrlOption} AnyPartialOption
*
* @typedef {ChartOption | SimulationOption | UrlOption} Option
*
* @typedef {Object} PartialOptionsGroup
@@ -106,6 +116,7 @@
* @property {OptionsTree} tree
*
* @typedef {(AnyPartialOption | PartialOptionsGroup)[]} PartialOptionsTree
*
* @typedef {(Option | OptionsGroup)[]} OptionsTree
*
*/
@@ -168,33 +179,28 @@ function createPartialOptions(colors) {
*/
/**
*
* @param {Object} args
* @param {ChartableVecId} args.key
* @param {string} args.name
* @returns
*/
function createBaseSeries({ key, name }) {
return { key, title: name, color: colors.bitcoin };
return { key, title: name };
}
/**
* @template {VecIdAverageBase} T
* @param {Object} args
* @param {T} args.concat
* @param {VecIdAverageBase} args.concat
*/
function createAverageSeries({ concat }) {
return /** @satisfies {AnyFetchedSeriesBlueprint} */ ({
key: `${concat}-average`,
title: "Average",
color: colors.orange,
});
}
/**
* @template {VecIdSumBase & TotalVecIdBase} T
* @param {Object} args
* @param {T} args.concat
* @param {VecIdSumBase & TotalVecIdBase} args.concat
*/
function createSumTotalSeries({ concat }) {
return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([
@@ -213,9 +219,8 @@ function createPartialOptions(colors) {
}
/**
* @template {VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} T
* @param {Object} args
* @param {T} args.concat
* @param {VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} args.concat
*/
function createMinMaxPercentilesSeries({ concat }) {
return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([
@@ -264,28 +269,30 @@ function createPartialOptions(colors) {
]);
}
/**
* @param {Object} args
* @param {ChartableVecId & VecIdAverageBase & VecIdSumBase & TotalVecIdBase & VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} args.key
* @param {string} args.name
*/
function createBaseAverageSumTotalMinMaxPercentilesSeries({ key, name }) {
return [
createBaseSeries({
key,
name,
}),
createAverageSeries({ concat: key }),
...createSumTotalSeries({ concat: key }),
...createMinMaxPercentilesSeries({ concat: key }),
];
}
return [
{
name: "Charts",
tree: [
{
name: "Price",
tree: [
{
name: "btc/usd",
title: "Bitcoin Price in US Dollars",
},
{
name: "usd/sats",
title: "Satoshis Per US Dollar",
bottom: [
createBaseSeries({
key: "sats-per-dollar",
name: "Satoshis",
}),
],
},
],
title: "Bitcoin Price",
},
{
name: "Block",
@@ -372,26 +379,36 @@ function createPartialOptions(colors) {
name: "Subsidy",
title: "Subsidy",
bottom: [
createBaseSeries({
...createBaseAverageSumTotalMinMaxPercentilesSeries({
key: "subsidy",
name: "Subsidy",
}),
createAverageSeries({ concat: "subsidy" }),
...createSumTotalSeries({ concat: "subsidy" }),
...createMinMaxPercentilesSeries({ concat: "subsidy" }),
...createBaseAverageSumTotalMinMaxPercentilesSeries({
key: "subsidy-in-btc",
name: "Subsidy",
}),
...createBaseAverageSumTotalMinMaxPercentilesSeries({
key: "subsidy-in-usd",
name: "Subsidy",
}),
],
},
{
name: "Coinbase",
title: "Coinbase",
bottom: [
createBaseSeries({
...createBaseAverageSumTotalMinMaxPercentilesSeries({
key: "coinbase",
name: "Coinbase",
}),
createAverageSeries({ concat: "coinbase" }),
...createSumTotalSeries({ concat: "coinbase" }),
...createMinMaxPercentilesSeries({ concat: "coinbase" }),
...createBaseAverageSumTotalMinMaxPercentilesSeries({
key: "coinbase-in-btc",
name: "Coinbase",
}),
...createBaseAverageSumTotalMinMaxPercentilesSeries({
key: "coinbase-in-usd",
name: "Coinbase",
}),
],
},
{
@@ -652,6 +669,53 @@ export function initOptions({
/** @type {string[] | undefined} */
const optionsIds = env.localhost ? [] : undefined;
/**
* @param {AnyFetchedSeriesBlueprint[]} [arr]
* @param {string} id
*/
function arrayToRecord(id, arr = []) {
return (arr || []).reduce((record, blueprint) => {
const key = blueprint.key;
/** @type {Unit} */
let unit;
if (key.includes("interval")) {
unit = "Seconds";
} else if (key.includes("feerate")) {
unit = "sat/vB";
} else if (key.includes("in-usd")) {
unit = "USD";
} else if (key.includes("in-btc")) {
unit = "BTC";
} else if (
key.includes("-in-sats") ||
key.startsWith("sats-") ||
key.includes("input-value") ||
key.includes("output-value") ||
key.includes("fee") ||
key.includes("coinbase") ||
key.includes("subsidy")
) {
unit = "Sats";
} else if (key.includes("count")) {
unit = "Count";
} else if (key.includes("-size")) {
unit = "Megabytes";
} else if (key.includes("weight")) {
unit = "Weight Units";
} else if (key.includes("vbytes") || key.includes("vsize")) {
unit = "Virtual Bytes";
} else if (key.match(/v[1-3]/g)) {
unit = "Version";
} else {
console.log([id, key]);
throw Error("Unit not set");
}
record[unit] ??= [];
record[unit].push(blueprint);
return record;
}, /** @type {Record<Unit, AnyFetchedSeriesBlueprint[]>} */ ({}));
}
/**
* @param {Object} args
* @param {Option} args.option
@@ -849,6 +913,9 @@ export function initOptions({
}
createRenderLiEffect();
} else {
/** @type {Option} */
let option;
/** @type {string} */
let id;
/** @type {Option["kind"]} */
@@ -857,63 +924,37 @@ export function initOptions({
let title;
if ("kind" in anyPartial && anyPartial.kind === "simulation") {
// Simulation
kind = anyPartial.kind;
id = anyPartial.kind;
title = anyPartial.title;
option = /** @satisfies {SimulationOption} */ ({
kind: "simulation",
id: anyPartial.kind,
name: anyPartial.name,
path: path || [],
title: anyPartial.title,
});
} else if ("url" in anyPartial) {
// Url
kind = "url";
id = `${utils.stringToId(anyPartial.name)}-url`;
title = anyPartial.name;
option = /** @satisfies {UrlOption} */ ({
kind: "url",
id: `${utils.stringToId(anyPartial.name)}-url`,
name: anyPartial.name,
path: path || [],
title: anyPartial.name,
qrcode: !!anyPartial.qrcode,
url: anyPartial.url,
});
} else {
// Chart
kind = "chart";
title = anyPartial.title || anyPartial.name;
id = `${kind}-${utils.stringToId(title)}`;
const key = anyPartial.bottom?.at(0)?.key;
if (key) {
if (key.includes("interval")) {
anyPartial.unit = "Seconds";
} else if (key.includes("feerate")) {
anyPartial.unit = "sat/vB";
} else if (
key.startsWith("sats-") ||
key.includes("input-value") ||
key.includes("output-value") ||
key.includes("fee") ||
key.startsWith("coinbase") ||
key.startsWith("subsidy")
) {
anyPartial.unit = "Sats";
} else if (key.includes("count")) {
anyPartial.unit = "Count";
} else if (key.includes("-size")) {
anyPartial.unit = "Megabytes";
} else if (key.includes("weight")) {
anyPartial.unit = "Weight Units";
} else if (key.includes("vbytes") || key.includes("vsize")) {
anyPartial.unit = "Virtual Bytes";
} else if (key.match(/v[1-3]/g)) {
anyPartial.unit = "Version";
} else {
console.log([key, anyPartial]);
throw Error("Unit not set");
}
}
const title = anyPartial.title || anyPartial.name;
const id = `chart-${utils.stringToId(title)}`;
option = /** @satisfies {ChartOption} */ ({
kind: "chart",
id,
name: anyPartial.name,
title,
path: path || [],
top: arrayToRecord(id, anyPartial.top),
bottom: arrayToRecord(id, anyPartial.bottom),
});
}
/** @type {ProcessedOptionAddons} */
const optionAddons = {
id,
path: path || [],
title,
};
Object.assign(anyPartial, optionAddons, { kind });
const option = /** @type {Option} */ (anyPartial);
if (urlSelected === option.id) {
selected.set(option);
} else if (!selected() && savedSelectedId === option.id) {

View File

@@ -78,6 +78,7 @@ export function createVecIdToIndexes() {
blockhash: [Height],
close: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"close-in-cents": [Dateindex, Height],
"close-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
coinbase: [Height],
"coinbase-10p": [Dateindex],
"coinbase-25p": [Dateindex],
@@ -178,6 +179,7 @@ export function createVecIdToIndexes() {
height: [Addressindex, Height, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, Emptyindex, Multisigindex, Opreturnindex, Pushonlyindex, Unknownindex],
high: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"high-in-cents": [Dateindex, Height],
"high-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"input-count": [Txindex],
"input-count-10p": [Height],
"input-count-25p": [Height],
@@ -204,11 +206,14 @@ export function createVecIdToIndexes() {
locktime: [Txindex],
low: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"low-in-cents": [Dateindex, Height],
"low-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
monthindex: [Dateindex, Monthindex],
ohlc: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"ohlc-in-cents": [Dateindex, Height],
"ohlc-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
open: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"open-in-cents": [Dateindex, Height],
"open-in-sats": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"output-count": [Txindex],
"output-count-10p": [Height],
"output-count-25p": [Height],
@@ -231,7 +236,6 @@ export function createVecIdToIndexes() {
p2wshaddressbytes: [P2WSHindex],
quarterindex: [Monthindex, Quarterindex],
"real-date": [Height],
"sats-per-dollar": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
subsidy: [Height],
"subsidy-10p": [Dateindex],
"subsidy-25p": [Dateindex],