mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 14:11:56 -07:00
global: snapshot
This commit is contained in:
Generated
-6
@@ -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
@@ -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)
|
||||
|
||||
+348
-158
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();
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+381
-176
File diff suppressed because it is too large
Load Diff
@@ -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,3 +1,4 @@
|
||||
*/**/*.md
|
||||
!scripts/**/_*.js
|
||||
*_old.js
|
||||
_*.js
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user