mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 15:33:33 -07:00
website: snapshot
This commit is contained in:
@@ -4352,20 +4352,20 @@ pub struct MetricsTree_Market_Dca {
|
||||
pub period_cagr: _10y2y3y4y5y6y8yPattern,
|
||||
pub period_days_in_profit: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>,
|
||||
pub period_days_in_loss: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>,
|
||||
pub period_max_drawdown: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub period_min_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub period_max_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub period_lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3,
|
||||
pub period_lump_sum_returns: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub period_lump_sum_days_in_profit: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>,
|
||||
pub period_lump_sum_days_in_loss: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>,
|
||||
pub period_lump_sum_max_drawdown: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub period_lump_sum_min_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub period_lump_sum_max_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>,
|
||||
pub class_stack: MetricsTree_Market_Dca_ClassStack,
|
||||
pub class_average_price: MetricsTree_Market_Dca_ClassAveragePrice,
|
||||
pub class_returns: _201520162017201820192020202120222023202420252026Pattern2<StoredF32>,
|
||||
pub class_days_in_profit: MetricsTree_Market_Dca_ClassDaysInProfit,
|
||||
pub class_days_in_loss: MetricsTree_Market_Dca_ClassDaysInLoss,
|
||||
pub class_max_drawdown: MetricsTree_Market_Dca_ClassMaxDrawdown,
|
||||
pub class_min_return: MetricsTree_Market_Dca_ClassMinReturn,
|
||||
pub class_max_return: MetricsTree_Market_Dca_ClassMaxReturn,
|
||||
}
|
||||
|
||||
@@ -4378,20 +4378,20 @@ impl MetricsTree_Market_Dca {
|
||||
period_cagr: _10y2y3y4y5y6y8yPattern::new(client.clone(), "dca_cagr".to_string()),
|
||||
period_days_in_profit: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_days_in_profit".to_string()),
|
||||
period_days_in_loss: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_days_in_loss".to_string()),
|
||||
period_max_drawdown: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_max_drawdown".to_string()),
|
||||
period_min_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_min_return".to_string()),
|
||||
period_max_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_max_return".to_string()),
|
||||
period_lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "lump_sum_stack".to_string()),
|
||||
period_lump_sum_returns: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_returns".to_string()),
|
||||
period_lump_sum_days_in_profit: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_days_in_profit".to_string()),
|
||||
period_lump_sum_days_in_loss: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_days_in_loss".to_string()),
|
||||
period_lump_sum_max_drawdown: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_max_drawdown".to_string()),
|
||||
period_lump_sum_min_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_min_return".to_string()),
|
||||
period_lump_sum_max_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_max_return".to_string()),
|
||||
class_stack: MetricsTree_Market_Dca_ClassStack::new(client.clone(), format!("{base_path}_class_stack")),
|
||||
class_average_price: MetricsTree_Market_Dca_ClassAveragePrice::new(client.clone(), format!("{base_path}_class_average_price")),
|
||||
class_returns: _201520162017201820192020202120222023202420252026Pattern2::new(client.clone(), "dca_class".to_string()),
|
||||
class_days_in_profit: MetricsTree_Market_Dca_ClassDaysInProfit::new(client.clone(), format!("{base_path}_class_days_in_profit")),
|
||||
class_days_in_loss: MetricsTree_Market_Dca_ClassDaysInLoss::new(client.clone(), format!("{base_path}_class_days_in_loss")),
|
||||
class_max_drawdown: MetricsTree_Market_Dca_ClassMaxDrawdown::new(client.clone(), format!("{base_path}_class_max_drawdown")),
|
||||
class_min_return: MetricsTree_Market_Dca_ClassMinReturn::new(client.clone(), format!("{base_path}_class_min_return")),
|
||||
class_max_return: MetricsTree_Market_Dca_ClassMaxReturn::new(client.clone(), format!("{base_path}_class_max_return")),
|
||||
}
|
||||
}
|
||||
@@ -4573,7 +4573,7 @@ impl MetricsTree_Market_Dca_ClassDaysInLoss {
|
||||
}
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Market_Dca_ClassMaxDrawdown {
|
||||
pub struct MetricsTree_Market_Dca_ClassMinReturn {
|
||||
pub _2015: MetricPattern4<StoredF32>,
|
||||
pub _2016: MetricPattern4<StoredF32>,
|
||||
pub _2017: MetricPattern4<StoredF32>,
|
||||
@@ -4588,21 +4588,21 @@ pub struct MetricsTree_Market_Dca_ClassMaxDrawdown {
|
||||
pub _2026: MetricPattern4<StoredF32>,
|
||||
}
|
||||
|
||||
impl MetricsTree_Market_Dca_ClassMaxDrawdown {
|
||||
impl MetricsTree_Market_Dca_ClassMinReturn {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
_2015: MetricPattern4::new(client.clone(), "dca_class_2015_max_drawdown".to_string()),
|
||||
_2016: MetricPattern4::new(client.clone(), "dca_class_2016_max_drawdown".to_string()),
|
||||
_2017: MetricPattern4::new(client.clone(), "dca_class_2017_max_drawdown".to_string()),
|
||||
_2018: MetricPattern4::new(client.clone(), "dca_class_2018_max_drawdown".to_string()),
|
||||
_2019: MetricPattern4::new(client.clone(), "dca_class_2019_max_drawdown".to_string()),
|
||||
_2020: MetricPattern4::new(client.clone(), "dca_class_2020_max_drawdown".to_string()),
|
||||
_2021: MetricPattern4::new(client.clone(), "dca_class_2021_max_drawdown".to_string()),
|
||||
_2022: MetricPattern4::new(client.clone(), "dca_class_2022_max_drawdown".to_string()),
|
||||
_2023: MetricPattern4::new(client.clone(), "dca_class_2023_max_drawdown".to_string()),
|
||||
_2024: MetricPattern4::new(client.clone(), "dca_class_2024_max_drawdown".to_string()),
|
||||
_2025: MetricPattern4::new(client.clone(), "dca_class_2025_max_drawdown".to_string()),
|
||||
_2026: MetricPattern4::new(client.clone(), "dca_class_2026_max_drawdown".to_string()),
|
||||
_2015: MetricPattern4::new(client.clone(), "dca_class_2015_min_return".to_string()),
|
||||
_2016: MetricPattern4::new(client.clone(), "dca_class_2016_min_return".to_string()),
|
||||
_2017: MetricPattern4::new(client.clone(), "dca_class_2017_min_return".to_string()),
|
||||
_2018: MetricPattern4::new(client.clone(), "dca_class_2018_min_return".to_string()),
|
||||
_2019: MetricPattern4::new(client.clone(), "dca_class_2019_min_return".to_string()),
|
||||
_2020: MetricPattern4::new(client.clone(), "dca_class_2020_min_return".to_string()),
|
||||
_2021: MetricPattern4::new(client.clone(), "dca_class_2021_min_return".to_string()),
|
||||
_2022: MetricPattern4::new(client.clone(), "dca_class_2022_min_return".to_string()),
|
||||
_2023: MetricPattern4::new(client.clone(), "dca_class_2023_min_return".to_string()),
|
||||
_2024: MetricPattern4::new(client.clone(), "dca_class_2024_min_return".to_string()),
|
||||
_2025: MetricPattern4::new(client.clone(), "dca_class_2025_min_return".to_string()),
|
||||
_2026: MetricPattern4::new(client.clone(), "dca_class_2026_min_return".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Vecs {
|
||||
compute_period_profitability(
|
||||
&mut self.period_days_in_profit,
|
||||
&mut self.period_days_in_loss,
|
||||
&mut self.period_max_drawdown,
|
||||
&mut self.period_min_return,
|
||||
&mut self.period_max_return,
|
||||
&self.period_returns,
|
||||
starting_indexes,
|
||||
@@ -95,7 +95,7 @@ impl Vecs {
|
||||
compute_period_profitability(
|
||||
&mut self.period_lump_sum_days_in_profit,
|
||||
&mut self.period_lump_sum_days_in_loss,
|
||||
&mut self.period_lump_sum_max_drawdown,
|
||||
&mut self.period_lump_sum_min_return,
|
||||
&mut self.period_lump_sum_max_return,
|
||||
&self.period_lump_sum_returns,
|
||||
starting_indexes,
|
||||
@@ -130,7 +130,7 @@ impl Vecs {
|
||||
compute_class_profitability(
|
||||
&mut self.class_days_in_profit,
|
||||
&mut self.class_days_in_loss,
|
||||
&mut self.class_max_drawdown,
|
||||
&mut self.class_min_return,
|
||||
&mut self.class_max_return,
|
||||
&self.class_returns,
|
||||
starting_indexes,
|
||||
@@ -144,16 +144,16 @@ impl Vecs {
|
||||
fn compute_period_profitability(
|
||||
days_in_profit: &mut ByDcaPeriod<ComputedFromDateLast<StoredU32>>,
|
||||
days_in_loss: &mut ByDcaPeriod<ComputedFromDateLast<StoredU32>>,
|
||||
max_drawdown: &mut ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
min_return: &mut ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
max_return: &mut ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
returns: &ByDcaPeriod<LazyBinaryFromDateLast<StoredF32, Close<Dollars>, Dollars>>,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
for ((((dip, dil), md), mr), (ret, days)) in days_in_profit
|
||||
for ((((dip, dil), minr), maxr), (ret, days)) in days_in_profit
|
||||
.iter_mut()
|
||||
.zip(days_in_loss.iter_mut())
|
||||
.zip(max_drawdown.iter_mut())
|
||||
.zip(min_return.iter_mut())
|
||||
.zip(max_return.iter_mut())
|
||||
.zip(returns.iter_with_days())
|
||||
{
|
||||
@@ -177,7 +177,7 @@ fn compute_period_profitability(
|
||||
)?)
|
||||
})?;
|
||||
|
||||
md.compute_all(starting_indexes, exit, |v| {
|
||||
minr.compute_all(starting_indexes, exit, |v| {
|
||||
Ok(v.compute_min(
|
||||
starting_indexes.dateindex,
|
||||
&ret.dateindex,
|
||||
@@ -186,7 +186,7 @@ fn compute_period_profitability(
|
||||
)?)
|
||||
})?;
|
||||
|
||||
mr.compute_all(starting_indexes, exit, |v| {
|
||||
maxr.compute_all(starting_indexes, exit, |v| {
|
||||
Ok(v.compute_max(
|
||||
starting_indexes.dateindex,
|
||||
&ret.dateindex,
|
||||
@@ -201,7 +201,7 @@ fn compute_period_profitability(
|
||||
fn compute_class_profitability(
|
||||
days_in_profit: &mut ByDcaClass<ComputedFromDateLast<StoredU32>>,
|
||||
days_in_loss: &mut ByDcaClass<ComputedFromDateLast<StoredU32>>,
|
||||
max_drawdown: &mut ByDcaClass<ComputedFromDateLast<StoredF32>>,
|
||||
min_return: &mut ByDcaClass<ComputedFromDateLast<StoredF32>>,
|
||||
max_return: &mut ByDcaClass<ComputedFromDateLast<StoredF32>>,
|
||||
returns: &ByDcaClass<LazyBinaryFromDateLast<StoredF32, Close<Dollars>, Dollars>>,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
@@ -209,10 +209,10 @@ fn compute_class_profitability(
|
||||
) -> Result<()> {
|
||||
let dateindexes = ByDcaClass::<()>::dateindexes();
|
||||
|
||||
for (((((dip, dil), md), mr), ret), from) in days_in_profit
|
||||
for (((((dip, dil), minr), maxr), ret), from) in days_in_profit
|
||||
.iter_mut()
|
||||
.zip(days_in_loss.iter_mut())
|
||||
.zip(max_drawdown.iter_mut())
|
||||
.zip(min_return.iter_mut())
|
||||
.zip(max_return.iter_mut())
|
||||
.zip(returns.iter())
|
||||
.zip(dateindexes)
|
||||
@@ -237,7 +237,7 @@ fn compute_class_profitability(
|
||||
)?)
|
||||
})?;
|
||||
|
||||
md.compute_all(starting_indexes, exit, |v| {
|
||||
minr.compute_all(starting_indexes, exit, |v| {
|
||||
Ok(v.compute_all_time_low_from(
|
||||
starting_indexes.dateindex,
|
||||
&ret.dateindex,
|
||||
@@ -246,7 +246,7 @@ fn compute_class_profitability(
|
||||
)?)
|
||||
})?;
|
||||
|
||||
mr.compute_all(starting_indexes, exit, |v| {
|
||||
maxr.compute_all(starting_indexes, exit, |v| {
|
||||
Ok(v.compute_all_time_high_from(
|
||||
starting_indexes.dateindex,
|
||||
&ret.dateindex,
|
||||
|
||||
@@ -67,10 +67,10 @@ impl Vecs {
|
||||
)
|
||||
})?;
|
||||
|
||||
let period_max_drawdown = ByDcaPeriod::try_new(|name, _days| {
|
||||
let period_min_return = ByDcaPeriod::try_new(|name, _days| {
|
||||
ComputedFromDateLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_dca_max_drawdown"),
|
||||
&format!("{name}_dca_min_return"),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
@@ -130,10 +130,10 @@ impl Vecs {
|
||||
)
|
||||
})?;
|
||||
|
||||
let period_lump_sum_max_drawdown = ByDcaPeriod::try_new(|name, _days| {
|
||||
let period_lump_sum_min_return = ByDcaPeriod::try_new(|name, _days| {
|
||||
ComputedFromDateLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_lump_sum_max_drawdown"),
|
||||
&format!("{name}_lump_sum_min_return"),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
@@ -189,10 +189,10 @@ impl Vecs {
|
||||
)
|
||||
})?;
|
||||
|
||||
let class_max_drawdown = ByDcaClass::try_new(|name, _year, _dateindex| {
|
||||
let class_min_return = ByDcaClass::try_new(|name, _year, _dateindex| {
|
||||
ComputedFromDateLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_max_drawdown"),
|
||||
&format!("{name}_min_return"),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
@@ -214,20 +214,20 @@ impl Vecs {
|
||||
period_cagr,
|
||||
period_days_in_profit,
|
||||
period_days_in_loss,
|
||||
period_max_drawdown,
|
||||
period_min_return,
|
||||
period_max_return,
|
||||
period_lump_sum_stack,
|
||||
period_lump_sum_returns,
|
||||
period_lump_sum_days_in_profit,
|
||||
period_lump_sum_days_in_loss,
|
||||
period_lump_sum_max_drawdown,
|
||||
period_lump_sum_min_return,
|
||||
period_lump_sum_max_return,
|
||||
class_stack,
|
||||
class_average_price,
|
||||
class_returns,
|
||||
class_days_in_profit,
|
||||
class_days_in_loss,
|
||||
class_max_drawdown,
|
||||
class_min_return,
|
||||
class_max_return,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct Vecs {
|
||||
// DCA by period - profitability
|
||||
pub period_days_in_profit: ByDcaPeriod<ComputedFromDateLast<StoredU32>>,
|
||||
pub period_days_in_loss: ByDcaPeriod<ComputedFromDateLast<StoredU32>>,
|
||||
pub period_max_drawdown: ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
pub period_min_return: ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
pub period_max_return: ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
|
||||
// Lump sum by period (for comparison with DCA) - KISS types
|
||||
@@ -26,7 +26,7 @@ pub struct Vecs {
|
||||
// Lump sum by period - profitability
|
||||
pub period_lump_sum_days_in_profit: ByDcaPeriod<ComputedFromDateLast<StoredU32>>,
|
||||
pub period_lump_sum_days_in_loss: ByDcaPeriod<ComputedFromDateLast<StoredU32>>,
|
||||
pub period_lump_sum_max_drawdown: ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
pub period_lump_sum_min_return: ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
pub period_lump_sum_max_return: ByDcaPeriod<ComputedFromDateLast<StoredF32>>,
|
||||
|
||||
// DCA by year class - KISS types
|
||||
@@ -37,6 +37,6 @@ pub struct Vecs {
|
||||
// DCA by year class - profitability
|
||||
pub class_days_in_profit: ByDcaClass<ComputedFromDateLast<StoredU32>>,
|
||||
pub class_days_in_loss: ByDcaClass<ComputedFromDateLast<StoredU32>>,
|
||||
pub class_max_drawdown: ByDcaClass<ComputedFromDateLast<StoredF32>>,
|
||||
pub class_min_return: ByDcaClass<ComputedFromDateLast<StoredF32>>,
|
||||
pub class_max_return: ByDcaClass<ComputedFromDateLast<StoredF32>>,
|
||||
}
|
||||
|
||||
+19
-19
@@ -4121,20 +4121,20 @@ function createUtxoPattern(client, acc) {
|
||||
* @property {_10y2y3y4y5y6y8yPattern} periodCagr
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>} periodDaysInProfit
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>} periodDaysInLoss
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodMaxDrawdown
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodMinReturn
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodMaxReturn
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern3} periodLumpSumStack
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodLumpSumReturns
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>} periodLumpSumDaysInProfit
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredU32>} periodLumpSumDaysInLoss
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodLumpSumMaxDrawdown
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodLumpSumMinReturn
|
||||
* @property {_10y1m1w1y2y3m3y4y5y6m6y8yPattern2<StoredF32>} periodLumpSumMaxReturn
|
||||
* @property {MetricsTree_Market_Dca_ClassStack} classStack
|
||||
* @property {MetricsTree_Market_Dca_ClassAveragePrice} classAveragePrice
|
||||
* @property {_201520162017201820192020202120222023202420252026Pattern2<StoredF32>} classReturns
|
||||
* @property {MetricsTree_Market_Dca_ClassDaysInProfit} classDaysInProfit
|
||||
* @property {MetricsTree_Market_Dca_ClassDaysInLoss} classDaysInLoss
|
||||
* @property {MetricsTree_Market_Dca_ClassMaxDrawdown} classMaxDrawdown
|
||||
* @property {MetricsTree_Market_Dca_ClassMinReturn} classMinReturn
|
||||
* @property {MetricsTree_Market_Dca_ClassMaxReturn} classMaxReturn
|
||||
*/
|
||||
|
||||
@@ -4219,7 +4219,7 @@ function createUtxoPattern(client, acc) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetricsTree_Market_Dca_ClassMaxDrawdown
|
||||
* @typedef {Object} MetricsTree_Market_Dca_ClassMinReturn
|
||||
* @property {MetricPattern4<StoredF32>} _2015
|
||||
* @property {MetricPattern4<StoredF32>} _2016
|
||||
* @property {MetricPattern4<StoredF32>} _2017
|
||||
@@ -6317,13 +6317,13 @@ class BrkClient extends BrkClientBase {
|
||||
periodCagr: create_10y2y3y4y5y6y8yPattern(this, 'dca_cagr'),
|
||||
periodDaysInProfit: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_days_in_profit'),
|
||||
periodDaysInLoss: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_days_in_loss'),
|
||||
periodMaxDrawdown: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_max_drawdown'),
|
||||
periodMinReturn: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_min_return'),
|
||||
periodMaxReturn: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'dca_max_return'),
|
||||
periodLumpSumStack: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(this, 'lump_sum_stack'),
|
||||
periodLumpSumReturns: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_returns'),
|
||||
periodLumpSumDaysInProfit: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_days_in_profit'),
|
||||
periodLumpSumDaysInLoss: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_days_in_loss'),
|
||||
periodLumpSumMaxDrawdown: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_max_drawdown'),
|
||||
periodLumpSumMinReturn: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_min_return'),
|
||||
periodLumpSumMaxReturn: create_10y1m1w1y2y3m3y4y5y6m6y8yPattern2(this, 'lump_sum_max_return'),
|
||||
classStack: {
|
||||
_2015: createBitcoinDollarsSatsPattern5(this, 'dca_class_2015_stack'),
|
||||
@@ -6382,19 +6382,19 @@ class BrkClient extends BrkClientBase {
|
||||
_2025: createMetricPattern4(this, 'dca_class_2025_days_in_loss'),
|
||||
_2026: createMetricPattern4(this, 'dca_class_2026_days_in_loss'),
|
||||
},
|
||||
classMaxDrawdown: {
|
||||
_2015: createMetricPattern4(this, 'dca_class_2015_max_drawdown'),
|
||||
_2016: createMetricPattern4(this, 'dca_class_2016_max_drawdown'),
|
||||
_2017: createMetricPattern4(this, 'dca_class_2017_max_drawdown'),
|
||||
_2018: createMetricPattern4(this, 'dca_class_2018_max_drawdown'),
|
||||
_2019: createMetricPattern4(this, 'dca_class_2019_max_drawdown'),
|
||||
_2020: createMetricPattern4(this, 'dca_class_2020_max_drawdown'),
|
||||
_2021: createMetricPattern4(this, 'dca_class_2021_max_drawdown'),
|
||||
_2022: createMetricPattern4(this, 'dca_class_2022_max_drawdown'),
|
||||
_2023: createMetricPattern4(this, 'dca_class_2023_max_drawdown'),
|
||||
_2024: createMetricPattern4(this, 'dca_class_2024_max_drawdown'),
|
||||
_2025: createMetricPattern4(this, 'dca_class_2025_max_drawdown'),
|
||||
_2026: createMetricPattern4(this, 'dca_class_2026_max_drawdown'),
|
||||
classMinReturn: {
|
||||
_2015: createMetricPattern4(this, 'dca_class_2015_min_return'),
|
||||
_2016: createMetricPattern4(this, 'dca_class_2016_min_return'),
|
||||
_2017: createMetricPattern4(this, 'dca_class_2017_min_return'),
|
||||
_2018: createMetricPattern4(this, 'dca_class_2018_min_return'),
|
||||
_2019: createMetricPattern4(this, 'dca_class_2019_min_return'),
|
||||
_2020: createMetricPattern4(this, 'dca_class_2020_min_return'),
|
||||
_2021: createMetricPattern4(this, 'dca_class_2021_min_return'),
|
||||
_2022: createMetricPattern4(this, 'dca_class_2022_min_return'),
|
||||
_2023: createMetricPattern4(this, 'dca_class_2023_min_return'),
|
||||
_2024: createMetricPattern4(this, 'dca_class_2024_min_return'),
|
||||
_2025: createMetricPattern4(this, 'dca_class_2025_min_return'),
|
||||
_2026: createMetricPattern4(this, 'dca_class_2026_min_return'),
|
||||
},
|
||||
classMaxReturn: {
|
||||
_2015: createMetricPattern4(this, 'dca_class_2015_max_return'),
|
||||
|
||||
@@ -3590,22 +3590,22 @@ class MetricsTree_Market_Dca_ClassDaysInLoss:
|
||||
self._2025: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2025_days_in_loss')
|
||||
self._2026: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2026_days_in_loss')
|
||||
|
||||
class MetricsTree_Market_Dca_ClassMaxDrawdown:
|
||||
class MetricsTree_Market_Dca_ClassMinReturn:
|
||||
"""Metrics tree node."""
|
||||
|
||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||
self._2015: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2015_max_drawdown')
|
||||
self._2016: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2016_max_drawdown')
|
||||
self._2017: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2017_max_drawdown')
|
||||
self._2018: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2018_max_drawdown')
|
||||
self._2019: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2019_max_drawdown')
|
||||
self._2020: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2020_max_drawdown')
|
||||
self._2021: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2021_max_drawdown')
|
||||
self._2022: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2022_max_drawdown')
|
||||
self._2023: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2023_max_drawdown')
|
||||
self._2024: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2024_max_drawdown')
|
||||
self._2025: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2025_max_drawdown')
|
||||
self._2026: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2026_max_drawdown')
|
||||
self._2015: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2015_min_return')
|
||||
self._2016: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2016_min_return')
|
||||
self._2017: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2017_min_return')
|
||||
self._2018: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2018_min_return')
|
||||
self._2019: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2019_min_return')
|
||||
self._2020: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2020_min_return')
|
||||
self._2021: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2021_min_return')
|
||||
self._2022: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2022_min_return')
|
||||
self._2023: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2023_min_return')
|
||||
self._2024: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2024_min_return')
|
||||
self._2025: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2025_min_return')
|
||||
self._2026: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2026_min_return')
|
||||
|
||||
class MetricsTree_Market_Dca_ClassMaxReturn:
|
||||
"""Metrics tree node."""
|
||||
@@ -3634,20 +3634,20 @@ class MetricsTree_Market_Dca:
|
||||
self.period_cagr: _10y2y3y4y5y6y8yPattern = _10y2y3y4y5y6y8yPattern(client, 'dca_cagr')
|
||||
self.period_days_in_profit: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredU32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_days_in_profit')
|
||||
self.period_days_in_loss: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredU32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_days_in_loss')
|
||||
self.period_max_drawdown: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_max_drawdown')
|
||||
self.period_min_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_min_return')
|
||||
self.period_max_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'dca_max_return')
|
||||
self.period_lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3 = _10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, 'lump_sum_stack')
|
||||
self.period_lump_sum_returns: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_returns')
|
||||
self.period_lump_sum_days_in_profit: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredU32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_days_in_profit')
|
||||
self.period_lump_sum_days_in_loss: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredU32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_days_in_loss')
|
||||
self.period_lump_sum_max_drawdown: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_max_drawdown')
|
||||
self.period_lump_sum_min_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_min_return')
|
||||
self.period_lump_sum_max_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2[StoredF32] = _10y1m1w1y2y3m3y4y5y6m6y8yPattern2(client, 'lump_sum_max_return')
|
||||
self.class_stack: MetricsTree_Market_Dca_ClassStack = MetricsTree_Market_Dca_ClassStack(client)
|
||||
self.class_average_price: MetricsTree_Market_Dca_ClassAveragePrice = MetricsTree_Market_Dca_ClassAveragePrice(client)
|
||||
self.class_returns: _201520162017201820192020202120222023202420252026Pattern2[StoredF32] = _201520162017201820192020202120222023202420252026Pattern2(client, 'dca_class')
|
||||
self.class_days_in_profit: MetricsTree_Market_Dca_ClassDaysInProfit = MetricsTree_Market_Dca_ClassDaysInProfit(client)
|
||||
self.class_days_in_loss: MetricsTree_Market_Dca_ClassDaysInLoss = MetricsTree_Market_Dca_ClassDaysInLoss(client)
|
||||
self.class_max_drawdown: MetricsTree_Market_Dca_ClassMaxDrawdown = MetricsTree_Market_Dca_ClassMaxDrawdown(client)
|
||||
self.class_min_return: MetricsTree_Market_Dca_ClassMinReturn = MetricsTree_Market_Dca_ClassMinReturn(client)
|
||||
self.class_max_return: MetricsTree_Market_Dca_ClassMaxReturn = MetricsTree_Market_Dca_ClassMaxReturn(client)
|
||||
|
||||
class MetricsTree_Market_Indicators:
|
||||
|
||||
@@ -86,11 +86,10 @@ const fuchsia = createColor(() => getColor("fuchsia"));
|
||||
const pink = createColor(() => getColor("pink"));
|
||||
const rose = createColor(() => getColor("rose"));
|
||||
|
||||
export const colors = {
|
||||
const baseColors = {
|
||||
default: createColor(() => getLightDarkValue("--color")),
|
||||
gray: createColor(() => getColor("gray")),
|
||||
border: createColor(() => getLightDarkValue("--border-color")),
|
||||
|
||||
red,
|
||||
orange,
|
||||
amber,
|
||||
@@ -109,6 +108,10 @@ export const colors = {
|
||||
fuchsia,
|
||||
pink,
|
||||
rose,
|
||||
};
|
||||
|
||||
export const colors = {
|
||||
...baseColors,
|
||||
|
||||
/** Semantic stat colors for pattern helpers */
|
||||
stat: {
|
||||
@@ -123,9 +126,47 @@ export const colors = {
|
||||
pct10: fuchsia,
|
||||
min: red,
|
||||
},
|
||||
|
||||
/** DCA period colors by term */
|
||||
dcaPeriods: {
|
||||
// Short term
|
||||
_1w: red,
|
||||
_1m: orange,
|
||||
_3m: yellow,
|
||||
_6m: lime,
|
||||
// Medium term
|
||||
_1y: green,
|
||||
_2y: teal,
|
||||
_3y: cyan,
|
||||
// Long term
|
||||
_4y: sky,
|
||||
_5y: blue,
|
||||
_6y: indigo,
|
||||
_8y: violet,
|
||||
_10y: purple,
|
||||
},
|
||||
|
||||
/** DCA year colors by halving epoch */
|
||||
dcaYears: {
|
||||
// Epoch 5 (2024+)
|
||||
_2026: rose,
|
||||
_2025: fuchsia,
|
||||
_2024: purple,
|
||||
// Epoch 4 (2020-2023)
|
||||
_2023: violet,
|
||||
_2022: blue,
|
||||
_2021: sky,
|
||||
_2020: teal,
|
||||
// Epoch 3 (2016-2019)
|
||||
_2019: green,
|
||||
_2018: yellow,
|
||||
_2017: orange,
|
||||
_2016: red,
|
||||
_2015: pink,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {typeof colors} Colors
|
||||
* @typedef {Exclude<keyof Colors, "stat">} ColorName
|
||||
* @typedef {keyof typeof baseColors} ColorName
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { line, baseline, dots } from "./series.js";
|
||||
import { line, baseline, dots, dotted } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { spendableTypeColors } from "./colors/index.js";
|
||||
|
||||
@@ -70,31 +70,88 @@ export function createChainSection(ctx) {
|
||||
const addressTypes = [
|
||||
{ key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] },
|
||||
{ key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] },
|
||||
{ key: "p2wpkh", name: "P2WPKH", color: colors[spendableTypeColors.p2wpkh] },
|
||||
{
|
||||
key: "p2wpkh",
|
||||
name: "P2WPKH",
|
||||
color: colors[spendableTypeColors.p2wpkh],
|
||||
},
|
||||
{ key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] },
|
||||
{ key: "p2tr", name: "P2TR", color: colors[spendableTypeColors.p2tr] },
|
||||
{ key: "p2pk65", name: "P2PK65", color: colors[spendableTypeColors.p2pk65], defaultActive: false },
|
||||
{ key: "p2pk33", name: "P2PK33", color: colors[spendableTypeColors.p2pk33], defaultActive: false },
|
||||
{ key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a], defaultActive: false },
|
||||
{
|
||||
key: "p2pk65",
|
||||
name: "P2PK65",
|
||||
color: colors[spendableTypeColors.p2pk65],
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
key: "p2pk33",
|
||||
name: "P2PK33",
|
||||
color: colors[spendableTypeColors.p2pk33],
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
key: "p2a",
|
||||
name: "P2A",
|
||||
color: colors[spendableTypeColors.p2a],
|
||||
defaultActive: false,
|
||||
},
|
||||
];
|
||||
|
||||
// Activity types for mapping
|
||||
/** @type {ReadonlyArray<{key: "sending" | "receiving" | "both" | "reactivated" | "balanceIncreased" | "balanceDecreased", name: string, title: string, compareTitle: string}>} */
|
||||
const activityTypes = [
|
||||
{ key: "sending", name: "Sending", title: "Sending Address Count", compareTitle: "Sending Address Count by Type" },
|
||||
{ key: "receiving", name: "Receiving", title: "Receiving Address Count", compareTitle: "Receiving Address Count by Type" },
|
||||
{ key: "both", name: "Both", title: "Addresses Sending & Receiving (Same Block)", compareTitle: "Addresses Sending & Receiving by Type" },
|
||||
{ key: "reactivated", name: "Reactivated", title: "Reactivated Address Count (Was Empty)", compareTitle: "Reactivated Address Count by Type" },
|
||||
{ key: "balanceIncreased", name: "Balance Increased", title: "Addresses with Increased Balance", compareTitle: "Addresses with Increased Balance by Type" },
|
||||
{ key: "balanceDecreased", name: "Balance Decreased", title: "Addresses with Decreased Balance", compareTitle: "Addresses with Decreased Balance by Type" },
|
||||
{
|
||||
key: "sending",
|
||||
name: "Sending",
|
||||
title: "Sending Address Count",
|
||||
compareTitle: "Sending Address Count by Type",
|
||||
},
|
||||
{
|
||||
key: "receiving",
|
||||
name: "Receiving",
|
||||
title: "Receiving Address Count",
|
||||
compareTitle: "Receiving Address Count by Type",
|
||||
},
|
||||
{
|
||||
key: "both",
|
||||
name: "Both",
|
||||
title: "Addresses Sending & Receiving (Same Block)",
|
||||
compareTitle: "Addresses Sending & Receiving by Type",
|
||||
},
|
||||
{
|
||||
key: "reactivated",
|
||||
name: "Reactivated",
|
||||
title: "Reactivated Address Count (Was Empty)",
|
||||
compareTitle: "Reactivated Address Count by Type",
|
||||
},
|
||||
{
|
||||
key: "balanceIncreased",
|
||||
name: "Balance Increased",
|
||||
title: "Addresses with Increased Balance",
|
||||
compareTitle: "Addresses with Increased Balance by Type",
|
||||
},
|
||||
{
|
||||
key: "balanceDecreased",
|
||||
name: "Balance Decreased",
|
||||
title: "Addresses with Decreased Balance",
|
||||
compareTitle: "Addresses with Decreased Balance by Type",
|
||||
},
|
||||
];
|
||||
|
||||
// Count types for comparison charts
|
||||
/** @type {ReadonlyArray<{key: "addrCount" | "emptyAddrCount" | "totalAddrCount", name: string, title: string}>} */
|
||||
const countTypes = [
|
||||
{ key: "addrCount", name: "Loaded", title: "Address Count by Type" },
|
||||
{ key: "emptyAddrCount", name: "Empty", title: "Empty Address Count by Type" },
|
||||
{ key: "totalAddrCount", name: "Total", title: "Total Address Count by Type" },
|
||||
{
|
||||
key: "emptyAddrCount",
|
||||
name: "Empty",
|
||||
title: "Empty Address Count by Type",
|
||||
},
|
||||
{
|
||||
key: "totalAddrCount",
|
||||
name: "Total",
|
||||
title: "Total Address Count by Type",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -131,12 +188,18 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "New",
|
||||
title: `${titlePrefix}New Address Count`,
|
||||
bottom: fromFullStatsPattern({ pattern: distribution.newAddrCount[key], unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: distribution.newAddrCount[key],
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: `${titlePrefix}Address Growth Rate`,
|
||||
bottom: fromBaseStatsPattern({ pattern: distribution.growthRate[key], unit: Unit.ratio }),
|
||||
bottom: fromBaseStatsPattern({
|
||||
pattern: distribution.growthRate[key],
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
@@ -262,7 +325,12 @@ export function createChainSection(ctx) {
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.emerald,
|
||||
}),
|
||||
...fromValuePattern({ pattern: pool.fee, title: "fee", sumColor: colors.cyan, cumulativeColor: colors.indigo }),
|
||||
...fromValuePattern({
|
||||
pattern: pool.fee,
|
||||
title: "fee",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.indigo,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -296,7 +364,10 @@ export function createChainSection(ctx) {
|
||||
name: "Count",
|
||||
title: "Block Count",
|
||||
bottom: [
|
||||
...fromCountPattern({ pattern: blocks.count.blockCount, unit: Unit.count }),
|
||||
...fromCountPattern({
|
||||
pattern: blocks.count.blockCount,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count.blockCountTarget,
|
||||
name: "Target",
|
||||
@@ -338,7 +409,11 @@ export function createChainSection(ctx) {
|
||||
name: "Interval",
|
||||
title: "Block Interval",
|
||||
bottom: [
|
||||
...fromBaseStatsPattern({ pattern: blocks.interval, unit: Unit.secs, avgActive: false }),
|
||||
...fromBaseStatsPattern({
|
||||
pattern: blocks.interval,
|
||||
unit: Unit.secs,
|
||||
avgActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }),
|
||||
],
|
||||
},
|
||||
@@ -346,7 +421,10 @@ export function createChainSection(ctx) {
|
||||
name: "Size",
|
||||
title: "Block Size",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({ pattern: blocks.size, unit: Unit.bytes }),
|
||||
...fromSumStatsPattern({
|
||||
pattern: blocks.size,
|
||||
unit: Unit.bytes,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.totalSize,
|
||||
name: "Total",
|
||||
@@ -354,8 +432,14 @@ export function createChainSection(ctx) {
|
||||
unit: Unit.bytes,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...fromBaseStatsPattern({ pattern: blocks.vbytes, unit: Unit.vb }),
|
||||
...fromBaseStatsPattern({ pattern: blocks.weight, unit: Unit.wu }),
|
||||
...fromBaseStatsPattern({
|
||||
pattern: blocks.vbytes,
|
||||
unit: Unit.vb,
|
||||
}),
|
||||
...fromBaseStatsPattern({
|
||||
pattern: blocks.weight,
|
||||
unit: Unit.wu,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.sum,
|
||||
name: "Sum",
|
||||
@@ -375,7 +459,10 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "Fullness",
|
||||
title: "Block Fullness",
|
||||
bottom: fromBaseStatsPattern({ pattern: blocks.fullness, unit: Unit.percentage }),
|
||||
bottom: fromBaseStatsPattern({
|
||||
pattern: blocks.fullness,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -387,7 +474,10 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "Count",
|
||||
title: "Transaction Count",
|
||||
bottom: fromFullStatsPattern({ pattern: transactions.count.txCount, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: transactions.count.txCount,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Speed",
|
||||
@@ -404,7 +494,10 @@ export function createChainSection(ctx) {
|
||||
name: "Volume",
|
||||
title: "Transaction Volume",
|
||||
bottom: [
|
||||
...satsBtcUsd({ pattern: transactions.volume.sentSum, name: "Sent" }),
|
||||
...satsBtcUsd({
|
||||
pattern: transactions.volume.sentSum,
|
||||
name: "Sent",
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: transactions.volume.receivedSum,
|
||||
name: "Received",
|
||||
@@ -423,14 +516,23 @@ export function createChainSection(ctx) {
|
||||
name: "Size",
|
||||
title: "Transaction Size",
|
||||
bottom: [
|
||||
...fromStatsPattern({ pattern: transactions.size.weight, unit: Unit.wu }),
|
||||
...fromStatsPattern({ pattern: transactions.size.vsize, unit: Unit.vb }),
|
||||
...fromStatsPattern({
|
||||
pattern: transactions.size.weight,
|
||||
unit: Unit.wu,
|
||||
}),
|
||||
...fromStatsPattern({
|
||||
pattern: transactions.size.vsize,
|
||||
unit: Unit.vb,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fee Rate",
|
||||
title: "Fee Rate",
|
||||
bottom: fromStatsPattern({ pattern: transactions.fees.feeRate, unit: Unit.feeRate }),
|
||||
bottom: fromStatsPattern({
|
||||
pattern: transactions.fees.feeRate,
|
||||
unit: Unit.feeRate,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Versions",
|
||||
@@ -486,12 +588,22 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "Input Count",
|
||||
title: "Input Count",
|
||||
bottom: [...fromSumStatsPattern({ pattern: inputs.count, unit: Unit.count })],
|
||||
bottom: [
|
||||
...fromSumStatsPattern({
|
||||
pattern: inputs.count,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Output Count",
|
||||
title: "Output Count",
|
||||
bottom: [...fromSumStatsPattern({ pattern: outputs.count.totalCount, unit: Unit.count })],
|
||||
bottom: [
|
||||
...fromSumStatsPattern({
|
||||
pattern: outputs.count.totalCount,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Inputs/sec",
|
||||
@@ -543,17 +655,26 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "P2PKH",
|
||||
title: "P2PKH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pkh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2pkh,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "P2PK33",
|
||||
title: "P2PK33 Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk33, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2pk33,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "P2PK65",
|
||||
title: "P2PK65 Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk65, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2pk65,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -564,12 +685,18 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "P2SH",
|
||||
title: "P2SH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2sh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2sh,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "P2MS",
|
||||
title: "P2MS Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2ms, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2ms,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -580,17 +707,26 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "All SegWit",
|
||||
title: "SegWit Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.segwit, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.segwit,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "P2WPKH",
|
||||
title: "P2WPKH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wpkh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2wpkh,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "P2WSH",
|
||||
title: "P2WSH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wsh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2wsh,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -601,12 +737,18 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "P2TR",
|
||||
title: "P2TR Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2tr, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2tr,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "P2A",
|
||||
title: "P2A Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2a, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2a,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -617,17 +759,26 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.opreturn, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.opreturn,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
title: "Empty Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.emptyoutput, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.emptyoutput,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Unknown",
|
||||
title: "Unknown Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.unknownoutput, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.unknownoutput,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -701,7 +852,10 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "Circulating",
|
||||
title: "Circulating Supply",
|
||||
bottom: fromSupplyPattern({ pattern: supply.circulating, title: "Supply" }),
|
||||
bottom: fromSupplyPattern({
|
||||
pattern: supply.circulating,
|
||||
title: "Supply",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Inflation",
|
||||
@@ -769,9 +923,18 @@ export function createChainSection(ctx) {
|
||||
name: "Fee",
|
||||
title: "Transaction Fees",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.bitcoin, unit: Unit.btc }),
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.sats, unit: Unit.sats }),
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.dollars, unit: Unit.usd }),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.bitcoin,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.sats,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.dollars,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.feeDominance,
|
||||
name: "Dominance",
|
||||
@@ -873,7 +1036,8 @@ export function createChainSection(ctx) {
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.addressActivity[t.key][a.key].average,
|
||||
metric:
|
||||
distribution.addressActivity[t.key][a.key].average,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
@@ -935,12 +1099,11 @@ export function createChainSection(ctx) {
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
dotted({
|
||||
metric: blocks.difficulty.asHash,
|
||||
name: "Difficulty",
|
||||
color: colors.default,
|
||||
unit: Unit.hashRate,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -1025,19 +1188,17 @@ export function createChainSection(ctx) {
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
dotted({
|
||||
metric: blocks.mining.hashPriceThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
line({
|
||||
dotted({
|
||||
metric: blocks.mining.hashPricePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -1063,19 +1224,17 @@ export function createChainSection(ctx) {
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
dotted({
|
||||
metric: blocks.mining.hashValueThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
line({
|
||||
dotted({
|
||||
metric: blocks.mining.hashValuePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -108,6 +108,11 @@ export function initOptions(brk) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const blueprint = arr[i];
|
||||
|
||||
// Check for undefined metric
|
||||
if (!blueprint.metric) {
|
||||
throw new Error(`Blueprint has undefined metric: ${blueprint.title}`);
|
||||
}
|
||||
|
||||
// Check for price pattern blueprint (has dollars/sats sub-metrics)
|
||||
// Use unknown cast for safe property access check
|
||||
const maybePriceMetric = /** @type {{ dollars?: AnyMetricPattern, sats?: AnyMetricPattern }} */ (
|
||||
|
||||
@@ -0,0 +1,749 @@
|
||||
/** Investing section - Investment strategy tools and analysis */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { line, baseline, price, dotted } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Create Investing section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createInvestingSection(ctx) {
|
||||
const { brk } = ctx;
|
||||
const { market } = brk.metrics;
|
||||
const { dca, lookback, returns } = market;
|
||||
|
||||
return {
|
||||
name: "Investing",
|
||||
tree: [
|
||||
createDcaVsLumpSumSection(ctx, { dca, lookback, returns }),
|
||||
createDcaByPeriodSection(ctx, { dca }),
|
||||
createLumpSumByPeriodSection(ctx, { dca, lookback }),
|
||||
createDcaByStartYearSection(ctx, { dca }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** Period configuration by term group */
|
||||
const PERIODS = {
|
||||
short: [
|
||||
{ id: "1w", key: /** @type {const} */ ("_1w") },
|
||||
{ id: "1m", key: /** @type {const} */ ("_1m") },
|
||||
{ id: "3m", key: /** @type {const} */ ("_3m") },
|
||||
{ id: "6m", key: /** @type {const} */ ("_6m") },
|
||||
],
|
||||
medium: [
|
||||
{ id: "1y", key: /** @type {const} */ ("_1y") },
|
||||
{ id: "2y", key: /** @type {const} */ ("_2y") },
|
||||
{ id: "3y", key: /** @type {const} */ ("_3y") },
|
||||
],
|
||||
long: [
|
||||
{ id: "4y", key: /** @type {const} */ ("_4y") },
|
||||
{ id: "5y", key: /** @type {const} */ ("_5y") },
|
||||
{ id: "6y", key: /** @type {const} */ ("_6y") },
|
||||
{ id: "8y", key: /** @type {const} */ ("_8y") },
|
||||
{ id: "10y", key: /** @type {const} */ ("_10y") },
|
||||
],
|
||||
};
|
||||
|
||||
const ALL_PERIODS = [...PERIODS.short, ...PERIODS.medium, ...PERIODS.long];
|
||||
|
||||
/** DCA year classes by decade */
|
||||
const YEAR_GROUPS = {
|
||||
_2020s: /** @type {const} */ ([2026, 2025, 2024, 2023, 2022, 2021, 2020]),
|
||||
_2010s: /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]),
|
||||
};
|
||||
|
||||
const ALL_YEARS = [...YEAR_GROUPS._2020s, ...YEAR_GROUPS._2010s];
|
||||
|
||||
/** @typedef {ReturnType<typeof buildYearClass>} YearClass */
|
||||
|
||||
/**
|
||||
* Build DCA class data from year
|
||||
* @param {Colors} colors
|
||||
* @param {MarketDca} dca
|
||||
* @param {number} year
|
||||
*/
|
||||
function buildYearClass(colors, dca, year) {
|
||||
const key = /** @type {keyof Colors["dcaYears"]} */ (`_${year}`);
|
||||
return {
|
||||
year,
|
||||
color: colors.dcaYears[key],
|
||||
costBasis: dca.classAveragePrice[key],
|
||||
returns: dca.classReturns[key],
|
||||
stack: dca.classStack[key],
|
||||
daysInProfit: dca.classDaysInProfit[key],
|
||||
daysInLoss: dca.classDaysInLoss[key],
|
||||
minReturn: dca.classMinReturn[key],
|
||||
maxReturn: dca.classMaxReturn[key],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pattern for creating a single entry (period or year)
|
||||
* @typedef {Object} SingleEntryPattern
|
||||
* @property {string} name - Display name
|
||||
* @property {string} [titlePrefix] - Prefix for chart titles (defaults to name)
|
||||
* @property {Color} color - Primary color
|
||||
* @property {AnyPricePattern} costBasis - Cost basis metric
|
||||
* @property {AnyMetricPattern} returns - Returns metric
|
||||
* @property {AnyMetricPattern} minReturn - Min return metric
|
||||
* @property {AnyMetricPattern} maxReturn - Max return metric
|
||||
* @property {AnyMetricPattern} daysInProfit - Days in profit metric
|
||||
* @property {AnyMetricPattern} daysInLoss - Days in loss metric
|
||||
* @property {AnyValuePattern} stack - Stack pattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* Item for compare charts
|
||||
* @typedef {Object} CompareItem
|
||||
* @property {string} name - Display name
|
||||
* @property {Color} color - Item color
|
||||
* @property {AnyPricePattern} costBasis - Cost basis metric
|
||||
* @property {AnyMetricPattern} returns - Returns metric
|
||||
* @property {AnyMetricPattern} daysInProfit - Days in profit metric
|
||||
* @property {AnyMetricPattern} daysInLoss - Days in loss metric
|
||||
* @property {AnyValuePattern} stack - Stack pattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create profitability folder for compare charts
|
||||
* @param {string} context
|
||||
* @param {CompareItem[]} items
|
||||
*/
|
||||
function createProfitabilityFolder(context, items) {
|
||||
const top = items.map(({ name, color, costBasis }) =>
|
||||
price({ metric: costBasis, name, color }),
|
||||
);
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Days in Profit",
|
||||
title: `Days in Profit: ${context}`,
|
||||
top,
|
||||
bottom: items.map(({ name, color, daysInProfit }) =>
|
||||
line({ metric: daysInProfit, name, color, unit: Unit.days }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Days in Loss",
|
||||
title: `Days in Loss: ${context}`,
|
||||
top,
|
||||
bottom: items.map(({ name, color, daysInLoss }) =>
|
||||
line({ metric: daysInLoss, name, color, unit: Unit.days }),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create compare folder from items
|
||||
* @param {string} context
|
||||
* @param {CompareItem[]} items
|
||||
*/
|
||||
function createCompareFolder(context, items) {
|
||||
const topPane = items.map(({ name, color, costBasis }) =>
|
||||
price({ metric: costBasis, name, color }),
|
||||
);
|
||||
return {
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Cost Basis",
|
||||
title: `Cost Basis: ${context}`,
|
||||
top: topPane,
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `Returns: ${context}`,
|
||||
top: topPane,
|
||||
bottom: items.map(({ name, color, returns }) =>
|
||||
baseline({
|
||||
metric: returns,
|
||||
name,
|
||||
color: [color, color],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
),
|
||||
},
|
||||
createProfitabilityFolder(context, items),
|
||||
{
|
||||
name: "Accumulated",
|
||||
title: `Accumulated Value: ${context}`,
|
||||
top: topPane,
|
||||
bottom: items.flatMap(({ name, color, stack }) =>
|
||||
satsBtcUsd({ pattern: stack, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single entry from a pattern
|
||||
* @param {Colors} colors
|
||||
* @param {SingleEntryPattern} pattern
|
||||
*/
|
||||
function createSingleEntry(colors, pattern) {
|
||||
const {
|
||||
name,
|
||||
titlePrefix = name,
|
||||
color,
|
||||
costBasis,
|
||||
returns,
|
||||
minReturn,
|
||||
maxReturn,
|
||||
daysInProfit,
|
||||
daysInLoss,
|
||||
stack,
|
||||
} = pattern;
|
||||
const top = [price({ metric: costBasis, name: "Cost Basis", color })];
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{ name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top },
|
||||
{
|
||||
name: "Returns",
|
||||
title: `Returns: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: [
|
||||
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
|
||||
dotted({
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
title: `Profitability: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: [
|
||||
line({
|
||||
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" }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA vs Lump Sum section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
* @param {Market["lookback"]} args.lookback
|
||||
* @param {Market["returns"]} args.returns
|
||||
*/
|
||||
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
// Chart builders
|
||||
/** @param {AllPeriodKey} key */
|
||||
const topPane = (key) => [
|
||||
price({
|
||||
metric: dca.periodAveragePrice[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
}),
|
||||
price({ metric: lookback[key], name: "Lump Sum", color: colors.orange }),
|
||||
];
|
||||
|
||||
/** @param {string} name @param {AllPeriodKey} key */
|
||||
const costBasisChart = (name, key) => ({
|
||||
name: "Cost Basis",
|
||||
title: `Cost Basis: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
});
|
||||
|
||||
/** @param {string} name @param {AllPeriodKey} key */
|
||||
const returnsFolder = (name, key) => ({
|
||||
name: "Returns",
|
||||
tree: [
|
||||
{
|
||||
name: "Current",
|
||||
title: `Returns: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodReturns[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumReturns[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
title: `Max Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodMaxReturn[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMaxReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
title: `Min Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodMinReturn[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMinReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {string} name @param {LongPeriodKey} key */
|
||||
const returnsFolderWithCagr = (name, key) => ({
|
||||
name: "Returns",
|
||||
tree: [
|
||||
{
|
||||
name: "Current",
|
||||
title: `Returns: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodReturns[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumReturns[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodCagr[key],
|
||||
name: "DCA CAGR",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: returns.cagr[key],
|
||||
name: "Lump Sum CAGR",
|
||||
color: colors.indigo,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
title: `Max Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodMaxReturn[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumMaxReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
title: `Min Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodMinReturn[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumMinReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {string} name @param {AllPeriodKey} key */
|
||||
const profitabilityFolder = (name, key) => ({
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Days in Profit",
|
||||
title: `Days in Profit: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodDaysInProfit[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumDaysInProfit[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Days in Loss",
|
||||
title: `Days in Loss: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodDaysInLoss[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumDaysInLoss[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {string} name @param {AllPeriodKey} key */
|
||||
const stackChart = (name, key) => ({
|
||||
name: "Accumulated",
|
||||
title: `Accumulated Value: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
...satsBtcUsd({
|
||||
pattern: dca.periodStack[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: dca.periodLumpSumStack[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if a period key has CAGR data
|
||||
* @param {AllPeriodKey} key
|
||||
* @returns {key is LongPeriodKey}
|
||||
*/
|
||||
const hasCagr = (key) => key in dca.periodCagr;
|
||||
|
||||
/**
|
||||
* Create individual period entry
|
||||
* @param {{ id: string, key: AllPeriodKey }} period
|
||||
*/
|
||||
const createPeriodEntry = ({ id, key }) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
hasCagr(key)
|
||||
? returnsFolderWithCagr(name, key)
|
||||
: returnsFolder(name, key),
|
||||
profitabilityFolder(name, key),
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create term group
|
||||
* @param {string} name
|
||||
* @param {string} title
|
||||
* @param {{ id: string, key: AllPeriodKey }[]} periods
|
||||
*/
|
||||
const createTermGroup = (name, title, periods) => ({
|
||||
name,
|
||||
title,
|
||||
tree: periods.map(createPeriodEntry),
|
||||
});
|
||||
|
||||
return {
|
||||
name: "DCA vs Lump Sum",
|
||||
title: "Compare Investment Strategies",
|
||||
tree: [
|
||||
createTermGroup("Short Term", "Under 1 Year", PERIODS.short),
|
||||
createTermGroup("Medium Term", "1-3 Years", PERIODS.medium),
|
||||
createTermGroup("Long Term", "4+ Years", PERIODS.long),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA by Period section (DCA only, no Lump Sum comparison)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
*/
|
||||
export function createDcaByPeriodSection(ctx, { dca }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
/**
|
||||
* Create compare charts for a set of periods
|
||||
* @param {string} context
|
||||
* @param {{ id: string, key: AllPeriodKey }[]} periods
|
||||
*/
|
||||
const createCompare = (context, periods) =>
|
||||
createCompareFolder(
|
||||
context,
|
||||
periods.map(({ id, key }) => ({
|
||||
name: id,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: dca.periodAveragePrice[key],
|
||||
returns: dca.periodReturns[key],
|
||||
daysInProfit: dca.periodDaysInProfit[key],
|
||||
daysInLoss: dca.periodDaysInLoss[key],
|
||||
stack: dca.periodStack[key],
|
||||
})),
|
||||
);
|
||||
|
||||
/**
|
||||
* Create individual period entry (DCA only)
|
||||
* @param {{ id: string, key: AllPeriodKey }} period
|
||||
*/
|
||||
const createPeriodEntry = ({ id, key }) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return createSingleEntry(colors, {
|
||||
name,
|
||||
titlePrefix: `${name} DCA`,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: dca.periodAveragePrice[key],
|
||||
returns: dca.periodReturns[key],
|
||||
maxReturn: dca.periodMaxReturn[key],
|
||||
minReturn: dca.periodMinReturn[key],
|
||||
daysInProfit: dca.periodDaysInProfit[key],
|
||||
daysInLoss: dca.periodDaysInLoss[key],
|
||||
stack: dca.periodStack[key],
|
||||
});
|
||||
};
|
||||
|
||||
/** @param {string} name @param {string} title @param {{ id: string, key: AllPeriodKey }[]} periods */
|
||||
const createTermGroup = (name, title, periods) => ({
|
||||
name,
|
||||
title,
|
||||
tree: [
|
||||
createCompare(`${name} DCA`, periods),
|
||||
...periods.map(createPeriodEntry),
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
name: "DCA by Period",
|
||||
title: "DCA Performance by Investment Period",
|
||||
tree: [
|
||||
createCompare("All Periods DCA", ALL_PERIODS),
|
||||
createTermGroup("Short Term", "Under 1 Year", PERIODS.short),
|
||||
createTermGroup("Medium Term", "1-3 Years", PERIODS.medium),
|
||||
createTermGroup("Long Term", "4+ Years", PERIODS.long),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Lump Sum by Period section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
* @param {Market["lookback"]} args.lookback
|
||||
*/
|
||||
export function createLumpSumByPeriodSection(ctx, { dca, lookback }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
/**
|
||||
* Create compare charts for a set of periods
|
||||
* @param {string} context
|
||||
* @param {{ id: string, key: AllPeriodKey }[]} periods
|
||||
*/
|
||||
const createCompare = (context, periods) =>
|
||||
createCompareFolder(
|
||||
context,
|
||||
periods.map(({ id, key }) => ({
|
||||
name: id,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: lookback[key],
|
||||
returns: dca.periodLumpSumReturns[key],
|
||||
daysInProfit: dca.periodLumpSumDaysInProfit[key],
|
||||
daysInLoss: dca.periodLumpSumDaysInLoss[key],
|
||||
stack: dca.periodLumpSumStack[key],
|
||||
})),
|
||||
);
|
||||
|
||||
/**
|
||||
* Create individual period entry (Lump Sum only)
|
||||
* @param {{ id: string, key: AllPeriodKey }} period
|
||||
*/
|
||||
const createPeriodEntry = ({ id, key }) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return createSingleEntry(colors, {
|
||||
name,
|
||||
titlePrefix: `${name} Lump Sum`,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: lookback[key],
|
||||
returns: dca.periodLumpSumReturns[key],
|
||||
maxReturn: dca.periodLumpSumMaxReturn[key],
|
||||
minReturn: dca.periodLumpSumMinReturn[key],
|
||||
daysInProfit: dca.periodLumpSumDaysInProfit[key],
|
||||
daysInLoss: dca.periodLumpSumDaysInLoss[key],
|
||||
stack: dca.periodLumpSumStack[key],
|
||||
});
|
||||
};
|
||||
|
||||
/** @param {string} name @param {string} title @param {{ id: string, key: AllPeriodKey }[]} periods */
|
||||
const createTermGroup = (name, title, periods) => ({
|
||||
name,
|
||||
title,
|
||||
tree: [
|
||||
createCompare(`${name} Lump Sum`, periods),
|
||||
...periods.map(createPeriodEntry),
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
name: "Lump Sum by Period",
|
||||
title: "Lump Sum Performance by Investment Period",
|
||||
tree: [
|
||||
createCompare("All Periods Lump Sum", ALL_PERIODS),
|
||||
createTermGroup("Short Term", "Under 1 Year", PERIODS.short),
|
||||
createTermGroup("Medium Term", "1-3 Years", PERIODS.medium),
|
||||
createTermGroup("Long Term", "4+ Years", PERIODS.long),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA by Start Year section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
*/
|
||||
export function createDcaByStartYearSection(ctx, { dca }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
/**
|
||||
* Convert YearClass to CompareItem
|
||||
* @param {YearClass} c
|
||||
* @returns {CompareItem}
|
||||
*/
|
||||
const toCompareItem = (c) => ({
|
||||
name: `${c.year}`,
|
||||
color: c.color,
|
||||
costBasis: c.costBasis,
|
||||
returns: c.returns,
|
||||
daysInProfit: c.daysInProfit,
|
||||
daysInLoss: c.daysInLoss,
|
||||
stack: c.stack,
|
||||
});
|
||||
|
||||
/**
|
||||
* Create individual year entry
|
||||
* @param {YearClass} yearClass
|
||||
*/
|
||||
const createYearEntry = (yearClass) =>
|
||||
createSingleEntry(colors, {
|
||||
name: `${yearClass.year}`,
|
||||
titlePrefix: `${yearClass.year} DCA`,
|
||||
color: yearClass.color,
|
||||
costBasis: yearClass.costBasis,
|
||||
returns: yearClass.returns,
|
||||
maxReturn: yearClass.maxReturn,
|
||||
minReturn: yearClass.minReturn,
|
||||
daysInProfit: yearClass.daysInProfit,
|
||||
daysInLoss: yearClass.daysInLoss,
|
||||
stack: yearClass.stack,
|
||||
});
|
||||
|
||||
/** @param {string} name @param {string} title @param {YearClass[]} classes */
|
||||
const createDecadeGroup = (name, title, classes) => ({
|
||||
name,
|
||||
title,
|
||||
tree: [
|
||||
createCompareFolder(`${name} DCA`, classes.map(toCompareItem)),
|
||||
...classes.map(createYearEntry),
|
||||
],
|
||||
});
|
||||
|
||||
// Build all classes once, then filter by decade
|
||||
const allClasses = ALL_YEARS.map((year) => buildYearClass(colors, dca, year));
|
||||
const classes2020s = allClasses.filter((c) => c.year >= 2020);
|
||||
const classes2010s = allClasses.filter((c) => c.year < 2020);
|
||||
|
||||
return {
|
||||
name: "DCA by Start Year",
|
||||
title: "DCA Performance by When You Started",
|
||||
tree: [
|
||||
createCompareFolder("All Years DCA", allClasses.map(toCompareItem)),
|
||||
createDecadeGroup("2020s", "2020-2026", classes2020s),
|
||||
createDecadeGroup("2010s", "2015-2019", classes2010s),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { price } from "../series.js";
|
||||
import { createPriceRatioCharts } from "../shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
import { periodIdToName } from "../utils.js";
|
||||
|
||||
/**
|
||||
* @param {Colors} colors
|
||||
@@ -79,12 +79,16 @@ const COMPARISON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
|
||||
*/
|
||||
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);
|
||||
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);
|
||||
}).filter(
|
||||
/** @type {(p: any) => p is { id: string, sma: ReturnType<typeof buildSmaAverages>[number], ema: ReturnType<typeof buildEmaAverages>[number] }} */ (
|
||||
p,
|
||||
) => p !== null,
|
||||
);
|
||||
|
||||
return {
|
||||
name: "Compare",
|
||||
@@ -93,8 +97,17 @@ function createCompareSection(smaAverages, emaAverages) {
|
||||
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, options: { lineStyle: 1 } }),
|
||||
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 }) => ({
|
||||
@@ -102,7 +115,12 @@ function createCompareSection(smaAverages, emaAverages) {
|
||||
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, options: { lineStyle: 1 } }),
|
||||
price({
|
||||
metric: ema.ratio.price,
|
||||
name: "EMA",
|
||||
color: ema.color,
|
||||
style: 1,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
@@ -123,8 +141,12 @@ export function createAveragesSection(ctx, movingAverage) {
|
||||
* @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));
|
||||
const commonAverages = averages.filter(({ id }) =>
|
||||
COMMON_PERIODS.includes(id),
|
||||
);
|
||||
const moreAverages = averages.filter(
|
||||
({ id }) => !COMMON_PERIODS.includes(id),
|
||||
);
|
||||
|
||||
return {
|
||||
name: label,
|
||||
|
||||
@@ -8,10 +8,6 @@ import { createMomentumSection } from "./momentum.js";
|
||||
import { createVolatilitySection } from "./volatility.js";
|
||||
import { createBandsSection } from "./bands.js";
|
||||
import { createValuationSection } from "./onchain.js";
|
||||
import {
|
||||
createDcaVsLumpSumSection,
|
||||
createDcaByYearSection,
|
||||
} from "./investing.js";
|
||||
|
||||
/**
|
||||
* Create Market section
|
||||
@@ -21,16 +17,7 @@ import {
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk } = ctx;
|
||||
const { market, supply } = brk.metrics;
|
||||
const {
|
||||
movingAverage,
|
||||
ath,
|
||||
returns,
|
||||
volatility,
|
||||
range,
|
||||
dca,
|
||||
lookback,
|
||||
indicators,
|
||||
} = market;
|
||||
const { movingAverage, ath, returns, volatility, range, indicators } = market;
|
||||
|
||||
return {
|
||||
name: "Market",
|
||||
@@ -183,12 +170,6 @@ export function createMarketSection(ctx) {
|
||||
|
||||
// Valuation
|
||||
createValuationSection(ctx, { indicators, movingAverage }),
|
||||
|
||||
// DCA vs Lump Sum
|
||||
createDcaVsLumpSumSection(ctx, { dca, lookback, returns }),
|
||||
|
||||
// DCA by Year
|
||||
createDcaByYearSection(ctx, { dca }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
/** Investing section (DCA) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { line, baseline, price } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Build DCA classes data array
|
||||
* @param {Colors} colors
|
||||
* @param {MarketDca} dca
|
||||
*/
|
||||
export function buildDcaClasses(colors, dca) {
|
||||
return /** @type {const} */ ([
|
||||
[2026, "rose", true],
|
||||
[2025, "pink", true],
|
||||
[2024, "fuchsia", true],
|
||||
[2023, "purple", true],
|
||||
[2022, "blue", true],
|
||||
[2021, "sky", true],
|
||||
[2020, "teal", true],
|
||||
[2019, "green", true],
|
||||
[2018, "yellow", true],
|
||||
[2017, "orange", true],
|
||||
[2016, "red", false],
|
||||
[2015, "pink", false],
|
||||
]).map(([year, colorKey, defaultActive]) => ({
|
||||
year,
|
||||
color: colors[colorKey],
|
||||
defaultActive,
|
||||
costBasis: dca.classAveragePrice[`_${year}`],
|
||||
returns: dca.classReturns[`_${year}`],
|
||||
stack: dca.classStack[`_${year}`],
|
||||
daysInProfit: dca.classDaysInProfit[`_${year}`],
|
||||
daysInLoss: dca.classDaysInLoss[`_${year}`],
|
||||
maxDrawdown: dca.classMaxDrawdown[`_${year}`],
|
||||
maxReturn: dca.classMaxReturn[`_${year}`],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA vs Lump Sum section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
* @param {Market["lookback"]} args.lookback
|
||||
* @param {Market["returns"]} args.returns
|
||||
*/
|
||||
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {AllPeriodKey} key
|
||||
*/
|
||||
const costBasisChart = (name, key) => ({
|
||||
name: "Cost Basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
price({
|
||||
metric: dca.periodAveragePrice[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
}),
|
||||
price({
|
||||
metric: lookback[key],
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {AllPeriodKey} key
|
||||
*/
|
||||
const daysInProfitChart = (name, key) => ({
|
||||
name: "Days in Profit",
|
||||
title: `${name} Days in Profit`,
|
||||
top: [
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ 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 }),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {AllPeriodKey} key
|
||||
*/
|
||||
const daysInLossChart = (name, key) => ({
|
||||
name: "Days in Loss",
|
||||
title: `${name} Days in Loss`,
|
||||
top: [
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.red, unit: Unit.days }),
|
||||
line({ metric: dca.periodLumpSumDaysInLoss[key], name: "Lump sum", color: colors.orange, unit: Unit.days }),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {AllPeriodKey} key
|
||||
*/
|
||||
const maxDrawdownChart = (name, key) => ({
|
||||
name: "Max Drawdown",
|
||||
title: `${name} Max Drawdown`,
|
||||
top: [
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodMaxDrawdown[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
|
||||
line({ metric: dca.periodLumpSumMaxDrawdown[key], name: "Lump sum", color: colors.orange, unit: Unit.percentage }),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {AllPeriodKey} key
|
||||
*/
|
||||
const maxReturnChart = (name, key) => ({
|
||||
name: "Max Return",
|
||||
title: `${name} Max Return`,
|
||||
top: [
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodMaxReturn[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
|
||||
line({ metric: dca.periodLumpSumMaxReturn[key], name: "Lump sum", color: colors.orange, unit: Unit.percentage }),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {AllPeriodKey} key
|
||||
*/
|
||||
const stackChart = (name, key) => ({
|
||||
name: "Stack",
|
||||
title: `${name} Stack`,
|
||||
bottom: [
|
||||
...satsBtcUsd({ pattern: dca.periodStack[key], name: "DCA", color: colors.green }),
|
||||
...satsBtcUsd({ pattern: dca.periodLumpSumStack[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {ShortPeriodKey} key
|
||||
*/
|
||||
const createPeriodTree = (id, key) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
daysInProfitChart(name, key),
|
||||
daysInLossChart(name, key),
|
||||
maxDrawdownChart(name, key),
|
||||
maxReturnChart(name, key),
|
||||
],
|
||||
},
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {LongPeriodKey} key
|
||||
*/
|
||||
const createPeriodTreeWithCagr = (id, key) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
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,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodCagr[key],
|
||||
name: "DCA CAGR",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: returns.cagr[key],
|
||||
name: "Lump sum CAGR",
|
||||
color: colors.indigo,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
daysInProfitChart(name, key),
|
||||
daysInLossChart(name, key),
|
||||
maxDrawdownChart(name, key),
|
||||
maxReturnChart(name, key),
|
||||
],
|
||||
},
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "DCA vs Lump Sum",
|
||||
tree: [
|
||||
createPeriodTree("1w", "_1w"),
|
||||
createPeriodTree("1m", "_1m"),
|
||||
createPeriodTree("3m", "_3m"),
|
||||
createPeriodTree("6m", "_6m"),
|
||||
createPeriodTree("1y", "_1y"),
|
||||
createPeriodTreeWithCagr("2y", "_2y"),
|
||||
createPeriodTreeWithCagr("3y", "_3y"),
|
||||
createPeriodTreeWithCagr("4y", "_4y"),
|
||||
createPeriodTreeWithCagr("5y", "_5y"),
|
||||
createPeriodTreeWithCagr("6y", "_6y"),
|
||||
createPeriodTreeWithCagr("8y", "_8y"),
|
||||
createPeriodTreeWithCagr("10y", "_10y"),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA by Year section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["dca"]} args.dca
|
||||
*/
|
||||
export function createDcaByYearSection(ctx, { dca }) {
|
||||
const { colors } = ctx;
|
||||
const dcaClasses = buildDcaClasses(colors, dca);
|
||||
|
||||
return {
|
||||
name: "DCA by Year",
|
||||
tree: [
|
||||
// Comparison charts (all years overlaid)
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: "DCA Cost Basis",
|
||||
top: dcaClasses.map(({ year, color, defaultActive, costBasis }) =>
|
||||
price({
|
||||
metric: costBasis,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: "DCA Returns",
|
||||
bottom: dcaClasses.map(({ year, defaultActive, returns }) =>
|
||||
baseline({
|
||||
metric: returns,
|
||||
name: `${year}`,
|
||||
defaultActive,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
title: "DCA Profitability",
|
||||
bottom: [
|
||||
...dcaClasses.map(({ year, color, defaultActive, daysInProfit }) =>
|
||||
line({
|
||||
metric: daysInProfit,
|
||||
name: `${year} Days in Profit`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
),
|
||||
...dcaClasses.map(({ year, color, daysInLoss }) =>
|
||||
line({
|
||||
metric: daysInLoss,
|
||||
name: `${year} Days in Loss`,
|
||||
color,
|
||||
defaultActive: false,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: "DCA Stack",
|
||||
bottom: dcaClasses.flatMap(
|
||||
({ year, color, defaultActive, stack }) =>
|
||||
satsBtcUsd({ pattern: stack, name: `${year}`, color, defaultActive }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Individual year charts
|
||||
...dcaClasses.map(
|
||||
({
|
||||
year,
|
||||
color,
|
||||
costBasis,
|
||||
returns,
|
||||
stack,
|
||||
daysInProfit,
|
||||
daysInLoss,
|
||||
maxDrawdown,
|
||||
maxReturn,
|
||||
}) => ({
|
||||
name: `${year}`,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost Basis",
|
||||
title: `${year} Cost Basis`,
|
||||
top: [
|
||||
price({
|
||||
metric: costBasis,
|
||||
name: "Cost Basis",
|
||||
color,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${year} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: returns,
|
||||
name: "Returns",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
title: `${year} Profitability`,
|
||||
bottom: [
|
||||
line({
|
||||
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,
|
||||
}),
|
||||
line({
|
||||
metric: maxDrawdown,
|
||||
name: "Max Drawdown",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: maxReturn,
|
||||
name: "Max Return",
|
||||
color: colors.cyan,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${year} Stack`,
|
||||
bottom: satsBtcUsd({ pattern: stack, name: "Stack", color }),
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { baseline } from "../series.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
import { periodIdToName } from "../utils.js";
|
||||
|
||||
/**
|
||||
* Create Returns section
|
||||
@@ -41,7 +41,9 @@ export function createReturnsSection(ctx, returns) {
|
||||
*/
|
||||
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 cagr = cagrKey
|
||||
? returns.cagr[/** @type {keyof typeof returns.cagr} */ (cagrKey)]
|
||||
: undefined;
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
@@ -75,13 +77,50 @@ export function createReturnsSection(ctx, returns) {
|
||||
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 }),
|
||||
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,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,592 @@
|
||||
/** Mining section - Network security and miner economics */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { line, baseline, dots, dotted } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
|
||||
/** Major pools to show in Compare section (by current hashrate dominance) */
|
||||
const MAJOR_POOL_IDS = [
|
||||
"foundryusa", // ~32% - largest pool
|
||||
"antpool", // ~18% - Bitmain-owned
|
||||
"viabtc", // ~14% - independent
|
||||
"f2pool", // ~10% - one of the oldest pools
|
||||
"marapool", // MARA Holdings
|
||||
"braiinspool", // formerly Slush Pool
|
||||
"spiderpool", // growing Asian pool
|
||||
"ocean", // decentralization-focused
|
||||
];
|
||||
|
||||
/**
|
||||
* AntPool & friends - pools sharing AntPool's block templates
|
||||
* Based on b10c's research: https://b10c.me/blog/015-bitcoin-mining-centralization/
|
||||
* Collectively ~35-40% of network hashrate
|
||||
*/
|
||||
const ANTPOOL_AND_FRIENDS_IDS = [
|
||||
"antpool", // Bitmain-owned, template source
|
||||
"poolin", // shares AntPool templates
|
||||
"btccom", // CloverPool (formerly BTC.com)
|
||||
"braiinspool", // shares AntPool templates
|
||||
"ultimuspool", // shares AntPool templates
|
||||
"binancepool", // shares AntPool templates
|
||||
"secpool", // shares AntPool templates
|
||||
"sigmapoolcom", // SigmaPool
|
||||
"rawpool", // shares AntPool templates
|
||||
"luxor", // shares AntPool templates
|
||||
];
|
||||
|
||||
/**
|
||||
* Create Mining section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMiningSection(ctx) {
|
||||
const {
|
||||
colors,
|
||||
brk,
|
||||
fromSumStatsPattern,
|
||||
fromCoinbasePattern,
|
||||
fromValuePattern,
|
||||
} = ctx;
|
||||
const { blocks, transactions, pools } = brk.metrics;
|
||||
|
||||
// Build pools tree dynamically
|
||||
const poolEntries = Object.entries(pools.vecs);
|
||||
const poolsTree = poolEntries.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase())
|
||||
] || key;
|
||||
return {
|
||||
name: poolName,
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: `Dominance: ${poolName}`,
|
||||
bottom: [
|
||||
dots({
|
||||
metric: pool._24hDominance,
|
||||
name: "24h",
|
||||
color: colors.pink,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wDominance,
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1mDominance,
|
||||
name: "1m",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1yDominance,
|
||||
name: "1y",
|
||||
color: colors.lime,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool.dominance,
|
||||
name: "All Time",
|
||||
color: colors.teal,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: `Blocks Mined: ${poolName}`,
|
||||
bottom: [
|
||||
dots({
|
||||
metric: pool.blocksMined.sum,
|
||||
name: "Sum",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: pool.blocksMined.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.blue,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._24hBlocksMined,
|
||||
name: "24h sum",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wBlocksMined,
|
||||
name: "1w sum",
|
||||
color: colors.red,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: "1m sum",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1yBlocksMined,
|
||||
name: "1y sum",
|
||||
color: colors.purple,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rewards",
|
||||
title: `Rewards: ${poolName}`,
|
||||
bottom: [
|
||||
...fromValuePattern({
|
||||
pattern: pool.coinbase,
|
||||
title: "coinbase",
|
||||
sumColor: colors.orange,
|
||||
cumulativeColor: colors.red,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.subsidy,
|
||||
title: "subsidy",
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.emerald,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.fee,
|
||||
title: "fee",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.indigo,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Since Last Block",
|
||||
title: `Since Last Block: ${poolName}`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pool.blocksSinceBlock,
|
||||
name: "Elapsed",
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: pool.daysSinceBlock,
|
||||
name: "Elapsed",
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
name: "Mining",
|
||||
tree: [
|
||||
// Hashrate
|
||||
{
|
||||
name: "Hashrate",
|
||||
title: "Network Hashrate",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: blocks.mining.hashRate,
|
||||
name: "Hashrate",
|
||||
unit: Unit.hashRate,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate1wSma,
|
||||
name: "1w SMA",
|
||||
color: colors.red,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate1mSma,
|
||||
name: "1m SMA",
|
||||
color: colors.orange,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate2mSma,
|
||||
name: "2m SMA",
|
||||
color: colors.yellow,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate1ySma,
|
||||
name: "1y SMA",
|
||||
color: colors.lime,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
dotted({
|
||||
metric: blocks.difficulty.asHash,
|
||||
name: "Difficulty",
|
||||
color: colors.default,
|
||||
unit: Unit.hashRate,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// Difficulty
|
||||
{
|
||||
name: "Difficulty",
|
||||
tree: [
|
||||
{
|
||||
name: "Current",
|
||||
title: "Mining Difficulty",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.difficulty.raw,
|
||||
name: "Difficulty",
|
||||
unit: Unit.difficulty,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Epoch",
|
||||
title: "Difficulty Epoch",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.difficulty.epoch,
|
||||
name: "Epoch",
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adjustment",
|
||||
title: "Difficulty Adjustment",
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: blocks.difficulty.adjustment,
|
||||
name: "Change",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
priceLine({ ctx, number: 0, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Countdown",
|
||||
title: "Next Difficulty Adjustment",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.difficulty.blocksBeforeNextAdjustment,
|
||||
name: "Remaining",
|
||||
color: colors.indigo,
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.daysBeforeNextAdjustment,
|
||||
name: "Remaining",
|
||||
color: colors.purple,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Revenue
|
||||
{
|
||||
name: "Revenue",
|
||||
tree: [
|
||||
{
|
||||
name: "Coinbase",
|
||||
title: "Coinbase Rewards",
|
||||
bottom: [
|
||||
...fromCoinbasePattern({ pattern: blocks.rewards.coinbase }),
|
||||
...satsBtcUsd({
|
||||
pattern: blocks.rewards._24hCoinbaseSum,
|
||||
name: "24h sum",
|
||||
color: colors.pink,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Subsidy",
|
||||
title: "Block Subsidy",
|
||||
bottom: [
|
||||
...fromCoinbasePattern({ pattern: blocks.rewards.subsidy }),
|
||||
line({
|
||||
metric: blocks.rewards.subsidyDominance,
|
||||
name: "Dominance",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.subsidyUsd1ySma,
|
||||
name: "1y SMA",
|
||||
color: colors.lime,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fees",
|
||||
title: "Transaction Fee Revenue",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.bitcoin,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.sats,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.dollars,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.feeDominance,
|
||||
name: "Dominance",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Unclaimed",
|
||||
title: "Unclaimed Rewards",
|
||||
bottom: fromValuePattern({
|
||||
pattern: blocks.rewards.unclaimedRewards,
|
||||
title: "Unclaimed",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Economics
|
||||
{
|
||||
name: "Economics",
|
||||
tree: [
|
||||
{
|
||||
name: "Hash Price",
|
||||
title: "Hash Price",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashPriceThs,
|
||||
name: "TH/s",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPricePhs,
|
||||
name: "PH/s",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPriceRebound,
|
||||
name: "Rebound",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
dotted({
|
||||
metric: blocks.mining.hashPriceThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
}),
|
||||
dotted({
|
||||
metric: blocks.mining.hashPricePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Hash Value",
|
||||
title: "Hash Value",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashValueThs,
|
||||
name: "TH/s",
|
||||
color: colors.orange,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValuePhs,
|
||||
name: "PH/s",
|
||||
color: colors.orange,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValueRebound,
|
||||
name: "Rebound",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
dotted({
|
||||
metric: blocks.mining.hashValueThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
}),
|
||||
dotted({
|
||||
metric: blocks.mining.hashValuePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Halving
|
||||
{
|
||||
name: "Halving",
|
||||
tree: [
|
||||
{
|
||||
name: "Countdown",
|
||||
title: "Next Halving",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.halving.blocksBeforeNextHalving,
|
||||
name: "Remaining",
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.halving.daysBeforeNextHalving,
|
||||
name: "Remaining",
|
||||
color: colors.blue,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Epoch",
|
||||
title: "Halving Epoch",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.halving.epoch,
|
||||
name: "Epoch",
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Pools
|
||||
{
|
||||
name: "Pools",
|
||||
tree: [
|
||||
// Compare section (major pools only)
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "Dominance: Major Pools",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) => MAJOR_POOL_IDS.includes(key.toLowerCase()))
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (
|
||||
key.toLowerCase()
|
||||
)
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mDominance,
|
||||
name: poolName,
|
||||
unit: Unit.percentage,
|
||||
});
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: "Blocks Mined: Major Pools (1m)",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) => MAJOR_POOL_IDS.includes(key.toLowerCase()))
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (
|
||||
key.toLowerCase()
|
||||
)
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: poolName,
|
||||
unit: Unit.count,
|
||||
});
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
// AntPool & friends - pools sharing block templates
|
||||
{
|
||||
name: "AntPool & Friends",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "Dominance: AntPool & Friends",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) =>
|
||||
ANTPOOL_AND_FRIENDS_IDS.includes(key.toLowerCase()),
|
||||
)
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (
|
||||
key.toLowerCase()
|
||||
)
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mDominance,
|
||||
name: poolName,
|
||||
unit: Unit.percentage,
|
||||
});
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: "Blocks Mined: AntPool & Friends (1m)",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) =>
|
||||
ANTPOOL_AND_FRIENDS_IDS.includes(key.toLowerCase()),
|
||||
)
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (
|
||||
key.toLowerCase()
|
||||
)
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: poolName,
|
||||
unit: Unit.count,
|
||||
});
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Individual pools
|
||||
{
|
||||
name: "Individual",
|
||||
tree: poolsTree,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
/** Network section - On-chain activity and health */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { line, dots } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { spendableTypeColors } from "./colors/index.js";
|
||||
|
||||
/**
|
||||
* Create Network section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createNetworkSection(ctx) {
|
||||
const {
|
||||
colors,
|
||||
brk,
|
||||
fromSumStatsPattern,
|
||||
fromBaseStatsPattern,
|
||||
fromFullStatsPattern,
|
||||
fromStatsPattern,
|
||||
fromCoinbasePattern,
|
||||
fromValuePattern,
|
||||
fromCountPattern,
|
||||
fromSupplyPattern,
|
||||
} = ctx;
|
||||
const {
|
||||
blocks,
|
||||
transactions,
|
||||
inputs,
|
||||
outputs,
|
||||
scripts,
|
||||
supply,
|
||||
distribution,
|
||||
} = brk.metrics;
|
||||
|
||||
// Address types for mapping (using spendableTypeColors for consistency)
|
||||
/** @type {ReadonlyArray<{key: AddressableType, name: string, color: Color, defaultActive?: boolean}>} */
|
||||
const addressTypes = [
|
||||
{ key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] },
|
||||
{ key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] },
|
||||
{ key: "p2wpkh", name: "P2WPKH", color: colors[spendableTypeColors.p2wpkh] },
|
||||
{ key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] },
|
||||
{ key: "p2tr", name: "P2TR", color: colors[spendableTypeColors.p2tr] },
|
||||
{ key: "p2pk65", name: "P2PK65", color: colors[spendableTypeColors.p2pk65], defaultActive: false },
|
||||
{ key: "p2pk33", name: "P2PK33", color: colors[spendableTypeColors.p2pk33], defaultActive: false },
|
||||
{ key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a], defaultActive: false },
|
||||
];
|
||||
|
||||
// Activity types for mapping
|
||||
/** @type {ReadonlyArray<{key: "sending" | "receiving" | "both" | "reactivated" | "balanceIncreased" | "balanceDecreased", name: string, title: string, compareTitle: string}>} */
|
||||
const activityTypes = [
|
||||
{ key: "sending", name: "Sending", title: "Sending Address Count", compareTitle: "Sending Address Count by Type" },
|
||||
{ key: "receiving", name: "Receiving", title: "Receiving Address Count", compareTitle: "Receiving Address Count by Type" },
|
||||
{ key: "both", name: "Both", title: "Addresses Sending & Receiving (Same Block)", compareTitle: "Addresses Sending & Receiving by Type" },
|
||||
{ key: "reactivated", name: "Reactivated", title: "Reactivated Address Count (Was Empty)", compareTitle: "Reactivated Address Count by Type" },
|
||||
{ key: "balanceIncreased", name: "Balance Increased", title: "Addresses with Increased Balance", compareTitle: "Addresses with Increased Balance by Type" },
|
||||
{ key: "balanceDecreased", name: "Balance Decreased", title: "Addresses with Decreased Balance", compareTitle: "Addresses with Decreased Balance by Type" },
|
||||
];
|
||||
|
||||
// Count types for comparison charts
|
||||
/** @type {ReadonlyArray<{key: "addrCount" | "emptyAddrCount" | "totalAddrCount", name: string, title: string}>} */
|
||||
const countTypes = [
|
||||
{ key: "addrCount", name: "Loaded", title: "Address Count by Type" },
|
||||
{ key: "emptyAddrCount", name: "Empty", title: "Empty Address Count by Type" },
|
||||
{ key: "totalAddrCount", name: "Total", title: "Total Address Count by Type" },
|
||||
];
|
||||
|
||||
/**
|
||||
* Create address metrics tree for a given type key
|
||||
* @param {AddressableType | "all"} key
|
||||
* @param {string} titlePrefix
|
||||
*/
|
||||
const createAddressMetricsTree = (key, titlePrefix) => [
|
||||
{
|
||||
name: "Count",
|
||||
title: `${titlePrefix}Address Count`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: distribution.addrCount[key],
|
||||
name: "Loaded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.totalAddrCount[key],
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount[key],
|
||||
name: "Empty",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "New",
|
||||
title: `${titlePrefix}New Address Count`,
|
||||
bottom: fromFullStatsPattern({ pattern: distribution.newAddrCount[key], unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: `${titlePrefix}Address Growth Rate`,
|
||||
bottom: fromBaseStatsPattern({ pattern: distribution.growthRate[key], unit: Unit.ratio }),
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
tree: activityTypes.map((a) => ({
|
||||
name: a.name,
|
||||
title: `${titlePrefix}${a.name} Address Count`,
|
||||
bottom: fromBaseStatsPattern({
|
||||
pattern: distribution.addressActivity[key][a.key],
|
||||
unit: Unit.count,
|
||||
}),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
name: "Network",
|
||||
tree: [
|
||||
// Transactions
|
||||
{
|
||||
name: "Transactions",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
title: "Transaction Count",
|
||||
bottom: fromFullStatsPattern({ pattern: transactions.count.txCount, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "Per Second",
|
||||
title: "Transactions Per Second",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: transactions.volume.txPerSec,
|
||||
name: "TPS",
|
||||
unit: Unit.perSec,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Volume",
|
||||
title: "Transaction Volume",
|
||||
bottom: [
|
||||
...satsBtcUsd({ pattern: transactions.volume.sentSum, name: "Sent" }),
|
||||
...satsBtcUsd({
|
||||
pattern: transactions.volume.receivedSum,
|
||||
name: "Received",
|
||||
color: colors.cyan,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: transactions.volume.annualizedVolume,
|
||||
name: "Annualized",
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Size",
|
||||
title: "Transaction Size",
|
||||
bottom: [
|
||||
...fromStatsPattern({ pattern: transactions.size.weight, unit: Unit.wu }),
|
||||
...fromStatsPattern({ pattern: transactions.size.vsize, unit: Unit.vb }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Versions",
|
||||
title: "Transaction Versions",
|
||||
bottom: [
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v1,
|
||||
unit: Unit.count,
|
||||
title: "v1",
|
||||
sumColor: colors.orange,
|
||||
cumulativeColor: colors.red,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v2,
|
||||
unit: Unit.count,
|
||||
title: "v2",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.blue,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v3,
|
||||
unit: Unit.count,
|
||||
title: "v3",
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.green,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Velocity",
|
||||
title: "Transaction Velocity",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.velocity.btc,
|
||||
name: "Bitcoin",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: supply.velocity.usd,
|
||||
name: "Dollars",
|
||||
color: colors.emerald,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Fees
|
||||
{
|
||||
name: "Fees",
|
||||
tree: [
|
||||
{
|
||||
name: "Fee Rate",
|
||||
title: "Fee Rate",
|
||||
bottom: fromStatsPattern({ pattern: transactions.fees.feeRate, unit: Unit.feeRate }),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Fees",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.bitcoin, unit: Unit.btc }),
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.sats, unit: Unit.sats }),
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.dollars, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Blocks
|
||||
{
|
||||
name: "Blocks",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
title: "Block Count",
|
||||
bottom: [
|
||||
...fromCountPattern({ pattern: blocks.count.blockCount, unit: Unit.count }),
|
||||
line({
|
||||
metric: blocks.count.blockCountTarget,
|
||||
name: "Target",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
options: { lineStyle: 4 },
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count._24hBlockCount,
|
||||
name: "24h sum",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count._1wBlockCount,
|
||||
name: "1w sum",
|
||||
color: colors.red,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count._1mBlockCount,
|
||||
name: "1m sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count._1yBlockCount,
|
||||
name: "1y sum",
|
||||
color: colors.purple,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Interval",
|
||||
title: "Block Interval",
|
||||
bottom: [
|
||||
...fromBaseStatsPattern({ pattern: blocks.interval, unit: Unit.secs, avgActive: false }),
|
||||
priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Size",
|
||||
title: "Block Size",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({ pattern: blocks.size, unit: Unit.bytes }),
|
||||
line({
|
||||
metric: blocks.totalSize,
|
||||
name: "Total",
|
||||
color: colors.purple,
|
||||
unit: Unit.bytes,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...fromBaseStatsPattern({ pattern: blocks.vbytes, unit: Unit.vb }),
|
||||
...fromBaseStatsPattern({ pattern: blocks.weight, unit: Unit.wu }),
|
||||
line({
|
||||
metric: blocks.weight.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fullness",
|
||||
title: "Block Fullness",
|
||||
bottom: fromBaseStatsPattern({ pattern: blocks.fullness, unit: Unit.percentage }),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// UTXO Set
|
||||
{
|
||||
name: "UTXO Set",
|
||||
tree: [
|
||||
{
|
||||
name: "UTXO Count",
|
||||
title: "UTXO Count",
|
||||
bottom: [
|
||||
line({
|
||||
metric: outputs.count.utxoCount,
|
||||
name: "Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Inputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
title: "Input Count",
|
||||
bottom: [...fromSumStatsPattern({ pattern: inputs.count, unit: Unit.count })],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
title: "Inputs Per Second",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: transactions.volume.inputsPerSec,
|
||||
name: "Inputs/sec",
|
||||
unit: Unit.perSec,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
title: "Output Count",
|
||||
bottom: [...fromSumStatsPattern({ pattern: outputs.count.totalCount, unit: Unit.count })],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
title: "Outputs Per Second",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: transactions.volume.outputsPerSec,
|
||||
name: "Outputs/sec",
|
||||
unit: Unit.perSec,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Addresses
|
||||
{
|
||||
name: "Addresses",
|
||||
tree: [
|
||||
// Overview - global metrics for all addresses
|
||||
{ name: "Overview", tree: createAddressMetricsTree("all", "") },
|
||||
|
||||
// Compare - cross-type comparisons
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: countTypes.map((c) => ({
|
||||
name: c.name,
|
||||
title: c.title,
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
metric: distribution[c.key][t.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "New",
|
||||
title: "New Address Count by Type",
|
||||
bottom: addressTypes.flatMap((t) => [
|
||||
dots({
|
||||
metric: distribution.newAddrCount[t.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.newAddrCount[t.key].average,
|
||||
name: `${t.name} Avg`,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: "Address Growth Rate by Type",
|
||||
bottom: addressTypes.flatMap((t) => [
|
||||
dots({
|
||||
metric: distribution.growthRate[t.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.growthRate[t.key].average,
|
||||
name: `${t.name} Avg`,
|
||||
color: t.color,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
tree: activityTypes.map((a) => ({
|
||||
name: a.name,
|
||||
title: a.compareTitle,
|
||||
bottom: addressTypes.flatMap((t) => [
|
||||
dots({
|
||||
metric: distribution.addressActivity[t.key][a.key].base,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.addressActivity[t.key][a.key].average,
|
||||
name: `${t.name} Avg`,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
]),
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Individual address types
|
||||
...addressTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressMetricsTree(t.key, `${t.name} `),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Scripts
|
||||
{
|
||||
name: "Scripts",
|
||||
tree: [
|
||||
{
|
||||
name: "Output Counts",
|
||||
tree: [
|
||||
// Legacy scripts
|
||||
{
|
||||
name: "Legacy",
|
||||
tree: [
|
||||
{
|
||||
name: "P2PKH",
|
||||
title: "P2PKH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pkh, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "P2PK33",
|
||||
title: "P2PK33 Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk33, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "P2PK65",
|
||||
title: "P2PK65 Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk65, unit: Unit.count }),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Script Hash
|
||||
{
|
||||
name: "Script Hash",
|
||||
tree: [
|
||||
{
|
||||
name: "P2SH",
|
||||
title: "P2SH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2sh, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "P2MS",
|
||||
title: "P2MS Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2ms, unit: Unit.count }),
|
||||
},
|
||||
],
|
||||
},
|
||||
// SegWit scripts
|
||||
{
|
||||
name: "SegWit",
|
||||
tree: [
|
||||
{
|
||||
name: "All SegWit",
|
||||
title: "SegWit Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.segwit, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "P2WPKH",
|
||||
title: "P2WPKH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wpkh, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "P2WSH",
|
||||
title: "P2WSH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wsh, unit: Unit.count }),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Taproot scripts
|
||||
{
|
||||
name: "Taproot",
|
||||
tree: [
|
||||
{
|
||||
name: "P2TR",
|
||||
title: "P2TR Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2tr, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "P2A",
|
||||
title: "P2A Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2a, unit: Unit.count }),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Other scripts
|
||||
{
|
||||
name: "Other",
|
||||
tree: [
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.opreturn, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
title: "Empty Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.emptyoutput, unit: Unit.count }),
|
||||
},
|
||||
{
|
||||
name: "Unknown",
|
||||
title: "Unknown Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.unknownoutput, unit: Unit.count }),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adoption",
|
||||
tree: [
|
||||
{
|
||||
name: "SegWit",
|
||||
title: "SegWit Adoption",
|
||||
bottom: [
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.base,
|
||||
name: "Base",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Taproot",
|
||||
title: "Taproot Adoption",
|
||||
bottom: [
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.base,
|
||||
name: "Base",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Supply
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "Circulating",
|
||||
title: "Circulating Supply",
|
||||
bottom: fromSupplyPattern({ pattern: supply.circulating, title: "Supply" }),
|
||||
},
|
||||
{
|
||||
name: "Inflation",
|
||||
title: "Inflation Rate",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: supply.inflation,
|
||||
name: "Rate",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Burned",
|
||||
tree: [
|
||||
{
|
||||
name: "Unspendable",
|
||||
title: "Unspendable Supply",
|
||||
bottom: fromValuePattern({ pattern: supply.burned.unspendable }),
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Burned",
|
||||
bottom: [
|
||||
...fromValuePattern({ pattern: supply.burned.opreturn }),
|
||||
...fromCoinbasePattern({ pattern: scripts.value.opreturn }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -16,8 +16,10 @@ import {
|
||||
createAddressCohortFolder,
|
||||
} from "./distribution/index.js";
|
||||
import { createMarketSection } from "./market/index.js";
|
||||
import { createChainSection } from "./chain.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
import { createMiningSection } from "./mining.js";
|
||||
import { createCointimeSection } from "./cointime.js";
|
||||
import { createInvestingSection } from "./investing.js";
|
||||
import { colors } from "../chart/colors.js";
|
||||
|
||||
// Re-export types for external consumers
|
||||
@@ -95,8 +97,11 @@ export function createPartialOptions({ brk }) {
|
||||
// Market section
|
||||
createMarketSection(ctx),
|
||||
|
||||
// Chain section
|
||||
createChainSection(ctx),
|
||||
// Network section (on-chain activity)
|
||||
createNetworkSection(ctx),
|
||||
|
||||
// Mining section (security & economics)
|
||||
createMiningSection(ctx),
|
||||
|
||||
// Cohorts section
|
||||
{
|
||||
@@ -294,6 +299,9 @@ export function createPartialOptions({ brk }) {
|
||||
name: "Frameworks",
|
||||
tree: [createCointimeSection(ctx)],
|
||||
},
|
||||
|
||||
// Investing section
|
||||
createInvestingSection(ctx),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -146,6 +146,24 @@ export function line({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Omit<Parameters<typeof line>[0], 'style'>} args
|
||||
*/
|
||||
export function dotted(args) {
|
||||
const _args = /** @type {Parameters<typeof line>[0]} */ (args);
|
||||
_args.style = 1;
|
||||
return line(_args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Omit<Parameters<typeof line>[0], 'style'>} args
|
||||
*/
|
||||
export function sparseDotted(args) {
|
||||
const _args = /** @type {Parameters<typeof line>[0]} */ (args);
|
||||
_args.style = 4;
|
||||
return line(_args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Dots series (line with only point markers visible)
|
||||
* @param {Object} args
|
||||
@@ -329,7 +347,10 @@ export function fromSumStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
* @param {boolean} [args.avgActive]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBaseStatsPattern(colors, { pattern, unit, title = "", baseColor, avgActive = true }) {
|
||||
export function fromBaseStatsPattern(
|
||||
colors,
|
||||
{ pattern, unit, title = "", baseColor, avgActive = true },
|
||||
) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title || "base", color: baseColor, unit },
|
||||
@@ -441,9 +462,21 @@ export function fromAnyFullStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
*/
|
||||
export function fromCoinbasePattern(colors, { pattern, title = "" }) {
|
||||
return [
|
||||
...fromAnyFullStatsPattern(colors, { pattern: pattern.bitcoin, unit: Unit.btc, title }),
|
||||
...fromAnyFullStatsPattern(colors, { pattern: pattern.sats, unit: Unit.sats, title }),
|
||||
...fromAnyFullStatsPattern(colors, { pattern: pattern.dollars, unit: Unit.usd, title }),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.bitcoin,
|
||||
unit: Unit.btc,
|
||||
title,
|
||||
}),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.sats,
|
||||
unit: Unit.sats,
|
||||
title,
|
||||
}),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.dollars,
|
||||
unit: Unit.usd,
|
||||
title,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -457,7 +490,10 @@ export function fromCoinbasePattern(colors, { pattern, title = "" }) {
|
||||
* @param {Color} [args.cumulativeColor]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromValuePattern(colors, { pattern, title = "", sumColor, cumulativeColor }) {
|
||||
export function fromValuePattern(
|
||||
colors,
|
||||
{ pattern, title = "", sumColor, cumulativeColor },
|
||||
) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.bitcoin.sum,
|
||||
@@ -469,7 +505,7 @@ export function fromValuePattern(colors, { pattern, title = "", sumColor, cumula
|
||||
metric: pattern.bitcoin.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
unit: Unit.btc,
|
||||
unit: Unit.btcCumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
@@ -482,7 +518,7 @@ export function fromValuePattern(colors, { pattern, title = "", sumColor, cumula
|
||||
metric: pattern.sats.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
unit: Unit.sats,
|
||||
unit: Unit.satsCumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
@@ -495,7 +531,7 @@ export function fromValuePattern(colors, { pattern, title = "", sumColor, cumula
|
||||
metric: pattern.dollars.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
unit: Unit.usd,
|
||||
unit: Unit.usdCumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
];
|
||||
@@ -513,7 +549,10 @@ export function fromValuePattern(colors, { pattern, title = "", sumColor, cumula
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBitcoinPatternWithUnit(colors, { pattern, unit, title = "", sumColor, cumulativeColor, defaultActive }) {
|
||||
export function fromBitcoinPatternWithUnit(
|
||||
colors,
|
||||
{ pattern, unit, title = "", sumColor, cumulativeColor, defaultActive },
|
||||
) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.sum,
|
||||
@@ -543,7 +582,10 @@ export function fromBitcoinPatternWithUnit(colors, { pattern, unit, title = "",
|
||||
* @param {Color} [args.cumulativeColor]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromCountPattern(colors, { pattern, unit, title = "", sumColor, cumulativeColor }) {
|
||||
export function fromCountPattern(
|
||||
colors,
|
||||
{ pattern, unit, title = "", sumColor, cumulativeColor },
|
||||
) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.sum,
|
||||
|
||||
@@ -15,7 +15,7 @@ export const formatCohortTitle = (cohortTitle) =>
|
||||
/**
|
||||
* Create sats/btc/usd line series from a pattern with .sats/.bitcoin/.dollars
|
||||
* @param {Object} args
|
||||
* @param {{ sats: AnyMetricPattern, bitcoin: AnyMetricPattern, dollars: AnyMetricPattern }} args.pattern
|
||||
* @param {AnyValuePattern} args.pattern
|
||||
* @param {string} args.name
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
* Any pattern with dollars and sats sub-metrics (auto-expands to USD + sats)
|
||||
* @typedef {{ dollars: AnyMetricPattern, sats: AnyMetricPattern }} AnyPricePattern
|
||||
*
|
||||
* Any pattern with sats, bitcoin, and dollars sub-metrics (value patterns like stack)
|
||||
* @typedef {{ sats: AnyMetricPattern, bitcoin: AnyMetricPattern, dollars: AnyMetricPattern }} AnyValuePattern
|
||||
*
|
||||
* Top pane price series - requires a price pattern with dollars/sats, auto-expands to USD + sats
|
||||
* @typedef {{ metric: AnyPricePattern }} FetchedPriceSeriesOptions
|
||||
* @typedef {LineSeriesBlueprint & FetchedPriceSeriesOptions} FetchedPriceSeriesBlueprint
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*
|
||||
* @import { WebSockets } from "./utils/ws.js"
|
||||
*
|
||||
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortMinAge, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupMinAge, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern } from "./options/partial.js"
|
||||
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortMinAge, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupMinAge, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern, AnyValuePattern } from "./options/partial.js"
|
||||
*
|
||||
*
|
||||
* @import { UnitObject as Unit } from "./utils/units.js"
|
||||
|
||||
@@ -9,6 +9,11 @@ export const Unit = /** @type {const} */ ({
|
||||
btc: { id: "btc", name: "Bitcoin" },
|
||||
usd: { id: "usd", name: "US Dollars" },
|
||||
|
||||
// Cumulative value units (running totals)
|
||||
satsCumulative: { id: "sats-total", name: "Satoshis (Total)" },
|
||||
btcCumulative: { id: "btc-total", name: "Bitcoin (Total)" },
|
||||
usdCumulative: { id: "usd-total", name: "US Dollars (Total)" },
|
||||
|
||||
// Ratios & percentages
|
||||
percentage: { id: "percentage", name: "Percentage" },
|
||||
ratio: { id: "ratio", name: "Ratio" },
|
||||
@@ -30,6 +35,7 @@ export const Unit = /** @type {const} */ ({
|
||||
|
||||
// Counts
|
||||
count: { id: "count", name: "Count" },
|
||||
countCumulative: { id: "count-total", name: "Count (Total)" },
|
||||
blocks: { id: "blocks", name: "Blocks" },
|
||||
|
||||
// Size
|
||||
|
||||
Reference in New Issue
Block a user