mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-05 08:08:15 -07:00
global: snapshot
This commit is contained in:
@@ -6015,17 +6015,17 @@ impl MetricsTree_Prices_Ohlc {
|
|||||||
|
|
||||||
/// Metrics tree node.
|
/// Metrics tree node.
|
||||||
pub struct MetricsTree_Prices_Price {
|
pub struct MetricsTree_Prices_Price {
|
||||||
pub cents: MetricPattern20<Cents>,
|
pub cents: MetricPattern1<Cents>,
|
||||||
pub usd: MetricPattern20<Dollars>,
|
pub usd: MetricPattern1<Dollars>,
|
||||||
pub sats: MetricPattern20<Sats>,
|
pub sats: MetricPattern1<Sats>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetricsTree_Prices_Price {
|
impl MetricsTree_Prices_Price {
|
||||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cents: MetricPattern20::new(client.clone(), "price_cents".to_string()),
|
cents: MetricPattern1::new(client.clone(), "price_cents".to_string()),
|
||||||
usd: MetricPattern20::new(client.clone(), "price".to_string()),
|
usd: MetricPattern1::new(client.clone(), "price".to_string()),
|
||||||
sats: MetricPattern20::new(client.clone(), "price_sats".to_string()),
|
sats: MetricPattern1::new(client.clone(), "price_sats".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,49 +14,9 @@ impl Vecs {
|
|||||||
starting_indexes: &ComputeIndexes,
|
starting_indexes: &ComputeIndexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
{
|
self.time
|
||||||
let ts = &mut self.time.timestamp;
|
.timestamp
|
||||||
|
.compute(indexer, indexes, starting_indexes, exit)?;
|
||||||
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
|
self.count
|
||||||
.compute(indexer, &self.time, starting_indexes, exit)?;
|
.compute(indexer, &self.time, starting_indexes, exit)?;
|
||||||
self.interval
|
self.interval
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl Vecs {
|
|||||||
let interval = IntervalVecs::forced_import(&db, version, indexes)?;
|
let interval = IntervalVecs::forced_import(&db, version, indexes)?;
|
||||||
let size = SizeVecs::forced_import(&db, version, indexes)?;
|
let size = SizeVecs::forced_import(&db, version, indexes)?;
|
||||||
let weight = WeightVecs::forced_import(&db, version, indexes)?;
|
let weight = WeightVecs::forced_import(&db, version, indexes)?;
|
||||||
let time = TimeVecs::forced_import(&db, version)?;
|
let time = TimeVecs::forced_import(&db, version, indexes)?;
|
||||||
let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?;
|
let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?;
|
||||||
let halving = HalvingVecs::forced_import(&db, version, indexes)?;
|
let halving = HalvingVecs::forced_import(&db, version, indexes)?;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ use brk_error::Result;
|
|||||||
use brk_types::{Date, Height, Version};
|
use brk_types::{Date, Height, Version};
|
||||||
use vecdb::{Database, EagerVec, ImportableVec, LazyVecFrom1, ReadableCloneableVec};
|
use vecdb::{Database, EagerVec, ImportableVec, LazyVecFrom1, ReadableCloneableVec};
|
||||||
|
|
||||||
use super::Vecs;
|
use super::{TimestampIndexes, Vecs};
|
||||||
use crate::internal::EagerIndexes;
|
use crate::indexes;
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
|
pub(crate) fn forced_import(
|
||||||
|
db: &Database,
|
||||||
|
version: Version,
|
||||||
|
indexes: &indexes::Vecs,
|
||||||
|
) -> Result<Self> {
|
||||||
let timestamp_monotonic =
|
let timestamp_monotonic =
|
||||||
EagerVec::forced_import(db, "timestamp_monotonic", version)?;
|
EagerVec::forced_import(db, "timestamp_monotonic", version)?;
|
||||||
|
|
||||||
@@ -18,7 +22,34 @@ impl Vecs {
|
|||||||
|_height: Height, timestamp| Date::from(timestamp),
|
|_height: Height, timestamp| Date::from(timestamp),
|
||||||
),
|
),
|
||||||
timestamp_monotonic,
|
timestamp_monotonic,
|
||||||
timestamp: EagerIndexes::forced_import(db, "timestamp", version)?,
|
timestamp: TimestampIndexes::forced_import(db, version, indexes)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TimestampIndexes {
|
||||||
|
fn forced_import(
|
||||||
|
db: &Database,
|
||||||
|
version: Version,
|
||||||
|
indexes: &indexes::Vecs,
|
||||||
|
) -> Result<Self> {
|
||||||
|
macro_rules! period {
|
||||||
|
($field:ident) => {
|
||||||
|
LazyVecFrom1::init(
|
||||||
|
"timestamp",
|
||||||
|
version,
|
||||||
|
indexes.$field.first_height.read_only_boxed_clone(),
|
||||||
|
|idx, _: Height| idx.to_timestamp(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! epoch {
|
||||||
|
($field:ident) => {
|
||||||
|
ImportableVec::forced_import(db, "timestamp", version)?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(crate::indexes_from!(period, epoch)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ mod compute;
|
|||||||
mod import;
|
mod import;
|
||||||
mod vecs;
|
mod vecs;
|
||||||
|
|
||||||
pub use vecs::Vecs;
|
pub use vecs::{TimestampIndexes, Vecs};
|
||||||
|
|||||||
@@ -1,13 +1,75 @@
|
|||||||
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{Date, Height, Timestamp};
|
use brk_types::{
|
||||||
use vecdb::{EagerVec, LazyVecFrom1, PcoVec, Rw, StorageMode};
|
Date, Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour12, Hour4, Minute1,
|
||||||
|
Minute10, Minute30, Minute5, Month1, Month3, Month6, Timestamp, Week1, Year1, Year10,
|
||||||
|
};
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use vecdb::{EagerVec, Exit, LazyVecFrom1, PcoVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::EagerIndexes;
|
use crate::{ComputeIndexes, indexes, internal::Indexes};
|
||||||
|
|
||||||
/// Timestamp and date metrics for blocks
|
/// Timestamp and date metrics for blocks
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
pub date: LazyVecFrom1<Height, Date, Height, Timestamp>,
|
pub date: LazyVecFrom1<Height, Date, Height, Timestamp>,
|
||||||
pub timestamp_monotonic: M::Stored<EagerVec<PcoVec<Height, Timestamp>>>,
|
pub timestamp_monotonic: M::Stored<EagerVec<PcoVec<Height, Timestamp>>>,
|
||||||
pub timestamp: EagerIndexes<Timestamp, M>,
|
pub timestamp: TimestampIndexes<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Per-period timestamp indexes.
|
||||||
|
///
|
||||||
|
/// Time-based periods (minute1–year10) are lazy: `idx.to_timestamp()` is a pure
|
||||||
|
/// function of the index, so no storage or decompression is needed.
|
||||||
|
/// Epoch-based periods (halvingepoch, difficultyepoch) are eager: their timestamps
|
||||||
|
/// come from block data via `compute_indirect`.
|
||||||
|
#[derive(Deref, DerefMut, Traversable)]
|
||||||
|
#[traversable(transparent)]
|
||||||
|
pub struct TimestampIndexes<M: StorageMode = Rw>(
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub Indexes<
|
||||||
|
LazyVecFrom1<Minute1, Timestamp, Minute1, Height>,
|
||||||
|
LazyVecFrom1<Minute5, Timestamp, Minute5, Height>,
|
||||||
|
LazyVecFrom1<Minute10, Timestamp, Minute10, Height>,
|
||||||
|
LazyVecFrom1<Minute30, Timestamp, Minute30, Height>,
|
||||||
|
LazyVecFrom1<Hour1, Timestamp, Hour1, Height>,
|
||||||
|
LazyVecFrom1<Hour4, Timestamp, Hour4, Height>,
|
||||||
|
LazyVecFrom1<Hour12, Timestamp, Hour12, Height>,
|
||||||
|
LazyVecFrom1<Day1, Timestamp, Day1, Height>,
|
||||||
|
LazyVecFrom1<Day3, Timestamp, Day3, Height>,
|
||||||
|
LazyVecFrom1<Week1, Timestamp, Week1, Height>,
|
||||||
|
LazyVecFrom1<Month1, Timestamp, Month1, Height>,
|
||||||
|
LazyVecFrom1<Month3, Timestamp, Month3, Height>,
|
||||||
|
LazyVecFrom1<Month6, Timestamp, Month6, Height>,
|
||||||
|
LazyVecFrom1<Year1, Timestamp, Year1, Height>,
|
||||||
|
LazyVecFrom1<Year10, Timestamp, Year10, Height>,
|
||||||
|
M::Stored<EagerVec<PcoVec<HalvingEpoch, Timestamp>>>,
|
||||||
|
M::Stored<EagerVec<PcoVec<DifficultyEpoch, Timestamp>>>,
|
||||||
|
>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl TimestampIndexes {
|
||||||
|
/// Compute epoch timestamps via indirect lookup from block timestamps.
|
||||||
|
/// Time-based periods are lazy (idx.to_timestamp()) and need no compute.
|
||||||
|
pub(crate) fn compute(
|
||||||
|
&mut self,
|
||||||
|
indexer: &brk_indexer::Indexer,
|
||||||
|
indexes: &indexes::Vecs,
|
||||||
|
starting_indexes: &ComputeIndexes,
|
||||||
|
exit: &Exit,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.halvingepoch.compute_indirect(
|
||||||
|
starting_indexes.halvingepoch,
|
||||||
|
&indexes.halvingepoch.first_height,
|
||||||
|
&indexer.vecs.blocks.timestamp,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
self.difficultyepoch.compute_indirect(
|
||||||
|
starting_indexes.difficultyepoch,
|
||||||
|
&indexes.difficultyepoch.first_height,
|
||||||
|
&indexer.vecs.blocks.timestamp,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl Vecs {
|
|||||||
|
|
||||||
self.hodl_bank.compute_cumulative_transformed_binary(
|
self.hodl_bank.compute_cumulative_transformed_binary(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&self.vocdd_365d_median,
|
&self.vocdd_365d_median,
|
||||||
|price, median| StoredF64::from(f64::from(price) - f64::from(median)),
|
|price, median| StoredF64::from(f64::from(price) - f64::from(median)),
|
||||||
exit,
|
exit,
|
||||||
@@ -31,7 +31,7 @@ impl Vecs {
|
|||||||
|
|
||||||
self.reserve_risk.height.compute_divide(
|
self.reserve_risk.height.compute_divide(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&self.hodl_bank,
|
&self.hodl_bank,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ impl Vecs {
|
|||||||
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
||||||
vec.compute_multiply(
|
vec.compute_multiply(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&coinblocks_destroyed.height,
|
&coinblocks_destroyed.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
@@ -56,7 +56,7 @@ impl Vecs {
|
|||||||
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
||||||
vec.compute_multiply(
|
vec.compute_multiply(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&activity.coinblocks_created.height,
|
&activity.coinblocks_created.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
@@ -67,7 +67,7 @@ impl Vecs {
|
|||||||
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
||||||
vec.compute_multiply(
|
vec.compute_multiply(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&activity.coinblocks_stored.height,
|
&activity.coinblocks_stored.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
@@ -81,7 +81,7 @@ impl Vecs {
|
|||||||
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
.compute(starting_indexes.height, &window_starts, exit, |vec| {
|
||||||
vec.compute_transform3(
|
vec.compute_transform3(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&coindays_destroyed.height,
|
&coindays_destroyed.height,
|
||||||
circulating_supply,
|
circulating_supply,
|
||||||
|(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| {
|
|(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ pub(crate) fn process_blocks(
|
|||||||
let txindex_to_input_count = &indexes.txindex.input_count;
|
let txindex_to_input_count = &indexes.txindex.input_count;
|
||||||
|
|
||||||
// From price - use cents for computation:
|
// From price - use cents for computation:
|
||||||
let height_to_price = &prices.price.cents;
|
let height_to_price = &prices.price.cents.height;
|
||||||
|
|
||||||
// Access pre-computed vectors from context for thread-safe access
|
// Access pre-computed vectors from context for thread-safe access
|
||||||
let height_to_price_vec = &ctx.height_to_price;
|
let height_to_price_vec = &ctx.height_to_price;
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ impl ComputeContext {
|
|||||||
blocks.time.timestamp_monotonic.collect();
|
blocks.time.timestamp_monotonic.collect();
|
||||||
|
|
||||||
let height_to_price: Vec<Cents> =
|
let height_to_price: Vec<Cents> =
|
||||||
prices.price.cents.collect();
|
prices.price.cents.height.collect();
|
||||||
|
|
||||||
// Build sparse table for O(1) range max queries on prices
|
// Build sparse table for O(1) range max queries on prices
|
||||||
// Used for computing peak price during UTXO holding periods (peak regret)
|
// Used for computing peak price during UTXO holding periods (peak regret)
|
||||||
|
|||||||
@@ -855,14 +855,14 @@ impl RealizedBase {
|
|||||||
|
|
||||||
self.realized_price_extra.compute_ratio(
|
self.realized_price_extra.compute_ratio(
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&self.realized_price.usd.height,
|
&self.realized_price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.investor_price_extra.compute_ratio(
|
self.investor_price_extra.compute_ratio(
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&self.investor_price.usd.height,
|
&self.investor_price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ impl UnrealizedBase {
|
|||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&self.investor_cap_in_loss_raw,
|
&self.investor_cap_in_loss_raw,
|
||||||
&self.invested_capital_in_loss_raw,
|
&self.invested_capital_in_loss_raw,
|
||||||
&prices.price.cents,
|
&prices.price.cents.height,
|
||||||
|(h, investor_cap, invested_cap, spot, ..)| {
|
|(h, investor_cap, invested_cap, spot, ..)| {
|
||||||
if invested_cap.inner() == 0 {
|
if invested_cap.inner() == 0 {
|
||||||
return (h, Dollars::ZERO);
|
return (h, Dollars::ZERO);
|
||||||
@@ -410,7 +410,7 @@ impl UnrealizedBase {
|
|||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&self.investor_cap_in_profit_raw,
|
&self.investor_cap_in_profit_raw,
|
||||||
&self.invested_capital_in_profit_raw,
|
&self.invested_capital_in_profit_raw,
|
||||||
&prices.price.cents,
|
&prices.price.cents.height,
|
||||||
|(h, investor_cap, invested_cap, spot, ..)| {
|
|(h, investor_cap, invested_cap, spot, ..)| {
|
||||||
if invested_cap.inner() == 0 {
|
if invested_cap.inner() == 0 {
|
||||||
return (h, Dollars::ZERO);
|
return (h, Dollars::ZERO);
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ impl Vecs {
|
|||||||
// Recover chain_state from stored values
|
// Recover chain_state from stored values
|
||||||
debug!("recovering chain_state from stored values");
|
debug!("recovering chain_state from stored values");
|
||||||
let height_to_timestamp = &blocks.time.timestamp_monotonic;
|
let height_to_timestamp = &blocks.time.timestamp_monotonic;
|
||||||
let height_to_price = &prices.price.cents;
|
let height_to_price = &prices.price.cents.height;
|
||||||
|
|
||||||
let end = usize::from(recovered_height);
|
let end = usize::from(recovered_height);
|
||||||
let timestamp_data: Vec<_> = height_to_timestamp.collect_range_at(0, end);
|
let timestamp_data: Vec<_> = height_to_timestamp.collect_range_at(0, end);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ impl ComputedFromHeightRatioExtended {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let close_price = &prices.price.usd;
|
let close_price = &prices.price.usd.height;
|
||||||
self.base
|
self.base
|
||||||
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
|
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
|
||||||
self.extended
|
self.extended
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl ValueFromHeightFull {
|
|||||||
.compute_binary::<Sats, Dollars, SatsToDollars>(
|
.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.base.sats.height,
|
&self.base.sats.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ impl ValueFromHeightLast {
|
|||||||
self.base.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
self.base.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.base.sats.height,
|
&self.base.sats.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ impl LazyComputedValueFromHeightCumulative {
|
|||||||
.compute_binary::<Sats, Dollars, SatsToDollars>(
|
.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.base.sats.height,
|
&self.base.sats.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ impl ValueFromHeightSumCumulative {
|
|||||||
.compute_binary::<Sats, Dollars, SatsToDollars>(
|
.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.base.sats.height,
|
&self.base.sats.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl ValueFromHeight {
|
|||||||
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.sats,
|
&self.sats,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ impl Vecs {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.price_ath.usd.height.compute_all_time_high(
|
self.price_ath.usd.height.compute_all_time_high(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ impl Vecs {
|
|||||||
self.days_since_price_ath.height.compute_transform2(
|
self.days_since_price_ath.height.compute_transform2(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&self.price_ath.usd.height,
|
&self.price_ath.usd.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
|(i, ath, price, slf)| {
|
|(i, ath, price, slf)| {
|
||||||
if prev.is_none() {
|
if prev.is_none() {
|
||||||
let i = i.to_usize();
|
let i = i.to_usize();
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ impl Vecs {
|
|||||||
{
|
{
|
||||||
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&average_price.usd.height,
|
&average_price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
@@ -165,7 +165,7 @@ impl Vecs {
|
|||||||
{
|
{
|
||||||
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&lookback_price.usd.height,
|
&lookback_price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
@@ -268,7 +268,7 @@ impl Vecs {
|
|||||||
|
|
||||||
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&average_price.usd.height,
|
&average_price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ impl Vecs {
|
|||||||
|
|
||||||
// Stochastic Oscillator: K = (close - low_2w) / (high_2w - low_2w) * 100
|
// Stochastic Oscillator: K = (close - low_2w) / (high_2w - low_2w) * 100
|
||||||
{
|
{
|
||||||
let price = &prices.price.usd;
|
let price = &prices.price.usd.height;
|
||||||
self.stoch_k.height.compute_transform3(
|
self.stoch_k.height.compute_transform3(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
price,
|
price,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub(super) fn compute(
|
|||||||
starting_indexes: &ComputeIndexes,
|
starting_indexes: &ComputeIndexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let source_version = prices.price.usd.version();
|
let source_version = prices.price.usd.height.version();
|
||||||
|
|
||||||
chain
|
chain
|
||||||
.line
|
.line
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ impl Vecs {
|
|||||||
starting_indexes: &ComputeIndexes,
|
starting_indexes: &ComputeIndexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let close_data: Vec<Dollars> = prices.price.usd.collect();
|
let close_data: Vec<Dollars> = prices.price.usd.height.collect();
|
||||||
|
|
||||||
for (price_ago, days) in self.price_ago.iter_mut_with_days() {
|
for (price_ago, days) in self.price_ago.iter_mut_with_days() {
|
||||||
let window_starts = blocks.count.start_vec(days as usize);
|
let window_starts = blocks.count.start_vec(days as usize);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ impl Vecs {
|
|||||||
starting_indexes: &ComputeIndexes,
|
starting_indexes: &ComputeIndexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let close = &prices.price.usd;
|
let close = &prices.price.usd.height;
|
||||||
|
|
||||||
for (sma, period) in [
|
for (sma, period) in [
|
||||||
(&mut self.price_1w_sma, 7),
|
(&mut self.price_1w_sma, 7),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ impl Vecs {
|
|||||||
starting_indexes: &ComputeIndexes,
|
starting_indexes: &ComputeIndexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let price = &prices.price.usd;
|
let price = &prices.price.usd.height;
|
||||||
|
|
||||||
self.price_1w_min.usd.height.compute_rolling_min_from_starts(
|
self.price_1w_min.usd.height.compute_rolling_min_from_starts(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl Vecs {
|
|||||||
{
|
{
|
||||||
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&prices.price.usd,
|
&prices.price.usd.height,
|
||||||
&lookback_price.usd.height,
|
&lookback_price.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{Cents, Dollars, Height, OHLCCents, OHLCDollars, OHLCSats, Sats};
|
use brk_types::{Cents, Dollars, OHLCCents, OHLCDollars, OHLCSats, Sats};
|
||||||
use vecdb::{LazyVecFrom1, PcoVec, Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes};
|
use crate::internal::{
|
||||||
|
ComputedFromHeightLast, ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes,
|
||||||
|
LazyFromHeightLast,
|
||||||
|
};
|
||||||
|
|
||||||
use super::ohlcs::{LazyOhlcVecs, OhlcVecs};
|
use super::ohlcs::{LazyOhlcVecs, OhlcVecs};
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ pub struct OhlcByUnit<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct PriceByUnit<M: StorageMode = Rw> {
|
pub struct PriceByUnit<M: StorageMode = Rw> {
|
||||||
pub cents: M::Stored<PcoVec<Height, Cents>>,
|
pub cents: ComputedFromHeightLast<Cents, M>,
|
||||||
pub usd: LazyVecFrom1<Height, Dollars, Height, Cents>,
|
pub usd: LazyFromHeightLast<Dollars, Cents>,
|
||||||
pub sats: LazyVecFrom1<Height, Sats, Height, Cents>,
|
pub sats: LazyFromHeightLast<Sats, Cents>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ impl Vecs {
|
|||||||
self.split
|
self.split
|
||||||
.open
|
.open
|
||||||
.cents
|
.cents
|
||||||
.compute_first(starting_indexes, &self.price.cents, indexes, exit)?;
|
.compute_first(starting_indexes, &self.price.cents.height, indexes, exit)?;
|
||||||
self.split
|
self.split
|
||||||
.high
|
.high
|
||||||
.cents
|
.cents
|
||||||
.compute_max(starting_indexes, &self.price.cents, indexes, exit)?;
|
.compute_max(starting_indexes, &self.price.cents.height, indexes, exit)?;
|
||||||
self.split
|
self.split
|
||||||
.low
|
.low
|
||||||
.cents
|
.cents
|
||||||
.compute_min(starting_indexes, &self.price.cents, indexes, exit)?;
|
.compute_min(starting_indexes, &self.price.cents.height, indexes, exit)?;
|
||||||
self.ohlc.cents.compute_from_split(
|
self.ohlc.cents.compute_from_split(
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
&self.split.open.cents,
|
&self.split.open.cents,
|
||||||
@@ -55,6 +55,7 @@ impl Vecs {
|
|||||||
indexer.vecs.outputs.value.version() + indexer.vecs.outputs.outputtype.version();
|
indexer.vecs.outputs.value.version() + indexer.vecs.outputs.outputtype.version();
|
||||||
self.price
|
self.price
|
||||||
.cents
|
.cents
|
||||||
|
.height
|
||||||
.validate_computed_version_or_reset(source_version)?;
|
.validate_computed_version_or_reset(source_version)?;
|
||||||
|
|
||||||
let total_heights = indexer.vecs.blocks.timestamp.len();
|
let total_heights = indexer.vecs.blocks.timestamp.len();
|
||||||
@@ -64,27 +65,32 @@ impl Vecs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reorg: truncate to starting_indexes
|
// Reorg: truncate to starting_indexes
|
||||||
let truncate_to = self.price.cents.len().min(starting_indexes.height.to_usize());
|
let truncate_to = self
|
||||||
self.price.cents.truncate_if_needed_at(truncate_to)?;
|
.price
|
||||||
|
.cents
|
||||||
|
.height
|
||||||
|
.len()
|
||||||
|
.min(starting_indexes.height.to_usize());
|
||||||
|
self.price.cents.height.truncate_if_needed_at(truncate_to)?;
|
||||||
|
|
||||||
if self.price.cents.len() < START_HEIGHT {
|
if self.price.cents.height.len() < START_HEIGHT {
|
||||||
for line in brk_oracle::PRICES.lines().skip(self.price.cents.len()) {
|
for line in brk_oracle::PRICES.lines().skip(self.price.cents.height.len()) {
|
||||||
if self.price.cents.len() >= START_HEIGHT {
|
if self.price.cents.height.len() >= START_HEIGHT {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let dollars: f64 = line.parse().unwrap_or(0.0);
|
let dollars: f64 = line.parse().unwrap_or(0.0);
|
||||||
let cents = (dollars * 100.0).round() as u64;
|
let cents = (dollars * 100.0).round() as u64;
|
||||||
self.price.cents.push(Cents::new(cents));
|
self.price.cents.height.push(Cents::new(cents));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.price.cents.len() >= total_heights {
|
if self.price.cents.height.len() >= total_heights {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let committed = self.price.cents.len();
|
let committed = self.price.cents.height.len();
|
||||||
let prev_cents = self.price.cents.collect_one_at(committed - 1).unwrap();
|
let prev_cents = self.price.cents.height.collect_one_at(committed - 1).unwrap();
|
||||||
let seed_bin = cents_to_bin(prev_cents.inner() as f64);
|
let seed_bin = cents_to_bin(prev_cents.inner() as f64);
|
||||||
let warmup = config.window_size.min(committed - START_HEIGHT);
|
let warmup = config.window_size.min(committed - START_HEIGHT);
|
||||||
let mut oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
|
let mut oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
|
||||||
@@ -100,7 +106,7 @@ impl Vecs {
|
|||||||
let ref_bins = Self::feed_blocks(&mut oracle, indexer, committed..total_heights);
|
let ref_bins = Self::feed_blocks(&mut oracle, indexer, committed..total_heights);
|
||||||
|
|
||||||
for (i, ref_bin) in ref_bins.into_iter().enumerate() {
|
for (i, ref_bin) in ref_bins.into_iter().enumerate() {
|
||||||
self.price.cents.push(Cents::new(bin_to_cents(ref_bin)));
|
self.price.cents.height.push(Cents::new(bin_to_cents(ref_bin)));
|
||||||
|
|
||||||
let progress = ((i + 1) * 100 / num_new) as u8;
|
let progress = ((i + 1) * 100 / num_new) as u8;
|
||||||
if i > 0 && progress > ((i * 100 / num_new) as u8) {
|
if i > 0 && progress > ((i * 100 / num_new) as u8) {
|
||||||
@@ -110,10 +116,13 @@ impl Vecs {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let _lock = exit.lock();
|
let _lock = exit.lock();
|
||||||
self.price.cents.write()?;
|
self.price.cents.height.write()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Oracle prices complete: {} committed", self.price.cents.len());
|
info!(
|
||||||
|
"Oracle prices complete: {} committed",
|
||||||
|
self.price.cents.height.len()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -201,7 +210,12 @@ impl<M: StorageMode> Vecs<M> {
|
|||||||
pub fn live_oracle<IM: StorageMode>(&self, indexer: &Indexer<IM>) -> Result<Oracle> {
|
pub fn live_oracle<IM: StorageMode>(&self, indexer: &Indexer<IM>) -> Result<Oracle> {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let height = indexer.vecs.blocks.timestamp.len();
|
let height = indexer.vecs.blocks.timestamp.len();
|
||||||
let last_cents = self.price.cents.collect_one_at(self.price.cents.len() - 1).unwrap();
|
let last_cents = self
|
||||||
|
.price
|
||||||
|
.cents
|
||||||
|
.height
|
||||||
|
.collect_one_at(self.price.cents.height.len() - 1)
|
||||||
|
.unwrap();
|
||||||
let seed_bin = cents_to_bin(last_cents.inner() as f64);
|
let seed_bin = cents_to_bin(last_cents.inner() as f64);
|
||||||
let window_size = config.window_size;
|
let window_size = config.window_size;
|
||||||
let oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
|
let oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
|
||||||
|
|||||||
@@ -6,16 +6,14 @@ use std::path::Path;
|
|||||||
|
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::Version;
|
use brk_types::Version;
|
||||||
use vecdb::{
|
use vecdb::{Database, ReadableCloneableVec, Rw, StorageMode, PAGE_SIZE};
|
||||||
Database, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode,
|
|
||||||
PAGE_SIZE,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
indexes,
|
indexes,
|
||||||
internal::{
|
internal::{
|
||||||
CentsUnsignedToDollars, CentsUnsignedToSats, ComputedHeightDerivedLast, EagerIndexes,
|
CentsUnsignedToDollars, CentsUnsignedToSats, ComputedFromHeightLast,
|
||||||
LazyEagerIndexes, OhlcCentsToDollars, OhlcCentsToSats,
|
ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes, LazyFromHeightLast,
|
||||||
|
OhlcCentsToDollars, OhlcCentsToSats,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,7 +64,8 @@ impl Vecs {
|
|||||||
|
|
||||||
// ── Cents (eager, stored) ───────────────────────────────────
|
// ── Cents (eager, stored) ───────────────────────────────────
|
||||||
|
|
||||||
let price_cents = PcoVec::forced_import(db, "price_cents", version)?;
|
let price_cents =
|
||||||
|
ComputedFromHeightLast::forced_import(db, "price_cents", version, indexes)?;
|
||||||
|
|
||||||
let open_cents = EagerIndexes::forced_import(db, "price_open_cents", version)?;
|
let open_cents = EagerIndexes::forced_import(db, "price_open_cents", version)?;
|
||||||
let high_cents = EagerIndexes::forced_import(db, "price_high_cents", version)?;
|
let high_cents = EagerIndexes::forced_import(db, "price_high_cents", version)?;
|
||||||
@@ -74,7 +73,7 @@ impl Vecs {
|
|||||||
|
|
||||||
let close_cents = ComputedHeightDerivedLast::forced_import(
|
let close_cents = ComputedHeightDerivedLast::forced_import(
|
||||||
"price_close_cents",
|
"price_close_cents",
|
||||||
price_cents.read_only_boxed_clone(),
|
price_cents.height.read_only_boxed_clone(),
|
||||||
version,
|
version,
|
||||||
indexes,
|
indexes,
|
||||||
);
|
);
|
||||||
@@ -83,10 +82,11 @@ impl Vecs {
|
|||||||
|
|
||||||
// ── USD (lazy from cents) ───────────────────────────────────
|
// ── USD (lazy from cents) ───────────────────────────────────
|
||||||
|
|
||||||
let price_usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
|
let price_usd = LazyFromHeightLast::from_computed::<CentsUnsignedToDollars>(
|
||||||
"price",
|
"price",
|
||||||
version,
|
version,
|
||||||
price_cents.read_only_boxed_clone(),
|
price_cents.height.read_only_boxed_clone(),
|
||||||
|
&price_cents,
|
||||||
);
|
);
|
||||||
|
|
||||||
let open_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
|
let open_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
|
||||||
@@ -107,7 +107,7 @@ impl Vecs {
|
|||||||
|
|
||||||
let close_usd = ComputedHeightDerivedLast::forced_import(
|
let close_usd = ComputedHeightDerivedLast::forced_import(
|
||||||
"price_close",
|
"price_close",
|
||||||
price_usd.read_only_boxed_clone(),
|
price_usd.height.read_only_boxed_clone(),
|
||||||
version,
|
version,
|
||||||
indexes,
|
indexes,
|
||||||
);
|
);
|
||||||
@@ -120,10 +120,11 @@ impl Vecs {
|
|||||||
|
|
||||||
// ── Sats (lazy from cents, high↔low swapped) ───────────────
|
// ── Sats (lazy from cents, high↔low swapped) ───────────────
|
||||||
|
|
||||||
let price_sats = LazyVecFrom1::transformed::<CentsUnsignedToSats>(
|
let price_sats = LazyFromHeightLast::from_computed::<CentsUnsignedToSats>(
|
||||||
"price_sats",
|
"price_sats",
|
||||||
version,
|
version,
|
||||||
price_cents.read_only_boxed_clone(),
|
price_cents.height.read_only_boxed_clone(),
|
||||||
|
&price_cents,
|
||||||
);
|
);
|
||||||
|
|
||||||
let open_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
|
let open_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
|
||||||
@@ -145,7 +146,7 @@ impl Vecs {
|
|||||||
|
|
||||||
let close_sats = ComputedHeightDerivedLast::forced_import(
|
let close_sats = ComputedHeightDerivedLast::forced_import(
|
||||||
"price_close_sats",
|
"price_close_sats",
|
||||||
price_sats.read_only_boxed_clone(),
|
price_sats.height.read_only_boxed_clone(),
|
||||||
version,
|
version,
|
||||||
indexes,
|
indexes,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use schemars::JsonSchema;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use vecdb::{
|
use vecdb::{
|
||||||
BytesVec, BytesVecValue, Database, EagerVec, Exit, Formattable, ImportableVec, LazyVecFrom1,
|
BytesVec, BytesVecValue, Database, EagerVec, Exit, Formattable, ImportableVec, LazyVecFrom1,
|
||||||
ReadableCloneableVec, Rw, StorageMode, UnaryTransform,
|
ReadableCloneableVec, ReadableVec, Rw, StorageMode, UnaryTransform,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -47,7 +47,7 @@ pub struct OhlcVecs<T, M: StorageMode = Rw>(
|
|||||||
where
|
where
|
||||||
T: BytesVecValue + Formattable + Serialize + JsonSchema;
|
T: BytesVecValue + Formattable + Serialize + JsonSchema;
|
||||||
|
|
||||||
const EAGER_VERSION: Version = Version::ZERO;
|
const EAGER_VERSION: Version = Version::ONE;
|
||||||
|
|
||||||
impl<T> OhlcVecs<T>
|
impl<T> OhlcVecs<T>
|
||||||
where
|
where
|
||||||
@@ -84,14 +84,22 @@ impl OhlcVecs<OHLCCents> {
|
|||||||
&high.$field,
|
&high.$field,
|
||||||
&low.$field,
|
&low.$field,
|
||||||
&close.$field,
|
&close.$field,
|
||||||
|(idx, o, h, l, c, _)| {
|
|(idx, o, h, l, c, this)| {
|
||||||
(
|
(
|
||||||
idx,
|
idx,
|
||||||
OHLCCents {
|
if let Some(c) = c {
|
||||||
open: Open::new(o),
|
OHLCCents {
|
||||||
high: High::new(h),
|
open: Open::new(o),
|
||||||
low: Low::new(l),
|
high: High::new(h),
|
||||||
close: Close::new(c.unwrap_or_default()),
|
low: Low::new(l),
|
||||||
|
close: Close::new(c),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Empty period (no blocks): flat candle at previous close
|
||||||
|
let prev_close = Close::new(
|
||||||
|
this.collect_last().map_or(o, |prev| *prev.close),
|
||||||
|
);
|
||||||
|
OHLCCents::from(prev_close)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use super::{
|
|||||||
timestamp::INDEX_EPOCH,
|
timestamp::INDEX_EPOCH,
|
||||||
minute1::MINUTE1_INTERVAL, minute5::MINUTE5_INTERVAL, minute10::MINUTE10_INTERVAL,
|
minute1::MINUTE1_INTERVAL, minute5::MINUTE5_INTERVAL, minute10::MINUTE10_INTERVAL,
|
||||||
minute30::MINUTE30_INTERVAL, hour1::HOUR1_INTERVAL, hour4::HOUR4_INTERVAL,
|
minute30::MINUTE30_INTERVAL, hour1::HOUR1_INTERVAL, hour4::HOUR4_INTERVAL,
|
||||||
hour12::HOUR12_INTERVAL, day3::DAY3_INTERVAL,
|
hour12::HOUR12_INTERVAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
|
/// Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
|
||||||
|
|||||||
@@ -5235,9 +5235,9 @@ function createRatioPattern2(client, acc) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} MetricsTree_Prices_Price
|
* @typedef {Object} MetricsTree_Prices_Price
|
||||||
* @property {MetricPattern20<Cents>} cents
|
* @property {MetricPattern1<Cents>} cents
|
||||||
* @property {MetricPattern20<Dollars>} usd
|
* @property {MetricPattern1<Dollars>} usd
|
||||||
* @property {MetricPattern20<Sats>} sats
|
* @property {MetricPattern1<Sats>} sats
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7505,9 +7505,9 @@ class BrkClient extends BrkClientBase {
|
|||||||
sats: createMetricPattern2(this, 'price_ohlc_sats'),
|
sats: createMetricPattern2(this, 'price_ohlc_sats'),
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
cents: createMetricPattern20(this, 'price_cents'),
|
cents: createMetricPattern1(this, 'price_cents'),
|
||||||
usd: createMetricPattern20(this, 'price'),
|
usd: createMetricPattern1(this, 'price'),
|
||||||
sats: createMetricPattern20(this, 'price_sats'),
|
sats: createMetricPattern1(this, 'price_sats'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
distribution: {
|
distribution: {
|
||||||
|
|||||||
@@ -4526,9 +4526,9 @@ class MetricsTree_Prices_Price:
|
|||||||
"""Metrics tree node."""
|
"""Metrics tree node."""
|
||||||
|
|
||||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||||
self.cents: MetricPattern20[Cents] = MetricPattern20(client, 'price_cents')
|
self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_cents')
|
||||||
self.usd: MetricPattern20[Dollars] = MetricPattern20(client, 'price')
|
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price')
|
||||||
self.sats: MetricPattern20[Sats] = MetricPattern20(client, 'price_sats')
|
self.sats: MetricPattern1[Sats] = MetricPattern1(client, 'price_sats')
|
||||||
|
|
||||||
class MetricsTree_Prices:
|
class MetricsTree_Prices:
|
||||||
"""Metrics tree node."""
|
"""Metrics tree node."""
|
||||||
|
|||||||
@@ -1383,6 +1383,25 @@ export function createChart({ parent, brk, fitContent }) {
|
|||||||
serieses.addCandlestick({ ...common, colors: blueprint.colors }),
|
serieses.addCandlestick({ ...common, colors: blueprint.colors }),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "Price":
|
||||||
|
if (idx === "height" || idx.startsWith("minute")) {
|
||||||
|
pane.series.push(
|
||||||
|
serieses.addLine({
|
||||||
|
...common,
|
||||||
|
color: colors.default,
|
||||||
|
options: { ...common.options, priceLineVisible: true },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
pane.series.push(
|
||||||
|
serieses.addCandlestick({
|
||||||
|
...common,
|
||||||
|
metric: blueprint.ohlcMetric,
|
||||||
|
colors: blueprint.colors,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "Dots":
|
case "Dots":
|
||||||
pane.series.push(
|
pane.series.push(
|
||||||
serieses.addDots({
|
serieses.addDots({
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export function createMarketSection() {
|
|||||||
title: "Sats per Dollar",
|
title: "Sats per Dollar",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({
|
line({
|
||||||
metric: prices.split.close.sats,
|
metric: prices.price.sats,
|
||||||
name: "Sats/$",
|
name: "Sats/$",
|
||||||
unit: Unit.sats,
|
unit: Unit.sats,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -42,7 +42,14 @@
|
|||||||
* @property {BaselineSeriesPartialOptions} [options]
|
* @property {BaselineSeriesPartialOptions} [options]
|
||||||
* @typedef {BaseSeriesBlueprint & DotsBaselineSeriesBlueprintSpecific} DotsBaselineSeriesBlueprint
|
* @typedef {BaseSeriesBlueprint & DotsBaselineSeriesBlueprintSpecific} DotsBaselineSeriesBlueprint
|
||||||
*
|
*
|
||||||
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint | HistogramSeriesBlueprint | DotsSeriesBlueprint | DotsBaselineSeriesBlueprint} AnySeriesBlueprint
|
* @typedef {Object} PriceSeriesBlueprintSpecific
|
||||||
|
* @property {"Price"} type
|
||||||
|
* @property {AnyMetricPattern} ohlcMetric - OHLC metric for candlestick (>= 1h indexes)
|
||||||
|
* @property {[Color, Color]} [colors]
|
||||||
|
* @property {CandlestickSeriesPartialOptions} [options]
|
||||||
|
* @typedef {BaseSeriesBlueprint & PriceSeriesBlueprintSpecific} PriceSeriesBlueprint
|
||||||
|
*
|
||||||
|
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint | HistogramSeriesBlueprint | DotsSeriesBlueprint | DotsBaselineSeriesBlueprint | PriceSeriesBlueprint} AnySeriesBlueprint
|
||||||
*
|
*
|
||||||
* @typedef {AnySeriesBlueprint["type"]} SeriesType
|
* @typedef {AnySeriesBlueprint["type"]} SeriesType
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -40,24 +40,28 @@ export function init() {
|
|||||||
/** @type {Map<Unit, AnyFetchedSeriesBlueprint[]>} */
|
/** @type {Map<Unit, AnyFetchedSeriesBlueprint[]>} */
|
||||||
const result = new Map();
|
const result = new Map();
|
||||||
|
|
||||||
// USD price + option blueprints
|
const { ohlc, price } = brk.metrics.prices;
|
||||||
/** @type {FetchedCandlestickSeriesBlueprint} */
|
|
||||||
const usdPrice = {
|
|
||||||
type: "Candlestick",
|
|
||||||
title: "Price",
|
|
||||||
metric: brk.metrics.prices.ohlc.usd,
|
|
||||||
};
|
|
||||||
result.set(Unit.usd, [usdPrice, ...(optionTop.get(Unit.usd) ?? [])]);
|
|
||||||
|
|
||||||
// Sats price + option blueprints
|
result.set(Unit.usd, [
|
||||||
/** @type {FetchedCandlestickSeriesBlueprint} */
|
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||||
const satsPrice = {
|
type: "Price",
|
||||||
type: "Candlestick",
|
title: "Price",
|
||||||
title: "Price",
|
metric: price.usd,
|
||||||
metric: brk.metrics.prices.ohlc.sats,
|
ohlcMetric: ohlc.usd,
|
||||||
colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]),
|
}),
|
||||||
};
|
...(optionTop.get(Unit.usd) ?? []),
|
||||||
result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]);
|
]);
|
||||||
|
|
||||||
|
result.set(Unit.sats, [
|
||||||
|
/** @type {AnyFetchedSeriesBlueprint} */ ({
|
||||||
|
type: "Price",
|
||||||
|
title: "Price",
|
||||||
|
metric: price.sats,
|
||||||
|
ohlcMetric: ohlc.sats,
|
||||||
|
colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]),
|
||||||
|
}),
|
||||||
|
...(optionTop.get(Unit.sats) ?? []),
|
||||||
|
]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -70,9 +74,7 @@ export function init() {
|
|||||||
const unit = chart.panes[0].unit;
|
const unit = chart.panes[0].unit;
|
||||||
if (!priceSeries?.hasData() || !unit) return;
|
if (!priceSeries?.hasData() || !unit) return;
|
||||||
|
|
||||||
const last = /** @type {CandlestickData | undefined} */ (
|
const last = priceSeries.getData().at(-1);
|
||||||
priceSeries.getData().at(-1)
|
|
||||||
);
|
|
||||||
if (!last) return;
|
if (!last) return;
|
||||||
|
|
||||||
// Convert to sats if needed
|
// Convert to sats if needed
|
||||||
@@ -81,7 +83,13 @@ export function init() {
|
|||||||
? Math.floor(ONE_BTC_IN_SATS / latest)
|
? Math.floor(ONE_BTC_IN_SATS / latest)
|
||||||
: latest;
|
: latest;
|
||||||
|
|
||||||
priceSeries.update({ ...last, close });
|
if ("close" in last) {
|
||||||
|
// Candlestick data
|
||||||
|
priceSeries.update({ ...last, close });
|
||||||
|
} else {
|
||||||
|
// Line data
|
||||||
|
priceSeries.update({ ...last, value: close });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the setOption function
|
// Set up the setOption function
|
||||||
|
|||||||
Reference in New Issue
Block a user