mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: v0.2 incoming
This commit is contained in:
@@ -151,7 +151,7 @@ fn fill_mixed_empty_field_parts(
|
|||||||
&& let Some(suffix) = leaf.strip_prefix(&prefix)
|
&& let Some(suffix) = leaf.strip_prefix(&prefix)
|
||||||
&& !suffix.is_empty()
|
&& !suffix.is_empty()
|
||||||
&& suffix.contains(field_name.trim_start_matches('_'))
|
&& suffix.contains(field_name.trim_start_matches('_'))
|
||||||
&& suffix.len() > field_name.trim_start_matches('_').len()
|
&& suffix.len() >= field_name.trim_start_matches('_').len()
|
||||||
{
|
{
|
||||||
updates.push((field_name.clone(), suffix.to_string()));
|
updates.push((field_name.clone(), suffix.to_string()));
|
||||||
}
|
}
|
||||||
@@ -485,19 +485,21 @@ fn determine_pattern_mode(
|
|||||||
) -> Option<PatternMode> {
|
) -> Option<PatternMode> {
|
||||||
analyses.first()?;
|
analyses.first()?;
|
||||||
|
|
||||||
// If any instance has outlier naming (no common prefix/suffix among children),
|
// Filter out outlier instances — they'll be inlined individually at generation
|
||||||
// the pattern can't be parameterized.
|
// time via the per-instance has_outlier check in prepare_tree_node.
|
||||||
if analyses.iter().any(|a| a.has_outlier) {
|
// Don't let a single outlier poison the entire pattern.
|
||||||
|
let non_outlier: Vec<&InstanceAnalysis> = analyses.iter().filter(|a| !a.has_outlier).collect();
|
||||||
|
if non_outlier.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick the majority mode
|
// Pick the majority mode
|
||||||
let suffix_count = analyses.iter().filter(|a| a.is_suffix_mode).count();
|
let suffix_count = non_outlier.iter().filter(|a| a.is_suffix_mode).count();
|
||||||
let is_suffix = suffix_count * 2 >= analyses.len();
|
let is_suffix = suffix_count * 2 >= non_outlier.len();
|
||||||
|
|
||||||
// All instances of the majority mode must agree on field_parts
|
// All instances of the majority mode must agree on field_parts
|
||||||
let majority: Vec<_> = analyses
|
let majority: Vec<&InstanceAnalysis> = non_outlier
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter(|a| a.is_suffix_mode == is_suffix)
|
.filter(|a| a.is_suffix_mode == is_suffix)
|
||||||
.collect();
|
.collect();
|
||||||
let first_majority = majority.first()?;
|
let first_majority = majority.first()?;
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ fn generate_tree_class(
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
if ctx.children.is_empty() {
|
||||||
|
writeln!(output, " pass").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let syntax = PythonSyntax;
|
let syntax = PythonSyntax;
|
||||||
for child in &ctx.children {
|
for child in &ctx.children {
|
||||||
if child.is_leaf {
|
if child.is_leaf {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPointsSigned32, Height, Indexes, StoredF32, StoredI64, StoredU32, StoredU64, Version};
|
use brk_types::{
|
||||||
|
BasisPointsSigned32, Height, Indexes, StoredF32, StoredI64, StoredU32, StoredU64, Version,
|
||||||
|
};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -8,7 +10,7 @@ use crate::{
|
|||||||
metrics::ImportConfig,
|
metrics::ImportConfig,
|
||||||
state::{CohortState, CostBasisOps, RealizedOps},
|
state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
},
|
},
|
||||||
internal::{PerBlock, PerBlockCumulativeRolling, PerBlockWithDeltas, RatioU32U64F32},
|
internal::{PerBlock, PerBlockCumulativeRolling, PerBlockWithDeltas, RatioU64F32},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Base output metrics: utxo_count + delta.
|
/// Base output metrics: utxo_count + delta.
|
||||||
@@ -32,12 +34,14 @@ impl OutputsBase {
|
|||||||
cfg.cached_starts,
|
cfg.cached_starts,
|
||||||
)?,
|
)?,
|
||||||
spent_count: cfg.import("spent_utxo_count", v1)?,
|
spent_count: cfg.import("spent_utxo_count", v1)?,
|
||||||
spending_rate: cfg.import("spending_rate", v1)?,
|
spending_rate: cfg.import("spending_rate", Version::TWO)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn min_len(&self) -> usize {
|
pub(crate) fn min_len(&self) -> usize {
|
||||||
self.unspent_count.height.len()
|
self.unspent_count
|
||||||
|
.height
|
||||||
|
.len()
|
||||||
.min(self.spent_count.block.len())
|
.min(self.spent_count.block.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +73,9 @@ impl OutputsBase {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.spending_rate
|
self.spending_rate
|
||||||
.compute_binary::<StoredU32, StoredU64, RatioU32U64F32>(
|
.compute_binary::<StoredU64, StoredU64, RatioU64F32>(
|
||||||
max_from,
|
max_from,
|
||||||
&self.spent_count.block,
|
&self.spent_count.sum.0._1y.height,
|
||||||
all_utxo_count,
|
all_utxo_count,
|
||||||
exit,
|
exit,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ pub use derived::{
|
|||||||
pub use ratio::{
|
pub use ratio::{
|
||||||
RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32,
|
RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32,
|
||||||
RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32,
|
RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32,
|
||||||
RatioDollarsBps32, RatioSatsBp16, RatioU32U64F32, RatioU64Bp16,
|
RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, RatioU64F32,
|
||||||
};
|
};
|
||||||
pub use specialized::{
|
pub use specialized::{
|
||||||
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
|
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use brk_types::{
|
use brk_types::{
|
||||||
BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats, StoredF32,
|
BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats, StoredF32,
|
||||||
StoredU32, StoredU64,
|
StoredU64,
|
||||||
};
|
};
|
||||||
use vecdb::BinaryTransform;
|
use vecdb::BinaryTransform;
|
||||||
|
|
||||||
@@ -112,11 +112,11 @@ impl BinaryTransform<Dollars, Dollars, BasisPoints32> for RatioDollarsBp32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RatioU32U64F32;
|
pub struct RatioU64F32;
|
||||||
|
|
||||||
impl BinaryTransform<StoredU32, StoredU64, StoredF32> for RatioU32U64F32 {
|
impl BinaryTransform<StoredU64, StoredU64, StoredF32> for RatioU64F32 {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn apply(numerator: StoredU32, denominator: StoredU64) -> StoredF32 {
|
fn apply(numerator: StoredU64, denominator: StoredU64) -> StoredF32 {
|
||||||
if *denominator > 0 {
|
if *denominator > 0 {
|
||||||
StoredF32::from(*numerator as f64 / *denominator as f64)
|
StoredF32::from(*numerator as f64 / *denominator as f64)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -309,10 +309,16 @@ fn gen_traversable(input: &DeriveInput) -> proc_macro2::TokenStream {
|
|||||||
&field_traversable_types,
|
&field_traversable_types,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let to_tree_node_body = if struct_attr.hidden {
|
||||||
|
quote! { brk_traversable::TreeNode::Branch(brk_traversable::IndexMap::new()) }
|
||||||
|
} else {
|
||||||
|
field_traversals
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics Traversable for #name #ty_generics #where_clause {
|
impl #impl_generics Traversable for #name #ty_generics #where_clause {
|
||||||
fn to_tree_node(&self) -> brk_traversable::TreeNode {
|
fn to_tree_node(&self) -> brk_traversable::TreeNode {
|
||||||
#field_traversals
|
#to_tree_node_body
|
||||||
}
|
}
|
||||||
|
|
||||||
#iterator_impl
|
#iterator_impl
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -266,7 +266,7 @@ export function createCointimeSection() {
|
|||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: activity.ratio,
|
series: activity.ratio,
|
||||||
name: "L/V Ratio",
|
name: "Liveliness / Vaultedness",
|
||||||
color: colors.activity,
|
color: colors.activity,
|
||||||
unit: Unit.ratio,
|
unit: Unit.ratio,
|
||||||
defaultActive: false,
|
defaultActive: false,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ function volumeTree(tv, color, title) {
|
|||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "Cumulative",
|
name: "Cumulative",
|
||||||
title: title("Cumulative Transfer Volume"),
|
title: title("Cumulative Transfer Volume Profitability"),
|
||||||
bottom: [
|
bottom: [
|
||||||
...satsBtcUsd({
|
...satsBtcUsd({
|
||||||
pattern: tv.inProfit.cumulative,
|
pattern: tv.inProfit.cumulative,
|
||||||
@@ -231,7 +231,7 @@ function singleSellSideRiskTree(sellSideRisk, title) {
|
|||||||
title: title(`${w.title} Sell Side Risk`),
|
title: title(`${w.title} Sell Side Risk`),
|
||||||
bottom: percentRatio({
|
bottom: percentRatio({
|
||||||
pattern: sellSideRisk[w.key],
|
pattern: sellSideRisk[w.key],
|
||||||
name: "Risk",
|
name: "Sell Side Risk",
|
||||||
color: w.color,
|
color: w.color,
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
@@ -381,7 +381,7 @@ export function createGroupedActivitySectionMinimal({ list, all, title }) {
|
|||||||
return {
|
return {
|
||||||
name: "Activity",
|
name: "Activity",
|
||||||
tree: groupedWindowsCumulativeSatsBtcUsd({
|
tree: groupedWindowsCumulativeSatsBtcUsd({
|
||||||
list, all, title, metricTitle: "Volume",
|
list, all, title, metricTitle: "Transfer Volume",
|
||||||
getMetric: (c) => c.tree.activity.transferVolume,
|
getMetric: (c) => c.tree.activity.transferVolume,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function percentileSeries(p, n = (x) => x) {
|
|||||||
.map(([key, s], i, arr) =>
|
.map(([key, s], i, arr) =>
|
||||||
price({
|
price({
|
||||||
series: s,
|
series: s,
|
||||||
name: n(key.replace("pct", "p")),
|
name: n(key.replace("pct", "P")),
|
||||||
color: colors.at(i, arr.length),
|
color: colors.at(i, arr.length),
|
||||||
...(ACTIVE_PCTS.has(key) ? {} : { defaultActive: false }),
|
...(ACTIVE_PCTS.has(key) ? {} : { defaultActive: false }),
|
||||||
}),
|
}),
|
||||||
@@ -70,9 +70,9 @@ function singleWeightFolder({ avgPrice, avgName, inProfit, inLoss, percentiles,
|
|||||||
title: title(`Cost Basis Distribution (${weightLabel})`),
|
title: title(`Cost Basis Distribution (${weightLabel})`),
|
||||||
top: [
|
top: [
|
||||||
price({ series: avgPrice, name: avgName, color }),
|
price({ series: avgPrice, name: avgName, color }),
|
||||||
...(max ? [price({ series: max, name: "p100", color: colors.stat.max, defaultActive: false })] : []),
|
...(max ? [price({ series: max, name: "P100", color: colors.stat.max, defaultActive: false })] : []),
|
||||||
...percentileSeries(percentiles),
|
...percentileSeries(percentiles),
|
||||||
...(min ? [price({ series: min, name: "p0", color: colors.stat.min, defaultActive: false })] : []),
|
...(min ? [price({ series: min, name: "P0", color: colors.stat.min, defaultActive: false })] : []),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ import {
|
|||||||
rollingPercentRatioTree,
|
rollingPercentRatioTree,
|
||||||
percentRatio,
|
percentRatio,
|
||||||
percentRatioBaseline,
|
percentRatioBaseline,
|
||||||
|
chartsFromCount,
|
||||||
} from "../series.js";
|
} from "../series.js";
|
||||||
import {
|
import {
|
||||||
satsBtcUsd,
|
satsBtcUsd,
|
||||||
flatMapCohorts,
|
flatMapCohorts,
|
||||||
mapCohortsWithAll,
|
mapCohortsWithAll,
|
||||||
flatMapCohortsWithAll,
|
flatMapCohortsWithAll,
|
||||||
|
groupedWindowsCumulative,
|
||||||
} from "../shared.js";
|
} from "../shared.js";
|
||||||
import { colors } from "../../utils/colors.js";
|
import { colors } from "../../utils/colors.js";
|
||||||
import { priceLines } from "../constants.js";
|
import { priceLines } from "../constants.js";
|
||||||
@@ -46,18 +48,39 @@ function simpleSupplySeries(supply) {
|
|||||||
* @param {CohortAll} all
|
* @param {CohortAll} all
|
||||||
* @param {(name: string) => string} title
|
* @param {(name: string) => string} title
|
||||||
*/
|
*/
|
||||||
function groupedUtxoCountFolder(list, all, title) {
|
function groupedOutputsFolder(list, all, title) {
|
||||||
return {
|
return {
|
||||||
name: "UTXOs",
|
name: "Outputs",
|
||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Count",
|
name: "Unspent",
|
||||||
title: title("UTXO Count"),
|
tree: [
|
||||||
|
{
|
||||||
|
name: "Count",
|
||||||
|
title: title("UTXO Count"),
|
||||||
|
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||||
|
line({ series: tree.outputs.unspentCount.base, name, color, unit: Unit.count }),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...groupedDeltaItems(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Spent",
|
||||||
|
tree: groupedWindowsCumulative({
|
||||||
|
list, all, title, metricTitle: "Spent UTXO Count",
|
||||||
|
getWindowSeries: (c, key) => c.tree.outputs.spentCount.sum[key],
|
||||||
|
getCumulativeSeries: (c) => c.tree.outputs.spentCount.cumulative,
|
||||||
|
seriesFn: line, unit: Unit.count,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Spending Rate",
|
||||||
|
title: title("Spending Rate"),
|
||||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||||
line({ series: tree.outputs.unspentCount.base, name, color, unit: Unit.count }),
|
line({ series: tree.outputs.spendingRate, name, color, unit: Unit.ratio }),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
...groupedDeltaItems(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"),
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -211,7 +234,32 @@ function ownSupplyChart(supply, title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count folder (UTXO or Address) with value + change
|
* @param {OutputsPattern} outputs
|
||||||
|
* @param {Color} color
|
||||||
|
* @param {(name: string) => string} title
|
||||||
|
* @returns {PartialOptionsGroup}
|
||||||
|
*/
|
||||||
|
function outputsFolder(outputs, color, title) {
|
||||||
|
return {
|
||||||
|
name: "Outputs",
|
||||||
|
tree: [
|
||||||
|
countFolder(outputs.unspentCount, "Unspent", "UTXO Count", color, title),
|
||||||
|
{
|
||||||
|
name: "Spent",
|
||||||
|
tree: chartsFromCount({ pattern: outputs.spentCount, title, metric: "Spent UTXO Count", unit: Unit.count, color }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Spending Rate",
|
||||||
|
title: title("Spending Rate"),
|
||||||
|
bottom: [
|
||||||
|
line({ series: outputs.spendingRate, name: "Rate", color, unit: Unit.ratio }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* @param {{ base: AnySeriesPattern, delta: DeltaPattern }} pattern
|
* @param {{ base: AnySeriesPattern, delta: DeltaPattern }} pattern
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {string} chartTitle
|
* @param {string} chartTitle
|
||||||
@@ -262,7 +310,7 @@ export function createHoldingsSection({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +334,7 @@ export function createHoldingsSectionAll({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
countFolder(cohort.addressCount, "Addresses", "Address Count", cohort.color, title),
|
countFolder(cohort.addressCount, "Addresses", "Address Count", cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -312,7 +360,7 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +384,7 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +407,7 @@ export function createHoldingsSectionWithProfitLoss({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +430,7 @@ export function createHoldingsSectionAddress({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
countFolder(cohort.addressCount, "Addresses", "Address Count", cohort.color, title),
|
countFolder(cohort.addressCount, "Addresses", "Address Count", cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -405,7 +453,7 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) {
|
|||||||
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
|
outputsFolder(cohort.tree.outputs, cohort.color, title),
|
||||||
countFolder(cohort.addressCount, "Addresses", "Address Count", cohort.color, title),
|
countFolder(cohort.addressCount, "Addresses", "Address Count", cohort.color, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -457,7 +505,7 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
|
|||||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
groupedUtxoCountFolder(list, all, title),
|
groupedOutputsFolder(list, all, title),
|
||||||
{
|
{
|
||||||
name: "Addresses",
|
name: "Addresses",
|
||||||
tree: [
|
tree: [
|
||||||
@@ -488,7 +536,7 @@ export function createGroupedHoldingsSectionAddressAmount({ list, all, title })
|
|||||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
groupedUtxoCountFolder(list, all, title),
|
groupedOutputsFolder(list, all, title),
|
||||||
{
|
{
|
||||||
name: "Addresses",
|
name: "Addresses",
|
||||||
tree: [
|
tree: [
|
||||||
@@ -515,7 +563,7 @@ export function createGroupedHoldingsSection({ list, all, title }) {
|
|||||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
groupedUtxoCountFolder(list, all, title),
|
groupedOutputsFolder(list, all, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +578,7 @@ export function createGroupedHoldingsSectionWithProfitLoss({ list, all, title })
|
|||||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
groupedUtxoCountFolder(list, all, title),
|
groupedOutputsFolder(list, all, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +594,7 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, all, title })
|
|||||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
groupedUtxoCountFolder(list, all, title),
|
groupedOutputsFolder(list, all, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,6 +616,6 @@ export function createGroupedHoldingsSectionWithRelative({ list, all, title }) {
|
|||||||
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
groupedUtxoCountFolder(list, all, title),
|
groupedOutputsFolder(list, all, title),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -496,10 +496,11 @@ export function createGroupedAddressCohortFolder({
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }} bucket
|
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }} bucket
|
||||||
|
* @param {string} [parentName]
|
||||||
* @returns {PartialOptionsGroup}
|
* @returns {PartialOptionsGroup}
|
||||||
*/
|
*/
|
||||||
function singleBucketFolder({ name, color, pattern }) {
|
function singleBucketFolder({ name, color, pattern }, parentName) {
|
||||||
const title = formatCohortTitle(name);
|
const title = formatCohortTitle(parentName ? `${parentName} ${name}` : name);
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
tree: [
|
tree: [
|
||||||
@@ -507,7 +508,7 @@ function singleBucketFolder({ name, color, pattern }) {
|
|||||||
name: "Supply",
|
name: "Supply",
|
||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Value",
|
name: "Total",
|
||||||
title: title("Supply"),
|
title: title("Supply"),
|
||||||
bottom: [
|
bottom: [
|
||||||
...satsBtcUsd({ pattern: pattern.supply.all, name: "Total" }),
|
...satsBtcUsd({ pattern: pattern.supply.all, name: "Total" }),
|
||||||
@@ -519,26 +520,21 @@ function singleBucketFolder({ name, color, pattern }) {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
...sumsTreeBaseline({
|
||||||
|
windows: pattern.supply.all.delta.absolute,
|
||||||
|
title,
|
||||||
|
metric: "Supply Change",
|
||||||
|
unit: Unit.sats,
|
||||||
|
}),
|
||||||
name: "Change",
|
name: "Change",
|
||||||
tree: [
|
},
|
||||||
{
|
{
|
||||||
...sumsTreeBaseline({
|
...rollingPercentRatioTree({
|
||||||
windows: pattern.supply.all.delta.absolute,
|
windows: pattern.supply.all.delta.rate,
|
||||||
title,
|
title,
|
||||||
metric: "Supply Change",
|
metric: "Supply Growth Rate",
|
||||||
unit: Unit.sats,
|
}),
|
||||||
}),
|
name: "Growth Rate",
|
||||||
name: "Change",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...rollingPercentRatioTree({
|
|
||||||
windows: pattern.supply.all.delta.rate,
|
|
||||||
title,
|
|
||||||
metric: "Supply Growth Rate",
|
|
||||||
}),
|
|
||||||
name: "Growth Rate",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -559,6 +555,23 @@ function singleBucketFolder({ name, color, pattern }) {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Unrealized PnL",
|
||||||
|
title: title("Unrealized PnL"),
|
||||||
|
bottom: [
|
||||||
|
line({
|
||||||
|
series: pattern.unrealizedPnl.all,
|
||||||
|
name: "Total",
|
||||||
|
unit: Unit.usd,
|
||||||
|
}),
|
||||||
|
line({
|
||||||
|
series: pattern.unrealizedPnl.sth,
|
||||||
|
name: "STH",
|
||||||
|
color: colors.term.short,
|
||||||
|
unit: Unit.usd,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "NUPL",
|
name: "NUPL",
|
||||||
title: title("NUPL"),
|
title: title("NUPL"),
|
||||||
@@ -599,65 +612,60 @@ function groupedBucketCharts(list, groupTitle) {
|
|||||||
name: "Change",
|
name: "Change",
|
||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Change",
|
name: "Compare",
|
||||||
tree: [
|
title: title("Supply Change"),
|
||||||
{
|
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||||
name: "Compare",
|
list.map(({ name, color, pattern }) =>
|
||||||
title: title("Supply Change"),
|
baseline({
|
||||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
series: pattern.supply.all.delta.absolute[w.key],
|
||||||
list.map(({ name, color, pattern }) =>
|
name: `${name} ${w.name}`,
|
||||||
baseline({
|
color,
|
||||||
series: pattern.supply.all.delta.absolute[w.key],
|
unit: Unit.sats,
|
||||||
name: `${name} ${w.name}`,
|
}),
|
||||||
color,
|
),
|
||||||
unit: Unit.sats,
|
),
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...ROLLING_WINDOWS.map((w) => ({
|
|
||||||
name: w.name,
|
|
||||||
title: title(`${w.title} Supply Change`),
|
|
||||||
bottom: list.map(({ name, color, pattern }) =>
|
|
||||||
baseline({
|
|
||||||
series: pattern.supply.all.delta.absolute[w.key],
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
unit: Unit.sats,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
...ROLLING_WINDOWS.map((w) => ({
|
||||||
|
name: w.name,
|
||||||
|
title: title(`${w.title} Supply Change`),
|
||||||
|
bottom: list.map(({ name, color, pattern }) =>
|
||||||
|
baseline({
|
||||||
|
series: pattern.supply.all.delta.absolute[w.key],
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
unit: Unit.sats,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Growth Rate",
|
||||||
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Growth Rate",
|
name: "Compare",
|
||||||
tree: [
|
title: title("Supply Growth Rate"),
|
||||||
{
|
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||||
name: "Compare",
|
list.flatMap(({ name, color, pattern }) =>
|
||||||
title: title("Supply Growth Rate"),
|
percentRatio({
|
||||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
pattern: pattern.supply.all.delta.rate[w.key],
|
||||||
list.flatMap(({ name, color, pattern }) =>
|
name: `${name} ${w.name}`,
|
||||||
percentRatio({
|
color,
|
||||||
pattern: pattern.supply.all.delta.rate[w.key],
|
}),
|
||||||
name: `${name} ${w.name}`,
|
),
|
||||||
color,
|
),
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...ROLLING_WINDOWS.map((w) => ({
|
|
||||||
name: w.name,
|
|
||||||
title: title(`${w.title} Supply Growth Rate`),
|
|
||||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
|
||||||
percentRatio({
|
|
||||||
pattern: pattern.supply.all.delta.rate[w.key],
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
...ROLLING_WINDOWS.map((w) => ({
|
||||||
|
name: w.name,
|
||||||
|
title: title(`${w.title} Supply Growth Rate`),
|
||||||
|
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||||
|
percentRatio({
|
||||||
|
pattern: pattern.supply.all.delta.rate[w.key],
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -691,6 +699,35 @@ function groupedBucketCharts(list, groupTitle) {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Unrealized PnL",
|
||||||
|
tree: [
|
||||||
|
{
|
||||||
|
name: "All",
|
||||||
|
title: title("Unrealized PnL"),
|
||||||
|
bottom: list.map(({ name, color, pattern }) =>
|
||||||
|
line({
|
||||||
|
series: pattern.unrealizedPnl.all,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
unit: Unit.usd,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "STH",
|
||||||
|
title: title("STH Unrealized PnL"),
|
||||||
|
bottom: list.map(({ name, color, pattern }) =>
|
||||||
|
line({
|
||||||
|
series: pattern.unrealizedPnl.sth,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
unit: Unit.usd,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "NUPL",
|
name: "NUPL",
|
||||||
title: title("NUPL"),
|
title: title("NUPL"),
|
||||||
@@ -716,21 +753,21 @@ export function createUtxoProfitabilitySection({ range, profit, loss }) {
|
|||||||
name: "Compare",
|
name: "Compare",
|
||||||
tree: groupedBucketCharts(range, "Profitability Range"),
|
tree: groupedBucketCharts(range, "Profitability Range"),
|
||||||
},
|
},
|
||||||
...range.map(singleBucketFolder),
|
...range.map((bucket) => singleBucketFolder(bucket)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "In Profit",
|
name: "In Profit",
|
||||||
tree: [
|
tree: [
|
||||||
{ name: "Compare", tree: groupedBucketCharts(profit, "In Profit") },
|
{ name: "Compare", tree: groupedBucketCharts(profit, "In Profit") },
|
||||||
...profit.map(singleBucketFolder),
|
...profit.map((bucket) => singleBucketFolder(bucket, "In Profit")),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "In Loss",
|
name: "In Loss",
|
||||||
tree: [
|
tree: [
|
||||||
{ name: "Compare", tree: groupedBucketCharts(loss, "In Loss") },
|
{ name: "Compare", tree: groupedBucketCharts(loss, "In Loss") },
|
||||||
...loss.map(singleBucketFolder),
|
...loss.map((bucket) => singleBucketFolder(bucket, "In Loss")),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function createPricesSectionFull({ cohort, title }) {
|
|||||||
{
|
{
|
||||||
name: "Realized",
|
name: "Realized",
|
||||||
tree: createPriceRatioCharts({
|
tree: createPriceRatioCharts({
|
||||||
context: cohort.name,
|
context: cohort.title,
|
||||||
legend: "Realized",
|
legend: "Realized",
|
||||||
pricePattern: tree.realized.price,
|
pricePattern: tree.realized.price,
|
||||||
ratio: tree.realized.price,
|
ratio: tree.realized.price,
|
||||||
@@ -54,6 +54,7 @@ export function createPricesSectionFull({ cohort, title }) {
|
|||||||
tree: priceRatioPercentilesTree({
|
tree: priceRatioPercentilesTree({
|
||||||
pattern: tree.realized.investor.price,
|
pattern: tree.realized.investor.price,
|
||||||
title: title("Investor Price"),
|
title: title("Investor Price"),
|
||||||
|
ratioTitle: title("Investor Price Ratio"),
|
||||||
legend: "Investor",
|
legend: "Investor",
|
||||||
color,
|
color,
|
||||||
}),
|
}),
|
||||||
@@ -82,24 +83,12 @@ export function createPricesSectionBasic({ cohort, title }) {
|
|||||||
top: [price({ series: tree.realized.price, name: "Realized", color })],
|
top: [price({ series: tree.realized.price, name: "Realized", color })],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MVRV",
|
name: "Ratio",
|
||||||
title: title("MVRV"),
|
|
||||||
bottom: [
|
|
||||||
baseline({
|
|
||||||
series: tree.realized.mvrv,
|
|
||||||
name: "MVRV",
|
|
||||||
unit: Unit.ratio,
|
|
||||||
base: 1,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Price Ratio",
|
|
||||||
title: title("Realized Price Ratio"),
|
title: title("Realized Price Ratio"),
|
||||||
bottom: [
|
bottom: [
|
||||||
baseline({
|
baseline({
|
||||||
series: tree.realized.price.ratio,
|
series: tree.realized.price.ratio,
|
||||||
name: "Price Ratio",
|
name: "Ratio",
|
||||||
unit: Unit.ratio,
|
unit: Unit.ratio,
|
||||||
base: 1,
|
base: 1,
|
||||||
}),
|
}),
|
||||||
@@ -136,7 +125,7 @@ function groupedRealizedPriceItems(list, all, title) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ratio",
|
name: "Ratio",
|
||||||
title: title("MVRV"),
|
title: title("Realized Price Ratio"),
|
||||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||||
baseline({ series: tree.realized.mvrv, name, color, unit: Unit.ratio, base: 1 }),
|
baseline({ series: tree.realized.mvrv, name, color, unit: Unit.ratio, base: 1 }),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function unrealizedOverview(profit, loss, netPnlUsd, title) {
|
|||||||
}),
|
}),
|
||||||
dotted({
|
dotted({
|
||||||
series: loss.negative,
|
series: loss.negative,
|
||||||
name: "Neg. Loss",
|
name: "Negated Loss",
|
||||||
color: colors.loss,
|
color: colors.loss,
|
||||||
unit: Unit.usd,
|
unit: Unit.usd,
|
||||||
}),
|
}),
|
||||||
@@ -233,12 +233,19 @@ function unrealizedTreeLongTerm(u, title) {
|
|||||||
ownPnlChart(u, title),
|
ownPnlChart(u, title),
|
||||||
{
|
{
|
||||||
name: "% of Market Cap",
|
name: "% of Market Cap",
|
||||||
title: title("Unrealized Loss (% of Market Cap)"),
|
title: title("Unrealized P&L (% of Market Cap)"),
|
||||||
bottom: percentRatio({
|
bottom: [
|
||||||
pattern: u.loss.toMcap,
|
...percentRatio({
|
||||||
name: "Loss",
|
pattern: u.profit.toMcap,
|
||||||
color: colors.loss,
|
name: "Profit",
|
||||||
}),
|
color: colors.profit,
|
||||||
|
}),
|
||||||
|
...percentRatio({
|
||||||
|
pattern: u.loss.toMcap,
|
||||||
|
name: "Loss",
|
||||||
|
color: colors.loss,
|
||||||
|
}),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
relPnlChartWithNet(u.netPnl.toOwnMcap, u.profit.toOwnMcap, u.loss.toOwnMcap, "% of Own Market Cap", title),
|
relPnlChartWithNet(u.netPnl.toOwnMcap, u.profit.toOwnMcap, u.loss.toOwnMcap, "% of Own Market Cap", title),
|
||||||
];
|
];
|
||||||
@@ -448,7 +455,7 @@ function realizedOverviewFolder({
|
|||||||
}),
|
}),
|
||||||
dotted({
|
dotted({
|
||||||
series: loss.negative.sum[w.key],
|
series: loss.negative.sum[w.key],
|
||||||
name: "Neg. Loss",
|
name: "Negated Loss",
|
||||||
color: colors.loss,
|
color: colors.loss,
|
||||||
unit: Unit.usd,
|
unit: Unit.usd,
|
||||||
}),
|
}),
|
||||||
@@ -518,7 +525,7 @@ function realizedSubfolderFull(r, title) {
|
|||||||
title: title("Net Realized P&L Change (% of Market Cap)"),
|
title: title("Net Realized P&L Change (% of Market Cap)"),
|
||||||
bottom: percentRatioBaseline({
|
bottom: percentRatioBaseline({
|
||||||
pattern: r.netPnl.change1m.toMcap,
|
pattern: r.netPnl.change1m.toMcap,
|
||||||
name: "30d Change",
|
name: "1m Change",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -526,7 +533,7 @@ function realizedSubfolderFull(r, title) {
|
|||||||
title: title("Net Realized P&L Change (% of Realized Cap)"),
|
title: title("Net Realized P&L Change (% of Realized Cap)"),
|
||||||
bottom: percentRatioBaseline({
|
bottom: percentRatioBaseline({
|
||||||
pattern: r.netPnl.change1m.toRcap,
|
pattern: r.netPnl.change1m.toRcap,
|
||||||
name: "30d Change",
|
name: "1m Change",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -740,7 +747,7 @@ export function createProfitabilitySectionWithProfitLoss({ cohort, title }) {
|
|||||||
title: title("Unrealized P&L"),
|
title: title("Unrealized P&L"),
|
||||||
bottom: [
|
bottom: [
|
||||||
line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd }),
|
line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd }),
|
||||||
line({ series: u.loss.negative, name: "Neg. Loss", color: colors.loss, unit: Unit.usd }),
|
line({ series: u.loss.negative, name: "Negated Loss", color: colors.loss, unit: Unit.usd }),
|
||||||
line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }),
|
line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }),
|
||||||
priceLine({ unit: Unit.usd }),
|
priceLine({ unit: Unit.usd }),
|
||||||
],
|
],
|
||||||
@@ -997,7 +1004,7 @@ function groupedRealizedSubfolderFull(list, all, title) {
|
|||||||
list,
|
list,
|
||||||
all,
|
all,
|
||||||
title,
|
title,
|
||||||
metricTitle: "Gross Realized P&L",
|
metricTitle: "Realized Gross P&L",
|
||||||
getMetric: (c) => c.tree.realized.grossPnl,
|
getMetric: (c) => c.tree.realized.grossPnl,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export function createValuationSectionFull({ cohort, title }) {
|
|||||||
},
|
},
|
||||||
{ name: "MVRV", title: title("MVRV"), bottom: ratioBottomSeries(tree.realized.price) },
|
{ name: "MVRV", title: title("MVRV"), bottom: ratioBottomSeries(tree.realized.price) },
|
||||||
...singleDeltaItems(tree, title),
|
...singleDeltaItems(tree, title),
|
||||||
{ name: "% of Own Market Cap", title: title("Realized Cap (% of Own Market Cap)"), bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "Rel. to Own Market Cap", color }) },
|
{ name: "% of Own Market Cap", title: title("Realized Cap (% of Own Market Cap)"), bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "% of Own Market Cap", color }) },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ function createSingleEntryTree(item, returnsBottom) {
|
|||||||
* @param {BaseEntryItem & { titlePrefix?: string }} item
|
* @param {BaseEntryItem & { titlePrefix?: string }} item
|
||||||
*/
|
*/
|
||||||
function createShortSingleEntry(item) {
|
function createShortSingleEntry(item) {
|
||||||
return createSingleEntryTree(item, percentRatioBaseline({ pattern: item.returns, name: "Current" }));
|
return createSingleEntryTree(item, percentRatioBaseline({ pattern: item.returns, name: "Return" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -249,7 +249,7 @@ function createLongSingleEntry(item) {
|
|||||||
name: "Returns",
|
name: "Returns",
|
||||||
title: `Returns: ${titlePrefix}`,
|
title: `Returns: ${titlePrefix}`,
|
||||||
top,
|
top,
|
||||||
bottom: percentRatioBaseline({ pattern: returns, name: "Current" }),
|
bottom: percentRatioBaseline({ pattern: returns, name: "Return" }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "CAGR",
|
name: "CAGR",
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ function createMaSubSection(label, averages) {
|
|||||||
tree: simplePriceRatioTree({
|
tree: simplePriceRatioTree({
|
||||||
pattern: a.ratio,
|
pattern: a.ratio,
|
||||||
title: `${periodIdToName(a.id, true)} ${label}`,
|
title: `${periodIdToName(a.id, true)} ${label}`,
|
||||||
legend: "average",
|
legend: "Average",
|
||||||
color: a.color,
|
color: a.color,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -204,14 +204,14 @@ function historicalSubSection(name, periods) {
|
|||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Compare",
|
name: "Compare",
|
||||||
title: `${name} Historical`,
|
title: `${name} Historical Prices`,
|
||||||
top: periods.map((p) =>
|
top: periods.map((p) =>
|
||||||
price({ series: p.lookback, name: p.id, color: p.color }),
|
price({ series: p.lookback, name: p.id, color: p.color }),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
...periods.map((p) => ({
|
...periods.map((p) => ({
|
||||||
name: periodIdToName(p.id, true),
|
name: periodIdToName(p.id, true),
|
||||||
title: `${periodIdToName(p.id, true)} Ago`,
|
title: `Price ${periodIdToName(p.id)} Ago`,
|
||||||
top: [price({ series: p.lookback, name: "Price" })],
|
top: [price({ series: p.lookback, name: "Price" })],
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@@ -438,23 +438,23 @@ export function createMarketSection() {
|
|||||||
bottom: [
|
bottom: [
|
||||||
line({
|
line({
|
||||||
series: ath.daysSince,
|
series: ath.daysSince,
|
||||||
name: "Since",
|
name: "Days",
|
||||||
unit: Unit.days,
|
unit: Unit.days,
|
||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: ath.yearsSince,
|
series: ath.yearsSince,
|
||||||
name: "Since",
|
name: "Years",
|
||||||
unit: Unit.years,
|
unit: Unit.years,
|
||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: ath.maxDaysBetween,
|
series: ath.maxDaysBetween,
|
||||||
name: "Max",
|
name: "Max Days",
|
||||||
color: colors.loss,
|
color: colors.loss,
|
||||||
unit: Unit.days,
|
unit: Unit.days,
|
||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: ath.maxYearsBetween,
|
series: ath.maxYearsBetween,
|
||||||
name: "Max",
|
name: "Max Years",
|
||||||
color: colors.loss,
|
color: colors.loss,
|
||||||
unit: Unit.years,
|
unit: Unit.years,
|
||||||
}),
|
}),
|
||||||
@@ -490,7 +490,7 @@ export function createMarketSection() {
|
|||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Compare",
|
name: "Compare",
|
||||||
title: "Historical Comparison",
|
title: "Historical Price Comparison",
|
||||||
top: [...shortPeriods, ...longPeriods].map((p) =>
|
top: [...shortPeriods, ...longPeriods].map((p) =>
|
||||||
price({
|
price({
|
||||||
series: p.lookback,
|
series: p.lookback,
|
||||||
@@ -511,7 +511,7 @@ export function createMarketSection() {
|
|||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Compare",
|
name: "Compare",
|
||||||
title: "Market vs Realized Capitalization",
|
title: "Market vs Realized Cap",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({
|
line({
|
||||||
series: supply.marketCap.usd,
|
series: supply.marketCap.usd,
|
||||||
@@ -553,7 +553,7 @@ export function createMarketSection() {
|
|||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Value",
|
name: "Value",
|
||||||
title: "Realized Capitalization",
|
title: "Realized Cap",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({
|
line({
|
||||||
series: cohorts.utxo.all.realized.cap.usd,
|
series: cohorts.utxo.all.realized.cap.usd,
|
||||||
@@ -802,11 +802,11 @@ export function createMarketSection() {
|
|||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
name: "Daily",
|
name: "Daily",
|
||||||
title: "True Range (Daily)",
|
title: "Daily True Range",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({
|
line({
|
||||||
series: range.trueRange,
|
series: range.trueRange,
|
||||||
name: "Daily",
|
name: "True Range",
|
||||||
color: colors.time._24h,
|
color: colors.time._24h,
|
||||||
unit: Unit.usd,
|
unit: Unit.usd,
|
||||||
}),
|
}),
|
||||||
@@ -814,7 +814,7 @@ export function createMarketSection() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "2 Week Sum",
|
name: "2 Week Sum",
|
||||||
title: "True Range (2 Week Sum)",
|
title: "2 Week True Range Sum",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({
|
line({
|
||||||
series: range.trueRangeSum2w,
|
series: range.trueRangeSum2w,
|
||||||
@@ -867,7 +867,7 @@ export function createMarketSection() {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "MinMax",
|
name: "Min/Max",
|
||||||
tree: [
|
tree: [
|
||||||
{
|
{
|
||||||
id: "1w",
|
id: "1w",
|
||||||
@@ -895,7 +895,7 @@ export function createMarketSection() {
|
|||||||
},
|
},
|
||||||
].map((p) => ({
|
].map((p) => ({
|
||||||
name: p.id,
|
name: p.id,
|
||||||
title: `${p.name} MinMax`,
|
title: `${p.name} Min/Max`,
|
||||||
top: [
|
top: [
|
||||||
price({
|
price({
|
||||||
series: p.max,
|
series: p.max,
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export function createMiningSection() {
|
|||||||
tree: [
|
tree: [
|
||||||
...ROLLING_WINDOWS.map((w) => ({
|
...ROLLING_WINDOWS.map((w) => ({
|
||||||
name: w.name,
|
name: w.name,
|
||||||
title: `${w.title} Revenue`,
|
title: `${w.title} Mining Revenue`,
|
||||||
bottom: revenueRollingBtcSatsUsd({
|
bottom: revenueRollingBtcSatsUsd({
|
||||||
coinbase: mining.rewards.coinbase.average[w.key],
|
coinbase: mining.rewards.coinbase.average[w.key],
|
||||||
subsidy: mining.rewards.subsidy.average[w.key],
|
subsidy: mining.rewards.subsidy.average[w.key],
|
||||||
@@ -308,7 +308,7 @@ export function createMiningSection() {
|
|||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "Cumulative",
|
name: "Cumulative",
|
||||||
title: "Revenue Comparison (Total)",
|
title: "Cumulative Mining Revenue",
|
||||||
bottom: revenueBtcSatsUsd({
|
bottom: revenueBtcSatsUsd({
|
||||||
coinbase: mining.rewards.coinbase,
|
coinbase: mining.rewards.coinbase,
|
||||||
subsidy: mining.rewards.subsidy,
|
subsidy: mining.rewards.subsidy,
|
||||||
@@ -338,7 +338,7 @@ export function createMiningSection() {
|
|||||||
metric: "Transaction Fee Revenue",
|
metric: "Transaction Fee Revenue",
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "Distributions",
|
name: "Distribution",
|
||||||
tree: ROLLING_WINDOWS.map((w) => ({
|
tree: ROLLING_WINDOWS.map((w) => ({
|
||||||
name: w.name,
|
name: w.name,
|
||||||
title: `${w.title} Fee Revenue per Block Distribution`,
|
title: `${w.title} Fee Revenue per Block Distribution`,
|
||||||
@@ -352,15 +352,15 @@ export function createMiningSection() {
|
|||||||
tree: [
|
tree: [
|
||||||
...ROLLING_WINDOWS.map((w) => ({
|
...ROLLING_WINDOWS.map((w) => ({
|
||||||
name: w.name,
|
name: w.name,
|
||||||
title: `${w.title} Revenue Dominance`,
|
title: `${w.title} Mining Revenue Dominance`,
|
||||||
bottom: [
|
bottom: [
|
||||||
...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }),
|
...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }),
|
||||||
...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }),
|
...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }),
|
||||||
],
|
],
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "All-time",
|
name: "All Time",
|
||||||
title: "Revenue Dominance (All-time)",
|
title: "All Time Mining Revenue Dominance",
|
||||||
bottom: [
|
bottom: [
|
||||||
...percentRatio({ pattern: mining.rewards.subsidy.dominance, name: "Subsidy", color: colors.mining.subsidy }),
|
...percentRatio({ pattern: mining.rewards.subsidy.dominance, name: "Subsidy", color: colors.mining.subsidy }),
|
||||||
...percentRatio({ pattern: mining.rewards.fees.dominance, name: "Fees", color: colors.mining.fee }),
|
...percentRatio({ pattern: mining.rewards.fees.dominance, name: "Fees", color: colors.mining.fee }),
|
||||||
@@ -369,7 +369,7 @@ export function createMiningSection() {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Fee Multiple",
|
name: "Fee-to-Subsidy",
|
||||||
tree: ROLLING_WINDOWS.map((w) => ({
|
tree: ROLLING_WINDOWS.map((w) => ({
|
||||||
name: w.name,
|
name: w.name,
|
||||||
title: `${w.title} Fee-to-Subsidy Ratio`,
|
title: `${w.title} Fee-to-Subsidy Ratio`,
|
||||||
@@ -378,11 +378,11 @@ export function createMiningSection() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Unclaimed",
|
name: "Unclaimed",
|
||||||
title: "Unclaimed Rewards (Total)",
|
title: "Unclaimed Rewards",
|
||||||
bottom: satsBtcUsdFrom({
|
bottom: satsBtcUsdFrom({
|
||||||
source: mining.rewards.unclaimed,
|
source: mining.rewards.unclaimed,
|
||||||
key: "cumulative",
|
key: "cumulative",
|
||||||
name: "all-time",
|
name: "All Time",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -397,8 +397,8 @@ export function createMiningSection() {
|
|||||||
bottom: [
|
bottom: [
|
||||||
line({ series: mining.hashrate.price.ths, name: "TH/s", color: colors.usd, unit: Unit.usdPerThsPerDay }),
|
line({ series: mining.hashrate.price.ths, name: "TH/s", color: colors.usd, unit: Unit.usdPerThsPerDay }),
|
||||||
line({ series: mining.hashrate.price.phs, name: "PH/s", color: colors.usd, unit: Unit.usdPerPhsPerDay }),
|
line({ series: mining.hashrate.price.phs, name: "PH/s", color: colors.usd, unit: Unit.usdPerPhsPerDay }),
|
||||||
dotted({ series: mining.hashrate.price.thsMin, name: "TH/s Min", color: colors.stat.min, unit: Unit.usdPerThsPerDay }),
|
dotted({ series: mining.hashrate.price.thsMin, name: "TH/s ATL", color: colors.stat.min, unit: Unit.usdPerThsPerDay }),
|
||||||
dotted({ series: mining.hashrate.price.phsMin, name: "PH/s Min", color: colors.stat.min, unit: Unit.usdPerPhsPerDay }),
|
dotted({ series: mining.hashrate.price.phsMin, name: "PH/s ATL", color: colors.stat.min, unit: Unit.usdPerPhsPerDay }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -407,13 +407,13 @@ export function createMiningSection() {
|
|||||||
bottom: [
|
bottom: [
|
||||||
line({ series: mining.hashrate.value.ths, name: "TH/s", color: colors.bitcoin, unit: Unit.satsPerThsPerDay }),
|
line({ series: mining.hashrate.value.ths, name: "TH/s", color: colors.bitcoin, unit: Unit.satsPerThsPerDay }),
|
||||||
line({ series: mining.hashrate.value.phs, name: "PH/s", color: colors.bitcoin, unit: Unit.satsPerPhsPerDay }),
|
line({ series: mining.hashrate.value.phs, name: "PH/s", color: colors.bitcoin, unit: Unit.satsPerPhsPerDay }),
|
||||||
dotted({ series: mining.hashrate.value.thsMin, name: "TH/s Min", color: colors.stat.min, unit: Unit.satsPerThsPerDay }),
|
dotted({ series: mining.hashrate.value.thsMin, name: "TH/s ATL", color: colors.stat.min, unit: Unit.satsPerThsPerDay }),
|
||||||
dotted({ series: mining.hashrate.value.phsMin, name: "PH/s Min", color: colors.stat.min, unit: Unit.satsPerPhsPerDay }),
|
dotted({ series: mining.hashrate.value.phsMin, name: "PH/s ATL", color: colors.stat.min, unit: Unit.satsPerPhsPerDay }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Recovery",
|
name: "Recovery",
|
||||||
title: "Recovery",
|
title: "Mining Recovery",
|
||||||
bottom: [
|
bottom: [
|
||||||
...percentRatio({ pattern: mining.hashrate.price.rebound, name: "Hash Price", color: colors.usd }),
|
...percentRatio({ pattern: mining.hashrate.price.rebound, name: "Hash Price", color: colors.usd }),
|
||||||
...percentRatio({ pattern: mining.hashrate.value.rebound, name: "Hash Value", color: colors.bitcoin }),
|
...percentRatio({ pattern: mining.hashrate.value.rebound, name: "Hash Value", color: colors.bitcoin }),
|
||||||
@@ -429,8 +429,8 @@ export function createMiningSection() {
|
|||||||
name: "Countdown",
|
name: "Countdown",
|
||||||
title: "Next Halving",
|
title: "Next Halving",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({ series: blocks.halving.blocksToHalving, name: "Remaining", unit: Unit.blocks }),
|
line({ series: blocks.halving.blocksToHalving, name: "Blocks", unit: Unit.blocks }),
|
||||||
line({ series: blocks.halving.daysToHalving, name: "Remaining", unit: Unit.days }),
|
line({ series: blocks.halving.daysToHalving, name: "Days", unit: Unit.days }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -458,8 +458,8 @@ export function createMiningSection() {
|
|||||||
name: "Countdown",
|
name: "Countdown",
|
||||||
title: "Next Difficulty Adjustment",
|
title: "Next Difficulty Adjustment",
|
||||||
bottom: [
|
bottom: [
|
||||||
line({ series: blocks.difficulty.blocksToRetarget, name: "Remaining", unit: Unit.blocks }),
|
line({ series: blocks.difficulty.blocksToRetarget, name: "Blocks", unit: Unit.blocks }),
|
||||||
line({ series: blocks.difficulty.daysToRetarget, name: "Remaining", unit: Unit.days }),
|
line({ series: blocks.difficulty.daysToRetarget, name: "Days", unit: Unit.days }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { entries } from "../utils/array.js";
|
|||||||
import {
|
import {
|
||||||
line,
|
line,
|
||||||
fromSupplyPattern,
|
fromSupplyPattern,
|
||||||
|
chartsFromFull,
|
||||||
chartsFromFullPerBlock,
|
chartsFromFullPerBlock,
|
||||||
chartsFromCount,
|
chartsFromCount,
|
||||||
chartsFromCountEntries,
|
chartsFromCountEntries,
|
||||||
@@ -80,7 +81,7 @@ export function createNetworkSection() {
|
|||||||
const activityTypes = /** @type {const} */ ([
|
const activityTypes = /** @type {const} */ ([
|
||||||
{ key: "sending", name: "Sending" },
|
{ key: "sending", name: "Sending" },
|
||||||
{ key: "receiving", name: "Receiving" },
|
{ key: "receiving", name: "Receiving" },
|
||||||
{ key: "both", name: "Both" },
|
{ key: "both", name: "Sending & Receiving" },
|
||||||
{ key: "reactivated", name: "Reactivated" },
|
{ key: "reactivated", name: "Reactivated" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ export function createNetworkSection() {
|
|||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "Cumulative",
|
name: "Cumulative",
|
||||||
title: `${groupName} Output Count (Total)`,
|
title: `Cumulative ${groupName} Output Count`,
|
||||||
bottom: types.map((t) =>
|
bottom: types.map((t) =>
|
||||||
line({
|
line({
|
||||||
series: scripts.count[t.key].cumulative,
|
series: scripts.count[t.key].cumulative,
|
||||||
@@ -288,7 +289,7 @@ export function createNetworkSection() {
|
|||||||
bottom: satsBtcUsdFrom({
|
bottom: satsBtcUsdFrom({
|
||||||
source: supply.burned,
|
source: supply.burned,
|
||||||
key: "cumulative",
|
key: "cumulative",
|
||||||
name: "all-time",
|
name: "All Time",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -296,7 +297,7 @@ export function createNetworkSection() {
|
|||||||
title: "OP_RETURN Burned",
|
title: "OP_RETURN Burned",
|
||||||
bottom: satsBtcUsd({
|
bottom: satsBtcUsd({
|
||||||
pattern: scripts.value.opReturn.cumulative,
|
pattern: scripts.value.opReturn.cumulative,
|
||||||
name: "all-time",
|
name: "All Time",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -420,11 +421,11 @@ export function createNetworkSection() {
|
|||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "Cumulative",
|
name: "Cumulative",
|
||||||
title: "Block Count (Total)",
|
title: "Cumulative Block Count",
|
||||||
bottom: [
|
bottom: [
|
||||||
{
|
{
|
||||||
series: blocks.count.total.cumulative,
|
series: blocks.count.total.cumulative,
|
||||||
title: "all-time",
|
title: "All Time",
|
||||||
unit: Unit.count,
|
unit: Unit.count,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -441,7 +442,7 @@ export function createNetworkSection() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Size",
|
name: "Size",
|
||||||
tree: chartsFromFullPerBlock({
|
tree: chartsFromFull({
|
||||||
pattern: blocks.size,
|
pattern: blocks.size,
|
||||||
metric: "Block Size",
|
metric: "Block Size",
|
||||||
unit: Unit.bytes,
|
unit: Unit.bytes,
|
||||||
@@ -449,7 +450,7 @@ export function createNetworkSection() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Weight",
|
name: "Weight",
|
||||||
tree: chartsFromFullPerBlock({
|
tree: chartsFromFull({
|
||||||
pattern: blocks.weight,
|
pattern: blocks.weight,
|
||||||
metric: "Block Weight",
|
metric: "Block Weight",
|
||||||
unit: Unit.wu,
|
unit: Unit.wu,
|
||||||
@@ -457,7 +458,7 @@ export function createNetworkSection() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "vBytes",
|
name: "vBytes",
|
||||||
tree: chartsFromFullPerBlock({
|
tree: chartsFromFull({
|
||||||
pattern: blocks.vbytes,
|
pattern: blocks.vbytes,
|
||||||
metric: "Block vBytes",
|
metric: "Block vBytes",
|
||||||
unit: Unit.vb,
|
unit: Unit.vb,
|
||||||
@@ -612,7 +613,7 @@ export function createNetworkSection() {
|
|||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "Cumulative",
|
name: "Cumulative",
|
||||||
title: "Output Count by Script Type (Total)",
|
title: "Cumulative Output Count by Script Type",
|
||||||
bottom: scriptTypes.map((t) =>
|
bottom: scriptTypes.map((t) =>
|
||||||
line({
|
line({
|
||||||
series: scripts.count[t.key].cumulative,
|
series: scripts.count[t.key].cumulative,
|
||||||
|
|||||||
@@ -101,13 +101,13 @@ function percentileSeries({ pattern, unit, title = "" }) {
|
|||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: pattern.pct90,
|
series: pattern.pct90,
|
||||||
name: `${title} pct90`.trim(),
|
name: `${title} P90`.trim(),
|
||||||
color: stat.pct90,
|
color: stat.pct90,
|
||||||
unit,
|
unit,
|
||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: pattern.pct75,
|
series: pattern.pct75,
|
||||||
name: `${title} pct75`.trim(),
|
name: `${title} P75`.trim(),
|
||||||
color: stat.pct75,
|
color: stat.pct75,
|
||||||
unit,
|
unit,
|
||||||
}),
|
}),
|
||||||
@@ -119,13 +119,13 @@ function percentileSeries({ pattern, unit, title = "" }) {
|
|||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: pattern.pct25,
|
series: pattern.pct25,
|
||||||
name: `${title} pct25`.trim(),
|
name: `${title} P25`.trim(),
|
||||||
color: stat.pct25,
|
color: stat.pct25,
|
||||||
unit,
|
unit,
|
||||||
}),
|
}),
|
||||||
line({
|
line({
|
||||||
series: pattern.pct10,
|
series: pattern.pct10,
|
||||||
name: `${title} pct10`.trim(),
|
name: `${title} P10`.trim(),
|
||||||
color: stat.pct10,
|
color: stat.pct10,
|
||||||
unit,
|
unit,
|
||||||
}),
|
}),
|
||||||
@@ -533,7 +533,7 @@ export function sumsAndAveragesCumulativeWith({
|
|||||||
{
|
{
|
||||||
name: "Cumulative",
|
name: "Cumulative",
|
||||||
title: title(`Cumulative ${metric}`),
|
title: title(`Cumulative ${metric}`),
|
||||||
bottom: series({ pattern: cumulative, name: "all-time", color }),
|
bottom: series({ pattern: cumulative, name: "All Time", color }),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,12 +276,12 @@ export function simplePriceRatioTree({ pattern, title, legend, color }) {
|
|||||||
*/
|
*/
|
||||||
function percentileBands(p, extract) {
|
function percentileBands(p, extract) {
|
||||||
return [
|
return [
|
||||||
{ name: "pct95", prop: extract(p.pct95), color: colors.ratioPct._95 },
|
{ name: "P95", prop: extract(p.pct95), color: colors.ratioPct._95 },
|
||||||
{ name: "pct5", prop: extract(p.pct5), color: colors.ratioPct._5 },
|
{ name: "P5", prop: extract(p.pct5), color: colors.ratioPct._5 },
|
||||||
{ name: "pct98", prop: extract(p.pct98), color: colors.ratioPct._98 },
|
{ name: "P98", prop: extract(p.pct98), color: colors.ratioPct._98 },
|
||||||
{ name: "pct2", prop: extract(p.pct2), color: colors.ratioPct._2 },
|
{ name: "P2", prop: extract(p.pct2), color: colors.ratioPct._2 },
|
||||||
{ name: "pct99", prop: extract(p.pct99), color: colors.ratioPct._99 },
|
{ name: "P99", prop: extract(p.pct99), color: colors.ratioPct._99 },
|
||||||
{ name: "pct1", prop: extract(p.pct1), color: colors.ratioPct._1 },
|
{ name: "P1", prop: extract(p.pct1), color: colors.ratioPct._1 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +306,7 @@ function ratioBands(bands) {
|
|||||||
* @param {string} args.title
|
* @param {string} args.title
|
||||||
* @param {string} args.legend
|
* @param {string} args.legend
|
||||||
* @param {Color} [args.color]
|
* @param {Color} [args.color]
|
||||||
|
* @param {string} [args.ratioTitle]
|
||||||
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences]
|
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences]
|
||||||
* @returns {PartialOptionsTree}
|
* @returns {PartialOptionsTree}
|
||||||
*/
|
*/
|
||||||
@@ -314,6 +315,7 @@ export function priceRatioPercentilesTree({
|
|||||||
title,
|
title,
|
||||||
legend,
|
legend,
|
||||||
color,
|
color,
|
||||||
|
ratioTitle,
|
||||||
priceReferences,
|
priceReferences,
|
||||||
}) {
|
}) {
|
||||||
const p = pattern.percentiles;
|
const p = pattern.percentiles;
|
||||||
@@ -331,7 +333,7 @@ export function priceRatioPercentilesTree({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ratio",
|
name: "Ratio",
|
||||||
title: `${title} Ratio`,
|
title: ratioTitle ?? `${title} Ratio`,
|
||||||
top: [
|
top: [
|
||||||
price({ series: pattern, name: legend, color }),
|
price({ series: pattern, name: legend, color }),
|
||||||
...priceBands(pctUsd),
|
...priceBands(pctUsd),
|
||||||
@@ -384,12 +386,12 @@ export function revenueRollingBtcSatsUsd({ coinbase, subsidy, fee }) {
|
|||||||
export function percentileUsdMap(ratio) {
|
export function percentileUsdMap(ratio) {
|
||||||
const p = ratio.percentiles;
|
const p = ratio.percentiles;
|
||||||
return /** @type {const} */ ([
|
return /** @type {const} */ ([
|
||||||
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
|
{ name: "P95", prop: p.pct95.price, color: colors.ratioPct._95 },
|
||||||
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
|
{ name: "P5", prop: p.pct5.price, color: colors.ratioPct._5 },
|
||||||
{ name: "pct98", prop: p.pct98.price, color: colors.ratioPct._98 },
|
{ name: "P98", prop: p.pct98.price, color: colors.ratioPct._98 },
|
||||||
{ name: "pct2", prop: p.pct2.price, color: colors.ratioPct._2 },
|
{ name: "P2", prop: p.pct2.price, color: colors.ratioPct._2 },
|
||||||
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
|
{ name: "P99", prop: p.pct99.price, color: colors.ratioPct._99 },
|
||||||
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
|
{ name: "P1", prop: p.pct1.price, color: colors.ratioPct._1 },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,12 +402,12 @@ export function percentileUsdMap(ratio) {
|
|||||||
export function percentileMap(ratio) {
|
export function percentileMap(ratio) {
|
||||||
const p = ratio.percentiles;
|
const p = ratio.percentiles;
|
||||||
return /** @type {const} */ ([
|
return /** @type {const} */ ([
|
||||||
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
|
{ name: "P95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
|
||||||
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
|
{ name: "P5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
|
||||||
{ name: "pct98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
|
{ name: "P98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
|
||||||
{ name: "pct2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
|
{ name: "P2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
|
||||||
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
|
{ name: "P99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
|
||||||
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
|
{ name: "P1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +419,7 @@ export function sdPatterns(ratio) {
|
|||||||
return /** @type {const} */ ([
|
return /** @type {const} */ ([
|
||||||
{
|
{
|
||||||
nameAddon: "All Time",
|
nameAddon: "All Time",
|
||||||
titleAddon: "",
|
titleAddon: "All Time",
|
||||||
sd: ratio.stdDev.all,
|
sd: ratio.stdDev.all,
|
||||||
smaRatio: ratio.sma.all.ratio,
|
smaRatio: ratio.sma.all.ratio,
|
||||||
},
|
},
|
||||||
@@ -498,7 +500,7 @@ export function ratioSmas(ratio) {
|
|||||||
{ name: "1y SMA", series: ratio.sma._1y.ratio },
|
{ name: "1y SMA", series: ratio.sma._1y.ratio },
|
||||||
{ name: "2y SMA", series: ratio.sma._2y.ratio },
|
{ name: "2y SMA", series: ratio.sma._2y.ratio },
|
||||||
{ name: "4y SMA", series: ratio.sma._4y.ratio },
|
{ name: "4y SMA", series: ratio.sma._4y.ratio },
|
||||||
{ name: "All SMA", series: ratio.sma.all.ratio, color: colors.time.all },
|
{ name: "All Time SMA", series: ratio.sma.all.ratio, color: colors.time.all },
|
||||||
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
|
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +544,7 @@ export function ratioBottomSeries(ratio) {
|
|||||||
*/
|
*/
|
||||||
export function createRatioChart({ title, pricePattern, ratio, color, name }) {
|
export function createRatioChart({ title, pricePattern, ratio, color, name }) {
|
||||||
return {
|
return {
|
||||||
name: name ?? "ratio",
|
name: name ?? "Ratio",
|
||||||
title: title(name ?? "Ratio"),
|
title: title(name ?? "Ratio"),
|
||||||
top: [
|
top: [
|
||||||
price({ series: pricePattern, name: "Price", color }),
|
price({ series: pricePattern, name: "Price", color }),
|
||||||
@@ -583,7 +585,7 @@ export function createZScoresFolder({
|
|||||||
{ name: "1y", sd: ratio.stdDev._1y },
|
{ name: "1y", sd: ratio.stdDev._1y },
|
||||||
{ name: "2y", sd: ratio.stdDev._2y },
|
{ name: "2y", sd: ratio.stdDev._2y },
|
||||||
{ name: "4y", sd: ratio.stdDev._4y },
|
{ name: "4y", sd: ratio.stdDev._4y },
|
||||||
{ name: "all", sd: ratio.stdDev.all, color: colors.time.all },
|
{ name: "All Time", sd: ratio.stdDev.all, color: colors.time.all },
|
||||||
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
|
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ export function periodIdToName(id, compoundAdjective) {
|
|||||||
const s = compoundAdjective || num === 1 ? "" : "s";
|
const s = compoundAdjective || num === 1 ? "" : "s";
|
||||||
switch (id.slice(-1)) {
|
switch (id.slice(-1)) {
|
||||||
case "h":
|
case "h":
|
||||||
return `${num} hour${s}`;
|
return `${num} Hour${s}`;
|
||||||
case "d":
|
case "d":
|
||||||
return `${num} day${s}`;
|
return `${num} Day${s}`;
|
||||||
case "w":
|
case "w":
|
||||||
return `${num} week${s}`;
|
return `${num} Week${s}`;
|
||||||
case "m":
|
case "m":
|
||||||
return `${num} month${s}`;
|
return `${num} Month${s}`;
|
||||||
case "y":
|
case "y":
|
||||||
return `${num} year${s}`;
|
return `${num} Year${s}`;
|
||||||
default:
|
default:
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
* @typedef {Brk.BtcCentsSatsUsdPattern} SupplyPattern
|
* @typedef {Brk.BtcCentsSatsUsdPattern} SupplyPattern
|
||||||
* @typedef {Brk.AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} BlockSizePattern
|
* @typedef {Brk.AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} BlockSizePattern
|
||||||
* @typedef {keyof Brk.SeriesTree_Cohorts_Utxo_Type} SpendableType
|
* @typedef {keyof Brk.SeriesTree_Cohorts_Utxo_Type} SpendableType
|
||||||
|
* @typedef {Brk.SpendingSpentUnspentPattern} OutputsPattern
|
||||||
* @typedef {keyof Brk.SeriesTree_Addrs_Raw} AddressableType
|
* @typedef {keyof Brk.SeriesTree_Addrs_Raw} AddressableType
|
||||||
*
|
*
|
||||||
* Brk pattern types (using new pattern names)
|
* Brk pattern types (using new pattern names)
|
||||||
@@ -71,8 +72,8 @@
|
|||||||
* @typedef {Brk.LossNetNuplProfitPattern} BasicRelativePattern
|
* @typedef {Brk.LossNetNuplProfitPattern} BasicRelativePattern
|
||||||
* @typedef {Brk.GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} FullRelativePattern
|
* @typedef {Brk.GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} FullRelativePattern
|
||||||
*
|
*
|
||||||
* Profitability bucket pattern (supply + realized_cap + nupl)
|
* Profitability bucket pattern (supply + realized_cap + unrealized_pnl + nupl)
|
||||||
* @typedef {Brk.NuplRealizedSupplyPattern} RealizedSupplyPattern
|
* @typedef {Brk.NuplRealizedSupplyUnrealizedPattern} RealizedSupplyPattern
|
||||||
*
|
*
|
||||||
* Realized pattern (full: cap + gross + investor + loss + mvrv + net + peak + price + profit + sell + sopr)
|
* Realized pattern (full: cap + gross + investor + loss + mvrv + net + peak + price + profit + sell + sopr)
|
||||||
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern
|
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
h1 {
|
h1 {
|
||||||
font-size: 1.375rem;
|
font-size: 1.375rem;
|
||||||
letter-spacing: 0.075rem;
|
letter-spacing: 0.075rem;
|
||||||
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user