mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
computer + kibo: part 13
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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)),
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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")?),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user