global: snapshot

This commit is contained in:
nym21
2026-03-14 13:05:50 +01:00
parent b4278842d9
commit d53e533c9f
18 changed files with 441 additions and 84 deletions

View File

@@ -5,7 +5,7 @@ use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlock, PerBlock, Resolutions, PercentPerBlock};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub base: Resolutions<StoredF64>,
pub value: Resolutions<StoredF64>,
pub as_hash: LazyPerBlock<StoredF64>,
pub adjustment: PercentPerBlock<BasisPointsSigned32, M>,
pub epoch: PerBlock<Epoch, M>,

View File

@@ -14,7 +14,7 @@ impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "day3_index", version)?,
first_height: EagerVec::forced_import(db, "day3_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -14,7 +14,7 @@ impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "hour1_index", version)?,
first_height: EagerVec::forced_import(db, "hour1_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -14,7 +14,7 @@ impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "hour12_index", version)?,
first_height: EagerVec::forced_import(db, "hour12_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -14,7 +14,7 @@ impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "hour4_index", version)?,
first_height: EagerVec::forced_import(db, "hour4_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -14,7 +14,7 @@ impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "minute10_index", version)?,
first_height: EagerVec::forced_import(db, "minute10_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -14,7 +14,7 @@ impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "minute30_index", version)?,
first_height: EagerVec::forced_import(db, "minute30_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -16,7 +16,7 @@ impl Vecs {
Ok(Self {
identity: EagerVec::forced_import(db, "month1_index", version)?,
date: EagerVec::forced_import(db, "date", version)?,
first_height: EagerVec::forced_import(db, "month1_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -16,7 +16,7 @@ impl Vecs {
Ok(Self {
identity: EagerVec::forced_import(db, "month3_index", version)?,
date: EagerVec::forced_import(db, "date", version)?,
first_height: EagerVec::forced_import(db, "month3_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -16,7 +16,7 @@ impl Vecs {
Ok(Self {
identity: EagerVec::forced_import(db, "month6_index", version)?,
date: EagerVec::forced_import(db, "date", version)?,
first_height: EagerVec::forced_import(db, "month6_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -16,7 +16,7 @@ impl Vecs {
Ok(Self {
identity: EagerVec::forced_import(db, "week1_index", version)?,
date: EagerVec::forced_import(db, "date", version)?,
first_height: EagerVec::forced_import(db, "week1_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -16,7 +16,7 @@ impl Vecs {
Ok(Self {
identity: EagerVec::forced_import(db, "year1_index", version)?,
date: EagerVec::forced_import(db, "date", version)?,
first_height: EagerVec::forced_import(db, "year1_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -16,7 +16,7 @@ impl Vecs {
Ok(Self {
identity: EagerVec::forced_import(db, "year10_index", version)?,
date: EagerVec::forced_import(db, "date", version)?,
first_height: EagerVec::forced_import(db, "year10_first_height", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
})
}
}

View File

@@ -11,7 +11,7 @@ use crate::parallel_import;
#[derive(Traversable)]
pub struct BlocksVecs<M: StorageMode = Rw> {
pub blockhash: M::Stored<BytesVec<Height, BlockHash>>,
#[traversable(wrap = "difficulty", rename = "raw")]
#[traversable(wrap = "difficulty", rename = "value")]
pub difficulty: M::Stored<PcoVec<Height, StoredF64>>,
/// Doesn't guarantee continuity due to possible reorgs and more generally the nature of mining
#[traversable(wrap = "time")]

View File

@@ -23,7 +23,7 @@ pub fn iter_difficulty_epochs(
let epoch_to_height = &computer.indexes.epoch.first_height;
let epoch_to_timestamp = &computer.blocks.time.timestamp.epoch;
let epoch_to_difficulty = &computer.blocks.difficulty.base.epoch;
let epoch_to_difficulty = &computer.blocks.difficulty.value.epoch;
let mut results = Vec::with_capacity(end_epoch.to_usize() - start_epoch.to_usize() + 1);
let mut prev_difficulty: Option<f64> = None;

View File

@@ -5,20 +5,27 @@ import { brk } from "../client.js";
import { includes } from "../utils/array.js";
import { Unit } from "../utils/units.js";
import { priceLine, priceLines } from "./constants.js";
import { baseline, histogram, line, price } from "./series.js";
import {
baseline,
histogram,
line,
price,
percentRatio,
percentRatioBaseline,
} from "./series.js";
import { periodIdToName } from "./utils.js";
/**
* @typedef {Object} Period
* @property {string} id
* @property {Color} color
* @property {AnyMetricPattern} returns
* @property {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} returns
* @property {AnyPricePattern} lookback
* @property {boolean} [defaultActive]
*/
/**
* @typedef {Period & { cagr: AnyMetricPattern }} PeriodWithCagr
* @typedef {Period & { cagr: { percent: AnyMetricPattern, ratio: AnyMetricPattern } }} PeriodWithCagr
*/
/**
@@ -442,14 +449,11 @@ export function createMarketSection() {
name: "Drawdown",
title: "ATH Drawdown",
top: [price({ metric: ath.high, name: "ATH" })],
bottom: [
line({
metric: ath.drawdown.percent,
name: "Drawdown",
color: colors.loss,
unit: Unit.percentage,
}),
],
bottom: percentRatio({
pattern: ath.drawdown,
name: "Drawdown",
color: colors.loss,
}),
},
{
name: "Time Since",
@@ -535,11 +539,10 @@ export function createMarketSection() {
name: "Choppiness",
title: "Choppiness Index",
bottom: [
line({
metric: range.choppinessIndex2w.percent,
...percentRatio({
pattern: range.choppinessIndex2w,
name: "2w",
color: colors.indicator.main,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [61.8, 38.2] }),
],
@@ -1182,14 +1185,11 @@ export function createMarketSection() {
{
name: "Gini",
title: "Gini Coefficient",
bottom: [
line({
metric: indicators.gini.percent,
name: "Gini",
color: colors.loss,
unit: Unit.ratio,
}),
],
bottom: percentRatio({
pattern: indicators.gini,
name: "Gini",
color: colors.loss,
}),
},
{
name: "RHODL Ratio",

View File

@@ -12,12 +12,15 @@ import {
fromSupplyPattern,
chartsFromFullPerBlock,
chartsFromCount,
fromStatsPattern,
chartsFromSumPerBlock,
rollingWindowsTree,
distributionWindowsTree,
mapWindows,
ROLLING_WINDOWS,
chartsFromBlockAnd6b,
percentRatio,
percentRatioDots,
rollingPercentRatioTree,
} from "./series.js";
import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js";
@@ -236,10 +239,9 @@ export function createNetworkSection() {
}),
],
},
rollingWindowsTree({
windows: mapWindows(addresses.delta[key].rate, (r) => r.ratio),
rollingPercentRatioTree({
windows: addresses.delta[key].rate,
title: `${titlePrefix}Address Growth Rate`,
unit: Unit.ratio,
}),
],
},
@@ -351,12 +353,11 @@ export function createNetworkSection() {
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${groupName} Address Growth Rate (${w.name})`,
bottom: types.map((t) =>
line({
metric: addresses.delta[t.key].rate[w.key].ratio,
bottom: types.flatMap((t) =>
percentRatio({
pattern: addresses.delta[t.key].rate[w.key],
name: t.name,
color: t.color,
unit: Unit.ratio,
}),
),
})),
@@ -465,19 +466,58 @@ export function createNetworkSection() {
{
name: "Inflation",
title: "Inflation Rate",
bottom: [
dots({
metric: supply.inflationRate.percent,
name: "Rate",
unit: Unit.percentage,
bottom: percentRatioDots({
pattern: supply.inflationRate,
name: "Rate",
}),
},
{
name: "Market Cap",
tree: [
{
name: "Base",
title: "Market Cap",
bottom: [
line({
metric: supply.marketCap.usd,
name: "Market Cap",
unit: Unit.usd,
}),
],
},
rollingWindowsTree({
windows: mapWindows(
supply.marketCap.delta.change,
(c) => c.usd,
),
title: "Market Cap Change",
unit: Unit.usd,
series: baseline,
}),
rollingPercentRatioTree({
windows: supply.marketCap.delta.rate,
title: "Market Cap Growth Rate",
}),
],
},
{
name: "Hodled or Lost",
title: "Hodled or Lost Supply",
bottom: satsBtcUsd({
pattern: supply.hodledOrLost,
name: "Supply",
}),
},
rollingWindowsTree({
windows: supply.marketMinusRealizedCapGrowthRate,
title: "Market - Realized Cap Growth Rate",
unit: Unit.ratio,
}),
{
name: "Unspendable",
tree: [
{
name: "Sum",
name: "Base",
title: "Unspendable Supply",
bottom: satsBtcUsdFrom({
source: supply.burned.unspendable,
@@ -485,6 +525,31 @@ export function createNetworkSection() {
name: "sum",
}),
},
{
name: "Rolling",
tree: [
{
name: "Compare",
title: "Unspendable Supply Rolling",
bottom: ROLLING_WINDOWS.flatMap((w) =>
satsBtcUsd({
pattern: supply.burned.unspendable.sum[w.key],
name: w.name,
color: w.color,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `Unspendable Supply ${w.name}`,
bottom: satsBtcUsd({
pattern: supply.burned.unspendable.sum[w.key],
name: w.name,
color: w.color,
}),
})),
],
},
{
name: "Cumulative",
title: "Unspendable Supply (Total)",
@@ -500,14 +565,45 @@ export function createNetworkSection() {
name: "OP_RETURN",
tree: [
{
name: "Sum",
name: "Base",
title: "OP_RETURN Burned",
bottom: satsBtcUsd({ pattern: scripts.value.opreturn.base, name: "sum" }),
bottom: satsBtcUsd({
pattern: supply.burned.opreturn.base,
name: "sum",
}),
},
{
name: "Rolling",
tree: [
{
name: "Compare",
title: "OP_RETURN Burned Rolling",
bottom: ROLLING_WINDOWS.flatMap((w) =>
satsBtcUsd({
pattern: supply.burned.opreturn.sum[w.key],
name: w.name,
color: w.color,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `OP_RETURN Burned ${w.name}`,
bottom: satsBtcUsd({
pattern: supply.burned.opreturn.sum[w.key],
name: w.name,
color: w.color,
}),
})),
],
},
{
name: "Cumulative",
title: "OP_RETURN Burned (Total)",
bottom: satsBtcUsd({ pattern: scripts.value.opreturn.cumulative, name: "all-time" }),
bottom: satsBtcUsd({
pattern: supply.burned.opreturn.cumulative,
name: "all-time",
}),
},
],
},
@@ -527,12 +623,25 @@ export function createNetworkSection() {
}),
},
{
name: "Fee Rate",
title: "Transaction Fee Rate",
bottom: fromStatsPattern({
pattern: transactions.fees.feeRate.block,
unit: Unit.feeRate,
}),
name: "Fees",
tree: [
{
name: "Fee Rate",
tree: chartsFromBlockAnd6b({
pattern: transactions.fees.feeRate,
title: "Transaction Fee Rate",
unit: Unit.feeRate,
}),
},
{
name: "Fee",
tree: chartsFromBlockAnd6b({
pattern: transactions.fees.fee,
title: "Transaction Fee",
unit: Unit.sats,
}),
},
],
},
{
name: "Volume",
@@ -553,27 +662,92 @@ export function createNetworkSection() {
],
},
{
name: "Annualized",
title: "Annualized Transaction Volume",
name: "Sent Rolling",
tree: [
{
name: "Compare",
title: "Sent Volume Rolling",
bottom: ROLLING_WINDOWS.flatMap((w) =>
satsBtcUsd({
pattern: transactions.volume.sentSum.sum[w.key],
name: w.name,
color: w.color,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `Sent Volume ${w.name}`,
bottom: satsBtcUsd({
pattern: transactions.volume.sentSum.sum[w.key],
name: w.name,
color: w.color,
}),
})),
],
},
{
name: "Sent Cumulative",
title: "Sent Volume (Total)",
bottom: satsBtcUsd({
pattern: transactions.volume.sentSum.sum._1y,
name: "Annualized",
pattern: transactions.volume.sentSum.cumulative,
name: "all-time",
}),
},
{
name: "Received Rolling",
tree: [
{
name: "Compare",
title: "Received Volume Rolling",
bottom: ROLLING_WINDOWS.flatMap((w) =>
satsBtcUsd({
pattern: transactions.volume.receivedSum.sum[w.key],
name: w.name,
color: w.color,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `Received Volume ${w.name}`,
bottom: satsBtcUsd({
pattern: transactions.volume.receivedSum.sum[w.key],
name: w.name,
color: w.color,
}),
})),
],
},
{
name: "Received Cumulative",
title: "Received Volume (Total)",
bottom: satsBtcUsd({
pattern: transactions.volume.receivedSum.cumulative,
name: "all-time",
}),
},
],
},
{
name: "Size",
title: "Transaction Size",
bottom: [
...fromStatsPattern({
pattern: transactions.size.weight.block,
unit: Unit.wu,
}),
...fromStatsPattern({
pattern: transactions.size.vsize.block,
unit: Unit.vb,
}),
tree: [
{
name: "Weight",
tree: chartsFromBlockAnd6b({
pattern: transactions.size.weight,
title: "Transaction Weight",
unit: Unit.wu,
}),
},
{
name: "vSize",
tree: chartsFromBlockAnd6b({
pattern: transactions.size.vsize,
title: "Transaction vSize",
unit: Unit.vb,
}),
},
],
},
{
@@ -592,6 +766,39 @@ export function createNetworkSection() {
}),
),
},
{
name: "Rolling",
tree: [
{
name: "Compare",
title: "Transaction Versions Rolling",
bottom: entries(transactions.versions).flatMap(
([v, data], i, arr) =>
ROLLING_WINDOWS.map((w) =>
line({
metric: data.sum[w.key],
name: `${v} ${w.name}`,
color: colors.at(i, arr.length),
unit: Unit.count,
}),
),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `Transaction Versions (${w.name})`,
bottom: entries(transactions.versions).map(
([v, data], i, arr) =>
line({
metric: data.sum[w.key],
name: v,
color: colors.at(i, arr.length),
unit: Unit.count,
}),
),
})),
],
},
{
name: "Cumulative",
title: "Transaction Versions (Total)",
@@ -815,12 +1022,33 @@ export function createNetworkSection() {
{
name: "Fullness",
title: "Block Fullness",
bottom: [
dots({
metric: blocks.fullness.percent,
name: "base",
unit: Unit.percentage,
}),
bottom: percentRatioDots({
pattern: blocks.fullness,
name: "base",
}),
},
{
name: "Difficulty",
tree: [
{
name: "Base",
title: "Mining Difficulty",
bottom: [
line({
metric: blocks.difficulty.base,
name: "Difficulty",
unit: Unit.count,
}),
],
},
{
name: "Adjustment",
title: "Difficulty Adjustment",
bottom: percentRatioDots({
pattern: blocks.difficulty.adjustment,
name: "Adjustment",
}),
},
],
},
],
@@ -996,12 +1224,11 @@ export function createNetworkSection() {
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `Address Growth Rate by Type (${w.name})`,
bottom: addressTypes.map((t) =>
line({
metric: addresses.delta[t.key].rate[w.key].ratio,
bottom: addressTypes.flatMap((t) =>
percentRatio({
pattern: addresses.delta[t.key].rate[w.key],
name: t.name,
color: t.color,
unit: Unit.ratio,
defaultActive: t.defaultActive,
}),
),
@@ -1218,12 +1445,24 @@ export function createNetworkSection() {
color: colors.segwit,
unit: Unit.percentage,
}),
line({
metric: scripts.adoption.segwit.ratio,
name: "SegWit",
color: colors.segwit,
unit: Unit.ratio,
}),
line({
metric: scripts.adoption.taproot.percent,
name: "Taproot",
color: taprootAddresses[1].color,
unit: Unit.percentage,
}),
line({
metric: scripts.adoption.taproot.ratio,
name: "Taproot",
color: taprootAddresses[1].color,
unit: Unit.ratio,
}),
],
},
{
@@ -1235,6 +1474,11 @@ export function createNetworkSection() {
name: "Adoption",
unit: Unit.percentage,
}),
line({
metric: scripts.adoption.segwit.ratio,
name: "Adoption",
unit: Unit.ratio,
}),
],
},
{
@@ -1246,6 +1490,11 @@ export function createNetworkSection() {
name: "Adoption",
unit: Unit.percentage,
}),
line({
metric: scripts.adoption.taproot.ratio,
name: "Adoption",
unit: Unit.ratio,
}),
],
},
],

View File

@@ -599,6 +599,86 @@ export function fromSupplyPattern({ pattern, title, color }) {
];
}
// ============================================================================
// Percent + Ratio helpers
// ============================================================================
/**
* Create percent + ratio series from a BpsPercentRatioPattern
* @param {Object} args
* @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} args.pattern
* @param {string} args.name
* @param {Color} [args.color]
* @param {boolean} [args.defaultActive]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function percentRatio({ pattern, name, color, defaultActive }) {
return [
line({ metric: pattern.percent, name, color, defaultActive, unit: Unit.percentage }),
line({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }),
];
}
/**
* Create percent + ratio dots series from a BpsPercentRatioPattern
* @param {Object} args
* @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} args.pattern
* @param {string} args.name
* @param {Color} [args.color]
* @param {boolean} [args.defaultActive]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function percentRatioDots({ pattern, name, color, defaultActive }) {
return [
dots({ metric: pattern.percent, name, color, defaultActive, unit: Unit.percentage }),
dots({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }),
];
}
/**
* Create percent + ratio baseline series from a BpsPercentRatioPattern
* @param {Object} args
* @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} args.pattern
* @param {string} args.name
* @param {Color} [args.color]
* @param {boolean} [args.defaultActive]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function percentRatioBaseline({ pattern, name, color, defaultActive }) {
return [
baseline({ metric: pattern.percent, name, color, defaultActive, unit: Unit.percentage }),
baseline({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }),
];
}
/**
* Create a Rolling folder tree where each window is a BpsPercentRatioPattern (percent + ratio)
* @param {Object} args
* @param {{ _24h: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1w: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1m: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1y: { percent: AnyMetricPattern, ratio: AnyMetricPattern } }} args.windows
* @param {string} args.title
* @param {(args: {pattern: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, name: string, color: Color}) => AnyFetchedSeriesBlueprint[]} [args.series]
* @returns {PartialOptionsGroup}
*/
export function rollingPercentRatioTree({ windows, title, series = percentRatio }) {
return {
name: "Rolling",
tree: [
{
name: "Compare",
title: `${title} Rolling`,
bottom: ROLLING_WINDOWS.flatMap((w) =>
series({ pattern: windows[w.key], name: w.name, color: w.color }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${title} ${w.name}`,
bottom: series({ pattern: windows[w.key], name: w.name, color: w.color }),
})),
],
};
}
// ============================================================================
// Chart-generating helpers (return PartialOptionsTree for folder structures)
// ============================================================================
@@ -798,6 +878,29 @@ export function chartsFromSum({
export const chartsFromSumPerBlock = (args) =>
chartsFromSum({ ...args, distributionSuffix: "per Block" });
/**
* Create Per Block + Per 6 Blocks stats charts from a _6bBlockTxPattern
* @param {Object} args
* @param {{ block: DistributionStats, _6b: DistributionStats }} args.pattern
* @param {string} args.title
* @param {Unit} args.unit
* @returns {PartialOptionsTree}
*/
export function chartsFromBlockAnd6b({ pattern, title, unit }) {
return [
{
name: "Per Block",
title: `${title} per Block`,
bottom: fromStatsPattern({ pattern: pattern.block, unit }),
},
{
name: "Per 6 Blocks",
title: `${title} per 6 Blocks`,
bottom: fromStatsPattern({ pattern: pattern._6b, unit }),
},
];
}
/**
* Split pattern with rolling sum windows + cumulative into charts
* @param {Object} args
@@ -809,6 +912,11 @@ export const chartsFromSumPerBlock = (args) =>
*/
export function chartsFromCount({ pattern, title, unit, color }) {
return [
{
name: "Base",
title,
bottom: [{ metric: pattern.base, title: "base", color, unit }],
},
rollingWindowsTree({ windows: pattern.sum, title, unit }),
{
name: "Cumulative",