website: snapshot

This commit is contained in:
nym21
2026-02-03 00:08:37 +01:00
parent a6d8278730
commit 9e3fe4e557
18 changed files with 1171 additions and 1295 deletions
+5 -5
View File
@@ -829,7 +829,7 @@ impl<T: DeserializeOwned> MetricPattern<T> for MetricPattern30<T> { fn get(&self
pub struct MetricPattern31By<T> { client: Arc<BrkClientBase>, name: Arc<str>, _marker: std::marker::PhantomData<T> } pub struct MetricPattern31By<T> { client: Arc<BrkClientBase>, name: Arc<str>, _marker: std::marker::PhantomData<T> }
impl<T: DeserializeOwned> MetricPattern31By<T> { impl<T: DeserializeOwned> MetricPattern31By<T> {
pub fn loadedaddressindex(&self) -> MetricEndpointBuilder<T> { _ep(&self.client, &self.name, Index::FundedAddressIndex) } pub fn fundedaddressindex(&self) -> MetricEndpointBuilder<T> { _ep(&self.client, &self.name, Index::FundedAddressIndex) }
} }
pub struct MetricPattern31<T> { name: Arc<str>, pub by: MetricPattern31By<T> } pub struct MetricPattern31<T> { name: Arc<str>, pub by: MetricPattern31By<T> }
@@ -5112,7 +5112,7 @@ pub struct MetricsTree_Distribution {
pub total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, pub total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern,
pub new_addr_count: MetricsTree_Distribution_NewAddrCount, pub new_addr_count: MetricsTree_Distribution_NewAddrCount,
pub growth_rate: MetricsTree_Distribution_GrowthRate, pub growth_rate: MetricsTree_Distribution_GrowthRate,
pub loadedaddressindex: MetricPattern31<FundedAddressIndex>, pub fundedaddressindex: MetricPattern31<FundedAddressIndex>,
pub emptyaddressindex: MetricPattern32<EmptyAddressIndex>, pub emptyaddressindex: MetricPattern32<EmptyAddressIndex>,
} }
@@ -5130,7 +5130,7 @@ impl MetricsTree_Distribution {
total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern::new(client.clone(), "total_addr_count".to_string()), total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern::new(client.clone(), "total_addr_count".to_string()),
new_addr_count: MetricsTree_Distribution_NewAddrCount::new(client.clone(), format!("{base_path}_new_addr_count")), new_addr_count: MetricsTree_Distribution_NewAddrCount::new(client.clone(), format!("{base_path}_new_addr_count")),
growth_rate: MetricsTree_Distribution_GrowthRate::new(client.clone(), format!("{base_path}_growth_rate")), growth_rate: MetricsTree_Distribution_GrowthRate::new(client.clone(), format!("{base_path}_growth_rate")),
loadedaddressindex: MetricPattern31::new(client.clone(), "loadedaddressindex".to_string()), fundedaddressindex: MetricPattern31::new(client.clone(), "fundedaddressindex".to_string()),
emptyaddressindex: MetricPattern32::new(client.clone(), "emptyaddressindex".to_string()), emptyaddressindex: MetricPattern32::new(client.clone(), "emptyaddressindex".to_string()),
} }
} }
@@ -5165,14 +5165,14 @@ impl MetricsTree_Distribution_AnyAddressIndexes {
/// Metrics tree node. /// Metrics tree node.
pub struct MetricsTree_Distribution_AddressesData { pub struct MetricsTree_Distribution_AddressesData {
pub loaded: MetricPattern31<FundedAddressData>, pub funded: MetricPattern31<FundedAddressData>,
pub empty: MetricPattern32<EmptyAddressData>, pub empty: MetricPattern32<EmptyAddressData>,
} }
impl MetricsTree_Distribution_AddressesData { impl MetricsTree_Distribution_AddressesData {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self { pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self { Self {
loaded: MetricPattern31::new(client.clone(), "loadedaddressdata".to_string()), funded: MetricPattern31::new(client.clone(), "fundedaddressdata".to_string()),
empty: MetricPattern32::new(client.clone(), "emptyaddressdata".to_string()), empty: MetricPattern32::new(client.clone(), "emptyaddressdata".to_string()),
} }
} }
+22 -22
View File
@@ -63,7 +63,7 @@
* @property {?string=} witnessProgram - Witness program in hex * @property {?string=} witnessProgram - Witness program in hex
*/ */
/** /**
* Unified index for any address type (loaded or empty) * Unified index for any address type (funded or empty)
* *
* @typedef {TypeIndex} AnyAddressIndex * @typedef {TypeIndex} AnyAddressIndex
*/ */
@@ -276,6 +276,19 @@
* *
* @typedef {("json"|"csv")} Format * @typedef {("json"|"csv")} Format
*/ */
/**
* Data for a funded (non-empty) address with current balance
*
* @typedef {Object} FundedAddressData
* @property {number} txCount - Total transaction count
* @property {number} fundedTxoCount - Number of transaction outputs funded to this address
* @property {number} spentTxoCount - Number of transaction outputs spent by this address
* @property {Sats} received - Satoshis received by this address
* @property {Sats} sent - Satoshis sent by this address
* @property {CentsSats} realizedCapRaw - The realized capitalization: Σ(price × sats)
* @property {CentsSquaredSats} investorCapRaw - The investor capitalization: Σ(price² × sats)
*/
/** @typedef {TypeIndex} FundedAddressIndex */
/** @typedef {number} HalvingEpoch */ /** @typedef {number} HalvingEpoch */
/** /**
* A single hashrate data point. * A single hashrate data point.
@@ -326,7 +339,7 @@
* Aggregation dimension for querying metrics. Includes time-based (date, week, month, year), * Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
* block-based (height, txindex), and address/output type indexes. * block-based (height, txindex), and address/output type indexes.
* *
* @typedef {("dateindex"|"decadeindex"|"difficultyepoch"|"emptyoutputindex"|"halvingepoch"|"height"|"txinindex"|"monthindex"|"opreturnindex"|"txoutindex"|"p2aaddressindex"|"p2msoutputindex"|"p2pk33addressindex"|"p2pk65addressindex"|"p2pkhaddressindex"|"p2shaddressindex"|"p2traddressindex"|"p2wpkhaddressindex"|"p2wshaddressindex"|"quarterindex"|"semesterindex"|"txindex"|"unknownoutputindex"|"weekindex"|"yearindex"|"loadedaddressindex"|"emptyaddressindex"|"pairoutputindex")} Index * @typedef {("dateindex"|"decadeindex"|"difficultyepoch"|"emptyoutputindex"|"halvingepoch"|"height"|"txinindex"|"monthindex"|"opreturnindex"|"txoutindex"|"p2aaddressindex"|"p2msoutputindex"|"p2pk33addressindex"|"p2pk65addressindex"|"p2pkhaddressindex"|"p2shaddressindex"|"p2traddressindex"|"p2wpkhaddressindex"|"p2wshaddressindex"|"quarterindex"|"semesterindex"|"txindex"|"unknownoutputindex"|"weekindex"|"yearindex"|"fundedaddressindex"|"emptyaddressindex"|"pairoutputindex")} Index
*/ */
/** /**
* Information about an available index and its query aliases * Information about an available index and its query aliases
@@ -344,19 +357,6 @@
* @typedef {Object} LimitParam * @typedef {Object} LimitParam
* @property {Limit=} limit * @property {Limit=} limit
*/ */
/**
* Data for a loaded (non-empty) address with current balance
*
* @typedef {Object} LoadedAddressData
* @property {number} txCount - Total transaction count
* @property {number} fundedTxoCount - Number of transaction outputs funded to this address
* @property {number} spentTxoCount - Number of transaction outputs spent by this address
* @property {Sats} received - Satoshis received by this address
* @property {Sats} sent - Satoshis sent by this address
* @property {CentsSats} realizedCapRaw - The realized capitalization: Σ(price × sats)
* @property {CentsSquaredSats} investorCapRaw - The investor capitalization: Σ(price² × sats)
*/
/** @typedef {TypeIndex} LoadedAddressIndex */
/** /**
* Lowest price value for a time period * Lowest price value for a time period
* *
@@ -1247,7 +1247,7 @@ const _i27 = /** @type {const} */ (["txindex"]);
const _i28 = /** @type {const} */ (["unknownoutputindex"]); const _i28 = /** @type {const} */ (["unknownoutputindex"]);
const _i29 = /** @type {const} */ (["weekindex"]); const _i29 = /** @type {const} */ (["weekindex"]);
const _i30 = /** @type {const} */ (["yearindex"]); const _i30 = /** @type {const} */ (["yearindex"]);
const _i31 = /** @type {const} */ (["loadedaddressindex"]); const _i31 = /** @type {const} */ (["fundedaddressindex"]);
const _i32 = /** @type {const} */ (["emptyaddressindex"]); const _i32 = /** @type {const} */ (["emptyaddressindex"]);
/** /**
@@ -1365,7 +1365,7 @@ function createMetricPattern29(client, name) { return _mp(client, name, _i29); }
/** @template T @typedef {{ name: string, by: { readonly yearindex: MetricEndpointBuilder<T> }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }} MetricPattern30 */ /** @template T @typedef {{ name: string, by: { readonly yearindex: MetricEndpointBuilder<T> }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }} MetricPattern30 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {MetricPattern30<T>} */ /** @template T @param {BrkClientBase} client @param {string} name @returns {MetricPattern30<T>} */
function createMetricPattern30(client, name) { return _mp(client, name, _i30); } function createMetricPattern30(client, name) { return _mp(client, name, _i30); }
/** @template T @typedef {{ name: string, by: { readonly loadedaddressindex: MetricEndpointBuilder<T> }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }} MetricPattern31 */ /** @template T @typedef {{ name: string, by: { readonly fundedaddressindex: MetricEndpointBuilder<T> }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }} MetricPattern31 */
/** @template T @param {BrkClientBase} client @param {string} name @returns {MetricPattern31<T>} */ /** @template T @param {BrkClientBase} client @param {string} name @returns {MetricPattern31<T>} */
function createMetricPattern31(client, name) { return _mp(client, name, _i31); } function createMetricPattern31(client, name) { return _mp(client, name, _i31); }
/** @template T @typedef {{ name: string, by: { readonly emptyaddressindex: MetricEndpointBuilder<T> }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }} MetricPattern32 */ /** @template T @typedef {{ name: string, by: { readonly emptyaddressindex: MetricEndpointBuilder<T> }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }} MetricPattern32 */
@@ -4481,7 +4481,7 @@ function createUtxoPattern(client, acc) {
* @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} totalAddrCount * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} totalAddrCount
* @property {MetricsTree_Distribution_NewAddrCount} newAddrCount * @property {MetricsTree_Distribution_NewAddrCount} newAddrCount
* @property {MetricsTree_Distribution_GrowthRate} growthRate * @property {MetricsTree_Distribution_GrowthRate} growthRate
* @property {MetricPattern31<LoadedAddressIndex>} loadedaddressindex * @property {MetricPattern31<FundedAddressIndex>} fundedaddressindex
* @property {MetricPattern32<EmptyAddressIndex>} emptyaddressindex * @property {MetricPattern32<EmptyAddressIndex>} emptyaddressindex
*/ */
@@ -4499,7 +4499,7 @@ function createUtxoPattern(client, acc) {
/** /**
* @typedef {Object} MetricsTree_Distribution_AddressesData * @typedef {Object} MetricsTree_Distribution_AddressesData
* @property {MetricPattern31<LoadedAddressData>} loaded * @property {MetricPattern31<FundedAddressData>} funded
* @property {MetricPattern32<EmptyAddressData>} empty * @property {MetricPattern32<EmptyAddressData>} empty
*/ */
@@ -4903,7 +4903,7 @@ class BrkClient extends BrkClientBase {
"unknownoutputindex", "unknownoutputindex",
"weekindex", "weekindex",
"yearindex", "yearindex",
"loadedaddressindex", "fundedaddressindex",
"emptyaddressindex", "emptyaddressindex",
"pairoutputindex" "pairoutputindex"
]); ]);
@@ -6625,7 +6625,7 @@ class BrkClient extends BrkClientBase {
p2wsh: createMetricPattern24(this, 'anyaddressindex'), p2wsh: createMetricPattern24(this, 'anyaddressindex'),
}, },
addressesData: { addressesData: {
loaded: createMetricPattern31(this, 'loadedaddressdata'), funded: createMetricPattern31(this, 'fundedaddressdata'),
empty: createMetricPattern32(this, 'emptyaddressdata'), empty: createMetricPattern32(this, 'emptyaddressdata'),
}, },
utxoCohorts: { utxoCohorts: {
@@ -6909,7 +6909,7 @@ class BrkClient extends BrkClientBase {
p2tr: createAverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(this, 'p2tr_growth_rate'), p2tr: createAverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(this, 'p2tr_growth_rate'),
p2a: createAverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(this, 'p2a_growth_rate'), p2a: createAverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(this, 'p2a_growth_rate'),
}, },
loadedaddressindex: createMetricPattern31(this, 'loadedaddressindex'), fundedaddressindex: createMetricPattern31(this, 'fundedaddressindex'),
emptyaddressindex: createMetricPattern32(this, 'emptyaddressindex'), emptyaddressindex: createMetricPattern32(this, 'emptyaddressindex'),
}, },
supply: { supply: {
+29 -29
View File
@@ -25,7 +25,7 @@ Sats = int
TypeIndex = int TypeIndex = int
# Transaction ID (hash) # Transaction ID (hash)
Txid = str Txid = str
# Unified index for any address type (loaded or empty) # Unified index for any address type (funded or empty)
AnyAddressIndex = TypeIndex AnyAddressIndex = TypeIndex
# Bitcoin amount as floating point (1 BTC = 100,000,000 satoshis) # Bitcoin amount as floating point (1 BTC = 100,000,000 satoshis)
Bitcoin = float Bitcoin = float
@@ -67,12 +67,12 @@ EmptyAddressIndex = TypeIndex
EmptyOutputIndex = TypeIndex EmptyOutputIndex = TypeIndex
# Fee rate in sats/vB # Fee rate in sats/vB
FeeRate = float FeeRate = float
FundedAddressIndex = TypeIndex
HalvingEpoch = int HalvingEpoch = int
# Hex-encoded string # Hex-encoded string
Hex = str Hex = str
# Highest price value for a time period # Highest price value for a time period
High = CentsUnsigned High = CentsUnsigned
LoadedAddressIndex = TypeIndex
# Lowest price value for a time period # Lowest price value for a time period
Low = CentsUnsigned Low = CentsUnsigned
# Virtual size in vbytes (weight / 4, rounded up) # Virtual size in vbytes (weight / 4, rounded up)
@@ -157,7 +157,7 @@ WeekIndex = int
YearIndex = int YearIndex = int
# Aggregation dimension for querying metrics. Includes time-based (date, week, month, year), # Aggregation dimension for querying metrics. Includes time-based (date, week, month, year),
# block-based (height, txindex), and address/output type indexes. # block-based (height, txindex), and address/output type indexes.
Index = Literal["dateindex", "decadeindex", "difficultyepoch", "emptyoutputindex", "halvingepoch", "height", "txinindex", "monthindex", "opreturnindex", "txoutindex", "p2aaddressindex", "p2msoutputindex", "p2pk33addressindex", "p2pk65addressindex", "p2pkhaddressindex", "p2shaddressindex", "p2traddressindex", "p2wpkhaddressindex", "p2wshaddressindex", "quarterindex", "semesterindex", "txindex", "unknownoutputindex", "weekindex", "yearindex", "loadedaddressindex", "emptyaddressindex", "pairoutputindex"] Index = Literal["dateindex", "decadeindex", "difficultyepoch", "emptyoutputindex", "halvingepoch", "height", "txinindex", "monthindex", "opreturnindex", "txoutindex", "p2aaddressindex", "p2msoutputindex", "p2pk33addressindex", "p2pk65addressindex", "p2pkhaddressindex", "p2shaddressindex", "p2traddressindex", "p2wpkhaddressindex", "p2wshaddressindex", "quarterindex", "semesterindex", "txindex", "unknownoutputindex", "weekindex", "yearindex", "fundedaddressindex", "emptyaddressindex", "pairoutputindex"]
# Hierarchical tree node for organizing metrics into categories # Hierarchical tree node for organizing metrics into categories
TreeNode = Union[dict[str, "TreeNode"], "MetricLeafWithSchema"] TreeNode = Union[dict[str, "TreeNode"], "MetricLeafWithSchema"]
class AddressChainStats(TypedDict): class AddressChainStats(TypedDict):
@@ -455,6 +455,27 @@ class EmptyAddressData(TypedDict):
funded_txo_count: int funded_txo_count: int
transfered: Sats transfered: Sats
class FundedAddressData(TypedDict):
"""
Data for a funded (non-empty) address with current balance
Attributes:
tx_count: Total transaction count
funded_txo_count: Number of transaction outputs funded to this address
spent_txo_count: Number of transaction outputs spent by this address
received: Satoshis received by this address
sent: Satoshis sent by this address
realized_cap_raw: The realized capitalization: Σ(price × sats)
investor_cap_raw: The investor capitalization: Σ(price² × sats)
"""
tx_count: int
funded_txo_count: int
spent_txo_count: int
received: Sats
sent: Sats
realized_cap_raw: CentsSats
investor_cap_raw: CentsSquaredSats
class HashrateEntry(TypedDict): class HashrateEntry(TypedDict):
""" """
A single hashrate data point. A single hashrate data point.
@@ -512,27 +533,6 @@ class IndexInfo(TypedDict):
class LimitParam(TypedDict): class LimitParam(TypedDict):
limit: Limit limit: Limit
class LoadedAddressData(TypedDict):
"""
Data for a loaded (non-empty) address with current balance
Attributes:
tx_count: Total transaction count
funded_txo_count: Number of transaction outputs funded to this address
spent_txo_count: Number of transaction outputs spent by this address
received: Satoshis received by this address
sent: Satoshis sent by this address
realized_cap_raw: The realized capitalization: Σ(price × sats)
investor_cap_raw: The investor capitalization: Σ(price² × sats)
"""
tx_count: int
funded_txo_count: int
spent_txo_count: int
received: Sats
sent: Sats
realized_cap_raw: CentsSats
investor_cap_raw: CentsSquaredSats
class MempoolBlock(TypedDict): class MempoolBlock(TypedDict):
""" """
Block info in a mempool.space like format for fee estimation. Block info in a mempool.space like format for fee estimation.
@@ -1406,7 +1406,7 @@ _i27 = ('txindex',)
_i28 = ('unknownoutputindex',) _i28 = ('unknownoutputindex',)
_i29 = ('weekindex',) _i29 = ('weekindex',)
_i30 = ('yearindex',) _i30 = ('yearindex',)
_i31 = ('loadedaddressindex',) _i31 = ('fundedaddressindex',)
_i32 = ('emptyaddressindex',) _i32 = ('emptyaddressindex',)
def _ep(c: BrkClientBase, n: str, i: Index) -> MetricEndpointBuilder[Any]: def _ep(c: BrkClientBase, n: str, i: Index) -> MetricEndpointBuilder[Any]:
@@ -1805,7 +1805,7 @@ class MetricPattern30(Generic[T]):
class _MetricPattern31By(Generic[T]): class _MetricPattern31By(Generic[T]):
def __init__(self, c: BrkClientBase, n: str): self._c, self._n = c, n def __init__(self, c: BrkClientBase, n: str): self._c, self._n = c, n
def loadedaddressindex(self) -> MetricEndpointBuilder[T]: return _ep(self._c, self._n, 'loadedaddressindex') def fundedaddressindex(self) -> MetricEndpointBuilder[T]: return _ep(self._c, self._n, 'fundedaddressindex')
class MetricPattern31(Generic[T]): class MetricPattern31(Generic[T]):
by: _MetricPattern31By[T] by: _MetricPattern31By[T]
@@ -3905,7 +3905,7 @@ class MetricsTree_Distribution_AddressesData:
"""Metrics tree node.""" """Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''): def __init__(self, client: BrkClientBase, base_path: str = ''):
self.loaded: MetricPattern31[LoadedAddressData] = MetricPattern31(client, 'loadedaddressdata') self.funded: MetricPattern31[FundedAddressData] = MetricPattern31(client, 'fundedaddressdata')
self.empty: MetricPattern32[EmptyAddressData] = MetricPattern32(client, 'emptyaddressdata') self.empty: MetricPattern32[EmptyAddressData] = MetricPattern32(client, 'emptyaddressdata')
class MetricsTree_Distribution_UtxoCohorts_All_Relative: class MetricsTree_Distribution_UtxoCohorts_All_Relative:
@@ -4284,7 +4284,7 @@ class MetricsTree_Distribution:
self.total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, 'total_addr_count') self.total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, 'total_addr_count')
self.new_addr_count: MetricsTree_Distribution_NewAddrCount = MetricsTree_Distribution_NewAddrCount(client) self.new_addr_count: MetricsTree_Distribution_NewAddrCount = MetricsTree_Distribution_NewAddrCount(client)
self.growth_rate: MetricsTree_Distribution_GrowthRate = MetricsTree_Distribution_GrowthRate(client) self.growth_rate: MetricsTree_Distribution_GrowthRate = MetricsTree_Distribution_GrowthRate(client)
self.loadedaddressindex: MetricPattern31[LoadedAddressIndex] = MetricPattern31(client, 'loadedaddressindex') self.fundedaddressindex: MetricPattern31[FundedAddressIndex] = MetricPattern31(client, 'fundedaddressindex')
self.emptyaddressindex: MetricPattern32[EmptyAddressIndex] = MetricPattern32(client, 'emptyaddressindex') self.emptyaddressindex: MetricPattern32[EmptyAddressIndex] = MetricPattern32(client, 'emptyaddressindex')
class MetricsTree_Supply_Circulating: class MetricsTree_Supply_Circulating:
@@ -4370,7 +4370,7 @@ class BrkClient(BrkClientBase):
"unknownoutputindex", "unknownoutputindex",
"weekindex", "weekindex",
"yearindex", "yearindex",
"loadedaddressindex", "fundedaddressindex",
"emptyaddressindex", "emptyaddressindex",
"pairoutputindex" "pairoutputindex"
] ]
+39
View File
@@ -177,6 +177,45 @@ export const colors = {
_2016: red, _2016: red,
_2015: pink, _2015: pink,
}, },
/** Returns/lookback colors by period */
returns: {
_1d: red,
_1w: orange,
_1m: yellow,
_3m: lime,
_6m: green,
_1y: teal,
_2y: cyan,
_3y: sky,
_4y: blue,
_5y: indigo,
_6y: violet,
_8y: purple,
_10y: fuchsia,
},
/** Moving average colors by period */
ma: {
_1w: red,
_8d: orange,
_12d: amber,
_13d: amber,
_21d: yellow,
_26d: lime,
_1m: lime,
_34d: green,
_55d: emerald,
_89d: teal,
_111d: cyan,
_144d: sky,
_200d: blue,
_350d: indigo,
_1y: violet,
_2y: purple,
_200w: fuchsia,
_4y: pink,
},
}; };
/** /**
+319 -249
View File
@@ -1,5 +1,5 @@
import { Unit } from "../utils/units.js"; import { Unit } from "../utils/units.js";
import { line, price } from "./series.js"; import { dots, line, price } from "./series.js";
import { satsBtcUsd, createPriceRatioCharts } from "./shared.js"; import { satsBtcUsd, createPriceRatioCharts } from "./shared.js";
/** /**
@@ -21,160 +21,193 @@ export function createCointimeSection(ctx) {
} = cointime; } = cointime;
const { all } = distribution.utxoCohorts; const { all } = distribution.utxoCohorts;
// Cointime prices data // Reference lines for cap comparisons
const cointimePrices = [ const capReferenceLines = /** @type {const} */ ([
{ metric: supply.marketCap, name: "Market", color: colors.default },
{
metric: all.realized.realizedCap,
name: "Realized",
color: colors.orange,
},
]);
const prices = /** @type {const} */ ([
{ {
pricePattern: pricing.trueMarketMean, pricePattern: pricing.trueMarketMean,
ratio: pricing.trueMarketMeanRatio, ratio: pricing.trueMarketMeanRatio,
name: "True Market Mean", name: "True Market Mean",
title: "True Market Mean",
color: colors.blue, color: colors.blue,
}, },
{ {
pricePattern: pricing.vaultedPrice, pricePattern: pricing.vaultedPrice,
ratio: pricing.vaultedPriceRatio, ratio: pricing.vaultedPriceRatio,
name: "Vaulted", name: "Vaulted",
title: "Vaulted Price",
color: colors.lime, color: colors.lime,
}, },
{ {
pricePattern: pricing.activePrice, pricePattern: pricing.activePrice,
ratio: pricing.activePriceRatio, ratio: pricing.activePriceRatio,
name: "Active", name: "Active",
title: "Active Price",
color: colors.rose, color: colors.rose,
}, },
{ {
pricePattern: pricing.cointimePrice, pricePattern: pricing.cointimePrice,
ratio: pricing.cointimePriceRatio, ratio: pricing.cointimePriceRatio,
name: "Cointime", name: "Cointime",
title: "Cointime Price",
color: colors.yellow, color: colors.yellow,
}, },
]; ]);
// Cointime capitalizations data const caps = /** @type {const} */ ([
const cointimeCapitalizations = [ { metric: cap.vaultedCap, name: "Vaulted", color: colors.lime },
{ metric: cap.activeCap, name: "Active", color: colors.rose },
{ metric: cap.cointimeCap, name: "Cointime", color: colors.yellow },
{ metric: cap.investorCap, name: "Investor", color: colors.fuchsia },
{ metric: cap.thermoCap, name: "Thermo", color: colors.emerald },
]);
const supplyBreakdown = /** @type {const} */ ([
{ pattern: all.supply.total, name: "Total", color: colors.orange },
{ {
metric: cap.vaultedCap, pattern: cointimeSupply.vaultedSupply,
name: "Vaulted", name: "Vaulted",
title: "Vaulted Cap",
color: colors.lime, color: colors.lime,
}, },
{ {
metric: cap.activeCap, pattern: cointimeSupply.activeSupply,
name: "Active", name: "Active",
title: "Active Cap",
color: colors.rose, color: colors.rose,
}, },
]);
const coinblocks = /** @type {const} */ ([
{ {
metric: cap.cointimeCap, pattern: all.activity.coinblocksDestroyed,
name: "Cointime", name: "Destroyed",
title: "Cointime Cap", title: "Coinblocks Destroyed",
color: colors.yellow, color: colors.red,
}, },
{ {
metric: cap.investorCap, pattern: activity.coinblocksCreated,
name: "Investor", name: "Created",
title: "Investor Cap", title: "Coinblocks Created",
color: colors.fuchsia, color: colors.orange,
}, },
{ {
metric: cap.thermoCap, pattern: activity.coinblocksStored,
name: "Thermo", name: "Stored",
title: "Thermo Cap", title: "Coinblocks Stored",
color: colors.emerald, color: colors.green,
}, },
]; ]);
// Colors aligned with coinblocks: Destroyed=red, Created=orange, Stored=green
const cointimeValues = /** @type {const} */ ([
{
pattern: value.cointimeValueCreated,
name: "Created",
title: "Cointime Value Created",
color: colors.orange,
},
{
pattern: value.cointimeValueDestroyed,
name: "Destroyed",
title: "Cointime Value Destroyed",
color: colors.red,
},
{
pattern: value.cointimeValueStored,
name: "Stored",
title: "Cointime Value Stored",
color: colors.green,
},
]);
const vocdd = /** @type {const} */ ({
pattern: value.vocdd,
name: "VOCDD",
title: "Value of Coin Days Destroyed",
color: colors.purple,
});
return { return {
name: "Cointime", name: "Cointime",
tree: [ tree: [
// Prices // Prices - the core pricing models
{ {
name: "Prices", name: "Prices",
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: "Cointime Prices", title: "Cointime Prices",
top: cointimePrices.map(({ pricePattern, name, color }) => top: [
price({ metric: pricePattern, name, color }), price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange }),
), price({ metric: all.realized.investorPrice, name: "Investor", color: colors.fuchsia }),
...prices.map(({ pricePattern, name, color }) =>
price({ metric: pricePattern, name, color }),
),
],
}, },
...cointimePrices.map(({ pricePattern, ratio, name, color, title }) => ({ ...prices.map(({ pricePattern, ratio, name, color }) => ({
name, name,
tree: createPriceRatioCharts(ctx, { tree: createPriceRatioCharts(ctx, {
context: title, context: `${name} Price`,
legend: name, legend: name,
pricePattern, pricePattern,
ratio, ratio,
color, color,
priceReferences: [price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange, defaultActive: false })],
}), }),
})), })),
], ],
}, },
// Capitalization // Caps - market capitalizations from different models
{ {
name: "Capitalization", name: "Caps",
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: "Cointime Caps", title: "Cointime Caps",
bottom: [ bottom: [
line({ ...capReferenceLines.map(({ metric, name, color }) =>
metric: supply.marketCap, line({ metric, name, color, unit: Unit.usd }),
name: "Market", ),
color: colors.default, ...caps.map(({ metric, name, color }) =>
unit: Unit.usd,
}),
line({
metric: all.realized.realizedCap,
name: "Realized",
color: colors.orange,
unit: Unit.usd,
}),
...cointimeCapitalizations.map(({ metric, name, color }) =>
line({ metric, name, color, unit: Unit.usd }), line({ metric, name, color, unit: Unit.usd }),
), ),
], ],
}, },
...cointimeCapitalizations.map(({ metric, name, color, title }) => ({ ...caps.map(({ metric, name, color }) => ({
name, name,
title, title: `${name} Cap`,
bottom: [ bottom: [
line({ metric, name, color, unit: Unit.usd }), line({ metric, name, color, unit: Unit.usd }),
line({ ...capReferenceLines.map((ref) =>
metric: supply.marketCap, line({
name: "Market", metric: ref.metric,
color: colors.default, name: ref.name,
unit: Unit.usd, color: ref.color,
}), unit: Unit.usd,
line({ }),
metric: all.realized.realizedCap, ),
name: "Realized",
color: colors.orange,
unit: Unit.usd,
}),
], ],
})), })),
], ],
}, },
// Supply // Supply - active vs vaulted breakdown
{ {
name: "Supply", name: "Supply",
title: "Cointime Supply", title: "Active vs Vaulted Supply",
bottom: [ bottom: supplyBreakdown.flatMap(({ pattern, name, color }) =>
...satsBtcUsd({ pattern: all.supply.total, name: "All", color: colors.orange }), satsBtcUsd({ pattern, name, color }),
...satsBtcUsd({ pattern: cointimeSupply.vaultedSupply, name: "Vaulted", color: colors.lime }), ),
...satsBtcUsd({ pattern: cointimeSupply.activeSupply, name: "Active", color: colors.rose }),
],
}, },
// Liveliness & Vaultedness // Liveliness - the foundational cointime ratios
{ {
name: "Liveliness & Vaultedness", name: "Activity",
title: "Liveliness & Vaultedness", title: "Liveliness & Vaultedness",
bottom: [ bottom: [
line({ line({
@@ -191,73 +224,196 @@ export function createCointimeSection(ctx) {
}), }),
line({ line({
metric: activity.activityToVaultednessRatio, metric: activity.activityToVaultednessRatio,
name: "Liveliness / Vaultedness", name: "L/V Ratio",
color: colors.purple, color: colors.purple,
unit: Unit.ratio, unit: Unit.ratio,
defaultActive: false,
}), }),
], ],
}, },
// Coinblocks // Coinblocks - created, destroyed, stored
{ {
name: "Coinblocks", name: "Coinblocks",
title: "Coinblocks", tree: [
bottom: [ {
// Destroyed comes from the all cohort's activity name: "Compare",
line({ tree: [
metric: all.activity.coinblocksDestroyed.sum, {
name: "Destroyed", name: "Sum",
color: colors.red, title: "Coinblocks",
unit: Unit.coinblocks, bottom: coinblocks.map(({ pattern, name, color }) =>
}), line({
line({ metric: pattern.sum,
metric: all.activity.coinblocksDestroyed.cumulative, name,
name: "Cumulative Destroyed", color,
color: colors.red, unit: Unit.coinblocks,
defaultActive: false, }),
unit: Unit.coinblocks, ),
}), },
// Created and stored from cointime {
line({ name: "Cumulative",
metric: activity.coinblocksCreated.sum, title: "Coinblocks (Total)",
name: "Created", bottom: coinblocks.map(({ pattern, name, color }) =>
color: colors.orange, line({
unit: Unit.coinblocks, metric: pattern.cumulative,
}), name,
line({ color,
metric: activity.coinblocksCreated.cumulative, unit: Unit.coinblocks,
name: "Cumulative Created", }),
color: colors.orange, ),
defaultActive: false, },
unit: Unit.coinblocks, ],
}), },
line({ ...coinblocks.map(({ pattern, name, title, color }) => ({
metric: activity.coinblocksStored.sum, name,
name: "Stored", tree: [
color: colors.green, {
unit: Unit.coinblocks, name: "Sum",
}), title,
line({ bottom: [
metric: activity.coinblocksStored.cumulative, line({
name: "Cumulative Stored", metric: pattern.sum,
color: colors.green, name,
defaultActive: false, color,
unit: Unit.coinblocks, unit: Unit.coinblocks,
}), }),
],
},
{
name: "Cumulative",
title: `${title} (Total)`,
bottom: [
line({
metric: pattern.cumulative,
name,
color,
unit: Unit.coinblocks,
}),
],
},
],
})),
], ],
}, },
// Reserve Risk // Value - cointime value flows
{ {
name: "Reserve Risk", name: "Value",
tree: [ tree: [
{ {
name: "Ratio", name: "Compare",
tree: [
{
name: "Sum",
title: "Cointime Value",
bottom: [
...cointimeValues.map(({ pattern, name, color }) =>
line({ metric: pattern.sum, name, color, unit: Unit.usd }),
),
line({
metric: vocdd.pattern.sum,
name: vocdd.name,
color: vocdd.color,
unit: Unit.usd,
}),
],
},
{
name: "Cumulative",
title: "Cointime Value (Total)",
bottom: [
...cointimeValues.map(({ pattern, name, color }) =>
line({
metric: pattern.cumulative,
name,
color,
unit: Unit.usd,
}),
),
line({
metric: vocdd.pattern.cumulative,
name: vocdd.name,
color: vocdd.color,
unit: Unit.usd,
}),
],
},
],
},
...cointimeValues.map(({ pattern, name, title, color }) => ({
name,
tree: [
{
name: "Sum",
title,
bottom: [
line({ metric: pattern.sum, name, color, unit: Unit.usd }),
],
},
{
name: "Cumulative",
title: `${title} (Total)`,
bottom: [
line({
metric: pattern.cumulative,
name,
color,
unit: Unit.usd,
}),
],
},
],
})),
{
name: vocdd.name,
tree: [
{
name: "Sum",
title: vocdd.title,
bottom: [
line({
metric: vocdd.pattern.sum,
name: vocdd.name,
color: vocdd.color,
unit: Unit.usd,
}),
line({
metric: reserveRisk.vocdd365dSma,
name: "365d SMA",
color: colors.cyan,
unit: Unit.usd,
}),
],
},
{
name: "Cumulative",
title: `${vocdd.title} (Total)`,
bottom: [
line({
metric: vocdd.pattern.cumulative,
name: vocdd.name,
color: vocdd.color,
unit: Unit.usd,
}),
],
},
],
},
],
},
// Indicators - derived decision metrics
{
name: "Indicators",
tree: [
{
name: "Reserve Risk",
title: "Reserve Risk", title: "Reserve Risk",
bottom: [ bottom: [
line({ line({
metric: reserveRisk.reserveRisk, metric: reserveRisk.reserveRisk,
name: "Reserve Risk", name: "Ratio",
color: colors.orange, color: colors.orange,
unit: Unit.ratio, unit: Unit.ratio,
}), }),
@@ -269,162 +425,76 @@ export function createCointimeSection(ctx) {
bottom: [ bottom: [
line({ line({
metric: reserveRisk.hodlBank, metric: reserveRisk.hodlBank,
name: "HODL Bank", name: "Value",
color: colors.blue, color: colors.blue,
unit: Unit.ratio, unit: Unit.usd,
}),
],
},
{
name: "VOCDD 365d SMA",
title: "VOCDD 365d SMA",
bottom: [
line({
metric: reserveRisk.vocdd365dSma,
name: "VOCDD 365d SMA",
color: colors.purple,
unit: Unit.ratio,
}), }),
], ],
}, },
], ],
}, },
// Cointime Value // Cointime-Adjusted - comparing base vs adjusted metrics
{ {
name: "Value", name: "Cointime-Adjusted",
tree: [ tree: [
{
name: "Created",
title: "Cointime Value Created",
bottom: [
line({
metric: value.cointimeValueCreated.sum,
name: "Created",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: value.cointimeValueCreated.cumulative,
name: "Cumulative",
color: colors.green,
unit: Unit.usd,
defaultActive: false,
}),
],
},
{
name: "Destroyed",
title: "Cointime Value Destroyed",
bottom: [
line({
metric: value.cointimeValueDestroyed.sum,
name: "Destroyed",
color: colors.red,
unit: Unit.usd,
}),
line({
metric: value.cointimeValueDestroyed.cumulative,
name: "Cumulative",
color: colors.red,
unit: Unit.usd,
defaultActive: false,
}),
],
},
{
name: "Stored",
title: "Cointime Value Stored",
bottom: [
line({
metric: value.cointimeValueStored.sum,
name: "Stored",
color: colors.blue,
unit: Unit.usd,
}),
line({
metric: value.cointimeValueStored.cumulative,
name: "Cumulative",
color: colors.blue,
unit: Unit.usd,
defaultActive: false,
}),
],
},
{
name: "VOCDD",
title: "VOCDD (Value of Coin Days Destroyed)",
bottom: [
line({
metric: value.vocdd.sum,
name: "VOCDD",
color: colors.orange,
unit: Unit.usd,
}),
line({
metric: value.vocdd.cumulative,
name: "Cumulative",
color: colors.orange,
unit: Unit.usd,
defaultActive: false,
}),
],
},
],
},
// Adjusted metrics
{
name: "Adjusted",
tree: [
// Inflation
{ {
name: "Inflation", name: "Inflation",
title: "Adjusted Inflation", title: "Cointime-Adjusted Inflation",
bottom: [ bottom: [
line({ dots({
metric: supply.inflation, metric: supply.inflation,
name: "Base", name: "Base",
color: colors.orange, color: colors.orange,
unit: Unit.percentage, unit: Unit.percentage,
}), }),
line({ dots({
metric: adjusted.cointimeAdjInflationRate, metric: adjusted.cointimeAdjInflationRate,
name: "Adjusted", name: "Cointime-Adjusted",
color: colors.purple, color: colors.purple,
unit: Unit.percentage, unit: Unit.percentage,
}), }),
], ],
}, },
// Velocity
{ {
name: "Velocity", name: "Velocity",
title: "Adjusted Velocity", tree: [
bottom: [ {
line({
metric: supply.velocity.btc,
name: "BTC", name: "BTC",
color: colors.orange, title: "Cointime-Adjusted BTC Velocity",
unit: Unit.ratio, bottom: [
}), line({
line({ metric: supply.velocity.btc,
metric: adjusted.cointimeAdjTxBtcVelocity, name: "Base",
name: "Adj. BTC", color: colors.orange,
color: colors.red, unit: Unit.ratio,
unit: Unit.ratio, }),
}), line({
line({ metric: adjusted.cointimeAdjTxBtcVelocity,
metric: supply.velocity.usd, name: "Cointime-Adjusted",
color: colors.red,
unit: Unit.ratio,
}),
],
},
{
name: "USD", name: "USD",
color: colors.emerald, title: "Cointime-Adjusted USD Velocity",
unit: Unit.ratio, bottom: [
}), line({
line({ metric: supply.velocity.usd,
metric: adjusted.cointimeAdjTxUsdVelocity, name: "Base",
name: "Adj. USD", color: colors.emerald,
color: colors.lime, unit: Unit.ratio,
unit: Unit.ratio, }),
}), line({
metric: adjusted.cointimeAdjTxUsdVelocity,
name: "Cointime-Adjusted",
color: colors.lime,
unit: Unit.ratio,
}),
],
},
], ],
}, },
], ],
+251 -50
View File
@@ -5,8 +5,22 @@ import { line, baseline, price, dotted } from "./series.js";
import { satsBtcUsd } from "./shared.js"; import { satsBtcUsd } from "./shared.js";
import { periodIdToName } from "./utils.js"; import { periodIdToName } from "./utils.js";
const SHORT_PERIODS = /** @type {const} */ (["_1w", "_1m", "_3m", "_6m", "_1y"]); const SHORT_PERIODS = /** @type {const} */ ([
const LONG_PERIODS = /** @type {const} */ (["_2y", "_3y", "_4y", "_5y", "_6y", "_8y", "_10y"]); "_1w",
"_1m",
"_3m",
"_6m",
"_1y",
]);
const LONG_PERIODS = /** @type {const} */ ([
"_2y",
"_3y",
"_4y",
"_5y",
"_6y",
"_8y",
"_10y",
]);
/** @typedef {typeof SHORT_PERIODS[number]} ShortPeriodKey */ /** @typedef {typeof SHORT_PERIODS[number]} ShortPeriodKey */
/** @typedef {typeof LONG_PERIODS[number]} LongPeriodKey */ /** @typedef {typeof LONG_PERIODS[number]} LongPeriodKey */
@@ -20,7 +34,9 @@ const LONG_PERIODS = /** @type {const} */ (["_2y", "_3y", "_4y", "_5y", "_6y", "
*/ */
const withCagr = (entry, cagr) => ({ ...entry, cagr }); const withCagr = (entry, cagr) => ({ ...entry, cagr });
const YEARS_2020S = /** @type {const} */ ([2026, 2025, 2024, 2023, 2022, 2021, 2020]); const YEARS_2020S = /** @type {const} */ ([
2026, 2025, 2024, 2023, 2022, 2021, 2020,
]);
const YEARS_2010S = /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]); const YEARS_2010S = /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]);
/** @param {AllPeriodKey} key */ /** @param {AllPeriodKey} key */
@@ -142,7 +158,12 @@ function createCompareFolder(context, items) {
title: `Returns: ${context}`, title: `Returns: ${context}`,
top: topPane, top: topPane,
bottom: items.map(({ name, color, returns }) => bottom: items.map(({ name, color, returns }) =>
baseline({ metric: returns, name, color: [color, color], unit: Unit.percentage }), baseline({
metric: returns,
name,
color: [color, color],
unit: Unit.percentage,
}),
), ),
}, },
createProfitabilityFolder(context, items), createProfitabilityFolder(context, items),
@@ -165,23 +186,51 @@ function createCompareFolder(context, items) {
* @param {object[]} returnsBottom - Bottom pane items for returns chart * @param {object[]} returnsBottom - Bottom pane items for returns chart
*/ */
function createSingleEntryTree(colors, item, returnsBottom) { function createSingleEntryTree(colors, item, returnsBottom) {
const { name, titlePrefix = name, color, costBasis, daysInProfit, daysInLoss, stack } = item; const {
name,
titlePrefix = name,
color,
costBasis,
daysInProfit,
daysInLoss,
stack,
} = item;
const top = [price({ metric: costBasis, name: "Cost Basis", color })]; const top = [price({ metric: costBasis, name: "Cost Basis", color })];
return { return {
name, name,
tree: [ tree: [
{ name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top }, { name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top },
{ name: "Returns", title: `Returns: ${titlePrefix}`, top, bottom: returnsBottom }, {
name: "Returns",
title: `Returns: ${titlePrefix}`,
top,
bottom: returnsBottom,
},
{ {
name: "Profitability", name: "Profitability",
title: `Profitability: ${titlePrefix}`, title: `Profitability: ${titlePrefix}`,
top, top,
bottom: [ bottom: [
line({ metric: daysInProfit, name: "Days in Profit", color: colors.green, unit: Unit.days }), line({
line({ metric: daysInLoss, name: "Days in Loss", color: colors.red, unit: Unit.days }), metric: daysInProfit,
name: "Days in Profit",
color: colors.green,
unit: Unit.days,
}),
line({
metric: daysInLoss,
name: "Days in Loss",
color: colors.red,
unit: Unit.days,
}),
], ],
}, },
{ name: "Accumulated", title: `Accumulated Value: ${titlePrefix}`, top, bottom: satsBtcUsd({ pattern: stack, name: "Value" }) }, {
name: "Accumulated",
title: `Accumulated Value: ${titlePrefix}`,
top,
bottom: satsBtcUsd({ pattern: stack, name: "Value" }),
},
], ],
}; };
} }
@@ -195,8 +244,20 @@ function createShortSingleEntry(colors, item) {
const { returns, minReturn, maxReturn } = item; const { returns, minReturn, maxReturn } = item;
return createSingleEntryTree(colors, item, [ return createSingleEntryTree(colors, item, [
baseline({ metric: returns, name: "Current", unit: Unit.percentage }), baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
dotted({ metric: maxReturn, name: "Max", color: colors.green, unit: Unit.percentage, defaultActive: false }), dotted({
dotted({ metric: minReturn, name: "Min", color: colors.red, unit: Unit.percentage, defaultActive: false }), metric: maxReturn,
name: "Max",
color: colors.green,
unit: Unit.percentage,
defaultActive: false,
}),
dotted({
metric: minReturn,
name: "Min",
color: colors.red,
unit: Unit.percentage,
defaultActive: false,
}),
]); ]);
} }
@@ -210,8 +271,20 @@ function createLongSingleEntry(colors, item) {
return createSingleEntryTree(colors, item, [ return createSingleEntryTree(colors, item, [
baseline({ metric: returns, name: "Current", unit: Unit.percentage }), baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
baseline({ metric: cagr, name: "CAGR", unit: Unit.cagr }), baseline({ metric: cagr, name: "CAGR", unit: Unit.cagr }),
dotted({ metric: maxReturn, name: "Max", color: colors.green, unit: Unit.percentage, defaultActive: false }), dotted({
dotted({ metric: minReturn, name: "Min", color: colors.red, unit: Unit.percentage, defaultActive: false }), metric: maxReturn,
name: "Max",
color: colors.green,
unit: Unit.percentage,
defaultActive: false,
}),
dotted({
metric: minReturn,
name: "Min",
color: colors.red,
unit: Unit.percentage,
defaultActive: false,
}),
]); ]);
} }
@@ -228,7 +301,11 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
/** @param {AllPeriodKey} key */ /** @param {AllPeriodKey} key */
const topPane = (key) => [ const topPane = (key) => [
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }), price({
metric: dca.periodAveragePrice[key],
name: "DCA",
color: colors.green,
}),
price({ metric: lookback[key], name: "Lump Sum", color: colors.orange }), price({ metric: lookback[key], name: "Lump Sum", color: colors.orange }),
]; ];
@@ -246,8 +323,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Max Return: ${name} DCA vs Lump Sum`, title: `Max Return: ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
baseline({ metric: dca.periodMaxReturn[key], name: "DCA", unit: Unit.percentage }), baseline({
baseline({ metric: dca.periodLumpSumMaxReturn[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), metric: dca.periodMaxReturn[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumMaxReturn[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
], ],
}, },
{ {
@@ -255,8 +341,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Min Return: ${name} DCA vs Lump Sum`, title: `Min Return: ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
baseline({ metric: dca.periodMinReturn[key], name: "DCA", unit: Unit.percentage }), baseline({
baseline({ metric: dca.periodLumpSumMinReturn[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), metric: dca.periodMinReturn[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumMinReturn[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
], ],
}, },
]; ];
@@ -270,8 +365,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Returns: ${name} DCA vs Lump Sum`, title: `Returns: ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }), baseline({
baseline({ metric: dca.periodLumpSumReturns[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), metric: dca.periodReturns[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
], ],
}, },
...returnsMinMax(name, key), ...returnsMinMax(name, key),
@@ -287,10 +391,28 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Returns: ${name} DCA vs Lump Sum`, title: `Returns: ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }), baseline({
baseline({ metric: dca.periodLumpSumReturns[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), metric: dca.periodReturns[key],
baseline({ metric: dca.periodCagr[key], name: "DCA CAGR", unit: Unit.cagr }), name: "DCA",
baseline({ metric: returns.cagr[key], name: "Lump Sum CAGR", color: [colors.cyan, colors.orange], unit: Unit.cagr }), unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
baseline({
metric: dca.periodCagr[key],
name: "DCA",
unit: Unit.cagr,
}),
baseline({
metric: returns.cagr[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
unit: Unit.cagr,
}),
], ],
}, },
...returnsMinMax(name, key), ...returnsMinMax(name, key),
@@ -306,8 +428,18 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Days in Profit: ${name} DCA vs Lump Sum`, title: `Days in Profit: ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
line({ metric: dca.periodDaysInProfit[key], name: "DCA", color: colors.green, unit: Unit.days }), line({
line({ metric: dca.periodLumpSumDaysInProfit[key], name: "Lump Sum", color: colors.orange, unit: Unit.days }), metric: dca.periodDaysInProfit[key],
name: "DCA",
color: colors.green,
unit: Unit.days,
}),
line({
metric: dca.periodLumpSumDaysInProfit[key],
name: "Lump Sum",
color: colors.orange,
unit: Unit.days,
}),
], ],
}, },
{ {
@@ -315,8 +447,18 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Days in Loss: ${name} DCA vs Lump Sum`, title: `Days in Loss: ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.green, unit: Unit.days }), line({
line({ metric: dca.periodLumpSumDaysInLoss[key], name: "Lump Sum", color: colors.orange, unit: Unit.days }), metric: dca.periodDaysInLoss[key],
name: "DCA",
color: colors.green,
unit: Unit.days,
}),
line({
metric: dca.periodLumpSumDaysInLoss[key],
name: "Lump Sum",
color: colors.orange,
unit: Unit.days,
}),
], ],
}, },
], ],
@@ -328,8 +470,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
title: `Accumulated Value ($100/day): ${name} DCA vs Lump Sum`, title: `Accumulated Value ($100/day): ${name} DCA vs Lump Sum`,
top: topPane(key), top: topPane(key),
bottom: [ bottom: [
...satsBtcUsd({ pattern: dca.periodStack[key], name: "DCA", color: colors.green }), ...satsBtcUsd({
...satsBtcUsd({ pattern: dca.periodLumpSumStack[key], name: "Lump Sum", color: colors.orange }), pattern: dca.periodStack[key],
name: "DCA",
color: colors.green,
}),
...satsBtcUsd({
pattern: dca.periodLumpSumStack[key],
name: "Lump Sum",
color: colors.orange,
}),
], ],
}); });
@@ -338,7 +488,12 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
const name = periodName(key); const name = periodName(key);
return { return {
name, name,
tree: [costBasisChart(name, key), shortReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)], tree: [
costBasisChart(name, key),
shortReturnsFolder(name, key),
profitabilityFolder(name, key),
stackChart(name, key),
],
}; };
}; };
@@ -347,7 +502,12 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
const name = periodName(key); const name = periodName(key);
return { return {
name, name,
tree: [costBasisChart(name, key), longReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)], tree: [
costBasisChart(name, key),
longReturnsFolder(name, key),
profitabilityFolder(name, key),
stackChart(name, key),
],
}; };
}; };
@@ -355,8 +515,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
name: "DCA vs Lump Sum", name: "DCA vs Lump Sum",
title: "Compare Investment Strategies", title: "Compare Investment Strategies",
tree: [ tree: [
{ name: "Short Term", title: "Up to 1 Year", tree: SHORT_PERIODS.map(createShortPeriodEntry) }, {
{ name: "Long Term", title: "2+ Years", tree: LONG_PERIODS.map(createLongPeriodEntry) }, name: "Short Term",
title: "Up to 1 Year",
tree: SHORT_PERIODS.map(createShortPeriodEntry),
},
{
name: "Long Term",
title: "2+ Years",
tree: LONG_PERIODS.map(createLongPeriodEntry),
},
], ],
}; };
} }
@@ -380,26 +548,41 @@ function createPeriodSection(ctx, { dca, lookback, returns }) {
color: colors.dcaPeriods[key], color: colors.dcaPeriods[key],
costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key], costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key],
returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key], returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key],
minReturn: isLumpSum ? dca.periodLumpSumMinReturn[key] : dca.periodMinReturn[key], minReturn: isLumpSum
maxReturn: isLumpSum ? dca.periodLumpSumMaxReturn[key] : dca.periodMaxReturn[key], ? dca.periodLumpSumMinReturn[key]
daysInProfit: isLumpSum ? dca.periodLumpSumDaysInProfit[key] : dca.periodDaysInProfit[key], : dca.periodMinReturn[key],
daysInLoss: isLumpSum ? dca.periodLumpSumDaysInLoss[key] : dca.periodDaysInLoss[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], stack: isLumpSum ? dca.periodLumpSumStack[key] : dca.periodStack[key],
}); });
/** @param {LongPeriodKey} key @returns {LongEntryItem} */ /** @param {LongPeriodKey} key @returns {LongEntryItem} */
const buildLongEntry = (key) => withCagr( const buildLongEntry = (key) =>
buildBaseEntry(key), withCagr(
isLumpSum ? returns.cagr[key] : dca.periodCagr[key], buildBaseEntry(key),
); isLumpSum ? returns.cagr[key] : dca.periodCagr[key],
);
/** @param {BaseEntryItem} entry */ /** @param {BaseEntryItem} entry */
const createShortEntry = (entry) => const createShortEntry = (entry) =>
createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` }); createShortSingleEntry(colors, {
...entry,
titlePrefix: `${entry.name} ${suffix}`,
});
/** @param {LongEntryItem} entry */ /** @param {LongEntryItem} entry */
const createLongEntry = (entry) => const createLongEntry = (entry) =>
createLongSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` }); createLongSingleEntry(colors, {
...entry,
titlePrefix: `${entry.name} ${suffix}`,
});
const shortEntries = SHORT_PERIODS.map(buildBaseEntry); const shortEntries = SHORT_PERIODS.map(buildBaseEntry);
const longEntries = LONG_PERIODS.map(buildLongEntry); const longEntries = LONG_PERIODS.map(buildLongEntry);
@@ -408,16 +591,25 @@ function createPeriodSection(ctx, { dca, lookback, returns }) {
name: `${suffix} by Period`, name: `${suffix} by Period`,
title: `${suffix} Performance by Investment Period`, title: `${suffix} Performance by Investment Period`,
tree: [ tree: [
createCompareFolder(`All Periods ${suffix}`, [...shortEntries, ...longEntries]), createCompareFolder(`All Periods ${suffix}`, [
...shortEntries,
...longEntries,
]),
{ {
name: "Short Term", name: "Short Term",
title: "Up to 1 Year", title: "Up to 1 Year",
tree: [createCompareFolder(`Short Term ${suffix}`, shortEntries), ...shortEntries.map(createShortEntry)], tree: [
createCompareFolder(`Short Term ${suffix}`, shortEntries),
...shortEntries.map(createShortEntry),
],
}, },
{ {
name: "Long Term", name: "Long Term",
title: "2+ Years", title: "2+ Years",
tree: [createCompareFolder(`Long Term ${suffix}`, longEntries), ...longEntries.map(createLongEntry)], tree: [
createCompareFolder(`Long Term ${suffix}`, longEntries),
...longEntries.map(createLongEntry),
],
}, },
], ],
}; };
@@ -461,12 +653,21 @@ export function createDcaByStartYearSection(ctx, { dca }) {
title, title,
tree: [ tree: [
createCompareFolder(`${name} DCA`, entries), createCompareFolder(`${name} DCA`, entries),
...entries.map((entry) => createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} DCA` })), ...entries.map((entry) =>
createShortSingleEntry(colors, {
...entry,
titlePrefix: `${entry.name} DCA`,
}),
),
], ],
}); });
const entries2020s = YEARS_2020S.map((year) => buildYearEntry(colors, dca, year)); const entries2020s = YEARS_2020S.map((year) =>
const entries2010s = YEARS_2010S.map((year) => buildYearEntry(colors, dca, year)); buildYearEntry(colors, dca, year),
);
const entries2010s = YEARS_2010S.map((year) =>
buildYearEntry(colors, dca, year),
);
return { return {
name: "DCA by Start Year", name: "DCA by Start Year",
+481
View File
@@ -0,0 +1,481 @@
/** Market section */
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";
/**
* @typedef {Object} Period
* @property {string} id
* @property {Color} color
* @property {AnyMetricPattern} returns
* @property {AnyPricePattern} lookback
* @property {boolean} [defaultActive]
*/
/**
* @typedef {Period & { cagr: AnyMetricPattern }} PeriodWithCagr
*/
/**
* @typedef {Object} MaPeriod
* @property {string} id
* @property {Color} color
* @property {ActivePriceRatioPattern} ratio
*/
const commonMaIds = /** @type {const} */ (["1w", "1m", "200d", "1y", "200w", "4y"]);
/**
* @param {PartialContext} ctx
* @param {string} label
* @param {MaPeriod[]} averages
*/
function createMaSubSection(ctx, label, averages) {
const common = averages.filter((a) => includes(commonMaIds, a.id));
const more = averages.filter((a) => !includes(commonMaIds, a.id));
/** @param {MaPeriod} a */
const toFolder = (a) => ({
name: periodIdToName(a.id, true),
tree: createPriceRatioCharts(ctx, {
context: `${periodIdToName(a.id, true)} ${label}`,
legend: "average",
pricePattern: a.ratio.price,
ratio: a.ratio,
color: a.color,
}),
});
return {
name: label,
tree: [
{
name: "Compare",
title: `Price ${label}s`,
top: averages.map((a) => price({ metric: a.ratio.price, name: a.id, color: a.color })),
},
...common.map(toFolder),
{ name: "More...", tree: more.map(toFolder) },
],
};
}
/**
* @param {Colors} colors
* @param {string} name
* @param {string} title
* @param {Unit} unit
* @param {{ _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }} metrics
*/
function volatilityChart(colors, name, title, unit, metrics) {
return {
name,
title,
bottom: [
line({ metric: metrics._1w, name: "1w", color: colors.red, unit }),
line({ metric: metrics._1m, name: "1m", color: colors.orange, unit }),
line({ metric: metrics._1y, name: "1y", color: colors.lime, unit }),
],
};
}
/**
* @param {string} name
* @param {Period[]} periods
*/
function returnsSubSection(name, periods) {
return {
name,
tree: [
{
name: "Compare",
title: `${name} Returns`,
bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
title: `${periodIdToName(p.id, true)} Returns`,
bottom: [baseline({ metric: p.returns, name: "Total", unit: Unit.percentage })],
})),
],
};
}
/**
* @param {string} name
* @param {PeriodWithCagr[]} periods
*/
function returnsSubSectionWithCagr(name, periods) {
return {
name,
tree: [
{
name: "Compare",
title: `${name} Returns`,
bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
title: `${periodIdToName(p.id, true)} Returns`,
bottom: [
baseline({ metric: p.returns, name: "Total", unit: Unit.percentage }),
baseline({ metric: p.cagr, name: "annual", unit: Unit.cagr }),
],
})),
],
};
}
/**
* @param {string} name
* @param {Period[]} periods
*/
function historicalSubSection(name, periods) {
return {
name,
tree: [
{
name: "Compare",
title: `${name} Historical`,
top: periods.map((p) => price({ metric: p.lookback, name: p.id, color: p.color })),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
title: `${periodIdToName(p.id, true)} Ago`,
top: [price({ metric: p.lookback, name: "Price" })],
})),
],
};
}
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk } = ctx;
const { market, supply, price: priceMetrics } = brk.metrics;
const { movingAverage: ma, ath, returns, volatility, range, indicators, lookback } = market;
/** @type {Period[]} */
const shortPeriods = [
{ id: "1d", color: colors.returns._1d, returns: returns.priceReturns._1d, lookback: lookback._1d },
{ id: "1w", color: colors.returns._1w, returns: returns.priceReturns._1w, lookback: lookback._1w },
{ id: "1m", color: colors.returns._1m, returns: returns.priceReturns._1m, lookback: lookback._1m },
{ id: "3m", color: colors.returns._3m, returns: returns.priceReturns._3m, lookback: lookback._3m, defaultActive: false },
{ id: "6m", color: colors.returns._6m, returns: returns.priceReturns._6m, lookback: lookback._6m, defaultActive: false },
{ id: "1y", color: colors.returns._1y, returns: returns.priceReturns._1y, lookback: lookback._1y },
];
/** @type {PeriodWithCagr[]} */
const longPeriods = [
{ id: "2y", color: colors.returns._2y, returns: returns.priceReturns._2y, cagr: returns.cagr._2y, lookback: lookback._2y, defaultActive: false },
{ id: "3y", color: colors.returns._3y, returns: returns.priceReturns._3y, cagr: returns.cagr._3y, lookback: lookback._3y, defaultActive: false },
{ id: "4y", color: colors.returns._4y, returns: returns.priceReturns._4y, cagr: returns.cagr._4y, lookback: lookback._4y },
{ id: "5y", color: colors.returns._5y, returns: returns.priceReturns._5y, cagr: returns.cagr._5y, lookback: lookback._5y, defaultActive: false },
{ id: "6y", color: colors.returns._6y, returns: returns.priceReturns._6y, cagr: returns.cagr._6y, lookback: lookback._6y, defaultActive: false },
{ id: "8y", color: colors.returns._8y, returns: returns.priceReturns._8y, cagr: returns.cagr._8y, lookback: lookback._8y, defaultActive: false },
{ id: "10y", color: colors.returns._10y, returns: returns.priceReturns._10y, cagr: returns.cagr._10y, lookback: lookback._10y, defaultActive: false },
];
/** @type {MaPeriod[]} */
const sma = [
{ id: "1w", color: colors.ma._1w, ratio: ma.price1wSma },
{ id: "8d", color: colors.ma._8d, ratio: ma.price8dSma },
{ id: "13d", color: colors.ma._13d, ratio: ma.price13dSma },
{ id: "21d", color: colors.ma._21d, ratio: ma.price21dSma },
{ id: "1m", color: colors.ma._1m, ratio: ma.price1mSma },
{ id: "34d", color: colors.ma._34d, ratio: ma.price34dSma },
{ id: "55d", color: colors.ma._55d, ratio: ma.price55dSma },
{ id: "89d", color: colors.ma._89d, ratio: ma.price89dSma },
{ id: "111d", color: colors.ma._111d, ratio: ma.price111dSma },
{ id: "144d", color: colors.ma._144d, ratio: ma.price144dSma },
{ id: "200d", color: colors.ma._200d, ratio: ma.price200dSma },
{ id: "350d", color: colors.ma._350d, ratio: ma.price350dSma },
{ id: "1y", color: colors.ma._1y, ratio: ma.price1ySma },
{ id: "2y", color: colors.ma._2y, ratio: ma.price2ySma },
{ id: "200w", color: colors.ma._200w, ratio: ma.price200wSma },
{ id: "4y", color: colors.ma._4y, ratio: ma.price4ySma },
];
/** @type {MaPeriod[]} */
const ema = [
{ id: "1w", color: colors.ma._1w, ratio: ma.price1wEma },
{ id: "8d", color: colors.ma._8d, ratio: ma.price8dEma },
{ id: "12d", color: colors.ma._12d, ratio: ma.price12dEma },
{ id: "13d", color: colors.ma._13d, ratio: ma.price13dEma },
{ id: "21d", color: colors.ma._21d, ratio: ma.price21dEma },
{ id: "26d", color: colors.ma._26d, ratio: ma.price26dEma },
{ id: "1m", color: colors.ma._1m, ratio: ma.price1mEma },
{ id: "34d", color: colors.ma._34d, ratio: ma.price34dEma },
{ id: "55d", color: colors.ma._55d, ratio: ma.price55dEma },
{ id: "89d", color: colors.ma._89d, ratio: ma.price89dEma },
{ id: "144d", color: colors.ma._144d, ratio: ma.price144dEma },
{ id: "200d", color: colors.ma._200d, ratio: ma.price200dEma },
{ id: "1y", color: colors.ma._1y, ratio: ma.price1yEma },
{ id: "2y", color: colors.ma._2y, ratio: ma.price2yEma },
{ id: "200w", color: colors.ma._200w, ratio: ma.price200wEma },
{ id: "4y", color: colors.ma._4y, ratio: ma.price4yEma },
];
// SMA vs EMA comparison periods (common periods only)
const smaVsEma = [
{ id: "1w", name: "1 Week", color: colors.ma._1w, sma: ma.price1wSma, ema: ma.price1wEma },
{ id: "1m", name: "1 Month", color: colors.ma._1m, sma: ma.price1mSma, ema: ma.price1mEma },
{ id: "200d", name: "200 Day", color: colors.ma._200d, sma: ma.price200dSma, ema: ma.price200dEma },
{ id: "1y", name: "1 Year", color: colors.ma._1y, sma: ma.price1ySma, ema: ma.price1yEma },
{ id: "200w", name: "200 Week", color: colors.ma._200w, sma: ma.price200wSma, ema: ma.price200wEma },
{ id: "4y", name: "4 Year", color: colors.ma._4y, sma: ma.price4ySma, ema: ma.price4yEma },
];
return {
name: "Market",
tree: [
{ name: "Price", title: "Bitcoin Price" },
{
name: "Sats/$",
title: "Sats per Dollar",
bottom: [line({ metric: priceMetrics.sats.split.close, name: "Sats/$", unit: Unit.sats })],
},
{
name: "Capitalization",
title: "Market Capitalization",
bottom: [line({ metric: supply.marketCap, name: "Capitalization", unit: Unit.usd })],
},
{
name: "All Time High",
tree: [
{
name: "Drawdown",
title: "ATH Drawdown",
top: [price({ metric: ath.priceAth, name: "ATH" })],
bottom: [line({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: Unit.percentage })],
},
{
name: "Time Since",
title: "Time Since ATH",
top: [price({ metric: ath.priceAth, name: "ATH" })],
bottom: [
line({ metric: ath.daysSincePriceAth, name: "Since", unit: Unit.days }),
line({ metric: ath.yearsSincePriceAth, name: "Since", unit: Unit.years }),
line({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.days }),
line({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.years }),
],
},
],
},
{
name: "Returns",
tree: [
{
name: "Compare",
title: "Returns Comparison",
bottom: [...shortPeriods, ...longPeriods].map((p) =>
baseline({
metric: p.returns,
name: p.id,
color: p.color,
unit: Unit.percentage,
defaultActive: p.defaultActive,
}),
),
},
returnsSubSection("Short-term", shortPeriods),
returnsSubSectionWithCagr("Long-term", longPeriods),
],
},
{
name: "Volatility",
tree: [
volatilityChart(colors, "Index", "Volatility Index", Unit.percentage, {
_1w: volatility.price1wVolatility,
_1m: volatility.price1mVolatility,
_1y: volatility.price1yVolatility,
}),
{
name: "True Range",
title: "True Range",
bottom: [
line({ metric: range.priceTrueRange, name: "Daily", color: colors.yellow, unit: Unit.usd }),
line({ metric: range.priceTrueRange2wSum, name: "2w Sum", color: colors.orange, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "Choppiness",
title: "Choppiness Index",
bottom: [
line({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: Unit.index }),
...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }),
],
},
volatilityChart(colors, "Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
_1w: volatility.sharpe1w,
_1m: volatility.sharpe1m,
_1y: volatility.sharpe1y,
}),
volatilityChart(colors, "Sortino Ratio", "Sortino Ratio", Unit.ratio, {
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
}),
],
},
{
name: "Moving Averages",
tree: [
{
name: "SMA vs EMA",
tree: [
{
name: "All Periods",
title: "SMA vs EMA Comparison",
top: smaVsEma.flatMap((p) => [
price({ metric: p.sma.price, name: `${p.id} SMA`, color: p.color }),
price({ metric: p.ema.price, name: `${p.id} EMA`, color: p.color, style: 1 }),
]),
},
...smaVsEma.map((p) => ({
name: p.name,
title: `${p.name} SMA vs EMA`,
top: [
price({ metric: p.sma.price, name: "SMA", color: p.color }),
price({ metric: p.ema.price, name: "EMA", color: p.color, style: 1 }),
],
})),
],
},
createMaSubSection(ctx, "SMA", sma),
createMaSubSection(ctx, "EMA", ema),
],
},
{
name: "Bands",
tree: [
{
name: "MinMax",
tree: [
{ id: "1w", name: "1 Week", min: range.price1wMin, max: range.price1wMax },
{ id: "2w", name: "2 Week", min: range.price2wMin, max: range.price2wMax },
{ id: "1m", name: "1 Month", min: range.price1mMin, max: range.price1mMax },
{ id: "1y", name: "1 Year", min: range.price1yMin, max: range.price1yMax },
].map((p) => ({
name: p.id,
title: `${p.name} MinMax`,
top: [
price({ metric: p.min, name: "Min", key: "price-min", color: colors.red }),
price({ metric: p.max, name: "Max", key: "price-max", color: colors.green }),
],
})),
},
{
name: "Mayer Multiple",
title: "Mayer Multiple",
top: [
price({ metric: ma.price200dSma.price, name: "200d SMA", color: colors.yellow }),
price({ metric: ma.price200dSmaX24, name: "200d SMA x2.4", color: colors.green }),
price({ metric: ma.price200dSmaX08, name: "200d SMA x0.8", color: colors.red }),
],
},
],
},
{
name: "Momentum",
tree: [
{
name: "RSI",
title: "RSI (14d)",
bottom: [
line({ metric: indicators.rsi14d, name: "RSI", color: colors.indigo, unit: Unit.index }),
line({ metric: indicators.rsi14dMin, name: "Min", color: colors.red, defaultActive: false, unit: Unit.index }),
line({ metric: indicators.rsi14dMax, name: "Max", color: colors.green, defaultActive: false, unit: Unit.index }),
priceLine({ ctx, unit: Unit.index, number: 70 }),
priceLine({ ctx, unit: Unit.index, number: 50, defaultActive: false }),
priceLine({ ctx, unit: Unit.index, number: 30 }),
],
},
{
name: "StochRSI",
title: "Stochastic RSI",
bottom: [
line({ metric: indicators.stochRsiK, name: "K", color: colors.blue, unit: Unit.index }),
line({ metric: indicators.stochRsiD, name: "D", color: colors.orange, unit: Unit.index }),
...priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }),
],
},
{
name: "MACD",
title: "MACD",
bottom: [
line({ metric: indicators.macdLine, name: "MACD", color: colors.blue, unit: Unit.usd }),
line({ metric: indicators.macdSignal, name: "Signal", color: colors.orange, unit: Unit.usd }),
histogram({ metric: indicators.macdHistogram, name: "Histogram", unit: Unit.usd }),
],
},
],
},
{
name: "Historical",
tree: [
{
name: "Compare",
title: "Historical Comparison",
top: [...shortPeriods, ...longPeriods].map((p) =>
price({
metric: p.lookback,
name: p.id,
color: p.color,
defaultActive: p.defaultActive,
}),
),
},
historicalSubSection("Short-term", shortPeriods),
historicalSubSection("Long-term", longPeriods),
],
},
{
name: "Indicators",
tree: [
{
name: "Pi Cycle",
title: "Pi Cycle",
top: [
price({ metric: ma.price111dSma.price, name: "111d SMA", color: colors.green }),
price({ metric: ma.price350dSmaX2, name: "350d SMA x2", color: colors.red }),
],
bottom: [baseline({ metric: indicators.piCycle, name: "Pi Cycle", unit: Unit.ratio, base: 1 })],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [line({ metric: indicators.puellMultiple, name: "Puell", color: colors.green, unit: Unit.ratio })],
},
{
name: "NVT",
title: "NVT Ratio",
bottom: [line({ metric: indicators.nvt, name: "NVT", color: colors.orange, unit: Unit.ratio })],
},
{
name: "Gini",
title: "Gini Coefficient",
bottom: [line({ metric: indicators.gini, name: "Gini", color: colors.red, unit: Unit.ratio })],
},
],
},
],
};
}
-202
View File
@@ -1,202 +0,0 @@
/** Moving averages section */
import { price } from "../series.js";
import { createPriceRatioCharts } from "../shared.js";
import { periodIdToName } from "../utils.js";
/**
* @param {Colors} colors
* @param {MarketMovingAverage} ma
*/
function buildSmaAverages(colors, ma) {
return /** @type {const} */ ([
["1w", 7, "red", ma.price1wSma],
["8d", 8, "orange", ma.price8dSma],
["13d", 13, "amber", ma.price13dSma],
["21d", 21, "yellow", ma.price21dSma],
["1m", 30, "lime", ma.price1mSma],
["34d", 34, "green", ma.price34dSma],
["55d", 55, "emerald", ma.price55dSma],
["89d", 89, "teal", ma.price89dSma],
["111d", 111, "cyan", ma.price111dSma],
["144d", 144, "sky", ma.price144dSma],
["200d", 200, "blue", ma.price200dSma],
["350d", 350, "indigo", ma.price350dSma],
["1y", 365, "violet", ma.price1ySma],
["2y", 730, "purple", ma.price2ySma],
["200w", 1400, "fuchsia", ma.price200wSma],
["4y", 1460, "pink", ma.price4ySma],
]).map(([id, days, colorKey, ratio]) => ({
id,
name: periodIdToName(id, true),
days,
color: colors[colorKey],
ratio,
}));
}
/**
* @param {Colors} colors
* @param {MarketMovingAverage} ma
*/
function buildEmaAverages(colors, ma) {
return /** @type {const} */ ([
["1w", 7, "red", ma.price1wEma],
["8d", 8, "orange", ma.price8dEma],
["12d", 12, "amber", ma.price12dEma],
["13d", 13, "yellow", ma.price13dEma],
["21d", 21, "lime", ma.price21dEma],
["26d", 26, "green", ma.price26dEma],
["1m", 30, "emerald", ma.price1mEma],
["34d", 34, "teal", ma.price34dEma],
["55d", 55, "cyan", ma.price55dEma],
["89d", 89, "sky", ma.price89dEma],
["144d", 144, "blue", ma.price144dEma],
["200d", 200, "indigo", ma.price200dEma],
["1y", 365, "violet", ma.price1yEma],
["2y", 730, "purple", ma.price2yEma],
["200w", 1400, "fuchsia", ma.price200wEma],
["4y", 1460, "pink", ma.price4yEma],
]).map(([id, days, colorKey, ratio]) => ({
id,
name: periodIdToName(id, true),
days,
color: colors[colorKey],
ratio,
}));
}
/** Common period IDs to show at top level */
const COMMON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
/** Periods to compare SMA vs EMA */
const COMPARISON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
/**
* Create SMA vs EMA comparison section
* @param {ReturnType<typeof buildSmaAverages>} smaAverages
* @param {ReturnType<typeof buildEmaAverages>} emaAverages
*/
function createCompareSection(smaAverages, emaAverages) {
// Find matching SMA/EMA pairs
const pairs = COMPARISON_PERIODS.map((id) => {
const sma = smaAverages.find((a) => a.id === id);
const ema = emaAverages.find((a) => a.id === id);
if (!sma || !ema) return null;
return { id, sma, ema };
}).filter(
/** @type {(p: any) => p is { id: string, sma: ReturnType<typeof buildSmaAverages>[number], ema: ReturnType<typeof buildEmaAverages>[number] }} */ (
p,
) => p !== null,
);
return {
name: "Compare",
tree: [
{
name: "All Periods",
title: "SMA vs EMA Comparison",
top: pairs.flatMap(({ sma, ema }) => [
price({
metric: sma.ratio.price,
name: `${sma.id} SMA`,
color: sma.color,
}),
price({
metric: ema.ratio.price,
name: `${ema.id} EMA`,
color: ema.color,
style: 1,
}),
]),
},
...pairs.map(({ id, sma, ema }) => ({
name: periodIdToName(id, true),
title: `${periodIdToName(id, true)} SMA vs EMA`,
top: [
price({ metric: sma.ratio.price, name: "SMA", color: sma.color }),
price({
metric: ema.ratio.price,
name: "EMA",
color: ema.color,
style: 1,
}),
],
})),
],
};
}
/**
* @param {PartialContext} ctx
* @param {MarketMovingAverage} movingAverage
*/
export function createAveragesSection(ctx, movingAverage) {
const { colors } = ctx;
const smaAverages = buildSmaAverages(colors, movingAverage);
const emaAverages = buildEmaAverages(colors, movingAverage);
/**
* @param {string} label
* @param {ReturnType<typeof buildSmaAverages> | ReturnType<typeof buildEmaAverages>} averages
*/
const createSubSection = (label, averages) => {
const commonAverages = averages.filter(({ id }) =>
COMMON_PERIODS.includes(id),
);
const moreAverages = averages.filter(
({ id }) => !COMMON_PERIODS.includes(id),
);
return {
name: label,
tree: [
{
name: "Compare",
title: `Price ${label}s`,
top: averages.map(({ id, color, ratio }) =>
price({
metric: ratio.price,
name: id,
color,
}),
),
},
// Common periods at top level
...commonAverages.map(({ name, color, ratio }) => ({
name,
tree: createPriceRatioCharts(ctx, {
context: `${name} ${label}`,
legend: "average",
pricePattern: ratio.price,
ratio,
color,
}),
})),
// Less common periods in "More..." folder
{
name: "More...",
tree: moreAverages.map(({ name, color, ratio }) => ({
name,
tree: createPriceRatioCharts(ctx, {
context: `${name} ${label}`,
legend: "average",
pricePattern: ratio.price,
ratio,
color,
}),
})),
},
],
};
};
return {
name: "Moving Averages",
tree: [
createCompareSection(smaAverages, emaAverages),
createSubSection("SMA", smaAverages),
createSubSection("EMA", emaAverages),
],
};
}
-85
View File
@@ -1,85 +0,0 @@
import { price } from "../series.js";
/**
* Create Bands section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["range"]} args.range
* @param {Market["movingAverage"]} args.movingAverage
*/
export function createBandsSection(ctx, { range, movingAverage }) {
const { colors } = ctx;
return {
name: "Bands",
tree: [
{
name: "MinMax",
tree: [
{
id: "1w",
title: "1 Week",
min: range.price1wMin,
max: range.price1wMax,
},
{
id: "2w",
title: "2 Week",
min: range.price2wMin,
max: range.price2wMax,
},
{
id: "1m",
title: "1 Month",
min: range.price1mMin,
max: range.price1mMax,
},
{
id: "1y",
title: "1 Year",
min: range.price1yMin,
max: range.price1yMax,
},
].map(({ id, title, min, max }) => ({
name: id,
title: `${title} MinMax`,
top: [
price({
metric: min,
name: "Min",
key: `price-min`,
color: colors.red,
}),
price({
metric: max,
name: "Max",
key: `price-max`,
color: colors.green,
}),
],
})),
},
{
name: "Mayer Multiple",
title: "Mayer Multiple",
top: [
price({
metric: movingAverage.price200dSma.price,
name: "200d SMA",
color: colors.yellow,
}),
price({
metric: movingAverage.price200dSmaX24,
name: "200d SMA x2.4",
color: colors.green,
}),
price({
metric: movingAverage.price200dSmaX08,
name: "200d SMA x0.8",
color: colors.red,
}),
],
},
],
};
}
-175
View File
@@ -1,175 +0,0 @@
/** Market section - Main entry point */
import { Unit } from "../../utils/units.js";
import { line, price } from "../series.js";
import { createAveragesSection } from "./averages.js";
import { createReturnsSection } from "./performance.js";
import { createMomentumSection } from "./momentum.js";
import { createVolatilitySection } from "./volatility.js";
import { createBandsSection } from "./bands.js";
import { createValuationSection } from "./onchain.js";
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk } = ctx;
const { market, supply } = brk.metrics;
const { movingAverage, ath, returns, volatility, range, indicators } = market;
return {
name: "Market",
tree: [
// Price
{
name: "Price",
title: "Bitcoin Price",
},
// Oracle section is localhost-only debug - uses non-price-pattern metrics
// ...(localhost
// ? /** @type {PartialOptionsTree} */ ([
// {
// name: "Oracle",
// title: "Oracle Price",
// top: /** @type {any} */ ([
// candlestick({
// metric: priceMetrics.oracle.closeOhlcDollars,
// name: "Close",
// unit: Unit.usd,
// }),
// candlestick({
// metric: priceMetrics.oracle.midOhlcDollars,
// name: "Mid",
// unit: Unit.usd,
// }),
// line({
// metric: priceMetrics.oracle.phaseDailyDollars.median,
// name: "o. p50",
// unit: Unit.usd,
// color: colors.yellow,
// }),
// line({
// metric: priceMetrics.oracle.phaseV2DailyDollars.median,
// name: "o2. p50",
// unit: Unit.usd,
// color: colors.orange,
// }),
// line({
// metric: priceMetrics.oracle.phaseV2PeakDailyDollars.median,
// name: "o2.2 p50",
// unit: Unit.usd,
// color: colors.orange,
// }),
// line({
// metric: priceMetrics.oracle.phaseV3DailyDollars.median,
// name: "o3. p50",
// unit: Unit.usd,
// color: colors.red,
// }),
// line({
// metric: priceMetrics.oracle.phaseV3PeakDailyDollars.median,
// name: "o3.2 p50",
// unit: Unit.usd,
// color: colors.red,
// }),
// line({
// metric: priceMetrics.oracle.phaseDailyDollars.max,
// name: "o. max",
// unit: Unit.usd,
// color: colors.lime,
// }),
// line({
// metric: priceMetrics.oracle.phaseV2DailyDollars.max,
// name: "o.2 max",
// unit: Unit.usd,
// color: colors.emerald,
// }),
// line({
// metric: priceMetrics.oracle.phaseDailyDollars.min,
// name: "o. min",
// unit: Unit.usd,
// color: colors.rose,
// }),
// line({
// metric: priceMetrics.oracle.phaseV2DailyDollars.min,
// name: "o.2 min",
// unit: Unit.usd,
// color: colors.purple,
// }),
// ]),
// },
// ])
// : []),
// Capitalization
{
name: "Capitalization",
title: "Market Cap",
bottom: [
line({
metric: supply.marketCap,
name: "Capitalization",
unit: Unit.usd,
}),
],
},
// All Time High
{
name: "All Time High",
title: "All Time High",
top: [price({ metric: ath.priceAth, name: "ATH" })],
bottom: [
line({
metric: ath.priceDrawdown,
name: "Drawdown",
color: colors.red,
unit: Unit.percentage,
}),
line({
metric: ath.daysSincePriceAth,
name: "Since",
unit: Unit.days,
}),
line({
metric: ath.yearsSincePriceAth,
name: "Since",
unit: Unit.years,
}),
line({
metric: ath.maxDaysBetweenPriceAths,
name: "Max",
color: colors.red,
unit: Unit.days,
}),
line({
metric: ath.maxYearsBetweenPriceAths,
name: "Max",
color: colors.red,
unit: Unit.years,
}),
],
},
// Moving Averages
createAveragesSection(ctx, movingAverage),
// Returns
createReturnsSection(ctx, returns),
// Volatility
createVolatilitySection(ctx, { volatility, range }),
// Momentum
createMomentumSection(ctx, indicators),
// Bands
createBandsSection(ctx, { range, movingAverage }),
// Valuation
createValuationSection(ctx, { indicators, movingAverage }),
],
};
}
-111
View File
@@ -1,111 +0,0 @@
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
import { Unit } from "../../utils/units.js";
import { priceLine, priceLines } from "../constants.js";
import { line, histogram } from "../series.js";
/**
* Create Momentum section
* @param {PartialContext} ctx
* @param {Market["indicators"]} indicators
*/
export function createMomentumSection(ctx, indicators) {
const { colors } = ctx;
return {
name: "Momentum",
tree: [
{
name: "RSI",
title: "RSI (14d)",
bottom: [
line({
metric: indicators.rsi14d,
name: "RSI",
color: colors.indigo,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMin,
name: "Min",
color: colors.red,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMax,
name: "Max",
color: colors.green,
defaultActive: false,
unit: Unit.index,
}),
priceLine({ ctx, unit: Unit.index, number: 70 }),
priceLine({
ctx,
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ ctx, unit: Unit.index, number: 30 }),
],
},
{
name: "StochRSI",
title: "Stochastic RSI",
bottom: [
// line({
// metric: indicators.stochRsi,
// name: "Stoch RSI",
// color: colors.purple,
// unit: Unit.index,
// }),
line({
metric: indicators.stochRsiK,
name: "K",
color: colors.blue,
unit: Unit.index,
}),
line({
metric: indicators.stochRsiD,
name: "D",
color: colors.orange,
unit: Unit.index,
}),
...priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }),
],
},
// {
// name: "Stochastic",
// title: "Stochastic Oscillator",
// bottom: [
// line({ metric: indicators.stochK, name: "K", color: colors.blue, unit: Unit.index }),
// line({ metric: indicators.stochD, name: "D", color: colors.orange, unit: Unit.index }),
// priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }),
// ],
// },
{
name: "MACD",
title: "MACD",
bottom: [
line({
metric: indicators.macdLine,
name: "MACD",
color: colors.blue,
unit: Unit.usd,
}),
line({
metric: indicators.macdSignal,
name: "Signal",
color: colors.orange,
unit: Unit.usd,
}),
histogram({
metric: indicators.macdHistogram,
name: "Histogram",
unit: Unit.usd,
}),
],
},
],
};
}
-81
View File
@@ -1,81 +0,0 @@
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
import { Unit } from "../../utils/units.js";
import { baseline, line, price } from "../series.js";
/**
* Create Valuation section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["indicators"]} args.indicators
* @param {Market["movingAverage"]} args.movingAverage
*/
export function createValuationSection(ctx, { indicators, movingAverage }) {
const { colors } = ctx;
return {
name: "Valuation",
tree: [
{
name: "Pi Cycle",
title: "Pi Cycle",
top: [
price({
metric: movingAverage.price111dSma.price,
name: "111d SMA",
color: colors.green,
}),
price({
metric: movingAverage.price350dSmaX2,
name: "350d SMA x2",
color: colors.red,
}),
],
bottom: [
baseline({
metric: indicators.piCycle,
name: "Pi Cycle",
unit: Unit.ratio,
base: 1,
}),
],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
line({
metric: indicators.puellMultiple,
name: "Puell",
color: colors.green,
unit: Unit.ratio,
}),
],
},
{
name: "NVT",
title: "NVT Ratio",
bottom: [
line({
metric: indicators.nvt,
name: "NVT",
color: colors.orange,
unit: Unit.ratio,
}),
],
},
{
name: "Gini",
title: "Gini Coefficient",
bottom: [
line({
metric: indicators.gini,
name: "Gini",
color: colors.red,
unit: Unit.ratio,
}),
],
},
],
};
}
@@ -1,142 +0,0 @@
/** Performance section */
import { Unit } from "../../utils/units.js";
import { priceLine } from "../constants.js";
import { baseline } from "../series.js";
import { periodIdToName } from "../utils.js";
/**
* Create Returns section
* @param {PartialContext} ctx
* @param {Market["returns"]} returns
*/
export function createReturnsSection(ctx, returns) {
const { colors } = ctx;
const shortTermPeriods = /** @type {const} */ ([
["1d", "_1d", undefined],
["1w", "_1w", undefined],
["1m", "_1m", undefined],
]);
const mediumTermPeriods = /** @type {const} */ ([
["3m", "_3m", undefined],
["6m", "_6m", undefined],
["1y", "_1y", undefined],
]);
const longTermPeriods = /** @type {const} */ ([
["2y", "_2y", "_2y"],
["3y", "_3y", "_3y"],
["4y", "_4y", "_4y"],
["5y", "_5y", "_5y"],
["6y", "_6y", "_6y"],
["8y", "_8y", "_8y"],
["10y", "_10y", "_10y"],
]);
/**
* @template {keyof typeof returns.priceReturns} K
* @param {readonly [string, K, K | undefined]} period
*/
const createPeriodChart = ([id, returnKey, cagrKey]) => {
const priceReturns = returns.priceReturns[/** @type {K} */ (returnKey)];
const cagr = cagrKey
? returns.cagr[/** @type {keyof typeof returns.cagr} */ (cagrKey)]
: undefined;
const name = periodIdToName(id, true);
return {
name,
title: `${name} Returns`,
bottom: [
baseline({
metric: priceReturns,
name: "Total",
unit: Unit.percentage,
}),
...(cagr
? [
baseline({
metric: cagr,
name: "CAGR",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
]
: []),
],
};
};
return {
name: "Returns",
tree: [
// Compare all periods
{
name: "Compare",
title: "Returns Comparison",
bottom: [
baseline({
metric: returns.priceReturns._1d,
name: "1d",
color: colors.red,
unit: Unit.percentage,
}),
baseline({
metric: returns.priceReturns._1w,
name: "1w",
color: colors.orange,
unit: Unit.percentage,
}),
baseline({
metric: returns.priceReturns._1m,
name: "1m",
color: colors.yellow,
unit: Unit.percentage,
}),
baseline({
metric: returns.priceReturns._3m,
name: "3m",
color: colors.lime,
unit: Unit.percentage,
defaultActive: false,
}),
baseline({
metric: returns.priceReturns._6m,
name: "6m",
color: colors.green,
unit: Unit.percentage,
defaultActive: false,
}),
baseline({
metric: returns.priceReturns._1y,
name: "1y",
color: colors.teal,
unit: Unit.percentage,
}),
baseline({
metric: returns.priceReturns._4y,
name: "4y",
color: colors.blue,
unit: Unit.percentage,
}),
],
},
// Short-term (1d, 1w, 1m)
{
name: "Short-term",
tree: shortTermPeriods.map(createPeriodChart),
},
// Medium-term (3m, 6m, 1y)
{
name: "Medium-term",
tree: mediumTermPeriods.map(createPeriodChart),
},
// Long-term (2y+)
{
name: "Long-term",
tree: longTermPeriods.map(createPeriodChart),
},
],
};
}
@@ -1,119 +0,0 @@
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
import { Unit } from "../../utils/units.js";
import { priceLine, priceLines } from "../constants.js";
import { line } from "../series.js";
/**
* Create Volatility section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["volatility"]} args.volatility
* @param {Market["range"]} args.range
*/
export function createVolatilitySection(ctx, { volatility, range }) {
const { colors } = ctx;
return {
name: "Volatility",
tree: [
{
name: "Index",
title: "Volatility Index",
bottom: [
line({
metric: volatility.price1wVolatility,
name: "1w",
color: colors.red,
unit: Unit.percentage,
}),
line({
metric: volatility.price1mVolatility,
name: "1m",
color: colors.orange,
unit: Unit.percentage,
}),
line({
metric: volatility.price1yVolatility,
name: "1y",
color: colors.lime,
unit: Unit.percentage,
}),
],
},
{
name: "True Range",
title: "True Range",
bottom: [
line({
metric: range.priceTrueRange,
name: "Value",
color: colors.yellow,
unit: Unit.usd,
}),
],
},
{
name: "Choppiness",
title: "Choppiness Index",
bottom: [
line({
metric: range.price2wChoppinessIndex,
name: "2w",
color: colors.red,
unit: Unit.index,
}),
...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }),
],
},
{
name: "Sharpe Ratio",
title: "Sharpe Ratio",
bottom: [
line({
metric: volatility.sharpe1w,
name: "1w",
color: colors.red,
unit: Unit.ratio,
}),
line({
metric: volatility.sharpe1m,
name: "1m",
color: colors.orange,
unit: Unit.ratio,
}),
line({
metric: volatility.sharpe1y,
name: "1y",
color: colors.lime,
unit: Unit.ratio,
}),
],
},
{
name: "Sortino Ratio",
title: "Sortino Ratio",
bottom: [
line({
metric: volatility.sortino1w,
name: "1w",
color: colors.red,
unit: Unit.ratio,
}),
line({
metric: volatility.sortino1m,
name: "1m",
color: colors.orange,
unit: Unit.ratio,
}),
line({
metric: volatility.sortino1y,
name: "1y",
color: colors.lime,
unit: Unit.ratio,
}),
],
},
],
};
}
+2 -4
View File
@@ -618,7 +618,6 @@ export function createNetworkSection(ctx) {
pattern: transactions.volume.receivedSum, pattern: transactions.volume.receivedSum,
name: "Received", name: "Received",
color: colors.cyan, color: colors.cyan,
defaultActive: false,
}), }),
], ],
}, },
@@ -794,7 +793,6 @@ export function createNetworkSection(ctx) {
...fromBaseStatsPattern({ ...fromBaseStatsPattern({
pattern: blocks.interval, pattern: blocks.interval,
unit: Unit.secs, unit: Unit.secs,
avgActive: false,
}), }),
priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }), priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }),
], ],
@@ -1003,8 +1001,8 @@ export function createNetworkSection(ctx) {
}), }),
}, },
{ {
name: "Throughput", name: "Activity Rate",
title: "Throughput", title: "Activity Rate",
bottom: [ bottom: [
dots({ dots({
metric: transactions.volume.txPerSec, metric: transactions.volume.txPerSec,
+1 -1
View File
@@ -15,7 +15,7 @@ import {
createCohortFolderAddress, createCohortFolderAddress,
createAddressCohortFolder, createAddressCohortFolder,
} from "./distribution/index.js"; } from "./distribution/index.js";
import { createMarketSection } from "./market/index.js"; import { createMarketSection } from "./market.js";
import { createNetworkSection } from "./network.js"; import { createNetworkSection } from "./network.js";
import { createMiningSection } from "./mining.js"; import { createMiningSection } from "./mining.js";
import { createCointimeSection } from "./cointime.js"; import { createCointimeSection } from "./cointime.js";
+3 -2
View File
@@ -401,16 +401,17 @@ export function createZScoresFolder(
* @param {string} [args.ratioName] - Optional custom name for ratio chart (default: "ratio") * @param {string} [args.ratioName] - Optional custom name for ratio chart (default: "ratio")
* @param {string} [args.priceTitle] - Optional override for price chart title (default: context) * @param {string} [args.priceTitle] - Optional override for price chart title (default: context)
* @param {string} [args.zScoresSuffix] - Optional suffix appended to context for z-scores (e.g., "MVRV" gives "2y Z-Score: STH MVRV") * @param {string} [args.zScoresSuffix] - Optional suffix appended to context for z-scores (e.g., "MVRV" gives "2y Z-Score: STH MVRV")
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences] - Optional additional price series to show in Price chart
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix }) { export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix, priceReferences }) {
const titleFn = formatCohortTitle(context); const titleFn = formatCohortTitle(context);
const zScoresTitleFn = zScoresSuffix ? formatCohortTitle(`${context} ${zScoresSuffix}`) : titleFn; const zScoresTitleFn = zScoresSuffix ? formatCohortTitle(`${context} ${zScoresSuffix}`) : titleFn;
return [ return [
{ {
name: "Price", name: "Price",
title: priceTitle ?? context, title: priceTitle ?? context,
top: [price({ metric: pricePattern, name: legend, color })], top: [price({ metric: pricePattern, name: legend, color }), ...(priceReferences ?? [])],
}, },
createRatioChart(ctx, { createRatioChart(ctx, {
title: titleFn, title: titleFn,
+19 -18
View File
@@ -28,28 +28,29 @@ function walk(node, map, path) {
for (const [key, value] of Object.entries(node)) { for (const [key, value] of Object.entries(node)) {
const kn = key.toLowerCase(); const kn = key.toLowerCase();
if ( if (
kn === "mvrv" || // kn === "mvrv" ||
kn === "time" || // kn === "time" ||
kn === "height" || // kn === "height" ||
kn === "constants" || // kn === "constants" ||
kn === "blockhash" || kn === "blockhash" ||
kn === "oracle" || kn === "date" ||
kn === "split" || // kn === "oracle" ||
kn === "ohlc" || // kn === "split" ||
kn === "outpoint" || // kn === "ohlc" ||
// kn === "outpoint" ||
kn === "positions" || kn === "positions" ||
kn === "outputtype" || // kn === "outputtype" ||
kn === "heighttopool" || kn === "heighttopool" ||
kn === "txid" || // kn === "txid" ||
kn.startsWith("satblocks") || kn.startsWith("timestamp") ||
kn.startsWith("satdays") || // kn.startsWith("satdays") ||
kn.endsWith("state") || // kn.endsWith("state") ||
kn.endsWith("cents") || // kn.endsWith("cents") ||
kn.endsWith("index") || kn.endsWith("index") ||
kn.endsWith("indexes") || kn.endsWith("indexes")
kn.endsWith("raw") || // kn.endsWith("raw") ||
kn.endsWith("bytes") || // kn.endsWith("bytes") ||
(kn.startsWith("_") && kn.endsWith("start")) // (kn.startsWith("_") && kn.endsWith("start"))
) )
continue; continue;
walk(/** @type {TreeNode | null | undefined} */ (value), map, [ walk(/** @type {TreeNode | null | undefined} */ (value), map, [