mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
website: snapshot
This commit is contained in:
@@ -368,7 +368,7 @@ const _I27: &[Index] = &[Index::TxIndex];
|
||||
const _I28: &[Index] = &[Index::UnknownOutputIndex];
|
||||
const _I29: &[Index] = &[Index::WeekIndex];
|
||||
const _I30: &[Index] = &[Index::YearIndex];
|
||||
const _I31: &[Index] = &[Index::LoadedAddressIndex];
|
||||
const _I31: &[Index] = &[Index::FundedAddressIndex];
|
||||
const _I32: &[Index] = &[Index::EmptyAddressIndex];
|
||||
|
||||
#[inline]
|
||||
@@ -829,7 +829,7 @@ impl<T: DeserializeOwned> MetricPattern<T> for MetricPattern30<T> { fn get(&self
|
||||
|
||||
pub struct MetricPattern31By<T> { client: Arc<BrkClientBase>, name: Arc<str>, _marker: std::marker::PhantomData<T> }
|
||||
impl<T: DeserializeOwned> MetricPattern31By<T> {
|
||||
pub fn loadedaddressindex(&self) -> MetricEndpointBuilder<T> { _ep(&self.client, &self.name, Index::LoadedAddressIndex) }
|
||||
pub fn loadedaddressindex(&self) -> MetricEndpointBuilder<T> { _ep(&self.client, &self.name, Index::FundedAddressIndex) }
|
||||
}
|
||||
|
||||
pub struct MetricPattern31<T> { name: Arc<str>, pub by: MetricPattern31By<T> }
|
||||
@@ -5112,7 +5112,7 @@ pub struct MetricsTree_Distribution {
|
||||
pub total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern,
|
||||
pub new_addr_count: MetricsTree_Distribution_NewAddrCount,
|
||||
pub growth_rate: MetricsTree_Distribution_GrowthRate,
|
||||
pub loadedaddressindex: MetricPattern31<LoadedAddressIndex>,
|
||||
pub loadedaddressindex: MetricPattern31<FundedAddressIndex>,
|
||||
pub emptyaddressindex: MetricPattern32<EmptyAddressIndex>,
|
||||
}
|
||||
|
||||
@@ -5165,7 +5165,7 @@ impl MetricsTree_Distribution_AnyAddressIndexes {
|
||||
|
||||
/// Metrics tree node.
|
||||
pub struct MetricsTree_Distribution_AddressesData {
|
||||
pub loaded: MetricPattern31<LoadedAddressData>,
|
||||
pub loaded: MetricPattern31<FundedAddressData>,
|
||||
pub empty: MetricPattern32<EmptyAddressData>,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ use brk_traversable::Traversable;
|
||||
|
||||
#[derive(Debug, Default, Traversable)]
|
||||
pub struct ByAnyAddress<T> {
|
||||
pub loaded: T,
|
||||
pub funded: T,
|
||||
pub empty: T,
|
||||
}
|
||||
|
||||
impl<T> ByAnyAddress<Option<T>> {
|
||||
pub fn take(&mut self) {
|
||||
self.loaded.take();
|
||||
self.funded.take();
|
||||
self.empty.take();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,27 +36,47 @@ fn run() -> Result<()> {
|
||||
let start = Instant::now();
|
||||
let mut buf = Vec::new();
|
||||
empty_data.write_json(Some(empty_data.len() - 1), Some(empty_data.len()), &mut buf)?;
|
||||
println!("emptyaddressdata last item JSON: {}", String::from_utf8_lossy(&buf));
|
||||
println!(
|
||||
"emptyaddressdata last item JSON: {}",
|
||||
String::from_utf8_lossy(&buf)
|
||||
);
|
||||
println!("Time for BytesVec write_json: {:?}", start.elapsed());
|
||||
|
||||
// Test emptyaddressindex (LazyVecFrom1 wrapper) - computed access
|
||||
let empty_index = &computer.distribution.emptyaddressindex;
|
||||
println!("\nemptyaddressindex (LazyVecFrom1) len: {}", empty_index.len());
|
||||
println!(
|
||||
"\nemptyaddressindex (LazyVecFrom1) len: {}",
|
||||
empty_index.len()
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
let mut buf = Vec::new();
|
||||
empty_index.write_json(Some(empty_index.len() - 1), Some(empty_index.len()), &mut buf)?;
|
||||
println!("emptyaddressindex last item JSON: {}", String::from_utf8_lossy(&buf));
|
||||
empty_index.write_json(
|
||||
Some(empty_index.len() - 1),
|
||||
Some(empty_index.len()),
|
||||
&mut buf,
|
||||
)?;
|
||||
println!(
|
||||
"emptyaddressindex last item JSON: {}",
|
||||
String::from_utf8_lossy(&buf)
|
||||
);
|
||||
println!("Time for LazyVecFrom1 write_json: {:?}", start.elapsed());
|
||||
|
||||
// Compare with loaded versions
|
||||
let loaded_data = &computer.distribution.addresses_data.loaded;
|
||||
println!("\nloadedaddressdata (BytesVec) len: {}", loaded_data.len());
|
||||
// Compare with funded versions
|
||||
let funded_data = &computer.distribution.addresses_data.funded;
|
||||
println!("\nfundedaddressdata (BytesVec) len: {}", funded_data.len());
|
||||
|
||||
let start = Instant::now();
|
||||
let mut buf = Vec::new();
|
||||
loaded_data.write_json(Some(loaded_data.len() - 1), Some(loaded_data.len()), &mut buf)?;
|
||||
println!("loadedaddressdata last item JSON: {}", String::from_utf8_lossy(&buf));
|
||||
funded_data.write_json(
|
||||
Some(funded_data.len() - 1),
|
||||
Some(funded_data.len()),
|
||||
&mut buf,
|
||||
)?;
|
||||
println!(
|
||||
"fundedaddressdata last item JSON: {}",
|
||||
String::from_utf8_lossy(&buf)
|
||||
);
|
||||
println!("Time for BytesVec write_json: {:?}", start.elapsed());
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex, Version,
|
||||
EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height, Version,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
@@ -10,10 +10,10 @@ use vecdb::{
|
||||
|
||||
const SAVED_STAMPED_CHANGES: u16 = 10;
|
||||
|
||||
/// Storage for both loaded and empty address data.
|
||||
/// Storage for both funded and empty address data.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct AddressesDataVecs {
|
||||
pub loaded: BytesVec<LoadedAddressIndex, LoadedAddressData>,
|
||||
pub funded: BytesVec<FundedAddressIndex, FundedAddressData>,
|
||||
pub empty: BytesVec<EmptyAddressIndex, EmptyAddressData>,
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ impl AddressesDataVecs {
|
||||
/// Import from database.
|
||||
pub fn forced_import(db: &Database, version: Version) -> Result<Self> {
|
||||
Ok(Self {
|
||||
loaded: BytesVec::forced_import_with(
|
||||
ImportOptions::new(db, "loadedaddressdata", version)
|
||||
funded: BytesVec::forced_import_with(
|
||||
ImportOptions::new(db, "fundedaddressdata", version)
|
||||
.with_saved_stamped_changes(SAVED_STAMPED_CHANGES),
|
||||
)?,
|
||||
empty: BytesVec::forced_import_with(
|
||||
@@ -32,31 +32,31 @@ impl AddressesDataVecs {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get minimum stamped height across loaded and empty data.
|
||||
/// Get minimum stamped height across funded and empty data.
|
||||
pub fn min_stamped_height(&self) -> Height {
|
||||
Height::from(self.loaded.stamp())
|
||||
Height::from(self.funded.stamp())
|
||||
.incremented()
|
||||
.min(Height::from(self.empty.stamp()).incremented())
|
||||
}
|
||||
|
||||
/// Rollback both loaded and empty data to before the given stamp.
|
||||
/// Rollback both funded and empty data to before the given stamp.
|
||||
pub fn rollback_before(&mut self, stamp: Stamp) -> Result<[Stamp; 2]> {
|
||||
Ok([
|
||||
self.loaded.rollback_before(stamp)?,
|
||||
self.funded.rollback_before(stamp)?,
|
||||
self.empty.rollback_before(stamp)?,
|
||||
])
|
||||
}
|
||||
|
||||
/// Reset both loaded and empty data.
|
||||
/// Reset both funded and empty data.
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
self.loaded.reset()?;
|
||||
self.funded.reset()?;
|
||||
self.empty.reset()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flush both loaded and empty data with stamp.
|
||||
/// Flush both funded and empty data with stamp.
|
||||
pub fn write(&mut self, stamp: Stamp, with_changes: bool) -> Result<()> {
|
||||
self.loaded
|
||||
self.funded
|
||||
.stamped_write_maybe_with_changes(stamp, with_changes)?;
|
||||
self.empty
|
||||
.stamped_write_maybe_with_changes(stamp, with_changes)?;
|
||||
@@ -66,7 +66,7 @@ impl AddressesDataVecs {
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
vec![
|
||||
&mut self.loaded as &mut dyn AnyStoredVec,
|
||||
&mut self.funded as &mut dyn AnyStoredVec,
|
||||
&mut self.empty as &mut dyn AnyStoredVec,
|
||||
]
|
||||
.into_par_iter()
|
||||
|
||||
@@ -136,7 +136,7 @@ define_any_address_indexes_vecs!(
|
||||
|
||||
impl AnyAddressIndexesVecs {
|
||||
/// Process index updates in parallel by address type.
|
||||
/// Accepts two maps (e.g. from empty and loaded processing) and merges per-thread.
|
||||
/// Accepts two maps (e.g. from empty and funded processing) and merges per-thread.
|
||||
/// Updates existing entries and pushes new ones (sorted).
|
||||
/// Returns (update_count, push_count).
|
||||
pub fn par_batch_update(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use brk_cohort::ByAddressType;
|
||||
use brk_types::{AnyAddressDataIndexEnum, LoadedAddressData, OutputType, TypeIndex};
|
||||
use brk_types::{AnyAddressDataIndexEnum, FundedAddressData, OutputType, TypeIndex};
|
||||
|
||||
use crate::distribution::{
|
||||
address::{AddressTypeToTypeIndexMap, AddressesDataVecs, AnyAddressIndexesVecs},
|
||||
@@ -7,7 +7,7 @@ use crate::distribution::{
|
||||
};
|
||||
|
||||
use super::super::cohort::{
|
||||
EmptyAddressDataWithSource, LoadedAddressDataWithSource, TxIndexVec, WithAddressDataSource,
|
||||
EmptyAddressDataWithSource, FundedAddressDataWithSource, TxIndexVec, WithAddressDataSource,
|
||||
update_tx_counts,
|
||||
};
|
||||
use super::lookup::AddressLookup;
|
||||
@@ -15,7 +15,7 @@ use super::lookup::AddressLookup;
|
||||
/// Cache for address data within a flush interval.
|
||||
pub struct AddressCache {
|
||||
/// Addresses with non-zero balance
|
||||
loaded: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
funded: AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
/// Addresses that became empty (zero balance)
|
||||
empty: AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
}
|
||||
@@ -29,15 +29,15 @@ impl Default for AddressCache {
|
||||
impl AddressCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
loaded: AddressTypeToTypeIndexMap::default(),
|
||||
funded: AddressTypeToTypeIndexMap::default(),
|
||||
empty: AddressTypeToTypeIndexMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if address is in cache (either loaded or empty).
|
||||
/// Check if address is in cache (either funded or empty).
|
||||
#[inline]
|
||||
pub fn contains(&self, address_type: OutputType, typeindex: TypeIndex) -> bool {
|
||||
self.loaded
|
||||
self.funded
|
||||
.get(address_type)
|
||||
.is_some_and(|m| m.contains_key(&typeindex))
|
||||
|| self
|
||||
@@ -46,24 +46,24 @@ impl AddressCache {
|
||||
.is_some_and(|m| m.contains_key(&typeindex))
|
||||
}
|
||||
|
||||
/// Merge address data into loaded cache.
|
||||
/// Merge address data into funded cache.
|
||||
#[inline]
|
||||
pub fn merge_loaded(&mut self, data: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>) {
|
||||
self.loaded.merge_mut(data);
|
||||
pub fn merge_funded(&mut self, data: AddressTypeToTypeIndexMap<FundedAddressDataWithSource>) {
|
||||
self.funded.merge_mut(data);
|
||||
}
|
||||
|
||||
/// Create an AddressLookup view into this cache.
|
||||
#[inline]
|
||||
pub fn as_lookup(&mut self) -> AddressLookup<'_> {
|
||||
AddressLookup {
|
||||
loaded: &mut self.loaded,
|
||||
funded: &mut self.funded,
|
||||
empty: &mut self.empty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update transaction counts for addresses.
|
||||
pub fn update_tx_counts(&mut self, txindex_vecs: AddressTypeToTypeIndexMap<TxIndexVec>) {
|
||||
update_tx_counts(&mut self.loaded, &mut self.empty, txindex_vecs);
|
||||
update_tx_counts(&mut self.funded, &mut self.empty, txindex_vecs);
|
||||
}
|
||||
|
||||
/// Take the cache contents for flushing, leaving empty caches.
|
||||
@@ -71,18 +71,18 @@ impl AddressCache {
|
||||
&mut self,
|
||||
) -> (
|
||||
AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
) {
|
||||
(
|
||||
std::mem::take(&mut self.empty),
|
||||
std::mem::take(&mut self.loaded),
|
||||
std::mem::take(&mut self.funded),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load address data from storage or create new.
|
||||
///
|
||||
/// Returns None if address is already in cache (loaded or empty).
|
||||
/// Returns None if address is already in cache (funded or empty).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn load_uncached_address_data(
|
||||
address_type: OutputType,
|
||||
@@ -92,11 +92,11 @@ pub fn load_uncached_address_data(
|
||||
vr: &VecsReaders,
|
||||
any_address_indexes: &AnyAddressIndexesVecs,
|
||||
addresses_data: &AddressesDataVecs,
|
||||
) -> Option<LoadedAddressDataWithSource> {
|
||||
) -> Option<FundedAddressDataWithSource> {
|
||||
// Check if this is a new address (typeindex >= first for this height)
|
||||
let first = *first_addressindexes.get(address_type).unwrap();
|
||||
if first <= typeindex {
|
||||
return Some(WithAddressDataSource::New(LoadedAddressData::default()));
|
||||
return Some(WithAddressDataSource::New(FundedAddressData::default()));
|
||||
}
|
||||
|
||||
// Skip if already in cache
|
||||
@@ -109,13 +109,13 @@ pub fn load_uncached_address_data(
|
||||
let anyaddressindex = any_address_indexes.get(address_type, typeindex, reader);
|
||||
|
||||
Some(match anyaddressindex.to_enum() {
|
||||
AnyAddressDataIndexEnum::Loaded(loaded_index) => {
|
||||
let reader = &vr.anyaddressindex_to_anyaddressdata.loaded;
|
||||
AnyAddressDataIndexEnum::Funded(funded_index) => {
|
||||
let reader = &vr.anyaddressindex_to_anyaddressdata.funded;
|
||||
// Use get_any_or_read_unwrap to check updated layer (needed after rollback)
|
||||
let loaded_data = addresses_data
|
||||
.loaded
|
||||
.get_any_or_read_unwrap(loaded_index, reader);
|
||||
WithAddressDataSource::FromLoaded(loaded_index, loaded_data)
|
||||
let funded_data = addresses_data
|
||||
.funded
|
||||
.get_any_or_read_unwrap(funded_index, reader);
|
||||
WithAddressDataSource::FromFunded(funded_index, funded_data)
|
||||
}
|
||||
AnyAddressDataIndexEnum::Empty(empty_index) => {
|
||||
let reader = &vr.anyaddressindex_to_anyaddressdata.empty;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use brk_types::{LoadedAddressData, OutputType, TypeIndex};
|
||||
use brk_types::{FundedAddressData, OutputType, TypeIndex};
|
||||
|
||||
use crate::distribution::address::AddressTypeToTypeIndexMap;
|
||||
|
||||
use super::super::cohort::{
|
||||
EmptyAddressDataWithSource, LoadedAddressDataWithSource, WithAddressDataSource,
|
||||
EmptyAddressDataWithSource, FundedAddressDataWithSource, WithAddressDataSource,
|
||||
};
|
||||
|
||||
/// Tracking status of an address - determines cohort update strategy.
|
||||
@@ -19,7 +19,7 @@ pub enum TrackingStatus {
|
||||
|
||||
/// Context for looking up and storing address data during block processing.
|
||||
pub struct AddressLookup<'a> {
|
||||
pub loaded: &'a mut AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
pub funded: &'a mut AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
pub empty: &'a mut AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
}
|
||||
|
||||
@@ -28,21 +28,21 @@ impl<'a> AddressLookup<'a> {
|
||||
&mut self,
|
||||
output_type: OutputType,
|
||||
type_index: TypeIndex,
|
||||
) -> (&mut LoadedAddressDataWithSource, TrackingStatus) {
|
||||
) -> (&mut FundedAddressDataWithSource, TrackingStatus) {
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
let map = self.loaded.get_mut(output_type).unwrap();
|
||||
let map = self.funded.get_mut(output_type).unwrap();
|
||||
|
||||
match map.entry(type_index) {
|
||||
Entry::Occupied(entry) => {
|
||||
// Address is in cache. Need to determine if it's been processed
|
||||
// by process_received (added to a cohort) or just loaded this block.
|
||||
// by process_received (added to a cohort) or just funded this block.
|
||||
//
|
||||
// - If wrapper is New AND funded_txo_count == 0: hasn't received yet,
|
||||
// was just created in process_outputs this block → New
|
||||
// - If wrapper is New AND funded_txo_count > 0: received in previous
|
||||
// block but still in cache (no flush) → Tracked
|
||||
// - If wrapper is FromLoaded: loaded from storage → Tracked
|
||||
// - If wrapper is FromFunded: funded from storage → Tracked
|
||||
// - If wrapper is FromEmpty AND utxo_count == 0: still empty → WasEmpty
|
||||
// - If wrapper is FromEmpty AND utxo_count > 0: already received → Tracked
|
||||
let status = match entry.get() {
|
||||
@@ -53,7 +53,7 @@ impl<'a> AddressLookup<'a> {
|
||||
TrackingStatus::Tracked
|
||||
}
|
||||
}
|
||||
WithAddressDataSource::FromLoaded(..) => TrackingStatus::Tracked,
|
||||
WithAddressDataSource::FromFunded(..) => TrackingStatus::Tracked,
|
||||
WithAddressDataSource::FromEmpty(_, data) => {
|
||||
if data.utxo_count() == 0 {
|
||||
TrackingStatus::WasEmpty
|
||||
@@ -71,7 +71,7 @@ impl<'a> AddressLookup<'a> {
|
||||
return (entry.insert(empty_data.into()), TrackingStatus::WasEmpty);
|
||||
}
|
||||
(
|
||||
entry.insert(WithAddressDataSource::New(LoadedAddressData::default())),
|
||||
entry.insert(WithAddressDataSource::New(FundedAddressData::default())),
|
||||
TrackingStatus::New,
|
||||
)
|
||||
}
|
||||
@@ -83,18 +83,18 @@ impl<'a> AddressLookup<'a> {
|
||||
&mut self,
|
||||
output_type: OutputType,
|
||||
type_index: TypeIndex,
|
||||
) -> &mut LoadedAddressDataWithSource {
|
||||
self.loaded
|
||||
) -> &mut FundedAddressDataWithSource {
|
||||
self.funded
|
||||
.get_mut(output_type)
|
||||
.unwrap()
|
||||
.get_mut(&type_index)
|
||||
.expect("Address must exist for send")
|
||||
}
|
||||
|
||||
/// Move address from loaded to empty set.
|
||||
/// Move address from funded to empty set.
|
||||
pub fn move_to_empty(&mut self, output_type: OutputType, type_index: TypeIndex) {
|
||||
let data = self
|
||||
.loaded
|
||||
.funded
|
||||
.get_mut(output_type)
|
||||
.unwrap()
|
||||
.remove(&type_index)
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{
|
||||
AnyAddressIndex, EmptyAddressData, EmptyAddressIndex, LoadedAddressData, LoadedAddressIndex,
|
||||
AnyAddressIndex, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex,
|
||||
OutputType, TypeIndex,
|
||||
};
|
||||
use vecdb::AnyVec;
|
||||
|
||||
use crate::distribution::{AddressTypeToTypeIndexMap, AddressesDataVecs};
|
||||
|
||||
use super::with_source::{EmptyAddressDataWithSource, LoadedAddressDataWithSource};
|
||||
use super::with_source::{EmptyAddressDataWithSource, FundedAddressDataWithSource};
|
||||
|
||||
/// Process loaded address data updates.
|
||||
/// Process funded address data updates.
|
||||
///
|
||||
/// Handles:
|
||||
/// - New loaded address: push to loaded storage
|
||||
/// - Updated loaded address (was loaded): update in place
|
||||
/// - Transition empty -> loaded: delete from empty, push to loaded
|
||||
pub fn process_loaded_addresses(
|
||||
/// - New funded address: push to funded storage
|
||||
/// - Updated funded address (was funded): update in place
|
||||
/// - Transition empty -> funded: delete from empty, push to funded
|
||||
pub fn process_funded_addresses(
|
||||
addresses_data: &mut AddressesDataVecs,
|
||||
loaded_updates: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
funded_updates: AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
) -> Result<AddressTypeToTypeIndexMap<AnyAddressIndex>> {
|
||||
let total: usize = loaded_updates.iter().map(|(_, m)| m.len()).sum();
|
||||
let total: usize = funded_updates.iter().map(|(_, m)| m.len()).sum();
|
||||
|
||||
let mut updates: Vec<(LoadedAddressIndex, LoadedAddressData)> = Vec::with_capacity(total);
|
||||
let mut updates: Vec<(FundedAddressIndex, FundedAddressData)> = Vec::with_capacity(total);
|
||||
let mut deletes: Vec<EmptyAddressIndex> = Vec::with_capacity(total);
|
||||
let mut pushes: Vec<(OutputType, TypeIndex, LoadedAddressData)> = Vec::with_capacity(total);
|
||||
let mut pushes: Vec<(OutputType, TypeIndex, FundedAddressData)> = Vec::with_capacity(total);
|
||||
|
||||
for (address_type, items) in loaded_updates.into_iter() {
|
||||
for (address_type, items) in funded_updates.into_iter() {
|
||||
for (typeindex, source) in items {
|
||||
match source {
|
||||
LoadedAddressDataWithSource::New(data) => {
|
||||
FundedAddressDataWithSource::New(data) => {
|
||||
pushes.push((address_type, typeindex, data));
|
||||
}
|
||||
LoadedAddressDataWithSource::FromLoaded(index, data) => {
|
||||
FundedAddressDataWithSource::FromFunded(index, data) => {
|
||||
updates.push((index, data));
|
||||
}
|
||||
LoadedAddressDataWithSource::FromEmpty(empty_index, data) => {
|
||||
FundedAddressDataWithSource::FromEmpty(empty_index, data) => {
|
||||
deletes.push(empty_index);
|
||||
pushes.push((address_type, typeindex, data));
|
||||
}
|
||||
@@ -49,16 +49,16 @@ pub fn process_loaded_addresses(
|
||||
|
||||
// Phase 2: Updates (in-place)
|
||||
for (index, data) in updates {
|
||||
addresses_data.loaded.update(index, data)?;
|
||||
addresses_data.funded.update(index, data)?;
|
||||
}
|
||||
|
||||
// Phase 3: Pushes (fill holes first, then pure pushes)
|
||||
let mut result = AddressTypeToTypeIndexMap::with_capacity(pushes.len() / 4);
|
||||
let holes_count = addresses_data.loaded.holes().len();
|
||||
let holes_count = addresses_data.funded.holes().len();
|
||||
let mut pushes_iter = pushes.into_iter();
|
||||
|
||||
for (address_type, typeindex, data) in pushes_iter.by_ref().take(holes_count) {
|
||||
let index = addresses_data.loaded.fill_first_hole_or_push(data)?;
|
||||
let index = addresses_data.funded.fill_first_hole_or_push(data)?;
|
||||
result
|
||||
.get_mut(address_type)
|
||||
.unwrap()
|
||||
@@ -66,14 +66,14 @@ pub fn process_loaded_addresses(
|
||||
}
|
||||
|
||||
// Pure pushes - no holes remain
|
||||
addresses_data.loaded.reserve_pushed(pushes_iter.len());
|
||||
let mut next_index = addresses_data.loaded.len();
|
||||
addresses_data.funded.reserve_pushed(pushes_iter.len());
|
||||
let mut next_index = addresses_data.funded.len();
|
||||
for (address_type, typeindex, data) in pushes_iter {
|
||||
addresses_data.loaded.push(data);
|
||||
result
|
||||
.get_mut(address_type)
|
||||
.unwrap()
|
||||
.insert(typeindex, AnyAddressIndex::from(LoadedAddressIndex::from(next_index)));
|
||||
addresses_data.funded.push(data);
|
||||
result.get_mut(address_type).unwrap().insert(
|
||||
typeindex,
|
||||
AnyAddressIndex::from(FundedAddressIndex::from(next_index)),
|
||||
);
|
||||
next_index += 1;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ pub fn process_loaded_addresses(
|
||||
/// Handles:
|
||||
/// - New empty address: push to empty storage
|
||||
/// - Updated empty address (was empty): update in place
|
||||
/// - Transition loaded -> empty: delete from loaded, push to empty
|
||||
/// - Transition funded -> empty: delete from funded, push to empty
|
||||
pub fn process_empty_addresses(
|
||||
addresses_data: &mut AddressesDataVecs,
|
||||
empty_updates: AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
@@ -93,7 +93,7 @@ pub fn process_empty_addresses(
|
||||
let total: usize = empty_updates.iter().map(|(_, m)| m.len()).sum();
|
||||
|
||||
let mut updates: Vec<(EmptyAddressIndex, EmptyAddressData)> = Vec::with_capacity(total);
|
||||
let mut deletes: Vec<LoadedAddressIndex> = Vec::with_capacity(total);
|
||||
let mut deletes: Vec<FundedAddressIndex> = Vec::with_capacity(total);
|
||||
let mut pushes: Vec<(OutputType, TypeIndex, EmptyAddressData)> = Vec::with_capacity(total);
|
||||
|
||||
for (address_type, items) in empty_updates.into_iter() {
|
||||
@@ -105,8 +105,8 @@ pub fn process_empty_addresses(
|
||||
EmptyAddressDataWithSource::FromEmpty(index, data) => {
|
||||
updates.push((index, data));
|
||||
}
|
||||
EmptyAddressDataWithSource::FromLoaded(loaded_index, data) => {
|
||||
deletes.push(loaded_index);
|
||||
EmptyAddressDataWithSource::FromFunded(funded_index, data) => {
|
||||
deletes.push(funded_index);
|
||||
pushes.push((address_type, typeindex, data));
|
||||
}
|
||||
}
|
||||
@@ -114,8 +114,8 @@ pub fn process_empty_addresses(
|
||||
}
|
||||
|
||||
// Phase 1: Deletes (creates holes)
|
||||
for loaded_index in deletes {
|
||||
addresses_data.loaded.delete(loaded_index);
|
||||
for funded_index in deletes {
|
||||
addresses_data.funded.delete(funded_index);
|
||||
}
|
||||
|
||||
// Phase 2: Updates (in-place)
|
||||
@@ -141,10 +141,10 @@ pub fn process_empty_addresses(
|
||||
let mut next_index = addresses_data.empty.len();
|
||||
for (address_type, typeindex, data) in pushes_iter {
|
||||
addresses_data.empty.push(data);
|
||||
result
|
||||
.get_mut(address_type)
|
||||
.unwrap()
|
||||
.insert(typeindex, AnyAddressIndex::from(EmptyAddressIndex::from(next_index)));
|
||||
result.get_mut(address_type).unwrap().insert(
|
||||
typeindex,
|
||||
AnyAddressIndex::from(EmptyAddressIndex::from(next_index)),
|
||||
);
|
||||
next_index += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use brk_cohort::{AmountBucket, ByAddressType};
|
||||
use brk_error::Result;
|
||||
use brk_types::{Age, CentsUnsigned, CheckedSub, Height, Sats, Timestamp, TypeIndex};
|
||||
use rustc_hash::FxHashSet;
|
||||
use vecdb::{unlikely, VecIndex};
|
||||
use vecdb::{VecIndex, unlikely};
|
||||
|
||||
use crate::distribution::{
|
||||
address::{AddressTypeToActivityCounts, HeightToAddressTypeToVec},
|
||||
@@ -131,7 +131,7 @@ pub fn process_sent(
|
||||
*type_addr_count -= 1;
|
||||
*type_empty_count += 1;
|
||||
|
||||
// Move from loaded to empty
|
||||
// Move from funded to empty
|
||||
lookup.move_to_empty(output_type, type_index);
|
||||
} else {
|
||||
// Add to new cohort
|
||||
@@ -151,7 +151,14 @@ pub fn process_sent(
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(addr_data, value, current_price.unwrap(), prev_price.unwrap(), peak_price.unwrap(), age)?;
|
||||
.send(
|
||||
addr_data,
|
||||
value,
|
||||
current_price.unwrap(),
|
||||
prev_price.unwrap(),
|
||||
peak_price.unwrap(),
|
||||
age,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::distribution::address::AddressTypeToTypeIndexMap;
|
||||
|
||||
use super::with_source::{EmptyAddressDataWithSource, LoadedAddressDataWithSource, TxIndexVec};
|
||||
use super::with_source::{EmptyAddressDataWithSource, FundedAddressDataWithSource, TxIndexVec};
|
||||
|
||||
/// Update tx_count for addresses based on unique transactions they participated in.
|
||||
///
|
||||
@@ -8,10 +8,10 @@ use super::with_source::{EmptyAddressDataWithSource, LoadedAddressDataWithSource
|
||||
/// 1. Deduplicate transaction indexes (an address may appear in multiple inputs/outputs of same tx)
|
||||
/// 2. Add the unique count to the address's tx_count field
|
||||
///
|
||||
/// Addresses are looked up in loaded_cache first, then empty_cache.
|
||||
/// NOTE: This should be called AFTER merging parallel-fetched address data into loaded_cache.
|
||||
/// Addresses are looked up in funded_cache first, then empty_cache.
|
||||
/// NOTE: This should be called AFTER merging parallel-fetched address data into funded_cache.
|
||||
pub fn update_tx_counts(
|
||||
loaded_cache: &mut AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
funded_cache: &mut AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
empty_cache: &mut AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
mut txindex_vecs: AddressTypeToTypeIndexMap<TxIndexVec>,
|
||||
) {
|
||||
@@ -32,7 +32,7 @@ pub fn update_tx_counts(
|
||||
{
|
||||
let tx_count = txindex_vec.len() as u32;
|
||||
|
||||
if let Some(addr_data) = loaded_cache
|
||||
if let Some(addr_data) = funded_cache
|
||||
.get_mut(address_type)
|
||||
.unwrap()
|
||||
.get_mut(&typeindex)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use brk_types::{EmptyAddressData, EmptyAddressIndex, LoadedAddressData, LoadedAddressIndex, TxIndex};
|
||||
use brk_types::{
|
||||
EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, TxIndex,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Loaded address data with source tracking for flush operations.
|
||||
pub type LoadedAddressDataWithSource = WithAddressDataSource<LoadedAddressData>;
|
||||
/// Funded address data with source tracking for flush operations.
|
||||
pub type FundedAddressDataWithSource = WithAddressDataSource<FundedAddressData>;
|
||||
|
||||
/// Empty address data with source tracking for flush operations.
|
||||
pub type EmptyAddressDataWithSource = WithAddressDataSource<EmptyAddressData>;
|
||||
@@ -18,9 +20,9 @@ pub type TxIndexVec = SmallVec<[TxIndex; 4]>;
|
||||
pub enum WithAddressDataSource<T> {
|
||||
/// Brand new address (never seen before)
|
||||
New(T),
|
||||
/// Loaded from loaded address storage (with original index)
|
||||
FromLoaded(LoadedAddressIndex, T),
|
||||
/// Loaded from empty address storage (with original index)
|
||||
/// Funded from funded address storage (with original index)
|
||||
FromFunded(FundedAddressIndex, T),
|
||||
/// Funded from empty address storage (with original index)
|
||||
FromEmpty(EmptyAddressIndex, T),
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ impl<T> std::ops::Deref for WithAddressDataSource<T> {
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::New(v) | Self::FromLoaded(_, v) | Self::FromEmpty(_, v) => v,
|
||||
Self::New(v) | Self::FromFunded(_, v) | Self::FromEmpty(_, v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,28 +39,28 @@ impl<T> std::ops::Deref for WithAddressDataSource<T> {
|
||||
impl<T> std::ops::DerefMut for WithAddressDataSource<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
match self {
|
||||
Self::New(v) | Self::FromLoaded(_, v) | Self::FromEmpty(_, v) => v,
|
||||
Self::New(v) | Self::FromFunded(_, v) | Self::FromEmpty(_, v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WithAddressDataSource<EmptyAddressData>> for WithAddressDataSource<LoadedAddressData> {
|
||||
impl From<WithAddressDataSource<EmptyAddressData>> for WithAddressDataSource<FundedAddressData> {
|
||||
#[inline]
|
||||
fn from(value: WithAddressDataSource<EmptyAddressData>) -> Self {
|
||||
match value {
|
||||
WithAddressDataSource::New(v) => Self::New(v.into()),
|
||||
WithAddressDataSource::FromLoaded(i, v) => Self::FromLoaded(i, v.into()),
|
||||
WithAddressDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()),
|
||||
WithAddressDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WithAddressDataSource<LoadedAddressData>> for WithAddressDataSource<EmptyAddressData> {
|
||||
impl From<WithAddressDataSource<FundedAddressData>> for WithAddressDataSource<EmptyAddressData> {
|
||||
#[inline]
|
||||
fn from(value: WithAddressDataSource<LoadedAddressData>) -> Self {
|
||||
fn from(value: WithAddressDataSource<FundedAddressData>) -> Self {
|
||||
match value {
|
||||
WithAddressDataSource::New(v) => Self::New(v.into()),
|
||||
WithAddressDataSource::FromLoaded(i, v) => Self::FromLoaded(i, v.into()),
|
||||
WithAddressDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()),
|
||||
WithAddressDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::distribution::address::HeightToAddressTypeToVec;
|
||||
|
||||
use super::super::{
|
||||
cache::{AddressCache, load_uncached_address_data},
|
||||
cohort::{LoadedAddressDataWithSource, TxIndexVec},
|
||||
cohort::{FundedAddressDataWithSource, TxIndexVec},
|
||||
};
|
||||
|
||||
/// Result of processing inputs for a block.
|
||||
@@ -23,7 +23,7 @@ pub struct InputsResult {
|
||||
/// Per-height, per-address-type sent data: (typeindex, value) for each address.
|
||||
pub sent_data: HeightToAddressTypeToVec<(TypeIndex, Sats)>,
|
||||
/// Address data looked up during processing, keyed by (address_type, typeindex).
|
||||
pub address_data: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
pub address_data: AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
/// Transaction indexes per address for tx_count tracking.
|
||||
pub txindex_vecs: AddressTypeToTypeIndexMap<TxIndexVec>,
|
||||
}
|
||||
@@ -100,7 +100,7 @@ pub fn process_inputs(
|
||||
);
|
||||
let mut sent_data = HeightToAddressTypeToVec::with_capacity(estimated_unique_heights);
|
||||
let mut address_data =
|
||||
AddressTypeToTypeIndexMap::<LoadedAddressDataWithSource>::with_capacity(estimated_per_type);
|
||||
AddressTypeToTypeIndexMap::<FundedAddressDataWithSource>::with_capacity(estimated_per_type);
|
||||
let mut txindex_vecs =
|
||||
AddressTypeToTypeIndexMap::<TxIndexVec>::with_capacity(estimated_per_type);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::distribution::{
|
||||
|
||||
use super::super::{
|
||||
cache::{AddressCache, load_uncached_address_data},
|
||||
cohort::{LoadedAddressDataWithSource, TxIndexVec},
|
||||
cohort::{FundedAddressDataWithSource, TxIndexVec},
|
||||
};
|
||||
|
||||
/// Result of processing outputs for a block.
|
||||
@@ -21,7 +21,7 @@ pub struct OutputsResult {
|
||||
/// Per-address-type received data: (typeindex, value) for each address.
|
||||
pub received_data: AddressTypeToVec<(TypeIndex, Sats)>,
|
||||
/// Address data looked up during processing, keyed by (address_type, typeindex).
|
||||
pub address_data: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
pub address_data: AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
/// Transaction indexes per address for tx_count tracking.
|
||||
pub txindex_vecs: AddressTypeToTypeIndexMap<TxIndexVec>,
|
||||
}
|
||||
@@ -50,7 +50,7 @@ pub fn process_outputs(
|
||||
let mut transacted = Transacted::default();
|
||||
let mut received_data = AddressTypeToVec::with_capacity(estimated_per_type);
|
||||
let mut address_data =
|
||||
AddressTypeToTypeIndexMap::<LoadedAddressDataWithSource>::with_capacity(estimated_per_type);
|
||||
AddressTypeToTypeIndexMap::<FundedAddressDataWithSource>::with_capacity(estimated_per_type);
|
||||
let mut txindex_vecs =
|
||||
AddressTypeToTypeIndexMap::<TxIndexVec>::with_capacity(estimated_per_type);
|
||||
|
||||
|
||||
@@ -263,8 +263,8 @@ pub fn process_blocks(
|
||||
});
|
||||
|
||||
// Merge new address data into current cache
|
||||
cache.merge_loaded(outputs_result.address_data);
|
||||
cache.merge_loaded(inputs_result.address_data);
|
||||
cache.merge_funded(outputs_result.address_data);
|
||||
cache.merge_funded(inputs_result.address_data);
|
||||
|
||||
// Combine txindex_vecs from outputs and inputs, then update tx_count
|
||||
let combined_txindex_vecs = outputs_result
|
||||
@@ -425,14 +425,14 @@ pub fn process_blocks(
|
||||
// Drop readers to release mmap handles
|
||||
drop(vr);
|
||||
|
||||
let (empty_updates, loaded_updates) = cache.take();
|
||||
let (empty_updates, funded_updates) = cache.take();
|
||||
|
||||
// Process address updates (mutations)
|
||||
process_address_updates(
|
||||
&mut vecs.addresses_data,
|
||||
&mut vecs.any_address_indexes,
|
||||
empty_updates,
|
||||
loaded_updates,
|
||||
funded_updates,
|
||||
)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
@@ -451,14 +451,14 @@ pub fn process_blocks(
|
||||
let _lock = exit.lock();
|
||||
drop(vr);
|
||||
|
||||
let (empty_updates, loaded_updates) = cache.take();
|
||||
let (empty_updates, funded_updates) = cache.take();
|
||||
|
||||
// Process address updates (mutations)
|
||||
process_address_updates(
|
||||
&mut vecs.addresses_data,
|
||||
&mut vecs.any_address_indexes,
|
||||
empty_updates,
|
||||
loaded_updates,
|
||||
funded_updates,
|
||||
)?;
|
||||
|
||||
// Write to disk (pure I/O) - save changes for rollback
|
||||
|
||||
@@ -140,7 +140,7 @@ impl VecsReaders {
|
||||
p2wsh: any_address_indexes.p2wsh.create_reader(),
|
||||
},
|
||||
anyaddressindex_to_anyaddressdata: ByAnyAddress {
|
||||
loaded: addresses_data.loaded.create_reader(),
|
||||
funded: addresses_data.funded.create_reader(),
|
||||
empty: addresses_data.empty.create_reader(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ use vecdb::{AnyStoredVec, GenericStoredVec, Stamp};
|
||||
use crate::distribution::{
|
||||
Vecs,
|
||||
block::{
|
||||
EmptyAddressDataWithSource, LoadedAddressDataWithSource, process_empty_addresses,
|
||||
process_loaded_addresses,
|
||||
EmptyAddressDataWithSource, FundedAddressDataWithSource, process_empty_addresses,
|
||||
process_funded_addresses,
|
||||
},
|
||||
state::BlockState,
|
||||
};
|
||||
@@ -21,7 +21,7 @@ use super::super::address::{AddressTypeToTypeIndexMap, AddressesDataVecs, AnyAdd
|
||||
///
|
||||
/// Applies all accumulated address changes to storage structures:
|
||||
/// - Processes empty address transitions
|
||||
/// - Processes loaded address transitions
|
||||
/// - Processes funded address transitions
|
||||
/// - Updates address indexes
|
||||
///
|
||||
/// Call this before `flush()` to prepare data for writing.
|
||||
@@ -29,14 +29,14 @@ pub fn process_address_updates(
|
||||
addresses_data: &mut AddressesDataVecs,
|
||||
address_indexes: &mut AnyAddressIndexesVecs,
|
||||
empty_updates: AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
|
||||
loaded_updates: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
|
||||
funded_updates: AddressTypeToTypeIndexMap<FundedAddressDataWithSource>,
|
||||
) -> Result<()> {
|
||||
info!("Processing address updates...");
|
||||
|
||||
let i = Instant::now();
|
||||
let empty_result = process_empty_addresses(addresses_data, empty_updates)?;
|
||||
let loaded_result = process_loaded_addresses(addresses_data, loaded_updates)?;
|
||||
address_indexes.par_batch_update(empty_result, loaded_result)?;
|
||||
let funded_result = process_funded_addresses(addresses_data, funded_updates)?;
|
||||
address_indexes.par_batch_update(empty_result, funded_result)?;
|
||||
|
||||
info!("Processed address updates in {:?}", i.elapsed());
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{Age, CentsUnsigned, Height, LoadedAddressData, Sats, SupplyState};
|
||||
use brk_types::{Age, CentsUnsigned, FundedAddressData, Height, Sats, SupplyState};
|
||||
use vecdb::unlikely;
|
||||
|
||||
use super::{super::cost_basis::RealizedState, base::CohortState};
|
||||
@@ -42,7 +42,7 @@ impl AddressCohortState {
|
||||
|
||||
pub fn send(
|
||||
&mut self,
|
||||
addressdata: &mut LoadedAddressData,
|
||||
addressdata: &mut FundedAddressData,
|
||||
value: Sats,
|
||||
current_price: CentsUnsigned,
|
||||
prev_price: CentsUnsigned,
|
||||
@@ -54,7 +54,10 @@ impl AddressCohortState {
|
||||
let current = addressdata.cost_basis_snapshot();
|
||||
|
||||
self.inner.send_address(
|
||||
&SupplyState { utxo_count: 1, value },
|
||||
&SupplyState {
|
||||
utxo_count: 1,
|
||||
value,
|
||||
},
|
||||
current_price,
|
||||
prev_price,
|
||||
ath,
|
||||
@@ -68,7 +71,7 @@ impl AddressCohortState {
|
||||
|
||||
pub fn receive(
|
||||
&mut self,
|
||||
address_data: &mut LoadedAddressData,
|
||||
address_data: &mut FundedAddressData,
|
||||
value: Sats,
|
||||
price: CentsUnsigned,
|
||||
) {
|
||||
@@ -77,7 +80,7 @@ impl AddressCohortState {
|
||||
|
||||
pub fn receive_outputs(
|
||||
&mut self,
|
||||
address_data: &mut LoadedAddressData,
|
||||
address_data: &mut FundedAddressData,
|
||||
value: Sats,
|
||||
price: CentsUnsigned,
|
||||
output_count: u32,
|
||||
@@ -87,19 +90,23 @@ impl AddressCohortState {
|
||||
let current = address_data.cost_basis_snapshot();
|
||||
|
||||
self.inner.receive_address(
|
||||
&SupplyState { utxo_count: output_count as u64, value },
|
||||
&SupplyState {
|
||||
utxo_count: output_count as u64,
|
||||
value,
|
||||
},
|
||||
price,
|
||||
¤t,
|
||||
&prev,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add(&mut self, addressdata: &LoadedAddressData) {
|
||||
pub fn add(&mut self, addressdata: &FundedAddressData) {
|
||||
self.addr_count += 1;
|
||||
self.inner.increment_snapshot(&addressdata.cost_basis_snapshot());
|
||||
self.inner
|
||||
.increment_snapshot(&addressdata.cost_basis_snapshot());
|
||||
}
|
||||
|
||||
pub fn subtract(&mut self, addressdata: &LoadedAddressData) {
|
||||
pub fn subtract(&mut self, addressdata: &FundedAddressData) {
|
||||
let snapshot = addressdata.cost_basis_snapshot();
|
||||
|
||||
// Check for potential underflow before it happens
|
||||
@@ -111,7 +118,11 @@ impl AddressCohortState {
|
||||
Address supply: {}\n\
|
||||
Realized price: {}\n\
|
||||
This means the address is not properly tracked in this cohort.",
|
||||
self.addr_count, self.inner.supply, addressdata, snapshot.supply_state, snapshot.realized_price
|
||||
self.addr_count,
|
||||
self.inner.supply,
|
||||
addressdata,
|
||||
snapshot.supply_state,
|
||||
snapshot.realized_price
|
||||
);
|
||||
}
|
||||
if unlikely(self.inner.supply.value < snapshot.supply_state.value) {
|
||||
@@ -122,7 +133,11 @@ impl AddressCohortState {
|
||||
Address supply: {}\n\
|
||||
Realized price: {}\n\
|
||||
This means the address is not properly tracked in this cohort.",
|
||||
self.addr_count, self.inner.supply, addressdata, snapshot.supply_state, snapshot.realized_price
|
||||
self.addr_count,
|
||||
self.inner.supply,
|
||||
addressdata,
|
||||
snapshot.supply_state,
|
||||
snapshot.realized_price
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
DateIndex, EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex,
|
||||
DateIndex, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height,
|
||||
SupplyState, Version,
|
||||
};
|
||||
use tracing::{debug, info};
|
||||
@@ -55,8 +55,8 @@ pub struct Vecs {
|
||||
/// Growth rate (new / addr_count) - lazy ratio with distribution stats, global + per-type
|
||||
pub growth_rate: GrowthRateVecs,
|
||||
|
||||
pub loadedaddressindex:
|
||||
LazyVecFrom1<LoadedAddressIndex, LoadedAddressIndex, LoadedAddressIndex, LoadedAddressData>,
|
||||
pub fundedaddressindex:
|
||||
LazyVecFrom1<FundedAddressIndex, FundedAddressIndex, FundedAddressIndex, FundedAddressData>,
|
||||
pub emptyaddressindex:
|
||||
LazyVecFrom1<EmptyAddressIndex, EmptyAddressIndex, EmptyAddressIndex, EmptyAddressData>,
|
||||
}
|
||||
@@ -92,8 +92,8 @@ impl Vecs {
|
||||
)?;
|
||||
|
||||
// Create address data BytesVecs first so we can also use them for identity mappings
|
||||
let loadedaddressindex_to_loadedaddressdata = BytesVec::forced_import_with(
|
||||
vecdb::ImportOptions::new(&db, "loadedaddressdata", version)
|
||||
let fundedaddressindex_to_fundedaddressdata = BytesVec::forced_import_with(
|
||||
vecdb::ImportOptions::new(&db, "fundedaddressdata", version)
|
||||
.with_saved_stamped_changes(SAVED_STAMPED_CHANGES),
|
||||
)?;
|
||||
let emptyaddressindex_to_emptyaddressdata = BytesVec::forced_import_with(
|
||||
@@ -102,10 +102,10 @@ impl Vecs {
|
||||
)?;
|
||||
|
||||
// Identity mappings for traversable
|
||||
let loadedaddressindex = LazyVecFrom1::init(
|
||||
"loadedaddressindex",
|
||||
let fundedaddressindex = LazyVecFrom1::init(
|
||||
"fundedaddressindex",
|
||||
version,
|
||||
loadedaddressindex_to_loadedaddressdata.boxed_clone(),
|
||||
fundedaddressindex_to_fundedaddressdata.boxed_clone(),
|
||||
|index, _| Some(index),
|
||||
);
|
||||
let emptyaddressindex = LazyVecFrom1::init(
|
||||
@@ -156,10 +156,10 @@ impl Vecs {
|
||||
|
||||
any_address_indexes: AnyAddressIndexesVecs::forced_import(&db, version)?,
|
||||
addresses_data: AddressesDataVecs {
|
||||
loaded: loadedaddressindex_to_loadedaddressdata,
|
||||
funded: fundedaddressindex_to_fundedaddressdata,
|
||||
empty: emptyaddressindex_to_emptyaddressdata,
|
||||
},
|
||||
loadedaddressindex,
|
||||
fundedaddressindex,
|
||||
emptyaddressindex,
|
||||
|
||||
db,
|
||||
|
||||
@@ -30,7 +30,7 @@ fn main() -> Result<()> {
|
||||
|
||||
println!("Loading indexer from: {}", outputs_dir.display());
|
||||
let indexer = Indexer::forced_import(&outputs_dir)?;
|
||||
println!("✅ Indexer loaded\n");
|
||||
println!("✅ Indexer funded\n");
|
||||
|
||||
// Warmup run
|
||||
println!("🔥 Warmup run...");
|
||||
|
||||
@@ -58,10 +58,10 @@ impl Query {
|
||||
.get_once(outputtype, type_index)?;
|
||||
|
||||
let address_data = match any_address_index.to_enum() {
|
||||
AnyAddressDataIndexEnum::Loaded(index) => computer
|
||||
AnyAddressDataIndexEnum::Funded(index) => computer
|
||||
.distribution
|
||||
.addresses_data
|
||||
.loaded
|
||||
.funded
|
||||
.iter()?
|
||||
.get_unwrap(index),
|
||||
AnyAddressDataIndexEnum::Empty(index) => computer
|
||||
|
||||
@@ -4,11 +4,11 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::{EmptyAddressIndex, LoadedAddressIndex, TypeIndex};
|
||||
use crate::{EmptyAddressIndex, FundedAddressIndex, TypeIndex};
|
||||
|
||||
const MIN_EMPTY_INDEX: u32 = u32::MAX - 4_000_000_000;
|
||||
|
||||
/// Unified index for any address type (loaded or empty)
|
||||
/// Unified index for any address type (funded or empty)
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Bytes, JsonSchema)]
|
||||
pub struct AnyAddressIndex(TypeIndex);
|
||||
|
||||
@@ -18,9 +18,9 @@ impl AnyAddressIndex {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadedAddressIndex> for AnyAddressIndex {
|
||||
impl From<FundedAddressIndex> for AnyAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: LoadedAddressIndex) -> Self {
|
||||
fn from(value: FundedAddressIndex) -> Self {
|
||||
if u32::from(value) >= MIN_EMPTY_INDEX {
|
||||
panic!("{value} is higher than MIN_EMPTY_INDEX ({MIN_EMPTY_INDEX})")
|
||||
}
|
||||
@@ -70,7 +70,7 @@ impl Formattable for AnyAddressIndex {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AnyAddressDataIndexEnum {
|
||||
Loaded(LoadedAddressIndex),
|
||||
Funded(FundedAddressIndex),
|
||||
Empty(EmptyAddressIndex),
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ impl From<AnyAddressIndex> for AnyAddressDataIndexEnum {
|
||||
if uvalue >= MIN_EMPTY_INDEX {
|
||||
Self::Empty(EmptyAddressIndex::from(uvalue - MIN_EMPTY_INDEX))
|
||||
} else {
|
||||
Self::Loaded(LoadedAddressIndex::from(value.0))
|
||||
Self::Funded(FundedAddressIndex::from(value.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ impl From<AnyAddressDataIndexEnum> for AnyAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: AnyAddressDataIndexEnum) -> Self {
|
||||
match value {
|
||||
AnyAddressDataIndexEnum::Loaded(idx) => Self::from(idx),
|
||||
AnyAddressDataIndexEnum::Funded(idx) => Self::from(idx),
|
||||
AnyAddressDataIndexEnum::Empty(idx) => Self::from(idx),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::{LoadedAddressData, Sats};
|
||||
use crate::{FundedAddressData, Sats};
|
||||
|
||||
/// Data of an empty address
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -16,16 +16,16 @@ pub struct EmptyAddressData {
|
||||
pub transfered: Sats,
|
||||
}
|
||||
|
||||
impl From<LoadedAddressData> for EmptyAddressData {
|
||||
impl From<FundedAddressData> for EmptyAddressData {
|
||||
#[inline]
|
||||
fn from(value: LoadedAddressData) -> Self {
|
||||
fn from(value: FundedAddressData) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LoadedAddressData> for EmptyAddressData {
|
||||
impl From<&FundedAddressData> for EmptyAddressData {
|
||||
#[inline]
|
||||
fn from(value: &LoadedAddressData) -> Self {
|
||||
fn from(value: &FundedAddressData) -> Self {
|
||||
if value.sent != value.received {
|
||||
dbg!(&value);
|
||||
panic!("Trying to convert not empty wallet to empty !");
|
||||
|
||||
@@ -31,10 +31,10 @@ impl CostBasisSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
/// Data for a loaded (non-empty) address with current balance
|
||||
/// Data for a funded (non-empty) address with current balance
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct LoadedAddressData {
|
||||
pub struct FundedAddressData {
|
||||
/// Total transaction count
|
||||
pub tx_count: u32,
|
||||
/// Number of transaction outputs funded to this address
|
||||
@@ -53,7 +53,7 @@ pub struct LoadedAddressData {
|
||||
pub investor_cap_raw: CentsSquaredSats,
|
||||
}
|
||||
|
||||
impl LoadedAddressData {
|
||||
impl FundedAddressData {
|
||||
pub fn balance(&self) -> Sats {
|
||||
(u64::from(self.received) - u64::from(self.sent)).into()
|
||||
}
|
||||
@@ -87,7 +87,7 @@ impl LoadedAddressData {
|
||||
.checked_sub(self.spent_txo_count)
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"LoadedAddressData corruption: spent_txo_count ({}) > funded_txo_count ({}). \
|
||||
"FundedAddressData corruption: spent_txo_count ({}) > funded_txo_count ({}). \
|
||||
Address data: {:?}",
|
||||
self.spent_txo_count, self.funded_txo_count, self
|
||||
)
|
||||
@@ -138,14 +138,14 @@ impl LoadedAddressData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EmptyAddressData> for LoadedAddressData {
|
||||
impl From<EmptyAddressData> for FundedAddressData {
|
||||
#[inline]
|
||||
fn from(value: EmptyAddressData) -> Self {
|
||||
Self::from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EmptyAddressData> for LoadedAddressData {
|
||||
impl From<&EmptyAddressData> for FundedAddressData {
|
||||
#[inline]
|
||||
fn from(value: &EmptyAddressData) -> Self {
|
||||
Self {
|
||||
@@ -161,7 +161,7 @@ impl From<&EmptyAddressData> for LoadedAddressData {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoadedAddressData {
|
||||
impl std::fmt::Display for FundedAddressData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
@@ -177,14 +177,14 @@ impl std::fmt::Display for LoadedAddressData {
|
||||
}
|
||||
}
|
||||
|
||||
impl Formattable for LoadedAddressData {
|
||||
impl Formattable for FundedAddressData {
|
||||
#[inline(always)]
|
||||
fn may_need_escaping() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Bytes for LoadedAddressData {
|
||||
impl Bytes for FundedAddressData {
|
||||
type Array = [u8; size_of::<Self>()];
|
||||
|
||||
fn to_bytes(&self) -> Self::Array {
|
||||
@@ -22,61 +22,61 @@ use crate::TypeIndex;
|
||||
Pco,
|
||||
JsonSchema,
|
||||
)]
|
||||
pub struct LoadedAddressIndex(TypeIndex);
|
||||
pub struct FundedAddressIndex(TypeIndex);
|
||||
|
||||
impl From<TypeIndex> for LoadedAddressIndex {
|
||||
impl From<TypeIndex> for FundedAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: TypeIndex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for LoadedAddressIndex {
|
||||
impl From<usize> for FundedAddressIndex {
|
||||
#[inline]
|
||||
fn from(value: usize) -> Self {
|
||||
Self(TypeIndex::from(value))
|
||||
}
|
||||
}
|
||||
impl From<LoadedAddressIndex> for usize {
|
||||
impl From<FundedAddressIndex> for usize {
|
||||
#[inline]
|
||||
fn from(value: LoadedAddressIndex) -> Self {
|
||||
fn from(value: FundedAddressIndex) -> Self {
|
||||
usize::from(value.0)
|
||||
}
|
||||
}
|
||||
impl From<LoadedAddressIndex> for u32 {
|
||||
impl From<FundedAddressIndex> for u32 {
|
||||
#[inline]
|
||||
fn from(value: LoadedAddressIndex) -> Self {
|
||||
fn from(value: FundedAddressIndex) -> Self {
|
||||
u32::from(value.0)
|
||||
}
|
||||
}
|
||||
impl Add<usize> for LoadedAddressIndex {
|
||||
impl Add<usize> for FundedAddressIndex {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
impl CheckedSub<LoadedAddressIndex> for LoadedAddressIndex {
|
||||
impl CheckedSub<FundedAddressIndex> for FundedAddressIndex {
|
||||
fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
self.0.checked_sub(rhs.0).map(Self)
|
||||
}
|
||||
}
|
||||
impl PrintableIndex for LoadedAddressIndex {
|
||||
impl PrintableIndex for FundedAddressIndex {
|
||||
fn to_string() -> &'static str {
|
||||
"loadedaddressindex"
|
||||
"fundedaddressindex"
|
||||
}
|
||||
|
||||
fn to_possible_strings() -> &'static [&'static str] {
|
||||
&["loadedaddr", "loadedaddressindex"]
|
||||
&["fundedaddr", "fundedaddressindex"]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LoadedAddressIndex {
|
||||
impl std::fmt::Display for FundedAddressIndex {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Formattable for LoadedAddressIndex {
|
||||
impl Formattable for FundedAddressIndex {
|
||||
#[inline(always)]
|
||||
fn may_need_escaping() -> bool {
|
||||
false
|
||||
@@ -9,7 +9,7 @@ use crate::PairOutputIndex;
|
||||
|
||||
use super::{
|
||||
Date, DateIndex, DecadeIndex, DifficultyEpoch, EmptyAddressIndex, EmptyOutputIndex, HalvingEpoch,
|
||||
Height, LoadedAddressIndex, MonthIndex, OpReturnIndex, P2AAddressIndex, P2MSOutputIndex,
|
||||
Height, FundedAddressIndex, MonthIndex, OpReturnIndex, P2AAddressIndex, P2MSOutputIndex,
|
||||
P2PK33AddressIndex, P2PK65AddressIndex, P2PKHAddressIndex, P2SHAddressIndex, P2TRAddressIndex,
|
||||
P2WPKHAddressIndex, P2WSHAddressIndex, QuarterIndex, SemesterIndex, TxInIndex, TxIndex,
|
||||
TxOutIndex, UnknownOutputIndex, WeekIndex, YearIndex,
|
||||
@@ -46,7 +46,7 @@ pub enum Index {
|
||||
UnknownOutputIndex,
|
||||
WeekIndex,
|
||||
YearIndex,
|
||||
LoadedAddressIndex,
|
||||
FundedAddressIndex,
|
||||
EmptyAddressIndex,
|
||||
PairOutputIndex,
|
||||
}
|
||||
@@ -79,7 +79,7 @@ impl Index {
|
||||
Self::UnknownOutputIndex,
|
||||
Self::WeekIndex,
|
||||
Self::YearIndex,
|
||||
Self::LoadedAddressIndex,
|
||||
Self::FundedAddressIndex,
|
||||
Self::EmptyAddressIndex,
|
||||
Self::PairOutputIndex,
|
||||
]
|
||||
@@ -112,7 +112,7 @@ impl Index {
|
||||
Self::UnknownOutputIndex => UnknownOutputIndex::to_possible_strings(),
|
||||
Self::WeekIndex => WeekIndex::to_possible_strings(),
|
||||
Self::YearIndex => YearIndex::to_possible_strings(),
|
||||
Self::LoadedAddressIndex => LoadedAddressIndex::to_possible_strings(),
|
||||
Self::FundedAddressIndex => FundedAddressIndex::to_possible_strings(),
|
||||
Self::EmptyAddressIndex => EmptyAddressIndex::to_possible_strings(),
|
||||
Self::PairOutputIndex => PairOutputIndex::to_possible_strings(),
|
||||
}
|
||||
@@ -216,8 +216,8 @@ impl TryFrom<&str> for Index {
|
||||
v if (Self::UnknownOutputIndex).possible_values().contains(&v) => {
|
||||
Self::UnknownOutputIndex
|
||||
}
|
||||
v if (Self::LoadedAddressIndex).possible_values().contains(&v) => {
|
||||
Self::LoadedAddressIndex
|
||||
v if (Self::FundedAddressIndex).possible_values().contains(&v) => {
|
||||
Self::FundedAddressIndex
|
||||
}
|
||||
v if (Self::EmptyAddressIndex).possible_values().contains(&v) => {
|
||||
Self::EmptyAddressIndex
|
||||
|
||||
@@ -35,11 +35,11 @@ mod blockstatus;
|
||||
mod blocktimestamp;
|
||||
mod blockweightentry;
|
||||
mod bytes;
|
||||
mod cents_sats;
|
||||
mod cents_signed;
|
||||
mod cents_squared_sats;
|
||||
mod cents_unsigned;
|
||||
mod cents_unsigned_compact;
|
||||
mod cents_sats;
|
||||
mod cents_squared_sats;
|
||||
mod datarange;
|
||||
mod datarangeformat;
|
||||
mod date;
|
||||
@@ -60,6 +60,8 @@ mod feerate;
|
||||
mod feeratepercentiles;
|
||||
mod format;
|
||||
mod formatresponse;
|
||||
mod fundedaddressdata;
|
||||
mod fundedaddressindex;
|
||||
mod halvingepoch;
|
||||
mod hashrateentry;
|
||||
mod hashratesummary;
|
||||
@@ -72,8 +74,6 @@ mod indexes;
|
||||
mod indexinfo;
|
||||
mod limit;
|
||||
mod limitparam;
|
||||
mod loadedaddressdata;
|
||||
mod loadedaddressindex;
|
||||
mod mempoolblock;
|
||||
mod mempoolentryinfo;
|
||||
mod mempoolinfo;
|
||||
@@ -211,11 +211,11 @@ pub use blockstatus::*;
|
||||
pub use blocktimestamp::*;
|
||||
pub use blockweightentry::*;
|
||||
pub use bytes::*;
|
||||
pub use cents_sats::*;
|
||||
pub use cents_signed::*;
|
||||
pub use cents_squared_sats::*;
|
||||
pub use cents_unsigned::*;
|
||||
pub use cents_unsigned_compact::*;
|
||||
pub use cents_sats::*;
|
||||
pub use cents_squared_sats::*;
|
||||
pub use datarange::*;
|
||||
pub use datarangeformat::*;
|
||||
pub use date::*;
|
||||
@@ -236,6 +236,8 @@ pub use feerate::*;
|
||||
pub use feeratepercentiles::*;
|
||||
pub use format::*;
|
||||
pub use formatresponse::*;
|
||||
pub use fundedaddressdata::*;
|
||||
pub use fundedaddressindex::*;
|
||||
pub use halvingepoch::*;
|
||||
pub use hashrateentry::*;
|
||||
pub use hashratesummary::*;
|
||||
@@ -248,8 +250,6 @@ pub use indexes::*;
|
||||
pub use indexinfo::*;
|
||||
pub use limit::*;
|
||||
pub use limitparam::*;
|
||||
pub use loadedaddressdata::*;
|
||||
pub use loadedaddressindex::*;
|
||||
pub use mempoolblock::*;
|
||||
pub use mempoolentryinfo::*;
|
||||
pub use mempoolinfo::*;
|
||||
|
||||
@@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::{Bytes, Formattable};
|
||||
|
||||
use crate::{CheckedSub, LoadedAddressData, Sats};
|
||||
use crate::{CheckedSub, FundedAddressData, Sats};
|
||||
|
||||
/// Current supply state tracking UTXO count and total value
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -65,9 +65,9 @@ impl SubAssign<&SupplyState> for SupplyState {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LoadedAddressData> for SupplyState {
|
||||
impl From<&FundedAddressData> for SupplyState {
|
||||
#[inline]
|
||||
fn from(value: &LoadedAddressData) -> Self {
|
||||
fn from(value: &FundedAddressData) -> Self {
|
||||
Self {
|
||||
utxo_count: value.utxo_count() as u64,
|
||||
value: value.balance(),
|
||||
|
||||
@@ -86,10 +86,7 @@ const fuchsia = createColor(() => getColor("fuchsia"));
|
||||
const pink = createColor(() => getColor("pink"));
|
||||
const rose = createColor(() => getColor("rose"));
|
||||
|
||||
const baseColors = {
|
||||
default: createColor(() => getLightDarkValue("--color")),
|
||||
gray: createColor(() => getColor("gray")),
|
||||
border: createColor(() => getLightDarkValue("--border-color")),
|
||||
const spectrumColors = {
|
||||
red,
|
||||
orange,
|
||||
amber,
|
||||
@@ -110,6 +107,13 @@ const baseColors = {
|
||||
rose,
|
||||
};
|
||||
|
||||
const baseColors = {
|
||||
default: createColor(() => getLightDarkValue("--color")),
|
||||
gray: createColor(() => getColor("gray")),
|
||||
border: createColor(() => getLightDarkValue("--border-color")),
|
||||
...spectrumColors,
|
||||
};
|
||||
|
||||
export const colors = {
|
||||
...baseColors,
|
||||
|
||||
@@ -127,6 +131,15 @@ export const colors = {
|
||||
min: red,
|
||||
},
|
||||
|
||||
/** Common time period colors */
|
||||
time: {
|
||||
_24h: pink,
|
||||
_1w: red,
|
||||
_1m: yellow,
|
||||
_1y: lime,
|
||||
all: teal,
|
||||
},
|
||||
|
||||
/** DCA period colors by term */
|
||||
dcaPeriods: {
|
||||
// Short term
|
||||
@@ -170,3 +183,12 @@ export const colors = {
|
||||
* @typedef {typeof colors} Colors
|
||||
* @typedef {keyof typeof baseColors} ColorName
|
||||
*/
|
||||
|
||||
/** Palette for indexed series */
|
||||
const palette = Object.values(spectrumColors);
|
||||
|
||||
/**
|
||||
* Get a color by index (cycles through palette)
|
||||
* @param {number} index
|
||||
*/
|
||||
export const colorAt = (index) => palette[index % palette.length];
|
||||
|
||||
@@ -1351,7 +1351,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
|
||||
const options = blueprint.options;
|
||||
const indexes = Object.keys(blueprint.metric.by);
|
||||
|
||||
const defaultColor = unit === Unit.usd || unit === Unit.usdCumulative ? colors.green : colors.orange;
|
||||
const defaultColor = unit === Unit.usd ? colors.green : colors.orange;
|
||||
|
||||
if (indexes.includes(idx)) {
|
||||
switch (blueprint.type) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
fromSumStatsPattern,
|
||||
fromBaseStatsPattern,
|
||||
fromFullStatsPattern,
|
||||
fromStatsPattern,
|
||||
fromCoinbasePattern,
|
||||
fromValuePattern,
|
||||
fromBitcoinPatternWithUnit,
|
||||
fromCountPattern,
|
||||
fromSupplyPattern,
|
||||
chartsFromFull,
|
||||
chartsFromSum,
|
||||
chartsFromCount,
|
||||
chartsFromValue,
|
||||
chartsFromValueFull,
|
||||
} from "./series.js";
|
||||
import { colors } from "../chart/colors.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
|
||||
/**
|
||||
* @template {(arg: any, ...args: any[]) => any} F
|
||||
@@ -29,6 +29,18 @@ const bind = (fn) =>
|
||||
(...args) => fn(colors, ...args)
|
||||
);
|
||||
|
||||
/**
|
||||
* Create distribution series for btc/sats/usd from a value pattern with stats (average + percentiles)
|
||||
* @param {FullValuePattern | SumValuePattern} source
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
const distributionBtcSatsUsd = (source) => [
|
||||
...fromStatsPattern(colors, { pattern: source.bitcoin, unit: Unit.btc }),
|
||||
...fromStatsPattern(colors, { pattern: source.sats, unit: Unit.sats }),
|
||||
...fromStatsPattern(colors, { pattern: source.dollars, unit: Unit.usd }),
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Create a context object with all dependencies for building partial options
|
||||
* @param {Object} args
|
||||
@@ -38,14 +50,16 @@ export function createContext({ brk }) {
|
||||
return {
|
||||
colors,
|
||||
brk,
|
||||
fromSumStatsPattern: bind(fromSumStatsPattern),
|
||||
// Series helpers (return series arrays for a single chart)
|
||||
fromBaseStatsPattern: bind(fromBaseStatsPattern),
|
||||
fromFullStatsPattern: bind(fromFullStatsPattern),
|
||||
fromStatsPattern: bind(fromStatsPattern),
|
||||
fromCoinbasePattern: bind(fromCoinbasePattern),
|
||||
fromValuePattern,
|
||||
fromBitcoinPatternWithUnit,
|
||||
fromCountPattern,
|
||||
fromSupplyPattern,
|
||||
distributionBtcSatsUsd,
|
||||
// Chart helpers (return chart trees for Sum/Distribution/Cumulative folders)
|
||||
chartsFromFull: bind(chartsFromFull),
|
||||
chartsFromSum: bind(chartsFromSum),
|
||||
chartsFromCount,
|
||||
chartsFromValue,
|
||||
chartsFromValueFull: bind(chartsFromValueFull),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -223,7 +223,6 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.usd, defaultActive: false }),
|
||||
]
|
||||
: []),
|
||||
// RealizedPattern (address cohorts) doesn't have realizedCapRelToOwnMarketCap
|
||||
@@ -437,9 +436,25 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
name: "Peak Regret",
|
||||
title: title("Peak Regret"),
|
||||
bottom: [
|
||||
line({ metric: realized.peakRegret.sum, name: "Sum", color: colors.red, unit: Unit.usd }),
|
||||
line({ metric: realized.peakRegret.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }),
|
||||
baseline({ metric: realized.peakRegretRelToRealizedCap, name: "Rel. to Realized Cap", color: colors.orange, unit: Unit.pctRcap }),
|
||||
line({
|
||||
metric: realized.peakRegret.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: realized.peakRegret.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.peakRegretRelToRealizedCap,
|
||||
name: "Rel. to Realized Cap",
|
||||
color: colors.orange,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -449,35 +464,109 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
bottom: [
|
||||
line({ metric: realized.sentInProfit.bitcoin.sum, name: "Sum", color: colors.green, unit: Unit.btc }),
|
||||
line({ metric: realized.sentInProfit.bitcoin.cumulative, name: "Cumulative", color: colors.green, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: realized.sentInProfit.sats.sum, name: "Sum", color: colors.green, unit: Unit.sats }),
|
||||
line({ metric: realized.sentInProfit.sats.cumulative, name: "Cumulative", color: colors.green, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: realized.sentInProfit.dollars.sum, name: "Sum", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: realized.sentInProfit.dollars.cumulative, name: "Cumulative", color: colors.green, unit: Unit.usd, defaultActive: false }),
|
||||
line({
|
||||
metric: realized.sentInProfit.bitcoin.sum,
|
||||
name: "Sum",
|
||||
color: colors.green,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInProfit.bitcoin.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInProfit.sats.sum,
|
||||
name: "Sum",
|
||||
color: colors.green,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInProfit.sats.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInProfit.dollars.sum,
|
||||
name: "Sum",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInProfit.dollars.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Sent In Loss"),
|
||||
bottom: [
|
||||
line({ metric: realized.sentInLoss.bitcoin.sum, name: "Sum", color: colors.red, unit: Unit.btc }),
|
||||
line({ metric: realized.sentInLoss.bitcoin.cumulative, name: "Cumulative", color: colors.red, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: realized.sentInLoss.sats.sum, name: "Sum", color: colors.red, unit: Unit.sats }),
|
||||
line({ metric: realized.sentInLoss.sats.cumulative, name: "Cumulative", color: colors.red, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: realized.sentInLoss.dollars.sum, name: "Sum", color: colors.red, unit: Unit.usd }),
|
||||
line({ metric: realized.sentInLoss.dollars.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }),
|
||||
line({
|
||||
metric: realized.sentInLoss.bitcoin.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInLoss.bitcoin.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInLoss.sats.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInLoss.sats.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInLoss.dollars.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sentInLoss.dollars.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: satsBtcUsd({ pattern: realized.sentInProfit14dEma, name: "14d EMA", color: colors.green }),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: realized.sentInProfit14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.green,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: satsBtcUsd({ pattern: realized.sentInLoss14dEma, name: "14d EMA", color: colors.red }),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: realized.sentInLoss14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.red,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -493,9 +582,27 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
*/
|
||||
function createGroupedRealizedPnlSection(ctx, list, title) {
|
||||
const pnlConfigs = /** @type {const} */ ([
|
||||
{ name: "Profit", sum: "realizedProfit", ema: "realizedProfit7dEma", rel: "realizedProfitRelToRealizedCap", isNet: false },
|
||||
{ name: "Loss", sum: "realizedLoss", ema: "realizedLoss7dEma", rel: "realizedLossRelToRealizedCap", isNet: false },
|
||||
{ name: "Net P&L", sum: "netRealizedPnl", ema: "netRealizedPnl7dEma", rel: "netRealizedPnlRelToRealizedCap", isNet: true },
|
||||
{
|
||||
name: "Profit",
|
||||
sum: "realizedProfit",
|
||||
ema: "realizedProfit7dEma",
|
||||
rel: "realizedProfitRelToRealizedCap",
|
||||
isNet: false,
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
sum: "realizedLoss",
|
||||
ema: "realizedLoss7dEma",
|
||||
rel: "realizedLossRelToRealizedCap",
|
||||
isNet: false,
|
||||
},
|
||||
{
|
||||
name: "Net P&L",
|
||||
sum: "netRealizedPnl",
|
||||
ema: "netRealizedPnl7dEma",
|
||||
rel: "netRealizedPnlRelToRealizedCap",
|
||||
isNet: true,
|
||||
},
|
||||
]);
|
||||
|
||||
return [
|
||||
@@ -507,10 +614,19 @@ function createGroupedRealizedPnlSection(ctx, list, title) {
|
||||
title: title(`Realized ${name}`),
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
(isNet ? baseline : line)({ metric: tree.realized[sum].sum, name, color, unit: Unit.usd }),
|
||||
baseline({ metric: tree.realized[rel].sum, name, color, unit: Unit.pctRcap }),
|
||||
(isNet ? baseline : line)({
|
||||
metric: tree.realized[sum].sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized[rel].sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -518,9 +634,13 @@ function createGroupedRealizedPnlSection(ctx, list, title) {
|
||||
title: title(`Realized ${name} 7d EMA`),
|
||||
bottom: [
|
||||
...list.map(({ color, name, tree }) =>
|
||||
(isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }),
|
||||
(isNet ? baseline : line)({
|
||||
metric: tree.realized[ema],
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -532,21 +652,36 @@ function createGroupedRealizedPnlSection(ctx, list, title) {
|
||||
name: "Sum",
|
||||
title: title("Peak Regret"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.peakRegret.sum, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.peakRegret.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Peak Regret Cumulative"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.peakRegret.cumulative, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.peakRegret.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Rel. to Realized Cap",
|
||||
title: title("Peak Regret Rel. to Realized Cap"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({ metric: tree.realized.peakRegretRelToRealizedCap, name, color, unit: Unit.pctRcap }),
|
||||
baseline({
|
||||
metric: tree.realized.peakRegretRelToRealizedCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
@@ -558,50 +693,118 @@ function createGroupedRealizedPnlSection(ctx, list, title) {
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInProfit.bitcoin.sum, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInProfit.sats.sum, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInProfit.dollars.sum, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.bitcoin.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.sats.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.dollars.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Profit Cumulative",
|
||||
title: title("Sent In Profit Cumulative"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInProfit.sats.cumulative, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInProfit.dollars.cumulative, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.bitcoin.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.sats.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.dollars.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Sent In Loss"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInLoss.bitcoin.sum, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInLoss.sats.sum, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInLoss.dollars.sum, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.bitcoin.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.sats.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.dollars.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Loss Cumulative",
|
||||
title: title("Sent In Loss Cumulative"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInLoss.sats.cumulative, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInLoss.dollars.cumulative, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.bitcoin.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.sats.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.dollars.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name, color }),
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInProfit14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name, color }),
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInLoss14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -12,16 +12,7 @@ import {
|
||||
spendableTypeColors,
|
||||
yearColors,
|
||||
} from "../colors/index.js";
|
||||
|
||||
/**
|
||||
* @template {Record<string, any>} T
|
||||
* @param {T} obj
|
||||
* @returns {[keyof T & string, T[keyof T & string]][]}
|
||||
*/
|
||||
const entries = (obj) =>
|
||||
/** @type {[keyof T & string, T[keyof T & string]][]} */ (
|
||||
Object.entries(obj)
|
||||
);
|
||||
import { entries } from "../../utils/array.js";
|
||||
|
||||
/** @type {readonly AddressableType[]} */
|
||||
const ADDRESSABLE_TYPES = [
|
||||
|
||||
@@ -854,7 +854,6 @@ function createSingleRealizedPriceChartsBasic(ctx, cohort, title) {
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio, number: 1 }),
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -891,7 +890,6 @@ function createSingleRealizedCapSeries(ctx, cohort, { extra = [] } = {}) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.usd, defaultActive: false }),
|
||||
...extra,
|
||||
];
|
||||
}
|
||||
@@ -983,56 +981,89 @@ function createSingleRealizedPnlSection(
|
||||
title,
|
||||
{ extra = [] } = {},
|
||||
) {
|
||||
const { colors, fromCountPattern, fromBitcoinPatternWithUnit } = ctx;
|
||||
const { colors } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
return [
|
||||
{
|
||||
name: "P&L",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Realized P&L"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.realized.realizedProfit.sum,
|
||||
name: "Profit",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedProfit7dEma,
|
||||
name: "Profit 7d EMA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedLoss.sum,
|
||||
name: "Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedLoss7dEma,
|
||||
name: "Loss 7d EMA",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.negRealizedLoss.sum,
|
||||
name: "Negative Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...extra,
|
||||
line({
|
||||
metric: tree.realized.totalRealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Realized P&L (Total)"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.realized.realizedProfit.cumulative,
|
||||
name: "Profit",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedLoss.cumulative,
|
||||
name: "Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.negRealizedLoss.cumulative,
|
||||
name: "Negative Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "P&L Relative",
|
||||
title: title("Realized P&L"),
|
||||
bottom: [
|
||||
...fromCountPattern({
|
||||
pattern: tree.realized.realizedProfit,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Profit",
|
||||
color: colors.green,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedProfit7dEma,
|
||||
name: "Profit 7d EMA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: tree.realized.realizedLoss,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Loss",
|
||||
color: colors.red,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedLoss7dEma,
|
||||
name: "Loss 7d EMA",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
...fromBitcoinPatternWithUnit({
|
||||
pattern: tree.realized.negRealizedLoss,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Negative Loss",
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...extra,
|
||||
line({
|
||||
metric: tree.realized.totalRealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
|
||||
name: "Profit",
|
||||
@@ -1059,57 +1090,67 @@ function createSingleRealizedPnlSection(
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.pctRcap }),
|
||||
priceLine({ ctx, unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Net P&L",
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: [
|
||||
...fromCountPattern({
|
||||
pattern: tree.realized.netRealizedPnl,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Net",
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl7dEma,
|
||||
name: "Net 7d EMA",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
|
||||
name: "Net",
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.cumulative,
|
||||
name: "Net Cumulative",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
priceLine({ ctx, unit: Unit.pctRcap }),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.sum,
|
||||
name: "Net",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl7dEma,
|
||||
name: "Net 7d EMA",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
|
||||
name: "Net",
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "30d Change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Net Realized P&L (Total)"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.cumulative,
|
||||
name: "Net",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
||||
name: "30d Change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.cumulative,
|
||||
name: "Net",
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
||||
name: "30d Change",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1119,35 +1160,109 @@ function createSingleRealizedPnlSection(
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
bottom: [
|
||||
line({ metric: tree.realized.sentInProfit.bitcoin.sum, name: "Sum", color: colors.green, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name: "Cumulative", color: colors.green, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: tree.realized.sentInProfit.sats.sum, name: "Sum", color: colors.green, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInProfit.sats.cumulative, name: "Cumulative", color: colors.green, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: tree.realized.sentInProfit.dollars.sum, name: "Sum", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: tree.realized.sentInProfit.dollars.cumulative, name: "Cumulative", color: colors.green, unit: Unit.usd, defaultActive: false }),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.bitcoin.sum,
|
||||
name: "Sum",
|
||||
color: colors.green,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.bitcoin.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.sats.sum,
|
||||
name: "Sum",
|
||||
color: colors.green,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.sats.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.dollars.sum,
|
||||
name: "Sum",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.dollars.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Sent In Loss"),
|
||||
bottom: [
|
||||
line({ metric: tree.realized.sentInLoss.bitcoin.sum, name: "Sum", color: colors.red, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name: "Cumulative", color: colors.red, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: tree.realized.sentInLoss.sats.sum, name: "Sum", color: colors.red, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInLoss.sats.cumulative, name: "Cumulative", color: colors.red, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: tree.realized.sentInLoss.dollars.sum, name: "Sum", color: colors.red, unit: Unit.usd }),
|
||||
line({ metric: tree.realized.sentInLoss.dollars.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.bitcoin.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.bitcoin.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.sats.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.sats.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.dollars.sum,
|
||||
name: "Sum",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.dollars.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name: "14d EMA", color: colors.green }),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: tree.realized.sentInProfit14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.green,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name: "14d EMA", color: colors.red }),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: tree.realized.sentInLoss14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.red,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1171,9 +1286,27 @@ function createGroupedRealizedPnlSections(
|
||||
{ ratioMetrics } = {},
|
||||
) {
|
||||
const pnlConfigs = /** @type {const} */ ([
|
||||
{ name: "Profit", sum: "realizedProfit", ema: "realizedProfit7dEma", rel: "realizedProfitRelToRealizedCap", isNet: false },
|
||||
{ name: "Loss", sum: "realizedLoss", ema: "realizedLoss7dEma", rel: "realizedLossRelToRealizedCap", isNet: false },
|
||||
{ name: "Net P&L", sum: "netRealizedPnl", ema: "netRealizedPnl7dEma", rel: "netRealizedPnlRelToRealizedCap", isNet: true },
|
||||
{
|
||||
name: "Profit",
|
||||
sum: "realizedProfit",
|
||||
ema: "realizedProfit7dEma",
|
||||
rel: "realizedProfitRelToRealizedCap",
|
||||
isNet: false,
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
sum: "realizedLoss",
|
||||
ema: "realizedLoss7dEma",
|
||||
rel: "realizedLossRelToRealizedCap",
|
||||
isNet: false,
|
||||
},
|
||||
{
|
||||
name: "Net P&L",
|
||||
sum: "netRealizedPnl",
|
||||
ema: "netRealizedPnl7dEma",
|
||||
rel: "netRealizedPnlRelToRealizedCap",
|
||||
isNet: true,
|
||||
},
|
||||
]);
|
||||
|
||||
return [
|
||||
@@ -1185,10 +1318,19 @@ function createGroupedRealizedPnlSections(
|
||||
title: title(`Realized ${name}`),
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
(isNet ? baseline : line)({ metric: tree.realized[sum].sum, name, color, unit: Unit.usd }),
|
||||
baseline({ metric: tree.realized[rel].sum, name, color, unit: Unit.pctRcap }),
|
||||
(isNet ? baseline : line)({
|
||||
metric: tree.realized[sum].sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized[rel].sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1196,9 +1338,13 @@ function createGroupedRealizedPnlSections(
|
||||
title: title(`Realized ${name} 7d EMA`),
|
||||
bottom: [
|
||||
...list.map(({ color, name, tree }) =>
|
||||
(isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }),
|
||||
(isNet ? baseline : line)({
|
||||
metric: tree.realized[ema],
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -1207,7 +1353,12 @@ function createGroupedRealizedPnlSections(
|
||||
name: "Total P&L",
|
||||
title: title("Total Realized P&L"),
|
||||
bottom: list.flatMap((cohort) => [
|
||||
line({ metric: cohort.tree.realized.totalRealizedPnl, name: cohort.name, color: cohort.color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: cohort.tree.realized.totalRealizedPnl,
|
||||
name: cohort.name,
|
||||
color: cohort.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
...(ratioMetrics ? ratioMetrics(cohort) : []),
|
||||
]),
|
||||
},
|
||||
@@ -1250,7 +1401,6 @@ function createGroupedRealizedPnlSections(
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1280,9 +1430,6 @@ function createGroupedRealizedPnlSections(
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
priceLine({ ctx, unit: Unit.pctRcap }),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -1294,50 +1441,118 @@ function createGroupedRealizedPnlSections(
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInProfit.bitcoin.sum, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInProfit.sats.sum, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInProfit.dollars.sum, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.bitcoin.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.sats.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.dollars.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Profit Cumulative",
|
||||
title: title("Sent In Profit Cumulative"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInProfit.sats.cumulative, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInProfit.dollars.cumulative, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.bitcoin.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.sats.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInProfit.dollars.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Sent In Loss"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInLoss.bitcoin.sum, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInLoss.sats.sum, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInLoss.dollars.sum, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.bitcoin.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.sats.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.dollars.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Loss Cumulative",
|
||||
title: title("Sent In Loss Cumulative"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name, color, unit: Unit.btc }),
|
||||
line({ metric: tree.realized.sentInLoss.sats.cumulative, name, color, unit: Unit.sats }),
|
||||
line({ metric: tree.realized.sentInLoss.dollars.cumulative, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.bitcoin.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.sats.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sentInLoss.dollars.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name, color }),
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInProfit14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name, color }),
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInLoss14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -1687,7 +1902,6 @@ function createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
||||
name: "Net",
|
||||
unit: Unit.pctOwnPnl,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.pctOwnPnl }),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1785,7 +1999,6 @@ function createNuplChart(ctx, rel, title) {
|
||||
name: "NUPL",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1880,10 +2093,7 @@ function createSingleInvestedCapitalRelativeChart(ctx, rel, title) {
|
||||
return {
|
||||
name: "Relative",
|
||||
title: title("Invested Capital In Profit & Loss %"),
|
||||
bottom: [
|
||||
...createInvestedCapitalRelMetrics(ctx, rel),
|
||||
priceLine({ ctx, unit: Unit.pctRcap }),
|
||||
],
|
||||
bottom: [...createInvestedCapitalRelMetrics(ctx, rel)],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1938,7 +2148,6 @@ function createGroupedNuplChart(ctx, list, title) {
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -2023,20 +2232,12 @@ function createUnrealizedSection({
|
||||
{
|
||||
name: "P&L",
|
||||
title: title("Unrealized P&L"),
|
||||
bottom: [
|
||||
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
||||
...pnl,
|
||||
priceLine({ ctx, unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
bottom: [...createUnrealizedPnlBaseMetrics(ctx, tree), ...pnl],
|
||||
},
|
||||
{
|
||||
name: "Net P&L",
|
||||
title: title("Net Unrealized P&L"),
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
...netPnl,
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
bottom: [createNetUnrealizedPnlBaseMetric(tree), ...netPnl],
|
||||
},
|
||||
investedCapitalFolder,
|
||||
{
|
||||
@@ -2256,14 +2457,16 @@ function createSingleUnrealizedSectionAll(ctx, cohort, title) {
|
||||
pnl: [
|
||||
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
|
||||
],
|
||||
netPnl: [
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [
|
||||
createNuplChart(ctx, tree.relative, title),
|
||||
createPeakRegretChartWithMarketCap(ctx, tree, title),
|
||||
@@ -2287,15 +2490,17 @@ function createSingleUnrealizedSectionFull(ctx, cohort, title) {
|
||||
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
||||
...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
|
||||
],
|
||||
netPnl: [
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [
|
||||
createNuplChart(ctx, tree.relative, title),
|
||||
createPeakRegretChartWithMarketCap(ctx, tree, title),
|
||||
@@ -2315,15 +2520,13 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
pnl: [
|
||||
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
|
||||
],
|
||||
netPnl: [
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
pnl: [...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative)],
|
||||
netPnl: [...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative)],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [
|
||||
createNuplChart(ctx, tree.relative, title),
|
||||
createPeakRegretChartWithMarketCap(ctx, tree, title),
|
||||
@@ -2343,15 +2546,13 @@ function createSingleUnrealizedSectionWithMarketCapOnly(ctx, cohort, title) {
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
pnl: [
|
||||
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
|
||||
],
|
||||
netPnl: [
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
pnl: [...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative)],
|
||||
netPnl: [...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative)],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [createNuplChart(ctx, tree.relative, title)],
|
||||
});
|
||||
}
|
||||
@@ -2368,15 +2569,13 @@ function createSingleUnrealizedSectionMinAge(ctx, cohort, title) {
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
pnl: [
|
||||
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
|
||||
],
|
||||
netPnl: [
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
pnl: [...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative)],
|
||||
netPnl: [...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative)],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [
|
||||
createNuplChart(ctx, tree.relative, title),
|
||||
createPeakRegretChartWithMarketCap(ctx, tree, title),
|
||||
@@ -2603,7 +2802,11 @@ function createSingleUnrealizedSectionWithNupl({ ctx, cohort, title }) {
|
||||
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [
|
||||
createNuplChart(ctx, tree.relative, title),
|
||||
createPeakRegretChartWithMarketCap(ctx, tree, title),
|
||||
@@ -2669,7 +2872,11 @@ function createSingleUnrealizedSectionAgeRange(ctx, cohort, title) {
|
||||
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
],
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
|
||||
investedCapitalFolder: createSingleInvestedCapitalFolderFull(
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
),
|
||||
charts: [createPeakRegretChart(ctx, tree, title)],
|
||||
});
|
||||
}
|
||||
@@ -2895,7 +3102,7 @@ function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
|
||||
const { colors, fromCountPattern, fromBitcoinPatternWithUnit } = ctx;
|
||||
const { colors } = ctx;
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return {
|
||||
@@ -2903,41 +3110,70 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
|
||||
tree: [
|
||||
{
|
||||
name: "Sent",
|
||||
title: title("Sent"),
|
||||
bottom: [
|
||||
...fromCountPattern({
|
||||
pattern: tree.activity.sent.sats,
|
||||
unit: Unit.sats,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
color: color,
|
||||
}),
|
||||
...fromBitcoinPatternWithUnit({
|
||||
pattern: tree.activity.sent.bitcoin,
|
||||
unit: Unit.btc,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
color: color,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: tree.activity.sent.dollars,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
color: color,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.sats,
|
||||
name: "14d EMA",
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.bitcoin,
|
||||
name: "14d EMA",
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.dollars,
|
||||
name: "14d EMA",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.sent.sats.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.sats,
|
||||
name: "14d EMA",
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.bitcoin,
|
||||
name: "14d EMA",
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.dollars,
|
||||
name: "14d EMA",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Sent (Total)"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.sent.sats.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -104,7 +104,6 @@ export function createMomentumSection(ctx, indicators) {
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -64,7 +64,6 @@ export function createReturnsSection(ctx, returns) {
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -121,7 +120,6 @@ export function createReturnsSection(ctx, returns) {
|
||||
color: colors.blue,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
// Short-term (1d, 1w, 1m)
|
||||
|
||||
@@ -88,7 +88,6 @@ export function createVolatilitySection(ctx, { volatility, range }) {
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -113,7 +112,6 @@ export function createVolatilitySection(ctx, { volatility, range }) {
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
/** Mining section - Network security and miner economics */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { entries, includes } from "../utils/array.js";
|
||||
import { colorAt } from "../chart/colors.js";
|
||||
import { line, baseline, dots, dotted } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { fromCountPattern } from "./series.js";
|
||||
import {
|
||||
satsBtcUsd,
|
||||
satsBtcUsdFrom,
|
||||
satsBtcUsdFromFull,
|
||||
revenueBtcSatsUsd,
|
||||
} from "./shared.js";
|
||||
|
||||
/** Major pools to show in Compare section (by current hashrate dominance) */
|
||||
const MAJOR_POOL_IDS = [
|
||||
const MAJOR_POOL_IDS = /** @type {const} */ ([
|
||||
"foundryusa", // ~32% - largest pool
|
||||
"antpool", // ~18% - Bitmain-owned
|
||||
"viabtc", // ~14% - independent
|
||||
@@ -16,14 +21,14 @@ const MAJOR_POOL_IDS = [
|
||||
"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 = [
|
||||
const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([
|
||||
"antpool", // Bitmain-owned, template source
|
||||
"poolin", // shares AntPool templates
|
||||
"btccom", // CloverPool (formerly BTC.com)
|
||||
@@ -34,7 +39,7 @@ const ANTPOOL_AND_FRIENDS_IDS = [
|
||||
"sigmapoolcom", // SigmaPool
|
||||
"rawpool", // shares AntPool templates
|
||||
"luxor", // shares AntPool templates
|
||||
];
|
||||
]);
|
||||
|
||||
/**
|
||||
* Create Mining section
|
||||
@@ -42,143 +47,164 @@ const ANTPOOL_AND_FRIENDS_IDS = [
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMiningSection(ctx) {
|
||||
const {
|
||||
colors,
|
||||
brk,
|
||||
fromSumStatsPattern,
|
||||
fromCoinbasePattern,
|
||||
fromValuePattern,
|
||||
} = ctx;
|
||||
const { colors, brk, distributionBtcSatsUsd } = 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,
|
||||
// Pre-compute pool entries with resolved names
|
||||
const poolData = entries(pools.vecs).map(([id, pool]) => ({
|
||||
id,
|
||||
name: brk.POOL_ID_TO_POOL_NAME[id],
|
||||
pool,
|
||||
}));
|
||||
|
||||
// Filtered pool groups for comparisons
|
||||
const majorPools = poolData.filter((p) => includes(MAJOR_POOL_IDS, p.id));
|
||||
const antpoolFriends = poolData.filter((p) =>
|
||||
includes(ANTPOOL_AND_FRIENDS_IDS, p.id),
|
||||
);
|
||||
|
||||
// Build individual pool trees
|
||||
const poolsTree = poolData.map(({ name, pool }) => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: `Dominance: ${name}`,
|
||||
bottom: [
|
||||
dots({
|
||||
metric: pool._24hDominance,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wDominance,
|
||||
name: "1w",
|
||||
color: colors.time._1w,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1mDominance,
|
||||
name: "1m",
|
||||
color: colors.time._1m,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1yDominance,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool.dominance,
|
||||
name: "All Time",
|
||||
color: colors.time.all,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: `Blocks Mined: ${name}`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pool.blocksMined.sum,
|
||||
name: "sum",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: pool._24hBlocksMined,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wBlocksMined,
|
||||
name: "1w",
|
||||
color: colors.time._1w,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: "1m",
|
||||
color: colors.time._1m,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1yBlocksMined,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `Blocks Mined: ${name} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pool.blocksMined.cumulative,
|
||||
name: "all-time",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rewards",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: `Rewards: ${name}`,
|
||||
bottom: revenueBtcSatsUsd(colors, {
|
||||
coinbase: pool.coinbase,
|
||||
subsidy: pool.subsidy,
|
||||
fee: pool.fee,
|
||||
key: "sum",
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wDominance,
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `Rewards: ${name} (Total)`,
|
||||
bottom: revenueBtcSatsUsd(colors, {
|
||||
coinbase: pool.coinbase,
|
||||
subsidy: pool.subsidy,
|
||||
fee: pool.fee,
|
||||
key: "cumulative",
|
||||
}),
|
||||
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: [
|
||||
...fromCountPattern({
|
||||
pattern: pool.blocksMined,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
line({
|
||||
metric: pool._24hBlocksMined,
|
||||
name: "24h",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wBlocksMined,
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: "1m",
|
||||
color: colors.orange,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1yBlocksMined,
|
||||
name: "1y",
|
||||
color: colors.purple,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rewards",
|
||||
title: `Rewards: ${poolName}`,
|
||||
bottom: [
|
||||
...fromValuePattern({
|
||||
pattern: pool.coinbase,
|
||||
title: "coinbase",
|
||||
color: colors.orange,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.subsidy,
|
||||
title: "subsidy",
|
||||
color: colors.lime,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.fee,
|
||||
title: "fee",
|
||||
color: colors.cyan,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Since Last Block",
|
||||
title: `Since Last Block: ${name}`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pool.blocksSinceBlock,
|
||||
name: "Elapsed",
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: pool.daysSinceBlock,
|
||||
name: "Elapsed",
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
return {
|
||||
name: "Mining",
|
||||
@@ -196,28 +222,28 @@ export function createMiningSection(ctx) {
|
||||
line({
|
||||
metric: blocks.mining.hashRate1wSma,
|
||||
name: "1w SMA",
|
||||
color: colors.red,
|
||||
color: colors.time._1w,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate1mSma,
|
||||
name: "1m SMA",
|
||||
color: colors.orange,
|
||||
color: colors.time._1m,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate2mSma,
|
||||
name: "2m SMA",
|
||||
color: colors.yellow,
|
||||
color: colors.orange,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashRate1ySma,
|
||||
name: "1y SMA",
|
||||
color: colors.lime,
|
||||
color: colors.time._1y,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -265,7 +291,6 @@ export function createMiningSection(ctx) {
|
||||
name: "Change",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
priceLine({ ctx, number: 0, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -293,72 +318,182 @@ export function createMiningSection(ctx) {
|
||||
{
|
||||
name: "Revenue",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Revenue Comparison",
|
||||
bottom: revenueBtcSatsUsd(colors, {
|
||||
coinbase: blocks.rewards.coinbase,
|
||||
subsidy: blocks.rewards.subsidy,
|
||||
fee: transactions.fees.fee,
|
||||
key: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Revenue Comparison (Total)",
|
||||
bottom: revenueBtcSatsUsd(colors, {
|
||||
coinbase: blocks.rewards.coinbase,
|
||||
subsidy: blocks.rewards.subsidy,
|
||||
fee: transactions.fees.fee,
|
||||
key: "cumulative",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Coinbase",
|
||||
title: "Coinbase Rewards",
|
||||
bottom: [
|
||||
...fromCoinbasePattern({ pattern: blocks.rewards.coinbase }),
|
||||
...satsBtcUsd({
|
||||
pattern: blocks.rewards._24hCoinbaseSum,
|
||||
name: "24h sum",
|
||||
color: colors.pink,
|
||||
defaultActive: false,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Coinbase Rewards",
|
||||
bottom: [
|
||||
...satsBtcUsdFromFull({
|
||||
source: blocks.rewards.coinbase,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
}),
|
||||
...satsBtcUsdFrom({
|
||||
source: blocks.rewards.coinbase,
|
||||
key: "sum",
|
||||
name: "sum",
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: blocks.rewards._24hCoinbaseSum,
|
||||
name: "24h",
|
||||
color: colors.time._24h,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: "Coinbase Rewards Distribution",
|
||||
bottom: distributionBtcSatsUsd(blocks.rewards.coinbase),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Coinbase Rewards (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: blocks.rewards.coinbase,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Block Subsidy",
|
||||
bottom: [
|
||||
...satsBtcUsdFromFull({
|
||||
source: blocks.rewards.subsidy,
|
||||
key: "base",
|
||||
name: "sum",
|
||||
}),
|
||||
...satsBtcUsdFrom({
|
||||
source: blocks.rewards.subsidy,
|
||||
key: "sum",
|
||||
name: "sum",
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.subsidyUsd1ySma,
|
||||
name: "1y SMA",
|
||||
color: colors.time._1y,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: "Block Subsidy Distribution",
|
||||
bottom: distributionBtcSatsUsd(blocks.rewards.subsidy),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Block Subsidy (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: blocks.rewards.subsidy,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fees",
|
||||
title: "Transaction Fee Revenue",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Transaction Fee Revenue",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: transactions.fees.fee,
|
||||
key: "sum",
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: "Transaction Fee Revenue Distribution",
|
||||
bottom: distributionBtcSatsUsd(transactions.fees.fee),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Transaction Fee Revenue (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: transactions.fees.fee,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "Revenue Dominance",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.bitcoin,
|
||||
unit: Unit.btc,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.sats,
|
||||
unit: Unit.sats,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.dollars,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
line({
|
||||
metric: blocks.rewards.subsidyDominance,
|
||||
name: "Subsidy",
|
||||
color: colors.lime,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.feeDominance,
|
||||
name: "Dominance",
|
||||
color: colors.purple,
|
||||
name: "Fees",
|
||||
color: colors.cyan,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Unclaimed",
|
||||
title: "Unclaimed Rewards",
|
||||
bottom: fromValuePattern({
|
||||
pattern: blocks.rewards.unclaimedRewards,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Unclaimed Rewards",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: blocks.rewards.unclaimedRewards,
|
||||
key: "sum",
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Unclaimed Rewards (Total)",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: blocks.rewards.unclaimedRewards,
|
||||
key: "cumulative",
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -383,12 +518,6 @@ export function createMiningSection(ctx) {
|
||||
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",
|
||||
@@ -419,12 +548,6 @@ export function createMiningSection(ctx) {
|
||||
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",
|
||||
@@ -439,6 +562,24 @@ export function createMiningSection(ctx) {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Recovery",
|
||||
title: "Recovery",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashPriceRebound,
|
||||
name: "Hash Price",
|
||||
color: colors.emerald,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValueRebound,
|
||||
name: "Hash Value",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -458,7 +599,6 @@ export function createMiningSection(ctx) {
|
||||
line({
|
||||
metric: blocks.halving.daysBeforeNextHalving,
|
||||
name: "Remaining",
|
||||
color: colors.blue,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
@@ -487,41 +627,39 @@ export function createMiningSection(ctx) {
|
||||
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,
|
||||
});
|
||||
title: "Dominance: Major Pools (1m)",
|
||||
bottom: majorPools.map((p, i) =>
|
||||
line({
|
||||
metric: p.pool._1mDominance,
|
||||
name: p.name,
|
||||
color: colorAt(i),
|
||||
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,
|
||||
});
|
||||
bottom: majorPools.map((p, i) =>
|
||||
line({
|
||||
metric: p.pool._1mBlocksMined,
|
||||
name: p.name,
|
||||
color: colorAt(i),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Total Rewards",
|
||||
title: "Total Rewards: Major Pools",
|
||||
bottom: majorPools.flatMap((p, i) =>
|
||||
satsBtcUsdFrom({
|
||||
source: p.pool.coinbase,
|
||||
key: "sum",
|
||||
name: p.name,
|
||||
color: colorAt(i),
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -531,51 +669,45 @@ export function createMiningSection(ctx) {
|
||||
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,
|
||||
});
|
||||
title: "Dominance: AntPool & Friends (1m)",
|
||||
bottom: antpoolFriends.map((p, i) =>
|
||||
line({
|
||||
metric: p.pool._1mDominance,
|
||||
name: p.name,
|
||||
color: colorAt(i),
|
||||
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,
|
||||
});
|
||||
bottom: antpoolFriends.map((p, i) =>
|
||||
line({
|
||||
metric: p.pool._1mBlocksMined,
|
||||
name: p.name,
|
||||
color: colorAt(i),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Total Rewards",
|
||||
title: "Total Rewards: AntPool & Friends",
|
||||
bottom: antpoolFriends.flatMap((p, i) =>
|
||||
satsBtcUsdFrom({
|
||||
source: p.pool.coinbase,
|
||||
key: "sum",
|
||||
name: p.name,
|
||||
color: colorAt(i),
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Individual pools
|
||||
// All pools
|
||||
{
|
||||
name: "Individual",
|
||||
name: "All Pools",
|
||||
tree: poolsTree,
|
||||
},
|
||||
],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,62 +50,18 @@ export function price({
|
||||
* @param {StatsPattern<any> | BaseStatsPattern<any> | FullStatsPattern<any> | AnyStatsPattern} pattern
|
||||
* @param {Unit} unit
|
||||
* @param {string} title
|
||||
* @param {{ type?: "Dots" }} [options]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function percentileSeries(colors, pattern, unit, title, { type } = {}) {
|
||||
function percentileSeries(colors, pattern, unit, title) {
|
||||
const { stat } = colors;
|
||||
const base = { unit, defaultActive: false };
|
||||
return [
|
||||
{
|
||||
type,
|
||||
metric: pattern.max,
|
||||
title: `${title} max`.trim(),
|
||||
color: stat.max,
|
||||
...base,
|
||||
},
|
||||
{
|
||||
type,
|
||||
metric: pattern.min,
|
||||
title: `${title} min`.trim(),
|
||||
color: stat.min,
|
||||
...base,
|
||||
},
|
||||
{
|
||||
type,
|
||||
metric: pattern.median,
|
||||
title: `${title} median`.trim(),
|
||||
color: stat.median,
|
||||
...base,
|
||||
},
|
||||
{
|
||||
type,
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`.trim(),
|
||||
color: stat.pct75,
|
||||
...base,
|
||||
},
|
||||
{
|
||||
type,
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`.trim(),
|
||||
color: stat.pct25,
|
||||
...base,
|
||||
},
|
||||
{
|
||||
type,
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`.trim(),
|
||||
color: stat.pct90,
|
||||
...base,
|
||||
},
|
||||
{
|
||||
type,
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`.trim(),
|
||||
color: stat.pct10,
|
||||
...base,
|
||||
},
|
||||
dots({ metric: pattern.max, name: `${title} max`.trim(), color: stat.max, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.min, name: `${title} min`.trim(), color: stat.min, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.median, name: `${title} median`.trim(), color: stat.median, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct75, name: `${title} pct75`.trim(), color: stat.pct75, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct25, name: `${title} pct25`.trim(), color: stat.pct25, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct90, name: `${title} pct90`.trim(), color: stat.pct90, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct10, name: `${title} pct10`.trim(), color: stat.pct10, unit, defaultActive: false }),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -305,36 +261,6 @@ export function histogram({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from patterns with sum + cumulative + percentiles (NO base)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {AnyStatsPattern} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromSumStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.average, title: `${title} avg`.trim(), unit },
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: title || "sum",
|
||||
color: stat.sum,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: title || "cumulative",
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a BaseStatsPattern (base + avg + percentiles, NO sum)
|
||||
* @param {Colors} colors
|
||||
@@ -352,59 +278,23 @@ export function fromBaseStatsPattern(
|
||||
) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title || "base", color: baseColor, unit },
|
||||
{
|
||||
dots({ metric: pattern.base, name: title || "base", color: baseColor, unit }),
|
||||
dots({
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`.trim(),
|
||||
name: `${title} avg`.trim(),
|
||||
color: stat.avg,
|
||||
unit,
|
||||
defaultActive: avgActive,
|
||||
},
|
||||
}),
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a FullStatsPattern (base + sum + cumulative + avg + percentiles)
|
||||
* Create series from any pattern with avg + percentiles (works with StatsPattern, SumStatsPattern, etc.)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {FullStatsPattern<any>} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromFullStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title || "base", unit },
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: title || "sum",
|
||||
color: stat.sum,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: title || "cumulative",
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
{
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`.trim(),
|
||||
color: stat.avg,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a StatsPattern (avg + percentiles, NO base)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {StatsPattern<any>} args.pattern
|
||||
* @param {StatsPattern<any> | BaseStatsPattern<any> | FullStatsPattern<any> | AnyStatsPattern} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
@@ -417,178 +307,7 @@ export function fromStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
title: `${title} avg`.trim(),
|
||||
unit,
|
||||
},
|
||||
...percentileSeries(colors, pattern, unit, title, { type: "Dots" }),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from AnyFullStatsPattern (base + sum + cumulative + avg + percentiles)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {AnyFullStatsPattern} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromAnyFullStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
...fromBaseStatsPattern(colors, { pattern, unit, title }),
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: title || "sum",
|
||||
color: stat.sum,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: title || "cumulative",
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each with stats + sum + cumulative)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {CoinbasePattern} args.pattern
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromCoinbasePattern(colors, { pattern, title = "" }) {
|
||||
return [
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.bitcoin,
|
||||
unit: Unit.btc,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
title,
|
||||
}),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.sats,
|
||||
unit: Unit.sats,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
title,
|
||||
}),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.dollars,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a ValuePattern ({ sats, bitcoin, dollars } each as CountPattern with sum + cumulative)
|
||||
* @param {Object} args
|
||||
* @param {ValuePattern} args.pattern
|
||||
* @param {string} [args.title]
|
||||
* @param {Color} [args.color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromValuePattern({ pattern, title = "", color }) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.bitcoin.sum,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
},
|
||||
{
|
||||
metric: pattern.bitcoin.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: Unit.btcCumulative,
|
||||
},
|
||||
{
|
||||
metric: pattern.sats.sum,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
},
|
||||
{
|
||||
metric: pattern.sats.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: Unit.satsCumulative,
|
||||
},
|
||||
{
|
||||
metric: pattern.dollars.sum,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
},
|
||||
{
|
||||
metric: pattern.dollars.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: Unit.usdCumulative,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sum/cumulative series from a BitcoinPattern ({ sum, cumulative }) with explicit unit and colors
|
||||
* @param {Object} args
|
||||
* @param {{ sum: AnyMetricPattern, cumulative: AnyMetricPattern }} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBitcoinPatternWithUnit({
|
||||
pattern,
|
||||
unit,
|
||||
cumulativeUnit,
|
||||
title = "",
|
||||
color,
|
||||
defaultActive,
|
||||
}) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit,
|
||||
defaultActive,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sum/cumulative series from a CountPattern with explicit unit and colors
|
||||
* @param {Object} args
|
||||
* @param {CountPattern<any>} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @param {Color} [args.color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromCountPattern({ pattern, unit, cumulativeUnit, title = "", color }) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -622,3 +341,207 @@ export function fromSupplyPattern({ pattern, title, color }) {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Chart-generating helpers (return PartialOptionsTree for folder structures)
|
||||
// ============================================================================
|
||||
// These split patterns into separate Sum/Distribution/Cumulative charts
|
||||
|
||||
/**
|
||||
* Create distribution series (avg + percentiles)
|
||||
* @param {Colors} colors
|
||||
* @param {StatsPattern<any> | BaseStatsPattern<any> | FullStatsPattern<any> | AnyStatsPattern} pattern
|
||||
* @param {Unit} unit
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function distributionSeries(colors, pattern, unit) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
dots({ metric: pattern.average, name: "avg", color: stat.avg, unit }),
|
||||
dots({ metric: pattern.median, name: "median", color: stat.median, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.max, name: "max", color: stat.max, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.min, name: "min", color: stat.min, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct75, name: "pct75", color: stat.pct75, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct25, name: "pct25", color: stat.pct25, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct90, name: "pct90", color: stat.pct90, unit, defaultActive: false }),
|
||||
dots({ metric: pattern.pct10, name: "pct10", color: stat.pct10, unit, defaultActive: false }),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create btc/sats/usd series from metrics
|
||||
* @param {Object} args
|
||||
* @param {{ bitcoin: AnyMetricPattern, sats: AnyMetricPattern, dollars: AnyMetricPattern }} args.metrics
|
||||
* @param {string} args.name
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function btcSatsUsdSeries({ metrics, name, color, defaultActive }) {
|
||||
return [
|
||||
{ metric: metrics.bitcoin, title: name, color, unit: Unit.btc, defaultActive },
|
||||
{ metric: metrics.sats, title: name, color, unit: Unit.sats, defaultActive },
|
||||
{ metric: metrics.dollars, title: name, color, unit: Unit.usd, defaultActive },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pattern with base + sum + distribution + cumulative into 3 charts
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {FullStatsPattern<any>} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromFull(colors, { pattern, title, unit }) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: [
|
||||
{ metric: pattern.base, title: "sum", unit },
|
||||
{ metric: pattern.sum, title: "sum", unit },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: `${title} Distribution`,
|
||||
bottom: distributionSeries(colors, pattern, unit),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: [{ metric: pattern.cumulative, title: "all-time", unit }],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pattern with sum + distribution + cumulative into 3 charts (no base)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {AnyStatsPattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromSum(colors, { pattern, title, unit }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: [{ metric: pattern.sum, title: "sum", color: stat.sum, unit }],
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: `${title} Distribution`,
|
||||
bottom: distributionSeries(colors, pattern, unit),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: [{ metric: pattern.cumulative, title: "all-time", unit }],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pattern with sum + cumulative into 2 charts
|
||||
* @param {Object} args
|
||||
* @param {CountPattern<any>} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromCount({ pattern, title, unit, color }) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: [{ metric: pattern.sum, title: "sum", color, unit }],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: [{ metric: pattern.cumulative, title: "all-time", color, unit }],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split value pattern (btc/sats/usd with sum + cumulative) into 2 charts
|
||||
* @param {Object} args
|
||||
* @param {ValuePattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromValue({ pattern, title, color }) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: btcSatsUsdSeries({
|
||||
metrics: { bitcoin: pattern.bitcoin.sum, sats: pattern.sats.sum, dollars: pattern.dollars.sum },
|
||||
name: "sum",
|
||||
color,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: btcSatsUsdSeries({
|
||||
metrics: { bitcoin: pattern.bitcoin.cumulative, sats: pattern.sats.cumulative, dollars: pattern.dollars.cumulative },
|
||||
name: "all-time",
|
||||
color,
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split btc/sats/usd pattern with full stats into 3 charts
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {CoinbasePattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromValueFull(colors, { pattern, title }) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: [
|
||||
...btcSatsUsdSeries({
|
||||
metrics: { bitcoin: pattern.bitcoin.base, sats: pattern.sats.base, dollars: pattern.dollars.base },
|
||||
name: "sum",
|
||||
}),
|
||||
...btcSatsUsdSeries({
|
||||
metrics: { bitcoin: pattern.bitcoin.sum, sats: pattern.sats.sum, dollars: pattern.dollars.sum },
|
||||
name: "sum",
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: `${title} Distribution`,
|
||||
bottom: [
|
||||
...distributionSeries(colors, pattern.bitcoin, Unit.btc),
|
||||
...distributionSeries(colors, pattern.sats, Unit.sats),
|
||||
...distributionSeries(colors, pattern.dollars, Unit.usd),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: btcSatsUsdSeries({
|
||||
metrics: { bitcoin: pattern.bitcoin.cumulative, sats: pattern.sats.cumulative, dollars: pattern.dollars.cumulative },
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -23,21 +23,65 @@ export const formatCohortTitle = (cohortTitle) =>
|
||||
*/
|
||||
export function satsBtcUsd({ pattern, name, color, defaultActive }) {
|
||||
return [
|
||||
line({
|
||||
metric: pattern.bitcoin,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
defaultActive,
|
||||
}),
|
||||
line({ metric: pattern.bitcoin, name, color, unit: Unit.btc, defaultActive }),
|
||||
line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
|
||||
line({
|
||||
metric: pattern.dollars,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive,
|
||||
}),
|
||||
line({ metric: pattern.dollars, name, color, unit: Unit.usd, defaultActive }),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sats/btc/usd series from any value pattern using sum or cumulative key
|
||||
* @param {Object} args
|
||||
* @param {AnyValuePatternType} args.source
|
||||
* @param {'sum' | 'cumulative'} args.key
|
||||
* @param {string} args.name
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @returns {FetchedLineSeriesBlueprint[]}
|
||||
*/
|
||||
export function satsBtcUsdFrom({ source, key, name, color, defaultActive }) {
|
||||
return satsBtcUsd({
|
||||
pattern: { bitcoin: source.bitcoin[key], sats: source.sats[key], dollars: source.dollars[key] },
|
||||
name,
|
||||
color,
|
||||
defaultActive,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sats/btc/usd series from a full value pattern using base or average key
|
||||
* @param {Object} args
|
||||
* @param {FullValuePattern} args.source
|
||||
* @param {'base' | 'average'} args.key
|
||||
* @param {string} args.name
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @returns {FetchedLineSeriesBlueprint[]}
|
||||
*/
|
||||
export function satsBtcUsdFromFull({ source, key, name, color, defaultActive }) {
|
||||
return satsBtcUsd({
|
||||
pattern: { bitcoin: source.bitcoin[key], sats: source.sats[key], dollars: source.dollars[key] },
|
||||
name,
|
||||
color,
|
||||
defaultActive,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create coinbase/subsidy/fee series from separate sources
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {AnyValuePatternType} args.coinbase
|
||||
* @param {AnyValuePatternType} args.subsidy
|
||||
* @param {AnyValuePatternType} args.fee
|
||||
* @param {'sum' | 'cumulative'} args.key
|
||||
* @returns {FetchedLineSeriesBlueprint[]}
|
||||
*/
|
||||
export function revenueBtcSatsUsd(colors, { coinbase, subsidy, fee, key }) {
|
||||
return [
|
||||
...satsBtcUsdFrom({ source: coinbase, key, name: "Coinbase", color: colors.orange }),
|
||||
...satsBtcUsdFrom({ source: subsidy, key, name: "Subsidy", color: colors.lime }),
|
||||
...satsBtcUsdFrom({ source: fee, key, name: "Fees", color: colors.cyan }),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,14 @@
|
||||
* @typedef {Brk.PriceRatioPattern} ActivePriceRatioPattern
|
||||
* AnyRatioPattern: full ratio patterns (with or without price) - has ratio, percentiles, z-scores
|
||||
* @typedef {Brk.RatioPattern | Brk.PriceRatioPattern} AnyRatioPattern
|
||||
* ValuePattern: patterns with {bitcoin.sum, bitcoin.cumulative, sats.sum, sats.cumulative, dollars.sum, dollars.cumulative}
|
||||
* @typedef {Brk.BitcoinDollarsSatsPattern6 | Brk.BitcoinDollarsSatsPattern3 | Brk.BitcoinDollarsSatsPattern2} ValuePattern
|
||||
* ValuePattern: patterns with minimal stats (sum, cumulative only) for bitcoin/sats/dollars
|
||||
* @typedef {Brk.BitcoinDollarsSatsPattern6 | Brk.BitcoinDollarsSatsPattern3} ValuePattern
|
||||
* FullValuePattern: patterns with full stats (base, sum, cumulative, average, percentiles) for bitcoin/sats/dollars
|
||||
* @typedef {Brk.BitcoinDollarsSatsPattern2} FullValuePattern
|
||||
* SumValuePattern: patterns with sum stats (sum, cumulative, average, percentiles - no base) for bitcoin/sats/dollars
|
||||
* @typedef {{bitcoin: SumStatsPattern<any>, sats: SumStatsPattern<any>, dollars: SumStatsPattern<any>}} SumValuePattern
|
||||
* AnyValuePatternType: union of all value pattern types
|
||||
* @typedef {ValuePattern | FullValuePattern} AnyValuePatternType
|
||||
* @typedef {Brk.AnyMetricPattern} AnyMetricPattern
|
||||
* @typedef {Brk.DollarsSatsPattern} ActivePricePattern
|
||||
* @typedef {Brk.AnyMetricEndpointBuilder} AnyMetricEndpoint
|
||||
|
||||
@@ -18,3 +18,20 @@ export function range(start, end) {
|
||||
export function randomFromArray(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed Object.entries that preserves key types
|
||||
* @template {Record<string, any>} T
|
||||
* @param {T} obj
|
||||
* @returns {[keyof T & string, T[keyof T & string]][]}
|
||||
*/
|
||||
export const entries = (obj) => /** @type {[keyof T & string, T[keyof T & string]][]} */ (Object.entries(obj));
|
||||
|
||||
/**
|
||||
* Type-safe includes that narrows the value type
|
||||
* @template T
|
||||
* @param {readonly T[]} arr
|
||||
* @param {unknown} value
|
||||
* @returns {value is T}
|
||||
*/
|
||||
export const includes = (arr, value) => arr.includes(/** @type {T} */ (value));
|
||||
|
||||
@@ -9,11 +9,6 @@ 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" },
|
||||
cagr: { id: "cagr", name: "CAGR (%/year)" },
|
||||
@@ -36,15 +31,12 @@ export const Unit = /** @type {const} */ ({
|
||||
|
||||
// Counts
|
||||
count: { id: "count", name: "Count" },
|
||||
countCumulative: { id: "count-total", name: "Count (Total)" },
|
||||
blocks: { id: "blocks", name: "Blocks" },
|
||||
|
||||
// Size
|
||||
bytes: { id: "bytes", name: "Bytes" },
|
||||
bytesCumulative: { id: "bytes-total", name: "Bytes (Total)" },
|
||||
vb: { id: "vb", name: "Virtual Bytes" },
|
||||
wu: { id: "wu", name: "Weight Units" },
|
||||
wuCumulative: { id: "wu-total", name: "Weight Units (Total)" },
|
||||
|
||||
// Mining
|
||||
hashRate: { id: "hashrate", name: "Hash Rate" },
|
||||
|
||||
Reference in New Issue
Block a user