mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 14:11:56 -07:00
global: snapshot
This commit is contained in:
@@ -81,7 +81,7 @@ function indexToDate(index, i) {{
|
||||
case 'hour4': return new Date(_EPOCH_MS + i * 14400000);
|
||||
case 'hour12': return new Date(_EPOCH_MS + i * 43200000);
|
||||
case 'day1': return i === 0 ? _GENESIS : new Date(_DAY_ONE.getTime() + (i - 1) * _MS_PER_DAY);
|
||||
case 'day3': return new Date(_EPOCH_MS + i * 259200000);
|
||||
case 'day3': return new Date(_EPOCH_MS - 86400000 + i * 259200000);
|
||||
case 'week1': return new Date(_GENESIS.getTime() + i * _MS_PER_WEEK);
|
||||
case 'month1': return _addMonths(i);
|
||||
case 'month3': return _addMonths(i * 3);
|
||||
@@ -113,7 +113,7 @@ function dateToIndex(index, d) {{
|
||||
if (ms < _DAY_ONE.getTime()) return 0;
|
||||
return 1 + Math.floor((ms - _DAY_ONE.getTime()) / _MS_PER_DAY);
|
||||
}}
|
||||
case 'day3': return Math.floor((ms - _EPOCH_MS) / 259200000);
|
||||
case 'day3': return Math.floor((ms - _EPOCH_MS + 86400000) / 259200000);
|
||||
case 'week1': return Math.floor((ms - _GENESIS.getTime()) / _MS_PER_WEEK);
|
||||
case 'month1': return (d.getFullYear() - 2009) * 12 + d.getMonth();
|
||||
case 'month3': return (d.getFullYear() - 2009) * 4 + Math.floor(d.getMonth() / 3);
|
||||
|
||||
@@ -162,7 +162,7 @@ def _index_to_date(index: str, i: int) -> Union[date, datetime]:
|
||||
elif index == 'day1':
|
||||
return _GENESIS if i == 0 else _DAY_ONE + timedelta(days=i - 1)
|
||||
elif index == 'day3':
|
||||
return _EPOCH.date() + timedelta(days=i * 3)
|
||||
return _EPOCH.date() - timedelta(days=1) + timedelta(days=i * 3)
|
||||
elif index == 'week1':
|
||||
return _GENESIS + timedelta(weeks=i)
|
||||
elif index == 'month1':
|
||||
@@ -202,7 +202,7 @@ def _date_to_index(index: str, d: Union[date, datetime]) -> int:
|
||||
return 0
|
||||
return 1 + (dd - _DAY_ONE).days
|
||||
elif index == 'day3':
|
||||
return (dd - date(2009, 1, 1)).days // 3
|
||||
return (dd - date(2008, 12, 31)).days // 3
|
||||
elif index == 'week1':
|
||||
return (dd - _GENESIS).days // 7
|
||||
elif index == 'month1':
|
||||
|
||||
+27
-103
@@ -2101,52 +2101,6 @@ impl GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<T> {
|
||||
pub day1: MetricPattern10<T>,
|
||||
pub day3: MetricPattern11<T>,
|
||||
pub difficultyepoch: MetricPattern19<T>,
|
||||
pub halvingepoch: MetricPattern18<T>,
|
||||
pub hour1: MetricPattern7<T>,
|
||||
pub hour12: MetricPattern9<T>,
|
||||
pub hour4: MetricPattern8<T>,
|
||||
pub minute1: MetricPattern3<T>,
|
||||
pub minute10: MetricPattern5<T>,
|
||||
pub minute30: MetricPattern6<T>,
|
||||
pub minute5: MetricPattern4<T>,
|
||||
pub month1: MetricPattern13<T>,
|
||||
pub month3: MetricPattern14<T>,
|
||||
pub month6: MetricPattern15<T>,
|
||||
pub week1: MetricPattern12<T>,
|
||||
pub year1: MetricPattern16<T>,
|
||||
pub year10: MetricPattern17<T>,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<T> {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
day1: MetricPattern10::new(client.clone(), _m(&acc, "day1")),
|
||||
day3: MetricPattern11::new(client.clone(), _m(&acc, "day3")),
|
||||
difficultyepoch: MetricPattern19::new(client.clone(), _m(&acc, "difficultyepoch")),
|
||||
halvingepoch: MetricPattern18::new(client.clone(), _m(&acc, "halvingepoch")),
|
||||
hour1: MetricPattern7::new(client.clone(), _m(&acc, "hour1")),
|
||||
hour12: MetricPattern9::new(client.clone(), _m(&acc, "hour12")),
|
||||
hour4: MetricPattern8::new(client.clone(), _m(&acc, "hour4")),
|
||||
minute1: MetricPattern3::new(client.clone(), _m(&acc, "minute1")),
|
||||
minute10: MetricPattern5::new(client.clone(), _m(&acc, "minute10")),
|
||||
minute30: MetricPattern6::new(client.clone(), _m(&acc, "minute30")),
|
||||
minute5: MetricPattern4::new(client.clone(), _m(&acc, "minute5")),
|
||||
month1: MetricPattern13::new(client.clone(), _m(&acc, "month1")),
|
||||
month3: MetricPattern14::new(client.clone(), _m(&acc, "month3")),
|
||||
month6: MetricPattern15::new(client.clone(), _m(&acc, "month6")),
|
||||
week1: MetricPattern12::new(client.clone(), _m(&acc, "week1")),
|
||||
year1: MetricPattern16::new(client.clone(), _m(&acc, "year1")),
|
||||
year10: MetricPattern17::new(client.clone(), _m(&acc, "year10")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern {
|
||||
pub greed_index: MetricPattern1<Dollars>,
|
||||
@@ -3093,18 +3047,18 @@ impl BtcSatsUsdPattern {
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CentsSatsUsdPattern {
|
||||
pub cents: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<OHLCCents>,
|
||||
pub sats: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<OHLCSats>,
|
||||
pub usd: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<OHLCDollars>,
|
||||
pub cents: MetricPattern2<Cents>,
|
||||
pub sats: MetricPattern2<Sats>,
|
||||
pub usd: MetricPattern2<Dollars>,
|
||||
}
|
||||
|
||||
impl CentsSatsUsdPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
cents: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern::new(client.clone(), _m(&acc, "cents")),
|
||||
sats: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern::new(client.clone(), _m(&acc, "sats")),
|
||||
usd: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern::new(client.clone(), acc.clone()),
|
||||
cents: MetricPattern2::new(client.clone(), _m(&acc, "cents")),
|
||||
sats: MetricPattern2::new(client.clone(), _m(&acc, "sats")),
|
||||
usd: MetricPattern2::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3390,7 +3344,7 @@ impl MetricsTree_Blocks_Difficulty {
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Blocks_Time {
|
||||
pub timestamp: MetricsTree_Blocks_Time_Timestamp,
|
||||
pub timestamp: MetricPattern1<Timestamp>,
|
||||
pub date: MetricPattern20<Date>,
|
||||
pub timestamp_monotonic: MetricPattern20<Timestamp>,
|
||||
}
|
||||
@@ -3398,60 +3352,13 @@ pub struct MetricsTree_Blocks_Time {
|
||||
impl MetricsTree_Blocks_Time {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
timestamp: MetricsTree_Blocks_Time_Timestamp::new(client.clone(), format!("{base_path}_timestamp")),
|
||||
timestamp: MetricPattern1::new(client.clone(), "timestamp".to_string()),
|
||||
date: MetricPattern20::new(client.clone(), "date".to_string()),
|
||||
timestamp_monotonic: MetricPattern20::new(client.clone(), "timestamp_monotonic".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Blocks_Time_Timestamp {
|
||||
pub base: MetricPattern20<Timestamp>,
|
||||
pub minute1: MetricPattern3<Timestamp>,
|
||||
pub minute5: MetricPattern4<Timestamp>,
|
||||
pub minute10: MetricPattern5<Timestamp>,
|
||||
pub minute30: MetricPattern6<Timestamp>,
|
||||
pub hour1: MetricPattern7<Timestamp>,
|
||||
pub hour4: MetricPattern8<Timestamp>,
|
||||
pub hour12: MetricPattern9<Timestamp>,
|
||||
pub day1: MetricPattern10<Timestamp>,
|
||||
pub day3: MetricPattern11<Timestamp>,
|
||||
pub week1: MetricPattern12<Timestamp>,
|
||||
pub month1: MetricPattern13<Timestamp>,
|
||||
pub month3: MetricPattern14<Timestamp>,
|
||||
pub month6: MetricPattern15<Timestamp>,
|
||||
pub year1: MetricPattern16<Timestamp>,
|
||||
pub year10: MetricPattern17<Timestamp>,
|
||||
pub halvingepoch: MetricPattern18<Timestamp>,
|
||||
pub difficultyepoch: MetricPattern19<Timestamp>,
|
||||
}
|
||||
|
||||
impl MetricsTree_Blocks_Time_Timestamp {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
base: MetricPattern20::new(client.clone(), "timestamp".to_string()),
|
||||
minute1: MetricPattern3::new(client.clone(), "timestamp_minute1".to_string()),
|
||||
minute5: MetricPattern4::new(client.clone(), "timestamp_minute5".to_string()),
|
||||
minute10: MetricPattern5::new(client.clone(), "timestamp_minute10".to_string()),
|
||||
minute30: MetricPattern6::new(client.clone(), "timestamp_minute30".to_string()),
|
||||
hour1: MetricPattern7::new(client.clone(), "timestamp_hour1".to_string()),
|
||||
hour4: MetricPattern8::new(client.clone(), "timestamp_hour4".to_string()),
|
||||
hour12: MetricPattern9::new(client.clone(), "timestamp_hour12".to_string()),
|
||||
day1: MetricPattern10::new(client.clone(), "timestamp_day1".to_string()),
|
||||
day3: MetricPattern11::new(client.clone(), "timestamp_day3".to_string()),
|
||||
week1: MetricPattern12::new(client.clone(), "timestamp_week1".to_string()),
|
||||
month1: MetricPattern13::new(client.clone(), "timestamp_month1".to_string()),
|
||||
month3: MetricPattern14::new(client.clone(), "timestamp_month3".to_string()),
|
||||
month6: MetricPattern15::new(client.clone(), "timestamp_month6".to_string()),
|
||||
year1: MetricPattern16::new(client.clone(), "timestamp_year1".to_string()),
|
||||
year10: MetricPattern17::new(client.clone(), "timestamp_year10".to_string()),
|
||||
halvingepoch: MetricPattern18::new(client.clone(), "timestamp_halvingepoch".to_string()),
|
||||
difficultyepoch: MetricPattern19::new(client.clone(), "timestamp_difficultyepoch".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Blocks_Weight {
|
||||
pub base: MetricPattern20<Weight>,
|
||||
@@ -6039,7 +5946,7 @@ impl MetricsTree_Pools_Vecs {
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Prices {
|
||||
pub split: MetricsTree_Prices_Split,
|
||||
pub ohlc: CentsSatsUsdPattern,
|
||||
pub ohlc: MetricsTree_Prices_Ohlc,
|
||||
pub price: MetricsTree_Prices_Price,
|
||||
}
|
||||
|
||||
@@ -6047,7 +5954,7 @@ impl MetricsTree_Prices {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
split: MetricsTree_Prices_Split::new(client.clone(), format!("{base_path}_split")),
|
||||
ohlc: CentsSatsUsdPattern::new(client.clone(), "price_ohlc".to_string()),
|
||||
ohlc: MetricsTree_Prices_Ohlc::new(client.clone(), format!("{base_path}_ohlc")),
|
||||
price: MetricsTree_Prices_Price::new(client.clone(), format!("{base_path}_price")),
|
||||
}
|
||||
}
|
||||
@@ -6089,6 +5996,23 @@ impl MetricsTree_Prices_Split_Close {
|
||||
}
|
||||
}
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Prices_Ohlc {
|
||||
pub cents: MetricPattern2<OHLCCents>,
|
||||
pub usd: MetricPattern2<OHLCDollars>,
|
||||
pub sats: MetricPattern2<OHLCSats>,
|
||||
}
|
||||
|
||||
impl MetricsTree_Prices_Ohlc {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
cents: MetricPattern2::new(client.clone(), "price_ohlc_cents".to_string()),
|
||||
usd: MetricPattern2::new(client.clone(), "price_ohlc".to_string()),
|
||||
sats: MetricPattern2::new(client.clone(), "price_ohlc_sats".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Prices_Price {
|
||||
pub cents: MetricPattern20<Cents>,
|
||||
|
||||
@@ -14,12 +14,49 @@ impl Vecs {
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.time.timestamp.compute_first(
|
||||
starting_indexes,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
indexes,
|
||||
exit,
|
||||
)?;
|
||||
{
|
||||
let ts = &mut self.time.timestamp;
|
||||
|
||||
macro_rules! period {
|
||||
($field:ident) => {
|
||||
ts.$field.compute_transform(
|
||||
starting_indexes.$field,
|
||||
&indexes.$field.first_height,
|
||||
|(idx, _, _)| (idx, idx.to_timestamp()),
|
||||
exit,
|
||||
)?;
|
||||
};
|
||||
}
|
||||
|
||||
period!(minute1);
|
||||
period!(minute5);
|
||||
period!(minute10);
|
||||
period!(minute30);
|
||||
period!(hour1);
|
||||
period!(hour4);
|
||||
period!(hour12);
|
||||
period!(day1);
|
||||
period!(day3);
|
||||
period!(week1);
|
||||
period!(month1);
|
||||
period!(month3);
|
||||
period!(month6);
|
||||
period!(year1);
|
||||
period!(year10);
|
||||
|
||||
ts.halvingepoch.compute_indirect(
|
||||
starting_indexes.halvingepoch,
|
||||
&indexes.halvingepoch.first_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
exit,
|
||||
)?;
|
||||
ts.difficultyepoch.compute_indirect(
|
||||
starting_indexes.difficultyepoch,
|
||||
&indexes.difficultyepoch.first_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
self.count
|
||||
.compute(indexer, &self.time, starting_indexes, exit)?;
|
||||
self.interval
|
||||
|
||||
@@ -204,7 +204,7 @@ pub(crate) fn process_blocks(
|
||||
debug!("AddressCache created, entering main loop");
|
||||
|
||||
// Cache for day1 lookups - same day1 repeats ~140 times per day
|
||||
let mut cached_day1 = Day1::default();
|
||||
let mut cached_day1: Option<Day1> = None;
|
||||
let mut cached_date_first_height = Height::ZERO;
|
||||
let mut cached_date_height_count = StoredU64::default();
|
||||
|
||||
@@ -428,12 +428,12 @@ pub(crate) fn process_blocks(
|
||||
// avoiding redundant PcoVec page decompressions.
|
||||
let date = height_to_date_vec[offset];
|
||||
let day1 = Day1::try_from(date).unwrap();
|
||||
let (date_first_height, date_height_count) = if day1 == cached_day1 {
|
||||
let (date_first_height, date_height_count) = if cached_day1 == Some(day1) {
|
||||
(cached_date_first_height, cached_date_height_count)
|
||||
} else {
|
||||
let fh: Height = day1_to_first_height.collect_one(day1).unwrap();
|
||||
let hc = day1_to_height_count.collect_one(day1).unwrap();
|
||||
cached_day1 = day1;
|
||||
cached_day1 = Some(day1);
|
||||
cached_date_first_height = fh;
|
||||
cached_date_height_count = hc;
|
||||
(fh, hc)
|
||||
|
||||
@@ -13,7 +13,8 @@ use brk_types::{
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{
|
||||
Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
|
||||
AnyVec, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode,
|
||||
VecIndex, WritableVec,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -59,7 +60,7 @@ where
|
||||
|
||||
macro_rules! period {
|
||||
($idx:ident) => {
|
||||
ImportableVec::forced_import(db, &format!("{name}_{}", stringify!($idx)), v)?
|
||||
ImportableVec::forced_import(db, name, v)?
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,15 +77,10 @@ where
|
||||
) -> Result<()> {
|
||||
macro_rules! period {
|
||||
($field:ident) => {
|
||||
self.0.$field.compute_transform(
|
||||
self.0.$field.compute_indirect(
|
||||
starting_indexes.$field,
|
||||
&indexes.$field.first_height,
|
||||
|(idx, first_h, _)| {
|
||||
let v = height_source
|
||||
.collect_one(first_h)
|
||||
.unwrap_or_else(|| T::from(0_usize));
|
||||
(idx, v)
|
||||
},
|
||||
height_source,
|
||||
exit,
|
||||
)?;
|
||||
};
|
||||
@@ -122,25 +118,17 @@ where
|
||||
let src_len = height_source.len();
|
||||
|
||||
macro_rules! period {
|
||||
($field:ident) => {{
|
||||
let fh = &indexes.$field.first_height;
|
||||
self.0.$field.compute_transform(
|
||||
($field:ident) => {
|
||||
compute_period_extremum(
|
||||
&mut self.0.$field,
|
||||
starting_indexes.$field,
|
||||
fh,
|
||||
|(idx, first_h, _)| {
|
||||
let end_h = Height::from(
|
||||
fh.collect_one_at(idx.to_usize() + 1)
|
||||
.map(|h: Height| h.to_usize())
|
||||
.unwrap_or(src_len),
|
||||
);
|
||||
let v = height_source
|
||||
.max(first_h, end_h)
|
||||
.unwrap_or_else(|| T::from(0_usize));
|
||||
(idx, v)
|
||||
},
|
||||
&indexes.$field.first_height,
|
||||
height_source,
|
||||
src_len,
|
||||
T::max,
|
||||
exit,
|
||||
)?;
|
||||
}};
|
||||
};
|
||||
}
|
||||
|
||||
period!(minute1);
|
||||
@@ -175,25 +163,17 @@ where
|
||||
let src_len = height_source.len();
|
||||
|
||||
macro_rules! period {
|
||||
($field:ident) => {{
|
||||
let fh = &indexes.$field.first_height;
|
||||
self.0.$field.compute_transform(
|
||||
($field:ident) => {
|
||||
compute_period_extremum(
|
||||
&mut self.0.$field,
|
||||
starting_indexes.$field,
|
||||
fh,
|
||||
|(idx, first_h, _)| {
|
||||
let end_h = Height::from(
|
||||
fh.collect_one_at(idx.to_usize() + 1)
|
||||
.map(|h: Height| h.to_usize())
|
||||
.unwrap_or(src_len),
|
||||
);
|
||||
let v = height_source
|
||||
.min(first_h, end_h)
|
||||
.unwrap_or_else(|| T::from(0_usize));
|
||||
(idx, v)
|
||||
},
|
||||
&indexes.$field.first_height,
|
||||
height_source,
|
||||
src_len,
|
||||
T::min,
|
||||
exit,
|
||||
)?;
|
||||
}};
|
||||
};
|
||||
}
|
||||
|
||||
period!(minute1);
|
||||
@@ -217,3 +197,60 @@ where
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute per-period extremum (max or min) of height_source values.
|
||||
///
|
||||
/// Each period's range is `[fh[i]..fh[i+1])` of height_source.
|
||||
/// Uses a cursor on height_source so each page is decompressed at most once.
|
||||
fn compute_period_extremum<I: VecIndex, T: ComputedVecValue + JsonSchema>(
|
||||
out: &mut EagerVec<PcoVec<I, T>>,
|
||||
starting_index: I,
|
||||
fh: &impl ReadableVec<I, Height>,
|
||||
height_source: &impl ReadableVec<Height, T>,
|
||||
src_len: usize,
|
||||
better: fn(T, T) -> T,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
out.validate_and_truncate(fh.version() + height_source.version(), starting_index)?;
|
||||
let mut cursor = height_source.cursor();
|
||||
Ok(out.repeat_until_complete(exit, |this| {
|
||||
let skip = this.len();
|
||||
let end = fh.len();
|
||||
if skip >= end {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let fh_batch: Vec<Height> = fh.collect_range_at(skip, (end + 1).min(fh.len()));
|
||||
|
||||
if cursor.position() < fh_batch[0].to_usize() {
|
||||
cursor.advance(fh_batch[0].to_usize() - cursor.position());
|
||||
}
|
||||
|
||||
for j in 0..(end - skip) {
|
||||
let first_h = fh_batch[j].to_usize();
|
||||
let end_h = fh_batch.get(j + 1).map_or(src_len, |h| h.to_usize());
|
||||
|
||||
if cursor.position() < first_h {
|
||||
cursor.advance(first_h - cursor.position());
|
||||
}
|
||||
|
||||
let range_len = end_h.saturating_sub(first_h);
|
||||
let v = if range_len > 0 {
|
||||
cursor
|
||||
.fold(range_len, None, |acc, b| {
|
||||
Some(match acc {
|
||||
Some(a) => better(a, b),
|
||||
None => b,
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| T::from(0_usize))
|
||||
} else {
|
||||
T::from(0_usize)
|
||||
};
|
||||
|
||||
this.checked_push_at(skip + j, v)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ where
|
||||
macro_rules! period {
|
||||
($idx:ident) => {
|
||||
LazyVecFrom1::transformed::<Transform>(
|
||||
&format!("{name}_{}", stringify!($idx)),
|
||||
name,
|
||||
version,
|
||||
source.$idx.read_only_boxed_clone(),
|
||||
)
|
||||
|
||||
@@ -65,10 +65,17 @@ impl Computer {
|
||||
let i = Instant::now();
|
||||
let (indexes, positions) = thread::scope(|s| -> Result<_> {
|
||||
let positions_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(positions::Vecs::forced_import(&computed_path, VERSION)?))
|
||||
Ok(Box::new(positions::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let indexes = Box::new(indexes::Vecs::forced_import(&computed_path, VERSION, indexer)?);
|
||||
let indexes = Box::new(indexes::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
indexer,
|
||||
)?);
|
||||
let positions = positions_handle.join().unwrap()?;
|
||||
|
||||
Ok((indexes, positions))
|
||||
@@ -79,11 +86,19 @@ impl Computer {
|
||||
let i = Instant::now();
|
||||
let (inputs, outputs) = thread::scope(|s| -> Result<_> {
|
||||
let inputs_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(inputs::Vecs::forced_import(&computed_path, VERSION, &indexes)?))
|
||||
Ok(Box::new(inputs::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let outputs_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(outputs::Vecs::forced_import(&computed_path, VERSION, &indexes)?))
|
||||
Ok(Box::new(outputs::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let inputs = inputs_handle.join().unwrap()?;
|
||||
@@ -96,7 +111,11 @@ impl Computer {
|
||||
let i = Instant::now();
|
||||
let constants = Box::new(constants::Vecs::new(VERSION, &indexes));
|
||||
// Price must be created before market since market's lazy vecs reference price
|
||||
let prices = Box::new(prices::Vecs::forced_import(&computed_path, VERSION, &indexes)?);
|
||||
let prices = Box::new(prices::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?);
|
||||
info!("Imported price/constants in {:?}", i.elapsed());
|
||||
|
||||
let i = Instant::now();
|
||||
@@ -104,12 +123,21 @@ impl Computer {
|
||||
thread::scope(|s| -> Result<_> {
|
||||
// Import blocks module (no longer needs prices)
|
||||
let blocks_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(blocks::Vecs::forced_import(&computed_path, VERSION, indexer, &indexes)?))
|
||||
Ok(Box::new(blocks::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
indexer,
|
||||
&indexes,
|
||||
)?))
|
||||
})?;
|
||||
|
||||
// Import mining module (separate database)
|
||||
let mining_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(mining::Vecs::forced_import(&computed_path, VERSION, &indexes)?))
|
||||
Ok(Box::new(mining::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?))
|
||||
})?;
|
||||
|
||||
// Import transactions module
|
||||
@@ -130,9 +158,11 @@ impl Computer {
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let cointime = Box::new(
|
||||
cointime::Vecs::forced_import(&computed_path, VERSION, &indexes)?
|
||||
);
|
||||
let cointime = Box::new(cointime::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?);
|
||||
|
||||
let blocks = blocks_handle.join().unwrap()?;
|
||||
let mining = mining_handle.join().unwrap()?;
|
||||
@@ -154,16 +184,21 @@ impl Computer {
|
||||
|
||||
// Threads inside
|
||||
let i = Instant::now();
|
||||
let distribution = Box::new(
|
||||
distribution::Vecs::forced_import(&computed_path, VERSION, &indexes)?
|
||||
);
|
||||
let distribution = Box::new(distribution::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?);
|
||||
info!("Imported distribution in {:?}", i.elapsed());
|
||||
|
||||
// Supply must be imported after distribution (references distribution's supply)
|
||||
let i = Instant::now();
|
||||
let supply = Box::new(
|
||||
supply::Vecs::forced_import(&computed_path, VERSION, &indexes, &distribution)?
|
||||
);
|
||||
let supply = Box::new(supply::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
&distribution,
|
||||
)?);
|
||||
info!("Imported supply in {:?}", i.elapsed());
|
||||
|
||||
let i = Instant::now();
|
||||
@@ -286,14 +321,25 @@ impl Computer {
|
||||
// Inputs → scripts → outputs (sequential)
|
||||
info!("Computing inputs...");
|
||||
let i = Instant::now();
|
||||
self.inputs
|
||||
.compute(indexer, &self.indexes, &self.blocks, &starting_indexes, exit)?;
|
||||
self.inputs.compute(
|
||||
indexer,
|
||||
&self.indexes,
|
||||
&self.blocks,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
info!("Computed inputs in {:?}", i.elapsed());
|
||||
|
||||
info!("Computing scripts...");
|
||||
let i = Instant::now();
|
||||
self.scripts
|
||||
.compute(indexer, &self.blocks, &self.outputs, &self.prices, &starting_indexes, exit)?;
|
||||
self.scripts.compute(
|
||||
indexer,
|
||||
&self.blocks,
|
||||
&self.outputs,
|
||||
&self.prices,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
info!("Computed scripts in {:?}", i.elapsed());
|
||||
|
||||
info!("Computing outputs...");
|
||||
|
||||
@@ -37,7 +37,6 @@ impl Vecs {
|
||||
&self.split.high.cents,
|
||||
&self.split.low.cents,
|
||||
&self.split.close.cents,
|
||||
indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -10,18 +10,18 @@ use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use vecdb::{
|
||||
BytesVec, BytesVecValue, Database, EagerVec, Exit, Formattable, ImportableVec, LazyVecFrom1,
|
||||
ReadableCloneableVec, ReadableVec, Rw, StorageMode, UnaryTransform,
|
||||
ReadableCloneableVec, Rw, StorageMode, UnaryTransform,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, indexes, indexes_from,
|
||||
ComputeIndexes, indexes_from,
|
||||
internal::{ComputedHeightDerivedLast, EagerIndexes, Indexes},
|
||||
};
|
||||
|
||||
// ── EagerOhlcIndexes ─────────────────────────────────────────────────
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
#[traversable(merge)]
|
||||
pub struct OhlcVecs<T, M: StorageMode = Rw>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub Indexes<
|
||||
@@ -58,7 +58,7 @@ where
|
||||
|
||||
macro_rules! period {
|
||||
($idx:ident) => {
|
||||
ImportableVec::forced_import(db, &format!("{name}_{}", stringify!($idx)), v)?
|
||||
ImportableVec::forced_import(db, name, v)?
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ where
|
||||
}
|
||||
|
||||
impl OhlcVecs<OHLCCents> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_from_split(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
@@ -75,26 +74,24 @@ impl OhlcVecs<OHLCCents> {
|
||||
high: &EagerIndexes<Cents>,
|
||||
low: &EagerIndexes<Cents>,
|
||||
close: &ComputedHeightDerivedLast<Cents>,
|
||||
indexes: &indexes::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
macro_rules! period {
|
||||
($field:ident) => {
|
||||
self.0.$field.compute_transform(
|
||||
self.0.$field.compute_transform4(
|
||||
starting_indexes.$field,
|
||||
&indexes.$field.first_height,
|
||||
|(idx, _first_h, _)| {
|
||||
let o = open.$field.collect_one(idx).unwrap_or_default();
|
||||
let h = high.$field.collect_one(idx).unwrap_or_default();
|
||||
let l = low.$field.collect_one(idx).unwrap_or_default();
|
||||
let c = close.$field.collect_one(idx).flatten().unwrap_or_default();
|
||||
&open.$field,
|
||||
&high.$field,
|
||||
&low.$field,
|
||||
&close.$field,
|
||||
|(idx, o, h, l, c, _)| {
|
||||
(
|
||||
idx,
|
||||
OHLCCents {
|
||||
open: Open::new(o),
|
||||
high: High::new(h),
|
||||
low: Low::new(l),
|
||||
close: Close::new(c),
|
||||
close: Close::new(c.unwrap_or_default()),
|
||||
},
|
||||
)
|
||||
},
|
||||
@@ -105,14 +102,13 @@ impl OhlcVecs<OHLCCents> {
|
||||
|
||||
macro_rules! epoch {
|
||||
($field:ident) => {
|
||||
self.0.$field.compute_transform(
|
||||
self.0.$field.compute_transform4(
|
||||
starting_indexes.$field,
|
||||
&indexes.$field.first_height,
|
||||
|(idx, _first_h, _)| {
|
||||
let o = open.$field.collect_one(idx).unwrap_or_default();
|
||||
let h = high.$field.collect_one(idx).unwrap_or_default();
|
||||
let l = low.$field.collect_one(idx).unwrap_or_default();
|
||||
let c = close.$field.collect_one(idx).unwrap_or_default();
|
||||
&open.$field,
|
||||
&high.$field,
|
||||
&low.$field,
|
||||
&close.$field,
|
||||
|(idx, o, h, l, c, _)| {
|
||||
(
|
||||
idx,
|
||||
OHLCCents {
|
||||
@@ -153,7 +149,7 @@ impl OhlcVecs<OHLCCents> {
|
||||
// ── LazyOhlcIndexes ──────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
#[traversable(merge)]
|
||||
pub struct LazyOhlcVecs<T, S>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub Indexes<
|
||||
@@ -193,7 +189,7 @@ where
|
||||
macro_rules! period {
|
||||
($idx:ident) => {
|
||||
LazyVecFrom1::transformed::<Transform>(
|
||||
&format!("{name}_{}", stringify!($idx)),
|
||||
name,
|
||||
version,
|
||||
source.$idx.read_only_boxed_clone(),
|
||||
)
|
||||
|
||||
@@ -14,10 +14,8 @@ use super::{Day1, Year10, Month1, Month3, Month6, Timestamp, Week1, Year1};
|
||||
pub struct Date(u32);
|
||||
|
||||
impl Date {
|
||||
pub const INDEX_ZERO: Self = Self(20090103);
|
||||
pub const INDEX_ZERO_: Date_ = Date_::constant(2009, 1, 3);
|
||||
pub const INDEX_ONE: Self = Self(20090109);
|
||||
pub const INDEX_ONE_: Date_ = Date_::constant(2009, 1, 9);
|
||||
pub const INDEX_ZERO: Self = Self(20090101);
|
||||
pub const INDEX_ZERO_: Date_ = Date_::constant(2009, 1, 1);
|
||||
pub const MIN_RATIO: Self = Self(20120101);
|
||||
|
||||
pub fn new(year: u16, month: u8, day: u8) -> Self {
|
||||
@@ -99,22 +97,18 @@ impl From<Timestamp> for Date {
|
||||
impl From<Day1> for Date {
|
||||
#[inline]
|
||||
fn from(value: Day1) -> Self {
|
||||
if value == Day1::default() {
|
||||
Date::INDEX_ZERO
|
||||
} else {
|
||||
Self::from(
|
||||
Self::INDEX_ONE_
|
||||
.checked_add(Span::new().days(i64::from(value) - 1))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
Self::from(
|
||||
Self::INDEX_ZERO_
|
||||
.checked_add(Span::new().days(i64::from(value)))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Week1> for Date {
|
||||
#[inline]
|
||||
fn from(value: Week1) -> Self {
|
||||
// Week 0 starts at genesis (2009-01-03), add i weeks
|
||||
// Week 0 starts at 2009-01-01, add i weeks
|
||||
Self::from(
|
||||
Self::INDEX_ZERO_
|
||||
.checked_add(Span::new().weeks(i64::from(u16::from(value))))
|
||||
@@ -279,49 +273,48 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_date_from_day1_zero() {
|
||||
// Day1 0 is genesis: Jan 3, 2009
|
||||
// Day1 0 is Jan 1, 2009
|
||||
let date = Date::from(Day1::from(0_usize));
|
||||
assert_eq!(date, Date::INDEX_ZERO);
|
||||
assert_eq!(date.year(), 2009);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_day1_two() {
|
||||
// Day1 2 is Jan 3, 2009 (genesis)
|
||||
let date = Date::from(Day1::from(2_usize));
|
||||
assert_eq!(date.year(), 2009);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_day1_one() {
|
||||
// Day1 1 is Jan 9, 2009 (6 day gap after genesis)
|
||||
let date = Date::from(Day1::from(1_usize));
|
||||
assert_eq!(date, Date::INDEX_ONE);
|
||||
fn test_date_from_day1_eight() {
|
||||
// Day1 8 is Jan 9, 2009
|
||||
let date = Date::from(Day1::from(8_usize));
|
||||
assert_eq!(date.year(), 2009);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_day1_two() {
|
||||
// Day1 2 is Jan 10, 2009
|
||||
let date = Date::from(Day1::from(2_usize));
|
||||
assert_eq!(date.year(), 2009);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_week1_zero() {
|
||||
// Week1 0 starts at genesis: Jan 3, 2009
|
||||
// Week1 0 starts at Jan 1, 2009
|
||||
let date = Date::from(Week1::from(0_usize));
|
||||
assert_eq!(date.year(), 2009);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 3);
|
||||
assert_eq!(date.day(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_week1_one() {
|
||||
// Week1 1 is Jan 10, 2009 (one week after genesis)
|
||||
// Week1 1 is Jan 8, 2009 (one week after epoch)
|
||||
let date = Date::from(Week1::from(1_usize));
|
||||
assert_eq!(date.year(), 2009);
|
||||
assert_eq!(date.month(), 1);
|
||||
assert_eq!(date.day(), 10);
|
||||
assert_eq!(date.day(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -11,7 +11,7 @@ use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use crate::{FromCoarserIndex, Month1, Month3, Month6, Week1, Year1, Year10};
|
||||
|
||||
use super::Date;
|
||||
use super::{Date, Timestamp};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -31,6 +31,10 @@ pub struct Day1(u16);
|
||||
|
||||
impl Day1 {
|
||||
pub const BYTES: usize = size_of::<Self>();
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Day1> for usize {
|
||||
@@ -74,14 +78,8 @@ impl TryFrom<Date> for Day1 {
|
||||
let value_ = jiff::civil::Date::from(value);
|
||||
if value_ < Date::INDEX_ZERO_ {
|
||||
Err(Error::UnindexableDate)
|
||||
} else if value == Date::INDEX_ZERO {
|
||||
Ok(Self(0))
|
||||
} else if value_ < Date::INDEX_ONE_ {
|
||||
Err(Error::UnindexableDate)
|
||||
} else if value == Date::INDEX_ONE {
|
||||
Ok(Self(1))
|
||||
} else {
|
||||
Ok(Self(Date::INDEX_ONE_.until(value_)?.get_days() as u16 + 1))
|
||||
Ok(Self(Date::INDEX_ZERO_.until(value_)?.get_days() as u16))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,40 +99,21 @@ impl Rem<usize> for Day1 {
|
||||
|
||||
impl FromCoarserIndex<Week1> for Day1 {
|
||||
fn min_from(coarser: Week1) -> usize {
|
||||
let coarser = usize::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else if coarser == 1 {
|
||||
1
|
||||
} else {
|
||||
4 + (coarser - 2) * 7
|
||||
}
|
||||
usize::from(coarser) * 7
|
||||
}
|
||||
|
||||
fn max_from_(coarser: Week1) -> usize {
|
||||
let coarser = usize::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else if coarser == 1 {
|
||||
3
|
||||
} else {
|
||||
3 + (coarser - 1) * 7
|
||||
}
|
||||
usize::from(coarser) * 7 + 6
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCoarserIndex<Month1> for Day1 {
|
||||
fn min_from(coarser: Month1) -> usize {
|
||||
let coarser = u16::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
let d = Date::new(2009, 1, 1)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(coarser))
|
||||
.unwrap();
|
||||
Day1::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
let d = Date::new(2009, 1, 1)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(u16::from(coarser)))
|
||||
.unwrap();
|
||||
Day1::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
|
||||
fn max_from_(coarser: Month1) -> usize {
|
||||
@@ -148,16 +127,11 @@ impl FromCoarserIndex<Month1> for Day1 {
|
||||
|
||||
impl FromCoarserIndex<Month3> for Day1 {
|
||||
fn min_from(coarser: Month3) -> usize {
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
let d = Date::new(2009, 1, 1)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(3 * coarser))
|
||||
.unwrap();
|
||||
Day1::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
let d = Date::new(2009, 1, 1)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(3 * u8::from(coarser)))
|
||||
.unwrap();
|
||||
Day1::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
|
||||
fn max_from_(coarser: Month3) -> usize {
|
||||
@@ -171,16 +145,11 @@ impl FromCoarserIndex<Month3> for Day1 {
|
||||
|
||||
impl FromCoarserIndex<Month6> for Day1 {
|
||||
fn min_from(coarser: Month6) -> usize {
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
let d = Date::new(2009, 1, 1)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(6 * coarser))
|
||||
.unwrap();
|
||||
Day1::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
let d = Date::new(2009, 1, 1)
|
||||
.into_jiff()
|
||||
.checked_add(Span::new().months(6 * u8::from(coarser)))
|
||||
.unwrap();
|
||||
Day1::try_from(Date::from(d)).unwrap().into()
|
||||
}
|
||||
|
||||
fn max_from_(coarser: Month6) -> usize {
|
||||
@@ -194,14 +163,9 @@ impl FromCoarserIndex<Month6> for Day1 {
|
||||
|
||||
impl FromCoarserIndex<Year1> for Day1 {
|
||||
fn min_from(coarser: Year1) -> usize {
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
0
|
||||
} else {
|
||||
Self::try_from(Date::new(2009 + coarser as u16, 1, 1))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
Self::try_from(Date::new(2009 + u8::from(coarser) as u16, 1, 1))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn max_from_(coarser: Year1) -> usize {
|
||||
@@ -215,6 +179,7 @@ impl FromCoarserIndex<Year10> for Day1 {
|
||||
fn min_from(coarser: Year10) -> usize {
|
||||
let coarser = u8::from(coarser);
|
||||
if coarser == 0 {
|
||||
// Decade 0 starts at 2000, before INDEX_ZERO (2009-01-01)
|
||||
0
|
||||
} else {
|
||||
Self::try_from(Date::new(2000 + 10 * coarser as u16, 1, 1))
|
||||
|
||||
@@ -26,7 +26,11 @@ pub struct Day3(u16);
|
||||
|
||||
impl Day3 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self(((*ts - INDEX_EPOCH) / DAY3_INTERVAL) as u16)
|
||||
Self(((*ts - INDEX_EPOCH + 86400) / DAY3_INTERVAL) as u16)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH - 86400 + self.0 as u32 * DAY3_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ impl PrintableIndex for DifficultyEpoch {
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["difficulty", "difficultyepoch"]
|
||||
&["difficulty", "difficultyepoch", "diff"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ impl PrintableIndex for HalvingEpoch {
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["halving", "halvingepoch"]
|
||||
&["halving", "halvingepoch", "halv"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ impl PrintableIndex for Height {
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["h", "height"]
|
||||
&["h", "height", "blk", "block"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Hour1 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / HOUR1_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * HOUR1_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hour1> for usize {
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Hour12 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / HOUR12_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * HOUR12_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hour12> for usize {
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Hour4 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / HOUR4_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * HOUR4_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hour4> for usize {
|
||||
|
||||
@@ -238,7 +238,7 @@ impl Index {
|
||||
Self::Hour1 => HOUR1_INTERVAL,
|
||||
Self::Hour4 => HOUR4_INTERVAL,
|
||||
Self::Hour12 => HOUR12_INTERVAL,
|
||||
Self::Day3 => DAY3_INTERVAL,
|
||||
Self::Day3 => return Some(Day3::from(i).to_timestamp()),
|
||||
_ => return self.index_to_date(i).map(|d| d.into()),
|
||||
};
|
||||
Some(Timestamp::new(INDEX_EPOCH + i as u32 * interval))
|
||||
@@ -276,7 +276,7 @@ impl Index {
|
||||
Self::Hour1 => HOUR1_INTERVAL,
|
||||
Self::Hour4 => HOUR4_INTERVAL,
|
||||
Self::Hour12 => HOUR12_INTERVAL,
|
||||
Self::Day3 => DAY3_INTERVAL,
|
||||
Self::Day3 => return Some(usize::from(Day3::from_timestamp(ts))),
|
||||
_ => return self.date_to_index(Date::from(ts)),
|
||||
};
|
||||
Some(((*ts - INDEX_EPOCH) / interval) as usize)
|
||||
@@ -442,13 +442,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_to_index_day1_genesis() {
|
||||
fn test_date_to_index_day1_zero() {
|
||||
assert_eq!(Index::Day1.date_to_index(Date::INDEX_ZERO), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_to_index_day1_one() {
|
||||
assert_eq!(Index::Day1.date_to_index(Date::INDEX_ONE), Some(1));
|
||||
fn test_date_to_index_day1_genesis() {
|
||||
assert_eq!(Index::Day1.date_to_index(Date::new(2009, 1, 3)), Some(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -494,11 +494,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_to_index_pre_genesis_returns_none() {
|
||||
let pre_genesis = Date::new(2009, 1, 2);
|
||||
assert!(Index::Day1.date_to_index(pre_genesis).is_none());
|
||||
assert!(Index::Week1.date_to_index(pre_genesis).is_none());
|
||||
assert!(Index::Month1.date_to_index(pre_genesis).is_none());
|
||||
fn test_date_to_index_pre_epoch_returns_none() {
|
||||
let pre_epoch = Date::new(2008, 12, 31);
|
||||
assert!(Index::Day1.date_to_index(pre_epoch).is_none());
|
||||
assert!(Index::Week1.date_to_index(pre_epoch).is_none());
|
||||
assert!(Index::Month1.date_to_index(pre_epoch).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -237,14 +237,14 @@ mod tests {
|
||||
let metric = date_based_metric();
|
||||
let dates: Vec<_> = metric.dates().unwrap().collect();
|
||||
assert_eq!(dates.len(), 5);
|
||||
// Day1 0 = Jan 3, 2009 (genesis)
|
||||
// Day1 0 = Jan 1, 2009
|
||||
assert_eq!(dates[0].year(), 2009);
|
||||
assert_eq!(dates[0].month(), 1);
|
||||
assert_eq!(dates[0].day(), 3);
|
||||
// Day1 1 = Jan 9, 2009 (day one)
|
||||
assert_eq!(dates[0].day(), 1);
|
||||
// Day1 1 = Jan 2, 2009
|
||||
assert_eq!(dates[1].year(), 2009);
|
||||
assert_eq!(dates[1].month(), 1);
|
||||
assert_eq!(dates[1].day(), 9);
|
||||
assert_eq!(dates[1].day(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -271,13 +271,13 @@ mod tests {
|
||||
let metric = date_based_metric();
|
||||
let pairs: Vec<_> = metric.iter_dates().unwrap().collect();
|
||||
assert_eq!(pairs.len(), 5);
|
||||
// First pair: (Jan 3 2009, 100)
|
||||
// First pair: (Jan 1 2009, 100)
|
||||
assert_eq!(pairs[0].0.year(), 2009);
|
||||
assert_eq!(pairs[0].0.month(), 1);
|
||||
assert_eq!(pairs[0].0.day(), 3);
|
||||
assert_eq!(pairs[0].0.day(), 1);
|
||||
assert_eq!(pairs[0].1, &100);
|
||||
// Second pair: (Jan 9 2009, 200)
|
||||
assert_eq!(pairs[1].0.day(), 9);
|
||||
// Second pair: (Jan 2 2009, 200)
|
||||
assert_eq!(pairs[1].0.day(), 2);
|
||||
assert_eq!(pairs[1].1, &200);
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ mod tests {
|
||||
let date_metric = DateMetricData::try_new(metric).unwrap();
|
||||
let pairs: Vec<_> = date_metric.iter_dates().unwrap().collect();
|
||||
assert_eq!(pairs.len(), 5);
|
||||
assert_eq!(pairs[0].0.day(), 3);
|
||||
assert_eq!(pairs[0].0.day(), 1);
|
||||
assert_eq!(pairs[0].1, &100);
|
||||
}
|
||||
|
||||
@@ -545,8 +545,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_timestamp_to_index_day1_via_date_fallback() {
|
||||
// Day1 goes through date_to_index fallback
|
||||
// 2009-01-09 = Day1 index 1
|
||||
// 2009-01-09 = Day1 index 8
|
||||
let ts = Timestamp::from(Date::new(2009, 1, 9));
|
||||
assert_eq!(Index::Day1.timestamp_to_index(ts), Some(1));
|
||||
assert_eq!(Index::Day1.timestamp_to_index(ts), Some(8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Minute1 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / MINUTE1_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * MINUTE1_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Minute1> for usize {
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Minute10 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / MINUTE10_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * MINUTE10_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Minute10> for usize {
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Minute30 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / MINUTE30_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * MINUTE30_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Minute30> for usize {
|
||||
|
||||
@@ -28,6 +28,10 @@ impl Minute5 {
|
||||
pub fn from_timestamp(ts: Timestamp) -> Self {
|
||||
Self((*ts - INDEX_EPOCH) / MINUTE5_INTERVAL)
|
||||
}
|
||||
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::new(INDEX_EPOCH + self.0 * MINUTE5_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Minute5> for usize {
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, Day1, Year1};
|
||||
use super::{Date, Day1, Timestamp, Year1};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -25,6 +25,12 @@ use super::{Date, Day1, Year1};
|
||||
)]
|
||||
pub struct Month1(u16);
|
||||
|
||||
impl Month1 {
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Month1 {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
@@ -115,7 +121,7 @@ impl PrintableIndex for Month1 {
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["month", "m", "monthly", "month1", "monthindex"]
|
||||
&["month", "m", "monthly", "month1", "monthindex", "1m", "1mo"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::Month1;
|
||||
use super::{Date, Month1, Timestamp};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -25,6 +25,12 @@ use super::Month1;
|
||||
)]
|
||||
pub struct Month3(u8);
|
||||
|
||||
impl Month3 {
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Month3 {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
@@ -101,7 +107,7 @@ impl PrintableIndex for Month3 {
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["quarter", "q", "quarterly", "month3", "quarterindex"]
|
||||
&["quarter", "q", "quarterly", "month3", "quarterindex", "3m", "3mo"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::Month1;
|
||||
use super::{Date, Month1, Timestamp};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -25,6 +25,12 @@ use super::Month1;
|
||||
)]
|
||||
pub struct Month6(u8);
|
||||
|
||||
impl Month6 {
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Month6 {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
@@ -101,7 +107,7 @@ impl PrintableIndex for Month6 {
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["semester", "s", "month6", "semesterindex"]
|
||||
&["semester", "s", "month6", "semesterindex", "6m", "6mo"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, Day1};
|
||||
use super::{Date, Day1, Timestamp};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -25,6 +25,12 @@ use super::{Date, Day1};
|
||||
)]
|
||||
pub struct Week1(u16);
|
||||
|
||||
impl Week1 {
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Week1 {
|
||||
#[inline]
|
||||
fn from(value: u16) -> Self {
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, Day1, Month1};
|
||||
use super::{Date, Day1, Month1, Timestamp};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -25,6 +25,12 @@ use super::{Date, Day1, Month1};
|
||||
)]
|
||||
pub struct Year1(u8);
|
||||
|
||||
impl Year1 {
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Year1 {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex};
|
||||
|
||||
use super::{Date, Day1, Year1};
|
||||
use super::{Date, Day1, Timestamp, Year1};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -25,6 +25,12 @@ use super::{Date, Day1, Year1};
|
||||
)]
|
||||
pub struct Year10(u8);
|
||||
|
||||
impl Year10 {
|
||||
pub fn to_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from(Date::from(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Year10 {
|
||||
#[inline]
|
||||
fn from(value: u8) -> Self {
|
||||
|
||||
+23
-104
@@ -951,7 +951,7 @@ function indexToDate(index, i) {
|
||||
case 'hour4': return new Date(_EPOCH_MS + i * 14400000);
|
||||
case 'hour12': return new Date(_EPOCH_MS + i * 43200000);
|
||||
case 'day1': return i === 0 ? _GENESIS : new Date(_DAY_ONE.getTime() + (i - 1) * _MS_PER_DAY);
|
||||
case 'day3': return new Date(_EPOCH_MS + i * 259200000);
|
||||
case 'day3': return new Date(_EPOCH_MS - 86400000 + i * 259200000);
|
||||
case 'week1': return new Date(_GENESIS.getTime() + i * _MS_PER_WEEK);
|
||||
case 'month1': return _addMonths(i);
|
||||
case 'month3': return _addMonths(i * 3);
|
||||
@@ -983,7 +983,7 @@ function dateToIndex(index, d) {
|
||||
if (ms < _DAY_ONE.getTime()) return 0;
|
||||
return 1 + Math.floor((ms - _DAY_ONE.getTime()) / _MS_PER_DAY);
|
||||
}
|
||||
case 'day3': return Math.floor((ms - _EPOCH_MS) / 259200000);
|
||||
case 'day3': return Math.floor((ms - _EPOCH_MS + 86400000) / 259200000);
|
||||
case 'week1': return Math.floor((ms - _GENESIS.getTime()) / _MS_PER_WEEK);
|
||||
case 'month1': return (d.getFullYear() - 2009) * 12 + d.getMonth();
|
||||
case 'month3': return (d.getFullYear() - 2009) * 4 + Math.floor(d.getMonth() / 3);
|
||||
@@ -2710,57 +2710,6 @@ function createGreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(c
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern
|
||||
* @property {MetricPattern10<T>} day1
|
||||
* @property {MetricPattern11<T>} day3
|
||||
* @property {MetricPattern19<T>} difficultyepoch
|
||||
* @property {MetricPattern18<T>} halvingepoch
|
||||
* @property {MetricPattern7<T>} hour1
|
||||
* @property {MetricPattern9<T>} hour12
|
||||
* @property {MetricPattern8<T>} hour4
|
||||
* @property {MetricPattern3<T>} minute1
|
||||
* @property {MetricPattern5<T>} minute10
|
||||
* @property {MetricPattern6<T>} minute30
|
||||
* @property {MetricPattern4<T>} minute5
|
||||
* @property {MetricPattern13<T>} month1
|
||||
* @property {MetricPattern14<T>} month3
|
||||
* @property {MetricPattern15<T>} month6
|
||||
* @property {MetricPattern12<T>} week1
|
||||
* @property {MetricPattern16<T>} year1
|
||||
* @property {MetricPattern17<T>} year10
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern pattern node
|
||||
* @template T
|
||||
* @param {BrkClientBase} client
|
||||
* @param {string} acc - Accumulated metric name
|
||||
* @returns {Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<T>}
|
||||
*/
|
||||
function createDay1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, acc) {
|
||||
return {
|
||||
day1: createMetricPattern10(client, _m(acc, 'day1')),
|
||||
day3: createMetricPattern11(client, _m(acc, 'day3')),
|
||||
difficultyepoch: createMetricPattern19(client, _m(acc, 'difficultyepoch')),
|
||||
halvingepoch: createMetricPattern18(client, _m(acc, 'halvingepoch')),
|
||||
hour1: createMetricPattern7(client, _m(acc, 'hour1')),
|
||||
hour12: createMetricPattern9(client, _m(acc, 'hour12')),
|
||||
hour4: createMetricPattern8(client, _m(acc, 'hour4')),
|
||||
minute1: createMetricPattern3(client, _m(acc, 'minute1')),
|
||||
minute10: createMetricPattern5(client, _m(acc, 'minute10')),
|
||||
minute30: createMetricPattern6(client, _m(acc, 'minute30')),
|
||||
minute5: createMetricPattern4(client, _m(acc, 'minute5')),
|
||||
month1: createMetricPattern13(client, _m(acc, 'month1')),
|
||||
month3: createMetricPattern14(client, _m(acc, 'month3')),
|
||||
month6: createMetricPattern15(client, _m(acc, 'month6')),
|
||||
week1: createMetricPattern12(client, _m(acc, 'week1')),
|
||||
year1: createMetricPattern16(client, _m(acc, 'year1')),
|
||||
year10: createMetricPattern17(client, _m(acc, 'year10')),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern
|
||||
* @property {MetricPattern1<Dollars>} greedIndex
|
||||
@@ -3818,9 +3767,9 @@ function createBtcSatsUsdPattern(client, acc) {
|
||||
|
||||
/**
|
||||
* @typedef {Object} CentsSatsUsdPattern
|
||||
* @property {Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<OHLCCents>} cents
|
||||
* @property {Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<OHLCSats>} sats
|
||||
* @property {Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern<OHLCDollars>} usd
|
||||
* @property {MetricPattern2<Cents>} cents
|
||||
* @property {MetricPattern2<Sats>} sats
|
||||
* @property {MetricPattern2<Dollars>} usd
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -3831,9 +3780,9 @@ function createBtcSatsUsdPattern(client, acc) {
|
||||
*/
|
||||
function createCentsSatsUsdPattern(client, acc) {
|
||||
return {
|
||||
cents: createDay1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, _m(acc, 'cents')),
|
||||
sats: createDay1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, _m(acc, 'sats')),
|
||||
usd: createDay1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, acc),
|
||||
cents: createMetricPattern2(client, _m(acc, 'cents')),
|
||||
sats: createMetricPattern2(client, _m(acc, 'sats')),
|
||||
usd: createMetricPattern2(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4099,33 +4048,11 @@ function createRatioPattern2(client, acc) {
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Blocks_Time
|
||||
* @property {MetricsTree_Blocks_Time_Timestamp} timestamp
|
||||
* @property {MetricPattern1<Timestamp>} timestamp
|
||||
* @property {MetricPattern20<Date>} date
|
||||
* @property {MetricPattern20<Timestamp>} timestampMonotonic
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Blocks_Time_Timestamp
|
||||
* @property {MetricPattern20<Timestamp>} base
|
||||
* @property {MetricPattern3<Timestamp>} minute1
|
||||
* @property {MetricPattern4<Timestamp>} minute5
|
||||
* @property {MetricPattern5<Timestamp>} minute10
|
||||
* @property {MetricPattern6<Timestamp>} minute30
|
||||
* @property {MetricPattern7<Timestamp>} hour1
|
||||
* @property {MetricPattern8<Timestamp>} hour4
|
||||
* @property {MetricPattern9<Timestamp>} hour12
|
||||
* @property {MetricPattern10<Timestamp>} day1
|
||||
* @property {MetricPattern11<Timestamp>} day3
|
||||
* @property {MetricPattern12<Timestamp>} week1
|
||||
* @property {MetricPattern13<Timestamp>} month1
|
||||
* @property {MetricPattern14<Timestamp>} month3
|
||||
* @property {MetricPattern15<Timestamp>} month6
|
||||
* @property {MetricPattern16<Timestamp>} year1
|
||||
* @property {MetricPattern17<Timestamp>} year10
|
||||
* @property {MetricPattern18<Timestamp>} halvingepoch
|
||||
* @property {MetricPattern19<Timestamp>} difficultyepoch
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Blocks_Weight
|
||||
* @property {MetricPattern20<Weight>} base
|
||||
@@ -5280,7 +5207,7 @@ function createRatioPattern2(client, acc) {
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Prices
|
||||
* @property {MetricsTree_Prices_Split} split
|
||||
* @property {CentsSatsUsdPattern} ohlc
|
||||
* @property {MetricsTree_Prices_Ohlc} ohlc
|
||||
* @property {MetricsTree_Prices_Price} price
|
||||
*/
|
||||
|
||||
@@ -5299,6 +5226,13 @@ function createRatioPattern2(client, acc) {
|
||||
* @property {MetricPattern2<Sats>} sats
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Prices_Ohlc
|
||||
* @property {MetricPattern2<OHLCCents>} cents
|
||||
* @property {MetricPattern2<OHLCDollars>} usd
|
||||
* @property {MetricPattern2<OHLCSats>} sats
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Prices_Price
|
||||
* @property {MetricPattern20<Cents>} cents
|
||||
@@ -6660,26 +6594,7 @@ class BrkClient extends BrkClientBase {
|
||||
daysBeforeNextAdjustment: createMetricPattern1(this, 'days_before_next_difficulty_adjustment'),
|
||||
},
|
||||
time: {
|
||||
timestamp: {
|
||||
base: createMetricPattern20(this, 'timestamp'),
|
||||
minute1: createMetricPattern3(this, 'timestamp_minute1'),
|
||||
minute5: createMetricPattern4(this, 'timestamp_minute5'),
|
||||
minute10: createMetricPattern5(this, 'timestamp_minute10'),
|
||||
minute30: createMetricPattern6(this, 'timestamp_minute30'),
|
||||
hour1: createMetricPattern7(this, 'timestamp_hour1'),
|
||||
hour4: createMetricPattern8(this, 'timestamp_hour4'),
|
||||
hour12: createMetricPattern9(this, 'timestamp_hour12'),
|
||||
day1: createMetricPattern10(this, 'timestamp_day1'),
|
||||
day3: createMetricPattern11(this, 'timestamp_day3'),
|
||||
week1: createMetricPattern12(this, 'timestamp_week1'),
|
||||
month1: createMetricPattern13(this, 'timestamp_month1'),
|
||||
month3: createMetricPattern14(this, 'timestamp_month3'),
|
||||
month6: createMetricPattern15(this, 'timestamp_month6'),
|
||||
year1: createMetricPattern16(this, 'timestamp_year1'),
|
||||
year10: createMetricPattern17(this, 'timestamp_year10'),
|
||||
halvingepoch: createMetricPattern18(this, 'timestamp_halvingepoch'),
|
||||
difficultyepoch: createMetricPattern19(this, 'timestamp_difficultyepoch'),
|
||||
},
|
||||
timestamp: createMetricPattern1(this, 'timestamp'),
|
||||
date: createMetricPattern20(this, 'date'),
|
||||
timestampMonotonic: createMetricPattern20(this, 'timestamp_monotonic'),
|
||||
},
|
||||
@@ -7584,7 +7499,11 @@ class BrkClient extends BrkClientBase {
|
||||
sats: createMetricPattern2(this, 'price_close_sats'),
|
||||
},
|
||||
},
|
||||
ohlc: createCentsSatsUsdPattern(this, 'price_ohlc'),
|
||||
ohlc: {
|
||||
cents: createMetricPattern2(this, 'price_ohlc_cents'),
|
||||
usd: createMetricPattern2(this, 'price_ohlc'),
|
||||
sats: createMetricPattern2(this, 'price_ohlc_sats'),
|
||||
},
|
||||
price: {
|
||||
cents: createMetricPattern20(this, 'price_cents'),
|
||||
usd: createMetricPattern20(this, 'price'),
|
||||
|
||||
@@ -1140,7 +1140,7 @@ def _index_to_date(index: str, i: int) -> Union[date, datetime]:
|
||||
elif index == 'day1':
|
||||
return _GENESIS if i == 0 else _DAY_ONE + timedelta(days=i - 1)
|
||||
elif index == 'day3':
|
||||
return _EPOCH.date() + timedelta(days=i * 3)
|
||||
return _EPOCH.date() - timedelta(days=1) + timedelta(days=i * 3)
|
||||
elif index == 'week1':
|
||||
return _GENESIS + timedelta(weeks=i)
|
||||
elif index == 'month1':
|
||||
@@ -1180,7 +1180,7 @@ def _date_to_index(index: str, d: Union[date, datetime]) -> int:
|
||||
return 0
|
||||
return 1 + (dd - _DAY_ONE).days
|
||||
elif index == 'day3':
|
||||
return (dd - date(2009, 1, 1)).days // 3
|
||||
return (dd - date(2008, 12, 31)).days // 3
|
||||
elif index == 'week1':
|
||||
return (dd - _GENESIS).days // 7
|
||||
elif index == 'month1':
|
||||
@@ -2645,29 +2645,6 @@ class GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern:
|
||||
self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss'))
|
||||
self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit'))
|
||||
|
||||
class Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(Generic[T]):
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.day1: MetricPattern10[T] = MetricPattern10(client, _m(acc, 'day1'))
|
||||
self.day3: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'day3'))
|
||||
self.difficultyepoch: MetricPattern19[T] = MetricPattern19(client, _m(acc, 'difficultyepoch'))
|
||||
self.halvingepoch: MetricPattern18[T] = MetricPattern18(client, _m(acc, 'halvingepoch'))
|
||||
self.hour1: MetricPattern7[T] = MetricPattern7(client, _m(acc, 'hour1'))
|
||||
self.hour12: MetricPattern9[T] = MetricPattern9(client, _m(acc, 'hour12'))
|
||||
self.hour4: MetricPattern8[T] = MetricPattern8(client, _m(acc, 'hour4'))
|
||||
self.minute1: MetricPattern3[T] = MetricPattern3(client, _m(acc, 'minute1'))
|
||||
self.minute10: MetricPattern5[T] = MetricPattern5(client, _m(acc, 'minute10'))
|
||||
self.minute30: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'minute30'))
|
||||
self.minute5: MetricPattern4[T] = MetricPattern4(client, _m(acc, 'minute5'))
|
||||
self.month1: MetricPattern13[T] = MetricPattern13(client, _m(acc, 'month1'))
|
||||
self.month3: MetricPattern14[T] = MetricPattern14(client, _m(acc, 'month3'))
|
||||
self.month6: MetricPattern15[T] = MetricPattern15(client, _m(acc, 'month6'))
|
||||
self.week1: MetricPattern12[T] = MetricPattern12(client, _m(acc, 'week1'))
|
||||
self.year1: MetricPattern16[T] = MetricPattern16(client, _m(acc, 'year1'))
|
||||
self.year10: MetricPattern17[T] = MetricPattern17(client, _m(acc, 'year10'))
|
||||
|
||||
class GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -3145,9 +3122,9 @@ class CentsSatsUsdPattern:
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.cents: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern[OHLCCents] = Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, _m(acc, 'cents'))
|
||||
self.sats: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern[OHLCSats] = Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, _m(acc, 'sats'))
|
||||
self.usd: Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern[OHLCDollars] = Day1Day3DifficultyepochHalvingepochHour1Hour12Hour4Minute1Minute10Minute30Minute5Month1Month3Month6Week1Year1Year10Pattern(client, acc)
|
||||
self.cents: MetricPattern2[Cents] = MetricPattern2(client, _m(acc, 'cents'))
|
||||
self.sats: MetricPattern2[Sats] = MetricPattern2(client, _m(acc, 'sats'))
|
||||
self.usd: MetricPattern2[Dollars] = MetricPattern2(client, acc)
|
||||
|
||||
class HistogramLineSignalPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
@@ -3251,34 +3228,11 @@ class MetricsTree_Blocks_Difficulty:
|
||||
self.blocks_before_next_adjustment: MetricPattern1[StoredU32] = MetricPattern1(client, 'blocks_before_next_difficulty_adjustment')
|
||||
self.days_before_next_adjustment: MetricPattern1[StoredF32] = MetricPattern1(client, 'days_before_next_difficulty_adjustment')
|
||||
|
||||
class MetricsTree_Blocks_Time_Timestamp:
|
||||
"""Metrics tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.base: MetricPattern20[Timestamp] = MetricPattern20(client, 'timestamp')
|
||||
self.minute1: MetricPattern3[Timestamp] = MetricPattern3(client, 'timestamp_minute1')
|
||||
self.minute5: MetricPattern4[Timestamp] = MetricPattern4(client, 'timestamp_minute5')
|
||||
self.minute10: MetricPattern5[Timestamp] = MetricPattern5(client, 'timestamp_minute10')
|
||||
self.minute30: MetricPattern6[Timestamp] = MetricPattern6(client, 'timestamp_minute30')
|
||||
self.hour1: MetricPattern7[Timestamp] = MetricPattern7(client, 'timestamp_hour1')
|
||||
self.hour4: MetricPattern8[Timestamp] = MetricPattern8(client, 'timestamp_hour4')
|
||||
self.hour12: MetricPattern9[Timestamp] = MetricPattern9(client, 'timestamp_hour12')
|
||||
self.day1: MetricPattern10[Timestamp] = MetricPattern10(client, 'timestamp_day1')
|
||||
self.day3: MetricPattern11[Timestamp] = MetricPattern11(client, 'timestamp_day3')
|
||||
self.week1: MetricPattern12[Timestamp] = MetricPattern12(client, 'timestamp_week1')
|
||||
self.month1: MetricPattern13[Timestamp] = MetricPattern13(client, 'timestamp_month1')
|
||||
self.month3: MetricPattern14[Timestamp] = MetricPattern14(client, 'timestamp_month3')
|
||||
self.month6: MetricPattern15[Timestamp] = MetricPattern15(client, 'timestamp_month6')
|
||||
self.year1: MetricPattern16[Timestamp] = MetricPattern16(client, 'timestamp_year1')
|
||||
self.year10: MetricPattern17[Timestamp] = MetricPattern17(client, 'timestamp_year10')
|
||||
self.halvingepoch: MetricPattern18[Timestamp] = MetricPattern18(client, 'timestamp_halvingepoch')
|
||||
self.difficultyepoch: MetricPattern19[Timestamp] = MetricPattern19(client, 'timestamp_difficultyepoch')
|
||||
|
||||
class MetricsTree_Blocks_Time:
|
||||
"""Metrics tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.timestamp: MetricsTree_Blocks_Time_Timestamp = MetricsTree_Blocks_Time_Timestamp(client)
|
||||
self.timestamp: MetricPattern1[Timestamp] = MetricPattern1(client, 'timestamp')
|
||||
self.date: MetricPattern20[Date] = MetricPattern20(client, 'date')
|
||||
self.timestamp_monotonic: MetricPattern20[Timestamp] = MetricPattern20(client, 'timestamp_monotonic')
|
||||
|
||||
@@ -4560,6 +4514,14 @@ class MetricsTree_Prices_Split:
|
||||
self.low: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'price_low')
|
||||
self.close: MetricsTree_Prices_Split_Close = MetricsTree_Prices_Split_Close(client)
|
||||
|
||||
class MetricsTree_Prices_Ohlc:
|
||||
"""Metrics tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.cents: MetricPattern2[OHLCCents] = MetricPattern2(client, 'price_ohlc_cents')
|
||||
self.usd: MetricPattern2[OHLCDollars] = MetricPattern2(client, 'price_ohlc')
|
||||
self.sats: MetricPattern2[OHLCSats] = MetricPattern2(client, 'price_ohlc_sats')
|
||||
|
||||
class MetricsTree_Prices_Price:
|
||||
"""Metrics tree node."""
|
||||
|
||||
@@ -4573,7 +4535,7 @@ class MetricsTree_Prices:
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.split: MetricsTree_Prices_Split = MetricsTree_Prices_Split(client)
|
||||
self.ohlc: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'price_ohlc')
|
||||
self.ohlc: MetricsTree_Prices_Ohlc = MetricsTree_Prices_Ohlc(client)
|
||||
self.price: MetricsTree_Prices_Price = MetricsTree_Prices_Price(client)
|
||||
|
||||
class MetricsTree_Distribution_AnyAddressIndexes:
|
||||
|
||||
@@ -291,9 +291,9 @@ class TestIndexToDate:
|
||||
|
||||
def test_day3(self, day3_metric):
|
||||
dates = day3_metric.dates()
|
||||
assert dates[0] == date(2009, 1, 1) # epoch
|
||||
assert dates[1] == date(2009, 1, 4) # +3 days
|
||||
assert dates[2] == date(2009, 1, 7) # +6 days
|
||||
assert dates[0] == date(2008, 12, 31) # epoch - 1 day (TradingView-aligned)
|
||||
assert dates[1] == date(2009, 1, 3) # +3 days
|
||||
assert dates[2] == date(2009, 1, 6) # +6 days
|
||||
|
||||
def test_hour1_returns_datetime(self, hour1_metric):
|
||||
"""Sub-daily indexes return datetime, not date."""
|
||||
|
||||
@@ -13,7 +13,7 @@ import { createRadios, createSelect } from "../utils/dom.js";
|
||||
import { createPersistedValue } from "../utils/persisted.js";
|
||||
import { onChange as onThemeChange } from "../utils/theme.js";
|
||||
import { throttle, debounce } from "../utils/timing.js";
|
||||
import { serdeBool, serdeChartableIndex } from "../utils/serde.js";
|
||||
import { serdeBool, INDEX_FROM_LABEL } from "../utils/serde.js";
|
||||
import { stringToId, numberToShortUSFormat } from "../utils/format.js";
|
||||
import { style } from "../utils/elements.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
@@ -87,22 +87,23 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
const getTimeEndpoint = (idx) =>
|
||||
idx === "height"
|
||||
? brk.metrics.blocks.time.timestampMonotonic.by[idx]
|
||||
: /** @type {any} */ (brk.metrics.blocks.time.timestamp)[idx].by[idx];
|
||||
: brk.metrics.blocks.time.timestamp.by[idx];
|
||||
|
||||
const index = {
|
||||
/** @type {Set<(index: ChartableIndex) => void>} */
|
||||
onChange: new Set(),
|
||||
|
||||
get() {
|
||||
return serdeChartableIndex.deserialize(index.name.value);
|
||||
return INDEX_FROM_LABEL[index.name.value];
|
||||
},
|
||||
|
||||
name: createPersistedValue({
|
||||
defaultValue: /** @type {ChartableIndexName} */ ("date"),
|
||||
defaultValue: /** @type {IndexLabel} */ ("1d"),
|
||||
storageKey: "chart-index",
|
||||
urlKey: "i",
|
||||
serialize: (v) => v,
|
||||
deserialize: (s) => /** @type {ChartableIndexName} */ (s),
|
||||
deserialize: (s) =>
|
||||
/** @type {IndexLabel} */ (s in INDEX_FROM_LABEL ? s : "1d"),
|
||||
onChange: () => {
|
||||
range.set(null);
|
||||
index.onChange.forEach((cb) => cb(index.get()));
|
||||
@@ -323,7 +324,12 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
|
||||
ichart.applyOptions({
|
||||
timeScale: {
|
||||
timeVisible: index === "height",
|
||||
timeVisible:
|
||||
index === "height" ||
|
||||
index === "difficultyepoch" ||
|
||||
index === "halvingepoch" ||
|
||||
index.startsWith("minute") ||
|
||||
index.startsWith("hour"),
|
||||
...(!fitContent
|
||||
? {
|
||||
minBarSpacing,
|
||||
@@ -1420,20 +1426,22 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
/** @type {HTMLElement | null} */
|
||||
let indexField = null;
|
||||
|
||||
const lastTd = ichart.chartElement().querySelector("table > tr:last-child > td:nth-child(2)");
|
||||
const lastTd = ichart
|
||||
.chartElement()
|
||||
.querySelector("table > tr:last-child > td:nth-child(2)");
|
||||
|
||||
const chart = {
|
||||
get panes() {
|
||||
return blueprints.panes;
|
||||
},
|
||||
|
||||
/** @param {ChartableIndexName[]} choices */
|
||||
setIndexChoices(choices) {
|
||||
/** @param {{ choices: IndexLabel[], groups: { label: string, items: IndexLabel[] }[] }} arg */
|
||||
setIndexChoices({ choices, groups }) {
|
||||
if (indexField) indexField.remove();
|
||||
|
||||
let currentValue = choices.includes(preferredIndex)
|
||||
? preferredIndex
|
||||
: (choices[0] ?? "date");
|
||||
: (choices[0] ?? "1d");
|
||||
|
||||
if (currentValue !== index.name.value) {
|
||||
index.name.set(currentValue);
|
||||
@@ -1446,6 +1454,7 @@ export function createChart({ parent, brk, fitContent }) {
|
||||
index.name.set(v);
|
||||
},
|
||||
choices,
|
||||
groups,
|
||||
id: "index",
|
||||
});
|
||||
const sep = document.createElement("span");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { localhost } from "../utils/env.js";
|
||||
import { serdeChartableIndex } from "../utils/serde.js";
|
||||
import { INDEX_LABEL } from "../utils/serde.js";
|
||||
|
||||
/**
|
||||
* Check if a metric pattern has at least one chartable index
|
||||
@@ -8,7 +8,7 @@ import { serdeChartableIndex } from "../utils/serde.js";
|
||||
*/
|
||||
function hasChartableIndex(node) {
|
||||
const indexes = node.indexes();
|
||||
return indexes.some((idx) => serdeChartableIndex.serialize(idx) !== null);
|
||||
return indexes.some((idx) => idx in INDEX_LABEL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createHeader } from "../utils/dom.js";
|
||||
import { chartElement } from "../utils/elements.js";
|
||||
import { serdeChartableIndex } from "../utils/serde.js";
|
||||
import { INDEX_FROM_LABEL } from "../utils/serde.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { createChart } from "../chart/index.js";
|
||||
import { colors } from "../utils/colors.js";
|
||||
@@ -45,7 +45,7 @@ export function init() {
|
||||
const usdPrice = {
|
||||
type: "Candlestick",
|
||||
title: "Price",
|
||||
metric: brk.metrics.prices.ohlc.usd.day1,
|
||||
metric: brk.metrics.prices.ohlc.usd,
|
||||
};
|
||||
result.set(Unit.usd, [usdPrice, ...(optionTop.get(Unit.usd) ?? [])]);
|
||||
|
||||
@@ -54,7 +54,7 @@ export function init() {
|
||||
const satsPrice = {
|
||||
type: "Candlestick",
|
||||
title: "Price",
|
||||
metric: brk.metrics.prices.ohlc.sats.day1,
|
||||
metric: brk.metrics.prices.ohlc.sats,
|
||||
colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]),
|
||||
};
|
||||
result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]);
|
||||
@@ -104,24 +104,32 @@ export function init() {
|
||||
onPrice(updatePriceWithLatest);
|
||||
}
|
||||
|
||||
const ALL_CHOICES = /** @satisfies {ChartableIndexName[]} */ ([
|
||||
"timestamp",
|
||||
"date",
|
||||
"week",
|
||||
"month",
|
||||
"month3",
|
||||
"month6",
|
||||
"year",
|
||||
"year10",
|
||||
]);
|
||||
/** @type {{ label: string, items: IndexLabel[] }[]} */
|
||||
const ALL_GROUPS = [
|
||||
{ label: "Height", items: ["blk", "halv", "diff"] },
|
||||
{
|
||||
label: "Time",
|
||||
items: [
|
||||
"1mn", "5mn", "10mn", "30mn",
|
||||
"1h", "4h", "12h",
|
||||
"1d", "3d", "1w",
|
||||
"1m", "3m", "6m",
|
||||
"1y", "10y",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const ALL_CHOICES = /** @satisfies {IndexLabel[]} */ (
|
||||
ALL_GROUPS.flatMap((g) => g.items)
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {ChartOption} opt
|
||||
* @returns {ChartableIndexName[]}
|
||||
* @returns {{ choices: IndexLabel[], groups: { label: string, items: IndexLabel[] }[] }}
|
||||
*/
|
||||
function computeChoices(opt) {
|
||||
if (!opt.top().size && !opt.bottom().size) {
|
||||
return [...ALL_CHOICES];
|
||||
return { choices: [...ALL_CHOICES], groups: ALL_GROUPS };
|
||||
}
|
||||
const rawIndexes = new Set(
|
||||
[Array.from(opt.top().values()), Array.from(opt.bottom().values())]
|
||||
@@ -133,8 +141,16 @@ function computeChoices(opt) {
|
||||
.flatMap((blueprint) => blueprint.metric.indexes()),
|
||||
);
|
||||
|
||||
return ALL_CHOICES.filter((choice) =>
|
||||
rawIndexes.has(serdeChartableIndex.deserialize(choice)),
|
||||
);
|
||||
const groups = ALL_GROUPS
|
||||
.map(({ label, items }) => ({
|
||||
label,
|
||||
items: items.filter((choice) => rawIndexes.has(INDEX_FROM_LABEL[choice])),
|
||||
}))
|
||||
.filter(({ items }) => items.length > 0);
|
||||
|
||||
return {
|
||||
choices: groups.flatMap((g) => g.items),
|
||||
groups,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*
|
||||
* @import { UnitObject as Unit } from "./utils/units.js"
|
||||
*
|
||||
* @import { ChartableIndexName } from "./utils/serde.js";
|
||||
* @import { ChartableIndex, IndexLabel } from "./utils/serde.js";
|
||||
*/
|
||||
|
||||
// import uFuzzy = require("./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts");
|
||||
@@ -215,6 +215,4 @@
|
||||
* Generic tree node type for walking
|
||||
* @typedef {AnyMetricPattern | Record<string, unknown>} TreeNode
|
||||
*
|
||||
* Chartable index IDs (subset of IndexName that can be charted)
|
||||
* @typedef {"height" | "day1" | "week1" | "month1" | "month3" | "month6" | "year1" | "year10"} ChartableIndex
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
*/
|
||||
export const entries = (obj) => /** @type {[keyof T & string, T[keyof T & string]][]} */ (Object.entries(obj));
|
||||
|
||||
/**
|
||||
* Typed Object.fromEntries that preserves key/value types
|
||||
* @template {string} K
|
||||
* @template V
|
||||
* @param {Iterable<readonly [K, V]>} pairs
|
||||
* @returns {Record<K, V>}
|
||||
*/
|
||||
export const fromEntries = (pairs) => /** @type {Record<K, V>} */ (Object.fromEntries(pairs));
|
||||
|
||||
/**
|
||||
* Type-safe includes that narrows the value type
|
||||
* @template T
|
||||
|
||||
@@ -238,10 +238,12 @@ export function createRadios({
|
||||
* @param {(choice: T) => string} [args.toKey]
|
||||
* @param {(choice: T) => string} [args.toLabel]
|
||||
* @param {boolean} [args.sorted]
|
||||
* @param {{ label: string, items: T[] }[]} [args.groups]
|
||||
*/
|
||||
export function createSelect({
|
||||
id,
|
||||
choices: unsortedChoices,
|
||||
groups,
|
||||
initialValue,
|
||||
onChange,
|
||||
sorted,
|
||||
@@ -271,15 +273,27 @@ export function createSelect({
|
||||
select.name = id ?? "";
|
||||
field.append(select);
|
||||
|
||||
choices.forEach((choice) => {
|
||||
/** @param {T} choice */
|
||||
const createOption = (choice) => {
|
||||
const option = window.document.createElement("option");
|
||||
option.value = toKey(choice);
|
||||
option.textContent = toLabel(choice);
|
||||
if (toKey(choice) === initialKey) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.append(option);
|
||||
});
|
||||
return option;
|
||||
};
|
||||
|
||||
if (groups) {
|
||||
groups.forEach(({ label, items }) => {
|
||||
const optgroup = window.document.createElement("optgroup");
|
||||
optgroup.label = label;
|
||||
items.forEach((choice) => optgroup.append(createOption(choice)));
|
||||
select.append(optgroup);
|
||||
});
|
||||
} else {
|
||||
choices.forEach((choice) => select.append(createOption(choice)));
|
||||
}
|
||||
|
||||
select.addEventListener("change", () => {
|
||||
onChange?.(fromKey(select.value));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { entries, fromEntries } from "./array.js";
|
||||
|
||||
export const serdeBool = {
|
||||
/**
|
||||
* @param {boolean} v
|
||||
@@ -17,64 +19,21 @@ export const serdeBool = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {"timestamp" | "date" | "week" | "month" | "month3" | "month6" | "year" | "year10"} ChartableIndexName
|
||||
*/
|
||||
export const INDEX_LABEL = /** @type {const} */ ({
|
||||
height: "blk",
|
||||
minute1: "1mn", minute5: "5mn", minute10: "10mn", minute30: "30mn",
|
||||
hour1: "1h", hour4: "4h", hour12: "12h",
|
||||
day1: "1d", day3: "3d", week1: "1w",
|
||||
month1: "1m", month3: "3m", month6: "6m",
|
||||
year1: "1y", year10: "10y",
|
||||
halvingepoch: "halv", difficultyepoch: "diff",
|
||||
});
|
||||
|
||||
export const serdeChartableIndex = {
|
||||
/**
|
||||
* @param {IndexName} v
|
||||
* @returns {ChartableIndexName | null}
|
||||
*/
|
||||
serialize(v) {
|
||||
switch (v) {
|
||||
case "day1":
|
||||
return "date";
|
||||
case "year10":
|
||||
return "year10";
|
||||
case "height":
|
||||
return "timestamp";
|
||||
case "month1":
|
||||
return "month";
|
||||
case "month3":
|
||||
return "month3";
|
||||
case "month6":
|
||||
return "month6";
|
||||
case "week1":
|
||||
return "week";
|
||||
case "year1":
|
||||
return "year";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {ChartableIndexName} v
|
||||
* @returns {ChartableIndex}
|
||||
*/
|
||||
deserialize(v) {
|
||||
switch (v) {
|
||||
case "timestamp":
|
||||
return "height";
|
||||
case "date":
|
||||
return "day1";
|
||||
case "week":
|
||||
return "week1";
|
||||
case "month":
|
||||
return "month1";
|
||||
case "month3":
|
||||
return "month3";
|
||||
case "month6":
|
||||
return "month6";
|
||||
case "year":
|
||||
return "year1";
|
||||
case "year10":
|
||||
return "year10";
|
||||
default:
|
||||
throw Error("todo");
|
||||
}
|
||||
},
|
||||
};
|
||||
/** @typedef {typeof INDEX_LABEL} IndexLabelMap */
|
||||
/** @typedef {keyof IndexLabelMap} ChartableIndex */
|
||||
/** @typedef {IndexLabelMap[ChartableIndex]} IndexLabel */
|
||||
|
||||
export const INDEX_FROM_LABEL = fromEntries(entries(INDEX_LABEL).map(([k, v]) => [v, k]));
|
||||
|
||||
/**
|
||||
* @typedef {"" |
|
||||
|
||||
Reference in New Issue
Block a user