global: add some market charts

This commit is contained in:
nym21
2025-05-09 16:04:54 +02:00
parent 851a6aac0e
commit 9f20664c6e
17 changed files with 449 additions and 46 deletions

View File

@@ -96,6 +96,7 @@ If you'd like to have your own instance hosted for you please contact [hosting@b
- Configurated for speed (`raw + eager`)
- Updates delivered at your convenience
- Direct communication for feature requests and support
- Bitcoin Core or Knots with desired version
- Optional subdomains: `*.bitcoinresearchkit.org`, `*.kibo.money` and `*.satonomics.xyz`
- Logo featured in the Readme if desired

View File

@@ -8,7 +8,7 @@ use brk_vec::{
};
use crate::storage::{
marketprice,
fetched,
vecs::{Indexes, indexes},
};
@@ -68,7 +68,7 @@ impl ComputedValueVecsFromHeight {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
marketprices: Option<&marketprice::Vecs>,
fetched: Option<&fetched::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
@@ -91,14 +91,7 @@ impl ComputedValueVecsFromHeight {
)?;
let height: Option<&StoredVec<Height, Sats>> = None;
self.compute_rest(
indexer,
indexes,
marketprices,
starting_indexes,
exit,
height,
)?;
self.compute_rest(indexer, indexes, fetched, starting_indexes, exit, height)?;
Ok(())
}
@@ -107,7 +100,7 @@ impl ComputedValueVecsFromHeight {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
marketprices: Option<&marketprice::Vecs>,
fetched: Option<&fetched::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
height: Option<&impl CollectableVec<Height, Sats>>,
@@ -147,7 +140,7 @@ impl ComputedValueVecsFromHeight {
}
let txindex = self.bitcoin.height.as_ref().unwrap();
let price = &marketprices.as_ref().unwrap().chainindexes_to_close.height;
let price = &fetched.as_ref().unwrap().chainindexes_to_close.height;
if let Some(dollars) = self.dollars.as_mut() {
dollars.compute_all(

View File

@@ -9,7 +9,7 @@ use brk_vec::{
};
use crate::storage::{
marketprice,
fetched,
vecs::{Indexes, indexes},
};
@@ -48,11 +48,11 @@ impl ComputedValueVecsFromTxindex {
version: Version,
computation: Computation,
compressed: Compressed,
marketprices: Option<&marketprice::Vecs>,
fetched: Option<&fetched::Vecs>,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let compute_source = source.is_none();
let compute_dollars = marketprices.is_some();
let compute_dollars = fetched.is_some();
let sats = ComputedVecsFromTxindex::forced_import(
path,
@@ -84,7 +84,7 @@ impl ComputedValueVecsFromTxindex {
options,
)?;
let dollars_txindex = marketprices.map(|marketprices| {
let dollars_txindex = fetched.map(|fetched| {
ComputedVecFrom3::forced_import_or_init_from_3(
computation,
path,
@@ -93,7 +93,7 @@ impl ComputedValueVecsFromTxindex {
compressed,
bitcoin_txindex.boxed_clone(),
indexes.txindex_to_height.boxed_clone(),
marketprices.chainindexes_to_close.height.boxed_clone(),
fetched.chainindexes_to_close.height.boxed_clone(),
|txindex: TxIndex,
txindex_to_btc_iter,
txindex_to_height_iter,
@@ -138,7 +138,7 @@ impl ComputedValueVecsFromTxindex {
// &mut self,
// indexer: &Indexer,
// indexes: &indexes::Vecs,
// marketprices: Option<&marketprice::Vecs>,
// fetched: Option<&marketprice::Vecs>,
// starting_indexes: &Indexes,
// exit: &Exit,
// mut compute: F,
@@ -164,7 +164,7 @@ impl ComputedValueVecsFromTxindex {
// self.compute_rest(
// indexer,
// indexes,
// marketprices,
// fetched,
// starting_indexes,
// exit,
// txindex,

View File

@@ -0,0 +1,253 @@
use std::{fs, path::Path};
use brk_core::{Dollars, StoredF64, StoredUsize};
use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, Compressed, Computation, StoredIndex, VecIterator, Version};
use super::{
Indexes, fetched,
grouped::{ComputedVecsFromDateindex, StorableVecGeneatorOptions},
indexes, transactions,
};
#[derive(Clone)]
pub struct Vecs {
pub indexes_to_marketcap: ComputedVecsFromDateindex<Dollars>,
pub indexes_to_ath: ComputedVecsFromDateindex<Dollars>,
pub indexes_to_drawdown: ComputedVecsFromDateindex<StoredF64>,
pub indexes_to_days_since_ath: ComputedVecsFromDateindex<StoredUsize>,
pub indexes_to_max_days_between_ath: ComputedVecsFromDateindex<StoredUsize>,
pub indexes_to_max_years_between_ath: ComputedVecsFromDateindex<StoredF64>,
}
impl Vecs {
pub fn forced_import(
path: &Path,
_computation: Computation,
compressed: Compressed,
) -> color_eyre::Result<Self> {
fs::create_dir_all(path)?;
Ok(Self {
indexes_to_marketcap: ComputedVecsFromDateindex::forced_import(
path,
"marketcap",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_ath: ComputedVecsFromDateindex::forced_import(
path,
"ath",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_drawdown: ComputedVecsFromDateindex::forced_import(
path,
"drawdown",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_days_since_ath: ComputedVecsFromDateindex::forced_import(
path,
"days_since_ath",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_max_days_between_ath: ComputedVecsFromDateindex::forced_import(
path,
"max_days_between_ath",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
indexes_to_max_years_between_ath: ComputedVecsFromDateindex::forced_import(
path,
"max_years_between_ath",
Version::ZERO,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
})
}
pub fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
fetched: &fetched::Vecs,
transactions: &mut transactions::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> color_eyre::Result<()> {
self.indexes_to_marketcap.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
let mut total_subsidy_in_btc = transactions
.indexes_to_subsidy
.bitcoin
.dateindex
.unwrap_total()
.into_iter();
v.compute_transform(
starting_indexes.dateindex,
&fetched.timeindexes_to_close.dateindex,
|(i, close, ..)| {
let supply = total_subsidy_in_btc.unwrap_get_inner(i);
(i, *close * supply)
},
exit,
)
},
)?;
self.indexes_to_ath.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
let mut prev = None;
v.compute_transform(
starting_indexes.dateindex,
&fetched.timeindexes_to_high.dateindex,
|(i, high, slf)| {
if prev.is_none() {
let i = i.unwrap_to_usize();
prev.replace(if i > 0 {
slf.into_iter().unwrap_get_inner_(i - 1)
} else {
Dollars::ZERO
});
}
let ath = prev.unwrap().max(*high);
prev.replace(ath);
(i, ath)
},
exit,
)
},
)?;
self.indexes_to_drawdown.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
let mut close_iter = fetched.timeindexes_to_close.dateindex.into_iter();
v.compute_transform(
starting_indexes.dateindex,
&self.indexes_to_ath.dateindex,
|(i, ath, ..)| {
if ath == Dollars::ZERO {
return (i, StoredF64::default());
}
let close = *close_iter.unwrap_get_inner(i);
let drawdown = StoredF64::from((*ath - *close) / *ath * -100.0);
(i, drawdown)
},
exit,
)
},
)?;
self.indexes_to_days_since_ath.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
let mut high_iter = fetched.timeindexes_to_high.dateindex.into_iter();
let mut prev = None;
v.compute_transform(
starting_indexes.dateindex,
&self.indexes_to_ath.dateindex,
|(i, ath, slf)| {
if prev.is_none() {
let i = i.unwrap_to_usize();
prev.replace(if i > 0 {
slf.into_iter().unwrap_get_inner_(i - 1)
} else {
StoredUsize::default()
});
}
let days = if *high_iter.unwrap_get_inner(i) == ath {
StoredUsize::default()
} else {
prev.unwrap() + StoredUsize::from(1)
};
prev.replace(days);
(i, days)
},
exit,
)
},
)?;
self.indexes_to_max_days_between_ath.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
let mut prev = None;
v.compute_transform(
starting_indexes.dateindex,
&self.indexes_to_days_since_ath.dateindex,
|(i, days, slf)| {
if prev.is_none() {
let i = i.unwrap_to_usize();
prev.replace(if i > 0 {
slf.into_iter().unwrap_get_inner_(i - 1)
} else {
StoredUsize::ZERO
});
}
let max = prev.unwrap().max(days);
prev.replace(max);
(i, max)
},
exit,
)
},
)?;
self.indexes_to_max_years_between_ath.compute(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform(
starting_indexes.dateindex,
&self.indexes_to_max_days_between_ath.dateindex,
|(i, max, ..)| (i, StoredF64::from(*max as f64 / 365.0)),
exit,
)
},
)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.indexes_to_marketcap.vecs(),
self.indexes_to_ath.vecs(),
self.indexes_to_drawdown.vecs(),
self.indexes_to_days_since_ath.vecs(),
self.indexes_to_max_days_between_ath.vecs(),
self.indexes_to_max_years_between_ath.vecs(),
]
.concat()
}
}

View File

@@ -6,9 +6,10 @@ use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, Compressed, Computation};
pub mod blocks;
pub mod fetched;
pub mod grouped;
pub mod indexes;
pub mod marketprice;
pub mod market;
pub mod mining;
pub mod transactions;
@@ -19,8 +20,9 @@ pub struct Vecs {
pub indexes: indexes::Vecs,
pub blocks: blocks::Vecs,
pub mining: mining::Vecs,
pub market: market::Vecs,
pub transactions: transactions::Vecs,
pub marketprice: Option<marketprice::Vecs>,
pub fetched: Option<fetched::Vecs>,
}
impl Vecs {
@@ -35,22 +37,23 @@ impl Vecs {
let indexes = indexes::Vecs::forced_import(path, indexer, computation, compressed)?;
let marketprice =
fetch.then(|| marketprice::Vecs::forced_import(path, computation, compressed).unwrap());
let fetched =
fetch.then(|| fetched::Vecs::forced_import(path, computation, compressed).unwrap());
Ok(Self {
blocks: blocks::Vecs::forced_import(path, computation, compressed)?,
mining: mining::Vecs::forced_import(path, computation, compressed)?,
market: market::Vecs::forced_import(path, computation, compressed)?,
transactions: transactions::Vecs::forced_import(
path,
indexer,
&indexes,
computation,
compressed,
marketprice.as_ref(),
fetched.as_ref(),
)?,
indexes,
marketprice,
fetched,
})
}
@@ -69,8 +72,8 @@ impl Vecs {
self.mining
.compute(indexer, &self.indexes, &starting_indexes, exit)?;
if let Some(marketprice) = self.marketprice.as_mut() {
marketprice.compute(
if let Some(fetched) = self.fetched.as_mut() {
fetched.compute(
indexer,
&self.indexes,
&starting_indexes,
@@ -83,10 +86,21 @@ impl Vecs {
indexer,
&self.indexes,
&starting_indexes,
self.marketprice.as_ref(),
self.fetched.as_ref(),
exit,
)?;
if let Some(fetched) = self.fetched.as_ref() {
self.market.compute(
indexer,
&self.indexes,
fetched,
&mut self.transactions,
&starting_indexes,
exit,
)?;
}
Ok(())
}
@@ -95,8 +109,9 @@ impl Vecs {
self.indexes.vecs(),
self.blocks.vecs(),
self.mining.vecs(),
self.market.vecs(),
self.transactions.vecs(),
self.marketprice.as_ref().map_or(vec![], |v| v.vecs()),
self.fetched.as_ref().map_or(vec![], |v| v.vecs()),
]
.concat()
}

View File

@@ -13,12 +13,12 @@ use brk_vec::{
};
use super::{
Indexes,
Indexes, fetched,
grouped::{
ComputedValueVecsFromHeight, ComputedValueVecsFromTxindex, ComputedVecsFromHeight,
ComputedVecsFromTxindex, StorableVecGeneatorOptions,
},
indexes, marketprice,
indexes,
};
#[derive(Clone)]
@@ -94,9 +94,9 @@ impl Vecs {
indexes: &indexes::Vecs,
computation: Computation,
compressed: Compressed,
marketprices: Option<&marketprice::Vecs>,
fetched: Option<&fetched::Vecs>,
) -> color_eyre::Result<Self> {
let compute_dollars = marketprices.is_some();
let compute_dollars = fetched.is_some();
fs::create_dir_all(path)?;
@@ -455,7 +455,7 @@ impl Vecs {
Version::ZERO,
computation,
compressed,
marketprices,
fetched,
StorableVecGeneatorOptions::default()
.add_sum()
.add_total()
@@ -700,7 +700,7 @@ impl Vecs {
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
marketprices: Option<&marketprice::Vecs>,
fetched: Option<&fetched::Vecs>,
exit: &Exit,
) -> color_eyre::Result<()> {
self.indexes_to_tx_count.compute_all(
@@ -845,7 +845,7 @@ impl Vecs {
self.indexes_to_coinbase.compute_all(
indexer,
indexes,
marketprices,
fetched,
starting_indexes,
exit,
|vec, indexer, _, starting_indexes, exit| {
@@ -878,7 +878,7 @@ impl Vecs {
self.indexes_to_subsidy.compute_all(
indexer,
indexes,
marketprices,
fetched,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {

View File

@@ -59,3 +59,18 @@ impl Div<usize> for Cents {
Self(self.0 / rhs as u64)
}
}
impl From<u128> for Cents {
fn from(value: u128) -> Self {
if value > u64::MAX as u128 {
panic!("u128 bigger than u64")
}
Self(value as u64)
}
}
impl From<Cents> for u128 {
fn from(value: Cents) -> Self {
value.0 as u128
}
}

View File

@@ -22,6 +22,10 @@ use super::{Bitcoin, Cents, Sats};
)]
pub struct Dollars(f64);
impl Dollars {
pub const ZERO: Self = Self(0.0);
}
impl From<f64> for Dollars {
fn from(value: f64) -> Self {
Self(value)
@@ -73,7 +77,7 @@ impl Mul<Bitcoin> for Dollars {
type Output = Dollars;
fn mul(self, rhs: Bitcoin) -> Self::Output {
Self::from(Cents::from(
u64::from(Sats::from(rhs)) * u64::from(Cents::from(self)) / u64::from(Sats::ONE_BTC),
u128::from(Sats::from(rhs)) * u128::from(Cents::from(self)) / u128::from(Sats::ONE_BTC),
))
}
}

View File

@@ -156,3 +156,18 @@ impl From<Sats> for u64 {
value.0
}
}
impl From<u128> for Sats {
fn from(value: u128) -> Self {
if value > u64::MAX as u128 {
panic!("u128 bigger than u64")
}
Self(value as u64)
}
}
impl From<Sats> for u128 {
fn from(value: Sats) -> Self {
value.0 as u128
}
}

View File

@@ -9,6 +9,7 @@ use crate::CheckedSub;
#[derive(
Debug,
Deref,
Default,
Clone,
Copy,
PartialEq,

View File

@@ -16,6 +16,7 @@ use super::{
Debug,
Deref,
Clone,
Default,
Copy,
PartialEq,
Eq,

View File

@@ -53,7 +53,12 @@ pub trait VecIterator<'a>: BaseVecIterator<Item = (Self::I, Value<'a, Self::T>)>
#[inline]
fn unwrap_get_inner(&mut self, i: Self::I) -> Self::T {
self.get_(i.unwrap_to_usize()).unwrap().into_inner()
self.unwrap_get_inner_(i.unwrap_to_usize())
}
#[inline]
fn unwrap_get_inner_(&mut self, i: usize) -> Self::T {
self.get_(i).unwrap().into_inner()
}
#[inline]

View File

@@ -441,9 +441,9 @@
h1,
h2 {
text-transform: uppercase;
font-size: var(--font-size-2xl);
line-height: var(--line-height-2xl);
font-weight: 300;
font-size: var(--font-size-xl);
line-height: var(--line-height-xl);
font-weight: 350;
}
h3 {

View File

@@ -25,7 +25,7 @@
* "Hash" |
* "Index" |
* "mb" |
* "%" |
* "percentage" |
* "Ratio" |
* "Sats" |
* "Seconds" |
@@ -37,6 +37,8 @@
* "Version" |
* "WU" |
* "Bool" |
* "Days" |
* "Years" |
* "Locktime" |
* "sat/vB" |
* "vB"
@@ -686,6 +688,10 @@ function createUtils() {
unit = "Index";
} else if (id.includes("type")) {
unit = "Type";
} else if (id.includes("days-")) {
unit = "Days";
} else if (id.includes("years-")) {
unit = "Years";
} else if (id === "rawlocktime") {
unit = "Locktime";
} else if (id.startsWith("is-")) {
@@ -722,7 +728,9 @@ function createUtils() {
id.includes("high") ||
id.includes("low") ||
id.includes("close") ||
id.includes("ohlc")
id.includes("ohlc") ||
id.includes("marketcap") ||
id.includes("ath")
) {
unit = "USD";
} else if (id.includes("count") || id.match(/v[1-3]/g)) {
@@ -743,6 +751,8 @@ function createUtils() {
unit = "Version";
} else if (id === "value") {
unit = "Sats";
} else if (id === "drawdown") {
unit = "percentage";
} else {
console.log();
throw Error(`Unit not set for "${id}"`);

View File

@@ -168,9 +168,10 @@ function createPartialOptions(colors) {
* @param {Object} args
* @param {ChartableVecId} args.key
* @param {string} args.name
* @param {Color} [args.color]
*/
function createBaseSeries({ key, name }) {
return { key, title: name };
function createBaseSeries({ key, name, color }) {
return { key, title: name, color };
}
/**
@@ -564,6 +565,89 @@ function createPartialOptions(colors) {
},
],
},
{
name: "Market",
tree: [
{
name: "Capitalization",
title: "Market Capitalization",
bottom: [
createBaseSeries({
key: "marketcap",
name: "Capitalization",
}),
],
},
{
name: "All Time High",
tree: [
{
name: "Value",
title: "All Time High",
top: [
createBaseSeries({
key: "ath",
name: "ath",
}),
],
},
{
name: "drawdown",
title: "All Time High Drawdown",
top: [
createBaseSeries({
key: "ath",
name: "ath",
}),
],
bottom: [
createBaseSeries({
key: "drawdown",
name: "Drawdown",
color: colors.red,
}),
],
},
{
name: "days since",
title: "Number of days Since All Time High",
top: [
createBaseSeries({
key: "ath",
name: "ath",
}),
],
bottom: [
createBaseSeries({
key: "days-since-ath",
name: "Days",
}),
],
},
{
name: "max between",
title: "Maximum time between All Time Highs",
top: [
createBaseSeries({
key: "ath",
name: "ath",
}),
],
bottom: [
createBaseSeries({
key: "max-days-between-ath",
name: "Days",
}),
createBaseSeries({
key: "max-years-between-ath",
name: "Years",
}),
],
},
],
},
],
},
],
},
{

View File

@@ -56,6 +56,7 @@ export function createVecIdToIndexes() {
const YearIndex = /** @satisfies {YearIndex} */ (23);
return /** @type {const} */ ({
ath: [DateIndex, DecadeIndex, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
"base-size": [TxIndex],
"block-count": [Height],
"block-count-sum": [DateIndex, DecadeIndex, DifficultyEpoch, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
@@ -108,9 +109,11 @@ export function createVecIdToIndexes() {
"date-fixed": [Height],
dateindex: [DateIndex, Height],
"dateindex-count": [MonthIndex, WeekIndex],
"days-since-ath": [DateIndex, DecadeIndex, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
decadeindex: [DecadeIndex, YearIndex],
difficulty: [DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
difficultyepoch: [DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
drawdown: [DateIndex, DecadeIndex, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
"emptyoutput-count": [Height],
"emptyoutput-count-10p": [DateIndex],
"emptyoutput-count-25p": [DateIndex],
@@ -202,6 +205,9 @@ export function createVecIdToIndexes() {
low: [DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
"low-in-cents": [DateIndex, Height],
"low-in-sats": [DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
marketcap: [DateIndex, DecadeIndex, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
"max-days-between-ath": [DateIndex, DecadeIndex, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
"max-years-between-ath": [DateIndex, DecadeIndex, MonthIndex, QuarterIndex, WeekIndex, YearIndex],
monthindex: [DateIndex, MonthIndex],
"monthindex-count": [QuarterIndex, YearIndex],
ohlc: [DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, WeekIndex, YearIndex],