global: snapshot

This commit is contained in:
nym21
2026-02-01 22:38:01 +01:00
parent f03bbd9a92
commit f7d7c5704a
47 changed files with 2924 additions and 837 deletions
Generated
-6
View File
@@ -2436,8 +2436,6 @@ dependencies = [
[[package]]
name = "rawdb"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32158f67cfcd5359af3294b26cc4acbd8e412106ab1d6e470038b5284df362e7"
dependencies = [
"libc",
"log",
@@ -3256,8 +3254,6 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23"
[[package]]
name = "vecdb"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c9f596e212ac69076b58735d340dd944f83531b5a83061020d4a922b73016d"
dependencies = [
"ctrlc",
"log",
@@ -3277,8 +3273,6 @@ dependencies = [
[[package]]
name = "vecdb_derive"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63c4ecf88e970a6275bad540fc68e022ed86987d817ef6711a7c57889aa2dfdf"
dependencies = [
"quote",
"syn",
+2 -2
View File
@@ -83,8 +83,8 @@ tokio = { version = "1.49.0", features = ["rt-multi-thread"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }
tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
tower-layer = "0.3"
vecdb = { version = "0.6.4", features = ["derive", "serde_json", "pco", "schemars"] }
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
# vecdb = { version = "0.6.4", features = ["derive", "serde_json", "pco", "schemars"] }
vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
[workspace.metadata.release]
shared-version = true
@@ -87,6 +87,13 @@ pub fn detect_structural_patterns(
})
.collect();
// Deduplicate patterns by name - different signatures can map to the same name
// when their normalized forms match but they can't be unified as generics
{
let mut seen_names: BTreeSet<String> = BTreeSet::new();
patterns.retain(|p| seen_names.insert(p.name.clone()));
}
patterns.extend(generic_patterns);
// Build pattern lookup for mode analysis (patterns appearing 2+ times)
File diff suppressed because it is too large Load Diff
@@ -24,7 +24,7 @@ use super::super::cache::AddressLookup;
/// parallel execution with UTXO cohort processing (which mutates chain_state).
///
/// `price_range_max` is used to compute the peak price during each UTXO's holding period
/// for accurate ATH regret calculation.
/// for accurate peak regret calculation.
#[allow(clippy::too_many_arguments)]
pub fn process_sent(
sent_data: HeightToAddressTypeToVec<(TypeIndex, Sats)>,
@@ -50,7 +50,7 @@ pub fn process_sent(
let blocks_old = current_height.to_usize() - receive_height.to_usize();
let age = Age::new(current_timestamp, prev_timestamp, blocks_old);
// Compute peak price during holding period for ATH regret
// Compute peak price during holding period for peak regret
// This is the max HIGH price between receive and send heights
let peak_price: Option<CentsUnsigned> =
price_range_max.map(|t| t.max_between(receive_height, current_height));
@@ -1,19 +1,21 @@
use std::{cmp::Reverse, collections::BinaryHeap, path::Path};
use brk_cohort::{
ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge, ByMinAge,
BySpendableType, ByTerm, ByYear, Filter, Filtered, StateLevel, UTXOGroups,
AGE_BOUNDARIES, ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount,
ByMaxAge, ByMinAge, BySpendableType, ByTerm, ByYear, Filter, Filtered, StateLevel, UTXOGroups,
};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{CentsUnsigned, DateIndex, Dollars, Height, Sats, StoredF32, Version};
use brk_types::{
CentsUnsigned, DateIndex, Dollars, Height, ONE_HOUR_IN_SEC, Sats, StoredF32, Timestamp, Version,
};
use derive_more::{Deref, DerefMut};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, GenericStoredVec, IterableVec};
use vecdb::{AnyStoredVec, Database, Exit, GenericStoredVec, IterableVec, VecIndex};
use crate::{
ComputeIndexes,
distribution::DynCohortVecs,
distribution::{DynCohortVecs, compute::PriceRangeMax, state::BlockState},
indexes,
internal::{PERCENTILES, PERCENTILES_LEN, compute_spot_percentile_rank},
price,
@@ -357,13 +359,13 @@ impl UTXOCohorts {
})
.collect();
// Compute percentiles for each aggregate filter
for aggregate in self.0.iter_aggregate_mut() {
// Compute percentiles for each aggregate filter in parallel
self.0.par_iter_aggregate_mut().try_for_each(|aggregate| {
let filter = aggregate.filter().clone();
// Get cost_basis, skip if not configured
let Some(cost_basis) = aggregate.metrics.cost_basis.as_mut() else {
continue;
return Ok(());
};
// Collect relevant cohort data for this aggregate and sum totals
@@ -397,7 +399,7 @@ impl UTXOCohorts {
.dateindex
.truncate_push(dateindex, StoredF32::NAN)?;
}
continue;
return Ok(());
}
// K-way merge using min-heap: O(n log k) where k = number of cohorts
@@ -507,9 +509,9 @@ impl UTXOCohorts {
let rank = compute_spot_percentile_rank(&usd_result, spot);
spot_pct.dateindex.truncate_push(dateindex, rank)?;
}
}
Ok(())
Ok(())
})
}
/// Validate computed versions for all cohorts (separate and aggregate).
@@ -525,4 +527,112 @@ impl UTXOCohorts {
Ok(())
}
/// Compute and push peak regret for all age_range cohorts.
///
/// Uses split points to efficiently compute regret per cohort.
/// All 21 cohorts are computed in parallel, then pushed sequentially.
/// Called once per day when dateindex changes.
pub fn compute_and_push_peak_regret(
&mut self,
chain_state: &[BlockState],
current_height: Height,
current_timestamp: Timestamp,
spot: CentsUnsigned,
price_range_max: &PriceRangeMax,
dateindex: DateIndex,
) -> Result<()> {
const FIRST_PRICE_HEIGHT: usize = 68_195;
let start_height = FIRST_PRICE_HEIGHT;
let end_height = current_height.to_usize() + 1;
// Early return: push zeros if no price data yet
if end_height <= start_height {
for cohort in self.0.age_range.iter_mut() {
if let Some(unrealized) = cohort.metrics.unrealized.as_mut()
&& let Some(peak_regret) = unrealized.peak_regret.as_mut()
{
peak_regret
.dateindex
.truncate_push(dateindex, Dollars::ZERO)?;
}
}
return Ok(());
}
let spot_u128 = spot.as_u128();
let current_ts = *current_timestamp;
// Compute split points: splits[k] = first index where age < AGE_BOUNDARIES[k]
let splits: [usize; 20] = std::array::from_fn(|k| {
let boundary_seconds = (AGE_BOUNDARIES[k] as u32) * ONE_HOUR_IN_SEC;
let threshold_ts = current_ts.saturating_sub(boundary_seconds);
chain_state[..end_height].partition_point(|b| *b.timestamp <= threshold_ts)
});
// Build ranges for all 21 cohorts
let ranges: [(usize, usize); 21] = std::array::from_fn(|i| {
if i == 0 {
(splits[0], end_height)
} else if i < 20 {
(splits[i], splits[i - 1])
} else {
(start_height, splits[19])
}
});
// Compute regret for all cohorts in parallel
let regrets: [Dollars; 21] = ranges
.into_par_iter()
.map(|(range_start, range_end)| {
let effective_start = range_start.max(start_height);
if effective_start >= range_end {
return Dollars::ZERO;
}
let mut regret: u128 = 0;
for h in effective_start..range_end {
let block = &chain_state[h];
let supply = block.supply.value;
if supply.is_zero() {
continue;
}
let cost_basis = match block.price {
Some(p) => p,
None => continue,
};
let receive_height = Height::from(h);
let peak = price_range_max.max_between(receive_height, current_height);
let peak_u128 = peak.as_u128();
let cost_u128 = cost_basis.as_u128();
let supply_u128 = supply.as_u128();
regret += if spot_u128 >= cost_u128 {
(peak_u128 - spot_u128) * supply_u128
} else {
(peak_u128 - cost_u128) * supply_u128
};
}
CentsUnsigned::new((regret / Sats::ONE_BTC_U128) as u64).to_dollars()
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
// Push results to cohorts
for (cohort, regret) in self.0.age_range.iter_mut().zip(regrets) {
if let Some(unrealized) = cohort.metrics.unrealized.as_mut()
&& let Some(peak_regret) = unrealized.peak_regret.as_mut()
{
peak_regret.dateindex.truncate_push(dateindex, regret)?;
}
}
Ok(())
}
}
@@ -19,7 +19,7 @@ impl UTXOCohorts {
/// We need to update the cohort states based on when that UTXO was created.
///
/// `price_range_max` is used to compute the peak price during each UTXO's holding period
/// for accurate ATH regret calculation.
/// for accurate peak regret calculation.
pub fn send(
&mut self,
height_to_sent: FxHashMap<Height, Transacted>,
@@ -45,7 +45,7 @@ impl UTXOCohorts {
let blocks_old = chain_len - 1 - receive_height.to_usize();
let age = Age::new(last_timestamp, block_state.timestamp, blocks_old);
// Compute peak price during holding period for ATH regret
// Compute peak price during holding period for peak regret
// This is the max HIGH price between receive and send heights
let peak_price: Option<CentsUnsigned> =
price_range_max.map(|t| t.max_between(receive_height, send_height));
@@ -11,10 +11,10 @@ impl UTXOCohorts {
/// UTXOs age with each block. When they cross hour boundaries,
/// they move between age-based cohorts (e.g., from "0-1h" to "1h-1d").
///
/// Complexity: O(k * (log n + m)) where:
/// Complexity: O(k * log n) where:
/// - k = 20 boundaries to check
/// - n = total blocks in chain_state
/// - m = blocks crossing each boundary (typically 0-2 per boundary per block)
/// - Linear scan for end_idx is faster than binary search since typically 0-2 blocks cross each boundary
pub fn tick_tock_next_block(&mut self, chain_state: &[BlockState], timestamp: Timestamp) {
if chain_state.is_empty() {
return;
@@ -49,9 +49,12 @@ impl UTXOCohorts {
continue;
}
// Binary search to find blocks in the timestamp range (lower, upper]
// Binary search to find start, then linear scan for end (typically 0-2 blocks)
let start_idx = chain_state.partition_point(|b| *b.timestamp <= lower_timestamp);
let end_idx = chain_state.partition_point(|b| *b.timestamp <= upper_timestamp);
let end_idx = chain_state[start_idx..]
.iter()
.position(|b| *b.timestamp > upper_timestamp)
.map_or(chain_state.len(), |pos| start_idx + pos);
// Move supply from younger cohort to older cohort
for block_state in &chain_state[start_idx..end_idx] {
@@ -6,7 +6,7 @@ use brk_indexer::Indexer;
use brk_types::{CentsUnsigned, DateIndex, Dollars, Height, OutputType, Sats, TxIndex, TypeIndex};
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use tracing::info;
use tracing::{debug, info};
use vecdb::{Exit, IterableVec, TypedVecIterator, VecIndex};
use crate::{
@@ -51,7 +51,9 @@ pub fn process_blocks(
exit: &Exit,
) -> Result<()> {
// Create computation context with pre-computed vectors for thread-safe access
debug!("creating ComputeContext");
let ctx = ComputeContext::new(starting_height, last_height, blocks, price);
debug!("ComputeContext created");
if ctx.starting_height > ctx.last_height {
return Ok(());
@@ -99,9 +101,12 @@ pub fn process_blocks(
let mut height_to_price_iter = height_to_price.map(|v| v.into_iter());
let mut dateindex_to_price_iter = dateindex_to_price.map(|v| v.into_iter());
debug!("creating VecsReaders");
let mut vr = VecsReaders::new(&vecs.any_address_indexes, &vecs.addresses_data);
debug!("VecsReaders created");
// Build txindex -> height lookup map for efficient prev_height computation
debug!("building txindex_to_height RangeMap");
let mut txindex_to_height: RangeMap<TxIndex, Height> = {
let mut map = RangeMap::with_capacity(last_height.to_usize() + 1);
for first_txindex in indexer.vecs.transactions.first_txindex.into_iter() {
@@ -109,6 +114,7 @@ pub fn process_blocks(
}
map
};
debug!("txindex_to_height RangeMap built");
// Create reusable iterators for sequential txout/txin reads (16KB buffered)
let mut txout_iters = TxOutIterators::new(indexer);
@@ -125,6 +131,7 @@ pub fn process_blocks(
let mut first_p2wsh_iter = indexer.vecs.addresses.first_p2wshaddressindex.into_iter();
// Track running totals - recover from previous height if resuming
debug!("recovering addr_counts from height {}", starting_height);
let (mut addr_counts, mut empty_addr_counts) = if starting_height > Height::ZERO {
let addr_counts =
AddressTypeToAddressCount::from((&vecs.addr_count.by_addresstype, starting_height));
@@ -139,11 +146,14 @@ pub fn process_blocks(
AddressTypeToAddressCount::default(),
)
};
debug!("addr_counts recovered");
// Track activity counts - reset each block
let mut activity_counts = AddressTypeToActivityCounts::default();
debug!("creating AddressCache");
let mut cache = AddressCache::new();
debug!("AddressCache created, entering main loop");
// Main block iteration
for height in starting_height.to_usize()..=last_height.to_usize() {
@@ -390,6 +400,21 @@ pub fn process_blocks(
.unwrap_or(Dollars::NAN);
vecs.utxo_cohorts
.truncate_push_aggregate_percentiles(dateindex, spot)?;
// Compute unrealized peak regret by age range (once per day)
// Aggregate cohorts (all, term, etc.) get values via compute_from_stateful
if let Some(spot_cents) = block_price
&& let Some(price_range_max) = ctx.price_range_max.as_ref()
{
vecs.utxo_cohorts.compute_and_push_peak_regret(
chain_state,
height,
timestamp,
spot_cents,
price_range_max,
dateindex,
)?;
}
}
// Periodic checkpoint flush
@@ -109,7 +109,7 @@ pub struct ComputeContext {
pub height_to_price: Option<Vec<CentsUnsigned>>,
/// Sparse table for O(1) range max queries on high prices.
/// Used for computing max price during UTXO holding periods (ATH regret).
/// Used for computing max price during UTXO holding periods (peak regret).
pub price_range_max: Option<PriceRangeMax>,
}
@@ -129,7 +129,7 @@ impl ComputeContext {
.map(|v| v.into_iter().map(|c| *c).collect());
// Build sparse table for O(1) range max queries on HIGH prices
// Used for computing peak price during UTXO holding periods (ATH regret)
// Used for computing peak price during UTXO holding periods (peak regret)
let price_range_max = price
.map(|p| &p.cents.split.height.high)
.map(|v| v.into_iter().map(|c| *c).collect::<Vec<_>>())
@@ -71,20 +71,24 @@ pub fn recover_state(
}
// Import UTXO cohort states - all must succeed
debug!("importing UTXO cohort states at height {}", consistent_height);
if !utxo_cohorts.import_separate_states(consistent_height) {
warn!("UTXO cohort state import failed at height {}", consistent_height);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
}
debug!("UTXO cohort states imported");
// Import address cohort states - all must succeed
debug!("importing address cohort states at height {}", consistent_height);
if !address_cohorts.import_separate_states(consistent_height) {
warn!("Address cohort state import failed at height {}", consistent_height);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
}
debug!("address cohort states imported");
Ok(RecoveredState {
starting_height: consistent_height,
@@ -66,9 +66,9 @@ pub fn write(
let stamp = Stamp::from(height);
// Prepare chain_state before parallel write
vecs.chain_state.truncate_if_needed(Height::ZERO)?;
vecs.supply_state.truncate_if_needed(Height::ZERO)?;
for block_state in chain_state {
vecs.chain_state.push(block_state.supply.clone());
vecs.supply_state.push(block_state.supply.clone());
}
vecs.any_address_indexes
@@ -78,7 +78,7 @@ pub fn write(
.chain(vecs.empty_addr_count.par_iter_height_mut())
.chain(vecs.address_activity.par_iter_height_mut())
.chain(rayon::iter::once(
&mut vecs.chain_state as &mut dyn AnyStoredVec,
&mut vecs.supply_state as &mut dyn AnyStoredVec,
))
.chain(vecs.utxo_cohorts.par_iter_vecs_mut())
.chain(vecs.address_cohorts.par_iter_vecs_mut())
@@ -6,7 +6,7 @@ use vecdb::{AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVe
use crate::{
ComputeIndexes, indexes,
internal::{ComputedFromHeightSumCum, LazyComputedValueFromHeightSumCum},
internal::{ComputedFromHeightSumCum, LazyComputedValueFromHeightSumCum, ValueFromDateLast},
};
use super::ImportConfig;
@@ -17,6 +17,9 @@ pub struct ActivityMetrics {
/// Total satoshis sent at each height + derived indexes
pub sent: LazyComputedValueFromHeightSumCum,
/// 14-day EMA of sent supply (sats, btc, usd)
pub sent_14d_ema: ValueFromDateLast,
/// Satoshi-blocks destroyed (supply * blocks_old when spent)
pub satblocks_destroyed: EagerVec<PcoVec<Height, Sats>>,
@@ -42,6 +45,14 @@ impl ActivityMetrics {
cfg.price,
)?,
sent_14d_ema: ValueFromDateLast::forced_import(
cfg.db,
&cfg.name("sent_14d_ema"),
cfg.version,
cfg.compute_dollars(),
cfg.indexes,
)?,
satblocks_destroyed: EagerVec::forced_import(
cfg.db,
&cfg.name("satblocks_destroyed"),
@@ -155,6 +166,15 @@ impl ActivityMetrics {
) -> Result<()> {
self.sent.compute_rest(indexes, starting_indexes, exit)?;
// 14-day EMA of sent (sats and dollars)
self.sent_14d_ema.compute_ema(
starting_indexes.dateindex,
&self.sent.sats.dateindex.sum.0,
self.sent.dollars.as_ref().map(|d| &d.dateindex.sum.0),
14,
exit,
)?;
self.coinblocks_destroyed
.compute_all(indexes, starting_indexes, exit, |v| {
v.compute_transform(
@@ -1,4 +1,4 @@
use brk_cohort::{CohortContext, Filter};
use brk_cohort::{CohortContext, Filter, TimeFilter};
use brk_types::Version;
use vecdb::Database;
@@ -56,4 +56,24 @@ impl<'a> ImportConfig<'a> {
format!("{}_{suffix}", self.full_name)
}
}
/// Whether this cohort needs peak_regret metric.
/// True for UTXO cohorts with age-based filters (all, term, time).
/// age_range cohorts compute directly, others aggregate from age_range.
pub fn compute_peak_regret(&self) -> bool {
matches!(self.context, CohortContext::Utxo)
&& matches!(
self.filter,
Filter::All | Filter::Term(_) | Filter::Time(_)
)
}
/// Whether this is an age_range cohort (UTXO context with Time::Range filter).
/// These cohorts have peak_regret computed directly from chain_state.
pub fn is_age_range(&self) -> bool {
matches!(
(&self.context, &self.filter),
(CohortContext::Utxo, Filter::Time(TimeFilter::Range(_)))
)
}
}
@@ -17,9 +17,10 @@ use crate::{
internal::{
CentsUnsignedToDollars, ComputedFromDateLast, ComputedFromDateRatio,
ComputedFromHeightLast, ComputedFromHeightSum, ComputedFromHeightSumCum, DollarsMinus,
DollarsPlus, LazyBinaryFromHeightSum, LazyBinaryFromHeightSumCum, LazyFromDateLast,
LazyFromHeightLast, LazyFromHeightSum, LazyFromHeightSumCum, LazyPriceFromCents,
PercentageDollarsF32, PriceFromHeight, StoredF32Identity,
DollarsPlus, LazyBinaryFromHeightSum, LazyBinaryFromHeightSumCum,
LazyComputedValueFromHeightSumCum, LazyFromDateLast, LazyFromHeightLast, LazyFromHeightSum,
LazyFromHeightSumCum, LazyPriceFromCents, PercentageDollarsF32, PriceFromHeight,
StoredF32Identity, ValueFromDateLast,
},
price,
};
@@ -54,9 +55,12 @@ pub struct RealizedMetrics {
// === Realized Profit/Loss ===
pub realized_profit: ComputedFromHeightSumCum<Dollars>,
pub realized_profit_7d_ema: ComputedFromDateLast<Dollars>,
pub realized_loss: ComputedFromHeightSumCum<Dollars>,
pub realized_loss_7d_ema: ComputedFromDateLast<Dollars>,
pub neg_realized_loss: LazyFromHeightSumCum<Dollars>,
pub net_realized_pnl: ComputedFromHeightSumCum<Dollars>,
pub net_realized_pnl_7d_ema: ComputedFromDateLast<Dollars>,
pub realized_value: ComputedFromHeightSum<Dollars>,
// === Realized vs Realized Cap Ratios (lazy) ===
@@ -106,10 +110,23 @@ pub struct RealizedMetrics {
pub net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: ComputedFromDateLast<StoredF32>,
pub net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: ComputedFromDateLast<StoredF32>,
// === ATH Regret ===
/// Realized ATH regret: Σ((ath - sell_price) × sats)
/// "How much more could have been made by selling at ATH instead"
pub ath_regret: ComputedFromHeightSumCum<Dollars>,
// === Peak Regret ===
/// Realized peak regret: Σ((peak - sell_price) × sats)
/// where peak = max price during holding period.
/// "How much more could have been made by selling at peak instead"
pub peak_regret: ComputedFromHeightSumCum<Dollars>,
/// Peak regret as % of realized cap
pub peak_regret_rel_to_realized_cap: LazyBinaryFromHeightSum<StoredF32, Dollars, Dollars>,
// === Sent in Profit/Loss ===
/// Sats sent in profit (sats/btc/usd)
pub sent_in_profit: LazyComputedValueFromHeightSumCum,
/// 14-day EMA of sent in profit (sats, btc, usd)
pub sent_in_profit_14d_ema: ValueFromDateLast,
/// Sats sent in loss (sats/btc/usd)
pub sent_in_loss: LazyComputedValueFromHeightSumCum,
/// 14-day EMA of sent in loss (sats, btc, usd)
pub sent_in_loss_14d_ema: ValueFromDateLast,
}
impl RealizedMetrics {
@@ -143,6 +160,13 @@ impl RealizedMetrics {
cfg.indexes,
)?;
let realized_profit_7d_ema = ComputedFromDateLast::forced_import(
cfg.db,
&cfg.name("realized_profit_7d_ema"),
cfg.version,
cfg.indexes,
)?;
let realized_loss = ComputedFromHeightSumCum::forced_import(
cfg.db,
&cfg.name("realized_loss"),
@@ -150,6 +174,13 @@ impl RealizedMetrics {
cfg.indexes,
)?;
let realized_loss_7d_ema = ComputedFromDateLast::forced_import(
cfg.db,
&cfg.name("realized_loss_7d_ema"),
cfg.version,
cfg.indexes,
)?;
let neg_realized_loss = LazyFromHeightSumCum::from_computed::<Negate>(
&cfg.name("neg_realized_loss"),
cfg.version + v1,
@@ -164,6 +195,20 @@ impl RealizedMetrics {
cfg.indexes,
)?;
let net_realized_pnl_7d_ema = ComputedFromDateLast::forced_import(
cfg.db,
&cfg.name("net_realized_pnl_7d_ema"),
cfg.version,
cfg.indexes,
)?;
let peak_regret = ComputedFromHeightSumCum::forced_import(
cfg.db,
&cfg.name("realized_peak_regret"),
cfg.version + v2,
cfg.indexes,
)?;
// realized_value is the source for total_realized_pnl (they're identical)
let realized_value = ComputedFromHeightSum::forced_import(
cfg.db,
@@ -360,7 +405,7 @@ impl RealizedMetrics {
Ok(Self {
// === Realized Cap ===
realized_cap_cents,
realized_cap,
realized_cap: realized_cap.clone(),
realized_price,
realized_price_extra,
realized_cap_rel_to_own_market_cap: extended
@@ -392,9 +437,12 @@ impl RealizedMetrics {
// === Realized Profit/Loss ===
realized_profit,
realized_profit_7d_ema,
realized_loss,
realized_loss_7d_ema,
neg_realized_loss,
net_realized_pnl,
net_realized_pnl_7d_ema,
realized_value,
// === Realized vs Realized Cap Ratios (lazy) ===
@@ -508,11 +556,46 @@ impl RealizedMetrics {
)?,
// === ATH Regret ===
// v2: Changed to use max HIGH price during holding period instead of global ATH at send time
ath_regret: ComputedFromHeightSumCum::forced_import(
peak_regret: peak_regret.clone(),
peak_regret_rel_to_realized_cap: LazyBinaryFromHeightSum::from_sumcum_lazy_last::<
PercentageDollarsF32,
_,
>(
&cfg.name("peak_regret_rel_to_realized_cap"),
cfg.version + v1,
peak_regret.height.boxed_clone(),
realized_cap.height.boxed_clone(),
&peak_regret,
&realized_cap,
),
// === Sent in Profit/Loss ===
sent_in_profit: LazyComputedValueFromHeightSumCum::forced_import(
cfg.db,
&cfg.name("realized_ath_regret"),
cfg.version + v2,
&cfg.name("sent_in_profit"),
cfg.version,
cfg.indexes,
cfg.price,
)?,
sent_in_profit_14d_ema: ValueFromDateLast::forced_import(
cfg.db,
&cfg.name("sent_in_profit_14d_ema"),
cfg.version,
cfg.compute_dollars(),
cfg.indexes,
)?,
sent_in_loss: LazyComputedValueFromHeightSumCum::forced_import(
cfg.db,
&cfg.name("sent_in_loss"),
cfg.version,
cfg.indexes,
cfg.price,
)?,
sent_in_loss_14d_ema: ValueFromDateLast::forced_import(
cfg.db,
&cfg.name("sent_in_loss_14d_ema"),
cfg.version,
cfg.compute_dollars(),
cfg.indexes,
)?,
})
@@ -532,7 +615,9 @@ impl RealizedMetrics {
.min(self.profit_value_destroyed.height.len())
.min(self.loss_value_created.height.len())
.min(self.loss_value_destroyed.height.len())
.min(self.ath_regret.height.len())
.min(self.peak_regret.height.len())
.min(self.sent_in_profit.sats.height.len())
.min(self.sent_in_loss.sats.height.len())
}
/// Push realized state values to height-indexed vectors.
@@ -568,9 +653,19 @@ impl RealizedMetrics {
.height
.truncate_push(height, state.loss_value_destroyed().to_dollars())?;
// ATH regret
self.ath_regret
self.peak_regret
.height
.truncate_push(height, state.ath_regret().to_dollars())?;
.truncate_push(height, state.peak_regret().to_dollars())?;
// Volume at profit/loss
self.sent_in_profit
.sats
.height
.truncate_push(height, state.sent_in_profit())?;
self.sent_in_loss
.sats
.height
.truncate_push(height, state.sent_in_loss())?;
Ok(())
}
@@ -591,7 +686,10 @@ impl RealizedMetrics {
&mut self.loss_value_created.height,
&mut self.loss_value_destroyed.height,
// ATH regret
&mut self.ath_regret.height,
&mut self.peak_regret.height,
// Sent in profit/loss
&mut self.sent_in_profit.sats.height,
&mut self.sent_in_loss.sats.height,
]
.into_par_iter()
}
@@ -725,11 +823,29 @@ impl RealizedMetrics {
exit,
)?;
// ATH regret
self.ath_regret.height.compute_sum_of_others(
self.peak_regret.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.ath_regret.height)
.map(|v| &v.peak_regret.height)
.collect::<Vec<_>>(),
exit,
)?;
// Volume at profit/loss
self.sent_in_profit.sats.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.sent_in_profit.sats.height)
.collect::<Vec<_>>(),
exit,
)?;
self.sent_in_loss.sats.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.sent_in_loss.sats.height)
.collect::<Vec<_>>(),
exit,
)?;
@@ -790,7 +906,13 @@ impl RealizedMetrics {
self.loss_value_destroyed
.compute_rest(indexes, starting_indexes, exit)?;
// ATH regret
self.ath_regret
self.peak_regret
.compute_rest(indexes, starting_indexes, exit)?;
// Volume at profit/loss
self.sent_in_profit
.compute_rest(indexes, starting_indexes, exit)?;
self.sent_in_loss
.compute_rest(indexes, starting_indexes, exit)?;
Ok(())
@@ -856,6 +978,52 @@ impl RealizedMetrics {
exit,
)?;
// 7d EMA of realized profit/loss
self.realized_profit_7d_ema.compute_all(starting_indexes, exit, |v| {
Ok(v.compute_ema(
starting_indexes.dateindex,
&self.realized_profit.dateindex.sum.0,
7,
exit,
)?)
})?;
self.realized_loss_7d_ema.compute_all(starting_indexes, exit, |v| {
Ok(v.compute_ema(
starting_indexes.dateindex,
&self.realized_loss.dateindex.sum.0,
7,
exit,
)?)
})?;
self.net_realized_pnl_7d_ema.compute_all(starting_indexes, exit, |v| {
Ok(v.compute_ema(
starting_indexes.dateindex,
&self.net_realized_pnl.dateindex.sum.0,
7,
exit,
)?)
})?;
// 14-day EMA of sent in profit (sats and dollars)
self.sent_in_profit_14d_ema.compute_ema(
starting_indexes.dateindex,
&self.sent_in_profit.sats.dateindex.sum.0,
self.sent_in_profit.dollars.as_ref().map(|d| &d.dateindex.sum.0),
14,
exit,
)?;
// 14-day EMA of sent in loss (sats and dollars)
self.sent_in_loss_14d_ema.compute_ema(
starting_indexes.dateindex,
&self.sent_in_loss.sats.dateindex.sum.0,
self.sent_in_loss.dollars.as_ref().map(|d| &d.dateindex.sum.0),
14,
exit,
)?;
self.sopr_7d_ema
.compute_ema(starting_indexes.dateindex, &self.sopr, 7, exit)?;
@@ -1,3 +1,4 @@
use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Sats, StoredF32, StoredF64, Version};
@@ -64,6 +65,10 @@ pub struct RelativeMetrics {
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
pub invested_capital_in_loss_pct:
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
// === Unrealized Peak Regret Relative to Market Cap (date-only, lazy) ===
pub unrealized_peak_regret_rel_to_market_cap:
Option<LazyBinaryFromDateLast<StoredF32, Dollars, Dollars>>,
}
impl RelativeMetrics {
@@ -94,6 +99,11 @@ impl RelativeMetrics {
// Own market cap source
let own_market_cap = supply.total.dollars.as_ref();
// For "all" cohort, own_market_cap IS the global market cap
let market_cap = global_market_cap.or_else(|| {
matches!(cfg.filter, Filter::All).then_some(own_market_cap).flatten()
});
Ok(Self {
// === Supply Relative to Circulating Supply (lazy from global supply) ===
supply_rel_to_circulating_supply: (compute_rel_to_all
@@ -189,7 +199,7 @@ impl RelativeMetrics {
// === Unrealized vs Market Cap (lazy from global market cap) ===
unrealized_profit_rel_to_market_cap:
global_market_cap.map(|mc| {
market_cap.map(|mc| {
LazyBinaryFromHeightLast::from_computed_height_date_and_lazy_binary_block_last::<
PercentageDollarsF32,
_,
@@ -202,7 +212,7 @@ impl RelativeMetrics {
)
}),
unrealized_loss_rel_to_market_cap:
global_market_cap.map(|mc| {
market_cap.map(|mc| {
LazyBinaryFromHeightLast::from_computed_height_date_and_lazy_binary_block_last::<
PercentageDollarsF32,
_,
@@ -214,7 +224,7 @@ impl RelativeMetrics {
mc,
)
}),
neg_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| {
neg_unrealized_loss_rel_to_market_cap: market_cap.map(|mc| {
LazyBinaryFromHeightLast::from_computed_height_date_and_lazy_binary_block_last::<
NegPercentageDollarsF32,
_,
@@ -226,7 +236,7 @@ impl RelativeMetrics {
mc,
)
}),
net_unrealized_pnl_rel_to_market_cap: global_market_cap.map(|mc| {
net_unrealized_pnl_rel_to_market_cap: market_cap.map(|mc| {
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
PercentageDollarsF32,
_,
@@ -242,7 +252,7 @@ impl RelativeMetrics {
}),
// NUPL is a proxy for net_unrealized_pnl_rel_to_market_cap
nupl: global_market_cap.map(|mc| {
nupl: market_cap.map(|mc| {
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
PercentageDollarsF32,
_,
@@ -382,6 +392,21 @@ impl RelativeMetrics {
&r.realized_cap,
)
}),
// === Peak Regret Relative to Market Cap (date-only, lazy) ===
unrealized_peak_regret_rel_to_market_cap: unrealized
.peak_regret
.as_ref()
.zip(market_cap)
.map(|(pr, mc)| {
LazyBinaryFromDateLast::from_computed_and_derived_last::<PercentageDollarsF32>(
&cfg.name("unrealized_peak_regret_rel_to_market_cap"),
cfg.version,
pr,
mc.rest.dateindex.boxed_clone(),
&mc.rest.dates,
)
}),
})
}
}
@@ -10,7 +10,7 @@ use crate::{
indexes,
internal::{
HalfClosePriceTimesSats, HalveDollars, HalveSats, HalveSatsToBitcoin,
LazyBinaryValueFromHeightLast, ValueFromHeightLast,
LazyBinaryValueFromHeightLast, ValueChangeFromDate, ValueFromHeightLast,
},
};
@@ -21,6 +21,8 @@ use super::ImportConfig;
pub struct SupplyMetrics {
pub total: ValueFromHeightLast,
pub halved: LazyBinaryValueFromHeightLast,
/// 30-day change in supply (net position change) - sats, btc, usd
pub _30d_change: ValueChangeFromDate,
}
impl SupplyMetrics {
@@ -41,9 +43,18 @@ impl SupplyMetrics {
HalveDollars,
>(&cfg.name("supply_halved"), &supply, cfg.price, cfg.version);
let _30d_change = ValueChangeFromDate::forced_import(
cfg.db,
&cfg.name("_30d_change"),
cfg.version,
cfg.compute_dollars(),
cfg.indexes,
)?;
Ok(Self {
total: supply,
halved: supply_halved,
_30d_change,
})
}
@@ -94,6 +105,17 @@ impl SupplyMetrics {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.total.compute_rest(indexes, starting_indexes, exit)
self.total.compute_rest(indexes, starting_indexes, exit)?;
// 30-day change in supply
self._30d_change.compute_change(
starting_indexes.dateindex,
&self.total.sats.dateindex.0,
self.total.dollars.as_ref().map(|d| &d.dateindex.0),
30,
exit,
)?;
Ok(())
}
}
@@ -1,10 +1,10 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{CentsSats, CentsSquaredSats, CentsUnsigned, DateIndex, Dollars, Height, Sats};
use brk_types::{CentsSats, CentsSquaredSats, CentsUnsigned, DateIndex, Dollars, Height};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, BytesVec, Exit, GenericStoredVec, ImportableVec, Negate,
TypedVecIterator, Version,
TypedVecIterator,
};
use crate::{
@@ -12,8 +12,8 @@ use crate::{
distribution::state::UnrealizedState,
indexes,
internal::{
ComputedFromHeightAndDateLast, ComputedFromHeightLast, DollarsMinus, DollarsPlus,
LazyBinaryFromHeightLast, LazyFromHeightLast, ValueFromHeightAndDateLast,
ComputedFromDateLast, ComputedFromHeightAndDateLast, ComputedFromHeightLast, DollarsMinus,
DollarsPlus, LazyBinaryFromHeightLast, LazyFromHeightLast, ValueFromHeightAndDateLast,
},
price,
};
@@ -60,10 +60,11 @@ pub struct UnrealizedMetrics {
pub net_unrealized_pnl: LazyBinaryFromHeightLast<Dollars>,
pub total_unrealized_pnl: LazyBinaryFromHeightLast<Dollars>,
// === ATH Regret ===
/// Unrealized ATH regret: (ATH - spot) × supply_in_profit + ATH × supply_in_loss - invested_capital_in_loss
/// "How much more I'd have if I sold at ATH instead of now" (refined formula accounting for cost basis)
pub ath_regret: ComputedFromHeightLast<Dollars>,
// === Peak Regret (age_range cohorts only) ===
/// Unrealized peak regret: sum of (peak_price - reference_price) × supply
/// where reference_price = max(spot, cost_basis) and peak = max price during holding period.
/// Only computed for age_range cohorts, then aggregated for overlapping cohorts.
pub peak_regret: Option<ComputedFromDateLast<Dollars>>,
}
impl UnrealizedMetrics {
@@ -176,16 +177,18 @@ impl UnrealizedMetrics {
&unrealized_loss,
);
// === ATH Regret ===
// v2: Changed to use HIGH prices consistently for ATH instead of mixing HIGH/CLOSE
// v3: Changed to ComputedFromHeightLast to derive dateindex from height (avoids precision loss)
let v3 = Version::new(3);
let ath_regret = ComputedFromHeightLast::forced_import(
cfg.db,
&cfg.name("unrealized_ath_regret"),
cfg.version + v3,
cfg.indexes,
)?;
// Peak regret: only for age-based UTXO cohorts
let peak_regret = cfg
.compute_peak_regret()
.then(|| {
ComputedFromDateLast::forced_import(
cfg.db,
&cfg.name("unrealized_peak_regret"),
cfg.version,
cfg.indexes,
)
})
.transpose()?;
Ok(Self {
supply_in_profit,
@@ -204,7 +207,7 @@ impl UnrealizedMetrics {
neg_unrealized_loss,
net_unrealized_pnl,
total_unrealized_pnl,
ath_regret,
peak_regret,
})
}
@@ -226,7 +229,8 @@ impl UnrealizedMetrics {
/// Get minimum length across dateindex-indexed vectors written in block loop.
pub fn min_stateful_dateindex_len(&self) -> usize {
self.supply_in_profit
let mut min = self
.supply_in_profit
.indexes
.sats_dateindex
.len()
@@ -234,7 +238,11 @@ impl UnrealizedMetrics {
.min(self.unrealized_profit.dateindex.len())
.min(self.unrealized_loss.dateindex.len())
.min(self.invested_capital_in_profit.dateindex.len())
.min(self.invested_capital_in_loss.dateindex.len())
.min(self.invested_capital_in_loss.dateindex.len());
if let Some(pr) = &self.peak_regret {
min = min.min(pr.dateindex.len());
}
min
}
/// Push unrealized state values to height-indexed vectors.
@@ -311,25 +319,28 @@ impl UnrealizedMetrics {
/// 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.supply_in_profit.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.height as &mut dyn AnyStoredVec,
&mut self.unrealized_profit.height as &mut dyn AnyStoredVec,
&mut self.unrealized_loss.height as &mut dyn AnyStoredVec,
&mut self.invested_capital_in_profit.height as &mut dyn AnyStoredVec,
&mut self.invested_capital_in_loss.height as &mut dyn AnyStoredVec,
&mut self.invested_capital_in_profit_raw as &mut dyn AnyStoredVec,
&mut self.invested_capital_in_loss_raw as &mut dyn AnyStoredVec,
&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec,
&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.indexes.sats_dateindex as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.indexes.sats_dateindex as &mut dyn AnyStoredVec,
&mut self.unrealized_profit.rest.dateindex as &mut dyn AnyStoredVec,
&mut self.unrealized_loss.rest.dateindex as &mut dyn AnyStoredVec,
&mut self.invested_capital_in_profit.rest.dateindex as &mut dyn AnyStoredVec,
&mut self.invested_capital_in_loss.rest.dateindex as &mut dyn AnyStoredVec,
]
.into_par_iter()
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![
&mut self.supply_in_profit.height,
&mut self.supply_in_loss.height,
&mut self.unrealized_profit.height,
&mut self.unrealized_loss.height,
&mut self.invested_capital_in_profit.height,
&mut self.invested_capital_in_loss.height,
&mut self.invested_capital_in_profit_raw,
&mut self.invested_capital_in_loss_raw,
&mut self.investor_cap_in_profit_raw,
&mut self.investor_cap_in_loss_raw,
&mut self.supply_in_profit.indexes.sats_dateindex,
&mut self.supply_in_loss.indexes.sats_dateindex,
&mut self.unrealized_profit.rest.dateindex,
&mut self.unrealized_loss.rest.dateindex,
&mut self.invested_capital_in_profit.rest.dateindex,
&mut self.invested_capital_in_loss.rest.dateindex,
];
if let Some(pr) = &mut self.peak_regret {
vecs.push(&mut pr.dateindex);
}
vecs.into_par_iter()
}
/// Compute aggregate values from separate cohorts.
@@ -501,6 +512,22 @@ impl UnrealizedMetrics {
.collect::<Vec<_>>(),
exit,
)?;
// Peak regret aggregation (only if this cohort has peak_regret)
if let Some(pr) = &mut self.peak_regret {
let other_prs: Vec<_> = others.iter().filter_map(|v| v.peak_regret.as_ref()).collect();
if !other_prs.is_empty() {
pr.dateindex.compute_sum_of_others(
starting_indexes.dateindex,
&other_prs
.iter()
.map(|v| &v.dateindex)
.collect::<Vec<_>>(),
exit,
)?;
}
}
Ok(())
}
@@ -582,58 +609,6 @@ impl UnrealizedMetrics {
)?)
})?;
// ATH regret: (ATH - spot) × supply_in_profit + ATH × supply_in_loss - invested_capital_in_loss
// This is the refined formula that accounts for cost basis:
// - For UTXOs in profit: regret = ATH - spot (they could have sold at ATH instead of now)
// - For UTXOs in loss: regret = ATH - cost_basis (they could have sold at ATH instead of holding)
// ath = running max of high prices
// Height computation
{
// Pre-compute ATH as running max of high prices
let height_ath: Vec<CentsUnsigned> = {
let mut ath = CentsUnsigned::ZERO;
price
.cents
.split
.height
.high
.into_iter()
.map(|high| {
if *high > ath {
ath = *high;
}
ath
})
.collect()
};
self.ath_regret.height.compute_transform4(
starting_indexes.height,
&price.cents.split.height.close,
&self.supply_in_profit.height,
&self.supply_in_loss.height,
&self.invested_capital_in_loss_raw,
|(h, spot, supply_profit, supply_loss, invested_loss_raw, ..)| {
let ath = height_ath[usize::from(h)];
// (ATH - spot) × supply_in_profit + ATH × supply_in_loss - invested_capital_in_loss
let ath_u128 = ath.as_u128();
let spot_u128 = spot.as_u128();
let profit_regret = (ath_u128 - spot_u128) * supply_profit.as_u128();
// invested_loss_raw is CentsSats (already in cents*sats scale)
let loss_regret = ath_u128 * supply_loss.as_u128() - invested_loss_raw.inner();
let regret_raw = profit_regret + loss_regret;
let regret_cents = CentsUnsigned::new((regret_raw / Sats::ONE_BTC_U128) as u64);
(h, regret_cents.to_dollars())
},
exit,
)?;
}
// DateIndex computation: derive from height values using last-value aggregation
self.ath_regret
.compute_rest(indexes, starting_indexes, exit)?;
Ok(())
}
}
@@ -237,7 +237,7 @@ impl CohortState {
let ath_ps = CentsSats::from_price_sats(ath_price, sats);
let prev_investor_cap = prev_ps.to_investor_cap(pp);
realized.send(current_ps, prev_ps, ath_ps, prev_investor_cap);
realized.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap);
self.cost_basis_data.as_mut().unwrap().decrement(
pp,
@@ -284,7 +284,7 @@ impl CohortState {
let ath_ps = CentsSats::from_price_sats(ath, sats);
let prev_investor_cap = prev_ps.to_investor_cap(prev_price);
realized.send(current_ps, prev_ps, ath_ps, prev_investor_cap);
realized.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap);
if current.supply_state.value.is_not_zero() {
self.cost_basis_data.as_mut().unwrap().increment(
@@ -23,8 +23,12 @@ pub struct RealizedState {
loss_value_created_raw: u128,
/// cost_basis × sats for loss cases (= capitulation_flow)
loss_value_destroyed_raw: u128,
/// Raw realized ATH regret: Σ((ath - sell_price) × sats)
ath_regret_raw: u128,
/// Raw realized peak regret: Σ((peak - sell_price) × sats)
peak_regret_raw: u128,
/// Sats sent in profit
sent_in_profit: Sats,
/// Sats sent in loss
sent_in_loss: Sats,
}
impl RealizedState {
@@ -137,12 +141,24 @@ impl RealizedState {
self.profit_value_destroyed()
}
/// Get realized ATH regret as CentsUnsigned.
/// This is Σ((ath - sell_price) × sats) - how much more could have been made
/// by selling at ATH instead of when actually sold.
/// Get realized peak regret as CentsUnsigned.
/// This is Σ((peak - sell_price) × sats) - how much more could have been made
/// by selling at peak instead of when actually sold.
#[inline]
pub fn ath_regret(&self) -> CentsUnsigned {
CentsUnsigned::new((self.ath_regret_raw / Sats::ONE_BTC_U128) as u64)
pub fn peak_regret(&self) -> CentsUnsigned {
CentsUnsigned::new((self.peak_regret_raw / Sats::ONE_BTC_U128) as u64)
}
/// Get sats sent in profit.
#[inline]
pub fn sent_in_profit(&self) -> Sats {
self.sent_in_profit
}
/// Get sats sent in loss.
#[inline]
pub fn sent_in_loss(&self) -> Sats {
self.sent_in_loss
}
pub fn reset_single_iteration_values(&mut self) {
@@ -152,7 +168,9 @@ impl RealizedState {
self.profit_value_destroyed_raw = 0;
self.loss_value_created_raw = 0;
self.loss_value_destroyed_raw = 0;
self.ath_regret_raw = 0;
self.peak_regret_raw = 0;
self.sent_in_profit = Sats::ZERO;
self.sent_in_loss = Sats::ZERO;
}
/// Increment using pre-computed values (for UTXO path)
@@ -189,6 +207,7 @@ impl RealizedState {
#[inline]
pub fn send(
&mut self,
sats: Sats,
current_ps: CentsSats,
prev_ps: CentsSats,
ath_ps: CentsSats,
@@ -199,21 +218,24 @@ impl RealizedState {
self.profit_raw += (current_ps - prev_ps).as_u128();
self.profit_value_created_raw += current_ps.as_u128();
self.profit_value_destroyed_raw += prev_ps.as_u128();
self.sent_in_profit += sats;
}
Ordering::Less => {
self.loss_raw += (prev_ps - current_ps).as_u128();
self.loss_value_created_raw += current_ps.as_u128();
self.loss_value_destroyed_raw += prev_ps.as_u128();
self.sent_in_loss += sats;
}
Ordering::Equal => {
// Break-even: count as profit side (arbitrary but consistent)
self.profit_value_created_raw += current_ps.as_u128();
self.profit_value_destroyed_raw += prev_ps.as_u128();
self.sent_in_profit += sats;
}
}
// Track ATH regret: (ath - sell_price) × sats
self.ath_regret_raw += (ath_ps - current_ps).as_u128();
// Track peak regret: (peak - sell_price) × sats
self.peak_regret_raw += (ath_ps - current_ps).as_u128();
// Inline decrement to avoid recomputation
self.cap_raw -= prev_ps.as_u128();
+25 -9
View File
@@ -7,7 +7,7 @@ use brk_types::{
DateIndex, EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex,
SupplyState, Version,
};
use tracing::info;
use tracing::{debug, info};
use vecdb::{
AnyVec, BytesVec, Database, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec,
LazyVecFrom1, PAGE_SIZE, Stamp, TypedVecIterator, VecIndex,
@@ -38,7 +38,7 @@ pub struct Vecs {
#[traversable(skip)]
db: Database,
pub chain_state: BytesVec<Height, SupplyState>,
pub supply_state: BytesVec<Height, SupplyState>,
pub any_address_indexes: AnyAddressIndexesVecs,
pub addresses_data: AddressesDataVecs,
pub utxo_cohorts: UTXOCohorts,
@@ -139,8 +139,8 @@ impl Vecs {
GrowthRateVecs::forced_import(&db, version, indexes, &new_addr_count, &addr_count)?;
let this = Self {
chain_state: BytesVec::forced_import_with(
vecdb::ImportOptions::new(&db, "chain", version)
supply_state: BytesVec::forced_import_with(
vecdb::ImportOptions::new(&db, "supply_state", version)
.with_saved_stamped_changes(SAVED_STAMPED_CHANGES),
)?,
@@ -197,7 +197,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
// 1. Find minimum height we have data for across stateful vecs
let current_height = Height::from(self.chain_state.len());
let current_height = Height::from(self.supply_state.len());
let height_based_min = self.min_stateful_height_len();
let dateindex_min = self.min_stateful_dateindex_len();
let min_stateful = adjust_for_dateindex_gap(height_based_min, dateindex_min, indexes)?;
@@ -219,7 +219,7 @@ impl Vecs {
let stamp = Stamp::from(height);
// Rollback BytesVec state and capture results for validation
let chain_state_rollback = self.chain_state.rollback_before(stamp);
let chain_state_rollback = self.supply_state.rollback_before(stamp);
// Validate all rollbacks and imports are consistent
let recovered = recover_state(
@@ -234,14 +234,20 @@ impl Vecs {
if recovered.starting_height.is_zero() {
info!("State recovery validation failed, falling back to fresh start");
}
debug!(
"recover_state completed, starting_height={}",
recovered.starting_height
);
recovered.starting_height
}
StartMode::Fresh => Height::ZERO,
};
debug!("recovered_height={}", recovered_height);
// Fresh start: reset all state
let (starting_height, mut chain_state) = if recovered_height.is_zero() {
self.chain_state.reset()?;
self.supply_state.reset()?;
self.addr_count.reset_height()?;
self.empty_addr_count.reset_height()?;
self.address_activity.reset_height()?;
@@ -256,13 +262,15 @@ impl Vecs {
(Height::ZERO, vec![])
} else {
// Recover chain_state from stored values
debug!("recovering chain_state from stored values");
let height_to_timestamp = &blocks.time.timestamp_monotonic;
let height_to_price = price.map(|p| &p.cents.split.height.close);
let mut height_to_timestamp_iter = height_to_timestamp.into_iter();
let mut height_to_price_iter = height_to_price.map(|v| v.into_iter());
let mut chain_state_iter = self.chain_state.into_iter();
let mut chain_state_iter = self.supply_state.into_iter();
debug!("building supply_state vec for {} heights", recovered_height);
let chain_state = (0..recovered_height.to_usize())
.map(|h| {
let h = Height::from(h);
@@ -274,6 +282,7 @@ impl Vecs {
}
})
.collect();
debug!("chain_state vec built");
(recovered_height, chain_state)
};
@@ -293,16 +302,23 @@ impl Vecs {
}
// 2b. Validate computed versions
debug!("validating computed versions");
let base_version = VERSION;
self.utxo_cohorts.validate_computed_versions(base_version)?;
self.address_cohorts
.validate_computed_versions(base_version)?;
debug!("computed versions validated");
// 3. Get last height from indexer
let last_height = Height::from(indexer.vecs.blocks.blockhash.len().saturating_sub(1));
debug!(
"last_height={}, starting_height={}",
last_height, starting_height
);
// 4. Process blocks
if starting_height <= last_height {
debug!("calling process_blocks");
process_blocks(
self,
indexer,
@@ -401,7 +417,7 @@ impl Vecs {
self.utxo_cohorts
.min_separate_stateful_height_len()
.min(self.address_cohorts.min_separate_stateful_height_len())
.min(Height::from(self.chain_state.len()))
.min(Height::from(self.supply_state.len()))
.min(self.any_address_indexes.min_stamped_height())
.min(self.addresses_data.min_stamped_height())
.min(Height::from(self.addr_count.min_stateful_height()))
@@ -832,4 +832,41 @@ where
decadeindex: period!(decadeindex),
}
}
/// Create from a ComputedFromDateLast and a LazyDateDerivedLast.
pub fn from_computed_and_derived_last<F: BinaryTransform<S1T, S2T, T>>(
name: &str,
version: Version,
source1: &ComputedFromDateLast<S1T>,
dateindex_source2: IterableBoxedVec<DateIndex, S2T>,
source2: &LazyDateDerivedLast<S2T>,
) -> Self {
let v = version + VERSION;
macro_rules! period {
($p:ident) => {
LazyBinaryTransformLast::from_lazy_last::<F, _, _, _, _>(
name,
v,
&source1.$p,
&source2.$p,
)
};
}
Self {
dateindex: LazyVecFrom2::transformed::<F>(
name,
v,
source1.dateindex.boxed_clone(),
dateindex_source2,
),
weekindex: period!(weekindex),
monthindex: period!(monthindex),
quarterindex: period!(quarterindex),
semesterindex: period!(semesterindex),
yearindex: period!(yearindex),
decadeindex: period!(decadeindex),
}
}
}
@@ -7,7 +7,10 @@ use brk_types::{
use schemars::JsonSchema;
use vecdb::{BinaryTransform, IterableCloneableVec};
use crate::internal::{ComputedVecValue, ComputedHeightDerivedSum, LazyBinaryTransformSum, NumericValue};
use crate::internal::{
ComputedFromHeightSumCum, ComputedHeightDerivedSum, ComputedVecValue, LazyBinaryTransformSum,
LazyFromHeightLast, NumericValue,
};
const VERSION: Version = Version::ZERO;
@@ -91,4 +94,41 @@ where
decadeindex: period!(decadeindex),
}
}
/// Create from a SumCum source (using only sum) and a LazyLast source.
pub fn from_sumcum_lazy_last<F, S2ST>(
name: &str,
version: Version,
source1: &ComputedFromHeightSumCum<S1T>,
source2: &LazyFromHeightLast<S2T, S2ST>,
) -> Self
where
F: BinaryTransform<S1T, S2T, T>,
S2ST: ComputedVecValue + JsonSchema,
{
let v = version + VERSION;
// source1 has SumCum pattern with .dateindex.sum, .weekindex.sum, etc.
// source2 has Last pattern via deref chain: .dates.dateindex, .dates.weekindex, etc.
macro_rules! period {
($p:ident) => {
LazyBinaryTransformSum::from_boxed::<F>(
name,
v,
source1.$p.sum.boxed_clone(),
source2.dates.$p.boxed_clone(),
)
};
}
Self {
dateindex: period!(dateindex),
weekindex: period!(weekindex),
monthindex: period!(monthindex),
quarterindex: period!(quarterindex),
semesterindex: period!(semesterindex),
yearindex: period!(yearindex),
decadeindex: period!(decadeindex),
}
}
}
@@ -19,6 +19,8 @@ mod price;
mod ratio;
mod stddev;
mod unary_last;
mod value_change;
mod value_change_derived;
mod value_derived_last;
mod value_last;
mod value_lazy_last;
@@ -44,6 +46,8 @@ pub use price::*;
pub use ratio::*;
pub use stddev::*;
pub use unary_last::*;
pub use value_change::*;
pub use value_change_derived::*;
pub use value_derived_last::*;
pub use value_last::*;
pub use value_lazy_last::*;
@@ -0,0 +1,77 @@
//! Change values from DateIndex - stores signed sats (changes can be negative).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DateIndex, Dollars, Sats, SatsSigned, Version};
use vecdb::{CollectableVec, Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, PcoVec};
use crate::{ComputeIndexes, indexes, price};
use super::LazyValueChangeDateDerived;
const VERSION: Version = Version::ZERO;
/// Change values indexed by date - uses signed sats since changes can be negative.
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct ValueChangeFromDate {
#[traversable(rename = "sats")]
pub sats: EagerVec<PcoVec<DateIndex, SatsSigned>>,
#[traversable(flatten)]
pub rest: LazyValueChangeDateDerived,
}
impl ValueChangeFromDate {
pub fn forced_import(
db: &Database,
name: &str,
version: Version,
compute_dollars: bool,
indexes: &indexes::Vecs,
) -> Result<Self> {
let sats = EagerVec::forced_import(db, name, version + VERSION)?;
let rest = LazyValueChangeDateDerived::from_source(
db,
name,
sats.boxed_clone(),
version + VERSION,
compute_dollars,
indexes,
)?;
Ok(Self { sats, rest })
}
/// Compute N-day change from unsigned sats source and optional dollars source.
pub fn compute_change(
&mut self,
starting_dateindex: DateIndex,
sats_source: &impl CollectableVec<DateIndex, Sats>,
dollars_source: Option<&impl CollectableVec<DateIndex, Dollars>>,
period: usize,
exit: &Exit,
) -> Result<()> {
self.sats
.compute_change(starting_dateindex, sats_source, period, exit)?;
if let (Some(dollars), Some(source)) = (self.rest.dollars.as_mut(), dollars_source) {
dollars
.dateindex
.compute_change(starting_dateindex, source, period, exit)?;
}
Ok(())
}
/// Compute dollars from price after sats change is computed.
pub fn compute_dollars_from_price(
&mut self,
price: Option<&price::Vecs>,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.rest
.compute_dollars_from_price(price, starting_indexes, exit)
}
}
@@ -0,0 +1,84 @@
//! Lazy derived values for change (bitcoin from sats, period aggregations).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, DateIndex, Dollars, SatsSigned, Version};
use vecdb::{Database, Exit, IterableBoxedVec};
use crate::{
ComputeIndexes, indexes,
internal::{ComputedFromDateLast, LazyDateDerivedLast, LazyFromDateLast, SatsSignedToBitcoin},
price,
traits::ComputeFromBitcoin,
utils::OptionExt,
};
const VERSION: Version = Version::ZERO;
/// Lazy derived values for change (bitcoin from sats, period aggregations).
#[derive(Clone, Traversable)]
pub struct LazyValueChangeDateDerived {
pub sats: LazyDateDerivedLast<SatsSigned>,
pub bitcoin: LazyFromDateLast<Bitcoin, SatsSigned>,
pub dollars: Option<ComputedFromDateLast<Dollars>>,
}
impl LazyValueChangeDateDerived {
pub fn from_source(
db: &Database,
name: &str,
source: IterableBoxedVec<DateIndex, SatsSigned>,
version: Version,
compute_dollars: bool,
indexes: &indexes::Vecs,
) -> Result<Self> {
let sats =
LazyDateDerivedLast::from_source(name, version + VERSION, source.clone(), indexes);
let bitcoin = LazyFromDateLast::from_derived::<SatsSignedToBitcoin>(
&format!("{name}_btc"),
version + VERSION,
source,
&sats,
);
let dollars = compute_dollars
.then(|| {
ComputedFromDateLast::forced_import(
db,
&format!("{name}_usd"),
version + VERSION,
indexes,
)
})
.transpose()?;
Ok(Self {
sats,
bitcoin,
dollars,
})
}
pub fn compute_dollars_from_price(
&mut self,
price: Option<&price::Vecs>,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
if let Some(dollars) = self.dollars.as_mut() {
let dateindex_to_bitcoin = &*self.bitcoin.dateindex;
let dateindex_to_price_close = &price.u().usd.split.close.dateindex;
dollars.compute_all(starting_indexes, exit, |v| {
v.compute_from_bitcoin(
starting_indexes.dateindex,
dateindex_to_bitcoin,
dateindex_to_price_close,
exit,
)
})?;
}
Ok(())
}
}
@@ -2,13 +2,13 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DateIndex, Sats, Version};
use brk_types::{DateIndex, Dollars, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, PcoVec};
use vecdb::{CollectableVec, Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, PcoVec};
use crate::{ComputeIndexes, indexes, price};
use super::LazyValueDateDerivedLast;
use super::{ComputedFromDateLast, LazyValueDateDerivedLast};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(merge)]
@@ -70,7 +70,7 @@ impl ValueFromDateLast {
pub fn compute_dollars<F>(&mut self, compute: F) -> Result<()>
where
F: FnMut(&mut crate::internal::ComputedFromDateLast<brk_types::Dollars>) -> Result<()>,
F: FnMut(&mut ComputedFromDateLast<Dollars>) -> Result<()>,
{
self.rest.compute_dollars(compute)
}
@@ -84,4 +84,63 @@ impl ValueFromDateLast {
self.rest
.compute_dollars_from_price(price, starting_indexes, exit)
}
/// Compute both sats and dollars using provided closures.
pub fn compute_both<S, D>(
&mut self,
compute_sats: S,
compute_dollars: D,
) -> Result<()>
where
S: FnOnce(&mut EagerVec<PcoVec<DateIndex, Sats>>) -> Result<()>,
D: FnOnce(&mut ComputedFromDateLast<Dollars>) -> Result<()>,
{
compute_sats(&mut self.sats_dateindex)?;
if let Some(dollars) = self.rest.dollars.as_mut() {
compute_dollars(dollars)?;
}
Ok(())
}
/// Compute EMA for sats and optionally dollars from source vecs.
pub fn compute_ema(
&mut self,
starting_dateindex: DateIndex,
sats_source: &impl CollectableVec<DateIndex, Sats>,
dollars_source: Option<&impl CollectableVec<DateIndex, Dollars>>,
period: usize,
exit: &Exit,
) -> Result<()> {
self.sats_dateindex
.compute_ema(starting_dateindex, sats_source, period, exit)?;
if let (Some(dollars), Some(source)) = (self.rest.dollars.as_mut(), dollars_source) {
dollars
.dateindex
.compute_ema(starting_dateindex, source, period, exit)?;
}
Ok(())
}
/// Compute N-day change for sats and optionally dollars from source vecs.
pub fn compute_change(
&mut self,
starting_dateindex: DateIndex,
sats_source: &impl CollectableVec<DateIndex, Sats>,
dollars_source: Option<&impl CollectableVec<DateIndex, Dollars>>,
period: usize,
exit: &Exit,
) -> Result<()> {
self.sats_dateindex
.compute_change(starting_dateindex, sats_source, period, exit)?;
if let (Some(dollars), Some(source)) = (self.rest.dollars.as_mut(), dollars_source) {
dollars
.dateindex
.compute_change(starting_dateindex, source, period, exit)?;
}
Ok(())
}
}
@@ -7,8 +7,8 @@ use schemars::JsonSchema;
use vecdb::{BinaryTransform, IterableBoxedVec, IterableCloneableVec, LazyVecFrom2};
use crate::internal::{
ComputedFromHeightSum, ComputedHeightDerivedSum, ComputedVecValue, LazyBinaryHeightDerivedSum,
NumericValue,
ComputedFromHeightSum, ComputedFromHeightSumCum, ComputedHeightDerivedSum, ComputedVecValue,
LazyBinaryHeightDerivedSum, LazyFromHeightLast, NumericValue,
};
const VERSION: Version = Version::ZERO;
@@ -100,4 +100,31 @@ where
),
}
}
/// Create from a SumCum source (using only sum) and a LazyLast source.
/// Produces sum-only output (no cumulative).
pub fn from_sumcum_lazy_last<F, S2ST>(
name: &str,
version: Version,
height_source1: IterableBoxedVec<Height, S1T>,
height_source2: IterableBoxedVec<Height, S2T>,
source1: &ComputedFromHeightSumCum<S1T>,
source2: &LazyFromHeightLast<S2T, S2ST>,
) -> Self
where
F: BinaryTransform<S1T, S2T, T>,
S2ST: ComputedVecValue + JsonSchema,
{
let v = version + VERSION;
Self {
height: LazyVecFrom2::transformed::<F>(name, v, height_source1, height_source2),
rest: LazyBinaryHeightDerivedSum::from_sumcum_lazy_last::<F, S2ST>(
name,
v,
source1,
source2,
),
}
}
}
@@ -6,7 +6,10 @@ use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{BinaryTransform, IterableCloneableVec};
use crate::internal::{ComputedVecValue, ComputedHeightDerivedSum, LazyBinaryFromDateSum, LazyBinaryTransformSum, NumericValue};
use crate::internal::{
ComputedFromHeightSumCum, ComputedHeightDerivedSum, ComputedVecValue, LazyBinaryFromDateSum,
LazyBinaryTransformSum, LazyFromHeightLast, NumericValue,
};
const VERSION: Version = Version::ZERO;
@@ -80,4 +83,33 @@ where
),
}
}
/// Create from a SumCum source (using only sum) and a LazyLast source.
pub fn from_sumcum_lazy_last<F, S2ST>(
name: &str,
version: Version,
source1: &ComputedFromHeightSumCum<S1T>,
source2: &LazyFromHeightLast<S2T, S2ST>,
) -> Self
where
F: BinaryTransform<S1T, S2T, T>,
S2ST: ComputedVecValue + JsonSchema,
{
let v = version + VERSION;
Self {
dates: LazyBinaryFromDateSum::from_sumcum_lazy_last::<F, S2ST>(
name,
v,
source1,
source2,
),
difficultyepoch: LazyBinaryTransformSum::from_boxed::<F>(
name,
v,
source1.difficultyepoch.sum.boxed_clone(),
source2.difficultyepoch.boxed_clone(),
),
}
}
}
@@ -1,4 +1,4 @@
use brk_types::{Bitcoin, Sats};
use brk_types::{Bitcoin, Sats, SatsSigned};
use vecdb::UnaryTransform;
/// Sats -> Bitcoin (divide by 1e8)
@@ -10,3 +10,13 @@ impl UnaryTransform<Sats, Bitcoin> for SatsToBitcoin {
Bitcoin::from(sats)
}
}
/// SatsSigned -> Bitcoin (divide by 1e8, preserves sign)
pub struct SatsSignedToBitcoin;
impl UnaryTransform<SatsSigned, Bitcoin> for SatsSignedToBitcoin {
#[inline(always)]
fn apply(sats: SatsSigned) -> Bitcoin {
Bitcoin::from(sats)
}
}
+2
View File
@@ -130,6 +130,7 @@ mod rawlocktime;
mod recommendedfees;
mod rewardstats;
mod sats;
mod sats_signed;
mod satsfract;
mod semesterindex;
mod stored_bool;
@@ -305,6 +306,7 @@ pub use rawlocktime::*;
pub use recommendedfees::*;
pub use rewardstats::*;
pub use sats::*;
pub use sats_signed::*;
pub use satsfract::*;
pub use semesterindex::*;
pub use stored_bool::*;
+14
View File
@@ -268,6 +268,20 @@ impl From<usize> for Sats {
}
}
impl From<f32> for Sats {
#[inline]
fn from(value: f32) -> Self {
Self(value.round() as u64)
}
}
impl From<Sats> for f32 {
#[inline]
fn from(value: Sats) -> Self {
value.0 as f32
}
}
impl From<f64> for Sats {
#[inline]
fn from(value: f64) -> Self {
+220
View File
@@ -0,0 +1,220 @@
use std::{
iter::Sum,
ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign},
};
use derive_more::Deref;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use vecdb::{CheckedSub, Formattable, Pco};
use super::{Bitcoin, Sats};
/// Signed satoshis (i64) - for values that can be negative.
/// Used for changes, deltas, profit/loss calculations, etc.
#[derive(
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Copy,
Deref,
Default,
Serialize,
Deserialize,
Pco,
JsonSchema,
)]
pub struct SatsSigned(i64);
impl SatsSigned {
pub const ZERO: Self = Self(0);
#[inline]
pub const fn new(value: i64) -> Self {
Self(value)
}
#[inline]
pub const fn inner(self) -> i64 {
self.0
}
#[inline]
pub fn is_zero(&self) -> bool {
self.0 == 0
}
#[inline]
pub fn is_negative(&self) -> bool {
self.0 < 0
}
#[inline]
pub fn is_positive(&self) -> bool {
self.0 > 0
}
#[inline]
pub fn abs(self) -> Self {
Self(self.0.abs())
}
}
impl Add for SatsSigned {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub for SatsSigned {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl AddAssign for SatsSigned {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl SubAssign for SatsSigned {
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
impl Neg for SatsSigned {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl CheckedSub for SatsSigned {
fn checked_sub(self, rhs: Self) -> Option<Self> {
self.0.checked_sub(rhs.0).map(Self)
}
}
impl Mul<i64> for SatsSigned {
type Output = Self;
fn mul(self, rhs: i64) -> Self::Output {
Self(self.0.checked_mul(rhs).expect("SatsSigned overflow"))
}
}
impl Mul<usize> for SatsSigned {
type Output = Self;
fn mul(self, rhs: usize) -> Self::Output {
Self(self.0.checked_mul(rhs as i64).expect("SatsSigned overflow"))
}
}
impl Div<usize> for SatsSigned {
type Output = Self;
fn div(self, rhs: usize) -> Self::Output {
if rhs == 0 {
Self::ZERO
} else {
Self(self.0 / rhs as i64)
}
}
}
impl Sum for SatsSigned {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let sats: i64 = iter.map(|s| s.0).sum();
Self(sats)
}
}
impl From<i64> for SatsSigned {
#[inline]
fn from(value: i64) -> Self {
Self(value)
}
}
impl From<SatsSigned> for i64 {
#[inline]
fn from(value: SatsSigned) -> Self {
value.0
}
}
impl From<usize> for SatsSigned {
#[inline]
fn from(value: usize) -> Self {
Self(value as i64)
}
}
impl From<f32> for SatsSigned {
#[inline]
fn from(value: f32) -> Self {
Self(value.round() as i64)
}
}
impl From<SatsSigned> for f32 {
#[inline]
fn from(value: SatsSigned) -> Self {
value.0 as f32
}
}
impl From<f64> for SatsSigned {
#[inline]
fn from(value: f64) -> Self {
Self(value.round() as i64)
}
}
impl From<SatsSigned> for f64 {
#[inline]
fn from(value: SatsSigned) -> Self {
value.0 as f64
}
}
impl From<Sats> for SatsSigned {
#[inline]
fn from(value: Sats) -> Self {
Self(*value as i64)
}
}
impl From<Bitcoin> for SatsSigned {
#[inline]
fn from(value: Bitcoin) -> Self {
Self::from(Sats::from(value))
}
}
impl From<SatsSigned> for Bitcoin {
#[inline]
fn from(value: SatsSigned) -> Self {
Self::from(value.0 as f64 / Sats::ONE_BTC_U64 as f64)
}
}
impl std::fmt::Display for SatsSigned {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = itoa::Buffer::new();
let str = buf.format(self.0);
f.write_str(str)
}
}
impl Formattable for SatsSigned {
#[inline(always)]
fn may_need_escaping() -> bool {
false
}
}
File diff suppressed because it is too large Load Diff
+177 -79
View File
@@ -123,6 +123,9 @@ RawLockTime = int
# - $0.001 = 1 sat
# - $0.0001 = 0.1 sats (fractional)
SatsFract = float
# Signed satoshis (i64) - for values that can be negative.
# Used for changes, deltas, profit/loss calculations, etc.
SatsSigned = int
SemesterIndex = int
# Fixed-size boolean value optimized for on-disk storage (stored as u8)
StoredBool = int
@@ -1826,7 +1829,7 @@ class MetricPattern32(Generic[T]):
# Reusable structural pattern classes
class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern:
class AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
@@ -1836,7 +1839,6 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.adjusted_sopr_7d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'adjusted_sopr_7d_ema'))
self.adjusted_value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'adjusted_value_created'))
self.adjusted_value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'adjusted_value_destroyed'))
self.ath_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_ath_regret'))
self.cap_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'cap_raw'))
self.capitulation_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'capitulation_flow'))
self.investor_cap_raw: MetricPattern11[CentsSquaredSats] = MetricPattern11(client, _m(acc, 'investor_cap_raw'))
@@ -1848,10 +1850,13 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.mvrv: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'mvrv'))
self.neg_realized_loss: CumulativeSumPattern2[Dollars] = CumulativeSumPattern2(client, _m(acc, 'neg_realized_loss'))
self.net_realized_pnl: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl'))
self.net_realized_pnl_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_7d_ema'))
self.net_realized_pnl_cumulative_30d_delta: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_market_cap'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap'))
self.net_realized_pnl_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl_rel_to_realized_cap'))
self.peak_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_peak_regret'))
self.peak_regret_rel_to_realized_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'peak_regret_rel_to_realized_cap'))
self.profit_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_flow'))
self.profit_value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_created'))
self.profit_value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_destroyed'))
@@ -1860,16 +1865,22 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.realized_cap_cents: MetricPattern1[CentsUnsigned] = MetricPattern1(client, _m(acc, 'realized_cap_cents'))
self.realized_cap_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'realized_cap_rel_to_own_market_cap'))
self.realized_loss: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_loss'))
self.realized_loss_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_loss_7d_ema'))
self.realized_loss_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_loss_rel_to_realized_cap'))
self.realized_price: DollarsSatsPattern = DollarsSatsPattern(client, _m(acc, 'realized_price'))
self.realized_price_extra: RatioPattern = RatioPattern(client, _m(acc, 'realized_price_ratio'))
self.realized_profit: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_profit'))
self.realized_profit_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_profit_7d_ema'))
self.realized_profit_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_profit_rel_to_realized_cap'))
self.realized_profit_to_loss_ratio: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'realized_profit_to_loss_ratio'))
self.realized_value: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'realized_value'))
self.sell_side_risk_ratio: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio'))
self.sell_side_risk_ratio_30d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_30d_ema'))
self.sell_side_risk_ratio_7d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_7d_ema'))
self.sent_in_loss: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_loss'))
self.sent_in_loss_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_loss_14d_ema'))
self.sent_in_profit: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_profit'))
self.sent_in_profit_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_profit_14d_ema'))
self.sopr: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr'))
self.sopr_30d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_30d_ema'))
self.sopr_7d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_7d_ema'))
@@ -1877,7 +1888,7 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'value_created'))
self.value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'value_destroyed'))
class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2:
class AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
@@ -1887,7 +1898,6 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.adjusted_sopr_7d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'adjusted_sopr_7d_ema'))
self.adjusted_value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'adjusted_value_created'))
self.adjusted_value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'adjusted_value_destroyed'))
self.ath_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_ath_regret'))
self.cap_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'cap_raw'))
self.capitulation_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'capitulation_flow'))
self.investor_cap_raw: MetricPattern11[CentsSquaredSats] = MetricPattern11(client, _m(acc, 'investor_cap_raw'))
@@ -1899,10 +1909,13 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.mvrv: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'mvrv'))
self.neg_realized_loss: CumulativeSumPattern2[Dollars] = CumulativeSumPattern2(client, _m(acc, 'neg_realized_loss'))
self.net_realized_pnl: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl'))
self.net_realized_pnl_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_7d_ema'))
self.net_realized_pnl_cumulative_30d_delta: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_market_cap'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap'))
self.net_realized_pnl_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl_rel_to_realized_cap'))
self.peak_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_peak_regret'))
self.peak_regret_rel_to_realized_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'peak_regret_rel_to_realized_cap'))
self.profit_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_flow'))
self.profit_value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_created'))
self.profit_value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_destroyed'))
@@ -1910,15 +1923,21 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.realized_cap_30d_delta: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_cap_30d_delta'))
self.realized_cap_cents: MetricPattern1[CentsUnsigned] = MetricPattern1(client, _m(acc, 'realized_cap_cents'))
self.realized_loss: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_loss'))
self.realized_loss_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_loss_7d_ema'))
self.realized_loss_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_loss_rel_to_realized_cap'))
self.realized_price: DollarsSatsPattern = DollarsSatsPattern(client, _m(acc, 'realized_price'))
self.realized_price_extra: RatioPattern2 = RatioPattern2(client, _m(acc, 'realized_price_ratio'))
self.realized_profit: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_profit'))
self.realized_profit_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_profit_7d_ema'))
self.realized_profit_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_profit_rel_to_realized_cap'))
self.realized_value: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'realized_value'))
self.sell_side_risk_ratio: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio'))
self.sell_side_risk_ratio_30d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_30d_ema'))
self.sell_side_risk_ratio_7d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_7d_ema'))
self.sent_in_loss: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_loss'))
self.sent_in_loss_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_loss_14d_ema'))
self.sent_in_profit: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_profit'))
self.sent_in_profit_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_profit_14d_ema'))
self.sopr: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr'))
self.sopr_30d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_30d_ema'))
self.sopr_7d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_7d_ema'))
@@ -1926,12 +1945,11 @@ class AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTota
self.value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'value_created'))
self.value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'value_destroyed'))
class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2:
class CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.ath_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_ath_regret'))
self.cap_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'cap_raw'))
self.capitulation_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'capitulation_flow'))
self.investor_cap_raw: MetricPattern11[CentsSquaredSats] = MetricPattern11(client, _m(acc, 'investor_cap_raw'))
@@ -1943,10 +1961,13 @@ class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePa
self.mvrv: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'mvrv'))
self.neg_realized_loss: CumulativeSumPattern2[Dollars] = CumulativeSumPattern2(client, _m(acc, 'neg_realized_loss'))
self.net_realized_pnl: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl'))
self.net_realized_pnl_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_7d_ema'))
self.net_realized_pnl_cumulative_30d_delta: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_market_cap'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap'))
self.net_realized_pnl_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl_rel_to_realized_cap'))
self.peak_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_peak_regret'))
self.peak_regret_rel_to_realized_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'peak_regret_rel_to_realized_cap'))
self.profit_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_flow'))
self.profit_value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_created'))
self.profit_value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_destroyed'))
@@ -1955,16 +1976,22 @@ class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePa
self.realized_cap_cents: MetricPattern1[CentsUnsigned] = MetricPattern1(client, _m(acc, 'realized_cap_cents'))
self.realized_cap_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'realized_cap_rel_to_own_market_cap'))
self.realized_loss: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_loss'))
self.realized_loss_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_loss_7d_ema'))
self.realized_loss_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_loss_rel_to_realized_cap'))
self.realized_price: DollarsSatsPattern = DollarsSatsPattern(client, _m(acc, 'realized_price'))
self.realized_price_extra: RatioPattern = RatioPattern(client, _m(acc, 'realized_price_ratio'))
self.realized_profit: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_profit'))
self.realized_profit_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_profit_7d_ema'))
self.realized_profit_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_profit_rel_to_realized_cap'))
self.realized_profit_to_loss_ratio: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'realized_profit_to_loss_ratio'))
self.realized_value: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'realized_value'))
self.sell_side_risk_ratio: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio'))
self.sell_side_risk_ratio_30d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_30d_ema'))
self.sell_side_risk_ratio_7d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_7d_ema'))
self.sent_in_loss: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_loss'))
self.sent_in_loss_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_loss_14d_ema'))
self.sent_in_profit: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_profit'))
self.sent_in_profit_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_profit_14d_ema'))
self.sopr: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr'))
self.sopr_30d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_30d_ema'))
self.sopr_7d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_7d_ema'))
@@ -1972,12 +1999,11 @@ class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePa
self.value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'value_created'))
self.value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'value_destroyed'))
class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern:
class CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.ath_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_ath_regret'))
self.cap_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'cap_raw'))
self.capitulation_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'capitulation_flow'))
self.investor_cap_raw: MetricPattern11[CentsSquaredSats] = MetricPattern11(client, _m(acc, 'investor_cap_raw'))
@@ -1989,10 +2015,13 @@ class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePa
self.mvrv: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'mvrv'))
self.neg_realized_loss: CumulativeSumPattern2[Dollars] = CumulativeSumPattern2(client, _m(acc, 'neg_realized_loss'))
self.net_realized_pnl: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl'))
self.net_realized_pnl_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_7d_ema'))
self.net_realized_pnl_cumulative_30d_delta: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_market_cap'))
self.net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap'))
self.net_realized_pnl_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'net_realized_pnl_rel_to_realized_cap'))
self.peak_regret: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_peak_regret'))
self.peak_regret_rel_to_realized_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'peak_regret_rel_to_realized_cap'))
self.profit_flow: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_flow'))
self.profit_value_created: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_created'))
self.profit_value_destroyed: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'profit_value_destroyed'))
@@ -2000,15 +2029,21 @@ class AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePa
self.realized_cap_30d_delta: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_cap_30d_delta'))
self.realized_cap_cents: MetricPattern1[CentsUnsigned] = MetricPattern1(client, _m(acc, 'realized_cap_cents'))
self.realized_loss: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_loss'))
self.realized_loss_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_loss_7d_ema'))
self.realized_loss_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_loss_rel_to_realized_cap'))
self.realized_price: DollarsSatsPattern = DollarsSatsPattern(client, _m(acc, 'realized_price'))
self.realized_price_extra: RatioPattern2 = RatioPattern2(client, _m(acc, 'realized_price_ratio'))
self.realized_profit: CumulativeSumPattern[Dollars] = CumulativeSumPattern(client, _m(acc, 'realized_profit'))
self.realized_profit_7d_ema: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'realized_profit_7d_ema'))
self.realized_profit_rel_to_realized_cap: CumulativeSumPattern[StoredF32] = CumulativeSumPattern(client, _m(acc, 'realized_profit_rel_to_realized_cap'))
self.realized_value: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'realized_value'))
self.sell_side_risk_ratio: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio'))
self.sell_side_risk_ratio_30d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_30d_ema'))
self.sell_side_risk_ratio_7d_ema: MetricPattern6[StoredF32] = MetricPattern6(client, _m(acc, 'sell_side_risk_ratio_7d_ema'))
self.sent_in_loss: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_loss'))
self.sent_in_loss_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_loss_14d_ema'))
self.sent_in_profit: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent_in_profit'))
self.sent_in_profit_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_in_profit_14d_ema'))
self.sopr: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr'))
self.sopr_30d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_30d_ema'))
self.sopr_7d_ema: MetricPattern6[StoredF64] = MetricPattern6(client, _m(acc, 'sopr_7d_ema'))
@@ -2050,7 +2085,7 @@ class _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern:
self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma'))
self.zscore: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'zscore'))
class InvestedNegNetNuplSupplyUnrealizedPattern2:
class InvestedNegNetNuplSupplyUnrealizedPattern4:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
@@ -2072,6 +2107,7 @@ class InvestedNegNetNuplSupplyUnrealizedPattern2:
self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap'))
self.unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap'))
self.unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl'))
self.unrealized_peak_regret_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'unrealized_peak_regret_rel_to_market_cap'))
self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap'))
self.unrealized_profit_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap'))
self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl'))
@@ -2152,12 +2188,34 @@ class RatioPattern:
self.ratio_pct99_usd: DollarsSatsPattern2 = DollarsSatsPattern2(client, _m(acc, 'pct99_usd'))
self.ratio_sd: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern(client, acc)
class AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern:
class GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.greed_index: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'greed_index'))
self.invested_capital_in_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'invested_capital_in_loss'))
self.invested_capital_in_loss_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'invested_capital_in_loss_raw'))
self.invested_capital_in_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'invested_capital_in_profit'))
self.invested_capital_in_profit_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'invested_capital_in_profit_raw'))
self.investor_cap_in_loss_raw: MetricPattern11[CentsSquaredSats] = MetricPattern11(client, _m(acc, 'investor_cap_in_loss_raw'))
self.investor_cap_in_profit_raw: MetricPattern11[CentsSquaredSats] = MetricPattern11(client, _m(acc, 'investor_cap_in_profit_raw'))
self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss'))
self.net_sentiment: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_sentiment'))
self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl'))
self.pain_index: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'pain_index'))
self.peak_regret: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'unrealized_peak_regret'))
self.supply_in_loss: BitcoinDollarsSatsPattern4 = BitcoinDollarsSatsPattern4(client, _m(acc, 'supply_in_loss'))
self.supply_in_profit: BitcoinDollarsSatsPattern4 = BitcoinDollarsSatsPattern4(client, _m(acc, 'supply_in_profit'))
self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl'))
self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss'))
self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit'))
class GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.ath_regret: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_ath_regret'))
self.greed_index: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'greed_index'))
self.invested_capital_in_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'invested_capital_in_loss'))
self.invested_capital_in_loss_raw: MetricPattern11[CentsSats] = MetricPattern11(client, _m(acc, 'invested_capital_in_loss_raw'))
@@ -2196,6 +2254,25 @@ class _1m1w1y24hBlocksCoinbaseDaysDominanceFeeSubsidyPattern:
self.fee: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'fee'))
self.subsidy: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'subsidy'))
class InvestedNegNetNuplSupplyUnrealizedPattern3:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.invested_capital_in_loss_pct: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'invested_capital_in_loss_pct'))
self.invested_capital_in_profit_pct: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'invested_capital_in_profit_pct'))
self.neg_unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_market_cap'))
self.net_unrealized_pnl_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_market_cap'))
self.nupl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'nupl'))
self.supply_in_loss_rel_to_circulating_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_circulating_supply'))
self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply'))
self.supply_in_profit_rel_to_circulating_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_circulating_supply'))
self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply'))
self.supply_rel_to_circulating_supply: MetricPattern4[StoredF64] = MetricPattern4(client, _m(acc, 'supply_rel_to_circulating_supply'))
self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap'))
self.unrealized_peak_regret_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'unrealized_peak_regret_rel_to_market_cap'))
self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap'))
class _10y1m1w1y2y3m3y4y5y6m6y8yPattern3:
"""Pattern struct for repeated tree structure."""
@@ -2390,10 +2467,10 @@ class ActivityAddrCostOutputsRealizedRelativeSupplyUnrealizedPattern:
self.addr_count: MetricPattern1[StoredU64] = MetricPattern1(client, _m(acc, 'addr_count'))
self.cost_basis: MaxMinPattern = MaxMinPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern = AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern(client, acc)
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, acc)
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern = InvestedNegNetNuplSupplyUnrealizedPattern(client, acc)
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, _m(acc, 'supply'))
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
class _10y2y3y4y5y6y8yPattern:
"""Pattern struct for repeated tree structure."""
@@ -2416,10 +2493,10 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern:
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2 = AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2(client, acc)
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2 = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2(client, acc)
self.relative: InvestedNegNetSupplyUnrealizedPattern = InvestedNegNetSupplyUnrealizedPattern(client, acc)
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, _m(acc, 'supply'))
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(client, acc)
class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern5:
"""Pattern struct for repeated tree structure."""
@@ -2429,10 +2506,10 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern5:
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2 = AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2(client, acc)
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern = InvestedNegNetNuplSupplyUnrealizedPattern(client, acc)
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, _m(acc, 'supply'))
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
self.realized: AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2 = AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2(client, acc)
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern3 = InvestedNegNetNuplSupplyUnrealizedPattern3(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(client, acc)
class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4:
"""Pattern struct for repeated tree structure."""
@@ -2442,10 +2519,23 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4:
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern = AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern(client, acc)
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, acc)
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern = InvestedNegNetNuplSupplyUnrealizedPattern(client, acc)
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, _m(acc, 'supply'))
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, acc)
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern3 = InvestedNegNetNuplSupplyUnrealizedPattern3(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(client, acc)
class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3:
"""Pattern struct for repeated tree structure."""
@@ -2455,10 +2545,10 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3:
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern = AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern(client, acc)
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, acc)
self.relative: InvestedSupplyPattern = InvestedSupplyPattern(client, acc)
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, _m(acc, 'supply'))
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
class ActivityCostOutputsRealizedSupplyUnrealizedPattern:
"""Pattern struct for repeated tree structure."""
@@ -2468,9 +2558,9 @@ class ActivityCostOutputsRealizedSupplyUnrealizedPattern:
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern = AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern(client, acc)
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, _m(acc, 'supply'))
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, acc)
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, acc)
self.unrealized: GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, acc)
class BalanceBothReactivatedReceivingSendingPattern:
"""Pattern struct for repeated tree structure."""
@@ -2484,6 +2574,18 @@ class BalanceBothReactivatedReceivingSendingPattern:
self.receiving: AverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern[StoredU32] = AverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, _m(acc, 'receiving'))
self.sending: AverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern[StoredU32] = AverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, _m(acc, 'sending'))
class CoinblocksCoindaysSatblocksSatdaysSentPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.coinblocks_destroyed: CumulativeSumPattern[StoredF64] = CumulativeSumPattern(client, _m(acc, 'coinblocks_destroyed'))
self.coindays_destroyed: CumulativeSumPattern[StoredF64] = CumulativeSumPattern(client, _m(acc, 'coindays_destroyed'))
self.satblocks_destroyed: MetricPattern11[Sats] = MetricPattern11(client, _m(acc, 'satblocks_destroyed'))
self.satdays_destroyed: MetricPattern11[Sats] = MetricPattern11(client, _m(acc, 'satdays_destroyed'))
self.sent: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent'))
self.sent_14d_ema: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, 'sent_14d_ema'))
class InvestedMaxMinPercentilesSpotPattern:
"""Pattern struct for repeated tree structure."""
@@ -2496,17 +2598,6 @@ class InvestedMaxMinPercentilesSpotPattern:
self.spot_cost_basis_percentile: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'spot_cost_basis_percentile'))
self.spot_invested_capital_percentile: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'spot_invested_capital_percentile'))
class CoinblocksCoindaysSatblocksSatdaysSentPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.coinblocks_destroyed: CumulativeSumPattern[StoredF64] = CumulativeSumPattern(client, _m(acc, 'coinblocks_destroyed'))
self.coindays_destroyed: CumulativeSumPattern[StoredF64] = CumulativeSumPattern(client, _m(acc, 'coindays_destroyed'))
self.satblocks_destroyed: MetricPattern11[Sats] = MetricPattern11(client, _m(acc, 'satblocks_destroyed'))
self.satdays_destroyed: MetricPattern11[Sats] = MetricPattern11(client, _m(acc, 'satdays_destroyed'))
self.sent: BitcoinDollarsSatsPattern3 = BitcoinDollarsSatsPattern3(client, _m(acc, 'sent'))
class InvestedSupplyPattern:
"""Pattern struct for repeated tree structure."""
@@ -2527,6 +2618,15 @@ class CloseHighLowOpenPattern2(Generic[T]):
self.low: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'low'))
self.open: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'open'))
class _30dHalvedTotalPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self._30d_change: BitcoinDollarsSatsPattern5 = BitcoinDollarsSatsPattern5(client, _m(acc, '_30d_change'))
self.halved: BitcoinDollarsSatsPattern4 = BitcoinDollarsSatsPattern4(client, _m(acc, 'supply_halved'))
self.total: BitcoinDollarsSatsPattern4 = BitcoinDollarsSatsPattern4(client, _m(acc, 'supply'))
class BaseCumulativeSumPattern:
"""Pattern struct for repeated tree structure."""
@@ -2597,14 +2697,6 @@ class DollarsSatsPattern2:
self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, acc)
self.sats: MetricPattern4[SatsFract] = MetricPattern4(client, _m(acc, 'sats'))
class HalvedTotalPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.halved: BitcoinDollarsSatsPattern4 = BitcoinDollarsSatsPattern4(client, _m(acc, 'halved'))
self.total: BitcoinDollarsSatsPattern4 = BitcoinDollarsSatsPattern4(client, acc)
class MaxMinPattern:
"""Pattern struct for repeated tree structure."""
@@ -3822,22 +3914,28 @@ class MetricsTree_Distribution_UtxoCohorts_All_Relative:
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, 'supply_in_profit_rel_to_own_supply')
self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, 'supply_in_loss_rel_to_own_supply')
self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, 'unrealized_profit_rel_to_market_cap')
self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, 'unrealized_loss_rel_to_market_cap')
self.neg_unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, 'neg_unrealized_loss_rel_to_market_cap')
self.net_unrealized_pnl_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, 'net_unrealized_pnl_rel_to_market_cap')
self.nupl: MetricPattern1[StoredF32] = MetricPattern1(client, 'nupl')
self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, 'unrealized_profit_rel_to_own_total_unrealized_pnl')
self.unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, 'unrealized_loss_rel_to_own_total_unrealized_pnl')
self.neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl')
self.net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl')
self.invested_capital_in_profit_pct: MetricPattern1[StoredF32] = MetricPattern1(client, 'invested_capital_in_profit_pct')
self.invested_capital_in_loss_pct: MetricPattern1[StoredF32] = MetricPattern1(client, 'invested_capital_in_loss_pct')
self.unrealized_peak_regret_rel_to_market_cap: MetricPattern4[StoredF32] = MetricPattern4(client, 'unrealized_peak_regret_rel_to_market_cap')
class MetricsTree_Distribution_UtxoCohorts_All:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, 'supply')
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, '')
self.outputs: UtxoPattern = UtxoPattern(client, 'utxo_count')
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, '')
self.realized: AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern = AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern(client, '')
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, '')
self.realized: AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, '')
self.unrealized: GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(client, '')
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, '')
self.relative: MetricsTree_Distribution_UtxoCohorts_All_Relative = MetricsTree_Distribution_UtxoCohorts_All_Relative(client)
@@ -3904,24 +4002,24 @@ class MetricsTree_Distribution_UtxoCohorts_MinAge:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self._1d: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_1d_old')
self._1w: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_1w_old')
self._1m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_1m_old')
self._2m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_2m_old')
self._3m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_3m_old')
self._4m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_4m_old')
self._5m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_5m_old')
self._6m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_6m_old')
self._1y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_1y_old')
self._2y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_2y_old')
self._3y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_3y_old')
self._4y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_4y_old')
self._5y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_5y_old')
self._6y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_6y_old')
self._7y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_7y_old')
self._8y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_8y_old')
self._10y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_10y_old')
self._12y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4(client, 'utxos_over_12y_old')
self._1d: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_1d_old')
self._1w: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_1w_old')
self._1m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_1m_old')
self._2m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_2m_old')
self._3m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_3m_old')
self._4m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_4m_old')
self._5m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_5m_old')
self._6m: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_6m_old')
self._1y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_1y_old')
self._2y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_2y_old')
self._3y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_3y_old')
self._4y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_4y_old')
self._5y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_5y_old')
self._6y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_6y_old')
self._7y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_7y_old')
self._8y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_8y_old')
self._10y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_10y_old')
self._12y: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6(client, 'utxos_over_12y_old')
class MetricsTree_Distribution_UtxoCohorts_GeAmount:
"""Metrics tree node."""
@@ -3965,25 +4063,25 @@ class MetricsTree_Distribution_UtxoCohorts_Term_Short:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, 'sth_supply')
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, 'sth')
self.outputs: UtxoPattern = UtxoPattern(client, 'sth_utxo_count')
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, 'sth')
self.realized: AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern = AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern(client, 'sth')
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, 'sth')
self.realized: AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern = AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern(client, 'sth')
self.unrealized: GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(client, 'sth')
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, 'sth')
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern2 = InvestedNegNetNuplSupplyUnrealizedPattern2(client, 'sth')
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern4 = InvestedNegNetNuplSupplyUnrealizedPattern4(client, 'sth')
class MetricsTree_Distribution_UtxoCohorts_Term_Long:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.supply: HalvedTotalPattern = HalvedTotalPattern(client, 'lth_supply')
self.supply: _30dHalvedTotalPattern = _30dHalvedTotalPattern(client, 'lth')
self.outputs: UtxoPattern = UtxoPattern(client, 'lth_utxo_count')
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, 'lth')
self.realized: AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2 = AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2(client, 'lth')
self.unrealized: AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern = AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(client, 'lth')
self.realized: CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2 = CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2(client, 'lth')
self.unrealized: GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern = GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern(client, 'lth')
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, 'lth')
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern2 = InvestedNegNetNuplSupplyUnrealizedPattern2(client, 'lth')
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern4 = InvestedNegNetNuplSupplyUnrealizedPattern4(client, 'lth')
class MetricsTree_Distribution_UtxoCohorts_Term:
"""Metrics tree node."""
@@ -4175,7 +4273,7 @@ class MetricsTree_Distribution:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.chain_state: MetricPattern11[SupplyState] = MetricPattern11(client, 'chain')
self.supply_state: MetricPattern11[SupplyState] = MetricPattern11(client, 'supply_state')
self.any_address_indexes: MetricsTree_Distribution_AnyAddressIndexes = MetricsTree_Distribution_AnyAddressIndexes(client)
self.addresses_data: MetricsTree_Distribution_AddressesData = MetricsTree_Distribution_AddressesData(client)
self.utxo_cohorts: MetricsTree_Distribution_UtxoCohorts = MetricsTree_Distribution_UtxoCohorts(client)
+1
View File
@@ -1,3 +1,4 @@
*/**/*.md
!scripts/**/_*.js
*_old.js
_*.js
+11 -6
View File
@@ -79,6 +79,9 @@ const lineWidth = /** @type {any} */ (1.5);
export function createChart({ parent, id: chartId, brk, fitContent }) {
const baseUrl = brk.baseUrl.replace(/\/$/, "");
/** @type {string} */
let storageId = "";
/** @param {ChartableIndex} idx */
const getTimeEndpoint = (idx) =>
idx === "height"
@@ -500,7 +503,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
const active = createPersistedValue({
defaultValue: defaultActive ?? true,
storageKey: `${chartId}-p${paneIndex}-${key}`,
storageKey: `${storageId}-p${paneIndex}-${key}`,
urlKey: `${paneIndex === 0 ? "t" : "b"}-${key}`,
...serdeBool,
});
@@ -1275,12 +1278,12 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
* @param {Unit} unit
*/
function applyScaleForUnit(paneIndex, unit) {
const id = `${chartId}-scale`;
const defaultValue = unit.id === "usd" ? "log" : "lin";
const id = `${storageId}-scale`;
const defaultValue = paneIndex === 0 ? "log" : "lin";
const persisted = createPersistedValue({
defaultValue: /** @type {"lin" | "log"} */ (defaultValue),
storageKey: `${id}-${paneIndex}-${unit.id}`,
storageKey: `${storageId}-p${paneIndex}-scale`,
urlKey: paneIndex === 0 ? "price_scale" : "unit_scale",
serialize: (v) => v,
deserialize: (s) => /** @type {"lin" | "log"} */ (s),
@@ -1457,11 +1460,13 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
/**
* @param {Object} args
* @param {string} args.name
* @param {Map<Unit, AnyFetchedSeriesBlueprint[]>} args.top
* @param {Map<Unit, AnyFetchedSeriesBlueprint[]>} args.bottom
* @param {VoidFunction} [args.onDataLoaded]
*/
setBlueprints({ top, bottom, onDataLoaded }) {
setBlueprints({ name, top, bottom, onDataLoaded }) {
storageId = stringToId(name);
blueprints.panes[0].map = top;
blueprints.panes[1].map = bottom;
blueprints.onDataLoaded = onDataLoaded;
@@ -1481,7 +1486,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
.join(",");
const persistedUnit = createPersistedValue({
defaultValue: /** @type {string} */ (defaultUnit.id),
storageKey: `unit-${sortedUnitIds}`,
storageKey: `${storageId}-p${paneIndex}-unit`,
urlKey: paneIndex === 0 ? "u0" : "u1",
serialize: (v) => v,
deserialize: (s) => s,
+229 -19
View File
@@ -7,7 +7,7 @@
import { Unit } from "../../utils/units.js";
import { priceLine } from "../constants.js";
import { line, baseline, price } from "../series.js";
import { formatCohortTitle } from "../shared.js";
import { formatCohortTitle, satsBtcUsd } from "../shared.js";
import {
createSingleSupplySeries,
createGroupedSupplySection,
@@ -19,9 +19,6 @@ import {
createGroupedCoinblocksDestroyedSeries,
createGroupedCoindaysDestroyedSeries,
createSingleSentSeries,
createGroupedSentSatsSeries,
createGroupedSentBitcoinSeries,
createGroupedSentDollarsSeries,
groupedSupplyRelativeGenerators,
createSingleSupplyRelativeOptions,
createSingleSellSideRiskSeries,
@@ -139,13 +136,13 @@ export function createAddressCohortFolder(ctx, args) {
}),
),
},
...(!useGroupName
? createRealizedPnlSection(
...(useGroupName
? createGroupedRealizedPnlSection(ctx, list, title)
: createRealizedPnlSection(
ctx,
/** @type {AddressCohortObject} */ (args),
title,
)
: []),
)),
],
},
@@ -255,6 +252,12 @@ function createRealizedPnlSection(ctx, args, title) {
color: colors.green,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit7dEma,
name: "Profit 7d EMA",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit.cumulative,
name: "Profit Cumulative",
@@ -268,6 +271,12 @@ function createRealizedPnlSection(ctx, args, title) {
color: colors.red,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss7dEma,
name: "Loss 7d EMA",
color: colors.red,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss.cumulative,
name: "Loss Cumulative",
@@ -333,6 +342,11 @@ function createRealizedPnlSection(ctx, args, title) {
name: "Net",
unit: Unit.usd,
}),
baseline({
metric: realized.netRealizedPnl7dEma,
name: "Net 7d EMA",
unit: Unit.usd,
}),
baseline({
metric: realized.netRealizedPnl.cumulative,
name: "Net Cumulative",
@@ -419,6 +433,179 @@ 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 }),
],
},
{
name: "Sent In P/L",
tree: [
{
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 }),
],
},
{
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 }),
],
},
{
name: "In Profit 14d EMA",
title: title("Sent In Profit 14d EMA"),
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 }),
},
],
},
];
}
/**
* Create grouped realized P&L section for address cohorts (for compare view)
* @param {PartialContext} ctx
* @param {readonly AddressCohortObject[]} list
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
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 },
]);
return [
...pnlConfigs.map(({ name, sum, ema, rel, isNet }) => ({
name,
tree: [
{
name: "Sum",
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 }),
]),
priceLine({ ctx, unit: Unit.usd }),
],
},
{
name: "7d EMA",
title: title(`Realized ${name} 7d EMA`),
bottom: [
...list.map(({ color, name, tree }) =>
(isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }),
),
priceLine({ ctx, unit: Unit.usd }),
],
},
],
})),
{
name: "Peak Regret",
tree: [
{
name: "Sum",
title: title("Peak Regret"),
bottom: list.flatMap(({ color, name, tree }) => [
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 }),
]),
},
{
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 }),
]),
},
],
},
{
name: "Sent In P/L",
tree: [
{
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 }),
]),
},
{
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 }),
]),
},
{
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 }),
]),
},
{
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 }),
]),
},
{
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 }),
),
},
{
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 }),
),
},
],
},
];
}
@@ -620,6 +807,22 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
}),
]),
},
{
name: "Net Sentiment",
title: title("Net Sentiment"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({
metric: tree.unrealized.netSentiment,
name: useGroupName ? name : "Net Sentiment",
color: useGroupName ? color : undefined,
unit: Unit.usd,
}),
priceLine({
ctx,
unit: Unit.usd,
}),
]),
},
],
},
];
@@ -715,19 +918,26 @@ function createActivitySection(args, title) {
name: "Sent",
tree: [
{
name: "Sats",
title: title("Sent (Sats)"),
bottom: createGroupedSentSatsSeries(list),
name: "Sum",
title: title("Sent"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({
pattern: {
sats: tree.activity.sent.sats.sum,
bitcoin: tree.activity.sent.bitcoin.sum,
dollars: tree.activity.sent.dollars.sum,
},
name,
color,
}),
),
},
{
name: "Bitcoin",
title: title("Sent (BTC)"),
bottom: createGroupedSentBitcoinSeries(list),
},
{
name: "Dollars",
title: title("Sent ($)"),
bottom: createGroupedSentDollarsSeries(list),
name: "14d EMA",
title: title("Sent 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
),
},
],
},
@@ -12,6 +12,7 @@ export {
createCohortFolderWithAdjusted,
createCohortFolderWithNupl,
createCohortFolderAgeRange,
createCohortFolderMinAge,
createCohortFolderBasicWithMarketCap,
createCohortFolderBasicWithoutMarketCap,
createCohortFolderWithoutRelative,
+22 -82
View File
@@ -125,6 +125,7 @@ function createSingleSupplySeriesBase(ctx, cohort) {
return [
...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color: colors.default }),
...satsBtcUsd({ pattern: tree.supply._30dChange, name: "30d Change", color: colors.orange }),
...satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name: "In Profit", color: colors.green }),
...satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name: "In Loss", color: colors.red }),
...satsBtcUsd({ pattern: tree.supply.halved, name: "half", color: colors.gray }).map((s) => ({
@@ -261,6 +262,13 @@ export function createGroupedSupplySection(list, title, { supplyRelativeMetrics,
title: title("Supply"),
bottom: createGroupedSupplyTotalSeries(list, { relativeMetrics: supplyRelativeMetrics }),
},
{
name: "30d Change",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "In Profit",
title: title("Supply In Profit"),
@@ -692,57 +700,10 @@ export function createSingleSentSeries(cohort) {
unit: Unit.usd,
defaultActive: false,
}),
...satsBtcUsd({ pattern: tree.activity.sent14dEma, name: "14d EMA" }),
];
}
/**
* Create sent (sats) series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSentSatsSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.sent.sats.sum,
name,
color,
unit: Unit.sats,
}),
]);
}
/**
* Create sent (bitcoin) series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSentBitcoinSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.sent.bitcoin.sum,
name,
color,
unit: Unit.btc,
}),
]);
}
/**
* Create sent (dollars) series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSentDollarsSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.sent.dollars.sum,
name,
color,
unit: Unit.usd,
}),
]);
}
// ============================================================================
// Sell Side Risk Ratio Helpers
// ============================================================================
@@ -1036,11 +997,11 @@ export function createGroupedInvestorPriceFolder(list, title) {
}
// ============================================================================
// ATH Regret Helpers
// Peak Regret Helpers
// ============================================================================
/**
* Create realized ATH regret series for single cohort
* Create realized peak regret series for single cohort
* @param {{ realized: AnyRealizedPattern }} tree
* @param {Color} color
* @returns {AnyFetchedSeriesBlueprint[]}
@@ -1048,34 +1009,23 @@ export function createGroupedInvestorPriceFolder(list, title) {
export function createSingleRealizedAthRegretSeries(tree, color) {
return [
line({
metric: tree.realized.athRegret.sum,
name: "ATH Regret",
metric: tree.realized.peakRegret.sum,
name: "Peak Regret",
color,
unit: Unit.usd,
}),
line({
metric: tree.realized.athRegret.cumulative,
metric: tree.realized.peakRegret.cumulative,
name: "Cumulative",
color,
unit: Unit.usd,
defaultActive: false,
}),
];
}
/**
* Create unrealized ATH regret series for single cohort
* @param {{ unrealized: UnrealizedPattern }} tree
* @param {Color} color
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleUnrealizedAthRegretSeries(tree, color) {
return [
line({
metric: tree.unrealized.athRegret,
name: "ATH Regret",
baseline({
metric: tree.realized.peakRegretRelToRealizedCap,
name: "Rel. to Realized Cap",
color,
unit: Unit.usd,
unit: Unit.pctRcap,
}),
];
}
@@ -1088,26 +1038,16 @@ export function createSingleUnrealizedAthRegretSeries(tree, color) {
export function createGroupedRealizedAthRegretSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.realized.athRegret.sum,
metric: tree.realized.peakRegret.sum,
name,
color,
unit: Unit.usd,
}),
]);
}
/**
* Create unrealized ATH regret series for grouped cohorts
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedPattern } }[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedUnrealizedAthRegretSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.unrealized.athRegret,
baseline({
metric: tree.realized.peakRegretRelToRealizedCap,
name,
color,
unit: Unit.usd,
unit: Unit.pctRcap,
}),
]);
}
+440 -114
View File
@@ -46,9 +46,7 @@ import {
createInvestorPriceFolderBasic,
createGroupedInvestorPriceFolder,
createSingleRealizedAthRegretSeries,
createSingleUnrealizedAthRegretSeries,
createGroupedRealizedAthRegretSeries,
createGroupedUnrealizedAthRegretSeries,
createSingleSentimentSeries,
createGroupedNetSentimentSeries,
createGroupedGreedIndexSeries,
@@ -57,6 +55,7 @@ import {
import {
createPriceRatioCharts,
formatCohortTitle,
satsBtcUsd,
} from "../shared.js";
import { Unit } from "../../utils/units.js";
import { line, baseline, price } from "../series.js";
@@ -67,7 +66,7 @@ import { priceLine } from "../constants.js";
// ============================================================================
/**
* All folder: for the special "All" cohort (adjustedSopr + percentiles but no RelToMarketCap)
* All folder: for the special "All" cohort (adjustedSopr + percentiles + RelToMarketCap)
* @param {PartialContext} ctx
* @param {CohortAll} args
* @returns {PartialOptionsGroup}
@@ -266,7 +265,52 @@ export function createCohortFolderAgeRange(ctx, args) {
}
/**
* Basic folder WITH RelToMarketCap (minAge.*, geAmount.*, ltAmount.*, type.*)
* MinAge folder - has peakRegret in unrealized (minAge.*)
* @param {PartialContext} ctx
* @param {CohortMinAge | CohortGroupMinAge} args
* @returns {PartialOptionsGroup}
*/
export function createCohortFolderMinAge(ctx, args) {
if ("list" in args) {
const { list } = args;
const title = formatCohortTitle(args.title);
return {
name: args.name || "all",
tree: [
createGroupedSupplySection(
list,
title,
groupedSupplyRelativeGenerators,
),
createGroupedUtxoCountChart(list, title),
createGroupedRealizedSectionBasic(ctx, list, title),
createGroupedUnrealizedSectionMinAge(ctx, list, title),
createGroupedCostBasisSection({ list, title }),
createGroupedActivitySection({ list, title }),
],
};
}
const title = formatCohortTitle(args.name);
return {
name: args.name || "all",
tree: [
createSingleSupplyChart(
ctx,
args,
title,
createSingleSupplyRelativeOptions(ctx, args),
),
createSingleUtxoCountChart(args, title),
createSingleRealizedSectionBasic(ctx, args, title),
createSingleUnrealizedSectionMinAge(ctx, args, title),
createCostBasisSection(ctx, { cohort: args, title }),
createActivitySection({ ctx, cohort: args, title }),
],
};
}
/**
* Basic folder WITH RelToMarketCap (geAmount.*, ltAmount.*)
* @param {PartialContext} ctx
* @param {CohortBasicWithMarketCap | CohortGroupBasicWithMarketCap} args
* @returns {PartialOptionsGroup}
@@ -546,8 +590,8 @@ function createSingleRealizedSectionFull(ctx, cohort, title) {
extra: createRealizedPnlRatioSeries(colors, tree),
}),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionWithAdjusted(ctx, cohort, title),
@@ -576,8 +620,8 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) {
},
...createSingleRealizedPnlSection(ctx, cohort, title),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionWithAdjusted(ctx, cohort, title),
@@ -622,8 +666,8 @@ function createGroupedRealizedSectionWithAdjusted(
},
...createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics }),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createGroupedRealizedAthRegretSeries(list),
},
createGroupedSoprSectionWithAdjusted(list, title),
@@ -657,8 +701,8 @@ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) {
extra: createRealizedPnlRatioSeries(colors, tree),
}),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionBasic(ctx, cohort, title),
@@ -687,8 +731,8 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) {
},
...createSingleRealizedPnlSection(ctx, cohort, title),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionBasic(ctx, cohort, title),
@@ -733,8 +777,8 @@ function createGroupedRealizedSectionBasic(
},
...createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics }),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createGroupedRealizedAthRegretSeries(list),
},
createGroupedSoprSectionBasic(list, title),
@@ -953,12 +997,24 @@ function createSingleRealizedPnlSection(
title: "Profit",
sumColor: 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,
title: "Loss",
sumColor: 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,
@@ -1013,6 +1069,11 @@ function createSingleRealizedPnlSection(
unit: Unit.usd,
title: "Net",
}),
baseline({
metric: tree.realized.netRealizedPnl7dEma,
name: "Net 7d EMA",
unit: Unit.usd,
}),
baseline({
metric: tree.realized.netRealizedPnlCumulative30dDelta,
name: "Cumulative 30d Change",
@@ -1047,6 +1108,45 @@ function createSingleRealizedPnlSection(
priceLine({ ctx, unit: Unit.usd }),
],
},
{
name: "Sent In P/L",
tree: [
{
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 }),
],
},
{
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 }),
],
},
{
name: "In Profit 14d EMA",
title: title("Sent In Profit 14d EMA"),
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 }),
},
],
},
];
}
@@ -1066,85 +1166,46 @@ function createGroupedRealizedPnlSections(
title,
{ 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 },
]);
return [
{
name: "Profit",
title: title("Realized Profit"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
line({
metric: tree.realized.realizedProfit.sum,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
name,
color,
unit: Unit.pctRcap,
}),
]),
priceLine({ ctx, unit: Unit.usd }),
...pnlConfigs.map(({ name, sum, ema, rel, isNet }) => ({
name,
tree: [
{
name: "Sum",
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 }),
]),
priceLine({ ctx, unit: Unit.usd }),
],
},
{
name: "7d EMA",
title: title(`Realized ${name} 7d EMA`),
bottom: [
...list.map(({ color, name, tree }) =>
(isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }),
),
priceLine({ ctx, unit: Unit.usd }),
],
},
],
},
{
name: "Loss",
title: title("Realized Loss"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
line({
metric: tree.realized.realizedLoss.sum,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.realized.realizedLossRelToRealizedCap.sum,
name,
color,
unit: Unit.pctRcap,
}),
]),
priceLine({ ctx, unit: Unit.usd }),
],
},
})),
{
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,
}),
...(ratioMetrics ? ratioMetrics(cohort) : []),
]),
],
},
{
name: "Net P&L",
title: title("Net Realized P&L"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
baseline({
metric: tree.realized.netRealizedPnl.sum,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
name,
color,
unit: Unit.pctRcap,
}),
]),
priceLine({ ctx, unit: Unit.usd }),
priceLine({ ctx, unit: Unit.pctRcap }),
],
bottom: list.flatMap((cohort) => [
line({ metric: cohort.tree.realized.totalRealizedPnl, name: cohort.name, color: cohort.color, unit: Unit.usd }),
...(ratioMetrics ? ratioMetrics(cohort) : []),
]),
},
{
name: "Cumulative",
@@ -1222,6 +1283,61 @@ function createGroupedRealizedPnlSections(
},
],
},
{
name: "Sent In P/L",
tree: [
{
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 }),
]),
},
{
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 }),
]),
},
{
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 }),
]),
},
{
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 }),
]),
},
{
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 }),
),
},
{
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 }),
),
},
],
},
];
}
@@ -1670,6 +1786,56 @@ function createNuplChart(ctx, rel, title) {
};
}
/**
* Create peak regret chart (basic - just absolute value)
* @param {PartialContext} ctx
* @param {{ unrealized: UnrealizedFullPattern }} tree
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createPeakRegretChart(ctx, tree, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: [
line({
metric: tree.unrealized.peakRegret,
name: "Peak Regret",
color: ctx.colors.orange,
unit: Unit.usd,
}),
],
};
}
/**
* Create peak regret chart with RelToMarketCap metric
* @param {PartialContext} ctx
* @param {{ unrealized: UnrealizedFullPattern, relative: RelativeWithMarketCap }} tree
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createPeakRegretChartWithMarketCap(ctx, tree, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: [
line({
metric: tree.unrealized.peakRegret,
name: "Peak Regret",
color: ctx.colors.orange,
unit: Unit.usd,
}),
baseline({
metric: tree.relative.unrealizedPeakRegretRelToMarketCap,
name: "Peak Regret",
color: ctx.colors.orange,
unit: Unit.pctMcap,
}),
],
};
}
/**
* Create invested capital absolute chart
* @param {PartialContext} ctx
@@ -1773,6 +1939,54 @@ function createGroupedNuplChart(ctx, list, title) {
};
}
/**
* Create grouped peak regret chart (basic - no RelToMarketCap)
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedFullPattern } }[]} list
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createGroupedPeakRegretChartBasic(list, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
metric: tree.unrealized.peakRegret,
name,
color,
unit: Unit.usd,
}),
]),
};
}
/**
* Create grouped peak regret chart with RelToMarketCap metric
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedFullPattern, relative: RelativeWithMarketCap } }[]} list
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createGroupedPeakRegretChart(list, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
metric: tree.unrealized.peakRegret,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.relative.unrealizedPeakRegretRelToMarketCap,
name,
color,
unit: Unit.pctMcap,
}),
]),
};
}
// ============================================================================
// Unrealized Section Builder (generic, type-safe composition)
// ============================================================================
@@ -1826,11 +2040,6 @@ function createUnrealizedSection({
title: title("Market Sentiment"),
bottom: createSingleSentimentSeries(colors, tree),
},
{
name: "ATH Regret",
title: title("Unrealized ATH Regret"),
bottom: createSingleUnrealizedAthRegretSeries(tree, colors.orange),
},
...charts,
],
};
@@ -1968,11 +2177,6 @@ function createGroupedUnrealizedSection({
},
],
},
{
name: "ATH Regret",
title: title("Unrealized ATH Regret"),
bottom: createGroupedUnrealizedAthRegretSeries(list),
},
...charts,
],
};
@@ -2025,11 +2229,6 @@ function createGroupedUnrealizedSectionWithoutRelative(list, title) {
},
],
},
{
name: "ATH Regret",
title: title("Unrealized ATH Regret"),
bottom: createGroupedUnrealizedAthRegretSeries(list),
},
],
};
}
@@ -2050,9 +2249,21 @@ function createSingleUnrealizedSectionAll(ctx, cohort, title) {
ctx,
tree,
title,
pnl: createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
netPnl: createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
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),
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2081,7 +2292,10 @@ function createSingleUnrealizedSectionFull(ctx, cohort, title) {
priceLine({ ctx, unit: Unit.pctMcap }),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createNuplChart(ctx, tree.relative, title)],
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2106,7 +2320,10 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
priceLine({ ctx, unit: Unit.pctMcap }),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createNuplChart(ctx, tree.relative, title)],
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2135,6 +2352,34 @@ function createSingleUnrealizedSectionWithMarketCapOnly(ctx, cohort, title) {
});
}
/**
* Unrealized section for minAge cohorts (has peakRegret)
* @param {PartialContext} ctx
* @param {CohortMinAge} cohort
* @param {(metric: string) => string} title
*/
function createSingleUnrealizedSectionMinAge(ctx, cohort, title) {
const { tree } = cohort;
return createUnrealizedSection({
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),
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
/**
* Unrealized section with only base metrics (no RelToMarketCap)
* @param {PartialContext} ctx
@@ -2169,7 +2414,7 @@ function createSingleUnrealizedSectionWithoutRelative(ctx, cohort, title) {
/**
* Grouped unrealized base charts (profit, loss, total pnl)
* @param {readonly { color: Color, name: string, tree: { unrealized: PatternAll["unrealized"] } }[]} list
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedPattern } }[]} list
* @param {(metric: string) => string} title
*/
function createGroupedUnrealizedBaseCharts(list, title) {
@@ -2243,7 +2488,10 @@ function createGroupedUnrealizedSectionFull(ctx, list, title) {
unit: Unit.pctOwnPnl,
}),
],
charts: [createGroupedNuplChart(ctx, list, title)],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
@@ -2265,7 +2513,10 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
unit: Unit.pctMcap,
}),
],
charts: [createGroupedNuplChart(ctx, list, title)],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
@@ -2291,6 +2542,31 @@ function createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title) {
});
}
/**
* Grouped unrealized section for minAge cohorts (has peakRegret)
* @param {PartialContext} ctx
* @param {readonly CohortMinAge[]} list
* @param {(metric: string) => string} title
*/
function createGroupedUnrealizedSectionMinAge(ctx, list, title) {
return createGroupedUnrealizedSection({
list,
title,
netPnlMetrics: ({ color, name, tree }) => [
baseline({
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
name,
color,
unit: Unit.pctMcap,
}),
],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
/**
* Grouped unrealized section without RelToMarketCap (for CohortBasicWithoutMarketCap)
* @param {readonly CohortBasicWithoutMarketCap[]} list
@@ -2324,7 +2600,10 @@ function createSingleUnrealizedSectionWithNupl({ ctx, cohort, title }) {
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createNuplChart(ctx, tree.relative, title)],
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2359,7 +2638,10 @@ function createGroupedUnrealizedSectionWithNupl({ ctx, list, title }) {
unit: Unit.pctOwnPnl,
}),
],
charts: [createGroupedNuplChart(ctx, list, title)],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
@@ -2384,6 +2666,7 @@ function createSingleUnrealizedSectionAgeRange(ctx, cohort, title) {
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createPeakRegretChart(ctx, tree, title)],
});
}
@@ -2410,6 +2693,7 @@ function createGroupedUnrealizedSectionAgeRange(list, title) {
unit: Unit.pctOwnPnl,
}),
],
charts: [createGroupedPeakRegretChartBasic(list, title)],
});
}
@@ -2632,6 +2916,21 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
unit: Unit.usd,
sumColor: 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,
}),
],
},
{
@@ -2768,6 +3067,33 @@ function createGroupedActivitySection({ list, title, valueTree }) {
return {
name: "Activity",
tree: [
{
name: "Sent",
tree: [
{
name: "Sum",
title: title("Sent"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({
pattern: {
sats: tree.activity.sent.sats.sum,
bitcoin: tree.activity.sent.bitcoin.sum,
dollars: tree.activity.sent.dollars.sum,
},
name,
color,
}),
),
},
{
name: "14d EMA",
title: title("Sent 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
),
},
],
},
{
name: "Sell Side Risk",
title: title("Sell Side Risk Ratio"),
+7 -1
View File
@@ -3,7 +3,8 @@ import { createButtonElement, createAnchorElement } from "../utils/dom.js";
import { pushHistory, resetParams } from "../utils/url.js";
import { readStored, writeToStorage } from "../utils/storage.js";
import { stringToId } from "../utils/format.js";
import { collect, markUsed, logUnused } from "./unused.js";
import { collect, markUsed, logUnused, extractTreeStructure } from "./unused.js";
import { localhost } from "../utils/env.js";
import { setQr } from "../panes/share.js";
import { getConstant } from "./constants.js";
import { colors } from "../chart/colors.js";
@@ -29,6 +30,11 @@ export function initOptions(brk) {
brk,
});
// Log tree structure for analysis (localhost only)
if (localhost) {
console.log(extractTreeStructure(partialOptions));
}
/** @type {Option[]} */
const list = [];
+5 -2
View File
@@ -8,6 +8,7 @@ import {
createCohortFolderWithAdjusted,
createCohortFolderWithNupl,
createCohortFolderAgeRange,
createCohortFolderMinAge,
createCohortFolderBasicWithMarketCap,
createCohortFolderBasicWithoutMarketCap,
createCohortFolderWithoutRelative,
@@ -62,6 +63,8 @@ export function createPartialOptions({ brk }) {
/** @param {CohortBasicWithMarketCap} cohort */
const mapBasicWithMarketCap = (cohort) =>
createCohortFolderBasicWithMarketCap(ctx, cohort);
/** @param {CohortMinAge} cohort */
const mapMinAge = (cohort) => createCohortFolderMinAge(ctx, cohort);
/** @param {CohortBasicWithoutMarketCap} cohort */
const mapBasicWithoutMarketCap = (cohort) =>
createCohortFolderBasicWithoutMarketCap(ctx, cohort);
@@ -135,12 +138,12 @@ export function createPartialOptions({ brk }) {
{
name: "Older Than",
tree: [
createCohortFolderBasicWithMarketCap(ctx, {
createCohortFolderMinAge(ctx, {
name: "Compare",
title: "Min Age",
list: fromDate,
}),
...fromDate.map(mapBasicWithMarketCap),
...fromDate.map(mapMinAge),
],
},
// Range
+13 -1
View File
@@ -214,13 +214,20 @@
* @property {Color} color
* @property {AgeRangePattern} tree
*
* Basic cohort WITH RelToMarketCap (minAge.*, geAmount.*, ltAmount.*)
* Basic cohort WITH RelToMarketCap (geAmount.*, ltAmount.*)
* @typedef {Object} CohortBasicWithMarketCap
* @property {string} name
* @property {string} title
* @property {Color} color
* @property {PatternBasicWithMarketCap} tree
*
* MinAge cohort - has peakRegret in unrealized (minAge.*)
* @typedef {Object} CohortMinAge
* @property {string} name
* @property {string} title
* @property {Color} color
* @property {MinAgePattern} tree
*
* Basic cohort WITHOUT RelToMarketCap (epoch.*, amountRange.*, year.*, type.*)
* @typedef {Object} CohortBasicWithoutMarketCap
* @property {string} name
@@ -279,6 +286,11 @@
* @property {string} title
* @property {readonly CohortBasicWithMarketCap[]} list
*
* @typedef {Object} CohortGroupMinAge
* @property {string} name
* @property {string} title
* @property {readonly CohortMinAge[]} list
*
* @typedef {Object} CohortGroupBasicWithoutMarketCap
* @property {string} name
* @property {string} title
+79 -10
View File
@@ -52,16 +52,6 @@ function walk(node, map, path) {
(kn.startsWith("_") && kn.endsWith("start"))
)
continue;
// if (
// kn === "mvrv" ||
// kn.endsWith("index") ||
// kn.endsWith("indexes") ||
// kn.endsWith("start") ||
// kn.endsWith("hash") ||
// kn.endsWith("data") ||
// kn.endsWith("constants")
// )
// return;
walk(/** @type {TreeNode | null | undefined} */ (value), map, [
...path,
key,
@@ -108,3 +98,82 @@ export function logUnused() {
console.log("Unused metrics:", { count: unused.size, tree });
}
/**
* Extract tree structure from partial options (names + hierarchy, series grouped by unit)
* @param {PartialOptionsTree} options
* @returns {object[]}
*/
export function extractTreeStructure(options) {
/**
* Group series by unit
* @param {(AnyFetchedSeriesBlueprint | FetchedPriceSeriesBlueprint)[]} series
* @param {boolean} isTop
* @returns {Record<string, string[]>}
*/
function groupByUnit(series, isTop) {
/** @type {Record<string, string[]>} */
const grouped = {};
for (const s of series) {
// Price patterns in top pane have dollars/sats sub-metrics
const metric = /** @type {any} */ (s.metric);
if (isTop && metric?.dollars && metric?.sats) {
const title = s.title || s.key || "unnamed";
(grouped["USD"] ??= []).push(title);
(grouped["sats"] ??= []).push(title);
} else {
const unit = /** @type {AnyFetchedSeriesBlueprint} */ (s).unit;
const unitName = unit?.name || "unknown";
const title = s.title || s.key || "unnamed";
(grouped[unitName] ??= []).push(title);
}
}
return grouped;
}
/**
* @param {AnyPartialOption | PartialOptionsGroup} node
* @returns {object}
*/
function processNode(node) {
// Group with children
if ("tree" in node && node.tree) {
return {
name: node.name,
children: node.tree.map(processNode),
};
}
// Chart option
if ("top" in node || "bottom" in node) {
const chartNode = /** @type {PartialChartOption} */ (node);
const top = chartNode.top ? groupByUnit(chartNode.top, true) : undefined;
const bottom = chartNode.bottom
? groupByUnit(chartNode.bottom, false)
: undefined;
return {
name: node.name,
title: chartNode.title,
...(top && Object.keys(top).length > 0 ? { top } : {}),
...(bottom && Object.keys(bottom).length > 0 ? { bottom } : {}),
};
}
// URL option
if ("url" in node) {
return { name: node.name, url: true };
}
// Other options (explorer, table, simulation)
return { name: node.name };
}
return options.map(processNode);
}
/**
* Log the options tree structure to console (localhost only)
* @param {PartialOptionsTree} options
*/
export function logTreeStructure(options) {
if (!localhost) return;
const structure = extractTreeStructure(options);
console.log("Options tree structure:", JSON.stringify(structure, null, 2));
}
+1
View File
@@ -103,6 +103,7 @@ export function init(brk) {
setChoices(computeChoices(opt));
blueprints = chart.setBlueprints({
name: opt.title,
top: buildTopBlueprints(opt.top),
bottom: opt.bottom,
onDataLoaded: updatePriceWithLatest,
+9 -6
View File
@@ -14,7 +14,7 @@
*
* @import { WebSockets } from "./utils/ws.js"
*
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern } from "./options/partial.js"
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortMinAge, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupMinAge, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern } from "./options/partial.js"
*
*
* @import { UnitObject as Unit } from "./utils/units.js"
@@ -49,6 +49,8 @@
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3} UtxoAmountPattern
* @typedef {Brk.ActivityAddrCostOutputsRealizedRelativeSupplyUnrealizedPattern} AddressAmountPattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4} BasicUtxoPattern
* MinAgePattern: minAge cohorts have peakRegret in unrealized (Pattern6)
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern6} MinAgePattern
* @typedef {Brk.ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3} EpochPattern
* @typedef {Brk.ActivityCostOutputsRealizedSupplyUnrealizedPattern} EmptyPattern
* @typedef {Brk._0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern} Ratio1ySdPattern
@@ -75,13 +77,14 @@
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern} GlobalRelativePattern
* @typedef {Brk.InvestedNegNetSupplyUnrealizedPattern} OwnRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern2} FullRelativePattern
* @typedef {Brk.AthGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern} UnrealizedPattern
* @typedef {Brk.GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern} UnrealizedPattern
* @typedef {Brk.GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern} UnrealizedFullPattern
*
* Realized patterns
* @typedef {Brk.AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern} RealizedPattern
* @typedef {Brk.AthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2} RealizedPattern2
* @typedef {Brk.AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern} RealizedPattern3
* @typedef {Brk.AdjustedAthCapCapitulationInvestorLossMvrvNegNetProfitRealizedSellSoprTotalValuePattern2} RealizedPattern4
* @typedef {Brk.CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern} RealizedPattern
* @typedef {Brk.CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern2} RealizedPattern2
* @typedef {Brk.AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern} RealizedPattern3
* @typedef {Brk.AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern2} RealizedPattern4
*/
/**