mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 07:23:32 -07:00
global: snapshot
This commit is contained in:
@@ -3831,7 +3831,6 @@ pub struct SeriesTree_Scripts {
|
||||
pub raw: SeriesTree_Scripts_Raw,
|
||||
pub count: SeriesTree_Scripts_Count,
|
||||
pub value: SeriesTree_Scripts_Value,
|
||||
pub adoption: SeriesTree_Scripts_Adoption,
|
||||
}
|
||||
|
||||
impl SeriesTree_Scripts {
|
||||
@@ -3840,7 +3839,6 @@ impl SeriesTree_Scripts {
|
||||
raw: SeriesTree_Scripts_Raw::new(client.clone(), format!("{base_path}_raw")),
|
||||
count: SeriesTree_Scripts_Count::new(client.clone(), format!("{base_path}_count")),
|
||||
value: SeriesTree_Scripts_Value::new(client.clone(), format!("{base_path}_value")),
|
||||
adoption: SeriesTree_Scripts_Adoption::new(client.clone(), format!("{base_path}_adoption")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3938,7 +3936,6 @@ pub struct SeriesTree_Scripts_Count {
|
||||
pub op_return: BaseCumulativeSumPattern<StoredU64>,
|
||||
pub empty_output: BaseCumulativeSumPattern<StoredU64>,
|
||||
pub unknown_output: BaseCumulativeSumPattern<StoredU64>,
|
||||
pub segwit: BaseCumulativeSumPattern<StoredU64>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Scripts_Count {
|
||||
@@ -3956,7 +3953,6 @@ impl SeriesTree_Scripts_Count {
|
||||
op_return: BaseCumulativeSumPattern::new(client.clone(), "op_return_count".to_string()),
|
||||
empty_output: BaseCumulativeSumPattern::new(client.clone(), "empty_output_count".to_string()),
|
||||
unknown_output: BaseCumulativeSumPattern::new(client.clone(), "unknown_output_count".to_string()),
|
||||
segwit: BaseCumulativeSumPattern::new(client.clone(), "segwit_count".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3974,21 +3970,6 @@ impl SeriesTree_Scripts_Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Scripts_Adoption {
|
||||
pub taproot: BpsPercentRatioPattern3,
|
||||
pub segwit: BpsPercentRatioPattern3,
|
||||
}
|
||||
|
||||
impl SeriesTree_Scripts_Adoption {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
taproot: BpsPercentRatioPattern3::new(client.clone(), "taproot_adoption".to_string()),
|
||||
segwit: BpsPercentRatioPattern3::new(client.clone(), "segwit_adoption".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Mining {
|
||||
pub rewards: SeriesTree_Mining_Rewards,
|
||||
@@ -4009,7 +3990,7 @@ pub struct SeriesTree_Mining_Rewards {
|
||||
pub coinbase: BaseCumulativeSumPattern4,
|
||||
pub subsidy: SeriesTree_Mining_Rewards_Subsidy,
|
||||
pub fees: SeriesTree_Mining_Rewards_Fees,
|
||||
pub unclaimed: BaseCumulativeSumPattern4,
|
||||
pub unclaimed: SeriesTree_Mining_Rewards_Unclaimed,
|
||||
}
|
||||
|
||||
impl SeriesTree_Mining_Rewards {
|
||||
@@ -4018,7 +3999,7 @@ impl SeriesTree_Mining_Rewards {
|
||||
coinbase: BaseCumulativeSumPattern4::new(client.clone(), "coinbase".to_string()),
|
||||
subsidy: SeriesTree_Mining_Rewards_Subsidy::new(client.clone(), format!("{base_path}_subsidy")),
|
||||
fees: SeriesTree_Mining_Rewards_Fees::new(client.clone(), format!("{base_path}_fees")),
|
||||
unclaimed: BaseCumulativeSumPattern4::new(client.clone(), "unclaimed_rewards".to_string()),
|
||||
unclaimed: SeriesTree_Mining_Rewards_Unclaimed::new(client.clone(), format!("{base_path}_unclaimed")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4098,6 +4079,21 @@ impl SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Mining_Rewards_Unclaimed {
|
||||
pub base: BtcCentsSatsUsdPattern,
|
||||
pub cumulative: BtcCentsSatsUsdPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Mining_Rewards_Unclaimed {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
base: BtcCentsSatsUsdPattern::new(client.clone(), "unclaimed_rewards".to_string()),
|
||||
cumulative: BtcCentsSatsUsdPattern::new(client.clone(), "unclaimed_rewards_cumulative".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Mining_Hashrate {
|
||||
pub rate: SeriesTree_Mining_Hashrate_Rate,
|
||||
@@ -4279,10 +4275,6 @@ pub struct SeriesTree_Cointime_Prices {
|
||||
pub active: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
pub true_market_mean: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
pub cointime: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
pub transfer: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
pub balanced: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
pub terminal: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
pub delta: BpsCentsPercentilesRatioSatsUsdPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cointime_Prices {
|
||||
@@ -4292,10 +4284,6 @@ impl SeriesTree_Cointime_Prices {
|
||||
active: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "active_price".to_string()),
|
||||
true_market_mean: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "true_market_mean".to_string()),
|
||||
cointime: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "cointime_price".to_string()),
|
||||
transfer: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "transfer_price".to_string()),
|
||||
balanced: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "balanced_price".to_string()),
|
||||
terminal: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "terminal_price".to_string()),
|
||||
delta: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "delta_price".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ impl Vecs {
|
||||
self.adjustment.bps.height.compute_ratio_change(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.blocks.difficulty,
|
||||
1,
|
||||
2016,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Vecs {
|
||||
adjustment: PercentPerBlock::forced_import(
|
||||
db,
|
||||
"difficulty_adjustment",
|
||||
version,
|
||||
version + Version::ONE,
|
||||
indexes,
|
||||
)?,
|
||||
epoch: PerBlock::forced_import(db, "difficulty_epoch", version, indexes)?,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Cents, Indexes};
|
||||
use brk_types::{Dollars, Indexes};
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::super::{activity, value};
|
||||
@@ -59,7 +59,8 @@ impl Vecs {
|
||||
let destroyed: f64 = *destroyed;
|
||||
let supply: f64 = supply.into();
|
||||
let stored: f64 = *stored;
|
||||
(i, Cents::from(destroyed * supply / stored))
|
||||
let usd = Dollars::from(destroyed * supply / stored);
|
||||
(i, usd.to_cents())
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
@@ -3,7 +3,10 @@ use brk_types::Version;
|
||||
use vecdb::Database;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{indexes, internal::{FiatPerBlock, RatioPerBlock}};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{FiatPerBlock, RatioPerBlock},
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn forced_import(
|
||||
@@ -16,7 +19,12 @@ impl Vecs {
|
||||
investor: FiatPerBlock::forced_import(db, "investor_cap", version, indexes)?,
|
||||
vaulted: FiatPerBlock::forced_import(db, "vaulted_cap", version, indexes)?,
|
||||
active: FiatPerBlock::forced_import(db, "active_cap", version, indexes)?,
|
||||
cointime: FiatPerBlock::forced_import(db, "cointime_cap", version, indexes)?,
|
||||
cointime: FiatPerBlock::forced_import(
|
||||
db,
|
||||
"cointime_cap",
|
||||
version + Version::ONE,
|
||||
indexes,
|
||||
)?,
|
||||
aviv: RatioPerBlock::forced_import(db, "aviv", version, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ impl Vecs {
|
||||
let activity = ActivityVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
let supply = SupplyVecs::forced_import(&db, v1, indexes)?;
|
||||
let value = ValueVecs::forced_import(&db, v1, indexes, cached_starts)?;
|
||||
let cap = CapVecs::forced_import(&db, v1, indexes)?;
|
||||
let prices = PricesVecs::forced_import(&db, version, indexes)?;
|
||||
let cap = CapVecs::forced_import(&db, version + Version::TWO, indexes)?;
|
||||
let prices = PricesVecs::forced_import(&db, version + Version::new(3), indexes)?;
|
||||
let adjusted = AdjustedVecs::forced_import(&db, version, indexes)?;
|
||||
let reserve_risk = ReserveRiskVecs::forced_import(&db, v1, indexes)?;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Cents, Indexes};
|
||||
use vecdb::{Exit, VecIndex};
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::super::{activity, cap, supply};
|
||||
use super::Vecs;
|
||||
@@ -21,7 +21,6 @@ impl Vecs {
|
||||
let all_metrics = &distribution.utxo_cohorts.all.metrics;
|
||||
let circulating_supply = &all_metrics.supply.total.btc.height;
|
||||
let realized_price = &all_metrics.realized.price.cents.height;
|
||||
let realized_cap = &all_metrics.realized.cap.cents.height;
|
||||
|
||||
self.vaulted.compute_all(
|
||||
prices,
|
||||
@@ -45,10 +44,13 @@ impl Vecs {
|
||||
starting_indexes,
|
||||
exit,
|
||||
|v| {
|
||||
Ok(v.compute_multiply(
|
||||
Ok(v.compute_transform2(
|
||||
starting_indexes.height,
|
||||
realized_price,
|
||||
&activity.liveliness.height,
|
||||
|(i, price, liveliness, ..)| {
|
||||
(i, Cents::from(f64::from(price) / f64::from(liveliness)))
|
||||
},
|
||||
exit,
|
||||
)?)
|
||||
},
|
||||
@@ -71,7 +73,6 @@ impl Vecs {
|
||||
},
|
||||
)?;
|
||||
|
||||
// cointime_price = cointime_cap / circulating_supply
|
||||
self.cointime.compute_all(
|
||||
prices,
|
||||
starting_indexes,
|
||||
@@ -89,72 +90,6 @@ impl Vecs {
|
||||
},
|
||||
)?;
|
||||
|
||||
// transfer_price = cointime_price - vaulted_price
|
||||
self.transfer.cents.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.cointime.cents.height,
|
||||
&self.vaulted.cents.height,
|
||||
|(i, cointime, vaulted, ..)| (i, cointime.saturating_sub(vaulted)),
|
||||
exit,
|
||||
)?;
|
||||
self.transfer.compute_rest(prices, starting_indexes, exit)?;
|
||||
|
||||
// balanced_price = (realized_price + transfer_price) / 2
|
||||
self.balanced.cents.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
realized_price,
|
||||
&self.transfer.cents.height,
|
||||
|(i, realized, transfer, ..)| (i, (realized + transfer) / 2u64),
|
||||
exit,
|
||||
)?;
|
||||
self.balanced.compute_rest(prices, starting_indexes, exit)?;
|
||||
|
||||
// terminal_price = 21M × transfer_price / circulating_supply_btc
|
||||
self.terminal.cents.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.transfer.cents.height,
|
||||
circulating_supply,
|
||||
|(i, transfer, supply_btc, ..)| {
|
||||
let supply = f64::from(supply_btc);
|
||||
if supply == 0.0 {
|
||||
(i, Cents::ZERO)
|
||||
} else {
|
||||
(i, Cents::from(f64::from(transfer) * 21_000_000.0 / supply))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.terminal.compute_rest(prices, starting_indexes, exit)?;
|
||||
|
||||
// cumulative_market_cap = Σ(market_cap) in dollars
|
||||
self.cumulative_market_cap
|
||||
.height
|
||||
.compute_cumulative(
|
||||
starting_indexes.height,
|
||||
&all_metrics.supply.total.cents.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// delta_price = (realized_cap - average_cap) / circulating_supply
|
||||
// average_cap = cumulative_market_cap / (height + 1)
|
||||
self.delta.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
realized_cap,
|
||||
&self.cumulative_market_cap.height,
|
||||
circulating_supply,
|
||||
|(i, realized_cap_cents, cum_mcap_dollars, supply_btc, ..)| {
|
||||
let supply = f64::from(supply_btc);
|
||||
if supply == 0.0 {
|
||||
return (i, Cents::ZERO);
|
||||
}
|
||||
let avg_cap_cents = f64::from(cum_mcap_dollars) * 100.0 / (i.to_usize() + 1) as f64;
|
||||
let delta = (f64::from(realized_cap_cents) - avg_cap_cents) / supply;
|
||||
(i, Cents::from(delta.max(0.0)))
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.delta.compute_rest(prices, starting_indexes, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use vecdb::Database;
|
||||
use super::Vecs;
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{PerBlock, PriceWithRatioExtendedPerBlock},
|
||||
internal::PriceWithRatioExtendedPerBlock,
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
@@ -25,16 +25,6 @@ impl Vecs {
|
||||
active: import!("active_price"),
|
||||
true_market_mean: import!("true_market_mean"),
|
||||
cointime: import!("cointime_price"),
|
||||
transfer: import!("transfer_price"),
|
||||
balanced: import!("balanced_price"),
|
||||
terminal: import!("terminal_price"),
|
||||
delta: import!("delta_price"),
|
||||
cumulative_market_cap: PerBlock::forced_import(
|
||||
db,
|
||||
"cumulative_market_cap",
|
||||
version,
|
||||
indexes,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,4 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub active: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub true_market_mean: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub cointime: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub transfer: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub balanced: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub terminal: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub delta: PriceWithRatioExtendedPerBlock<M>,
|
||||
|
||||
#[traversable(hidden)]
|
||||
pub cumulative_market_cap: PerBlock<Dollars, M>,
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
internal::{
|
||||
CentsUnsignedToDollars, PerBlock, PerBlockCumulative,
|
||||
PerBlockCumulativeWithSums, FiatPerBlockCumulativeWithSums,
|
||||
LazyPerBlock, PercentPerBlock, PercentRollingWindows, Price,
|
||||
LazyPerBlock, PercentPerBlock, PercentRollingWindows,
|
||||
PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32,
|
||||
RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDollarsBp32,
|
||||
RatioPerBlockPercentiles, RatioPerBlockStdDevBands, RatioSma, RollingWindows,
|
||||
@@ -70,8 +70,6 @@ pub struct RealizedPeakRegret<M: StorageMode = Rw> {
|
||||
#[derive(Traversable)]
|
||||
pub struct RealizedInvestor<M: StorageMode = Rw> {
|
||||
pub price: PriceWithRatioExtendedPerBlock<M>,
|
||||
pub investor_lower_band: Price<PerBlock<Cents, M>>,
|
||||
pub investor_upper_band: Price<PerBlock<Cents, M>>,
|
||||
#[traversable(hidden)]
|
||||
pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||
}
|
||||
@@ -176,8 +174,6 @@ impl RealizedFull {
|
||||
// Investor
|
||||
let investor = RealizedInvestor {
|
||||
price: cfg.import("investor_price", v0)?,
|
||||
investor_lower_band: cfg.import("investor_lower_band", v0)?,
|
||||
investor_upper_band: cfg.import("investor_upper_band", v0)?,
|
||||
cap_raw: cfg.import("investor_cap_raw", v0)?,
|
||||
};
|
||||
|
||||
@@ -486,46 +482,6 @@ impl RealizedFull {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.investor
|
||||
.investor_lower_band
|
||||
.cents
|
||||
.height
|
||||
.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.core.minimal.price.cents.height,
|
||||
&self.investor.price.cents.height,
|
||||
|(i, rp, ip, ..)| {
|
||||
let rp = rp.as_u128();
|
||||
let ip = ip.as_u128();
|
||||
if ip == 0 {
|
||||
(i, Cents::ZERO)
|
||||
} else {
|
||||
(i, Cents::from(rp * rp / ip))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.investor
|
||||
.investor_upper_band
|
||||
.cents
|
||||
.height
|
||||
.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.investor.price.cents.height,
|
||||
&self.core.minimal.price.cents.height,
|
||||
|(i, ip, rp, ..)| {
|
||||
let ip = ip.as_u128();
|
||||
let rp = rp.as_u128();
|
||||
if rp == 0 {
|
||||
(i, Cents::ZERO)
|
||||
} else {
|
||||
(i, Cents::from(ip * ip / rp))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Sell-side risk ratios
|
||||
for (ssrr, rv) in self
|
||||
.sell_side_risk_ratio
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use brk_types::{BasisPoints32, Cents, StoredF32, StoredF64, StoredU64, Timestamp};
|
||||
use brk_types::{BasisPoints32, Cents, StoredF32, StoredF64};
|
||||
use vecdb::{BinaryTransform, UnaryTransform};
|
||||
|
||||
pub struct PerSec;
|
||||
|
||||
impl BinaryTransform<StoredU64, Timestamp, StoredF32> for PerSec {
|
||||
#[inline(always)]
|
||||
fn apply(count: StoredU64, interval: Timestamp) -> StoredF32 {
|
||||
let interval_f64 = f64::from(*interval);
|
||||
if interval_f64 > 0.0 {
|
||||
StoredF32::from(*count as f64 / interval_f64)
|
||||
} else {
|
||||
StoredF32::NAN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DaysToYears;
|
||||
|
||||
impl UnaryTransform<StoredF32, StoredF32> for DaysToYears {
|
||||
|
||||
@@ -6,26 +6,28 @@ mod ratio;
|
||||
mod specialized;
|
||||
|
||||
pub use arithmetic::{
|
||||
BlocksToDaysF32, DifficultyToHashF64, HalveCents, HalveDollars, HalveSats,
|
||||
HalveSatsToBitcoin, Identity, MaskSats, OneMinusBp16, OneMinusF64, ReturnF32Tenths, ReturnI8,
|
||||
ReturnU16, ThsToPhsF32, VBytesToWeight, VSizeToWeight,
|
||||
BlocksToDaysF32, DifficultyToHashF64, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
|
||||
Identity, MaskSats, OneMinusBp16, OneMinusF64, ReturnF32Tenths, ReturnI8, ReturnU16,
|
||||
ThsToPhsF32, VBytesToWeight, VSizeToWeight,
|
||||
};
|
||||
pub use bps::{
|
||||
Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent, Bps32ToFloat,
|
||||
Bps32ToPercent,
|
||||
Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent,
|
||||
Bps32ToFloat, Bps32ToPercent,
|
||||
};
|
||||
pub use currency::{
|
||||
CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths,
|
||||
CentsUnsignedToDollars, CentsUnsignedToSats, DollarsToSatsFract, NegCentsUnsignedToDollars,
|
||||
SatsToBitcoin, SatsToCents,
|
||||
CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths, CentsUnsignedToDollars,
|
||||
CentsUnsignedToSats, DollarsToSatsFract, NegCentsUnsignedToDollars, SatsToBitcoin, SatsToCents,
|
||||
};
|
||||
pub use derived::{
|
||||
Days1, Days7, Days30, Days365, DaysToYears, PerSec, PriceTimesRatioBp32Cents, PriceTimesRatioCents,
|
||||
Days1, Days7, Days30, Days365, DaysToYears, PriceTimesRatioBp32Cents, PriceTimesRatioCents,
|
||||
RatioCents64, TimesSqrt,
|
||||
};
|
||||
pub use ratio::{
|
||||
RatioCentsBp32, RatioCentsSignedCentsBps32,
|
||||
RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32,
|
||||
RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16,
|
||||
RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32,
|
||||
RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32,
|
||||
RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16,
|
||||
};
|
||||
pub use specialized::{
|
||||
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
|
||||
OhlcCentsToDollars, OhlcCentsToSats,
|
||||
};
|
||||
pub use specialized::{BlockCountTarget24h, BlockCountTarget1w, BlockCountTarget1m, BlockCountTarget1y, OhlcCentsToDollars, OhlcCentsToSats};
|
||||
|
||||
@@ -349,7 +349,6 @@ impl Computer {
|
||||
timed("Computed scripts", || {
|
||||
self.scripts.compute(
|
||||
indexer,
|
||||
&self.outputs,
|
||||
&self.prices,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
|
||||
@@ -97,24 +97,17 @@ impl Vecs {
|
||||
)?;
|
||||
self.subsidy.compute(prices, starting_indexes.height, exit)?;
|
||||
|
||||
self.unclaimed.compute(
|
||||
self.unclaimed.base.sats.height.compute_transform(
|
||||
starting_indexes.height,
|
||||
prices,
|
||||
exit,
|
||||
|vec| {
|
||||
vec.compute_transform(
|
||||
starting_indexes.height,
|
||||
&self.subsidy.base.sats.height,
|
||||
|(height, subsidy, ..)| {
|
||||
let halving = Halving::from(height);
|
||||
let expected = Sats::FIFTY_BTC / 2_usize.pow(halving.to_usize() as u32);
|
||||
(height, expected.checked_sub(subsidy).unwrap())
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
&self.subsidy.base.sats.height,
|
||||
|(height, subsidy, ..)| {
|
||||
let halving = Halving::from(height);
|
||||
let expected = Sats::FIFTY_BTC / 2_usize.pow(halving.to_usize() as u32);
|
||||
(height, expected.checked_sub(subsidy).unwrap())
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.unclaimed.compute(prices, starting_indexes.height, exit)?;
|
||||
|
||||
self.fee_dominance
|
||||
.compute_binary::<Sats, Sats, RatioSatsBp16>(
|
||||
|
||||
@@ -38,12 +38,11 @@ impl Vecs {
|
||||
)?,
|
||||
subsidy: AmountPerBlockCumulative::forced_import(db, "subsidy", version, indexes)?,
|
||||
fees: AmountPerBlockFull::forced_import(db, "fees", version, indexes, cached_starts)?,
|
||||
unclaimed: AmountPerBlockCumulativeWithSums::forced_import(
|
||||
unclaimed: AmountPerBlockCumulative::forced_import(
|
||||
db,
|
||||
"unclaimed_rewards",
|
||||
version,
|
||||
indexes,
|
||||
cached_starts,
|
||||
)?,
|
||||
fee_dominance: PercentPerBlock::forced_import(db, "fee_dominance", version, indexes)?,
|
||||
fee_dominance_rolling,
|
||||
|
||||
@@ -13,7 +13,7 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub coinbase: AmountPerBlockCumulativeWithSums<M>,
|
||||
pub subsidy: AmountPerBlockCumulative<M>,
|
||||
pub fees: AmountPerBlockFull<M>,
|
||||
pub unclaimed: AmountPerBlockCumulativeWithSums<M>,
|
||||
pub unclaimed: AmountPerBlockCumulative<M>,
|
||||
#[traversable(wrap = "fees", rename = "dominance")]
|
||||
pub fee_dominance: PercentPerBlock<BasisPoints16, M>,
|
||||
#[traversable(wrap = "fees", rename = "dominance")]
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPoints16, Indexes, Version};
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{PercentPerBlock, RatioU64Bp16},
|
||||
outputs,
|
||||
};
|
||||
|
||||
use super::count::Vecs as CountVecs;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub taproot: PercentPerBlock<BasisPoints16, M>,
|
||||
pub segwit: PercentPerBlock<BasisPoints16, M>,
|
||||
}
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
taproot: PercentPerBlock::forced_import(db, "taproot_adoption", version, indexes)?,
|
||||
segwit: PercentPerBlock::forced_import(db, "segwit_adoption", version, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
count: &CountVecs,
|
||||
outputs_count: &outputs::CountVecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.taproot.compute_binary::<_, _, RatioU64Bp16>(
|
||||
starting_indexes.height,
|
||||
&count.p2tr.base.height,
|
||||
&outputs_count.total.sum.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.segwit.compute_binary::<_, _, RatioU64Bp16>(
|
||||
starting_indexes.height,
|
||||
&count.segwit.base.height,
|
||||
&outputs_count.total.sum.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use brk_indexer::Indexer;
|
||||
use brk_types::Indexes;
|
||||
use vecdb::Exit;
|
||||
|
||||
use crate::{outputs, prices};
|
||||
use crate::prices;
|
||||
|
||||
use super::Vecs;
|
||||
|
||||
@@ -11,20 +11,15 @@ impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
outputs: &outputs::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.count
|
||||
.compute(indexer, starting_indexes, exit)?;
|
||||
self.count.compute(indexer, starting_indexes, exit)?;
|
||||
|
||||
self.value
|
||||
.compute(indexer, prices, starting_indexes, exit)?;
|
||||
|
||||
self.adoption
|
||||
.compute(&self.count, &outputs.count, starting_indexes, exit)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_types::{Indexes, StoredU64};
|
||||
use brk_types::Indexes;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
@@ -12,105 +12,95 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.p2a
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2a.first_index,
|
||||
&indexer.vecs.addrs.p2a.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2a.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2a.first_index,
|
||||
&indexer.vecs.addrs.p2a.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2ms
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.scripts.p2ms.first_index,
|
||||
&indexer.vecs.scripts.p2ms.to_tx_index,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2ms.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.scripts.p2ms.first_index,
|
||||
&indexer.vecs.scripts.p2ms.to_tx_index,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2pk33
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2pk33.first_index,
|
||||
&indexer.vecs.addrs.p2pk33.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2pk33.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2pk33.first_index,
|
||||
&indexer.vecs.addrs.p2pk33.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2pk65
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2pk65.first_index,
|
||||
&indexer.vecs.addrs.p2pk65.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2pk65.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2pk65.first_index,
|
||||
&indexer.vecs.addrs.p2pk65.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2pkh
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2pkh.first_index,
|
||||
&indexer.vecs.addrs.p2pkh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2pkh.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2pkh.first_index,
|
||||
&indexer.vecs.addrs.p2pkh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2sh
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2sh.first_index,
|
||||
&indexer.vecs.addrs.p2sh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2sh.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2sh.first_index,
|
||||
&indexer.vecs.addrs.p2sh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2tr
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2tr.first_index,
|
||||
&indexer.vecs.addrs.p2tr.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2tr.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2tr.first_index,
|
||||
&indexer.vecs.addrs.p2tr.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2wpkh
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2wpkh.first_index,
|
||||
&indexer.vecs.addrs.p2wpkh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2wpkh.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2wpkh.first_index,
|
||||
&indexer.vecs.addrs.p2wpkh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.p2wsh
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2wsh.first_index,
|
||||
&indexer.vecs.addrs.p2wsh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.p2wsh.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.addrs.p2wsh.first_index,
|
||||
&indexer.vecs.addrs.p2wsh.bytes,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.op_return
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.scripts.op_return.first_index,
|
||||
&indexer.vecs.scripts.op_return.to_tx_index,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
self.op_return.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_count_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.scripts.op_return.first_index,
|
||||
&indexer.vecs.scripts.op_return.to_tx_index,
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
self.unknown_output
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
@@ -132,19 +122,6 @@ impl Vecs {
|
||||
)?)
|
||||
})?;
|
||||
|
||||
// Compute segwit = p2wpkh + p2wsh + p2tr
|
||||
self.segwit
|
||||
.compute(starting_indexes.height, exit, |v| {
|
||||
Ok(v.compute_transform3(
|
||||
starting_indexes.height,
|
||||
&self.p2wpkh.base.height,
|
||||
&self.p2wsh.base.height,
|
||||
&self.p2tr.base.height,
|
||||
|(h, p2wpkh, p2wsh, p2tr, ..)| (h, StoredU64::from(*p2wpkh + *p2wsh + *p2tr)),
|
||||
exit,
|
||||
)?)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,6 @@ impl Vecs {
|
||||
PerBlockCumulativeWithSums::forced_import(db, "p2wpkh_count", version, indexes, cached_starts)?;
|
||||
let p2wsh =
|
||||
PerBlockCumulativeWithSums::forced_import(db, "p2wsh_count", version, indexes, cached_starts)?;
|
||||
let segwit =
|
||||
PerBlockCumulativeWithSums::forced_import(db, "segwit_count", version, indexes, cached_starts)?;
|
||||
|
||||
Ok(Self {
|
||||
p2a,
|
||||
p2ms,
|
||||
@@ -67,7 +64,6 @@ impl Vecs {
|
||||
indexes,
|
||||
cached_starts,
|
||||
)?,
|
||||
segwit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,4 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub op_return: PerBlockCumulativeWithSums<StoredU64, StoredU64, M>,
|
||||
pub empty_output: PerBlockCumulativeWithSums<StoredU64, StoredU64, M>,
|
||||
pub unknown_output: PerBlockCumulativeWithSums<StoredU64, StoredU64, M>,
|
||||
|
||||
pub segwit: PerBlockCumulativeWithSums<StoredU64, StoredU64, M>,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
internal::db_utils::{finalize_db, open_db},
|
||||
};
|
||||
|
||||
use super::{AdoptionVecs, CountVecs, ValueVecs, Vecs};
|
||||
use super::{CountVecs, ValueVecs, Vecs};
|
||||
use crate::internal::CachedWindowStarts;
|
||||
|
||||
impl Vecs {
|
||||
@@ -23,14 +23,8 @@ impl Vecs {
|
||||
|
||||
let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
let value = ValueVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
let adoption = AdoptionVecs::forced_import(&db, version, indexes)?;
|
||||
|
||||
let this = Self {
|
||||
db,
|
||||
count,
|
||||
value,
|
||||
adoption,
|
||||
};
|
||||
let this = Self { db, count, value };
|
||||
finalize_db(&this.db, &this)?;
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod adoption;
|
||||
pub mod count;
|
||||
pub mod value;
|
||||
|
||||
@@ -8,7 +7,6 @@ mod import;
|
||||
use brk_traversable::Traversable;
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
pub use adoption::Vecs as AdoptionVecs;
|
||||
pub use count::Vecs as CountVecs;
|
||||
pub use value::Vecs as ValueVecs;
|
||||
|
||||
@@ -21,5 +19,4 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
|
||||
pub count: CountVecs<M>,
|
||||
pub value: ValueVecs<M>,
|
||||
pub adoption: AdoptionVecs<M>,
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ impl Vecs {
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let (r1, (r2, r3)) = rayon::join(
|
||||
|| self.count.compute(indexer, &blocks.lookback, starting_indexes, exit),
|
||||
|| {
|
||||
self.count
|
||||
.compute(indexer, &blocks.lookback, starting_indexes, exit)
|
||||
},
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.versions.compute(indexer, starting_indexes, exit),
|
||||
@@ -33,13 +36,18 @@ impl Vecs {
|
||||
r2?;
|
||||
r3?;
|
||||
|
||||
self.fees
|
||||
.compute(indexer, indexes, &inputs.spent, &self.size, starting_indexes, exit)?;
|
||||
self.fees.compute(
|
||||
indexer,
|
||||
indexes,
|
||||
&inputs.spent,
|
||||
&self.size,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.volume.compute(
|
||||
indexer,
|
||||
indexes,
|
||||
blocks,
|
||||
prices,
|
||||
&self.count,
|
||||
&self.fees,
|
||||
|
||||
@@ -5,14 +5,9 @@ use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::transactions::{count, fees};
|
||||
use crate::{blocks, indexes, inputs, outputs, prices};
|
||||
use crate::{indexes, inputs, outputs, prices};
|
||||
|
||||
const WINDOW_SECS: [f64; 4] = [
|
||||
86400.0,
|
||||
7.0 * 86400.0,
|
||||
30.0 * 86400.0,
|
||||
365.0 * 86400.0,
|
||||
];
|
||||
const WINDOW_SECS: [f64; 4] = [86400.0, 7.0 * 86400.0, 30.0 * 86400.0, 365.0 * 86400.0];
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -20,7 +15,6 @@ impl Vecs {
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
count_vecs: &count::Vecs,
|
||||
fees_vecs: &fees::Vecs,
|
||||
@@ -29,11 +23,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.transfer_volume.compute(
|
||||
starting_indexes.height,
|
||||
prices,
|
||||
exit,
|
||||
|sats_vec| {
|
||||
self.transfer_volume
|
||||
.compute(starting_indexes.height, prices, exit, |sats_vec| {
|
||||
Ok(sats_vec.compute_filtered_sum_from_indexes(
|
||||
starting_indexes.height,
|
||||
&indexer.vecs.transactions.first_tx_index,
|
||||
@@ -42,8 +33,7 @@ impl Vecs {
|
||||
|sats| !sats.is_max(),
|
||||
exit,
|
||||
)?)
|
||||
},
|
||||
)?;
|
||||
})?;
|
||||
|
||||
let h = starting_indexes.height;
|
||||
let tx_sums = count_vecs.total.rolling.sum.0.as_array();
|
||||
@@ -51,14 +41,12 @@ impl Vecs {
|
||||
let output_sums = outputs_count.total.rolling.sum.0.as_array();
|
||||
|
||||
for (i, &secs) in WINDOW_SECS.iter().enumerate() {
|
||||
self.tx_per_sec.as_mut_array()[i]
|
||||
.height
|
||||
.compute_transform(
|
||||
h,
|
||||
&tx_sums[i].height,
|
||||
|(h, sum, ..)| (h, StoredF32::from(*sum as f64 / secs)),
|
||||
exit,
|
||||
)?;
|
||||
self.tx_per_sec.as_mut_array()[i].height.compute_transform(
|
||||
h,
|
||||
&tx_sums[i].height,
|
||||
|(h, sum, ..)| (h, StoredF32::from(*sum as f64 / secs)),
|
||||
exit,
|
||||
)?;
|
||||
self.inputs_per_sec.as_mut_array()[i]
|
||||
.height
|
||||
.compute_transform(
|
||||
|
||||
@@ -11,7 +11,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{CheckedSub, Formattable, Pco};
|
||||
|
||||
use crate::{Low, Open};
|
||||
use crate::{Cents, Low, Open};
|
||||
|
||||
use super::{Bitcoin, CentsSigned, Close, High, Sats, StoredF32, StoredF64};
|
||||
|
||||
@@ -52,6 +52,10 @@ impl Dollars {
|
||||
pub fn halved(self) -> Self {
|
||||
Self(self.0 / 2.0)
|
||||
}
|
||||
|
||||
pub fn to_cents(self) -> Cents {
|
||||
Cents::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Dollars {
|
||||
|
||||
+11
-23
@@ -4291,7 +4291,6 @@ function createUnspentPattern(client, acc) {
|
||||
* @property {SeriesTree_Scripts_Raw} raw
|
||||
* @property {SeriesTree_Scripts_Count} count
|
||||
* @property {SeriesTree_Scripts_Value} value
|
||||
* @property {SeriesTree_Scripts_Adoption} adoption
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -4340,7 +4339,6 @@ function createUnspentPattern(client, acc) {
|
||||
* @property {BaseCumulativeSumPattern<StoredU64>} opReturn
|
||||
* @property {BaseCumulativeSumPattern<StoredU64>} emptyOutput
|
||||
* @property {BaseCumulativeSumPattern<StoredU64>} unknownOutput
|
||||
* @property {BaseCumulativeSumPattern<StoredU64>} segwit
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -4348,12 +4346,6 @@ function createUnspentPattern(client, acc) {
|
||||
* @property {BaseCumulativeSumPattern4} opReturn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SeriesTree_Scripts_Adoption
|
||||
* @property {BpsPercentRatioPattern3} taproot
|
||||
* @property {BpsPercentRatioPattern3} segwit
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SeriesTree_Mining
|
||||
* @property {SeriesTree_Mining_Rewards} rewards
|
||||
@@ -4365,7 +4357,7 @@ function createUnspentPattern(client, acc) {
|
||||
* @property {BaseCumulativeSumPattern4} coinbase
|
||||
* @property {SeriesTree_Mining_Rewards_Subsidy} subsidy
|
||||
* @property {SeriesTree_Mining_Rewards_Fees} fees
|
||||
* @property {BaseCumulativeSumPattern4} unclaimed
|
||||
* @property {SeriesTree_Mining_Rewards_Unclaimed} unclaimed
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -4401,6 +4393,12 @@ function createUnspentPattern(client, acc) {
|
||||
* @property {BpsRatioPattern2} _1y
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SeriesTree_Mining_Rewards_Unclaimed
|
||||
* @property {BtcCentsSatsUsdPattern} base
|
||||
* @property {BtcCentsSatsUsdPattern} cumulative
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SeriesTree_Mining_Hashrate
|
||||
* @property {SeriesTree_Mining_Hashrate_Rate} rate
|
||||
@@ -4481,10 +4479,6 @@ function createUnspentPattern(client, acc) {
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} active
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} trueMarketMean
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} cointime
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} transfer
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} balanced
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} terminal
|
||||
* @property {BpsCentsPercentilesRatioSatsUsdPattern} delta
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -7767,15 +7761,10 @@ class BrkClient extends BrkClientBase {
|
||||
opReturn: createBaseCumulativeSumPattern(this, 'op_return_count'),
|
||||
emptyOutput: createBaseCumulativeSumPattern(this, 'empty_output_count'),
|
||||
unknownOutput: createBaseCumulativeSumPattern(this, 'unknown_output_count'),
|
||||
segwit: createBaseCumulativeSumPattern(this, 'segwit_count'),
|
||||
},
|
||||
value: {
|
||||
opReturn: createBaseCumulativeSumPattern4(this, 'op_return_value'),
|
||||
},
|
||||
adoption: {
|
||||
taproot: createBpsPercentRatioPattern3(this, 'taproot_adoption'),
|
||||
segwit: createBpsPercentRatioPattern3(this, 'segwit_adoption'),
|
||||
},
|
||||
},
|
||||
mining: {
|
||||
rewards: {
|
||||
@@ -7806,7 +7795,10 @@ class BrkClient extends BrkClientBase {
|
||||
_1y: createBpsRatioPattern2(this, 'fee_to_subsidy_ratio_1y'),
|
||||
},
|
||||
},
|
||||
unclaimed: createBaseCumulativeSumPattern4(this, 'unclaimed_rewards'),
|
||||
unclaimed: {
|
||||
base: createBtcCentsSatsUsdPattern(this, 'unclaimed_rewards'),
|
||||
cumulative: createBtcCentsSatsUsdPattern(this, 'unclaimed_rewards_cumulative'),
|
||||
},
|
||||
},
|
||||
hashrate: {
|
||||
rate: {
|
||||
@@ -7860,10 +7852,6 @@ class BrkClient extends BrkClientBase {
|
||||
active: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'active_price'),
|
||||
trueMarketMean: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'true_market_mean'),
|
||||
cointime: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'cointime_price'),
|
||||
transfer: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'transfer_price'),
|
||||
balanced: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'balanced_price'),
|
||||
terminal: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'terminal_price'),
|
||||
delta: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'delta_price'),
|
||||
},
|
||||
adjusted: {
|
||||
inflationRate: createBpsPercentRatioPattern(this, 'cointime_adj_inflation_rate'),
|
||||
|
||||
@@ -3594,7 +3594,6 @@ class SeriesTree_Scripts_Count:
|
||||
self.op_return: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'op_return_count')
|
||||
self.empty_output: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'empty_output_count')
|
||||
self.unknown_output: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'unknown_output_count')
|
||||
self.segwit: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'segwit_count')
|
||||
|
||||
class SeriesTree_Scripts_Value:
|
||||
"""Series tree node."""
|
||||
@@ -3602,13 +3601,6 @@ class SeriesTree_Scripts_Value:
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.op_return: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'op_return_value')
|
||||
|
||||
class SeriesTree_Scripts_Adoption:
|
||||
"""Series tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.taproot: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'taproot_adoption')
|
||||
self.segwit: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'segwit_adoption')
|
||||
|
||||
class SeriesTree_Scripts:
|
||||
"""Series tree node."""
|
||||
|
||||
@@ -3616,7 +3608,6 @@ class SeriesTree_Scripts:
|
||||
self.raw: SeriesTree_Scripts_Raw = SeriesTree_Scripts_Raw(client)
|
||||
self.count: SeriesTree_Scripts_Count = SeriesTree_Scripts_Count(client)
|
||||
self.value: SeriesTree_Scripts_Value = SeriesTree_Scripts_Value(client)
|
||||
self.adoption: SeriesTree_Scripts_Adoption = SeriesTree_Scripts_Adoption(client)
|
||||
|
||||
class SeriesTree_Mining_Rewards_Subsidy:
|
||||
"""Series tree node."""
|
||||
@@ -3654,6 +3645,13 @@ class SeriesTree_Mining_Rewards_Fees:
|
||||
self.dominance: _1m1w1y24hBpsPercentRatioPattern = _1m1w1y24hBpsPercentRatioPattern(client, 'fee_dominance')
|
||||
self.to_subsidy_ratio: SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio = SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio(client)
|
||||
|
||||
class SeriesTree_Mining_Rewards_Unclaimed:
|
||||
"""Series tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self.base: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'unclaimed_rewards')
|
||||
self.cumulative: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'unclaimed_rewards_cumulative')
|
||||
|
||||
class SeriesTree_Mining_Rewards:
|
||||
"""Series tree node."""
|
||||
|
||||
@@ -3661,7 +3659,7 @@ class SeriesTree_Mining_Rewards:
|
||||
self.coinbase: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'coinbase')
|
||||
self.subsidy: SeriesTree_Mining_Rewards_Subsidy = SeriesTree_Mining_Rewards_Subsidy(client)
|
||||
self.fees: SeriesTree_Mining_Rewards_Fees = SeriesTree_Mining_Rewards_Fees(client)
|
||||
self.unclaimed: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'unclaimed_rewards')
|
||||
self.unclaimed: SeriesTree_Mining_Rewards_Unclaimed = SeriesTree_Mining_Rewards_Unclaimed(client)
|
||||
|
||||
class SeriesTree_Mining_Hashrate_Rate_Sma:
|
||||
"""Series tree node."""
|
||||
@@ -3749,10 +3747,6 @@ class SeriesTree_Cointime_Prices:
|
||||
self.active: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'active_price')
|
||||
self.true_market_mean: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'true_market_mean')
|
||||
self.cointime: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'cointime_price')
|
||||
self.transfer: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'transfer_price')
|
||||
self.balanced: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'balanced_price')
|
||||
self.terminal: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'terminal_price')
|
||||
self.delta: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'delta_price')
|
||||
|
||||
class SeriesTree_Cointime_Adjusted:
|
||||
"""Series tree node."""
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { colors } from "../utils/colors.js";
|
||||
import { brk } from "../client.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { dots, line, baseline, price, rollingWindowsTree, percentRatioDots } from "./series.js";
|
||||
import {
|
||||
dots,
|
||||
line,
|
||||
price,
|
||||
sumsTree,
|
||||
multiSeriesTree,
|
||||
percentRatioDots,
|
||||
} from "./series.js";
|
||||
import { satsBtcUsd, priceRatioPercentilesTree } from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -36,50 +43,59 @@ export function createCointimeSection() {
|
||||
pattern: cointimePrices.trueMarketMean,
|
||||
name: "True Market Mean",
|
||||
color: colors.trueMarketMean,
|
||||
defaultActive: true,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.vaulted,
|
||||
name: "Vaulted",
|
||||
color: colors.vaulted,
|
||||
defaultActive: true,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.active,
|
||||
name: "Active",
|
||||
color: colors.active,
|
||||
defaultActive: true,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.cointime,
|
||||
name: "Cointime",
|
||||
color: colors.cointime,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.transfer,
|
||||
name: "Transfer",
|
||||
color: colors.transfer,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.balanced,
|
||||
name: "Balanced",
|
||||
color: colors.balanced,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.terminal,
|
||||
name: "Terminal",
|
||||
color: colors.terminal,
|
||||
},
|
||||
{
|
||||
pattern: cointimePrices.delta,
|
||||
name: "Delta",
|
||||
color: colors.delta,
|
||||
defaultActive: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const caps = /** @type {const} */ ([
|
||||
{ series: cap.vaulted.usd, name: "Vaulted", color: colors.vaulted },
|
||||
{ series: cap.active.usd, name: "Active", color: colors.active },
|
||||
{ series: cap.cointime.usd, name: "Cointime", color: colors.cointime },
|
||||
{ series: cap.investor.usd, name: "Investor", color: colors.investor },
|
||||
{ series: cap.thermo.usd, name: "Thermo", color: colors.thermo },
|
||||
{
|
||||
series: cap.vaulted.usd,
|
||||
name: "Vaulted",
|
||||
color: colors.vaulted,
|
||||
defaultActive: true,
|
||||
},
|
||||
{
|
||||
series: cap.active.usd,
|
||||
name: "Active",
|
||||
color: colors.active,
|
||||
defaultActive: true,
|
||||
},
|
||||
{
|
||||
series: cap.cointime.usd,
|
||||
name: "Cointime",
|
||||
color: colors.cointime,
|
||||
defaultActive: true,
|
||||
},
|
||||
{
|
||||
series: cap.investor.usd,
|
||||
name: "Investor",
|
||||
color: colors.investor,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
series: cap.thermo.usd,
|
||||
name: "Thermo",
|
||||
color: colors.thermo,
|
||||
defaultActive: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const supplyBreakdown = /** @type {const} */ ([
|
||||
@@ -167,8 +183,8 @@ export function createCointimeSection() {
|
||||
name: "Investor",
|
||||
color: colors.investor,
|
||||
}),
|
||||
...prices.map(({ pattern, name, color }) =>
|
||||
price({ series: pattern, name, color }),
|
||||
...prices.map(({ pattern, name, color, defaultActive }) =>
|
||||
price({ series: pattern, name, color, defaultActive }),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -180,7 +196,12 @@ export function createCointimeSection() {
|
||||
legend: name,
|
||||
color,
|
||||
priceReferences: [
|
||||
price({ series: all.realized.price, name: "Realized", color: colors.realized, defaultActive: false }),
|
||||
price({
|
||||
series: all.realized.price,
|
||||
name: "Realized",
|
||||
color: colors.realized,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
})),
|
||||
@@ -198,8 +219,8 @@ export function createCointimeSection() {
|
||||
...capReferenceLines.map(({ series, name, color }) =>
|
||||
line({ series, name, color, unit: Unit.usd }),
|
||||
),
|
||||
...caps.map(({ series, name, color }) =>
|
||||
line({ series, name, color, unit: Unit.usd }),
|
||||
...caps.map(({ series, name, color, defaultActive }) =>
|
||||
line({ series, name, color, defaultActive, unit: Unit.usd }),
|
||||
),
|
||||
],
|
||||
},
|
||||
@@ -263,32 +284,17 @@ export function createCointimeSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: "Coinblocks",
|
||||
bottom: coinblocks.map(({ pattern, name, color }) =>
|
||||
line({
|
||||
series: pattern.base,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Coinblocks (Total)",
|
||||
bottom: coinblocks.map(({ pattern, name, color }) =>
|
||||
line({
|
||||
series: pattern.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
tree: multiSeriesTree({
|
||||
entries: coinblocks.map(({ pattern, name, color }) => ({
|
||||
name,
|
||||
color,
|
||||
base: pattern.base,
|
||||
rolling: pattern.sum,
|
||||
cumulative: pattern.cumulative,
|
||||
})),
|
||||
title: "Coinblocks",
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
},
|
||||
...coinblocks.map(({ pattern, name, title, color }) => ({
|
||||
name,
|
||||
@@ -305,7 +311,7 @@ export function createCointimeSection() {
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({ windows: pattern.sum, title, unit: Unit.coinblocks }),
|
||||
sumsTree({ windows: pattern.sum, title, unit: Unit.coinblocks }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
@@ -329,43 +335,26 @@ export function createCointimeSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: "Cointime Value",
|
||||
bottom: [
|
||||
...cointimeValues.map(({ pattern, name, color }) =>
|
||||
line({ series: pattern.base, name, color, unit: Unit.usd }),
|
||||
),
|
||||
line({
|
||||
series: vocdd.pattern.base,
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Cointime Value (Total)",
|
||||
bottom: [
|
||||
...cointimeValues.map(({ pattern, name, color }) =>
|
||||
line({
|
||||
series: pattern.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
line({
|
||||
series: vocdd.pattern.cumulative,
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
tree: multiSeriesTree({
|
||||
entries: [
|
||||
...cointimeValues.map(({ pattern, name, color }) => ({
|
||||
name,
|
||||
color,
|
||||
base: pattern.base,
|
||||
rolling: pattern.sum,
|
||||
cumulative: pattern.cumulative,
|
||||
})),
|
||||
{
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
base: vocdd.pattern.base,
|
||||
rolling: vocdd.pattern.sum,
|
||||
cumulative: vocdd.pattern.cumulative,
|
||||
},
|
||||
],
|
||||
title: "Cointime Value",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
},
|
||||
...cointimeValues.map(({ pattern, name, title, color }) => ({
|
||||
name,
|
||||
@@ -377,7 +366,7 @@ export function createCointimeSection() {
|
||||
line({ series: pattern.base, name, color, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({ windows: pattern.sum, title, unit: Unit.usd }),
|
||||
sumsTree({ windows: pattern.sum, title, unit: Unit.usd }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
@@ -413,7 +402,11 @@ export function createCointimeSection() {
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({ windows: vocdd.pattern.sum, title: vocdd.title, unit: Unit.usd }),
|
||||
sumsTree({
|
||||
windows: vocdd.pattern.sum,
|
||||
title: vocdd.title,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${vocdd.title} (Total)`,
|
||||
@@ -450,25 +443,11 @@ export function createCointimeSection() {
|
||||
{
|
||||
name: "AVIV",
|
||||
title: "AVIV Ratio",
|
||||
bottom: [
|
||||
baseline({
|
||||
series: cap.aviv.ratio,
|
||||
name: "Ratio",
|
||||
color: colors.reserveRisk,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "HODL Bank",
|
||||
title: "HODL Bank",
|
||||
bottom: [
|
||||
line({
|
||||
series: reserveRisk.hodlBank,
|
||||
name: "Value",
|
||||
color: colors.hodlBank,
|
||||
unit: Unit.usd,
|
||||
series: cap.aviv.ratio,
|
||||
name: "aviv",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, rollingWindowsTree, rollingPercentRatioTree, percentRatio } from "../series.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, sumsTree, rollingPercentRatioTree, percentRatio } from "../series.js";
|
||||
import {
|
||||
satsBtcUsd,
|
||||
mapCohorts,
|
||||
@@ -174,7 +174,7 @@ function singleDeltaTree(delta, unit, title, name) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{ ...rollingWindowsTree({ windows: delta.absolute, title: title(`${name} Change`), unit, series: baseline }), name: "Absolute" },
|
||||
{ ...sumsTree({ windows: delta.absolute, title: title(`${name} Change`), unit, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: delta.rate, title: title(`${name} Rate`) }), name: "Rate" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import { formatCohortTitle, satsBtcUsd, satsBtcUsdFullTree } from "../shared.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, percentRatio, rollingWindowsTree, rollingPercentRatioTree } from "../series.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, percentRatio, sumsTree, rollingPercentRatioTree } from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
@@ -613,7 +613,7 @@ function singleBucketFolder({ name, color, pattern }) {
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...rollingWindowsTree({ windows: pattern.supply.all.delta.absolute, title: `${name}: Supply Change`, unit: Unit.sats, series: baseline }), name: "Absolute" },
|
||||
{ ...sumsTree({ windows: pattern.supply.all.delta.absolute, title: `${name}: Supply Change`, unit: Unit.sats, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: pattern.supply.all.delta.rate, title: `${name}: Supply Rate` }), name: "Rate" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -35,8 +35,6 @@ export function createPricesSectionFull({ cohort, title }) {
|
||||
top: [
|
||||
price({ series: tree.realized.price, name: "Realized", color: colors.realized }),
|
||||
price({ series: tree.realized.investor.price, name: "Investor", color: colors.investor }),
|
||||
price({ series: tree.realized.investor.investorUpperBand, name: "I²/R", color: colors.stat.max, style: 2, defaultActive: false }),
|
||||
price({ series: tree.realized.investor.investorLowerBand, name: "R²/I", color: colors.stat.min, style: 2, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, mapWindows, rollingWindowsTree, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTree, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
|
||||
import { createRatioChart, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export function createValuationSectionFull({ cohort, title }) {
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...rollingWindowsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" },
|
||||
{ ...sumsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" },
|
||||
],
|
||||
},
|
||||
@@ -93,7 +93,7 @@ export function createValuationSection({ cohort, title }) {
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...rollingWindowsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" },
|
||||
{ ...sumsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -145,6 +145,59 @@ function createCompareFolder(context, items) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create compare folder from long items (includes CAGR chart)
|
||||
* @param {string} context
|
||||
* @param {LongEntryItem[]} items
|
||||
*/
|
||||
function createLongCompareFolder(context, items) {
|
||||
const topPane = items.map(({ name, color, costBasis }) =>
|
||||
price({ series: costBasis, name, color }),
|
||||
);
|
||||
return {
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Cost Basis",
|
||||
title: `Cost Basis: ${context}`,
|
||||
top: topPane,
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `Returns: ${context}`,
|
||||
top: topPane,
|
||||
bottom: items.flatMap(({ name, color, returns }) =>
|
||||
percentRatioBaseline({
|
||||
pattern: returns,
|
||||
name,
|
||||
color: [color, color],
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "CAGR",
|
||||
title: `CAGR: ${context}`,
|
||||
top: topPane,
|
||||
bottom: items.flatMap(({ name, color, cagr }) =>
|
||||
percentRatioBaseline({
|
||||
pattern: cagr,
|
||||
name,
|
||||
color: [color, color],
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Accumulated",
|
||||
title: `Accumulated Value: ${context}`,
|
||||
top: topPane,
|
||||
bottom: items.flatMap(({ name, color, stack }) =>
|
||||
satsBtcUsd({ pattern: stack, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create single entry tree structure
|
||||
* @param {BaseEntryItem & { titlePrefix?: string }} item
|
||||
@@ -182,14 +235,36 @@ function createShortSingleEntry(item) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single entry from a long item (with CAGR)
|
||||
* Create a single entry from a long item (with CAGR as its own chart)
|
||||
* @param {LongEntryItem & { titlePrefix?: string }} item
|
||||
*/
|
||||
function createLongSingleEntry(item) {
|
||||
return createSingleEntryTree(item, [
|
||||
...percentRatioBaseline({ pattern: item.returns, name: "Current" }),
|
||||
...percentRatioBaseline({ pattern: item.cagr, name: "CAGR" }),
|
||||
]);
|
||||
const { name, titlePrefix = name, color, costBasis, returns, cagr, stack } = item;
|
||||
const top = [price({ series: costBasis, name: "Cost Basis", color })];
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{ name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top },
|
||||
{
|
||||
name: "Returns",
|
||||
title: `Returns: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: percentRatioBaseline({ pattern: returns, name: "Current" }),
|
||||
},
|
||||
{
|
||||
name: "CAGR",
|
||||
title: `CAGR: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: percentRatioBaseline({ pattern: cagr, name: "CAGR" }),
|
||||
},
|
||||
{
|
||||
name: "Accumulated",
|
||||
title: `Accumulated Value: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: satsBtcUsd({ pattern: stack, name: "Value" }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,13 +325,22 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.p2,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {string} name @param {LongPeriodKey} key */
|
||||
const longCagrChart = (name, key) => ({
|
||||
name: "CAGR",
|
||||
title: `CAGR: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
...percentRatioBaseline({
|
||||
pattern: dca.period.cagr[key],
|
||||
name: "DCA CAGR",
|
||||
name: "DCA",
|
||||
}),
|
||||
...percentRatioBaseline({
|
||||
pattern: returns.cagr[key],
|
||||
name: "Lump Sum CAGR",
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.p2,
|
||||
}),
|
||||
],
|
||||
@@ -302,6 +386,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
longReturnsChart(name, key),
|
||||
longCagrChart(name, key),
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
@@ -381,10 +466,6 @@ function createPeriodSection({ dca, lookback, returns }) {
|
||||
name: `${suffix} by Period`,
|
||||
title: `${suffix} Performance by Investment Period`,
|
||||
tree: [
|
||||
createCompareFolder(`All Periods ${suffix}`, [
|
||||
...shortEntries,
|
||||
...longEntries,
|
||||
]),
|
||||
{
|
||||
name: "Short Term",
|
||||
title: "Up to 1 Year",
|
||||
@@ -397,7 +478,7 @@ function createPeriodSection({ dca, lookback, returns }) {
|
||||
name: "Long Term",
|
||||
title: "2+ Years",
|
||||
tree: [
|
||||
createCompareFolder(`Long Term ${suffix}`, longEntries),
|
||||
createLongCompareFolder(`Long Term ${suffix}`, longEntries),
|
||||
...longEntries.map(createLongEntry),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -86,7 +86,12 @@ function createMaSubSection(label, averages) {
|
||||
name: "Compare",
|
||||
title: `Price ${label}s`,
|
||||
top: averages.map((a) =>
|
||||
price({ series: a.ratio, name: a.id, color: a.color }),
|
||||
price({
|
||||
series: a.ratio,
|
||||
name: a.id,
|
||||
color: a.color,
|
||||
defaultActive: includes(commonMaIds, a.id),
|
||||
}),
|
||||
),
|
||||
},
|
||||
...common.map(toFolder),
|
||||
|
||||
+200
-597
@@ -10,46 +10,44 @@ import {
|
||||
dotted,
|
||||
distributionBtcSatsUsd,
|
||||
statsAtWindow,
|
||||
rollingWindowsTree,
|
||||
ROLLING_WINDOWS,
|
||||
percentRatio,
|
||||
percentRatioDots,
|
||||
chartsFromCount,
|
||||
} from "./series.js";
|
||||
import {
|
||||
satsBtcUsd,
|
||||
satsBtcUsdFrom,
|
||||
satsBtcUsdFullTree,
|
||||
revenueBtcSatsUsd,
|
||||
} from "./shared.js";
|
||||
import { brk } from "../client.js";
|
||||
|
||||
/** Major pools to show in Compare section (by current hashrate dominance) */
|
||||
const MAJOR_POOL_IDS = /** @type {const} */ ([
|
||||
"foundryusa", // ~32% - largest pool
|
||||
"antpool", // ~18% - Bitmain-owned
|
||||
"viabtc", // ~14% - independent
|
||||
"f2pool", // ~10% - one of the oldest pools
|
||||
"marapool", // MARA Holdings
|
||||
"braiinspool", // formerly Slush Pool
|
||||
"spiderpool", // growing Asian pool
|
||||
"ocean", // decentralization-focused
|
||||
"foundryusa",
|
||||
"antpool",
|
||||
"viabtc",
|
||||
"f2pool",
|
||||
"marapool",
|
||||
"braiinspool",
|
||||
"spiderpool",
|
||||
"ocean",
|
||||
]);
|
||||
|
||||
/**
|
||||
* AntPool & friends - pools sharing AntPool's block templates
|
||||
* Based on b10c's research: https://b10c.me/blog/015-bitcoin-mining-centralization/
|
||||
* Collectively ~35-40% of network hashrate
|
||||
*/
|
||||
const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([
|
||||
"antpool", // Bitmain-owned, template source
|
||||
"poolin", // shares AntPool templates
|
||||
"btccom", // CloverPool (formerly BTC.com)
|
||||
"braiinspool", // shares AntPool templates
|
||||
"ultimuspool", // shares AntPool templates
|
||||
"binancepool", // shares AntPool templates
|
||||
"secpool", // shares AntPool templates
|
||||
"sigmapoolcom", // SigmaPool
|
||||
"rawpool", // shares AntPool templates
|
||||
"luxor", // shares AntPool templates
|
||||
"antpool",
|
||||
"poolin",
|
||||
"btccom",
|
||||
"braiinspool",
|
||||
"ultimuspool",
|
||||
"binancepool",
|
||||
"secpool",
|
||||
"sigmapoolcom",
|
||||
"rawpool",
|
||||
"luxor",
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -59,7 +57,6 @@ const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([
|
||||
export function createMiningSection() {
|
||||
const { blocks, pools, mining } = brk.series;
|
||||
|
||||
// Pre-compute pool entries with resolved names
|
||||
const majorPoolData = entries(pools.major).map(([id, pool]) => ({
|
||||
id,
|
||||
name: brk.POOL_ID_TO_POOL_NAME[id],
|
||||
@@ -71,7 +68,6 @@ export function createMiningSection() {
|
||||
pool,
|
||||
}));
|
||||
|
||||
// Filtered pool groups for comparisons (major pools only have windowed dominance)
|
||||
const featuredPools = majorPoolData.filter((p) =>
|
||||
includes(MAJOR_POOL_IDS, p.id),
|
||||
);
|
||||
@@ -79,135 +75,126 @@ export function createMiningSection() {
|
||||
includes(ANTPOOL_AND_FRIENDS_IDS, p.id),
|
||||
);
|
||||
|
||||
// Build individual pool trees
|
||||
const majorPoolsTree = majorPoolData.map(({ name, pool }) => ({
|
||||
name,
|
||||
/** @param {string} title @param {{ _24h: any, _1w: any, _1m: any, _1y: any, percent: any, ratio: any }} dominance */
|
||||
const dominanceTree = (title, dominance) => ({
|
||||
name: "Dominance",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: `Dominance: ${name}`,
|
||||
name: "Compare",
|
||||
title,
|
||||
bottom: [
|
||||
...percentRatioDots({ pattern: pool.dominance._24h, name: "24h", color: colors.time._24h, defaultActive: false }),
|
||||
...percentRatio({ pattern: pool.dominance._1w, name: "1w", color: colors.time._1w, defaultActive: false }),
|
||||
...percentRatio({ pattern: pool.dominance._1m, name: "1m", color: colors.time._1m }),
|
||||
...percentRatio({ pattern: pool.dominance._1y, name: "1y", color: colors.time._1y, defaultActive: false }),
|
||||
...percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all, defaultActive: false }),
|
||||
...ROLLING_WINDOWS.flatMap((w) =>
|
||||
percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color, defaultActive: w.key !== "_24h" }),
|
||||
),
|
||||
...percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} ${w.title}`,
|
||||
bottom: percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color }),
|
||||
})),
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: `Blocks Mined: ${name}`,
|
||||
bottom: [
|
||||
line({
|
||||
series: pool.blocksMined.base,
|
||||
name: "base",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({ windows: pool.blocksMined.sum, title: `Blocks Mined: ${name}`, unit: Unit.count }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `Blocks Mined: ${name} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
series: pool.blocksMined.cumulative,
|
||||
name: "all-time",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rewards",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: `Rewards: ${name}`,
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: pool.rewards,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Rewards: ${name} Rolling`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
satsBtcUsd({ pattern: pool.rewards.sum[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Rewards: ${name} (${w.name})`,
|
||||
bottom: satsBtcUsd({ pattern: pool.rewards.sum[w.key], name: w.name, color: w.color }),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `Rewards: ${name} (Total)`,
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: pool.rewards,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
name: "All Time",
|
||||
title: `${title} All Time`,
|
||||
bottom: percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }),
|
||||
},
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
||||
const minorPoolsTree = minorPoolData.map(({ name, pool }) => ({
|
||||
name,
|
||||
/**
|
||||
* @param {typeof majorPoolData} poolList
|
||||
*/
|
||||
const createPoolTree = (poolList) =>
|
||||
poolList.map(({ name, pool }) => ({
|
||||
name,
|
||||
tree: [
|
||||
dominanceTree(`Dominance: ${name}`, pool.dominance),
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
tree: chartsFromCount({
|
||||
pattern: pool.blocksMined,
|
||||
title: `Blocks Mined: ${name}`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Rewards",
|
||||
tree: satsBtcUsdFullTree({
|
||||
pattern: pool.rewards,
|
||||
name: "Rewards",
|
||||
title: `Rewards: ${name}`,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
/**
|
||||
* @param {typeof minorPoolData} poolList
|
||||
*/
|
||||
const createMinorPoolTree = (poolList) =>
|
||||
poolList.map(({ name, pool }) => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: `Dominance: ${name}`,
|
||||
bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
tree: chartsFromCount({
|
||||
pattern: pool.blocksMined,
|
||||
title: `Blocks Mined: ${name}`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
/**
|
||||
* @param {string} groupTitle
|
||||
* @param {typeof majorPoolData} poolList
|
||||
*/
|
||||
const createPoolCompare = (groupTitle, poolList) => ({
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: `Dominance: ${name}`,
|
||||
bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }),
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Dominance: ${groupTitle} ${w.title}`,
|
||||
bottom: poolList.flatMap((p, i) =>
|
||||
percentRatio({
|
||||
pattern: p.pool.dominance[w.key],
|
||||
name: p.name,
|
||||
color: colors.at(i, poolList.length),
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: `Blocks Mined: ${name}`,
|
||||
bottom: [
|
||||
line({
|
||||
series: pool.blocksMined.base,
|
||||
name: "base",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({ windows: pool.blocksMined.sum, title: `Blocks Mined: ${name}`, unit: Unit.count }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `Blocks Mined: ${name} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
series: pool.blocksMined.cumulative,
|
||||
name: "all-time",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Blocks Mined: ${groupTitle} ${w.title} Sum`,
|
||||
bottom: poolList.map((p, i) =>
|
||||
line({
|
||||
series: p.pool.blocksMined.sum[w.key],
|
||||
name: p.name,
|
||||
color: colors.at(i, poolList.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
name: "Mining",
|
||||
tree: [
|
||||
// Hashrate
|
||||
{
|
||||
name: "Hashrate",
|
||||
tree: [
|
||||
@@ -293,63 +280,6 @@ export function createMiningSection() {
|
||||
],
|
||||
},
|
||||
|
||||
// Difficulty
|
||||
{
|
||||
name: "Difficulty",
|
||||
tree: [
|
||||
{
|
||||
name: "Current",
|
||||
title: "Mining Difficulty",
|
||||
bottom: [
|
||||
line({
|
||||
series: blocks.difficulty.value,
|
||||
name: "Difficulty",
|
||||
unit: Unit.difficulty,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Epoch",
|
||||
title: "Difficulty Epoch",
|
||||
bottom: [
|
||||
line({
|
||||
series: blocks.difficulty.epoch,
|
||||
name: "Epoch",
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adjustment",
|
||||
title: "Difficulty Adjustment",
|
||||
bottom: [
|
||||
baseline({
|
||||
series: blocks.difficulty.adjustment.percent,
|
||||
name: "Change",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Countdown",
|
||||
title: "Next Difficulty Adjustment",
|
||||
bottom: [
|
||||
line({
|
||||
series: blocks.difficulty.blocksToRetarget,
|
||||
name: "Remaining",
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
series: blocks.difficulty.daysToRetarget,
|
||||
name: "Remaining",
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Revenue
|
||||
{
|
||||
name: "Revenue",
|
||||
tree: [
|
||||
@@ -357,7 +287,7 @@ export function createMiningSection() {
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
name: "Per Block",
|
||||
title: "Revenue Comparison",
|
||||
bottom: revenueBtcSatsUsd({
|
||||
coinbase: mining.rewards.coinbase,
|
||||
@@ -380,105 +310,23 @@ export function createMiningSection() {
|
||||
},
|
||||
{
|
||||
name: "Coinbase",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Coinbase Rewards",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.coinbase,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Coinbase Rolling Sum",
|
||||
bottom: [
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._24h,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._1w,
|
||||
name: "7d",
|
||||
color: colors.time._1w,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._1m,
|
||||
name: "30d",
|
||||
color: colors.time._1m,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._1y,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "24h",
|
||||
title: "Coinbase 24h Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._24h,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "7d",
|
||||
title: "Coinbase 7d Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._1w,
|
||||
name: "7d",
|
||||
color: colors.time._1w,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "30d",
|
||||
title: "Coinbase 30d Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._1m,
|
||||
name: "30d",
|
||||
color: colors.time._1m,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "1y",
|
||||
title: "Coinbase 1y Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.coinbase.sum._1y,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Coinbase Rewards (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.coinbase,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
tree: satsBtcUsdFullTree({
|
||||
pattern: mining.rewards.coinbase,
|
||||
name: "Coinbase",
|
||||
title: "Coinbase Rewards",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Subsidy",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
name: "Per Block",
|
||||
title: "Block Subsidy",
|
||||
bottom: [
|
||||
...satsBtcUsdFrom({
|
||||
source: mining.rewards.subsidy,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
name: "base",
|
||||
}),
|
||||
line({
|
||||
series: mining.rewards.subsidy.sma1y.usd,
|
||||
@@ -503,129 +351,32 @@ export function createMiningSection() {
|
||||
{
|
||||
name: "Fees",
|
||||
tree: [
|
||||
...satsBtcUsdFullTree({
|
||||
pattern: mining.rewards.fees,
|
||||
name: "Fees",
|
||||
title: "Transaction Fee Revenue",
|
||||
}),
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Transaction Fee Revenue per Block",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.fees,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Fee Rolling Sum",
|
||||
bottom: [
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._24h,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._1w,
|
||||
name: "7d",
|
||||
color: colors.time._1w,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._1m,
|
||||
name: "30d",
|
||||
color: colors.time._1m,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._1y,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "24h",
|
||||
title: "Fee 24h Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._24h,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "7d",
|
||||
title: "Fee 7d Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._1w,
|
||||
name: "7d",
|
||||
color: colors.time._1w,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "30d",
|
||||
title: "Fee 30d Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._1m,
|
||||
name: "30d",
|
||||
color: colors.time._1m,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "1y",
|
||||
title: "Fee 1y Rolling Sum",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: mining.rewards.fees.sum._1y,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
name: "Distributions",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Fee Revenue per Block Distribution (${w.name})`,
|
||||
title: `Fee Revenue per Block ${w.title} Distribution`,
|
||||
bottom: distributionBtcSatsUsd(statsAtWindow(mining.rewards.fees, w.key)),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Transaction Fee Revenue (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.fees,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Dominance",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Subsidy",
|
||||
title: "Subsidy Dominance",
|
||||
bottom: [
|
||||
...percentRatio({ pattern: mining.rewards.subsidy.dominance, name: "All-time", color: colors.time.all }),
|
||||
...ROLLING_WINDOWS.flatMap((w) =>
|
||||
percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fees",
|
||||
title: "Fee Dominance",
|
||||
bottom: [
|
||||
...percentRatio({ pattern: mining.rewards.fees.dominance, name: "All-time", color: colors.time.all }),
|
||||
...ROLLING_WINDOWS.flatMap((w) =>
|
||||
percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Revenue Dominance ${w.title}`,
|
||||
bottom: [
|
||||
...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }),
|
||||
...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }),
|
||||
],
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: "All-time",
|
||||
title: "Revenue Dominance (All-time)",
|
||||
@@ -634,77 +385,28 @@ export function createMiningSection() {
|
||||
...percentRatio({ pattern: mining.rewards.fees.dominance, name: "Fees", color: colors.mining.fee }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Revenue Dominance (${w.name})`,
|
||||
bottom: [
|
||||
...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }),
|
||||
...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fee Multiple",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Fee-to-Subsidy Ratio",
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({ series: mining.rewards.fees.toSubsidyRatio[w.key].ratio, name: w.name, color: w.color, unit: Unit.ratio }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Fee-to-Subsidy Ratio (${w.name})`,
|
||||
bottom: [line({ series: mining.rewards.fees.toSubsidyRatio[w.key].ratio, name: w.name, color: w.color, unit: Unit.ratio })],
|
||||
})),
|
||||
],
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Fee-to-Subsidy Ratio ${w.title}`,
|
||||
bottom: [line({ series: mining.rewards.fees.toSubsidyRatio[w.key].ratio, name: "Ratio", color: colors.mining.fee, unit: Unit.ratio })],
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Unclaimed",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Unclaimed Rewards",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.unclaimed,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Unclaimed Rewards Rolling",
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
satsBtcUsd({ pattern: mining.rewards.unclaimed.sum[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Unclaimed Rewards ${w.name}`,
|
||||
bottom: satsBtcUsd({ pattern: mining.rewards.unclaimed.sum[w.key], name: w.name, color: w.color }),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Unclaimed Rewards (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.unclaimed,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
title: "Unclaimed Rewards (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: mining.rewards.unclaimed,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Economics
|
||||
{
|
||||
name: "Economics",
|
||||
tree: [
|
||||
@@ -712,60 +414,20 @@ export function createMiningSection() {
|
||||
name: "Hash Price",
|
||||
title: "Hash Price",
|
||||
bottom: [
|
||||
line({
|
||||
series: mining.hashrate.price.ths,
|
||||
name: "TH/s",
|
||||
color: colors.usd,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
series: mining.hashrate.price.phs,
|
||||
name: "PH/s",
|
||||
color: colors.usd,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
}),
|
||||
dotted({
|
||||
series: mining.hashrate.price.thsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.stat.min,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
}),
|
||||
dotted({
|
||||
series: mining.hashrate.price.phsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.stat.min,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
}),
|
||||
line({ series: mining.hashrate.price.ths, name: "TH/s", color: colors.usd, unit: Unit.usdPerThsPerDay }),
|
||||
line({ series: mining.hashrate.price.phs, name: "PH/s", color: colors.usd, unit: Unit.usdPerPhsPerDay }),
|
||||
dotted({ series: mining.hashrate.price.thsMin, name: "TH/s Min", color: colors.stat.min, unit: Unit.usdPerThsPerDay }),
|
||||
dotted({ series: mining.hashrate.price.phsMin, name: "PH/s Min", color: colors.stat.min, unit: Unit.usdPerPhsPerDay }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Hash Value",
|
||||
title: "Hash Value",
|
||||
bottom: [
|
||||
line({
|
||||
series: mining.hashrate.value.ths,
|
||||
name: "TH/s",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
series: mining.hashrate.value.phs,
|
||||
name: "PH/s",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
}),
|
||||
dotted({
|
||||
series: mining.hashrate.value.thsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.stat.min,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
}),
|
||||
dotted({
|
||||
series: mining.hashrate.value.phsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.stat.min,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
}),
|
||||
line({ series: mining.hashrate.value.ths, name: "TH/s", color: colors.bitcoin, unit: Unit.satsPerThsPerDay }),
|
||||
line({ series: mining.hashrate.value.phs, name: "PH/s", color: colors.bitcoin, unit: Unit.satsPerPhsPerDay }),
|
||||
dotted({ series: mining.hashrate.value.thsMin, name: "TH/s Min", color: colors.stat.min, unit: Unit.satsPerThsPerDay }),
|
||||
dotted({ series: mining.hashrate.value.phsMin, name: "PH/s Min", color: colors.stat.min, unit: Unit.satsPerPhsPerDay }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -779,7 +441,6 @@ export function createMiningSection() {
|
||||
],
|
||||
},
|
||||
|
||||
// Halving
|
||||
{
|
||||
name: "Halving",
|
||||
tree: [
|
||||
@@ -787,122 +448,64 @@ export function createMiningSection() {
|
||||
name: "Countdown",
|
||||
title: "Next Halving",
|
||||
bottom: [
|
||||
line({
|
||||
series: blocks.halving.blocksToHalving,
|
||||
name: "Remaining",
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
series: blocks.halving.daysToHalving,
|
||||
name: "Remaining",
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({ series: blocks.halving.blocksToHalving, name: "Remaining", unit: Unit.blocks }),
|
||||
line({ series: blocks.halving.daysToHalving, name: "Remaining", unit: Unit.days }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Epoch",
|
||||
title: "Halving Epoch",
|
||||
bottom: [
|
||||
line({
|
||||
series: blocks.halving.epoch,
|
||||
name: "Epoch",
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
],
|
||||
bottom: [line({ series: blocks.halving.epoch, name: "Epoch", unit: Unit.epoch })],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Pools
|
||||
{
|
||||
name: "Difficulty",
|
||||
tree: [
|
||||
{
|
||||
name: "Current",
|
||||
title: "Mining Difficulty",
|
||||
bottom: [line({ series: blocks.difficulty.value, name: "Difficulty", unit: Unit.difficulty })],
|
||||
},
|
||||
{
|
||||
name: "Adjustment",
|
||||
title: "Difficulty Adjustment",
|
||||
bottom: [baseline({ series: blocks.difficulty.adjustment.percent, name: "Change", unit: Unit.percentage })],
|
||||
},
|
||||
{
|
||||
name: "Countdown",
|
||||
title: "Next Difficulty Adjustment",
|
||||
bottom: [
|
||||
line({ series: blocks.difficulty.blocksToRetarget, name: "Remaining", unit: Unit.blocks }),
|
||||
line({ series: blocks.difficulty.daysToRetarget, name: "Remaining", unit: Unit.days }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Epoch",
|
||||
title: "Difficulty Epoch",
|
||||
bottom: [line({ series: blocks.difficulty.epoch, name: "Epoch", unit: Unit.epoch })],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Pools",
|
||||
tree: [
|
||||
// Compare section (major pools only)
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "Dominance: Major Pools (1m)",
|
||||
bottom: featuredPools.flatMap((p, i) =>
|
||||
percentRatio({
|
||||
pattern: p.pool.dominance._1m,
|
||||
name: p.name,
|
||||
color: colors.at(i, featuredPools.length),
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: "Blocks Mined: Major Pools (1m)",
|
||||
bottom: featuredPools.map((p, i) =>
|
||||
line({
|
||||
series: p.pool.blocksMined.sum._1m,
|
||||
name: p.name,
|
||||
color: colors.at(i, featuredPools.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Total Rewards",
|
||||
title: "Total Rewards: Major Pools",
|
||||
bottom: featuredPools.flatMap((p, i) =>
|
||||
satsBtcUsdFrom({
|
||||
source: p.pool.rewards,
|
||||
key: "base",
|
||||
name: p.name,
|
||||
color: colors.at(i, featuredPools.length),
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
// AntPool & friends - pools sharing block templates
|
||||
createPoolCompare("Major Pools", featuredPools),
|
||||
{
|
||||
name: "AntPool & Friends",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "Dominance: AntPool & Friends (1m)",
|
||||
bottom: antpoolFriends.flatMap((p, i) =>
|
||||
percentRatio({
|
||||
pattern: p.pool.dominance._1m,
|
||||
name: p.name,
|
||||
color: colors.at(i, antpoolFriends.length),
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: "Blocks Mined: AntPool & Friends (1m)",
|
||||
bottom: antpoolFriends.map((p, i) =>
|
||||
line({
|
||||
series: p.pool.blocksMined.sum._1m,
|
||||
name: p.name,
|
||||
color: colors.at(i, antpoolFriends.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Total Rewards",
|
||||
title: "Total Rewards: AntPool & Friends",
|
||||
bottom: antpoolFriends.flatMap((p, i) =>
|
||||
satsBtcUsdFrom({
|
||||
source: p.pool.rewards,
|
||||
key: "base",
|
||||
name: p.name,
|
||||
color: colors.at(i, antpoolFriends.length),
|
||||
}),
|
||||
),
|
||||
},
|
||||
createPoolCompare("AntPool & Friends", antpoolFriends),
|
||||
...createPoolTree(antpoolFriends),
|
||||
],
|
||||
},
|
||||
// All pools
|
||||
{
|
||||
name: "All Pools",
|
||||
tree: [...majorPoolsTree, ...minorPoolsTree],
|
||||
name: "Major",
|
||||
tree: createPoolTree(majorPoolData),
|
||||
},
|
||||
{
|
||||
name: "Minor",
|
||||
tree: createMinorPoolTree(minorPoolData),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+162
-685
@@ -8,21 +8,17 @@ import { priceLine } from "./constants.js";
|
||||
import {
|
||||
line,
|
||||
dots,
|
||||
baseline,
|
||||
fromSupplyPattern,
|
||||
chartsFromFullPerBlock,
|
||||
chartsFromCount,
|
||||
chartsFromCountEntries,
|
||||
chartsFromAggregatedPerBlock,
|
||||
rollingWindowsTree,
|
||||
|
||||
averagesTree,
|
||||
simpleDeltaTree,
|
||||
ROLLING_WINDOWS,
|
||||
chartsFromBlockAnd6b,
|
||||
multiSeriesTree,
|
||||
simpleDeltaTree,
|
||||
percentRatio,
|
||||
percentRatioDots,
|
||||
rollingPercentRatioTree,
|
||||
} from "./series.js";
|
||||
import { satsBtcUsd, satsBtcUsdFrom, satsBtcUsdFullTree } from "./shared.js";
|
||||
|
||||
@@ -59,12 +55,7 @@ export function createNetworkSection() {
|
||||
// Non-addressable script types
|
||||
const nonAddressableTypes = /** @type {const} */ ([
|
||||
{ key: "p2ms", name: "P2MS", color: st.p2ms, defaultActive: false },
|
||||
{
|
||||
key: "opReturn",
|
||||
name: "OP_RETURN",
|
||||
color: st.opReturn,
|
||||
defaultActive: false,
|
||||
},
|
||||
{ key: "opReturn", name: "OP_RETURN", color: st.opReturn, defaultActive: true },
|
||||
{
|
||||
key: "emptyOutput",
|
||||
name: "Empty",
|
||||
@@ -82,42 +73,13 @@ export function createNetworkSection() {
|
||||
// All script types = addressable + non-addressable
|
||||
const scriptTypes = [...addressTypes, ...nonAddressableTypes];
|
||||
|
||||
// Address type groups (by era)
|
||||
const taprootAddresses = /** @type {const} */ ([
|
||||
{ key: "p2a", name: "P2A", color: st.p2a },
|
||||
{ key: "p2tr", name: "P2TR", color: st.p2tr },
|
||||
]);
|
||||
const segwitAddresses = /** @type {const} */ ([
|
||||
{ key: "p2wsh", name: "P2WSH", color: st.p2wsh },
|
||||
{ key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh },
|
||||
]);
|
||||
const legacyAddresses = /** @type {const} */ ([
|
||||
{ key: "p2sh", name: "P2SH", color: st.p2sh },
|
||||
{ key: "p2pkh", name: "P2PKH", color: st.p2pkh },
|
||||
{ key: "p2pk33", name: "P2PK33", color: st.p2pk33 },
|
||||
{ key: "p2pk65", name: "P2PK65", color: st.p2pk65 },
|
||||
]);
|
||||
|
||||
// Transacting types (transaction participation)
|
||||
const transactingTypes = /** @type {const} */ ([
|
||||
{
|
||||
key: "sending",
|
||||
name: "Sending",
|
||||
title: "Unique Sending Addresses per Block",
|
||||
compareTitle: "Unique Sending Addresses per Block by Type",
|
||||
},
|
||||
{
|
||||
key: "receiving",
|
||||
name: "Receiving",
|
||||
title: "Unique Receiving Addresses per Block",
|
||||
compareTitle: "Unique Receiving Addresses per Block by Type",
|
||||
},
|
||||
{
|
||||
key: "both",
|
||||
name: "Sending & Receiving",
|
||||
title: "Unique Addresses Sending & Receiving per Block",
|
||||
compareTitle: "Unique Addresses Sending & Receiving per Block by Type",
|
||||
},
|
||||
const activityTypes = /** @type {const} */ ([
|
||||
{ key: "sending", name: "Sending" },
|
||||
{ key: "receiving", name: "Receiving" },
|
||||
{ key: "both", name: "Both" },
|
||||
{ key: "reactivated", name: "Reactivated" },
|
||||
]);
|
||||
|
||||
const countTypes = /** @type {const} */ ([
|
||||
@@ -141,311 +103,140 @@ export function createNetworkSection() {
|
||||
},
|
||||
]);
|
||||
|
||||
const countMetrics = /** @type {const} */ ([
|
||||
{ key: "funded", name: "Funded", color: undefined },
|
||||
{ key: "empty", name: "Empty", color: colors.gray },
|
||||
{ key: "total", name: "Total", color: colors.default },
|
||||
]);
|
||||
|
||||
/**
|
||||
* Create address series tree for a given type key
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {string} titlePrefix
|
||||
*/
|
||||
const createAddressSeriesTree = (key, titlePrefix) => [
|
||||
{
|
||||
name: "Count",
|
||||
title: `${titlePrefix}Address Count`,
|
||||
bottom: [
|
||||
line({
|
||||
series: addrs.funded[key],
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.empty[key],
|
||||
name: "Empty",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
series: addrs.total[key],
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Trends",
|
||||
tree: [
|
||||
rollingWindowsTree({
|
||||
windows: addrs.delta[key].absolute,
|
||||
title: `${titlePrefix}Address Count Change`,
|
||||
unit: Unit.count,
|
||||
series: baseline,
|
||||
}),
|
||||
{
|
||||
name: "New",
|
||||
tree: (() => {
|
||||
const p = addrs.new[key];
|
||||
const t = `${titlePrefix}New Address Count`;
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title: t,
|
||||
bottom: [
|
||||
line({ series: p.base, name: "base", unit: Unit.count }),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({
|
||||
windows: p.sum,
|
||||
title: t,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${t} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
series: p.cumulative,
|
||||
name: "all-time",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
})(),
|
||||
},
|
||||
{
|
||||
name: "Reactivated",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: `${titlePrefix}Reactivated Addresses per Block`,
|
||||
bottom: [
|
||||
dots({
|
||||
series: addrs.activity[key].reactivated.base,
|
||||
name: "base",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.activity[key].reactivated._24h,
|
||||
name: "24h avg",
|
||||
color: colors.stat.avg,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({
|
||||
windows: addrs.activity[key].reactivated,
|
||||
title: `${titlePrefix}Reactivated Addresses`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingPercentRatioTree({
|
||||
windows: addrs.delta[key].rate,
|
||||
title: `${titlePrefix}Address Growth Rate`,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Transacting",
|
||||
tree: transactingTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: `${titlePrefix}${t.title}`,
|
||||
bottom: [
|
||||
dots({
|
||||
series: addrs.activity[key][t.key].base,
|
||||
name: "base",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.activity[key][t.key]._24h,
|
||||
name: "24h avg",
|
||||
color: colors.stat.avg,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({
|
||||
windows: addrs.activity[key][t.key],
|
||||
title: `${titlePrefix}${t.title.replace(" per Block", "")}`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Create Compare charts for an address group
|
||||
* @template {AddressableType} K
|
||||
* @param {string} groupName
|
||||
* @param {ReadonlyArray<{key: K, name: string, color: Color}>} types
|
||||
*/
|
||||
const createAddressCompare = (groupName, types) => ({
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: countTypes.map((c) => ({
|
||||
name: c.name,
|
||||
title: `${groupName} ${c.title}`,
|
||||
bottom: types.map((t) =>
|
||||
name: "Compare",
|
||||
title: `${titlePrefix}Address Count`,
|
||||
bottom: countMetrics.map((m) =>
|
||||
line({
|
||||
series: c.getSeries(t.key),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
series: addrs[m.key][key],
|
||||
name: m.name,
|
||||
color: m.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...countMetrics.map((m) => ({
|
||||
name: m.name,
|
||||
title: `${titlePrefix}${m.name} Addresses`,
|
||||
bottom: [
|
||||
line({ series: addrs[m.key][key], name: m.name, unit: Unit.count }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "New",
|
||||
title: `${groupName} New Address Count`,
|
||||
bottom: types.flatMap((t) => [
|
||||
line({
|
||||
series: addrs.new[t.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: addrs.new[t.key].sum._24h,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Reactivated",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: `${groupName} Reactivated Addresses per Block`,
|
||||
bottom: types.map((t) =>
|
||||
],
|
||||
},
|
||||
...simpleDeltaTree({
|
||||
delta: addrs.delta[key],
|
||||
title: `${titlePrefix}Address Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
{
|
||||
name: "New",
|
||||
tree: chartsFromCount({
|
||||
pattern: addrs.new[key],
|
||||
title: `${titlePrefix}New Addresses`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${titlePrefix}Active Addresses ${w.title} Average`,
|
||||
bottom: activityTypes.map((t, i) =>
|
||||
line({
|
||||
series: addrs.activity[t.key].reactivated.base,
|
||||
series: addrs.activity[key][t.key][w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
color: colors.at(i, activityTypes.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
})),
|
||||
},
|
||||
...activityTypes.map((t) =>
|
||||
averagesTree({
|
||||
windows: addrs.activity[key][t.key],
|
||||
title: `${titlePrefix}${t.name} Addresses`,
|
||||
unit: Unit.count,
|
||||
name: t.name,
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {Record<string, typeof scriptTypes[number]>} */
|
||||
const byKey = Object.fromEntries(scriptTypes.map((t) => [t.key, t]));
|
||||
|
||||
const scriptGroups = [
|
||||
{ name: "Legacy", types: [byKey.p2pkh, byKey.p2pk33, byKey.p2pk65] },
|
||||
{ name: "Script Hash", types: [byKey.p2sh, byKey.p2ms] },
|
||||
{ name: "SegWit", types: [byKey.p2wsh, byKey.p2wpkh] },
|
||||
{ name: "Taproot", types: [byKey.p2a, byKey.p2tr] },
|
||||
{ name: "Other", types: [byKey.opReturn, byKey.emptyOutput, byKey.unknownOutput] },
|
||||
];
|
||||
|
||||
/**
|
||||
* @template {keyof typeof scripts.count} K
|
||||
* @param {string} groupName
|
||||
* @param {ReadonlyArray<{key: K, name: string, color: Color}>} types
|
||||
*/
|
||||
const createScriptGroup = (groupName, types) => ({
|
||||
name: groupName,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${groupName} Reactivated Addresses (${w.name})`,
|
||||
title: `${groupName} Output Count ${w.title} Sum`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key].reactivated[w.key],
|
||||
series: /** @type {CountPattern<number>} */ (scripts.count[t.key]).sum[w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${groupName} Output Count (Total)`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: scripts.count[t.key].cumulative,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${groupName} Address Growth Rate (${w.name})`,
|
||||
bottom: types.flatMap((t) =>
|
||||
percentRatio({
|
||||
pattern: addrs.delta[t.key].rate[w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Transacting",
|
||||
tree: transactingTypes.map((tr) => ({
|
||||
name: tr.name,
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: `${groupName} ${tr.compareTitle}`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key][tr.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${groupName} ${tr.compareTitle} (${w.name})`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key][tr.key][w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
],
|
||||
})),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Script type groups for Output Counts
|
||||
const legacyScripts = legacyAddresses.slice(1); // p2pkh, p2pk33, p2pk65
|
||||
const scriptHashScripts = [legacyAddresses[0], nonAddressableTypes[0]]; // p2sh, p2ms
|
||||
const segwitScripts = [
|
||||
/** @type {const} */ ({
|
||||
key: "segwit",
|
||||
name: "All SegWit",
|
||||
color: colors.segwit,
|
||||
}),
|
||||
...segwitAddresses,
|
||||
];
|
||||
const otherScripts = nonAddressableTypes.slice(1); // opreturn, empty, unknown
|
||||
|
||||
/**
|
||||
* Create Compare charts for a script group
|
||||
* @template {keyof typeof scripts.count} K
|
||||
* @param {string} groupName
|
||||
* @param {ReadonlyArray<{key: K, name: string, color: Color}>} types
|
||||
*/
|
||||
const createScriptCompare = (groupName, types) => ({
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: `${groupName} Output Count`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: /** @type {CountPattern<number>} */ (scripts.count[t.key])
|
||||
.sum._24h,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${groupName} Output Count (Total)`,
|
||||
bottom: types.map((t) =>
|
||||
line({
|
||||
series: /** @type {CountPattern<number>} */ (scripts.count[t.key])
|
||||
.cumulative,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...types.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromCount({
|
||||
pattern: /** @type {CountPattern<number>} */ (scripts.count[t.key]),
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -563,47 +354,19 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Velocity",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Transaction Velocity",
|
||||
bottom: [
|
||||
line({
|
||||
series: supply.velocity.native,
|
||||
name: "BTC",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: supply.velocity.fiat,
|
||||
name: "USD",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Native",
|
||||
title: "Transaction Velocity (BTC)",
|
||||
bottom: [
|
||||
line({
|
||||
series: supply.velocity.native,
|
||||
name: "BTC",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fiat",
|
||||
title: "Transaction Velocity (USD)",
|
||||
bottom: [
|
||||
line({
|
||||
series: supply.velocity.fiat,
|
||||
name: "USD",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
title: "Transaction Velocity",
|
||||
bottom: [
|
||||
line({
|
||||
series: supply.velocity.native,
|
||||
name: "BTC",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: supply.velocity.fiat,
|
||||
name: "USD",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -621,7 +384,7 @@ export function createNetworkSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Block Count Rolling",
|
||||
title: "Block Count",
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({
|
||||
series: blocks.count.total.sum[w.key],
|
||||
@@ -633,7 +396,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Block Count ${w.name}`,
|
||||
title: `Block Count ${w.title} Sum`,
|
||||
bottom: [
|
||||
line({
|
||||
series: blocks.count.total.sum[w.key],
|
||||
@@ -655,7 +418,11 @@ export function createNetworkSection() {
|
||||
name: "Cumulative",
|
||||
title: "Block Count (Total)",
|
||||
bottom: [
|
||||
{ series: blocks.count.total.cumulative, title: "all-time", unit: Unit.count },
|
||||
{
|
||||
series: blocks.count.total.cumulative,
|
||||
title: "all-time",
|
||||
unit: Unit.count,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -681,10 +448,9 @@ export function createNetworkSection() {
|
||||
priceLine({ unit: Unit.secs, name: "Target", number: 600 }),
|
||||
],
|
||||
},
|
||||
rollingWindowsTree({
|
||||
averagesTree({
|
||||
windows: blocks.interval,
|
||||
title: "Block Interval",
|
||||
name: "Averages",
|
||||
unit: Unit.secs,
|
||||
}),
|
||||
],
|
||||
@@ -778,10 +544,10 @@ export function createNetworkSection() {
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Activity Rate",
|
||||
name: "Throughput",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Activity Rate (${w.name})`,
|
||||
title: `Throughput ${w.title} Average`,
|
||||
bottom: [
|
||||
line({
|
||||
series: transactions.volume.txPerSec[w.key],
|
||||
@@ -809,15 +575,12 @@ export function createNetworkSection() {
|
||||
{
|
||||
name: "Addresses",
|
||||
tree: [
|
||||
// Overview - global series for all addresses
|
||||
{ name: "Overview", tree: createAddressSeriesTree("all", "") },
|
||||
|
||||
// Top-level Compare - all types
|
||||
...createAddressSeriesTree("all", ""),
|
||||
{
|
||||
name: "Compare",
|
||||
name: "By Type",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
name: "Compare",
|
||||
tree: countTypes.map((c) => ({
|
||||
name: c.name,
|
||||
title: c.title,
|
||||
@@ -832,139 +595,7 @@ export function createNetworkSection() {
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "New",
|
||||
title: "New Address Count by Type",
|
||||
bottom: addressTypes.flatMap((t) => [
|
||||
line({
|
||||
series: addrs.new[t.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
line({
|
||||
series: addrs.new[t.key].sum._24h,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Reactivated",
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: "Reactivated Addresses per Block by Type",
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key].reactivated.base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Reactivated Addresses by Type (${w.name})`,
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key].reactivated[w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Address Growth Rate by Type (${w.name})`,
|
||||
bottom: addressTypes.flatMap((t) =>
|
||||
percentRatio({
|
||||
pattern: addrs.delta[t.key].rate[w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Transacting",
|
||||
tree: transactingTypes.map((tr) => ({
|
||||
name: tr.name,
|
||||
tree: [
|
||||
{
|
||||
name: "Base",
|
||||
title: tr.compareTitle,
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key][tr.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${tr.compareTitle} (${w.name})`,
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
series: addrs.activity[t.key][tr.key][w.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
],
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Legacy (pre-SegWit)
|
||||
{
|
||||
name: "Legacy",
|
||||
tree: [
|
||||
createAddressCompare("Legacy", legacyAddresses),
|
||||
...legacyAddresses.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressSeriesTree(t.key, `${t.name} `),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// SegWit
|
||||
{
|
||||
name: "SegWit",
|
||||
tree: [
|
||||
createAddressCompare("SegWit", segwitAddresses),
|
||||
...segwitAddresses.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressSeriesTree(t.key, `${t.name} `),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Taproot
|
||||
{
|
||||
name: "Taproot",
|
||||
tree: [
|
||||
createAddressCompare("Taproot", taprootAddresses),
|
||||
...taprootAddresses.map((t) => ({
|
||||
...addressTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressSeriesTree(t.key, `${t.name} `),
|
||||
})),
|
||||
@@ -978,191 +609,37 @@ export function createNetworkSection() {
|
||||
name: "Scripts",
|
||||
tree: [
|
||||
{
|
||||
name: "By Type",
|
||||
name: "Compare",
|
||||
tree: [
|
||||
// Compare section
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Output Count by Script Type",
|
||||
bottom: scriptTypes.map((t) =>
|
||||
line({
|
||||
series: /** @type {CountPattern<number>} */ (
|
||||
scripts.count[t.key]
|
||||
).sum._24h,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Output Count by Script Type (Total)",
|
||||
bottom: scriptTypes.map((t) =>
|
||||
line({
|
||||
series: scripts.count[t.key].cumulative,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Legacy",
|
||||
tree: [
|
||||
createScriptCompare("Legacy", legacyScripts),
|
||||
...legacyScripts.map((t) => ({
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `Output Count by Script Type ${w.title} Sum`,
|
||||
bottom: scriptTypes.map((t) =>
|
||||
line({
|
||||
series: /** @type {CountPattern<number>} */ (scripts.count[t.key]).sum[w.key],
|
||||
name: t.name,
|
||||
tree: chartsFromCount({
|
||||
pattern: /** @type {CountPattern<number>} */ (
|
||||
scripts.count[t.key]
|
||||
),
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Script Hash",
|
||||
tree: [
|
||||
createScriptCompare("Script Hash", scriptHashScripts),
|
||||
...scriptHashScripts.map((t) => ({
|
||||
name: "Cumulative",
|
||||
title: "Output Count by Script Type (Total)",
|
||||
bottom: scriptTypes.map((t) =>
|
||||
line({
|
||||
series: scripts.count[t.key].cumulative,
|
||||
name: t.name,
|
||||
tree: chartsFromCount({
|
||||
pattern: /** @type {CountPattern<number>} */ (
|
||||
scripts.count[t.key]
|
||||
),
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "SegWit",
|
||||
tree: [
|
||||
createScriptCompare("SegWit", segwitScripts),
|
||||
...segwitScripts.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromCount({
|
||||
pattern: /** @type {CountPattern<number>} */ (
|
||||
scripts.count[t.key]
|
||||
),
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Taproot",
|
||||
tree: [
|
||||
createScriptCompare("Taproot", taprootAddresses),
|
||||
...taprootAddresses.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromCount({
|
||||
pattern: /** @type {CountPattern<number>} */ (
|
||||
scripts.count[t.key]
|
||||
),
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Other",
|
||||
tree: [
|
||||
createScriptCompare("Other", otherScripts),
|
||||
...otherScripts.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromCount({
|
||||
pattern: /** @type {CountPattern<number>} */ (
|
||||
scripts.count[t.key]
|
||||
),
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adoption",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Script Adoption",
|
||||
bottom: [
|
||||
line({
|
||||
series: scripts.adoption.segwit.percent,
|
||||
name: "SegWit",
|
||||
color: colors.segwit,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
series: scripts.adoption.segwit.ratio,
|
||||
name: "SegWit",
|
||||
color: colors.segwit,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: scripts.adoption.taproot.percent,
|
||||
name: "Taproot",
|
||||
color: taprootAddresses[1].color,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
series: scripts.adoption.taproot.ratio,
|
||||
name: "Taproot",
|
||||
color: taprootAddresses[1].color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "SegWit",
|
||||
title: "SegWit Adoption",
|
||||
bottom: [
|
||||
line({
|
||||
series: scripts.adoption.segwit.percent,
|
||||
name: "Adoption",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
series: scripts.adoption.segwit.ratio,
|
||||
name: "Adoption",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Taproot",
|
||||
title: "Taproot Adoption",
|
||||
bottom: [
|
||||
line({
|
||||
series: scripts.adoption.taproot.percent,
|
||||
name: "Adoption",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
series: scripts.adoption.taproot.ratio,
|
||||
name: "Adoption",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
...scriptGroups.map((g) => createScriptGroup(g.name, g.types)),
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -153,7 +153,9 @@ export function createPartialOptions() {
|
||||
list: utxosUnderAmount,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...utxosUnderAmount.map(createCohortFolderBasicWithMarketCap),
|
||||
...utxosUnderAmount.map(
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
),
|
||||
],
|
||||
},
|
||||
// More Than (≥ X sats)
|
||||
@@ -166,7 +168,9 @@ export function createPartialOptions() {
|
||||
list: utxosOverAmount,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...utxosOverAmount.map(createCohortFolderBasicWithMarketCap),
|
||||
...utxosOverAmount.map(
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
),
|
||||
],
|
||||
},
|
||||
// Range
|
||||
@@ -179,7 +183,9 @@ export function createPartialOptions() {
|
||||
list: utxosAmountRange,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...utxosAmountRange.map(createCohortFolderBasicWithoutMarketCap),
|
||||
...utxosAmountRange.map(
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -283,14 +289,14 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
|
||||
// Investing section
|
||||
createInvestingSection(),
|
||||
|
||||
// Frameworks section
|
||||
{
|
||||
name: "Frameworks",
|
||||
tree: [createCointimeSection()],
|
||||
},
|
||||
|
||||
// Investing section
|
||||
createInvestingSection(),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ import { Unit } from "../utils/units.js";
|
||||
|
||||
/** @typedef {'_24h' | '_1w' | '_1m' | '_1y'} RollingWindowKey */
|
||||
|
||||
/** @type {ReadonlyArray<{key: RollingWindowKey, name: string, color: Color}>} */
|
||||
/** @type {ReadonlyArray<{key: RollingWindowKey, name: string, title: string, color: Color}>} */
|
||||
export const ROLLING_WINDOWS = [
|
||||
{ key: "_24h", name: "24h", color: colors.time._24h },
|
||||
{ key: "_1w", name: "1w", color: colors.time._1w },
|
||||
{ key: "_1m", name: "1m", color: colors.time._1m },
|
||||
{ key: "_1y", name: "1y", color: colors.time._1y },
|
||||
{ key: "_24h", name: "24h", title: "Daily", color: colors.time._24h },
|
||||
{ key: "_1w", name: "1w", title: "Weekly", color: colors.time._1w },
|
||||
{ key: "_1m", name: "1m", title: "Monthly", color: colors.time._1m },
|
||||
{ key: "_1y", name: "1y", title: "Yearly", color: colors.time._1y },
|
||||
];
|
||||
|
||||
/** @type {ReadonlyArray<{key: '_24h' | '_1w' | '_1m', name: string, color: Color}>} */
|
||||
@@ -474,19 +474,20 @@ export function statsAtWindow(pattern, window) {
|
||||
* Create a Rolling folder tree from a _1m1w1y24hPattern (4 rolling windows)
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {string} args.title - Compare chart title
|
||||
* @param {(w: typeof ROLLING_WINDOWS[number]) => string} args.windowTitle - Individual window chart title
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} [args.name]
|
||||
* @param {string} args.name
|
||||
* @param {(args: {series: AnySeriesPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series]
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function rollingWindowsTree({ windows, title, unit, name = "Sums", series = line }) {
|
||||
function rollingWindowsTree({ windows, title, windowTitle, unit, name, series = line }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${title} Rolling`,
|
||||
title,
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
series({
|
||||
series: windows[w.key],
|
||||
@@ -498,7 +499,7 @@ export function rollingWindowsTree({ windows, title, unit, name = "Sums", series
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} ${w.name}`,
|
||||
title: windowTitle(w),
|
||||
bottom: [
|
||||
series({
|
||||
series: windows[w.key],
|
||||
@@ -512,6 +513,32 @@ export function rollingWindowsTree({ windows, title, unit, name = "Sums", series
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolling sums tree
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @param {(args: {series: AnySeriesPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series]
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function sumsTree({ windows, title, unit, series }) {
|
||||
return rollingWindowsTree({ windows, title, windowTitle: (w) => `${title} ${w.title} Sum`, unit, name: "Sums", ...(series ? { series } : {}) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolling averages tree
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} [args.name]
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function averagesTree({ windows, title, unit, name = "Averages" }) {
|
||||
return rollingWindowsTree({ windows, title, windowTitle: (w) => `${title} ${w.title} Average`, unit, name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Distribution folder tree with stats at each rolling window (24h/7d/30d/1y)
|
||||
* @param {Object} args
|
||||
@@ -819,7 +846,7 @@ export function chartsFromFull({
|
||||
title,
|
||||
bottom: [{ series: pattern.base, title: "base", unit }],
|
||||
},
|
||||
rollingWindowsTree({ windows: pattern.sum, title, unit }),
|
||||
sumsTree({ windows: pattern.sum, title, unit }),
|
||||
distributionWindowsTree({ pattern, title: distTitle, unit }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
@@ -865,7 +892,7 @@ export function chartsFromAggregated({
|
||||
title,
|
||||
bottom: [{ series: pattern.sum, title: "base", color: stat.sum, unit }],
|
||||
},
|
||||
rollingWindowsTree({ windows: pattern.rolling.sum, title, unit }),
|
||||
sumsTree({ windows: pattern.rolling.sum, title, unit }),
|
||||
distributionWindowsTree({ pattern: pattern.rolling, title: distTitle, unit }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
@@ -920,7 +947,7 @@ export function chartsFromBlockAnd6b({ pattern, title, unit }) {
|
||||
*/
|
||||
export function chartsFromSumsCumulative({ pattern, title, unit, color }) {
|
||||
return [
|
||||
rollingWindowsTree({ windows: pattern.sum, title, unit }),
|
||||
sumsTree({ windows: pattern.sum, title, unit }),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
@@ -988,16 +1015,13 @@ export function multiSeriesTree({ entries, title, unit }) {
|
||||
line({ series: e.base, name: e.name, color: e.color, unit }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Sums",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} (${w.name})`,
|
||||
bottom: entries.map((e) =>
|
||||
line({ series: e.rolling[w.key], name: e.name, color: e.color, unit }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} ${w.title} Sum`,
|
||||
bottom: entries.map((e) =>
|
||||
line({ series: e.rolling[w.key], name: e.name, color: e.color, unit }),
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
|
||||
Reference in New Issue
Block a user