global: snapshot

This commit is contained in:
nym21
2026-03-14 12:36:37 +01:00
parent a0d378d06d
commit b4278842d9
37 changed files with 2591 additions and 4435 deletions

View File

@@ -15,7 +15,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "day1", version)?,
identity: EagerVec::forced_import(db, "day1_index", version)?,
date: EagerVec::forced_import(db, "date", version + Version::ONE)?,
first_height: EagerVec::forced_import(db, "first_height", version)?,
height_count: EagerVec::forced_import(db, "height_count", version)?,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "month1", version)?,
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)?,
})

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "month3", version)?,
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)?,
})

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "month6", version)?,
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)?,
})

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "week1", version)?,
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)?,
})

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "year1", version)?,
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)?,
})

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self {
identity: EagerVec::forced_import(db, "year10", version)?,
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)?,
})

View File

@@ -5,8 +5,6 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use vecdb::PrintableIndex;
use crate::PairOutputIndex;
use super::{
Date, Day1, Day3, EmptyAddressIndex, EmptyOutputIndex, Epoch, FundedAddressIndex, Halving,
Height, Hour1, Hour4, Hour12, Minute10, Minute30, Month1, Month3, Month6, OpReturnIndex,
@@ -39,28 +37,44 @@ pub enum Index {
Halving,
Epoch,
Height,
#[serde(rename = "tx_index")]
TxIndex,
#[serde(rename = "txin_index")]
TxInIndex,
#[serde(rename = "txout_index")]
TxOutIndex,
#[serde(rename = "empty_output_index")]
EmptyOutputIndex,
#[serde(rename = "op_return_index")]
OpReturnIndex,
#[serde(rename = "p2a_address_index")]
P2AAddressIndex,
#[serde(rename = "p2ms_output_index")]
P2MSOutputIndex,
#[serde(rename = "p2pk33_address_index")]
P2PK33AddressIndex,
#[serde(rename = "p2pk65_address_index")]
P2PK65AddressIndex,
#[serde(rename = "p2pkh_address_index")]
P2PKHAddressIndex,
#[serde(rename = "p2sh_address_index")]
P2SHAddressIndex,
#[serde(rename = "p2tr_address_index")]
P2TRAddressIndex,
#[serde(rename = "p2wpkh_address_index")]
P2WPKHAddressIndex,
#[serde(rename = "p2wsh_address_index")]
P2WSHAddressIndex,
#[serde(rename = "unknown_output_index")]
UnknownOutputIndex,
#[serde(rename = "funded_address_index")]
FundedAddressIndex,
#[serde(rename = "empty_address_index")]
EmptyAddressIndex,
PairOutputIndex,
}
impl Index {
pub const fn all() -> [Self; 34] {
pub const fn all() -> [Self; 33] {
[
Self::Minute10,
Self::Minute30,
@@ -95,7 +109,6 @@ impl Index {
Self::UnknownOutputIndex,
Self::FundedAddressIndex,
Self::EmptyAddressIndex,
Self::PairOutputIndex,
]
}
@@ -134,7 +147,6 @@ impl Index {
Self::UnknownOutputIndex => UnknownOutputIndex::to_possible_strings(),
Self::FundedAddressIndex => FundedAddressIndex::to_possible_strings(),
Self::EmptyAddressIndex => EmptyAddressIndex::to_possible_strings(),
Self::PairOutputIndex => PairOutputIndex::to_possible_strings(),
}
}
@@ -180,7 +192,6 @@ impl Index {
Self::UnknownOutputIndex => <UnknownOutputIndex as PrintableIndex>::to_string(),
Self::FundedAddressIndex => <FundedAddressIndex as PrintableIndex>::to_string(),
Self::EmptyAddressIndex => <EmptyAddressIndex as PrintableIndex>::to_string(),
Self::PairOutputIndex => <PairOutputIndex as PrintableIndex>::to_string(),
}
}

View File

@@ -131,7 +131,6 @@ mod p2wsh_address_index;
mod p2wsh_bytes;
mod pagination;
mod pagination_index;
mod pair_output_index;
mod percentile;
mod pool;
mod pool_detail;
@@ -327,7 +326,6 @@ pub use p2wsh_address_index::*;
pub use p2wsh_bytes::*;
pub use pagination::*;
pub use pagination_index::*;
pub use pair_output_index::*;
pub use percentile::*;
pub use pool::*;
pub use pool_detail::*;

View File

@@ -426,7 +426,7 @@
* Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
* block-based (height, tx_index), and address/output type indexes.
*
* @typedef {("minute10"|"minute30"|"hour1"|"hour4"|"hour12"|"day1"|"day3"|"week1"|"month1"|"month3"|"month6"|"year1"|"year10"|"halving"|"epoch"|"height"|"txindex"|"txinindex"|"txoutindex"|"emptyoutputindex"|"opreturnindex"|"p2aaddressindex"|"p2msoutputindex"|"p2pk33addressindex"|"p2pk65addressindex"|"p2pkhaddressindex"|"p2shaddressindex"|"p2traddressindex"|"p2wpkhaddressindex"|"p2wshaddressindex"|"unknownoutputindex"|"fundedaddressindex"|"emptyaddressindex"|"pairoutputindex")} Index
* @typedef {("minute10"|"minute30"|"hour1"|"hour4"|"hour12"|"day1"|"day3"|"week1"|"month1"|"month3"|"month6"|"year1"|"year10"|"halving"|"epoch"|"height"|"tx_index"|"txin_index"|"txout_index"|"empty_output_index"|"op_return_index"|"p2a_address_index"|"p2ms_output_index"|"p2pk33_address_index"|"p2pk65_address_index"|"p2pkh_address_index"|"p2sh_address_index"|"p2tr_address_index"|"p2wpkh_address_index"|"p2wsh_address_index"|"unknown_output_index"|"funded_address_index"|"empty_address_index")} Index
*/
/**
* Information about an available index and its query aliases
@@ -6001,8 +6001,7 @@ class BrkClient extends BrkClientBase {
"p2wsh_address_index",
"unknown_output_index",
"funded_address_index",
"empty_address_index",
"pair_output_index"
"empty_address_index"
]);
POOL_ID_TO_POOL_NAME = /** @type {const} */ ({

View File

@@ -199,7 +199,7 @@ Year1 = int
Year10 = int
# Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
# block-based (height, tx_index), and address/output type indexes.
Index = Literal["minute10", "minute30", "hour1", "hour4", "hour12", "day1", "day3", "week1", "month1", "month3", "month6", "year1", "year10", "halving", "epoch", "height", "txindex", "txinindex", "txoutindex", "emptyoutputindex", "opreturnindex", "p2aaddressindex", "p2msoutputindex", "p2pk33addressindex", "p2pk65addressindex", "p2pkhaddressindex", "p2shaddressindex", "p2traddressindex", "p2wpkhaddressindex", "p2wshaddressindex", "unknownoutputindex", "fundedaddressindex", "emptyaddressindex", "pairoutputindex"]
Index = Literal["minute10", "minute30", "hour1", "hour4", "hour12", "day1", "day3", "week1", "month1", "month3", "month6", "year1", "year10", "halving", "epoch", "height", "tx_index", "txin_index", "txout_index", "empty_output_index", "op_return_index", "p2a_address_index", "p2ms_output_index", "p2pk33_address_index", "p2pk65_address_index", "p2pkh_address_index", "p2sh_address_index", "p2tr_address_index", "p2wpkh_address_index", "p2wsh_address_index", "unknown_output_index", "funded_address_index", "empty_address_index"]
# Hierarchical tree node for organizing metrics into categories
TreeNode = Union[dict[str, "TreeNode"], "MetricLeafWithSchema"]
class AddressChainStats(TypedDict):
@@ -5322,8 +5322,7 @@ class BrkClient(BrkClientBase):
"p2wsh_address_index",
"unknown_output_index",
"funded_address_index",
"empty_address_index",
"pair_output_index"
"empty_address_index"
]
POOL_ID_TO_POOL_NAME = {

View File

@@ -71,6 +71,8 @@ import { Unit } from "../utils/units.js";
const lineWidth = /** @type {any} */ (1.5);
const MAX_SIZE = 100_000;
/**
* @param {Object} args
* @param {HTMLElement} args.parent
@@ -138,7 +140,7 @@ export function createChart({ parent, brk, fitContent }) {
if (cached) {
this.data = cached;
}
endpoint.slice(-10000).fetch((/** @type {any} */ result) => {
endpoint.slice(-MAX_SIZE).fetch((/** @type {any} */ result) => {
if (currentGen !== generation) return;
cache.set(endpoint.path, result);
this.data = result;
@@ -741,7 +743,7 @@ export function createChart({ parent, brk, fitContent }) {
valuesVersion = cachedValues.version;
tryProcess();
}
await valuesEndpoint.slice(-10000).fetch((result) => {
await valuesEndpoint.slice(-MAX_SIZE).fetch((result) => {
cache.set(valuesEndpoint.path, result);
valuesData = result.data;
valuesStamp = result.stamp;

View File

@@ -1,8 +1,8 @@
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { Unit } from "../utils/units.js";
import { dots, line, price, rollingWindowsTree } from "./series.js";
import { satsBtcUsd, createPriceRatioCharts } from "./shared.js";
import { dots, line, baseline, price, rollingWindowsTree } from "./series.js";
import { satsBtcUsd } from "./shared.js";
/**
* Create Cointime section
@@ -24,7 +24,7 @@ export function createCointimeSection() {
// Reference lines for cap comparisons
const capReferenceLines = /** @type {const} */ ([
{ metric: supply.marketCap, name: "Market", color: colors.default },
{ metric: supply.marketCap.usd, name: "Market", color: colors.default },
{
metric: all.realized.cap.usd,
name: "Realized",
@@ -153,24 +153,53 @@ export function createCointimeSection() {
),
],
},
...prices.map(({ pattern, name, color }) => ({
name,
tree: createPriceRatioCharts({
context: `${name} Price`,
legend: name,
pricePattern: pattern,
ratio: pattern,
color,
priceReferences: [
price({
metric: all.realized.price,
name: "Realized",
color: colors.realized,
defaultActive: false,
}),
...prices.map(({ pattern, name, color }) => {
const p = pattern.percentiles;
const pctUsd = /** @type {const} */ ([
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
]);
const pctRatio = /** @type {const} */ ([
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
]);
return {
name,
tree: [
{
name: "Price",
title: `${name} Price`,
top: [
price({ metric: pattern, name, color }),
price({
metric: all.realized.price,
name: "Realized",
color: colors.realized,
defaultActive: false,
}),
...pctUsd.map(({ name: pName, prop, color: pColor }) =>
price({ metric: prop, name: pName, color: pColor, defaultActive: false, options: { lineStyle: 1 } }),
),
],
},
{
name: "Ratio",
title: `${name} Price Ratio`,
top: [price({ metric: pattern, name, color })],
bottom: [
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
...pctRatio.map(({ name: pName, prop, color: pColor }) =>
line({ metric: prop, name: pName, color: pColor, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }),
),
],
},
],
}),
})),
};
}),
],
},

View File

@@ -6,7 +6,7 @@ import { line } from "./series.js";
/**
* Get constant pattern by number dynamically from tree
* Examples: 0 → constant0, 38.2 → constant382, -1 → constantMinus1
* Examples: 0 → _0, 38.2 → _382, -1 → minus1
* @param {BrkClient["metrics"]["constants"]} constants
* @param {number} num
* @returns {AnyMetricPattern}
@@ -14,8 +14,8 @@ import { line } from "./series.js";
export function getConstant(constants, num) {
const key =
num >= 0
? `constant${String(num).replace(".", "")}`
: `constantMinus${Math.abs(num)}`;
? `_${String(num).replace(".", "")}`
: `minus${Math.abs(num)}`;
const constant = /** @type {AnyMetricPattern | undefined} */ (
/** @type {Record<string, AnyMetricPattern>} */ (constants)[key]
);

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@
* - Summary: Key stats (avg + median active, quartiles/extremes available)
* - By Coin: BTC-weighted percentiles (IQR active: p25, p50, p75)
* - By Capital: USD-weighted percentiles (IQR active: p25, p50, p75)
* - Price Position: Spot percentile (both perspectives active)
* - Supply Density: Cost basis supply density percentage
*
* For cohorts WITHOUT percentiles: Summary only
* Only for cohorts WITH costBasis (All, STH, LTH)
*/
import { colors } from "../../utils/colors.js";
@@ -38,37 +38,14 @@ function createCorePercentileSeries(p, n = (x) => x) {
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @param {CohortAll | CohortFull | CohortLongTerm} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleSummarySeriesBasic(cohort) {
const { color, tree } = cohort;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: tree.costBasis.max,
name: "Max",
color: colors.stat.max,
defaultActive: false,
}),
price({
metric: tree.costBasis.min,
name: "Min",
color: colors.stat.min,
defaultActive: false,
}),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleSummarySeriesWithPercentiles(cohort) {
function createSingleSummarySeries(cohort) {
const { color, tree } = cohort;
const p = tree.costBasis.percentiles;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({ metric: tree.realized.price, name: "Average", color }),
price({
metric: tree.costBasis.max,
name: "Max (p100)",
@@ -98,25 +75,25 @@ function createSingleSummarySeriesWithPercentiles(cohort) {
}
/**
* @param {readonly CohortObject[]} list
* @param {readonly (CohortAll | CohortFull | CohortLongTerm)[]} list
* @param {CohortAll} all
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createGroupedSummarySeries(list, all) {
return mapCohortsWithAll(list, all, ({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
price({ metric: tree.realized.price, name, color }),
);
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @param {CohortAll | CohortFull | CohortLongTerm} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleByCoinSeries(cohort) {
const { color, tree } = cohort;
const cb = tree.costBasis;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({ metric: tree.realized.price, name: "Average", color }),
price({
metric: cb.max,
name: "p100",
@@ -134,59 +111,36 @@ function createSingleByCoinSeries(cohort) {
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @param {CohortAll | CohortFull | CohortLongTerm} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleByCapitalSeries(cohort) {
const { color, tree } = cohort;
return [
price({ metric: tree.realized.investorPrice, name: "Average", color }),
price({ metric: tree.realized.investor.price, name: "Average", color }),
...createCorePercentileSeries(tree.costBasis.investedCapital),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @param {CohortAll | CohortFull | CohortLongTerm} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSinglePricePositionSeries(cohort) {
function createSingleSupplyDensitySeries(cohort) {
const { tree } = cohort;
return [
line({
metric: tree.costBasis.spotCostBasisPercentile,
name: "By Coin",
metric: tree.costBasis.supplyDensity.percent,
name: "Supply Density",
color: colors.bitcoin,
unit: Unit.percentage,
}),
line({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name: "By Capital",
color: colors.usd,
unit: Unit.percentage,
}),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
];
}
/**
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createCostBasisSection({ cohort, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createSingleSummarySeriesBasic(cohort),
},
],
};
}
/**
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @param {{ cohort: CohortAll | CohortFull | CohortLongTerm, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createCostBasisSectionWithPercentiles({ cohort, title }) {
@@ -196,7 +150,7 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createSingleSummarySeriesWithPercentiles(cohort),
top: createSingleSummarySeries(cohort),
},
{
name: "By Coin",
@@ -209,33 +163,16 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
top: createSingleByCapitalSeries(cohort),
},
{
name: "Price Position",
title: title("Current Price Position"),
bottom: createSinglePricePositionSeries(cohort),
name: "Supply Density",
title: title("Cost Basis Supply Density"),
bottom: createSingleSupplyDensitySeries(cohort),
},
],
};
}
/**
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCostBasisSection({ list, all, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createGroupedSummarySeries(list, all),
},
],
};
}
/**
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], all: CohortAll, title: (metric: string) => string }} args
* @param {{ list: readonly (CohortAll | CohortFull | CohortLongTerm)[], all: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCostBasisSectionWithPercentiles({
@@ -258,7 +195,7 @@ export function createGroupedCostBasisSectionWithPercentiles({
name: "Average",
title: title("Realized Price Comparison"),
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
price({ metric: tree.realized.price, name, color }),
),
},
{
@@ -291,7 +228,7 @@ export function createGroupedCostBasisSectionWithPercentiles({
name: "Average",
title: title("Investor Price Comparison"),
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
price({ metric: tree.realized.investorPrice, name, color }),
price({ metric: tree.realized.investor.price, name, color }),
),
},
{
@@ -330,39 +267,16 @@ export function createGroupedCostBasisSectionWithPercentiles({
],
},
{
name: "Price Position",
tree: [
{
name: "By Coin",
title: title("Price Position (BTC-weighted)"),
bottom: [
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
metric: tree.costBasis.spotCostBasisPercentile,
name,
color,
unit: Unit.percentage,
}),
),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
},
{
name: "By Capital",
title: title("Price Position (USD-weighted)"),
bottom: [
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name,
color,
unit: Unit.percentage,
}),
),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
},
],
name: "Supply Density",
title: title("Cost Basis Supply Density"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
metric: tree.costBasis.supplyDensity.percent,
name,
color,
unit: Unit.percentage,
}),
),
},
],
};

View File

@@ -16,7 +16,7 @@ const ADDRESSABLE_TYPES = [
/** @type {(key: SpendableType) => key is AddressableType} */
const isAddressable = (key) =>
ADDRESSABLE_TYPES.includes(/** @type {any} */ (key));
/** @type {readonly string[]} */ (ADDRESSABLE_TYPES).includes(key);
export function buildCohortData() {
const utxoCohorts = brk.metrics.cohorts.utxo;

File diff suppressed because it is too large Load Diff

View File

@@ -38,9 +38,7 @@ import {
createGroupedPricesSection,
} from "./prices.js";
import {
createCostBasisSection,
createCostBasisSectionWithPercentiles,
createGroupedCostBasisSection,
createGroupedCostBasisSectionWithPercentiles,
} from "./cost-basis.js";
import {
@@ -60,8 +58,12 @@ import {
import {
createActivitySection,
createActivitySectionWithAdjusted,
createActivitySectionWithActivity,
createActivitySectionMinimal,
createGroupedActivitySection,
createGroupedActivitySectionWithAdjusted,
createGroupedActivitySectionWithActivity,
createGroupedActivitySectionMinimal,
} from "./activity.js";
// Re-export data builder
@@ -121,12 +123,11 @@ export function createCohortFolderWithAdjusted(cohort) {
return {
name: cohort.name || "all",
tree: [
createHoldingsSectionWithRelative({ cohort, title }),
createHoldingsSectionWithOwnSupply({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createCostBasisSection({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createActivitySectionWithAdjusted({ cohort, title }),
createActivitySectionWithActivity({ cohort, title }),
],
};
}
@@ -182,11 +183,10 @@ export function createCohortFolderAgeRange(cohort) {
name: cohort.name || "all",
tree: [
createHoldingsSectionWithOwnSupply({ cohort, title }),
createValuationSectionFull({ cohort, title }),
createPricesSectionFull({ cohort, title }),
createCostBasisSectionWithPercentiles({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }),
createActivitySection({ cohort, title }),
createActivitySectionWithActivity({ cohort, title }),
],
};
}
@@ -201,12 +201,11 @@ export function createCohortFolderBasicWithMarketCap(cohort) {
return {
name: cohort.name || "all",
tree: [
createHoldingsSectionWithRelative({ cohort, title }),
createHoldingsSection({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createCostBasisSection({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createActivitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
@@ -221,12 +220,11 @@ export function createCohortFolderBasicWithoutMarketCap(cohort) {
return {
name: cohort.name || "all",
tree: [
createHoldingsSectionWithOwnSupply({ cohort, title }),
createHoldingsSection({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createCostBasisSection({ cohort, title }),
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
createActivitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
@@ -244,9 +242,8 @@ export function createCohortFolderAddress(cohort) {
createHoldingsSectionAddress({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createCostBasisSection({ cohort, title }),
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
createActivitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
@@ -264,9 +261,8 @@ export function createCohortFolderWithoutRelative(cohort) {
createHoldingsSection({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createCostBasisSection({ cohort, title }),
createProfitabilitySection({ cohort, title }),
createActivitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
@@ -284,9 +280,8 @@ export function createAddressCohortFolder(cohort) {
createHoldingsSectionAddressAmount({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createCostBasisSection({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createActivitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
@@ -333,12 +328,11 @@ export function createGroupedCohortFolderWithAdjusted({
return {
name: name || "all",
tree: [
createGroupedHoldingsSectionWithRelative({ list, all, title }),
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSection({ list, all, title }),
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
createGroupedActivitySectionWithAdjusted({ list, all, title }),
createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, all, title }),
createGroupedActivitySectionWithActivity({ list, all, title }),
],
};
}
@@ -406,15 +400,14 @@ export function createGroupedCohortFolderAgeRange({
name: name || "all",
tree: [
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
createGroupedProfitabilitySectionWithInvestedCapitalPct({
list,
all,
title,
}),
createGroupedActivitySection({ list, all, title }),
createGroupedActivitySectionWithActivity({ list, all, title }),
],
};
}
@@ -433,12 +426,11 @@ export function createGroupedCohortFolderBasicWithMarketCap({
return {
name: name || "all",
tree: [
createGroupedHoldingsSectionWithRelative({ list, all, title }),
createGroupedHoldingsSection({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSection({ list, all, title }),
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
createGroupedActivitySection({ list, all, title }),
createGroupedProfitabilitySection({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}
@@ -457,16 +449,15 @@ export function createGroupedCohortFolderBasicWithoutMarketCap({
return {
name: name || "all",
tree: [
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
createGroupedHoldingsSection({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSection({ list, all, title }),
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
list,
all,
title,
}),
createGroupedActivitySection({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}
@@ -488,13 +479,12 @@ export function createGroupedCohortFolderAddress({
createGroupedHoldingsSectionAddress({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSection({ list, all, title }),
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
list,
all,
title,
}),
createGroupedActivitySection({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}
@@ -516,9 +506,8 @@ export function createGroupedCohortFolderWithoutRelative({
createGroupedHoldingsSection({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSection({ list, all, title }),
createGroupedProfitabilitySection({ list, all, title }),
createGroupedActivitySection({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}
@@ -540,9 +529,8 @@ export function createGroupedAddressCohortFolder({
createGroupedHoldingsSectionAddressAmount({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedCostBasisSection({ list, all, title }),
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
createGroupedActivitySection({ list, all, title }),
createGroupedProfitabilitySection({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}

View File

@@ -19,47 +19,9 @@ import { baseline, price } from "../series.js";
import { Unit } from "../../utils/units.js";
/**
* @param {{ realized: { realizedPrice: ActivePricePattern, investorPrice: ActivePricePattern, lowerPriceBand: ActivePricePattern, upperPriceBand: ActivePricePattern } }} tree
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createCompareChart(tree, title) {
return {
name: "Compare",
title: title("Prices"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color: colors.realized,
}),
price({
metric: tree.realized.investorPrice,
name: "Investor",
color: colors.investor,
}),
price({
metric: tree.realized.upperPriceBand,
name: "I²/R",
color: colors.stat.max,
style: 2,
defaultActive: false,
}),
price({
metric: tree.realized.lowerPriceBand,
name: "R²/I",
color: colors.stat.min,
style: 2,
defaultActive: false,
}),
],
};
}
/**
* Create prices section for cohorts with full ActivePriceRatioPattern
* (CohortAll, CohortFull, CohortWithPercentiles)
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* Create prices section for cohorts with full ratio patterns
* (CohortAll, CohortFull, CohortLongTerm)
* @param {{ cohort: CohortAll | CohortFull | CohortLongTerm, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionFull({ cohort, title }) {
@@ -67,14 +29,23 @@ export function createPricesSectionFull({ cohort, title }) {
return {
name: "Prices",
tree: [
createCompareChart(tree, title),
{
name: "Compare",
title: title("Prices"),
top: [
price({ metric: tree.realized.price, name: "Realized", color: colors.realized }),
price({ metric: tree.realized.investor.price, name: "Investor", color: colors.investor }),
price({ metric: tree.realized.investor.upperPriceBand, name: "I²/R", color: colors.stat.max, style: 2, defaultActive: false }),
price({ metric: tree.realized.investor.lowerPriceBand, name: "R²/I", color: colors.stat.min, style: 2, defaultActive: false }),
],
},
{
name: "Realized",
tree: createPriceRatioCharts({
context: cohort.name,
legend: "Realized",
pricePattern: tree.realized.realizedPrice,
ratio: { ...tree.realized.realizedPriceExtra, ...tree.realized.realizedPriceRatioExt },
pricePattern: tree.realized.price,
ratio: tree.realized.price,
color,
priceTitle: title("Realized Price"),
titlePrefix: "Realized Price",
@@ -82,52 +53,19 @@ export function createPricesSectionFull({ cohort, title }) {
},
{
name: "Investor",
tree: createPriceRatioCharts({
context: cohort.name,
legend: "Investor",
pricePattern: tree.realized.investorPrice,
ratio: { ...tree.realized.investorPriceExtra, ...tree.realized.investorPriceRatioExt },
color,
priceTitle: title("Investor Price"),
titlePrefix: "Investor Price",
}),
},
],
};
}
/**
* Create prices section for cohorts with basic ratio patterns only
* (CohortWithAdjusted, CohortBasic, CohortAddress, CohortWithoutRelative)
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionBasic({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Prices",
tree: [
createCompareChart(tree, title),
{
name: "Realized",
tree: [
{
name: "Price",
title: title("Realized Price"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color,
}),
],
title: title("Investor Price"),
top: [price({ metric: tree.realized.investor.price, name: "Investor", color })],
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
title: title("Investor Price Ratio"),
top: [price({ metric: tree.realized.investor.price, name: "Investor", color })],
bottom: [
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
metric: tree.realized.investor.price.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
@@ -136,27 +74,36 @@ export function createPricesSectionBasic({ cohort, title }) {
},
],
},
],
};
}
/**
* Create prices section for cohorts with basic ratio patterns only
* (CohortWithAdjusted, CohortBasic, CohortAddress, CohortWithoutRelative)
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative | CohortAgeRange, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionBasic({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Prices",
tree: [
{
name: "Investor",
name: "Realized",
tree: [
{
name: "Price",
title: title("Investor Price"),
top: [
price({
metric: tree.realized.investorPrice,
name: "Investor",
color,
}),
],
title: title("Realized Price"),
top: [price({ metric: tree.realized.price, name: "Realized", color })],
},
{
name: "Ratio",
title: title("Investor Price Ratio"),
title: title("Realized Price Ratio"),
bottom: [
baseline({
metric: tree.realized.investorPriceExtra.ratio,
name: "Ratio",
metric: tree.realized.mvrv,
name: "MVRV",
unit: Unit.ratio,
base: 1,
}),
@@ -184,40 +131,15 @@ export function createGroupedPricesSection({ list, all, title }) {
name: "Price",
title: title("Realized Price"),
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
price({ metric: tree.realized.price, name, color }),
),
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
title: title("MVRV"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
},
{
name: "Investor",
tree: [
{
name: "Price",
title: title("Investor Price"),
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
price({ metric: tree.realized.investorPrice, name, color }),
),
},
{
name: "Ratio",
title: title("Investor Price Ratio"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.investorPriceExtra.ratio,
metric: tree.realized.mvrv,
name,
color,
unit: Unit.ratio,

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@ function createSingleRealizedCapSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.realized.realizedCap,
metric: tree.realized.cap.usd,
name: "Realized Cap",
color,
unit: Unit.usd,
@@ -37,7 +37,7 @@ function createSingleRealizedCapSeries(cohort) {
function createSingle30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.tree.realized.realizedCap30dDelta,
metric: cohort.tree.realized.cap.delta.change._1m.usd,
name: "30d Change",
unit: Unit.usd,
}),
@@ -47,7 +47,7 @@ function createSingle30dChangeSeries(cohort) {
/**
* Create valuation section for cohorts with full ratio patterns
* (CohortAll, CohortFull, CohortWithPercentiles)
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @param {{ cohort: CohortAll | CohortFull | CohortLongTerm, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createValuationSectionFull({ cohort, title }) {
@@ -61,7 +61,7 @@ export function createValuationSectionFull({ cohort, title }) {
bottom: [
...createSingleRealizedCapSeries(cohort),
baseline({
metric: tree.realized.realizedCapRelToOwnMarketCap,
metric: tree.realized.cap.relToOwnMcap.percent,
name: "Rel. to Own M.Cap",
color,
unit: Unit.pctOwnMcap,
@@ -75,8 +75,8 @@ export function createValuationSectionFull({ cohort, title }) {
},
createRatioChart({
title,
pricePattern: tree.realized.realizedPrice,
ratio: { ...tree.realized.realizedPriceExtra, ...tree.realized.realizedPriceRatioExt },
pricePattern: tree.realized.price,
ratio: tree.realized.price,
color,
name: "MVRV",
}),
@@ -110,7 +110,7 @@ export function createValuationSection({ cohort, title }) {
title: title("MVRV"),
bottom: [
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
metric: tree.realized.mvrv,
name: "MVRV",
unit: Unit.ratio,
base: 1,
@@ -134,7 +134,7 @@ export function createGroupedValuationSection({ list, all, title }) {
title: title("Realized Cap"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
metric: tree.realized.realizedCap,
metric: tree.realized.cap.usd,
name,
color,
unit: Unit.usd,
@@ -146,7 +146,7 @@ export function createGroupedValuationSection({ list, all, title }) {
title: title("Realized Cap 30d Change"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedCap30dDelta,
metric: tree.realized.cap.delta.change._1m.usd,
name,
color,
unit: Unit.usd,
@@ -158,7 +158,7 @@ export function createGroupedValuationSection({ list, all, title }) {
title: title("MVRV"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
metric: tree.realized.mvrv,
name,
color,
unit: Unit.ratio,
@@ -171,7 +171,7 @@ export function createGroupedValuationSection({ list, all, title }) {
}
/**
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], all: CohortAll, title: (metric: string) => string }} args
* @param {{ list: readonly (CohortAll | CohortFull | CohortLongTerm)[], all: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedValuationSectionWithOwnMarketCap({
@@ -188,7 +188,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({
bottom: [
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
metric: tree.realized.realizedCap,
metric: tree.realized.cap.usd,
name,
color,
unit: Unit.usd,
@@ -196,7 +196,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({
),
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedCapRelToOwnMarketCap,
metric: tree.realized.cap.relToOwnMcap.percent,
name,
color,
unit: Unit.pctOwnMcap,
@@ -209,7 +209,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({
title: title("Realized Cap 30d Change"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedCap30dDelta,
metric: tree.realized.cap.delta.change._1m.usd,
name,
color,
unit: Unit.usd,
@@ -221,7 +221,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({
title: title("MVRV"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
metric: tree.realized.mvrv,
name,
color,
unit: Unit.ratio,

View File

@@ -3,7 +3,7 @@
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { Unit } from "../utils/units.js";
import { line, baseline, price, dotted } from "./series.js";
import { baseline, price } from "./series.js";
import { satsBtcUsd } from "./shared.js";
import { periodIdToName } from "./utils.js";
@@ -42,7 +42,7 @@ const YEARS_2020S = /** @type {const} */ ([
const YEARS_2010S = /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]);
/** @typedef {typeof YEARS_2020S[number] | typeof YEARS_2010S[number]} DcaYear */
/** @typedef {`_${DcaYear}`} DcaYearKey */
/** @typedef {`from${DcaYear}`} DcaYearKey */
/** @param {AllPeriodKey} key */
const periodName = (key) => periodIdToName(key.slice(1), true);
@@ -54,10 +54,6 @@ const periodName = (key) => periodIdToName(key.slice(1), true);
* @property {Color} color - Item color
* @property {AnyPricePattern} costBasis - Cost basis metric
* @property {AnyMetricPattern} returns - Returns metric
* @property {AnyMetricPattern} minReturn - Min return metric
* @property {AnyMetricPattern} maxReturn - Max return metric
* @property {AnyMetricPattern} daysInProfit - Days in profit metric
* @property {AnyMetricPattern} daysInLoss - Days in loss metric
* @property {AnyValuePattern} stack - Stack pattern
*/
@@ -76,17 +72,13 @@ const ALL_YEARS = /** @type {const} */ ([...YEARS_2020S, ...YEARS_2010S]);
* @returns {BaseEntryItem}
*/
function buildYearEntry(dca, year, i) {
const key = /** @type {DcaYearKey} */ (`_${year}`);
const key = /** @type {DcaYearKey} */ (`from${year}`);
return {
name: `${year}`,
color: colors.at(i, ALL_YEARS.length),
costBasis: dca.classAveragePrice[key],
returns: dca.classReturns[key],
minReturn: dca.classMinReturn[key],
maxReturn: dca.classMaxReturn[key],
daysInProfit: dca.classDaysInProfit[key],
daysInLoss: dca.classDaysInLoss[key],
stack: dca.classStack[key],
costBasis: dca.class.costBasis[key],
returns: dca.class.return[key].ratio,
stack: dca.class.stack[key],
};
}
@@ -109,42 +101,10 @@ export function createInvestingSection() {
};
}
/**
* Create profitability folder for compare charts
* @param {string} context
* @param {Pick<BaseEntryItem, 'name' | 'color' | 'costBasis' | 'daysInProfit' | 'daysInLoss'>[]} items
*/
function createProfitabilityFolder(context, items) {
const top = items.map(({ name, color, costBasis }) =>
price({ metric: costBasis, name, color }),
);
return {
name: "Profitability",
tree: [
{
name: "Days in Profit",
title: `Days in Profit: ${context}`,
top,
bottom: items.map(({ name, color, daysInProfit }) =>
line({ metric: daysInProfit, name, color, unit: Unit.days }),
),
},
{
name: "Days in Loss",
title: `Days in Loss: ${context}`,
top,
bottom: items.map(({ name, color, daysInLoss }) =>
line({ metric: daysInLoss, name, color, unit: Unit.days }),
),
},
],
};
}
/**
* Create compare folder from items
* @param {string} context
* @param {Pick<BaseEntryItem, 'name' | 'color' | 'costBasis' | 'returns' | 'daysInProfit' | 'daysInLoss' | 'stack'>[]} items
* @param {Pick<BaseEntryItem, 'name' | 'color' | 'costBasis' | 'returns' | 'stack'>[]} items
*/
function createCompareFolder(context, items) {
const topPane = items.map(({ name, color, costBasis }) =>
@@ -171,7 +131,6 @@ function createCompareFolder(context, items) {
}),
),
},
createProfitabilityFolder(context, items),
{
name: "Accumulated",
title: `Accumulated Value: ${context}`,
@@ -190,15 +149,7 @@ function createCompareFolder(context, items) {
* @param {object[]} returnsBottom - Bottom pane items for returns chart
*/
function createSingleEntryTree(item, returnsBottom) {
const {
name,
titlePrefix = name,
color,
costBasis,
daysInProfit,
daysInLoss,
stack,
} = item;
const { name, titlePrefix = name, color, costBasis, stack } = item;
const top = [price({ metric: costBasis, name: "Cost Basis", color })];
return {
name,
@@ -210,25 +161,6 @@ function createSingleEntryTree(item, returnsBottom) {
top,
bottom: returnsBottom,
},
{
name: "Profitability",
title: `Profitability: ${titlePrefix}`,
top,
bottom: [
line({
metric: daysInProfit,
name: "Days in Profit",
color: colors.profit,
unit: Unit.days,
}),
line({
metric: daysInLoss,
name: "Days in Loss",
color: colors.loss,
unit: Unit.days,
}),
],
},
{
name: "Accumulated",
title: `Accumulated Value: ${titlePrefix}`,
@@ -244,23 +176,8 @@ function createSingleEntryTree(item, returnsBottom) {
* @param {BaseEntryItem & { titlePrefix?: string }} item
*/
function createShortSingleEntry(item) {
const { returns, minReturn, maxReturn } = item;
return createSingleEntryTree(item, [
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
dotted({
metric: maxReturn,
name: "Max",
color: colors.profit,
unit: Unit.percentage,
defaultActive: false,
}),
dotted({
metric: minReturn,
name: "Min",
color: colors.loss,
unit: Unit.percentage,
defaultActive: false,
}),
baseline({ metric: item.returns, name: "Current", unit: Unit.percentage }),
]);
}
@@ -269,24 +186,9 @@ function createShortSingleEntry(item) {
* @param {LongEntryItem & { titlePrefix?: string }} item
*/
function createLongSingleEntry(item) {
const { returns, minReturn, maxReturn, cagr } = item;
return createSingleEntryTree(item, [
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
baseline({ metric: cagr, name: "CAGR", unit: Unit.cagr }),
dotted({
metric: maxReturn,
name: "Max",
color: colors.profit,
unit: Unit.percentage,
defaultActive: false,
}),
dotted({
metric: minReturn,
name: "Min",
color: colors.loss,
unit: Unit.percentage,
defaultActive: false,
}),
baseline({ metric: item.returns, name: "Current", unit: Unit.percentage }),
baseline({ metric: item.cagr, name: "CAGR", unit: Unit.cagr }),
]);
}
@@ -301,7 +203,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
/** @param {AllPeriodKey} key */
const topPane = (key) => [
price({
metric: dca.periodAveragePrice[key],
metric: dca.period.costBasis[key],
name: "DCA",
color: colors.profit,
}),
@@ -315,151 +217,54 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
top: topPane(key),
});
/** @param {string} name @param {AllPeriodKey} key */
const returnsMinMax = (name, key) => [
{
name: "Max",
title: `Max Return: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
baseline({
metric: dca.periodMaxReturn[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumMaxReturn[key],
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.percentage,
}),
],
},
{
name: "Min",
title: `Min Return: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
baseline({
metric: dca.periodMinReturn[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumMinReturn[key],
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.percentage,
}),
],
},
];
/** @param {string} name @param {ShortPeriodKey} key */
const shortReturnsFolder = (name, key) => ({
const shortReturnsChart = (name, key) => ({
name: "Returns",
tree: [
{
name: "Current",
title: `Returns: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
baseline({
metric: dca.periodReturns[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.percentage,
}),
],
},
...returnsMinMax(name, key),
title: `Returns: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
baseline({
metric: dca.period.return[key].ratio,
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.period.lumpSumReturn[key].ratio,
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.percentage,
}),
],
});
/** @param {string} name @param {LongPeriodKey} key */
const longReturnsFolder = (name, key) => ({
const longReturnsChart = (name, key) => ({
name: "Returns",
tree: [
{
name: "Current",
title: `Returns: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
baseline({
metric: dca.periodReturns[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.percentage,
}),
baseline({
metric: dca.periodCagr[key],
name: "DCA",
unit: Unit.cagr,
}),
baseline({
metric: returns.cagr[key],
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.cagr,
}),
],
},
...returnsMinMax(name, key),
],
});
/** @param {string} name @param {AllPeriodKey} key */
const profitabilityFolder = (name, key) => ({
name: "Profitability",
tree: [
{
name: "Days in Profit",
title: `Days in Profit: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
line({
metric: dca.periodDaysInProfit[key],
name: "DCA",
color: colors.profit,
unit: Unit.days,
}),
line({
metric: dca.periodLumpSumDaysInProfit[key],
name: "Lump Sum",
color: colors.bitcoin,
unit: Unit.days,
}),
],
},
{
name: "Days in Loss",
title: `Days in Loss: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
line({
metric: dca.periodDaysInLoss[key],
name: "DCA",
color: colors.profit,
unit: Unit.days,
}),
line({
metric: dca.periodLumpSumDaysInLoss[key],
name: "Lump Sum",
color: colors.bitcoin,
unit: Unit.days,
}),
],
},
title: `Returns: ${name} DCA vs Lump Sum`,
top: topPane(key),
bottom: [
baseline({
metric: dca.period.return[key].ratio,
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.period.lumpSumReturn[key].ratio,
name: "Lump Sum",
color: colors.bi.p2,
unit: Unit.percentage,
}),
baseline({
metric: dca.period.cagr[key].ratio,
name: "DCA CAGR",
unit: Unit.cagr,
}),
baseline({
metric: returns.cagr[key].ratio,
name: "Lump Sum CAGR",
color: colors.bi.p2,
unit: Unit.cagr,
}),
],
});
@@ -470,12 +275,12 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
top: topPane(key),
bottom: [
...satsBtcUsd({
pattern: dca.periodStack[key],
pattern: dca.period.stack[key],
name: "DCA",
color: colors.profit,
}),
...satsBtcUsd({
pattern: dca.periodLumpSumStack[key],
pattern: dca.period.lumpSumStack[key],
name: "Lump Sum",
color: colors.bitcoin,
}),
@@ -489,8 +294,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
name,
tree: [
costBasisChart(name, key),
shortReturnsFolder(name, key),
profitabilityFolder(name, key),
shortReturnsChart(name, key),
stackChart(name, key),
],
};
@@ -503,8 +307,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
name,
tree: [
costBasisChart(name, key),
longReturnsFolder(name, key),
profitabilityFolder(name, key),
longReturnsChart(name, key),
stackChart(name, key),
],
};
@@ -545,28 +348,20 @@ function createPeriodSection({ dca, lookback, returns }) {
const buildBaseEntry = (key, i) => ({
name: periodName(key),
color: colors.at(i, allPeriods.length),
costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key],
returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key],
minReturn: isLumpSum
? dca.periodLumpSumMinReturn[key]
: dca.periodMinReturn[key],
maxReturn: isLumpSum
? dca.periodLumpSumMaxReturn[key]
: dca.periodMaxReturn[key],
daysInProfit: isLumpSum
? dca.periodLumpSumDaysInProfit[key]
: dca.periodDaysInProfit[key],
daysInLoss: isLumpSum
? dca.periodLumpSumDaysInLoss[key]
: dca.periodDaysInLoss[key],
stack: isLumpSum ? dca.periodLumpSumStack[key] : dca.periodStack[key],
costBasis: isLumpSum ? lookback[key] : dca.period.costBasis[key],
returns: isLumpSum
? dca.period.lumpSumReturn[key].ratio
: dca.period.return[key].ratio,
stack: isLumpSum
? dca.period.lumpSumStack[key]
: dca.period.stack[key],
});
/** @param {LongPeriodKey} key @param {number} i @returns {LongEntryItem} */
const buildLongEntry = (key, i) =>
withCagr(
buildBaseEntry(key, i),
isLumpSum ? returns.cagr[key] : dca.periodCagr[key],
isLumpSum ? returns.cagr[key].ratio : dca.period.cagr[key].ratio,
);
/** @param {BaseEntryItem} entry */

View File

@@ -6,7 +6,6 @@ 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 { createPriceRatioCharts } from "./shared.js";
import { periodIdToName } from "./utils.js";
/**
@@ -26,7 +25,7 @@ import { periodIdToName } from "./utils.js";
* @typedef {Object} MaPeriod
* @property {string} id
* @property {Color} color
* @property {ActivePriceRatioPattern} ratio
* @property {Brk.BpsCentsRatioSatsUsdPattern} ratio
*/
const commonMaIds = /** @type {const} */ ([
@@ -49,13 +48,26 @@ function createMaSubSection(label, averages) {
/** @param {MaPeriod} a */
const toFolder = (a) => ({
name: periodIdToName(a.id, true),
tree: createPriceRatioCharts({
context: `${periodIdToName(a.id, true)} ${label}`,
legend: "average",
pricePattern: a.ratio.price,
ratio: a.ratio,
color: a.color,
}),
tree: [
{
name: "Price",
title: `${periodIdToName(a.id, true)} ${label}`,
top: [price({ metric: a.ratio, name: "average", color: a.color })],
},
{
name: "Ratio",
title: `${periodIdToName(a.id, true)} ${label} Ratio`,
top: [price({ metric: a.ratio, name: "average", color: a.color })],
bottom: [
baseline({
metric: a.ratio.ratio,
name: "Ratio",
color: a.color,
unit: Unit.ratio,
}),
],
},
],
});
return {
@@ -65,7 +77,7 @@ function createMaSubSection(label, averages) {
name: "Compare",
title: `Price ${label}s`,
top: averages.map((a) =>
price({ metric: a.ratio.price, name: a.id, color: a.color }),
price({ metric: a.ratio, name: a.id, color: a.color }),
),
},
...common.map(toFolder),
@@ -184,83 +196,83 @@ function historicalSubSection(name, periods) {
* @returns {PartialOptionsGroup}
*/
export function createMarketSection() {
const { market, supply, distribution, prices } = brk.metrics;
const { market, supply, cohorts, prices, indicators } = brk.metrics;
const {
movingAverage: ma,
ath,
returns,
volatility,
range,
indicators,
technical,
lookback,
dca,
} = market;
const shortPeriodsBase = [
{ id: "24h", returns: returns.priceReturns._24h, lookback: lookback._24h },
{ id: "1w", returns: returns.priceReturns._1w, lookback: lookback._1w },
{ id: "1m", returns: returns.priceReturns._1m, lookback: lookback._1m },
{ id: "24h", returns: returns.periods._24h.ratio, lookback: lookback._24h },
{ id: "1w", returns: returns.periods._1w.ratio, lookback: lookback._1w },
{ id: "1m", returns: returns.periods._1m.ratio, lookback: lookback._1m },
{
id: "3m",
returns: returns.priceReturns._3m,
returns: returns.periods._3m.ratio,
lookback: lookback._3m,
defaultActive: false,
},
{
id: "6m",
returns: returns.priceReturns._6m,
returns: returns.periods._6m.ratio,
lookback: lookback._6m,
defaultActive: false,
},
{ id: "1y", returns: returns.priceReturns._1y, lookback: lookback._1y },
{ id: "1y", returns: returns.periods._1y.ratio, lookback: lookback._1y },
];
const longPeriodsBase = [
{
id: "2y",
returns: returns.priceReturns._2y,
cagr: returns.cagr._2y,
returns: returns.periods._2y.ratio,
cagr: returns.cagr._2y.ratio,
lookback: lookback._2y,
defaultActive: false,
},
{
id: "3y",
returns: returns.priceReturns._3y,
cagr: returns.cagr._3y,
returns: returns.periods._3y.ratio,
cagr: returns.cagr._3y.ratio,
lookback: lookback._3y,
defaultActive: false,
},
{
id: "4y",
returns: returns.priceReturns._4y,
cagr: returns.cagr._4y,
returns: returns.periods._4y.ratio,
cagr: returns.cagr._4y.ratio,
lookback: lookback._4y,
},
{
id: "5y",
returns: returns.priceReturns._5y,
cagr: returns.cagr._5y,
returns: returns.periods._5y.ratio,
cagr: returns.cagr._5y.ratio,
lookback: lookback._5y,
defaultActive: false,
},
{
id: "6y",
returns: returns.priceReturns._6y,
cagr: returns.cagr._6y,
returns: returns.periods._6y.ratio,
cagr: returns.cagr._6y.ratio,
lookback: lookback._6y,
defaultActive: false,
},
{
id: "8y",
returns: returns.priceReturns._8y,
cagr: returns.cagr._8y,
returns: returns.periods._8y.ratio,
cagr: returns.cagr._8y.ratio,
lookback: lookback._8y,
defaultActive: false,
},
{
id: "10y",
returns: returns.priceReturns._10y,
cagr: returns.cagr._10y,
returns: returns.periods._10y.ratio,
cagr: returns.cagr._10y.ratio,
lookback: lookback._10y,
defaultActive: false,
},
@@ -282,42 +294,42 @@ export function createMarketSection() {
/** @type {MaPeriod[]} */
const sma = [
{ id: "1w", ratio: ma.price1wSma },
{ id: "8d", ratio: ma.price8dSma },
{ id: "13d", ratio: ma.price13dSma },
{ id: "21d", ratio: ma.price21dSma },
{ id: "1m", ratio: ma.price1mSma },
{ id: "34d", ratio: ma.price34dSma },
{ id: "55d", ratio: ma.price55dSma },
{ id: "89d", ratio: ma.price89dSma },
{ id: "111d", ratio: ma.price111dSma },
{ id: "144d", ratio: ma.price144dSma },
{ id: "200d", ratio: ma.price200dSma },
{ id: "350d", ratio: ma.price350dSma },
{ id: "1y", ratio: ma.price1ySma },
{ id: "2y", ratio: ma.price2ySma },
{ id: "200w", ratio: ma.price200wSma },
{ id: "4y", ratio: ma.price4ySma },
{ id: "1w", ratio: ma.sma._1w },
{ id: "8d", ratio: ma.sma._8d },
{ id: "13d", ratio: ma.sma._13d },
{ id: "21d", ratio: ma.sma._21d },
{ id: "1m", ratio: ma.sma._1m },
{ id: "34d", ratio: ma.sma._34d },
{ id: "55d", ratio: ma.sma._55d },
{ id: "89d", ratio: ma.sma._89d },
{ id: "111d", ratio: ma.sma._111d },
{ id: "144d", ratio: ma.sma._144d },
{ id: "200d", ratio: ma.sma._200d },
{ id: "350d", ratio: ma.sma._350d },
{ id: "1y", ratio: ma.sma._1y },
{ id: "2y", ratio: ma.sma._2y },
{ id: "200w", ratio: ma.sma._200w },
{ id: "4y", ratio: ma.sma._4y },
].map((p, i, arr) => ({ ...p, color: colors.at(i, arr.length) }));
/** @type {MaPeriod[]} */
const ema = [
{ id: "1w", ratio: ma.price1wEma },
{ id: "8d", ratio: ma.price8dEma },
{ id: "12d", ratio: ma.price12dEma },
{ id: "13d", ratio: ma.price13dEma },
{ id: "21d", ratio: ma.price21dEma },
{ id: "26d", ratio: ma.price26dEma },
{ id: "1m", ratio: ma.price1mEma },
{ id: "34d", ratio: ma.price34dEma },
{ id: "55d", ratio: ma.price55dEma },
{ id: "89d", ratio: ma.price89dEma },
{ id: "144d", ratio: ma.price144dEma },
{ id: "200d", ratio: ma.price200dEma },
{ id: "1y", ratio: ma.price1yEma },
{ id: "2y", ratio: ma.price2yEma },
{ id: "200w", ratio: ma.price200wEma },
{ id: "4y", ratio: ma.price4yEma },
{ id: "1w", ratio: ma.ema._1w },
{ id: "8d", ratio: ma.ema._8d },
{ id: "12d", ratio: ma.ema._12d },
{ id: "13d", ratio: ma.ema._13d },
{ id: "21d", ratio: ma.ema._21d },
{ id: "26d", ratio: ma.ema._26d },
{ id: "1m", ratio: ma.ema._1m },
{ id: "34d", ratio: ma.ema._34d },
{ id: "55d", ratio: ma.ema._55d },
{ id: "89d", ratio: ma.ema._89d },
{ id: "144d", ratio: ma.ema._144d },
{ id: "200d", ratio: ma.ema._200d },
{ id: "1y", ratio: ma.ema._1y },
{ id: "2y", ratio: ma.ema._2y },
{ id: "200w", ratio: ma.ema._200w },
{ id: "4y", ratio: ma.ema._4y },
].map((p, i, arr) => ({ ...p, color: colors.at(i, arr.length) }));
// SMA vs EMA comparison periods (common periods only)
@@ -325,38 +337,38 @@ export function createMarketSection() {
{
id: "1w",
name: "1 Week",
sma: ma.price1wSma,
ema: ma.price1wEma,
sma: ma.sma._1w,
ema: ma.ema._1w,
},
{
id: "1m",
name: "1 Month",
sma: ma.price1mSma,
ema: ma.price1mEma,
sma: ma.sma._1m,
ema: ma.ema._1m,
},
{
id: "200d",
name: "200 Day",
sma: ma.price200dSma,
ema: ma.price200dEma,
sma: ma.sma._200d,
ema: ma.ema._200d,
},
{
id: "1y",
name: "1 Year",
sma: ma.price1ySma,
ema: ma.price1yEma,
sma: ma.sma._1y,
ema: ma.ema._1y,
},
{
id: "200w",
name: "200 Week",
sma: ma.price200wSma,
ema: ma.price200wEma,
sma: ma.sma._200w,
ema: ma.ema._200w,
},
{
id: "4y",
name: "4 Year",
sma: ma.price4ySma,
ema: ma.price4yEma,
sma: ma.sma._4y,
ema: ma.ema._4y,
},
].map((p, i, arr) => ({ ...p, color: colors.at(i, arr.length) }));
@@ -370,7 +382,7 @@ export function createMarketSection() {
title: "Sats per Dollar",
bottom: [
line({
metric: prices.price.sats,
metric: prices.spot.sats,
name: "Sats/$",
unit: Unit.sats,
}),
@@ -385,7 +397,7 @@ export function createMarketSection() {
title: "Market Capitalization",
bottom: [
line({
metric: supply.marketCap,
metric: supply.marketCap.usd,
name: "Market Cap",
unit: Unit.usd,
}),
@@ -396,7 +408,7 @@ export function createMarketSection() {
title: "Realized Capitalization",
bottom: [
line({
metric: distribution.utxoCohorts.all.realized.realizedCap,
metric: cohorts.utxo.all.realized.cap.usd,
name: "Realized Cap",
color: colors.realized,
unit: Unit.usd,
@@ -408,20 +420,14 @@ export function createMarketSection() {
title: "Capitalization Growth Rate",
bottom: [
line({
metric: supply.marketCapGrowthRate,
name: "Market Cap",
metric: supply.marketCap.delta.rate._24h.percent,
name: "Market Cap (24h)",
color: colors.bitcoin,
unit: Unit.percentage,
}),
line({
metric: supply.realizedCapGrowthRate,
name: "Realized Cap",
color: colors.usd,
unit: Unit.percentage,
}),
baseline({
metric: supply.capGrowthRateDiff,
name: "Difference",
metric: supply.marketMinusRealizedCapGrowthRate._24h,
name: "Market - Realized",
unit: Unit.percentage,
}),
],
@@ -435,10 +441,10 @@ export function createMarketSection() {
{
name: "Drawdown",
title: "ATH Drawdown",
top: [price({ metric: ath.priceAth, name: "ATH" })],
top: [price({ metric: ath.high, name: "ATH" })],
bottom: [
line({
metric: ath.priceDrawdown,
metric: ath.drawdown.percent,
name: "Drawdown",
color: colors.loss,
unit: Unit.percentage,
@@ -448,26 +454,26 @@ export function createMarketSection() {
{
name: "Time Since",
title: "Time Since ATH",
top: [price({ metric: ath.priceAth, name: "ATH" })],
top: [price({ metric: ath.high, name: "ATH" })],
bottom: [
line({
metric: ath.daysSincePriceAth,
metric: ath.daysSince,
name: "Since",
unit: Unit.days,
}),
line({
metric: ath.yearsSincePriceAth,
metric: ath.yearsSince,
name: "Since",
unit: Unit.years,
}),
line({
metric: ath.maxDaysBetweenPriceAths,
metric: ath.maxDaysBetween,
name: "Max",
color: colors.loss,
unit: Unit.days,
}),
line({
metric: ath.maxYearsBetweenPriceAths,
metric: ath.maxYearsBetween,
name: "Max",
color: colors.loss,
unit: Unit.years,
@@ -502,22 +508,22 @@ export function createMarketSection() {
name: "Volatility",
tree: [
volatilityChart("Index", "Volatility Index", Unit.percentage, {
_1w: volatility.price1wVolatility,
_1m: volatility.price1mVolatility,
_1y: volatility.price1yVolatility,
_1w: volatility._1w,
_1m: volatility._1m,
_1y: volatility._1y,
}),
{
name: "True Range",
title: "True Range",
bottom: [
line({
metric: range.priceTrueRange,
metric: range.trueRange,
name: "Daily",
color: colors.time._24h,
unit: Unit.usd,
}),
line({
metric: range.priceTrueRange2wSum,
metric: range.trueRangeSum2w,
name: "2w Sum",
color: colors.time._1w,
unit: Unit.usd,
@@ -530,7 +536,7 @@ export function createMarketSection() {
title: "Choppiness Index",
bottom: [
line({
metric: range.price2wChoppinessIndex,
metric: range.choppinessIndex2w.percent,
name: "2w",
color: colors.indicator.main,
unit: Unit.index,
@@ -552,12 +558,12 @@ export function createMarketSection() {
title: "SMA vs EMA Comparison",
top: smaVsEma.flatMap((p) => [
price({
metric: p.sma.price,
metric: p.sma,
name: `${p.id} SMA`,
color: p.color,
}),
price({
metric: p.ema.price,
metric: p.ema,
name: `${p.id} EMA`,
color: p.color,
style: 1,
@@ -568,9 +574,9 @@ export function createMarketSection() {
name: p.name,
title: `${p.name} SMA vs EMA`,
top: [
price({ metric: p.sma.price, name: "SMA", color: p.color }),
price({ metric: p.sma, name: "SMA", color: p.color }),
price({
metric: p.ema.price,
metric: p.ema,
name: "EMA",
color: p.color,
style: 1,
@@ -593,26 +599,26 @@ export function createMarketSection() {
{
id: "1w",
name: "1 Week",
min: range.price1wMin,
max: range.price1wMax,
min: range.min._1w,
max: range.max._1w,
},
{
id: "2w",
name: "2 Week",
min: range.price2wMin,
max: range.price2wMax,
min: range.min._2w,
max: range.max._2w,
},
{
id: "1m",
name: "1 Month",
min: range.price1mMin,
max: range.price1mMax,
min: range.min._1m,
max: range.max._1m,
},
{
id: "1y",
name: "1 Year",
min: range.price1yMin,
max: range.price1yMax,
min: range.min._1y,
max: range.max._1y,
},
].map((p) => ({
name: p.id,
@@ -638,17 +644,17 @@ export function createMarketSection() {
title: "Mayer Multiple",
top: [
price({
metric: ma.price200dSma.price,
metric: ma.sma._200d,
name: "200d SMA",
color: colors.indicator.main,
}),
price({
metric: ma.price200dSmaX24,
metric: ma.sma._200d.x24,
name: "200d SMA x2.4",
color: colors.indicator.upper,
}),
price({
metric: ma.price200dSmaX08,
metric: ma.sma._200d.x08,
name: "200d SMA x0.8",
color: colors.indicator.lower,
}),
@@ -668,25 +674,25 @@ export function createMarketSection() {
title: "RSI Comparison",
bottom: [
line({
metric: indicators.rsi._1d.rsi,
metric: technical.rsi._24h.rsi.percent,
name: "1d",
color: colors.time._24h,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1w.rsi,
metric: technical.rsi._1w.rsi.percent,
name: "1w",
color: colors.time._1w,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1m.rsi,
metric: technical.rsi._1m.rsi.percent,
name: "1m",
color: colors.time._1m,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1y.rsi,
metric: technical.rsi._1y.rsi.percent,
name: "1y",
color: colors.time._1y,
unit: Unit.index,
@@ -700,20 +706,20 @@ export function createMarketSection() {
title: "RSI (1d)",
bottom: [
line({
metric: indicators.rsi._1d.rsi,
metric: technical.rsi._24h.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1d.rsiMax,
metric: technical.rsi._24h.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1d.rsiMin,
metric: technical.rsi._24h.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
@@ -733,20 +739,20 @@ export function createMarketSection() {
title: "RSI (1w)",
bottom: [
line({
metric: indicators.rsi._1w.rsi,
metric: technical.rsi._1w.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1w.rsiMax,
metric: technical.rsi._1w.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1w.rsiMin,
metric: technical.rsi._1w.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
@@ -766,20 +772,20 @@ export function createMarketSection() {
title: "RSI (1m)",
bottom: [
line({
metric: indicators.rsi._1m.rsi,
metric: technical.rsi._1m.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1m.rsiMax,
metric: technical.rsi._1m.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1m.rsiMin,
metric: technical.rsi._1m.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
@@ -799,20 +805,20 @@ export function createMarketSection() {
title: "RSI (1y)",
bottom: [
line({
metric: indicators.rsi._1y.rsi,
metric: technical.rsi._1y.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1y.rsiMax,
metric: technical.rsi._1y.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1y.rsiMin,
metric: technical.rsi._1y.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
@@ -837,25 +843,25 @@ export function createMarketSection() {
title: "Stochastic RSI Comparison",
bottom: [
line({
metric: indicators.rsi._1d.stochRsiK,
metric: technical.rsi._24h.stochRsiK.percent,
name: "1d K",
color: colors.time._24h,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1w.stochRsiK,
metric: technical.rsi._1w.stochRsiK.percent,
name: "1w K",
color: colors.time._1w,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1m.stochRsiK,
metric: technical.rsi._1m.stochRsiK.percent,
name: "1m K",
color: colors.time._1m,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1y.stochRsiK,
metric: technical.rsi._1y.stochRsiK.percent,
name: "1y K",
color: colors.time._1y,
unit: Unit.index,
@@ -868,13 +874,13 @@ export function createMarketSection() {
title: "Stochastic RSI (1d)",
bottom: [
line({
metric: indicators.rsi._1d.stochRsiK,
metric: technical.rsi._24h.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1d.stochRsiD,
metric: technical.rsi._24h.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
@@ -887,13 +893,13 @@ export function createMarketSection() {
title: "Stochastic RSI (1w)",
bottom: [
line({
metric: indicators.rsi._1w.stochRsiK,
metric: technical.rsi._1w.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1w.stochRsiD,
metric: technical.rsi._1w.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
@@ -906,13 +912,13 @@ export function createMarketSection() {
title: "Stochastic RSI (1m)",
bottom: [
line({
metric: indicators.rsi._1m.stochRsiK,
metric: technical.rsi._1m.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1m.stochRsiD,
metric: technical.rsi._1m.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
@@ -925,13 +931,13 @@ export function createMarketSection() {
title: "Stochastic RSI (1y)",
bottom: [
line({
metric: indicators.rsi._1y.stochRsiK,
metric: technical.rsi._1y.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: indicators.rsi._1y.stochRsiD,
metric: technical.rsi._1y.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
@@ -946,13 +952,13 @@ export function createMarketSection() {
title: "Stochastic Oscillator",
bottom: [
line({
metric: indicators.stochK,
metric: technical.stochK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: indicators.stochD,
metric: technical.stochD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
@@ -968,25 +974,25 @@ export function createMarketSection() {
title: "MACD Comparison",
bottom: [
line({
metric: indicators.macd._1d.line,
metric: technical.macd._24h.line,
name: "1d",
color: colors.time._24h,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1w.line,
metric: technical.macd._1w.line,
name: "1w",
color: colors.time._1w,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1m.line,
metric: technical.macd._1m.line,
name: "1m",
color: colors.time._1m,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1y.line,
metric: technical.macd._1y.line,
name: "1y",
color: colors.time._1y,
unit: Unit.usd,
@@ -998,19 +1004,19 @@ export function createMarketSection() {
title: "MACD (1d)",
bottom: [
line({
metric: indicators.macd._1d.line,
metric: technical.macd._24h.line,
name: "MACD",
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1d.signal,
metric: technical.macd._24h.signal,
name: "Signal",
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
metric: indicators.macd._1d.histogram,
metric: technical.macd._24h.histogram,
name: "Histogram",
unit: Unit.usd,
}),
@@ -1021,19 +1027,19 @@ export function createMarketSection() {
title: "MACD (1w)",
bottom: [
line({
metric: indicators.macd._1w.line,
metric: technical.macd._1w.line,
name: "MACD",
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1w.signal,
metric: technical.macd._1w.signal,
name: "Signal",
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
metric: indicators.macd._1w.histogram,
metric: technical.macd._1w.histogram,
name: "Histogram",
unit: Unit.usd,
}),
@@ -1044,19 +1050,19 @@ export function createMarketSection() {
title: "MACD (1m)",
bottom: [
line({
metric: indicators.macd._1m.line,
metric: technical.macd._1m.line,
name: "MACD",
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1m.signal,
metric: technical.macd._1m.signal,
name: "Signal",
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
metric: indicators.macd._1m.histogram,
metric: technical.macd._1m.histogram,
name: "Histogram",
unit: Unit.usd,
}),
@@ -1067,19 +1073,19 @@ export function createMarketSection() {
title: "MACD (1y)",
bottom: [
line({
metric: indicators.macd._1y.line,
metric: technical.macd._1y.line,
name: "MACD",
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
metric: indicators.macd._1y.signal,
metric: technical.macd._1y.signal,
name: "Signal",
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
metric: indicators.macd._1y.histogram,
metric: technical.macd._1y.histogram,
name: "Histogram",
unit: Unit.usd,
}),
@@ -1115,7 +1121,7 @@ export function createMarketSection() {
title: "Dollar Cost Average Sats/Day",
bottom: [
line({
metric: dca.dcaSatsPerDay,
metric: dca.satsPerDay,
name: "Sats/Day",
unit: Unit.sats,
}),
@@ -1130,19 +1136,19 @@ export function createMarketSection() {
title: "Pi Cycle",
top: [
price({
metric: ma.price111dSma.price,
metric: ma.sma._111d,
name: "111d SMA",
color: colors.indicator.upper,
}),
price({
metric: ma.price350dSmaX2,
metric: ma.sma._350d.x2,
name: "350d SMA x2",
color: colors.indicator.lower,
}),
],
bottom: [
baseline({
metric: indicators.piCycle,
metric: technical.piCycle.ratio,
name: "Pi Cycle",
unit: Unit.ratio,
base: 1,
@@ -1154,7 +1160,7 @@ export function createMarketSection() {
title: "Puell Multiple",
bottom: [
line({
metric: indicators.puellMultiple,
metric: indicators.puellMultiple.ratio,
name: "Puell",
color: colors.usd,
unit: Unit.ratio,
@@ -1166,7 +1172,7 @@ export function createMarketSection() {
title: "NVT Ratio",
bottom: [
line({
metric: indicators.nvt,
metric: indicators.nvt.ratio,
name: "NVT",
color: colors.bitcoin,
unit: Unit.ratio,
@@ -1178,13 +1184,104 @@ export function createMarketSection() {
title: "Gini Coefficient",
bottom: [
line({
metric: indicators.gini,
metric: indicators.gini.percent,
name: "Gini",
color: colors.loss,
unit: Unit.ratio,
}),
],
},
{
name: "RHODL Ratio",
title: "RHODL Ratio",
bottom: [
line({
metric: indicators.rhodlRatio.ratio,
name: "RHODL",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Thermocap Multiple",
title: "Thermocap Multiple",
bottom: [
line({
metric: indicators.thermocapMultiple.ratio,
name: "Thermocap",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Stock-to-Flow",
title: "Stock-to-Flow",
bottom: [
line({
metric: indicators.stockToFlow,
name: "S2F",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "Dormancy",
title: "Dormancy",
bottom: [
line({
metric: indicators.dormancy.supplyAdjusted,
name: "Supply Adjusted",
color: colors.bitcoin,
unit: Unit.ratio,
}),
line({
metric: indicators.dormancy.flow,
name: "Flow",
color: colors.usd,
unit: Unit.ratio,
defaultActive: false,
}),
],
},
{
name: "Seller Exhaustion",
title: "Seller Exhaustion Constant",
bottom: [
line({
metric: indicators.sellerExhaustionConstant,
name: "SEC",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "CDD Supply Adjusted",
title: "Coindays Destroyed (Supply Adjusted)",
bottom: [
line({
metric: indicators.coindaysDestroyedSupplyAdjusted,
name: "CDD SA",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
{
name: "CYD Supply Adjusted",
title: "Coinyears Destroyed (Supply Adjusted)",
bottom: [
line({
metric: indicators.coinyearsDestroyedSupplyAdjusted,
name: "CYD SA",
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
},
],
},
],

View File

@@ -56,20 +56,27 @@ export function createMiningSection() {
const { blocks, pools, mining } = brk.metrics;
// Pre-compute pool entries with resolved names
const poolData = entries(pools.vecs).map(([id, pool]) => ({
const majorPoolData = entries(pools.major).map(([id, pool]) => ({
id,
name: brk.POOL_ID_TO_POOL_NAME[id],
pool,
}));
const minorPoolData = entries(pools.minor).map(([id, pool]) => ({
id,
name: brk.POOL_ID_TO_POOL_NAME[id],
pool,
}));
// Filtered pool groups for comparisons
const majorPools = poolData.filter((p) => includes(MAJOR_POOL_IDS, p.id));
const antpoolFriends = poolData.filter((p) =>
// Filtered pool groups for comparisons (major pools only have windowed dominance)
const featuredPools = majorPoolData.filter((p) =>
includes(MAJOR_POOL_IDS, p.id),
);
const antpoolFriends = majorPoolData.filter((p) =>
includes(ANTPOOL_AND_FRIENDS_IDS, p.id),
);
// Build individual pool trees
const poolsTree = poolData.map(({ name, pool }) => ({
const majorPoolsTree = majorPoolData.map(({ name, pool }) => ({
name,
tree: [
{
@@ -77,34 +84,34 @@ export function createMiningSection() {
title: `Dominance: ${name}`,
bottom: [
dots({
metric: pool.dominance24h,
metric: pool.dominance._24h.percent,
name: "24h",
color: colors.time._24h,
unit: Unit.percentage,
defaultActive: false,
}),
line({
metric: pool.dominance1w,
metric: pool.dominance._1w.percent,
name: "1w",
color: colors.time._1w,
unit: Unit.percentage,
defaultActive: false,
}),
line({
metric: pool.dominance1m,
metric: pool.dominance._1m.percent,
name: "1m",
color: colors.time._1m,
unit: Unit.percentage,
}),
line({
metric: pool.dominance1y,
metric: pool.dominance._1y.percent,
name: "1y",
color: colors.time._1y,
unit: Unit.percentage,
defaultActive: false,
}),
line({
metric: pool.dominance,
metric: pool.dominance.percent,
name: "All Time",
color: colors.time.all,
unit: Unit.percentage,
@@ -120,7 +127,7 @@ export function createMiningSection() {
title: `Blocks Mined: ${name}`,
bottom: [
line({
metric: pool.blocksMined.height,
metric: pool.blocksMined.base,
name: "base",
unit: Unit.count,
}),
@@ -146,41 +153,69 @@ export function createMiningSection() {
{
name: "Sum",
title: `Rewards: ${name}`,
bottom: revenueBtcSatsUsd({
coinbase: pool.coinbase,
subsidy: pool.subsidy,
fee: pool.fee,
bottom: satsBtcUsdFrom({
source: pool.rewards,
key: "base",
name: "sum",
}),
},
{
name: "Cumulative",
title: `Rewards: ${name} (Total)`,
bottom: revenueBtcSatsUsd({
coinbase: pool.coinbase,
subsidy: pool.subsidy,
fee: pool.fee,
bottom: satsBtcUsdFrom({
source: pool.rewards,
key: "cumulative",
name: "all-time",
}),
},
],
},
],
}));
const minorPoolsTree = minorPoolData.map(({ name, pool }) => ({
name,
tree: [
{
name: "Since Last Block",
title: `Since Last Block: ${name}`,
name: "Dominance",
title: `Dominance: ${name}`,
bottom: [
line({
metric: pool.blocksSinceBlock,
name: "Elapsed",
unit: Unit.blocks,
}),
line({
metric: pool.daysSinceBlock,
name: "Elapsed",
unit: Unit.days,
metric: pool.dominance.percent,
name: "All Time",
color: colors.time.all,
unit: Unit.percentage,
}),
],
},
{
name: "Blocks Mined",
tree: [
{
name: "Base",
title: `Blocks Mined: ${name}`,
bottom: [
line({
metric: 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({
metric: pool.blocksMined.cumulative,
name: "all-time",
unit: Unit.count,
}),
],
},
],
},
],
}));
@@ -196,33 +231,33 @@ export function createMiningSection() {
title: "Network Hashrate",
bottom: [
dots({
metric: mining.hashrate.hashRate,
metric: mining.hashrate.rate.base,
name: "Hashrate",
unit: Unit.hashRate,
}),
line({
metric: mining.hashrate.hashRate1wSma,
metric: mining.hashrate.rate.sma._1w,
name: "1w SMA",
color: colors.time._1w,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: mining.hashrate.hashRate1mSma,
metric: mining.hashrate.rate.sma._1m,
name: "1m SMA",
color: colors.time._1m,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: mining.hashrate.hashRate2mSma,
metric: mining.hashrate.rate.sma._2m,
name: "2m SMA",
color: colors.indicator.main,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: mining.hashrate.hashRate1ySma,
metric: mining.hashrate.rate.sma._1y,
name: "1y SMA",
color: colors.time._1y,
unit: Unit.hashRate,
@@ -235,7 +270,7 @@ export function createMiningSection() {
unit: Unit.hashRate,
}),
line({
metric: mining.hashrate.hashRateAth,
metric: mining.hashrate.rate.ath,
name: "ATH",
color: colors.loss,
unit: Unit.hashRate,
@@ -248,13 +283,13 @@ export function createMiningSection() {
title: "Network Hashrate ATH",
bottom: [
line({
metric: mining.hashrate.hashRateAth,
metric: mining.hashrate.rate.ath,
name: "ATH",
color: colors.loss,
unit: Unit.hashRate,
}),
dots({
metric: mining.hashrate.hashRate,
metric: mining.hashrate.rate.base,
name: "Hashrate",
color: colors.bitcoin,
unit: Unit.hashRate,
@@ -266,7 +301,7 @@ export function createMiningSection() {
title: "Network Hashrate Drawdown",
bottom: [
line({
metric: mining.hashrate.hashRateDrawdown,
metric: mining.hashrate.rate.drawdown.percent,
name: "Drawdown",
unit: Unit.percentage,
color: colors.loss,
@@ -307,7 +342,7 @@ export function createMiningSection() {
title: "Difficulty Adjustment",
bottom: [
baseline({
metric: blocks.difficulty.adjustment,
metric: blocks.difficulty.adjustment.percent,
name: "Change",
unit: Unit.percentage,
}),
@@ -318,12 +353,12 @@ export function createMiningSection() {
title: "Next Difficulty Adjustment",
bottom: [
line({
metric: blocks.difficulty.blocksBeforeNextAdjustment,
metric: blocks.difficulty.blocksBeforeNext,
name: "Remaining",
unit: Unit.blocks,
}),
line({
metric: blocks.difficulty.daysBeforeNextAdjustment,
metric: blocks.difficulty.daysBeforeNext,
name: "Remaining",
unit: Unit.days,
}),
@@ -381,22 +416,22 @@ export function createMiningSection() {
title: "Coinbase Rolling Sum",
bottom: [
...satsBtcUsd({
pattern: mining.rewards.coinbase._24h.sum,
pattern: mining.rewards.coinbase.sum._24h,
name: "24h",
color: colors.time._24h,
}),
...satsBtcUsd({
pattern: mining.rewards.coinbase._7d.sum,
pattern: mining.rewards.coinbase.sum._1w,
name: "7d",
color: colors.time._1w,
}),
...satsBtcUsd({
pattern: mining.rewards.coinbase._30d.sum,
pattern: mining.rewards.coinbase.sum._1m,
name: "30d",
color: colors.time._1m,
}),
...satsBtcUsd({
pattern: mining.rewards.coinbase._1y.sum,
pattern: mining.rewards.coinbase.sum._1y,
name: "1y",
color: colors.time._1y,
}),
@@ -406,7 +441,7 @@ export function createMiningSection() {
name: "24h",
title: "Coinbase 24h Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.coinbase._24h.sum,
pattern: mining.rewards.coinbase.sum._24h,
name: "24h",
color: colors.time._24h,
}),
@@ -415,7 +450,7 @@ export function createMiningSection() {
name: "7d",
title: "Coinbase 7d Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.coinbase._7d.sum,
pattern: mining.rewards.coinbase.sum._1w,
name: "7d",
color: colors.time._1w,
}),
@@ -424,7 +459,7 @@ export function createMiningSection() {
name: "30d",
title: "Coinbase 30d Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.coinbase._30d.sum,
pattern: mining.rewards.coinbase.sum._1m,
name: "30d",
color: colors.time._1m,
}),
@@ -433,18 +468,13 @@ export function createMiningSection() {
name: "1y",
title: "Coinbase 1y Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.coinbase._1y.sum,
pattern: mining.rewards.coinbase.sum._1y,
name: "1y",
color: colors.time._1y,
}),
},
],
},
{
name: "Distribution",
title: "Coinbase Rewards per Block Distribution",
bottom: distributionBtcSatsUsd(mining.rewards.coinbase._24h),
},
{
name: "Cumulative",
title: "Coinbase Rewards (Total)",
@@ -469,7 +499,7 @@ export function createMiningSection() {
name: "sum",
}),
line({
metric: mining.rewards.subsidyUsd1ySma,
metric: mining.rewards.subsidy.sma1y.usd,
name: "1y SMA",
color: colors.time._1y,
unit: Unit.usd,
@@ -477,78 +507,6 @@ export function createMiningSection() {
}),
],
},
{
name: "Rolling",
tree: [
{
name: "Compare",
title: "Subsidy Rolling Sum",
bottom: [
...satsBtcUsd({
pattern: mining.rewards.subsidy._24h.sum,
name: "24h",
color: colors.time._24h,
}),
...satsBtcUsd({
pattern: mining.rewards.subsidy._7d.sum,
name: "7d",
color: colors.time._1w,
}),
...satsBtcUsd({
pattern: mining.rewards.subsidy._30d.sum,
name: "30d",
color: colors.time._1m,
}),
...satsBtcUsd({
pattern: mining.rewards.subsidy._1y.sum,
name: "1y",
color: colors.time._1y,
}),
],
},
{
name: "24h",
title: "Subsidy 24h Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.subsidy._24h.sum,
name: "24h",
color: colors.time._24h,
}),
},
{
name: "7d",
title: "Subsidy 7d Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.subsidy._7d.sum,
name: "7d",
color: colors.time._1w,
}),
},
{
name: "30d",
title: "Subsidy 30d Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.subsidy._30d.sum,
name: "30d",
color: colors.time._1m,
}),
},
{
name: "1y",
title: "Subsidy 1y Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.subsidy._1y.sum,
name: "1y",
color: colors.time._1y,
}),
},
],
},
{
name: "Distribution",
title: "Block Subsidy Distribution",
bottom: distributionBtcSatsUsd(mining.rewards.subsidy._24h),
},
{
name: "Cumulative",
title: "Block Subsidy (Total)",
@@ -580,22 +538,22 @@ export function createMiningSection() {
title: "Fee Rolling Sum",
bottom: [
...satsBtcUsd({
pattern: mining.rewards.fees._24h.sum,
pattern: mining.rewards.fees.sum._24h,
name: "24h",
color: colors.time._24h,
}),
...satsBtcUsd({
pattern: mining.rewards.fees._7d.sum,
pattern: mining.rewards.fees.sum._1w,
name: "7d",
color: colors.time._1w,
}),
...satsBtcUsd({
pattern: mining.rewards.fees._30d.sum,
pattern: mining.rewards.fees.sum._1m,
name: "30d",
color: colors.time._1m,
}),
...satsBtcUsd({
pattern: mining.rewards.fees._1y.sum,
pattern: mining.rewards.fees.sum._1y,
name: "1y",
color: colors.time._1y,
}),
@@ -605,7 +563,7 @@ export function createMiningSection() {
name: "24h",
title: "Fee 24h Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.fees._24h.sum,
pattern: mining.rewards.fees.sum._24h,
name: "24h",
color: colors.time._24h,
}),
@@ -614,7 +572,7 @@ export function createMiningSection() {
name: "7d",
title: "Fee 7d Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.fees._7d.sum,
pattern: mining.rewards.fees.sum._1w,
name: "7d",
color: colors.time._1w,
}),
@@ -623,7 +581,7 @@ export function createMiningSection() {
name: "30d",
title: "Fee 30d Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.fees._30d.sum,
pattern: mining.rewards.fees.sum._1m,
name: "30d",
color: colors.time._1m,
}),
@@ -632,7 +590,7 @@ export function createMiningSection() {
name: "1y",
title: "Fee 1y Rolling Sum",
bottom: satsBtcUsd({
pattern: mining.rewards.fees._1y.sum,
pattern: mining.rewards.fees.sum._1y,
name: "1y",
color: colors.time._1y,
}),
@@ -666,31 +624,31 @@ export function createMiningSection() {
title: "Subsidy Dominance",
bottom: [
line({
metric: mining.rewards.subsidyDominance,
metric: mining.rewards.subsidy.dominance.percent,
name: "All-time",
color: colors.time.all,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.subsidyDominance24h,
metric: mining.rewards.subsidy.dominance._24h.percent,
name: "24h",
color: colors.time._24h,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.subsidyDominance7d,
metric: mining.rewards.subsidy.dominance._1w.percent,
name: "7d",
color: colors.time._1w,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.subsidyDominance30d,
metric: mining.rewards.subsidy.dominance._1m.percent,
name: "30d",
color: colors.time._1m,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.subsidyDominance1y,
metric: mining.rewards.subsidy.dominance._1y.percent,
name: "1y",
color: colors.time._1y,
unit: Unit.percentage,
@@ -702,31 +660,31 @@ export function createMiningSection() {
title: "Fee Dominance",
bottom: [
line({
metric: mining.rewards.feeDominance,
metric: mining.rewards.fees.dominance.percent,
name: "All-time",
color: colors.time.all,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance24h,
metric: mining.rewards.fees.dominance._24h.percent,
name: "24h",
color: colors.time._24h,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance7d,
metric: mining.rewards.fees.dominance._1w.percent,
name: "7d",
color: colors.time._1w,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance30d,
metric: mining.rewards.fees.dominance._1m.percent,
name: "30d",
color: colors.time._1m,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance1y,
metric: mining.rewards.fees.dominance._1y.percent,
name: "1y",
color: colors.time._1y,
unit: Unit.percentage,
@@ -740,13 +698,13 @@ export function createMiningSection() {
title: "Revenue Dominance (All-time)",
bottom: [
line({
metric: mining.rewards.subsidyDominance,
metric: mining.rewards.subsidy.dominance.percent,
name: "Subsidy",
color: colors.mining.subsidy,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance,
metric: mining.rewards.fees.dominance.percent,
name: "Fees",
color: colors.mining.fee,
unit: Unit.percentage,
@@ -758,13 +716,13 @@ export function createMiningSection() {
title: "Revenue Dominance (24h)",
bottom: [
line({
metric: mining.rewards.subsidyDominance24h,
metric: mining.rewards.subsidy.dominance._24h.percent,
name: "Subsidy",
color: colors.mining.subsidy,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance24h,
metric: mining.rewards.fees.dominance._24h.percent,
name: "Fees",
color: colors.mining.fee,
unit: Unit.percentage,
@@ -776,13 +734,13 @@ export function createMiningSection() {
title: "Revenue Dominance (7d)",
bottom: [
line({
metric: mining.rewards.subsidyDominance7d,
metric: mining.rewards.subsidy.dominance._1w.percent,
name: "Subsidy",
color: colors.mining.subsidy,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance7d,
metric: mining.rewards.fees.dominance._1w.percent,
name: "Fees",
color: colors.mining.fee,
unit: Unit.percentage,
@@ -794,13 +752,13 @@ export function createMiningSection() {
title: "Revenue Dominance (30d)",
bottom: [
line({
metric: mining.rewards.subsidyDominance30d,
metric: mining.rewards.subsidy.dominance._1m.percent,
name: "Subsidy",
color: colors.mining.subsidy,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance30d,
metric: mining.rewards.fees.dominance._1m.percent,
name: "Fees",
color: colors.mining.fee,
unit: Unit.percentage,
@@ -812,13 +770,13 @@ export function createMiningSection() {
title: "Revenue Dominance (1y)",
bottom: [
line({
metric: mining.rewards.subsidyDominance1y,
metric: mining.rewards.subsidy.dominance._1y.percent,
name: "Subsidy",
color: colors.mining.subsidy,
unit: Unit.percentage,
}),
line({
metric: mining.rewards.feeDominance1y,
metric: mining.rewards.fees.dominance._1y.percent,
name: "Fees",
color: colors.mining.fee,
unit: Unit.percentage,
@@ -834,7 +792,7 @@ export function createMiningSection() {
name: "Sum",
title: "Unclaimed Rewards",
bottom: satsBtcUsdFrom({
source: mining.rewards.unclaimedRewards,
source: mining.rewards.unclaimed,
key: "base",
name: "sum",
}),
@@ -843,7 +801,7 @@ export function createMiningSection() {
name: "Cumulative",
title: "Unclaimed Rewards (Total)",
bottom: satsBtcUsdFrom({
source: mining.rewards.unclaimedRewards,
source: mining.rewards.unclaimed,
key: "cumulative",
name: "all-time",
}),
@@ -862,25 +820,25 @@ export function createMiningSection() {
title: "Hash Price",
bottom: [
line({
metric: mining.hashrate.hashPriceThs,
metric: mining.hashrate.price.ths,
name: "TH/s",
color: colors.usd,
unit: Unit.usdPerThsPerDay,
}),
line({
metric: mining.hashrate.hashPricePhs,
metric: mining.hashrate.price.phs,
name: "PH/s",
color: colors.usd,
unit: Unit.usdPerPhsPerDay,
}),
dotted({
metric: mining.hashrate.hashPriceThsMin,
metric: mining.hashrate.price.thsMin,
name: "TH/s Min",
color: colors.stat.min,
unit: Unit.usdPerThsPerDay,
}),
dotted({
metric: mining.hashrate.hashPricePhsMin,
metric: mining.hashrate.price.phsMin,
name: "PH/s Min",
color: colors.stat.min,
unit: Unit.usdPerPhsPerDay,
@@ -892,25 +850,25 @@ export function createMiningSection() {
title: "Hash Value",
bottom: [
line({
metric: mining.hashrate.hashValueThs,
metric: mining.hashrate.value.ths,
name: "TH/s",
color: colors.bitcoin,
unit: Unit.satsPerThsPerDay,
}),
line({
metric: mining.hashrate.hashValuePhs,
metric: mining.hashrate.value.phs,
name: "PH/s",
color: colors.bitcoin,
unit: Unit.satsPerPhsPerDay,
}),
dotted({
metric: mining.hashrate.hashValueThsMin,
metric: mining.hashrate.value.thsMin,
name: "TH/s Min",
color: colors.stat.min,
unit: Unit.satsPerThsPerDay,
}),
dotted({
metric: mining.hashrate.hashValuePhsMin,
metric: mining.hashrate.value.phsMin,
name: "PH/s Min",
color: colors.stat.min,
unit: Unit.satsPerPhsPerDay,
@@ -922,13 +880,13 @@ export function createMiningSection() {
title: "Recovery",
bottom: [
line({
metric: mining.hashrate.hashPriceRebound,
metric: mining.hashrate.price.rebound.percent,
name: "Hash Price",
color: colors.usd,
unit: Unit.percentage,
}),
line({
metric: mining.hashrate.hashValueRebound,
metric: mining.hashrate.value.rebound.percent,
name: "Hash Value",
color: colors.bitcoin,
unit: Unit.percentage,
@@ -947,12 +905,12 @@ export function createMiningSection() {
title: "Next Halving",
bottom: [
line({
metric: blocks.halving.blocksBeforeNextHalving,
metric: blocks.halving.blocksBeforeNext,
name: "Remaining",
unit: Unit.blocks,
}),
line({
metric: blocks.halving.daysBeforeNextHalving,
metric: blocks.halving.daysBeforeNext,
name: "Remaining",
unit: Unit.days,
}),
@@ -983,11 +941,11 @@ export function createMiningSection() {
{
name: "Dominance",
title: "Dominance: Major Pools (1m)",
bottom: majorPools.map((p, i) =>
bottom: featuredPools.map((p, i) =>
line({
metric: p.pool.dominance1m,
metric: p.pool.dominance._1m.percent,
name: p.name,
color: colors.at(i, majorPools.length),
color: colors.at(i, featuredPools.length),
unit: Unit.percentage,
}),
),
@@ -995,11 +953,11 @@ export function createMiningSection() {
{
name: "Blocks Mined",
title: "Blocks Mined: Major Pools (1m)",
bottom: majorPools.map((p, i) =>
bottom: featuredPools.map((p, i) =>
line({
metric: p.pool.blocksMined1mSum,
metric: p.pool.blocksMined.sum._1m,
name: p.name,
color: colors.at(i, majorPools.length),
color: colors.at(i, featuredPools.length),
unit: Unit.count,
}),
),
@@ -1007,12 +965,12 @@ export function createMiningSection() {
{
name: "Total Rewards",
title: "Total Rewards: Major Pools",
bottom: majorPools.flatMap((p, i) =>
bottom: featuredPools.flatMap((p, i) =>
satsBtcUsdFrom({
source: p.pool.coinbase,
source: p.pool.rewards,
key: "base",
name: p.name,
color: colors.at(i, majorPools.length),
color: colors.at(i, featuredPools.length),
}),
),
},
@@ -1027,7 +985,7 @@ export function createMiningSection() {
title: "Dominance: AntPool & Friends (1m)",
bottom: antpoolFriends.map((p, i) =>
line({
metric: p.pool.dominance1m,
metric: p.pool.dominance._1m.percent,
name: p.name,
color: colors.at(i, antpoolFriends.length),
unit: Unit.percentage,
@@ -1039,7 +997,7 @@ export function createMiningSection() {
title: "Blocks Mined: AntPool & Friends (1m)",
bottom: antpoolFriends.map((p, i) =>
line({
metric: p.pool.blocksMined1mSum,
metric: p.pool.blocksMined.sum._1m,
name: p.name,
color: colors.at(i, antpoolFriends.length),
unit: Unit.count,
@@ -1051,7 +1009,7 @@ export function createMiningSection() {
title: "Total Rewards: AntPool & Friends",
bottom: antpoolFriends.flatMap((p, i) =>
satsBtcUsdFrom({
source: p.pool.coinbase,
source: p.pool.rewards,
key: "base",
name: p.name,
color: colors.at(i, antpoolFriends.length),
@@ -1063,7 +1021,7 @@ export function createMiningSection() {
// All pools
{
name: "All Pools",
tree: poolsTree,
tree: [...majorPoolsTree, ...minorPoolsTree],
},
],
},

View File

@@ -10,14 +10,14 @@ import {
dots,
baseline,
fromSupplyPattern,
fromBaseStatsPattern,
chartsFromFullPerBlock,
chartsFromCount,
chartsFromValueFull,
fromStatsPattern,
chartsFromSumPerBlock,
statsAtWindow,
rollingWindowsTree,
distributionWindowsTree,
mapWindows,
ROLLING_WINDOWS,
} from "./series.js";
import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js";
@@ -33,7 +33,8 @@ export function createNetworkSection() {
outputs,
scripts,
supply,
distribution,
addresses,
cohorts,
} = brk.metrics;
const st = colors.scriptType;
@@ -60,13 +61,13 @@ export function createNetworkSection() {
defaultActive: false,
},
{
key: "emptyoutput",
key: "emptyOutput",
name: "Empty",
color: st.empty,
defaultActive: false,
},
{
key: "unknownoutput",
key: "unknownOutput",
name: "Unknown",
color: st.unknown,
defaultActive: false,
@@ -114,42 +115,25 @@ export function createNetworkSection() {
},
]);
// Balance change types
const balanceTypes = /** @type {const} */ ([
{
key: "balanceIncreased",
name: "Accumulating",
title: "Accumulating Addresses per Block",
compareTitle: "Accumulating Addresses per Block by Type",
},
{
key: "balanceDecreased",
name: "Distributing",
title: "Distributing Addresses per Block",
compareTitle: "Distributing Addresses per Block by Type",
},
]);
// Count types for comparison charts
// addrCount and emptyAddrCount have .count, totalAddrCount doesn't
const countTypes = /** @type {const} */ ([
{
name: "Funded",
title: "Address Count by Type",
/** @param {AddressableType} t */
getMetric: (t) => distribution.addrCount[t].count,
getMetric: (t) => addresses.funded[t],
},
{
name: "Empty",
title: "Empty Address Count by Type",
/** @param {AddressableType} t */
getMetric: (t) => distribution.emptyAddrCount[t].count,
getMetric: (t) => addresses.empty[t],
},
{
name: "Total",
title: "Total Address Count by Type",
/** @param {AddressableType} t */
getMetric: (t) => distribution.totalAddrCount[t],
getMetric: (t) => addresses.total[t],
},
]);
@@ -164,19 +148,19 @@ export function createNetworkSection() {
title: `${titlePrefix}Address Count`,
bottom: [
line({
metric: distribution.addrCount[key].count,
metric: addresses.funded[key],
name: "Funded",
unit: Unit.count,
}),
line({
metric: distribution.emptyAddrCount[key].count,
metric: addresses.empty[key],
name: "Empty",
color: colors.gray,
unit: Unit.count,
defaultActive: false,
}),
line({
metric: distribution.totalAddrCount[key],
metric: addresses.total[key],
name: "Total",
color: colors.default,
unit: Unit.count,
@@ -187,35 +171,23 @@ export function createNetworkSection() {
{
name: "Trends",
tree: [
{
name: "30d Change",
title: `${titlePrefix}Address Count 30d Change`,
bottom: [
baseline({
metric: distribution.addrCount[key]._30dChange,
name: "Funded",
unit: Unit.count,
}),
baseline({
metric: distribution.emptyAddrCount[key]._30dChange,
name: "Empty",
color: colors.gray,
unit: Unit.count,
defaultActive: false,
}),
],
},
rollingWindowsTree({
windows: addresses.delta[key].change,
title: `${titlePrefix}Address Count Change`,
unit: Unit.count,
series: baseline,
}),
{
name: "New",
tree: (() => {
const p = distribution.newAddrCount[key];
const p = addresses.new[key];
const t = `${titlePrefix}New Address Count`;
return [
{
name: "Sum",
title: t,
bottom: [
line({ metric: p.height, name: "base", unit: Unit.count }),
line({ metric: p.base, name: "base", unit: Unit.count }),
],
},
rollingWindowsTree({
@@ -239,46 +211,66 @@ export function createNetworkSection() {
},
{
name: "Reactivated",
title: `${titlePrefix}Reactivated Addresses per Block`,
bottom: fromBaseStatsPattern({
pattern: distribution.addressActivity[key].reactivated,
window: "_24h",
unit: Unit.count,
}),
},
{
name: "Growth Rate",
title: `${titlePrefix}Address Growth Rate per Block`,
bottom: fromBaseStatsPattern({
pattern: distribution.growthRate[key],
window: "_24h",
unit: Unit.ratio,
}),
tree: [
{
name: "Base",
title: `${titlePrefix}Reactivated Addresses per Block`,
bottom: [
dots({
metric: addresses.activity[key].reactivated.height,
name: "base",
unit: Unit.count,
}),
line({
metric: addresses.activity[key].reactivated._24h,
name: "24h avg",
color: colors.stat.avg,
unit: Unit.count,
}),
],
},
rollingWindowsTree({
windows: addresses.activity[key].reactivated,
title: `${titlePrefix}Reactivated Addresses`,
unit: Unit.count,
}),
],
},
rollingWindowsTree({
windows: mapWindows(addresses.delta[key].rate, (r) => r.ratio),
title: `${titlePrefix}Address Growth Rate`,
unit: Unit.ratio,
}),
],
},
{
name: "Transacting",
tree: transactingTypes.map((t) => ({
name: t.name,
title: `${titlePrefix}${t.title}`,
bottom: fromBaseStatsPattern({
pattern: distribution.addressActivity[key][t.key],
window: "_24h",
unit: Unit.count,
}),
})),
},
{
name: "Balance",
tree: balanceTypes.map((b) => ({
name: b.name,
title: `${titlePrefix}${b.title}`,
bottom: fromBaseStatsPattern({
pattern: distribution.addressActivity[key][b.key],
window: "_24h",
unit: Unit.count,
}),
tree: [
{
name: "Base",
title: `${titlePrefix}${t.title}`,
bottom: [
dots({
metric: addresses.activity[key][t.key].height,
name: "base",
unit: Unit.count,
}),
line({
metric: addresses.activity[key][t.key]._24h,
name: "24h avg",
color: colors.stat.avg,
unit: Unit.count,
}),
],
},
rollingWindowsTree({
windows: addresses.activity[key][t.key],
title: `${titlePrefix}${t.title.replace(" per Block", "")}`,
unit: Unit.count,
}),
],
})),
},
];
@@ -312,13 +304,13 @@ export function createNetworkSection() {
title: `${groupName} New Address Count`,
bottom: types.flatMap((t) => [
line({
metric: distribution.newAddrCount[t.key].height,
metric: addresses.new[t.key].base,
name: t.name,
color: t.color,
unit: Unit.count,
}),
line({
metric: distribution.newAddrCount[t.key].sum._24h,
metric: addresses.new[t.key].sum._24h,
name: t.name,
color: t.color,
unit: Unit.count,
@@ -327,81 +319,78 @@ export function createNetworkSection() {
},
{
name: "Reactivated",
title: `${groupName} Reactivated Addresses per Block`,
bottom: types.flatMap((t) => [
line({
metric: distribution.addressActivity[t.key].reactivated.height,
name: t.name,
color: t.color,
unit: Unit.count,
}),
line({
metric:
distribution.addressActivity[t.key].reactivated.average._24h,
name: t.name,
color: t.color,
unit: Unit.count,
}),
]),
tree: [
{
name: "Base",
title: `${groupName} Reactivated Addresses per Block`,
bottom: types.map((t) =>
line({
metric: addresses.activity[t.key].reactivated.height,
name: t.name,
color: t.color,
unit: Unit.count,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${groupName} Reactivated Addresses (${w.name})`,
bottom: types.map((t) =>
line({
metric: addresses.activity[t.key].reactivated[w.key],
name: t.name,
color: t.color,
unit: Unit.count,
}),
),
})),
],
},
{
name: "Growth Rate",
title: `${groupName} Address Growth Rate per Block`,
bottom: types.flatMap((t) => [
dots({
metric: distribution.growthRate[t.key].height,
name: t.name,
color: t.color,
unit: Unit.ratio,
}),
dots({
metric: distribution.growthRate[t.key].average._24h,
name: t.name,
color: t.color,
unit: Unit.ratio,
}),
]),
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,
name: t.name,
color: t.color,
unit: Unit.ratio,
}),
),
})),
},
{
name: "Transacting",
tree: transactingTypes.map((tr) => ({
name: tr.name,
title: `${groupName} ${tr.compareTitle}`,
bottom: types.flatMap((t) => [
line({
metric: distribution.addressActivity[t.key][tr.key].height,
name: t.name,
color: t.color,
unit: Unit.count,
}),
line({
metric: distribution.addressActivity[t.key][tr.key].average._24h,
name: t.name,
color: t.color,
unit: Unit.count,
}),
]),
})),
},
{
name: "Balance",
tree: balanceTypes.map((b) => ({
name: b.name,
title: `${groupName} ${b.compareTitle}`,
bottom: types.flatMap((t) => [
line({
metric: distribution.addressActivity[t.key][b.key].height,
name: t.name,
color: t.color,
unit: Unit.count,
}),
line({
metric: distribution.addressActivity[t.key][b.key].average._24h,
name: t.name,
color: t.color,
unit: Unit.count,
}),
]),
tree: [
{
name: "Base",
title: `${groupName} ${tr.compareTitle}`,
bottom: types.map((t) =>
line({
metric: addresses.activity[t.key][tr.key].height,
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({
metric: addresses.activity[t.key][tr.key][w.key],
name: t.name,
color: t.color,
unit: Unit.count,
}),
),
})),
],
})),
},
],
@@ -478,7 +467,7 @@ export function createNetworkSection() {
title: "Inflation Rate",
bottom: [
dots({
metric: supply.inflation,
metric: supply.inflationRate.percent,
name: "Rate",
unit: Unit.percentage,
}),
@@ -509,10 +498,18 @@ export function createNetworkSection() {
},
{
name: "OP_RETURN",
tree: chartsFromValueFull({
pattern: scripts.value.opreturn,
title: "OP_RETURN Burned",
}),
tree: [
{
name: "Sum",
title: "OP_RETURN Burned",
bottom: satsBtcUsd({ pattern: scripts.value.opreturn.base, name: "sum" }),
},
{
name: "Cumulative",
title: "OP_RETURN Burned (Total)",
bottom: satsBtcUsd({ pattern: scripts.value.opreturn.cumulative, name: "all-time" }),
},
],
},
],
},
@@ -524,7 +521,7 @@ export function createNetworkSection() {
{
name: "Count",
tree: chartsFromFullPerBlock({
pattern: transactions.count.txCount,
pattern: transactions.count.total,
title: "Transaction Count",
unit: Unit.count,
}),
@@ -545,11 +542,11 @@ export function createNetworkSection() {
title: "Transaction Volume",
bottom: [
...satsBtcUsd({
pattern: transactions.volume.sentSum,
pattern: transactions.volume.sentSum.base,
name: "Sent",
}),
...satsBtcUsd({
pattern: transactions.volume.receivedSum,
pattern: transactions.volume.receivedSum.base,
name: "Received",
color: colors.entity.output,
}),
@@ -559,7 +556,7 @@ export function createNetworkSection() {
name: "Annualized",
title: "Annualized Transaction Volume",
bottom: satsBtcUsd({
pattern: transactions.volume.annualizedVolume,
pattern: transactions.volume.sentSum.sum._1y,
name: "Annualized",
}),
},
@@ -588,7 +585,7 @@ export function createNetworkSection() {
bottom: entries(transactions.versions).map(
([v, data], i, arr) =>
line({
metric: data.height,
metric: data.base,
name: v,
color: colors.at(i, arr.length),
unit: Unit.count,
@@ -642,12 +639,12 @@ export function createNetworkSection() {
title: "Block Count",
bottom: [
line({
metric: blocks.count.blockCount.height,
metric: blocks.count.total.base,
name: "base",
unit: Unit.count,
}),
line({
metric: blocks.count.blockCountTarget,
metric: blocks.count.target,
name: "Target",
color: colors.gray,
unit: Unit.count,
@@ -656,7 +653,7 @@ export function createNetworkSection() {
],
},
rollingWindowsTree({
windows: blocks.count.blockCountSum,
windows: blocks.count.total.sum,
title: "Block Count",
unit: Unit.count,
}),
@@ -665,7 +662,7 @@ export function createNetworkSection() {
title: "Block Count (Total)",
bottom: [
line({
metric: blocks.count.blockCount.cumulative,
metric: blocks.count.total.cumulative,
name: "all-time",
unit: Unit.count,
}),
@@ -675,14 +672,30 @@ export function createNetworkSection() {
},
{
name: "Interval",
title: "Block Interval",
bottom: [
...fromBaseStatsPattern({
pattern: blocks.interval,
window: "_24h",
tree: [
{
name: "Base",
title: "Block Interval",
bottom: [
dots({
metric: blocks.interval.height,
name: "base",
unit: Unit.secs,
}),
line({
metric: blocks.interval._24h,
name: "24h avg",
color: colors.stat.avg,
unit: Unit.secs,
}),
priceLine({ unit: Unit.secs, name: "Target", number: 600 }),
],
},
rollingWindowsTree({
windows: blocks.interval,
title: "Block Interval",
unit: Unit.secs,
}),
priceLine({ unit: Unit.secs, name: "Target", number: 600 }),
],
},
{
@@ -693,7 +706,7 @@ export function createNetworkSection() {
title: "Block Size",
bottom: [
line({
metric: blocks.totalSize,
metric: blocks.size.total,
name: "base",
unit: Unit.bytes,
}),
@@ -704,21 +717,12 @@ export function createNetworkSection() {
title: "Block Size",
unit: Unit.bytes,
}),
{
name: "Distribution",
title: "Block Size Distribution",
bottom: [
line({
metric: blocks.totalSize,
name: "base",
unit: Unit.bytes,
}),
...fromStatsPattern({
pattern: statsAtWindow(blocks.size, "_24h"),
unit: Unit.bytes,
}),
],
},
distributionWindowsTree({
pattern: blocks.size,
base: blocks.size.total,
title: "Block Size",
unit: Unit.bytes,
}),
{
name: "Cumulative",
title: "Block Size (Total)",
@@ -740,7 +744,7 @@ export function createNetworkSection() {
title: "Block Weight",
bottom: [
line({
metric: blocks.weight.base,
metric: blocks.weight.raw,
name: "base",
unit: Unit.wu,
}),
@@ -751,21 +755,12 @@ export function createNetworkSection() {
title: "Block Weight",
unit: Unit.wu,
}),
{
name: "Distribution",
title: "Block Weight Distribution",
bottom: [
line({
metric: blocks.weight.base,
name: "base",
unit: Unit.wu,
}),
...fromStatsPattern({
pattern: statsAtWindow(blocks.weight, "_24h"),
unit: Unit.wu,
}),
],
},
distributionWindowsTree({
pattern: blocks.weight,
base: blocks.weight.raw,
title: "Block Weight",
unit: Unit.wu,
}),
{
name: "Cumulative",
title: "Block Weight (Total)",
@@ -787,7 +782,7 @@ export function createNetworkSection() {
title: "Block vBytes",
bottom: [
line({
metric: blocks.vbytes.height,
metric: blocks.vbytes.base,
name: "base",
unit: Unit.vb,
}),
@@ -798,21 +793,12 @@ export function createNetworkSection() {
title: "Block vBytes",
unit: Unit.vb,
}),
{
name: "Distribution",
title: "Block vBytes Distribution",
bottom: [
line({
metric: blocks.vbytes.height,
name: "base",
unit: Unit.vb,
}),
...fromStatsPattern({
pattern: statsAtWindow(blocks.vbytes, "_24h"),
unit: Unit.vb,
}),
],
},
distributionWindowsTree({
pattern: blocks.vbytes,
base: blocks.vbytes.base,
title: "Block vBytes",
unit: Unit.vb,
}),
{
name: "Cumulative",
title: "Block vBytes (Total)",
@@ -829,11 +815,13 @@ export function createNetworkSection() {
{
name: "Fullness",
title: "Block Fullness",
bottom: fromBaseStatsPattern({
pattern: blocks.fullness,
window: "_24h",
unit: Unit.percentage,
}),
bottom: [
dots({
metric: blocks.fullness.percent,
name: "base",
unit: Unit.percentage,
}),
],
},
],
},
@@ -847,7 +835,7 @@ export function createNetworkSection() {
title: "UTXO Count",
bottom: [
line({
metric: outputs.count.utxoCount,
metric: outputs.count.unspent,
name: "Count",
unit: Unit.count,
}),
@@ -858,7 +846,7 @@ export function createNetworkSection() {
title: "UTXO Count 30d Change",
bottom: [
baseline({
metric: distribution.utxoCohorts.all.outputs.utxoCount30dChange,
metric: cohorts.utxo.all.outputs.unspentCount.delta.change._1m,
name: "30d Change",
unit: Unit.count,
}),
@@ -869,7 +857,7 @@ export function createNetworkSection() {
title: "UTXO Flow",
bottom: [
line({
metric: outputs.count.totalCount.sum,
metric: outputs.count.total.sum,
name: "Created",
color: colors.entity.output,
unit: Unit.count,
@@ -895,7 +883,7 @@ export function createNetworkSection() {
{
name: "Outputs",
tree: chartsFromSumPerBlock({
pattern: outputs.count.totalCount,
pattern: outputs.count.total,
title: "Output Count",
unit: Unit.count,
}),
@@ -957,14 +945,14 @@ export function createNetworkSection() {
title: "New Address Count by Type",
bottom: addressTypes.flatMap((t) => [
line({
metric: distribution.newAddrCount[t.key].height,
metric: addresses.new[t.key].base,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
line({
metric: distribution.newAddrCount[t.key].sum._24h,
metric: addresses.new[t.key].sum._24h,
name: t.name,
color: t.color,
unit: Unit.count,
@@ -974,95 +962,83 @@ export function createNetworkSection() {
},
{
name: "Reactivated",
title: "Reactivated Addresses per Block by Type",
bottom: addressTypes.flatMap((t) => [
line({
metric:
distribution.addressActivity[t.key].reactivated.height,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
line({
metric:
distribution.addressActivity[t.key].reactivated.average
._24h,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
]),
tree: [
{
name: "Base",
title: "Reactivated Addresses per Block by Type",
bottom: addressTypes.map((t) =>
line({
metric: addresses.activity[t.key].reactivated.height,
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({
metric: addresses.activity[t.key].reactivated[w.key],
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
),
})),
],
},
{
name: "Growth Rate",
title: "Address Growth Rate per Block by Type",
bottom: addressTypes.flatMap((t) => [
dots({
metric: distribution.growthRate[t.key].height,
name: t.name,
color: t.color,
unit: Unit.ratio,
defaultActive: t.defaultActive,
}),
dots({
metric: distribution.growthRate[t.key].average._24h,
name: t.name,
color: t.color,
unit: Unit.ratio,
defaultActive: t.defaultActive,
}),
]),
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,
name: t.name,
color: t.color,
unit: Unit.ratio,
defaultActive: t.defaultActive,
}),
),
})),
},
{
name: "Transacting",
tree: transactingTypes.map((tr) => ({
name: tr.name,
title: tr.compareTitle,
bottom: addressTypes.flatMap((t) => [
line({
metric:
distribution.addressActivity[t.key][tr.key].height,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
line({
metric:
distribution.addressActivity[t.key][tr.key].average
._24h,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
]),
})),
},
{
name: "Balance",
tree: balanceTypes.map((b) => ({
name: b.name,
title: b.compareTitle,
bottom: addressTypes.flatMap((t) => [
line({
metric: distribution.addressActivity[t.key][b.key].height,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
line({
metric:
distribution.addressActivity[t.key][b.key].average._24h,
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
]),
tree: [
{
name: "Base",
title: tr.compareTitle,
bottom: addressTypes.map((t) =>
line({
metric: addresses.activity[t.key][tr.key].height,
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({
metric: addresses.activity[t.key][tr.key][w.key],
name: t.name,
color: t.color,
unit: Unit.count,
defaultActive: t.defaultActive,
}),
),
})),
],
})),
},
],
@@ -1237,13 +1213,13 @@ export function createNetworkSection() {
title: "Script Adoption",
bottom: [
line({
metric: scripts.adoption.segwit,
metric: scripts.adoption.segwit.percent,
name: "SegWit",
color: colors.segwit,
unit: Unit.percentage,
}),
line({
metric: scripts.adoption.taproot,
metric: scripts.adoption.taproot.percent,
name: "Taproot",
color: taprootAddresses[1].color,
unit: Unit.percentage,
@@ -1255,7 +1231,7 @@ export function createNetworkSection() {
title: "SegWit Adoption",
bottom: [
line({
metric: scripts.adoption.segwit,
metric: scripts.adoption.segwit.percent,
name: "Adoption",
unit: Unit.percentage,
}),
@@ -1266,7 +1242,7 @@ export function createNetworkSection() {
title: "Taproot Adoption",
bottom: [
line({
metric: scripts.adoption.taproot,
metric: scripts.adoption.taproot.percent,
name: "Adoption",
unit: Unit.percentage,
}),

View File

@@ -3,6 +3,36 @@
import { colors } from "../utils/colors.js";
import { Unit } from "../utils/units.js";
// ============================================================================
// Rolling window constants
// ============================================================================
/** @typedef {'_24h' | '_1w' | '_1m' | '_1y'} RollingWindowKey */
/** @type {ReadonlyArray<{key: RollingWindowKey, name: 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 },
];
/**
* Extract a metric from each rolling window via a mapping function
* @template T
* @param {{ _24h: T, _1w: T, _1m: T, _1y: T }} windows
* @param {(v: T) => AnyMetricPattern} extract
* @returns {{ _24h: AnyMetricPattern, _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }}
*/
export function mapWindows(windows, extract) {
return {
_24h: extract(windows._24h),
_1w: extract(windows._1w),
_1m: extract(windows._1m),
_1y: extract(windows._1y),
};
}
// ============================================================================
// Price helper for top pane (auto-expands to USD + sats)
// ============================================================================
@@ -439,46 +469,67 @@ export function statsAtWindow(pattern, window) {
* @param {{ _24h: AnyMetricPattern, _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }} args.windows
* @param {string} args.title
* @param {Unit} args.unit
* @param {(args: {metric: AnyMetricPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series]
* @returns {PartialOptionsGroup}
*/
export function rollingWindowsTree({ windows, title, unit }) {
export function rollingWindowsTree({ windows, title, unit, series = line }) {
return {
name: "Rolling",
tree: [
{
name: "Compare",
title: `${title} Rolling`,
bottom: ROLLING_WINDOWS.map((w) =>
series({
metric: windows[w.key],
name: w.name,
color: w.color,
unit,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${title} ${w.name}`,
bottom: [
line({ metric: windows._24h, name: "24h", color: colors.time._24h, unit }),
line({ metric: windows._1w, name: "7d", color: colors.time._1w, unit }),
line({ metric: windows._1m, name: "30d", color: colors.time._1m, unit }),
line({ metric: windows._1y, name: "1y", color: colors.time._1y, unit }),
series({
metric: windows[w.key],
name: w.name,
color: w.color,
unit,
}),
],
},
{
name: "24h",
title: `${title} 24h`,
bottom: [line({ metric: windows._24h, name: "24h", color: colors.time._24h, unit })],
},
{
name: "7d",
title: `${title} 7d`,
bottom: [line({ metric: windows._1w, name: "7d", color: colors.time._1w, unit })],
},
{
name: "30d",
title: `${title} 30d`,
bottom: [line({ metric: windows._1m, name: "30d", color: colors.time._1m, unit })],
},
{
name: "1y",
title: `${title} 1y`,
bottom: [line({ metric: windows._1y, name: "1y", color: colors.time._1y, unit })],
},
})),
],
};
}
/**
* Create a Distribution folder tree with stats at each rolling window (24h/7d/30d/1y)
* @param {Object} args
* @param {Record<string, any>} args.pattern - Pattern with pct10/pct25/... and average/median/... as _1y24h30d7dPattern
* @param {AnyMetricPattern} [args.base] - Optional base metric to show as dots on each chart
* @param {string} args.title
* @param {Unit} args.unit
* @returns {PartialOptionsGroup}
*/
export function distributionWindowsTree({ pattern, base, title, unit }) {
return {
name: "Distribution",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: `${title} Distribution (${w.name})`,
bottom: [
...(base ? [line({ metric: base, name: "base", unit })] : []),
...fromStatsPattern({
pattern: statsAtWindow(pattern, w.key),
unit,
}),
],
})),
};
}
/**
* Map a rolling window slot's stats to a specific unit, producing a stats-compatible pattern
* @param {RollingWindowSlot} slot - Rolling window slot (e.g., pattern.rolling._24h)
@@ -503,9 +554,18 @@ function rollingSlotForUnit(slot, unitKey) {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export const distributionBtcSatsUsd = (slot) => [
...fromStatsPattern({ pattern: rollingSlotForUnit(slot, "btc"), unit: Unit.btc }),
...fromStatsPattern({ pattern: rollingSlotForUnit(slot, "sats"), unit: Unit.sats }),
...fromStatsPattern({ pattern: rollingSlotForUnit(slot, "usd"), unit: Unit.usd }),
...fromStatsPattern({
pattern: rollingSlotForUnit(slot, "btc"),
unit: Unit.btc,
}),
...fromStatsPattern({
pattern: rollingSlotForUnit(slot, "sats"),
unit: Unit.sats,
}),
...fromStatsPattern({
pattern: rollingSlotForUnit(slot, "usd"),
unit: Unit.usd,
}),
];
/**
@@ -658,20 +718,16 @@ export function chartsFromFull({
distributionSuffix = "",
}) {
const distTitle = distributionSuffix
? `${title} ${distributionSuffix} Distribution`
: `${title} Distribution`;
? `${title} ${distributionSuffix}`
: title;
return [
{
name: "Sum",
title,
bottom: [{ metric: pattern.height, title: "base", unit }],
bottom: [{ metric: pattern.base, title: "base", unit }],
},
rollingWindowsTree({ windows: pattern.sum, title, unit }),
{
name: "Distribution",
title: distTitle,
bottom: distributionSeries(statsAtWindow(pattern, "_24h"), unit),
},
distributionWindowsTree({ pattern, title: distTitle, unit }),
{
name: "Cumulative",
title: `${title} (Total)`,
@@ -708,17 +764,19 @@ export function chartsFromSum({
}) {
const { stat } = colors;
const distTitle = distributionSuffix
? `${title} ${distributionSuffix} Distribution`
: `${title} Distribution`;
? `${title} ${distributionSuffix}`
: title;
return [
{
name: "Sum",
title,
bottom: [{ metric: pattern.sum, title: "sum", color: stat.sum, unit }],
},
rollingWindowsTree({ windows: pattern.rolling.sum, title, unit }),
distributionWindowsTree({ pattern: pattern.rolling, title: distTitle, unit }),
{
name: "Distribution",
title: distTitle,
title: `${distTitle} Distribution`,
bottom: distributionSeries(pattern, unit),
},
{
@@ -775,21 +833,19 @@ export function chartsFromValueFull({ pattern, title }) {
bottom: [
...btcSatsUsdSeries({ metrics: pattern.base, name: "sum" }),
...btcSatsUsdSeries({
metrics: pattern._24h.sum,
metrics: pattern.sum._24h,
name: "24h sum",
defaultActive: false,
}),
],
},
{
name: "Distribution",
title: `${title} Distribution`,
bottom: distributionBtcSatsUsd(pattern._24h),
},
{
name: "Cumulative",
title: `${title} (Total)`,
bottom: btcSatsUsdSeries({ metrics: pattern.cumulative, name: "all-time" }),
bottom: btcSatsUsdSeries({
metrics: pattern.cumulative,
name: "all-time",
}),
},
];
}

View File

@@ -148,7 +148,7 @@ export function satsBtcUsdBaseline({ pattern, name, color, defaultActive }) {
/**
* Create sats/btc/usd series from any value pattern using base or cumulative key
* @param {Object} args
* @param {AnyValuePatternType} args.source
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.source
* @param {'base' | 'cumulative'} args.key
* @param {string} args.name
* @param {Color} [args.color]
@@ -167,7 +167,7 @@ export function satsBtcUsdFrom({ source, key, name, color, defaultActive }) {
/**
* Create sats/btc/usd series from a full value pattern using base or cumulative key
* @param {Object} args
* @param {FullValuePattern} args.source
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.source
* @param {'base' | 'cumulative'} args.key
* @param {string} args.name
* @param {Color} [args.color]
@@ -192,9 +192,9 @@ export function satsBtcUsdFromFull({
/**
* Create coinbase/subsidy/fee series from separate sources
* @param {Object} args
* @param {AnyValuePatternType} args.coinbase
* @param {AnyValuePatternType} args.subsidy
* @param {AnyValuePatternType} args.fee
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.coinbase
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.subsidy
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.fee
* @param {'base' | 'cumulative'} args.key
* @returns {FetchedLineSeriesBlueprint[]}
*/
@@ -267,13 +267,14 @@ export function revenueRollingBtcSatsUsd({ coinbase, subsidy, fee }) {
* @param {AnyRatioPattern} ratio
*/
export function percentileUsdMap(ratio) {
const p = ratio.percentiles;
return /** @type {const} */ ([
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.ratioPct._95 },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.ratioPct._5 },
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.ratioPct._98 },
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.ratioPct._2 },
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.ratioPct._99 },
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.ratioPct._1 },
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
{ name: "pct98", prop: p.pct98.price, color: colors.ratioPct._98 },
{ name: "pct2", prop: p.pct2.price, color: colors.ratioPct._2 },
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
]);
}
@@ -282,13 +283,14 @@ export function percentileUsdMap(ratio) {
* @param {AnyRatioPattern} ratio
*/
export function percentileMap(ratio) {
const p = ratio.percentiles;
return /** @type {const} */ ([
{ name: "pct95", prop: ratio.ratioPct95, color: colors.ratioPct._95 },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.ratioPct._5 },
{ name: "pct98", prop: ratio.ratioPct98, color: colors.ratioPct._98 },
{ name: "pct2", prop: ratio.ratioPct2, color: colors.ratioPct._2 },
{ name: "pct99", prop: ratio.ratioPct99, color: colors.ratioPct._99 },
{ name: "pct1", prop: ratio.ratioPct1, color: colors.ratioPct._1 },
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
{ name: "pct98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
{ name: "pct2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
]);
}
@@ -298,10 +300,10 @@ export function percentileMap(ratio) {
*/
export function sdPatterns(ratio) {
return /** @type {const} */ ([
{ nameAddon: "All Time", titleAddon: "", sd: ratio.ratioSd },
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.ratio4ySd },
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.ratio2ySd },
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.ratio1ySd },
{ nameAddon: "All Time", titleAddon: "", sd: ratio.stdDev.all, smaRatio: ratio.sma.all.ratio },
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.stdDev._4y, smaRatio: ratio.sma._4y.ratio },
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.stdDev._2y, smaRatio: ratio.sma._2y.ratio },
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.stdDev._1y, smaRatio: ratio.sma._1y.ratio },
]);
}
@@ -311,41 +313,42 @@ export function sdPatterns(ratio) {
*/
export function sdBandsUsd(sd) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd._0sdUsd, color: colors.sd._0 },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.sd.p05 },
{ name: "0.5σ", prop: sd.m05sdUsd, color: colors.sd.m05 },
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.sd.p1 },
{ name: "1σ", prop: sd.m1sdUsd, color: colors.sd.m1 },
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.sd.p15 },
{ name: "1.5σ", prop: sd.m15sdUsd, color: colors.sd.m15 },
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.sd.p2 },
{ name: "2σ", prop: sd.m2sdUsd, color: colors.sd.m2 },
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.sd.p25 },
{ name: "2.5σ", prop: sd.m25sdUsd, color: colors.sd.m25 },
{ name: "+3σ", prop: sd.p3sdUsd, color: colors.sd.p3 },
{ name: "3σ", prop: sd.m3sdUsd, color: colors.sd.m3 },
{ name: "0σ", prop: sd._0sd, color: colors.sd._0 },
{ name: "+0.5σ", prop: sd.p05sd.price, color: colors.sd.p05 },
{ name: "0.5σ", prop: sd.m05sd.price, color: colors.sd.m05 },
{ name: "+1σ", prop: sd.p1sd.price, color: colors.sd.p1 },
{ name: "1σ", prop: sd.m1sd.price, color: colors.sd.m1 },
{ name: "+1.5σ", prop: sd.p15sd.price, color: colors.sd.p15 },
{ name: "1.5σ", prop: sd.m15sd.price, color: colors.sd.m15 },
{ name: "+2σ", prop: sd.p2sd.price, color: colors.sd.p2 },
{ name: "2σ", prop: sd.m2sd.price, color: colors.sd.m2 },
{ name: "+2.5σ", prop: sd.p25sd.price, color: colors.sd.p25 },
{ name: "2.5σ", prop: sd.m25sd.price, color: colors.sd.m25 },
{ name: "+3σ", prop: sd.p3sd.price, color: colors.sd.p3 },
{ name: "3σ", prop: sd.m3sd.price, color: colors.sd.m3 },
]);
}
/**
* Build SD band mappings (ratio) from an SD pattern
* @param {Ratio1ySdPattern} sd
* @param {AnyMetricPattern} smaRatio
*/
export function sdBandsRatio(sd) {
export function sdBandsRatio(sd, smaRatio) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd.sma, color: colors.sd._0 },
{ name: "+0.5σ", prop: sd.p05sd, color: colors.sd.p05 },
{ name: "0.5σ", prop: sd.m05sd, color: colors.sd.m05 },
{ name: "+1σ", prop: sd.p1sd, color: colors.sd.p1 },
{ name: "1σ", prop: sd.m1sd, color: colors.sd.m1 },
{ name: "+1.5σ", prop: sd.p15sd, color: colors.sd.p15 },
{ name: "1.5σ", prop: sd.m15sd, color: colors.sd.m15 },
{ name: "+2σ", prop: sd.p2sd, color: colors.sd.p2 },
{ name: "2σ", prop: sd.m2sd, color: colors.sd.m2 },
{ name: "+2.5σ", prop: sd.p25sd, color: colors.sd.p25 },
{ name: "2.5σ", prop: sd.m25sd, color: colors.sd.m25 },
{ name: "+3σ", prop: sd.p3sd, color: colors.sd.p3 },
{ name: "3σ", prop: sd.m3sd, color: colors.sd.m3 },
{ name: "0σ", prop: smaRatio, color: colors.sd._0 },
{ name: "+0.5σ", prop: sd.p05sd.value, color: colors.sd.p05 },
{ name: "0.5σ", prop: sd.m05sd.value, color: colors.sd.m05 },
{ name: "+1σ", prop: sd.p1sd.value, color: colors.sd.p1 },
{ name: "1σ", prop: sd.m1sd.value, color: colors.sd.m1 },
{ name: "+1.5σ", prop: sd.p15sd.value, color: colors.sd.p15 },
{ name: "1.5σ", prop: sd.m15sd.value, color: colors.sd.m15 },
{ name: "+2σ", prop: sd.p2sd.value, color: colors.sd.p2 },
{ name: "2σ", prop: sd.m2sd.value, color: colors.sd.m2 },
{ name: "+2.5σ", prop: sd.p25sd.value, color: colors.sd.p25 },
{ name: "2.5σ", prop: sd.m25sd.value, color: colors.sd.m25 },
{ name: "+3σ", prop: sd.p3sd.value, color: colors.sd.p3 },
{ name: "3σ", prop: sd.m3sd.value, color: colors.sd.m3 },
]);
}
@@ -355,12 +358,12 @@ export function sdBandsRatio(sd) {
*/
export function ratioSmas(ratio) {
return [
{ name: "1w SMA", metric: ratio.ratio1wSma },
{ name: "1m SMA", metric: ratio.ratio1mSma },
{ name: "1y SMA", metric: ratio.ratio1ySd.sma },
{ name: "2y SMA", metric: ratio.ratio2ySd.sma },
{ name: "4y SMA", metric: ratio.ratio4ySd.sma },
{ name: "All SMA", metric: ratio.ratioSd.sma, color: colors.time.all },
{ name: "1w SMA", metric: ratio.sma._1w.ratio },
{ name: "1m SMA", metric: ratio.sma._1m.ratio },
{ name: "1y SMA", metric: ratio.sma._1y.ratio },
{ name: "2y SMA", metric: ratio.sma._2y.ratio },
{ name: "4y SMA", metric: ratio.sma._4y.ratio },
{ name: "All SMA", metric: ratio.sma.all.ratio, color: colors.time.all },
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
}
@@ -434,10 +437,10 @@ export function createZScoresFolder({
const sdPats = sdPatterns(ratio);
const zscorePeriods = [
{ name: "1y", sd: ratio.ratio1ySd },
{ name: "2y", sd: ratio.ratio2ySd },
{ name: "4y", sd: ratio.ratio4ySd },
{ name: "all", sd: ratio.ratioSd, color: colors.time.all },
{ name: "1y", sd: ratio.stdDev._1y },
{ name: "2y", sd: ratio.stdDev._2y },
{ name: "4y", sd: ratio.stdDev._4y },
{ name: "all", sd: ratio.stdDev.all, color: colors.time.all },
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
return {
@@ -450,7 +453,7 @@ export function createZScoresFolder({
price({ metric: pricePattern, name: legend, color }),
...zscorePeriods.map((p) =>
price({
metric: p.sd._0sdUsd,
metric: p.sd._0sd,
name: `${p.name} 0σ`,
color: p.color,
defaultActive: false,
@@ -473,7 +476,7 @@ export function createZScoresFolder({
}),
],
},
...sdPats.map(({ nameAddon, titleAddon, sd }) => {
...sdPats.map(({ nameAddon, titleAddon, sd, smaRatio }) => {
const prefix = titleAddon ? `${titleAddon} ` : "";
const topPrice = price({ metric: pricePattern, name: legend, color });
return {
@@ -521,7 +524,7 @@ export function createZScoresFolder({
unit: Unit.ratio,
base: 1,
}),
...sdBandsRatio(sd).map(
...sdBandsRatio(sd, smaRatio).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,

View File

@@ -173,9 +173,11 @@
* Patterns with RelToMarketCap in relative (geAmount.*, ltAmount.*):
* @typedef {UtxoAmountPattern | AddressAmountPattern} PatternBasicWithMarketCap
*
* Patterns without RelToMarketCap in relative (RelativePattern4):
* - EpochPattern (epoch.*, amountRange.*, year.*, type.*)
* @typedef {EpochPattern} PatternBasicWithoutMarketCap
* Patterns without RelToMarketCap in relative:
* - EpochPattern (epoch.*, year.*)
* - UtxoAmountPattern (amountRange.*)
* - OutputsRealizedSupplyUnrealizedPattern2 (addressable type.*)
* @typedef {EpochPattern | UtxoAmountPattern | Brk.OutputsRealizedSupplyUnrealizedPattern2} PatternBasicWithoutMarketCap
*
* Patterns without relative section entirely (edge case output types):
* - EmptyPattern (type.empty, type.p2ms, type.unknown)
@@ -259,8 +261,8 @@
* Extended Cohort Types (with address count)
* ============================================================================
*
* Addressable cohort with address count (for "type" cohorts - no RelToMarketCap)
* @typedef {CohortBasicWithoutMarketCap & { addressCount: Brk.DeltaInnerPattern }} CohortAddress
* Addressable cohort with address count (for "type" cohorts - uses OutputsRealizedSupplyUnrealizedPattern2)
* @typedef {{ name: string, title: string, color: Color, tree: Brk.OutputsRealizedSupplyUnrealizedPattern2, addressCount: Brk.DeltaInnerPattern }} CohortAddress
*
* ============================================================================
* Cohort Group Types (by capability)

View File

@@ -30,6 +30,8 @@ function walkMetrics(node, map, path) {
key.endsWith("Cents") ||
key.endsWith("State") ||
key.endsWith("Start") ||
kn === "cents" ||
kn === "bps" ||
kn === "mvrv" ||
kn === "constants" ||
kn === "blockhash" ||
@@ -144,8 +146,10 @@ export function extractTreeStructure(options) {
/** @type {Record<string, string[]>} */
const grouped = {};
for (const s of series) {
const metric = /** @type {any} */ (s.metric);
if (isTop && metric?.usd && metric?.sats) {
const metric = /** @type {AnyMetricPattern | AnyPricePattern} */ (
s.metric
);
if (isTop && "usd" in metric && "sats" in metric) {
const title = s.title || s.key || "unnamed";
(grouped["USD"] ??= []).push(title);
(grouped["sats"] ??= []).push(title);

View File

@@ -30,63 +30,63 @@
* @typedef {SeriesMarker<Time>} TimeSeriesMarker
*
* Brk tree types (stable across regenerations)
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts} UtxoCohortTree
* @typedef {Brk.MetricsTree_Distribution_AddressCohorts} AddressCohortTree
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_All} AllUtxoPattern
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_Sth} ShortTermPattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern} LongTermPattern
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_All_Relative} AllRelativePattern
* @typedef {keyof Brk.BtcSatsUsdPattern} BtcSatsUsdKey
* @typedef {Brk.BtcSatsUsdPattern} SupplyPattern
* @typedef {Brk.AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} BlockSizePattern
* @typedef {Brk.MetricsTree_Cohorts_Utxo} UtxoCohortTree
* @typedef {Brk.MetricsTree_Cohorts_Address} AddressCohortTree
* @typedef {Brk.MetricsTree_Cohorts_Utxo_All} AllUtxoPattern
* @typedef {Brk.MetricsTree_Cohorts_Utxo_Sth} ShortTermPattern
* @typedef {Brk.MetricsTree_Cohorts_Utxo_Lth} LongTermPattern
* @typedef {Brk.MetricsTree_Cohorts_Utxo_All_Unrealized} AllRelativePattern
* @typedef {keyof Brk.BtcCentsSatsUsdPattern} BtcSatsUsdKey
* @typedef {Brk.BtcCentsSatsUsdPattern} SupplyPattern
* @typedef {Brk.AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} BlockSizePattern
* @typedef {keyof Brk.MetricsTree_Cohorts_Utxo_Type} SpendableType
* @typedef {keyof Brk.MetricsTree_Addresses_Raw} AddressableType
*
* Brk pattern types (using new pattern names)
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4} MaxAgePattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern} AgeRangePattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3} UtxoAmountPattern
* @typedef {Brk.ActivityAddrCostOutputsRealizedRelativeSupplyUnrealizedPattern} AddressAmountPattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4} BasicUtxoPattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3} EpochPattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3} EmptyPattern
* @typedef {Brk._0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern} Ratio1ySdPattern
* @typedef {Brk.ActivityOutputsRealizedSupplyUnrealizedPattern} MaxAgePattern
* @typedef {Brk.ActivityOutputsRealizedSupplyUnrealizedPattern} AgeRangePattern
* @typedef {Brk.OutputsRealizedSupplyUnrealizedPattern} UtxoAmountPattern
* @typedef {Brk.AddressOutputsRealizedSupplyUnrealizedPattern} AddressAmountPattern
* @typedef {Brk.ActivityOutputsRealizedSupplyUnrealizedPattern} BasicUtxoPattern
* @typedef {Brk.ActivityOutputsRealizedSupplyUnrealizedPattern} EpochPattern
* @typedef {Brk.OutputsRealizedSupplyUnrealizedPattern} EmptyPattern
* @typedef {Brk._0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern} Ratio1ySdPattern
* @typedef {Brk.Dollars} Dollars
* CoinbasePattern: base + cumulative + rolling windows (flattened)
* @typedef {Brk._1y24h30d7dBaseCumulativePattern} CoinbasePattern
* @typedef {Brk.BaseCumulativeSumPattern4} CoinbasePattern
* ActivePriceRatioPattern: ratio pattern with price (extended)
* @typedef {Brk.PriceRatioPattern} ActivePriceRatioPattern
* AnyRatioPattern: full ratio patterns (with or without price) - has ratio, percentiles, z-scores
* @typedef {Brk.RatioPattern | Brk.PriceRatioPattern} AnyRatioPattern
* @typedef {Brk.BpsPriceRatioPattern} ActivePriceRatioPattern
* AnyRatioPattern: full ratio pattern with percentiles, SMAs, and std dev bands
* @typedef {Brk.BpsCentsPercentilesRatioSatsSmaStdUsdPattern} AnyRatioPattern
* ValuePattern: patterns with base + cumulative (no rolling)
* @typedef {Brk.BaseCumulativeSumPattern<number> | Brk.BaseCumulativePattern} ValuePattern
* @typedef {Brk.BaseCumulativeSumPattern<number> | Brk.BaseCumulativeRelPattern} ValuePattern
* FullValuePattern: base + cumulative + rolling windows (flattened)
* @typedef {Brk._1y24h30d7dBaseCumulativePattern} FullValuePattern
* RollingWindowSlot: a single rolling window with stats (average, pct10, pct25, median, pct75, pct90, max, min, sum) per unit
* @typedef {Brk.AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern2} RollingWindowSlot
* @typedef {Brk.BaseCumulativeSumPattern4} FullValuePattern
* RollingWindowSlot: a single rolling window with stats (average, pct10, pct25, median, pct75, pct90, max, min) per unit
* @typedef {Brk.AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern} RollingWindowSlot
* AnyValuePatternType: union of all value pattern types
* @typedef {Brk._1y24h30d7dBaseCumulativePattern | Brk.BaseCumulativeSumPattern<number> | Brk.BaseCumulativePattern} AnyValuePatternType
* @typedef {Brk.BaseCumulativeSumPattern4 | Brk.BaseCumulativeSumPattern<number> | Brk.BaseCumulativeRelPattern} AnyValuePatternType
* @typedef {Brk.AnyMetricPattern} AnyMetricPattern
* @typedef {Brk.SatsUsdPattern} ActivePricePattern
* @typedef {Brk.CentsSatsUsdPattern} ActivePricePattern
* @typedef {Brk.AnyMetricEndpointBuilder} AnyMetricEndpoint
* @typedef {Brk.AnyMetricData} AnyMetricData
* @typedef {Brk.AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} AddrCountPattern
* @typedef {Brk.AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern3} AddrCountPattern
* Relative patterns by capability:
* - BasicRelativePattern: minimal relative (investedCapitalIn*Pct, supplyIn*RelToOwnSupply only)
* - GlobalRelativePattern: has RelToMarketCap metrics (netUnrealizedPnlRelToMarketCap, etc)
* - OwnRelativePattern: has RelToOwnMarketCap metrics (netUnrealizedPnlRelToOwnMarketCap, etc)
* - FullRelativePattern: has BOTH RelToMarketCap AND RelToOwnMarketCap
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern} BasicRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern} GlobalRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern2} OwnRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern2} FullRelativePattern
* @typedef {Brk.GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern} UnrealizedPattern
* @typedef {Brk.LossNetNuplProfitPattern} BasicRelativePattern
* @typedef {Brk.LossNetNuplProfitPattern} GlobalRelativePattern
* @typedef {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} OwnRelativePattern
* @typedef {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} FullRelativePattern
* @typedef {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} UnrealizedPattern
*
* Realized patterns
* @typedef {Brk.CapCapitulationInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprTotalUpperValuePattern} RealizedPattern
* @typedef {Brk.CapCapitulationInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprTotalUpperValuePattern2} RealizedPattern2
* @typedef {Brk.AdjustedCapCapitulationInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprTotalUpperValuePattern} RealizedPattern3
* @typedef {Brk.AdjustedCapCapitulationInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprTotalUpperValuePattern2} RealizedPattern4
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern2
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern3
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern4
*/
/**
@@ -98,9 +98,8 @@
* @typedef {Brk.AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern} StatsPattern
*/
/**
* Base stats pattern: height, average, min, max, percentiles (windowed, NO sum/cumulative)
* @template T
* @typedef {Brk.AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern<T>} BaseStatsPattern
* Base stats pattern: average, min, max, percentiles
* @typedef {Brk.AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern} BaseStatsPattern
*/
/**
* Full stats pattern: cumulative, sum, average, min, max, percentiles + rolling
@@ -117,11 +116,11 @@
/**
* Count pattern: height, cumulative, and rolling sum windows
* @template T
* @typedef {Brk.CumulativeHeightSumPattern<T>} CountPattern
* @typedef {Brk.BaseCumulativeSumPattern<T>} CountPattern
*/
/**
* Full per-block pattern: height, cumulative, sum, and distribution stats (all flat)
* @typedef {Brk.AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern} FullPerBlockPattern
* @typedef {Brk.AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} FullPerBlockPattern
*/
/**
* Any stats pattern union - patterns with sum/cumulative + percentiles