global: snapshot

This commit is contained in:
nym21
2026-03-14 18:27:25 +01:00
parent 9d365f4bbb
commit 0d177494d9
55 changed files with 1117 additions and 1006 deletions
+90 -90
View File
@@ -1596,7 +1596,7 @@ pub struct BaseChangeCumulativeDeltaRelSumPattern {
pub base: CentsUsdPattern,
pub change_1m: RelPattern,
pub cumulative: CentsUsdPattern,
pub delta: ChangeRatePattern2,
pub delta: AbsoluteRatePattern2,
pub rel_to_rcap: BpsPercentRatioPattern,
pub sum: _1m1w1y24hPattern3,
}
@@ -1608,7 +1608,7 @@ impl BaseChangeCumulativeDeltaRelSumPattern {
base: CentsUsdPattern::new(client.clone(), _m(&acc, "realized_pnl")),
change_1m: RelPattern::new(client.clone(), _m(&acc, "pnl_change_1m_rel_to")),
cumulative: CentsUsdPattern::new(client.clone(), _m(&acc, "realized_pnl_cumulative")),
delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "realized_pnl_delta")),
delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "realized_pnl_delta")),
rel_to_rcap: BpsPercentRatioPattern::new(client.clone(), _m(&acc, "realized_pnl_rel_to_rcap")),
sum: _1m1w1y24hPattern3::new(client.clone(), _m(&acc, "realized_pnl_sum")),
}
@@ -1713,7 +1713,7 @@ impl CapLossMvrvPriceProfitSoprPattern {
/// Pattern struct for repeated tree structure.
pub struct DeltaHalfInRelTotalPattern {
pub delta: ChangeRatePattern,
pub delta: AbsoluteRatePattern,
pub half: BtcCentsSatsUsdPattern,
pub in_loss: BtcCentsRelSatsUsdPattern,
pub in_profit: BtcCentsRelSatsUsdPattern,
@@ -1725,7 +1725,7 @@ impl DeltaHalfInRelTotalPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")),
half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")),
in_loss: BtcCentsRelSatsUsdPattern::new(client.clone(), _m(&acc, "in_loss")),
in_profit: BtcCentsRelSatsUsdPattern::new(client.clone(), _m(&acc, "in_profit")),
@@ -1737,7 +1737,7 @@ impl DeltaHalfInRelTotalPattern {
/// Pattern struct for repeated tree structure.
pub struct DeltaHalfInRelTotalPattern2 {
pub delta: ChangeRatePattern,
pub delta: AbsoluteRatePattern,
pub half: BtcCentsSatsUsdPattern,
pub in_loss: BtcCentsRelSatsUsdPattern3,
pub in_profit: BtcCentsRelSatsUsdPattern3,
@@ -1749,7 +1749,7 @@ impl DeltaHalfInRelTotalPattern2 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")),
half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")),
in_loss: BtcCentsRelSatsUsdPattern3::new(client.clone(), _m(&acc, "in_loss")),
in_profit: BtcCentsRelSatsUsdPattern3::new(client.clone(), _m(&acc, "in_profit")),
@@ -1917,7 +1917,7 @@ impl BtcCentsRelSatsUsdPattern2 {
/// Pattern struct for repeated tree structure.
pub struct DeltaHalfInTotalPattern2 {
pub delta: ChangeRatePattern,
pub delta: AbsoluteRatePattern,
pub half: BtcCentsSatsUsdPattern,
pub in_loss: BtcCentsSatsUsdPattern,
pub in_profit: BtcCentsSatsUsdPattern,
@@ -1928,7 +1928,7 @@ impl DeltaHalfInTotalPattern2 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")),
half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")),
in_loss: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "in_loss")),
in_profit: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "in_profit")),
@@ -2189,7 +2189,7 @@ impl AdjustedRatioValuePattern {
pub struct BaseCumulativeDeltaSumPattern {
pub base: CentsUsdPattern,
pub cumulative: CentsUsdPattern,
pub delta: ChangeRatePattern2,
pub delta: AbsoluteRatePattern2,
pub sum: _1m1w1y24hPattern3,
}
@@ -2199,7 +2199,7 @@ impl BaseCumulativeDeltaSumPattern {
Self {
base: CentsUsdPattern::new(client.clone(), acc.clone()),
cumulative: CentsUsdPattern::new(client.clone(), _m(&acc, "cumulative")),
delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "delta")),
sum: _1m1w1y24hPattern3::new(client.clone(), _m(&acc, "sum")),
}
}
@@ -2268,7 +2268,7 @@ impl BtcCentsSatsUsdPattern {
/// Pattern struct for repeated tree structure.
pub struct CentsDeltaRelUsdPattern {
pub cents: MetricPattern1<Cents>,
pub delta: ChangeRatePattern2,
pub delta: AbsoluteRatePattern2,
pub rel_to_own_mcap: BpsPercentRatioPattern4,
pub usd: MetricPattern1<Dollars>,
}
@@ -2278,7 +2278,7 @@ impl CentsDeltaRelUsdPattern {
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), _m(&acc, "cents")),
delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "delta")),
rel_to_own_mcap: BpsPercentRatioPattern4::new(client.clone(), _m(&acc, "rel_to_own_mcap")),
usd: MetricPattern1::new(client.clone(), acc.clone()),
}
@@ -2606,7 +2606,7 @@ impl CentsSatsUsdPattern3 {
/// Pattern struct for repeated tree structure.
pub struct CentsDeltaUsdPattern {
pub cents: MetricPattern1<Cents>,
pub delta: ChangeRatePattern2,
pub delta: AbsoluteRatePattern2,
pub usd: MetricPattern1<Dollars>,
}
@@ -2615,7 +2615,7 @@ impl CentsDeltaUsdPattern {
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), _m(&acc, "cents")),
delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "delta")),
usd: MetricPattern1::new(client.clone(), acc.clone()),
}
}
@@ -2641,7 +2641,7 @@ impl CentsSatsUsdPattern {
/// Pattern struct for repeated tree structure.
pub struct DeltaHalfTotalPattern {
pub delta: ChangeRatePattern,
pub delta: AbsoluteRatePattern,
pub half: BtcCentsSatsUsdPattern,
pub total: BtcCentsSatsUsdPattern,
}
@@ -2650,7 +2650,7 @@ impl DeltaHalfTotalPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")),
half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")),
total: BtcCentsSatsUsdPattern::new(client.clone(), acc.clone()),
}
@@ -2783,6 +2783,38 @@ impl<T: DeserializeOwned> BaseCumulativeSumPattern<T> {
}
}
/// Pattern struct for repeated tree structure.
pub struct AbsoluteRatePattern {
pub absolute: _1m1w1y24hPattern<StoredI64>,
pub rate: _1m1w1y24hPattern2,
}
impl AbsoluteRatePattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
absolute: _1m1w1y24hPattern::new(client.clone(), acc.clone()),
rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct AbsoluteRatePattern2 {
pub absolute: _1m1w1y24hPattern3,
pub rate: _1m1w1y24hPattern2,
}
impl AbsoluteRatePattern2 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
absolute: _1m1w1y24hPattern3::new(client.clone(), acc.clone()),
rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct BlocksDominancePattern {
pub blocks_mined: BaseCumulativeSumPattern2,
@@ -2863,38 +2895,6 @@ impl CentsUsdPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct ChangeRatePattern {
pub change: _1m1w1y24hPattern<StoredI64>,
pub rate: _1m1w1y24hPattern2,
}
impl ChangeRatePattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
change: _1m1w1y24hPattern::new(client.clone(), acc.clone()),
rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct ChangeRatePattern2 {
pub change: _1m1w1y24hPattern3,
pub rate: _1m1w1y24hPattern2,
}
impl ChangeRatePattern2 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
change: _1m1w1y24hPattern3::new(client.clone(), acc.clone()),
rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct CoindaysSentPattern {
pub coindays_destroyed: BaseCumulativeSumPattern<StoredF64>,
@@ -2913,7 +2913,7 @@ impl CoindaysSentPattern {
/// Pattern struct for repeated tree structure.
pub struct DeltaInnerPattern {
pub delta: ChangeRatePattern,
pub delta: AbsoluteRatePattern,
pub inner: MetricPattern1<StoredU64>,
}
@@ -2921,7 +2921,7 @@ impl DeltaInnerPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")),
delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")),
inner: MetricPattern1::new(client.clone(), acc.clone()),
}
}
@@ -3937,29 +3937,29 @@ impl MetricsTree_Addresses_New {
/// Metrics tree node.
pub struct MetricsTree_Addresses_Delta {
pub all: ChangeRatePattern,
pub p2pk65: ChangeRatePattern,
pub p2pk33: ChangeRatePattern,
pub p2pkh: ChangeRatePattern,
pub p2sh: ChangeRatePattern,
pub p2wpkh: ChangeRatePattern,
pub p2wsh: ChangeRatePattern,
pub p2tr: ChangeRatePattern,
pub p2a: ChangeRatePattern,
pub all: AbsoluteRatePattern,
pub p2pk65: AbsoluteRatePattern,
pub p2pk33: AbsoluteRatePattern,
pub p2pkh: AbsoluteRatePattern,
pub p2sh: AbsoluteRatePattern,
pub p2wpkh: AbsoluteRatePattern,
pub p2wsh: AbsoluteRatePattern,
pub p2tr: AbsoluteRatePattern,
pub p2a: AbsoluteRatePattern,
}
impl MetricsTree_Addresses_Delta {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
all: ChangeRatePattern::new(client.clone(), "address_count".to_string()),
p2pk65: ChangeRatePattern::new(client.clone(), "p2pk65_address_count".to_string()),
p2pk33: ChangeRatePattern::new(client.clone(), "p2pk33_address_count".to_string()),
p2pkh: ChangeRatePattern::new(client.clone(), "p2pkh_address_count".to_string()),
p2sh: ChangeRatePattern::new(client.clone(), "p2sh_address_count".to_string()),
p2wpkh: ChangeRatePattern::new(client.clone(), "p2wpkh_address_count".to_string()),
p2wsh: ChangeRatePattern::new(client.clone(), "p2wsh_address_count".to_string()),
p2tr: ChangeRatePattern::new(client.clone(), "p2tr_address_count".to_string()),
p2a: ChangeRatePattern::new(client.clone(), "p2a_address_count".to_string()),
all: AbsoluteRatePattern::new(client.clone(), "address_count".to_string()),
p2pk65: AbsoluteRatePattern::new(client.clone(), "p2pk65_address_count".to_string()),
p2pk33: AbsoluteRatePattern::new(client.clone(), "p2pk33_address_count".to_string()),
p2pkh: AbsoluteRatePattern::new(client.clone(), "p2pkh_address_count".to_string()),
p2sh: AbsoluteRatePattern::new(client.clone(), "p2sh_address_count".to_string()),
p2wpkh: AbsoluteRatePattern::new(client.clone(), "p2wpkh_address_count".to_string()),
p2wsh: AbsoluteRatePattern::new(client.clone(), "p2wsh_address_count".to_string()),
p2tr: AbsoluteRatePattern::new(client.clone(), "p2tr_address_count".to_string()),
p2a: AbsoluteRatePattern::new(client.clone(), "p2a_address_count".to_string()),
}
}
}
@@ -4435,16 +4435,16 @@ impl MetricsTree_Cointime_Prices {
/// Metrics tree node.
pub struct MetricsTree_Cointime_Adjusted {
pub inflation_rate: BpsPercentRatioPattern,
pub tx_velocity_btc: MetricPattern1<StoredF64>,
pub tx_velocity_usd: MetricPattern1<StoredF64>,
pub tx_velocity_native: MetricPattern1<StoredF64>,
pub tx_velocity_fiat: MetricPattern1<StoredF64>,
}
impl MetricsTree_Cointime_Adjusted {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
inflation_rate: BpsPercentRatioPattern::new(client.clone(), "cointime_adj_inflation_rate".to_string()),
tx_velocity_btc: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity_btc".to_string()),
tx_velocity_usd: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity_usd".to_string()),
tx_velocity_native: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity".to_string()),
tx_velocity_fiat: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity_fiat".to_string()),
}
}
}
@@ -5434,8 +5434,8 @@ impl MetricsTree_Market_MovingAverage_Sma {
/// Metrics tree node.
pub struct MetricsTree_Market_MovingAverage_Sma_200d {
pub cents: MetricPattern1<Cents>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<Cents>,
pub sats: MetricPattern1<SatsFract>,
pub bps: MetricPattern1<BasisPoints32>,
pub ratio: MetricPattern1<StoredF32>,
@@ -5446,8 +5446,8 @@ pub struct MetricsTree_Market_MovingAverage_Sma_200d {
impl MetricsTree_Market_MovingAverage_Sma_200d {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "price_sma_200d_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "price_sma_200d".to_string()),
cents: MetricPattern1::new(client.clone(), "price_sma_200d_cents".to_string()),
sats: MetricPattern1::new(client.clone(), "price_sma_200d_sats".to_string()),
bps: MetricPattern1::new(client.clone(), "price_sma_200d_ratio_bps".to_string()),
ratio: MetricPattern1::new(client.clone(), "price_sma_200d_ratio".to_string()),
@@ -5459,16 +5459,16 @@ impl MetricsTree_Market_MovingAverage_Sma_200d {
/// Metrics tree node.
pub struct MetricsTree_Market_MovingAverage_Sma_200d_X24 {
pub cents: MetricPattern1<Cents>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<Cents>,
pub sats: MetricPattern1<SatsFract>,
}
impl MetricsTree_Market_MovingAverage_Sma_200d_X24 {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_usd".to_string()),
cents: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_cents".to_string()),
sats: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_sats".to_string()),
}
}
@@ -5476,16 +5476,16 @@ impl MetricsTree_Market_MovingAverage_Sma_200d_X24 {
/// Metrics tree node.
pub struct MetricsTree_Market_MovingAverage_Sma_200d_X08 {
pub cents: MetricPattern1<Cents>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<Cents>,
pub sats: MetricPattern1<SatsFract>,
}
impl MetricsTree_Market_MovingAverage_Sma_200d_X08 {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_usd".to_string()),
cents: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_cents".to_string()),
sats: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_sats".to_string()),
}
}
@@ -5493,8 +5493,8 @@ impl MetricsTree_Market_MovingAverage_Sma_200d_X08 {
/// Metrics tree node.
pub struct MetricsTree_Market_MovingAverage_Sma_350d {
pub cents: MetricPattern1<Cents>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<Cents>,
pub sats: MetricPattern1<SatsFract>,
pub bps: MetricPattern1<BasisPoints32>,
pub ratio: MetricPattern1<StoredF32>,
@@ -5504,8 +5504,8 @@ pub struct MetricsTree_Market_MovingAverage_Sma_350d {
impl MetricsTree_Market_MovingAverage_Sma_350d {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "price_sma_350d_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "price_sma_350d".to_string()),
cents: MetricPattern1::new(client.clone(), "price_sma_350d_cents".to_string()),
sats: MetricPattern1::new(client.clone(), "price_sma_350d_sats".to_string()),
bps: MetricPattern1::new(client.clone(), "price_sma_350d_ratio_bps".to_string()),
ratio: MetricPattern1::new(client.clone(), "price_sma_350d_ratio".to_string()),
@@ -5516,16 +5516,16 @@ impl MetricsTree_Market_MovingAverage_Sma_350d {
/// Metrics tree node.
pub struct MetricsTree_Market_MovingAverage_Sma_350d_X2 {
pub cents: MetricPattern1<Cents>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<Cents>,
pub sats: MetricPattern1<SatsFract>,
}
impl MetricsTree_Market_MovingAverage_Sma_350d_X2 {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "price_sma_350d_x2_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "price_sma_350d_x2_usd".to_string()),
cents: MetricPattern1::new(client.clone(), "price_sma_350d_x2_cents".to_string()),
sats: MetricPattern1::new(client.clone(), "price_sma_350d_x2_sats".to_string()),
}
}
@@ -6387,16 +6387,16 @@ impl MetricsTree_Prices_Split {
/// Metrics tree node.
pub struct MetricsTree_Prices_Ohlc {
pub cents: MetricPattern2<OHLCCents>,
pub usd: MetricPattern2<OHLCDollars>,
pub cents: MetricPattern2<OHLCCents>,
pub sats: MetricPattern2<OHLCSats>,
}
impl MetricsTree_Prices_Ohlc {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern2::new(client.clone(), "price_ohlc_cents".to_string()),
usd: MetricPattern2::new(client.clone(), "price_ohlc".to_string()),
cents: MetricPattern2::new(client.clone(), "price_ohlc_cents".to_string()),
sats: MetricPattern2::new(client.clone(), "price_ohlc_sats".to_string()),
}
}
@@ -6404,16 +6404,16 @@ impl MetricsTree_Prices_Ohlc {
/// Metrics tree node.
pub struct MetricsTree_Prices_Spot {
pub cents: MetricPattern1<Cents>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<Cents>,
pub sats: MetricPattern1<Sats>,
}
impl MetricsTree_Prices_Spot {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "price_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "price".to_string()),
cents: MetricPattern1::new(client.clone(), "price_cents".to_string()),
sats: MetricPattern1::new(client.clone(), "price_sats".to_string()),
}
}
@@ -6557,7 +6557,7 @@ impl MetricsTree_Cohorts_Utxo_All {
pub struct MetricsTree_Cohorts_Utxo_All_Supply {
pub total: BtcCentsSatsUsdPattern,
pub half: BtcCentsSatsUsdPattern,
pub delta: ChangeRatePattern,
pub delta: AbsoluteRatePattern,
pub in_profit: BtcCentsRelSatsUsdPattern2,
pub in_loss: BtcCentsRelSatsUsdPattern2,
}
@@ -6567,7 +6567,7 @@ impl MetricsTree_Cohorts_Utxo_All_Supply {
Self {
total: BtcCentsSatsUsdPattern::new(client.clone(), "supply".to_string()),
half: BtcCentsSatsUsdPattern::new(client.clone(), "supply_half".to_string()),
delta: ChangeRatePattern::new(client.clone(), "supply_delta".to_string()),
delta: AbsoluteRatePattern::new(client.clone(), "supply_delta".to_string()),
in_profit: BtcCentsRelSatsUsdPattern2::new(client.clone(), "supply_in_profit".to_string()),
in_loss: BtcCentsRelSatsUsdPattern2::new(client.clone(), "supply_in_loss".to_string()),
}
@@ -6645,16 +6645,16 @@ impl MetricsTree_Cohorts_Utxo_All_Unrealized_Loss {
/// Metrics tree node.
pub struct MetricsTree_Cohorts_Utxo_All_Unrealized_NetPnl {
pub cents: MetricPattern1<CentsSigned>,
pub usd: MetricPattern1<Dollars>,
pub cents: MetricPattern1<CentsSigned>,
pub rel_to_own_gross: BpsPercentRatioPattern,
}
impl MetricsTree_Cohorts_Utxo_All_Unrealized_NetPnl {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cents: MetricPattern1::new(client.clone(), "net_unrealized_pnl_cents".to_string()),
usd: MetricPattern1::new(client.clone(), "net_unrealized_pnl".to_string()),
cents: MetricPattern1::new(client.clone(), "net_unrealized_pnl_cents".to_string()),
rel_to_own_gross: BpsPercentRatioPattern::new(client.clone(), "net_unrealized_pnl_rel_to_own_gross_pnl".to_string()),
}
}
+7 -7
View File
@@ -107,20 +107,20 @@ impl<T> ByEpoch<T> {
.into_par_iter()
}
pub fn mut_vec_from_height(&mut self, height: Height) -> &mut T {
pub fn mut_vec_from_height(&mut self, height: Height) -> Option<&mut T> {
let epoch = Halving::from(height);
if epoch == Halving::new(0) {
&mut self._0
Some(&mut self._0)
} else if epoch == Halving::new(1) {
&mut self._1
Some(&mut self._1)
} else if epoch == Halving::new(2) {
&mut self._2
Some(&mut self._2)
} else if epoch == Halving::new(3) {
&mut self._3
Some(&mut self._3)
} else if epoch == Halving::new(4) {
&mut self._4
Some(&mut self._4)
} else {
todo!("")
None
}
}
}
+21 -21
View File
@@ -231,32 +231,32 @@ impl<T> Class<T> {
.into_par_iter()
}
pub fn mut_vec_from_timestamp(&mut self, timestamp: Timestamp) -> &mut T {
pub fn mut_vec_from_timestamp(&mut self, timestamp: Timestamp) -> Option<&mut T> {
let year = Year::from(timestamp);
self.get_mut(year)
}
pub fn get_mut(&mut self, year: Year) -> &mut T {
pub fn get_mut(&mut self, year: Year) -> Option<&mut T> {
match u16::from(year) {
2009 => &mut self._2009,
2010 => &mut self._2010,
2011 => &mut self._2011,
2012 => &mut self._2012,
2013 => &mut self._2013,
2014 => &mut self._2014,
2015 => &mut self._2015,
2016 => &mut self._2016,
2017 => &mut self._2017,
2018 => &mut self._2018,
2019 => &mut self._2019,
2020 => &mut self._2020,
2021 => &mut self._2021,
2022 => &mut self._2022,
2023 => &mut self._2023,
2024 => &mut self._2024,
2025 => &mut self._2025,
2026 => &mut self._2026,
_ => todo!("Year {} not yet supported", u16::from(year)),
2009 => Some(&mut self._2009),
2010 => Some(&mut self._2010),
2011 => Some(&mut self._2011),
2012 => Some(&mut self._2012),
2013 => Some(&mut self._2013),
2014 => Some(&mut self._2014),
2015 => Some(&mut self._2015),
2016 => Some(&mut self._2016),
2017 => Some(&mut self._2017),
2018 => Some(&mut self._2018),
2019 => Some(&mut self._2019),
2020 => Some(&mut self._2020),
2021 => Some(&mut self._2021),
2022 => Some(&mut self._2022),
2023 => Some(&mut self._2023),
2024 => Some(&mut self._2024),
2025 => Some(&mut self._2025),
2026 => Some(&mut self._2026),
_ => None,
}
}
}
+2 -2
View File
@@ -4,7 +4,7 @@ use serde::Serialize;
use super::CohortName;
/// "At least X% loss" threshold names (10 thresholds).
/// "At least X% loss" threshold names (9 thresholds).
pub const LOSS_NAMES: Loss<CohortName> = Loss {
breakeven: CohortName::new("utxos_in_loss", "<0%", "In Loss (Below Breakeven)"),
_10pct: CohortName::new("utxos_over_10pct_in_loss", "≥10%L", "10%+ Loss"),
@@ -26,7 +26,7 @@ impl Loss<CohortName> {
}
}
/// 10 "at least X% loss" aggregate thresholds.
/// 9 "at least X% loss" aggregate thresholds.
///
/// Each is a suffix sum over the profitability ranges, from most loss-making up.
#[derive(Default, Clone, Traversable, Serialize)]
+2 -2
View File
@@ -4,7 +4,7 @@ use serde::Serialize;
use super::CohortName;
/// "At least X% profit" threshold names (15 thresholds).
/// "At least X% profit" threshold names (14 thresholds).
pub const PROFIT_NAMES: Profit<CohortName> = Profit {
breakeven: CohortName::new("utxos_in_profit", "≥0%", "In Profit (Breakeven+)"),
_10pct: CohortName::new("utxos_over_10pct_in_profit", "≥10%", "10%+ Profit"),
@@ -31,7 +31,7 @@ impl Profit<CohortName> {
}
}
/// 15 "at least X% profit" aggregate thresholds.
/// 14 "at least X% profit" aggregate thresholds.
///
/// Each is a prefix sum over the profitability ranges, from most profitable down.
#[derive(Default, Clone, Traversable, Serialize)]
+4 -4
View File
@@ -8,11 +8,11 @@ pub enum TimeFilter {
}
impl TimeFilter {
pub fn contains(&self, days: usize) -> bool {
pub fn contains(&self, hours: usize) -> bool {
match self {
TimeFilter::LowerThan(max) => days < *max,
TimeFilter::Range(r) => r.contains(&days),
TimeFilter::GreaterOrEqual(min) => days >= *min,
TimeFilter::LowerThan(max) => hours < *max,
TimeFilter::Range(r) => r.contains(&hours),
TimeFilter::GreaterOrEqual(min) => hours >= *min,
}
}
@@ -27,14 +27,14 @@ impl Vecs {
exit,
)?;
self.tx_velocity_btc.height.compute_multiply(
self.tx_velocity_native.height.compute_multiply(
starting_indexes.height,
&activity.ratio.height,
&supply.velocity.native.height,
exit,
)?;
self.tx_velocity_usd.height.compute_multiply(
self.tx_velocity_fiat.height.compute_multiply(
starting_indexes.height,
&activity.ratio.height,
&supply.velocity.fiat.height,
@@ -21,15 +21,15 @@ impl Vecs {
version,
indexes,
)?,
tx_velocity_btc: PerBlock::forced_import(
tx_velocity_native: PerBlock::forced_import(
db,
"cointime_adj_tx_velocity_btc",
"cointime_adj_tx_velocity",
version,
indexes,
)?,
tx_velocity_usd: PerBlock::forced_import(
tx_velocity_fiat: PerBlock::forced_import(
db,
"cointime_adj_tx_velocity_usd",
"cointime_adj_tx_velocity_fiat",
version,
indexes,
)?,
@@ -7,6 +7,6 @@ use crate::internal::{PerBlock, PercentPerBlock};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub inflation_rate: PercentPerBlock<BasisPointsSigned32, M>,
pub tx_velocity_btc: PerBlock<StoredF64, M>,
pub tx_velocity_usd: PerBlock<StoredF64, M>,
pub tx_velocity_native: PerBlock<StoredF64, M>,
pub tx_velocity_fiat: PerBlock<StoredF64, M>,
}
@@ -2,7 +2,7 @@ use brk_cohort::{AmountBucket, ByAddressType};
use brk_error::Result;
use brk_types::{Age, Cents, CheckedSub, Height, Sats, Timestamp, TypeIndex};
use rustc_hash::FxHashSet;
use vecdb::{VecIndex, unlikely};
use vecdb::VecIndex;
use crate::distribution::{
address::{AddressTypeToActivityCounts, HeightToAddressTypeToVec},
@@ -49,8 +49,7 @@ pub(crate) fn process_sent(
let prev_timestamp = height_to_timestamp[receive_height.to_usize()];
let age = Age::new(current_timestamp, prev_timestamp);
// Compute peak price during holding period for peak regret
// This is the max HIGH price between receive and send heights
// Compute peak spot price during holding period for peak regret
let peak_price = price_range_max.max_between(receive_height, current_height);
for (output_type, vec) in by_type.unwrap().into_iter() {
@@ -84,72 +83,33 @@ pub(crate) fn process_sent(
let new_bucket = AmountBucket::from(new_balance);
let crossing_boundary = prev_bucket != new_bucket;
let cohort_state = cohorts
.amount_range
.get_mut_by_bucket(prev_bucket)
.state
.as_mut()
.unwrap();
cohort_state.send(addr_data, value, current_price, prev_price, peak_price, age)?;
// If crossing a bucket boundary, remove the (now-updated) address from old bucket
if will_be_empty || crossing_boundary {
// Subtract from old cohort
let cohort_state = cohorts
.amount_range
.get_mut_by_bucket(prev_bucket)
.state
.as_mut()
.unwrap();
// Debug info for tracking down underflow issues
if unlikely(
cohort_state.inner.supply.utxo_count < addr_data.utxo_count() as u64,
) {
panic!(
"process_sent: cohort underflow detected!\n\
Block context: receive_height={:?}, output_type={:?}, type_index={:?}\n\
prev_balance={}, new_balance={}, value={}\n\
will_be_empty={}, crossing_boundary={}\n\
Address: {:?}",
receive_height,
output_type,
type_index,
prev_balance,
new_balance,
value,
will_be_empty,
crossing_boundary,
addr_data
);
}
cohort_state.subtract(addr_data);
}
// Update address data
addr_data.send(value, prev_price)?;
if will_be_empty {
// Address becoming empty - invariant check
if new_balance.is_not_zero() {
unreachable!()
}
*type_address_count -= 1;
*type_empty_count += 1;
// Move from funded to empty
lookup.move_to_empty(output_type, type_index);
} else {
// Add to new cohort
cohorts
.amount_range
.get_mut_by_bucket(new_bucket)
.state
.as_mut()
.unwrap()
.add(addr_data);
}
} else {
// Address staying in same cohort - update in place
// Migrate address to new bucket or mark as empty
if will_be_empty {
*type_address_count -= 1;
*type_empty_count += 1;
lookup.move_to_empty(output_type, type_index);
} else if crossing_boundary {
cohorts
.amount_range
.get_mut_by_bucket(new_bucket)
.state
.as_mut()
.unwrap()
.send(addr_data, value, current_price, prev_price, peak_price, age)?;
.add(addr_data);
}
}
}
@@ -33,18 +33,12 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
self.epoch
.mut_vec_from_height(height)
.state
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
self.class
.mut_vec_from_timestamp(timestamp)
.state
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
if let Some(v) = self.epoch.mut_vec_from_height(height) {
v.state.as_mut().unwrap().receive_utxo_snapshot(&supply_state, &snapshot);
}
if let Some(v) = self.class.mut_vec_from_timestamp(timestamp) {
v.state.as_mut().unwrap().receive_utxo_snapshot(&supply_state, &snapshot);
}
// Update output type cohorts (skip types with no outputs this block)
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
@@ -63,34 +63,22 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
self.epoch
.mut_vec_from_height(receive_height)
.state
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
self.class
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
if let Some(v) = self.epoch.mut_vec_from_height(receive_height) {
v.state.as_mut().unwrap().send_utxo_precomputed(&sent.spendable_supply, &pre);
}
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
v.state.as_mut().unwrap().send_utxo_precomputed(&sent.spendable_supply, &pre);
}
} else if sent.spendable_supply.utxo_count > 0 {
// Zero-value UTXOs: just subtract supply
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
&sent.spendable_supply;
self.epoch
.mut_vec_from_height(receive_height)
.state
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
self.class
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
if let Some(v) = self.epoch.mut_vec_from_height(receive_height) {
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
}
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
}
}
// Update output type cohorts (skip zero-supply entries)
@@ -460,7 +460,7 @@ impl RealizedFull {
.change_1m_rel_to_rcap
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps32>(
starting_indexes.height,
&self.core.net_pnl.delta.change._1m.cents.height,
&self.core.net_pnl.delta.absolute._1m.cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
@@ -468,7 +468,7 @@ impl RealizedFull {
.change_1m_rel_to_mcap
.compute_binary::<CentsSigned, Dollars, RatioCentsSignedDollarsBps32>(
starting_indexes.height,
&self.core.net_pnl.delta.change._1m.cents.height,
&self.core.net_pnl.delta.absolute._1m.cents.height,
height_to_market_cap,
exit,
)?;
@@ -373,6 +373,7 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
let (base, rest) = CostBasisDistribution::deserialize_with_rest(&data)?;
self.map = Some(base);
self.raw.state = Some(RawState::deserialize(rest)?);
debug_assert!(rest.len() >= 32, "CostBasisData state too short: {} bytes", rest.len());
self.investor_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?;
self.pending.clear();
self.raw.pending_cap = PendingCapDelta::default();
@@ -431,6 +432,14 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
self.apply_map_pending();
self.raw.apply_pending_cap();
self.investor_cap_raw += self.pending_investor_cap.inc;
debug_assert!(
self.investor_cap_raw >= self.pending_investor_cap.dec,
"CostBasis investor_cap_raw underflow!\n\
Path: {:?}\n\
Current (after increments): {:?}\n\
Trying to decrement by: {:?}",
self.raw.pathbuf, self.investor_cap_raw, self.pending_investor_cap.dec
);
self.investor_cap_raw -= self.pending_investor_cap.dec;
self.pending_investor_cap = PendingInvestorCapDelta::default();
}
@@ -224,20 +224,27 @@ impl SlidingWindowSorted {
let rank = p * last;
let lo = rank.floor() as usize;
let hi = rank.ceil() as usize;
let frac = rank - lo as f64;
lo_hi[i] = (lo, hi, frac);
lo_hi[i] = (lo, hi, rank - lo as f64);
// Insert unique ranks in sorted order (they're already ~sorted since ps is sorted)
if rank_count == 0 || rank_set[rank_count - 1] != lo {
rank_set[rank_count] = lo;
rank_count += 1;
}
if hi != lo && (rank_count == 0 || rank_set[rank_count - 1] != hi) {
rank_set[rank_count] = lo;
rank_count += 1;
if hi != lo {
rank_set[rank_count] = hi;
rank_count += 1;
}
}
// Sort and deduplicate (interleaved lo/hi values aren't necessarily sorted)
rank_set[..rank_count].sort_unstable();
let mut w = 1;
for r in 1..rank_count {
if rank_set[r] != rank_set[w - 1] {
rank_set[w] = rank_set[r];
w += 1;
}
}
rank_count = w;
// Single pass through blocks to get all values
let ranks = &rank_set[..rank_count];
let mut values = [0.0f64; 10];
+4 -4
View File
@@ -9,10 +9,10 @@ use crate::internal::AmountPerBlock;
/// All fields are lazy transforms from existing sources - no storage.
#[derive(Clone, Traversable)]
pub struct LazyAmount<I: VecIndex> {
pub sats: LazyVecFrom1<I, Sats, I, Sats>,
pub btc: LazyVecFrom1<I, Bitcoin, I, Sats>,
pub cents: LazyVecFrom1<I, Cents, I, Cents>,
pub sats: LazyVecFrom1<I, Sats, I, Sats>,
pub usd: LazyVecFrom1<I, Dollars, I, Dollars>,
pub cents: LazyVecFrom1<I, Cents, I, Cents>,
}
impl LazyAmount<Height> {
@@ -57,10 +57,10 @@ impl LazyAmount<Height> {
);
Self {
sats,
btc,
cents,
sats,
usd,
cents,
}
}
}
@@ -13,10 +13,10 @@ use crate::{
#[derive(Traversable)]
pub struct AmountPerBlock<M: StorageMode = Rw> {
pub sats: PerBlock<Sats, M>,
pub btc: LazyPerBlock<Bitcoin, Sats>,
pub cents: PerBlock<Cents, M>,
pub sats: PerBlock<Sats, M>,
pub usd: LazyPerBlock<Dollars, Cents>,
pub cents: PerBlock<Cents, M>,
}
impl AmountPerBlock {
@@ -47,10 +47,10 @@ impl AmountPerBlock {
);
Ok(Self {
sats,
btc,
cents,
sats,
usd,
cents,
})
}
@@ -6,10 +6,10 @@ use crate::internal::{AmountPerBlock, DerivedResolutions};
#[derive(Clone, Traversable)]
pub struct LazyAmountDerivedResolutions {
pub sats: DerivedResolutions<Sats, Sats>,
pub btc: DerivedResolutions<Bitcoin, Sats>,
pub cents: DerivedResolutions<Cents, Cents>,
pub sats: DerivedResolutions<Sats, Sats>,
pub usd: DerivedResolutions<Dollars, Dollars>,
pub cents: DerivedResolutions<Cents, Cents>,
}
impl LazyAmountDerivedResolutions {
@@ -54,10 +54,10 @@ impl LazyAmountDerivedResolutions {
);
Self {
sats,
btc,
cents,
sats,
usd,
cents,
}
}
}
@@ -14,10 +14,10 @@ use crate::{
/// Single window slot: lazy rolling sum for Amount (sats + btc + cents + usd).
#[derive(Clone, Traversable)]
pub struct LazyRollingSumAmountFromHeight {
pub sats: LazyRollingSumFromHeight<Sats>,
pub btc: LazyPerBlock<Bitcoin, Sats>,
pub cents: LazyRollingSumFromHeight<Cents>,
pub sats: LazyRollingSumFromHeight<Sats>,
pub usd: LazyPerBlock<Dollars, Cents>,
pub cents: LazyRollingSumFromHeight<Cents>,
}
/// Lazy rolling sums for all 4 windows, for Amount (sats + btc + cents + usd).
@@ -112,10 +112,10 @@ impl LazyRollingSumsAmountFromHeight {
};
LazyRollingSumAmountFromHeight {
sats,
btc,
cents,
sats,
usd,
cents,
}
};
@@ -29,8 +29,8 @@ impl CentsType for CentsSigned {
/// Generic over `C` to support both `Cents` (unsigned) and `CentsSigned` (signed).
#[derive(Traversable)]
pub struct FiatPerBlock<C: CentsType, M: StorageMode = Rw> {
pub cents: PerBlock<C, M>,
pub usd: LazyPerBlock<Dollars, C>,
pub cents: PerBlock<C, M>,
}
impl<C: CentsType> FiatPerBlock<C> {
@@ -48,6 +48,6 @@ impl<C: CentsType> FiatPerBlock<C> {
cents.height.read_only_boxed_clone(),
&cents,
);
Ok(Self { cents, usd })
Ok(Self { usd, cents })
}
}
@@ -8,8 +8,8 @@ use crate::internal::{CentsType, PerBlock, Identity, LazyPerBlock, NumericValue}
/// Zero extra stored vecs.
#[derive(Clone, Traversable)]
pub struct LazyFiatPerBlock<C: CentsType> {
pub cents: LazyPerBlock<C, C>,
pub usd: LazyPerBlock<Dollars, C>,
pub cents: LazyPerBlock<C, C>,
}
impl<C: CentsType> LazyFiatPerBlock<C> {
@@ -33,6 +33,6 @@ impl<C: CentsType> LazyFiatPerBlock<C> {
source.height.read_only_boxed_clone(),
source,
);
Self { cents, usd }
Self { usd, cents }
}
}
@@ -13,8 +13,8 @@ use crate::{
#[derive(Clone, Traversable)]
pub struct LazyRollingSumFiatFromHeight<C: CentsType> {
pub cents: LazyRollingSumFromHeight<C>,
pub usd: LazyPerBlock<Dollars, C>,
pub cents: LazyRollingSumFromHeight<C>,
}
#[derive(Clone, Deref, DerefMut, Traversable)]
@@ -69,7 +69,7 @@ impl<C: CentsType> LazyRollingSumsFiatFromHeight<C> {
)),
};
LazyRollingSumFiatFromHeight { cents, usd }
LazyRollingSumFiatFromHeight { usd, cents }
};
Self(cached_starts.0.map_with_suffix(make_slot))
@@ -19,8 +19,8 @@ use crate::{
/// Generic price metric with cents, USD, and sats representations.
#[derive(Clone, Traversable)]
pub struct Price<C> {
pub cents: C,
pub usd: LazyPerBlock<Dollars, Cents>,
pub cents: C,
pub sats: LazyPerBlock<SatsFract, Dollars>,
}
@@ -45,7 +45,7 @@ impl Price<PerBlock<Cents>> {
version,
&usd,
);
Ok(Self { cents, usd, sats })
Ok(Self { usd, cents, sats })
}
}
@@ -75,6 +75,6 @@ where
version,
&usd,
);
Self { cents, usd, sats }
Self { usd, cents, sats }
}
}
@@ -11,8 +11,8 @@ use super::{RatioPerBlock, RatioPerBlockPercentiles};
#[derive(Traversable)]
pub struct PriceWithRatioPerBlock<M: StorageMode = Rw> {
pub cents: PerBlock<Cents, M>,
pub usd: LazyPerBlock<Dollars, Cents>,
pub cents: PerBlock<Cents, M>,
pub sats: LazyPerBlock<SatsFract, Dollars>,
pub bps: PerBlock<BasisPoints32, M>,
pub ratio: LazyPerBlock<StoredF32, BasisPoints32>,
@@ -28,8 +28,8 @@ impl PriceWithRatioPerBlock {
let price = Price::forced_import(db, name, version, indexes)?;
let ratio = RatioPerBlock::forced_import(db, name, version, indexes)?;
Ok(Self {
cents: price.cents,
usd: price.usd,
cents: price.cents,
sats: price.sats,
bps: ratio.bps,
ratio: ratio.ratio,
@@ -44,7 +44,7 @@ where
/// Lazy rolling deltas for all 4 window durations (24h, 1w, 1m, 1y).
///
/// Tree shape: `change._24h/...`, `rate._24h/...` — matches old `RollingDelta`.
/// Tree shape: `absolute._24h/...`, `rate._24h/...` — matches old `RollingDelta`.
///
/// Replaces `RollingDelta`, `RollingDelta1m`, and `RollingDeltaExcept1m` — since
/// there is no storage cost, all 4 windows are always available.
@@ -55,7 +55,7 @@ where
C: NumericValue + JsonSchema,
B: BpsType,
{
pub change: Windows<LazyDeltaFromHeight<S, C, DeltaChange>>,
pub absolute: Windows<LazyDeltaFromHeight<S, C, DeltaChange>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
}
@@ -96,7 +96,7 @@ where
version,
indexes,
);
let change = LazyDeltaFromHeight {
let absolute = LazyDeltaFromHeight {
height: change_vec,
resolutions: Box::new(change_resolutions),
};
@@ -154,12 +154,12 @@ where
percent,
};
(change, rate)
(absolute, rate)
};
let (change, rate) = cached_starts.0.map_with_suffix(make_slot).unzip();
let (absolute, rate) = cached_starts.0.map_with_suffix(make_slot).unzip();
Self { change, rate }
Self { absolute, rate }
}
}
@@ -174,13 +174,13 @@ where
S: VecValue,
C: CentsType,
{
pub cents: LazyDeltaFromHeight<S, C, DeltaChange>,
pub usd: LazyPerBlock<Dollars, C>,
pub cents: LazyDeltaFromHeight<S, C, DeltaChange>,
}
/// Lazy fiat rolling deltas for all 4 windows.
///
/// Tree shape: `change._24h.{cents,usd}/...`, `rate._24h/...` — matches old `FiatRollingDelta`.
/// Tree shape: `absolute._24h.{cents,usd}/...`, `rate._24h/...` — matches old `FiatRollingDelta`.
///
/// Replaces `FiatRollingDelta`, `FiatRollingDelta1m`, and `FiatRollingDeltaExcept1m`.
#[derive(Clone, Traversable)]
@@ -190,7 +190,7 @@ where
C: CentsType,
B: BpsType,
{
pub change: Windows<LazyDeltaFiatFromHeight<S, C>>,
pub absolute: Windows<LazyDeltaFiatFromHeight<S, C>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
}
@@ -250,7 +250,7 @@ where
)),
};
let change = LazyDeltaFiatFromHeight { cents, usd };
let absolute = LazyDeltaFiatFromHeight { usd, cents };
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
@@ -303,11 +303,11 @@ where
percent,
};
(change, rate)
(absolute, rate)
};
let (change, rate) = cached_starts.0.map_with_suffix(make_slot).unzip();
let (absolute, rate) = cached_starts.0.map_with_suffix(make_slot).unzip();
Self { change, rate }
Self { absolute, rate }
}
}
+4 -4
View File
@@ -18,28 +18,28 @@ pub struct SplitByUnit<M: StorageMode = Rw> {
#[derive(Traversable)]
pub struct SplitIndexesByUnit<M: StorageMode = Rw> {
pub cents: EagerIndexes<Cents, M>,
pub usd: LazyEagerIndexes<Dollars, Cents>,
pub cents: EagerIndexes<Cents, M>,
pub sats: LazyEagerIndexes<Sats, Cents>,
}
#[derive(Clone, Traversable)]
pub struct SplitCloseByUnit {
pub cents: Resolutions<Cents>,
pub usd: Resolutions<Dollars>,
pub cents: Resolutions<Cents>,
pub sats: Resolutions<Sats>,
}
#[derive(Traversable)]
pub struct OhlcByUnit<M: StorageMode = Rw> {
pub cents: OhlcVecs<OHLCCents, M>,
pub usd: LazyOhlcVecs<OHLCDollars, OHLCCents>,
pub cents: OhlcVecs<OHLCCents, M>,
pub sats: LazyOhlcVecs<OHLCSats, OHLCCents>,
}
#[derive(Traversable)]
pub struct PriceByUnit<M: StorageMode = Rw> {
pub cents: PerBlock<Cents, M>,
pub usd: LazyPerBlock<Dollars, Cents>,
pub cents: PerBlock<Cents, M>,
pub sats: LazyPerBlock<Sats, Cents>,
}
+6 -6
View File
@@ -142,36 +142,36 @@ impl Vecs {
let split = SplitByUnit {
open: SplitIndexesByUnit {
cents: open_cents,
usd: open_usd,
cents: open_cents,
sats: open_sats,
},
high: SplitIndexesByUnit {
cents: high_cents,
usd: high_usd,
cents: high_cents,
sats: high_sats,
},
low: SplitIndexesByUnit {
cents: low_cents,
usd: low_usd,
cents: low_cents,
sats: low_sats,
},
close: SplitCloseByUnit {
cents: close_cents,
usd: close_usd,
cents: close_cents,
sats: close_sats,
},
};
let ohlc = OhlcByUnit {
cents: ohlc_cents,
usd: ohlc_usd,
cents: ohlc_cents,
sats: ohlc_sats,
};
let spot = PriceByUnit {
cents: price_cents,
usd: price_usd,
cents: price_cents,
sats: price_sats,
};
+3
View File
@@ -142,6 +142,9 @@ pub enum Error {
#[error("Request weight {requested} exceeds maximum {max}")]
WeightExceeded { requested: usize, max: usize },
#[error("Deserialization error: {0}")]
Deserialization(String),
#[error("Fetch failed after retries: {0}")]
FetchFailed(String),
+2 -3
View File
@@ -39,8 +39,7 @@ impl Binance {
previous_timestamp: Option<Timestamp>,
) -> Result<OHLCCents> {
// Try live API data first
if self._1mn.is_none()
|| self._1mn.as_ref().unwrap().last_key_value().unwrap().0 <= &timestamp
if self._1mn.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= &timestamp)
{
self._1mn.replace(Self::fetch_1mn()?);
}
@@ -80,7 +79,7 @@ impl Binance {
}
pub fn get_from_1d(&mut self, date: &Date) -> Result<OHLCCents> {
if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 <= date {
if self._1d.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= date) {
self._1d.replace(Self::fetch_1d()?);
}
+2 -3
View File
@@ -22,8 +22,7 @@ impl Kraken {
timestamp: Timestamp,
previous_timestamp: Option<Timestamp>,
) -> Result<OHLCCents> {
if self._1mn.is_none()
|| self._1mn.as_ref().unwrap().last_key_value().unwrap().0 <= &timestamp
if self._1mn.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= &timestamp)
{
self._1mn.replace(Self::fetch_1mn()?);
}
@@ -46,7 +45,7 @@ impl Kraken {
}
fn get_from_1d(&mut self, date: &Date) -> Result<OHLCCents> {
if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 <= date {
if self._1d.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= date) {
self._1d.replace(Self::fetch_1d()?);
}
self._1d
+13 -7
View File
@@ -4,7 +4,7 @@ use std::{path::Path, thread::sleep, time::Duration};
use brk_error::{Error, Result};
use brk_types::{Date, Height, OHLCCents, Timestamp};
use tracing::info;
use tracing::{info, warn};
mod binance;
mod brk;
@@ -70,14 +70,20 @@ impl Fetcher {
where
F: FnMut(&mut dyn PriceSource) -> Option<Result<OHLCCents>>,
{
if let Some(Ok(ohlc)) = fetch(&mut self.binance) {
return Some(Ok(ohlc));
match fetch(&mut self.binance) {
Some(Ok(ohlc)) => return Some(Ok(ohlc)),
Some(Err(e)) => warn!("Binance fetch failed: {e}"),
None => {}
}
if let Some(Ok(ohlc)) = fetch(&mut self.kraken) {
return Some(Ok(ohlc));
match fetch(&mut self.kraken) {
Some(Ok(ohlc)) => return Some(Ok(ohlc)),
Some(Err(e)) => warn!("Kraken fetch failed: {e}"),
None => {}
}
if let Some(Ok(ohlc)) = fetch(&mut self.brk) {
return Some(Ok(ohlc));
match fetch(&mut self.brk) {
Some(Ok(ohlc)) => return Some(Ok(ohlc)),
Some(Err(e)) => warn!("Brk fetch failed: {e}"),
None => {}
}
None
}
+12 -7
View File
@@ -5,13 +5,18 @@ use brk_types::{Cents, Close, Date, Dollars, High, Low, OHLCCents, Open, Timesta
/// Parse OHLC value from a JSON array element at given index
pub fn parse_cents(array: &[serde_json::Value], index: usize) -> Cents {
Cents::from(Dollars::from(
array
.get(index)
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or(0.0),
))
let value = array
.get(index)
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<f64>().ok())
.unwrap_or_else(|| {
tracing::warn!(
"Failed to parse price at index {index}: {:?}",
array.get(index)
);
0.0
});
Cents::from(Dollars::from(value))
}
/// Build OHLCCentsUnsigned from array indices 1-4 (open, high, low, close)
+1
View File
@@ -186,6 +186,7 @@ impl Query {
.unwrap_or(total),
};
let end = end.max(start);
let weight = Self::weight(&vecs, Some(start as i64), Some(end as i64));
if weight > max_weight {
return Err(Error::WeightExceeded {
+22 -6
View File
@@ -20,7 +20,7 @@ use crossbeam::channel::bounded;
use derive_more::Deref;
use parking_lot::{RwLock, RwLockReadGuard};
use rayon::prelude::*;
use tracing::error;
use tracing::{error, warn};
mod blk_index_to_blk_path;
mod decode;
@@ -169,6 +169,10 @@ impl ReaderInner {
};
i += offset;
if i + 4 > blk_bytes.len() {
warn!("Truncated blk file {blk_index}: not enough bytes for block length at offset {i}");
break;
}
let len = u32::from_le_bytes(
xor_i
.bytes(&mut blk_bytes[i..(i + 4)], xor_bytes)
@@ -177,6 +181,10 @@ impl ReaderInner {
) as usize;
i += 4;
if i + len > blk_bytes.len() {
warn!("Truncated blk file {blk_index}: block at offset {} claims {len} bytes but only {} remain", i - 4, blk_bytes.len() - i);
break;
}
let position = BlkPosition::new(blk_index, i as u32);
let metadata = BlkMetadata::new(position, len as u32);
@@ -208,12 +216,20 @@ impl ReaderInner {
.into_iter()
.par_bridge()
.try_for_each(|(metadata, bytes, xor_i)| {
if let Ok(Some(block)) = decode_block(
let position = metadata.position();
match decode_block(
bytes, metadata, &client, xor_i, xor_bytes, start, end, start_time,
end_time,
) && send_block.send(block).is_err()
{
return ControlFlow::Break(());
) {
Ok(Some(block)) => {
if send_block.send(block).is_err() {
return ControlFlow::Break(());
}
}
Ok(None) => {} // Block filtered out (outside range, unconfirmed)
Err(e) => {
warn!("Failed to decode block at {position}: {e}");
}
}
ControlFlow::Continue(())
});
@@ -247,7 +263,7 @@ impl ReaderInner {
"Chain discontinuity detected at height {}: expected prev_hash {}, got {}. Stopping iteration.",
*block.height(),
expected_prev,
block.hash()
block.header.prev_blockhash
);
return ControlFlow::Break(());
}
+4 -4
View File
@@ -144,7 +144,7 @@ impl std::fmt::Display for Bitcoin {
impl Formattable for Bitcoin {
#[inline(always)]
fn write_to(&self, buf: &mut Vec<u8>) {
if !self.0.is_nan() {
if self.0.is_finite() {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
}
@@ -152,10 +152,10 @@ impl Formattable for Bitcoin {
#[inline(always)]
fn fmt_json(&self, buf: &mut Vec<u8>) {
if self.0.is_nan() {
buf.extend_from_slice(b"null");
} else {
if self.0.is_finite() {
self.write_to(buf);
} else {
buf.extend_from_slice(b"null");
}
}
}
@@ -27,6 +27,12 @@ pub type CostBasisFormatted = BTreeMap<Dollars, f64>;
impl CostBasisDistribution {
/// Deserialize from the pco-compressed format, returning remaining bytes.
pub fn deserialize_with_rest(data: &[u8]) -> Result<(Self, &[u8])> {
if data.len() < 24 {
return Err(brk_error::Error::Deserialization(format!(
"CostBasisDistribution: data too short ({} bytes, need >= 24)",
data.len()
)));
}
let entry_count = usize::from_bytes(&data[0..8])?;
let keys_len = usize::from_bytes(&data[8..16])?;
let values_len = usize::from_bytes(&data[16..24])?;
@@ -35,6 +41,13 @@ impl CostBasisDistribution {
let values_start = keys_start + keys_len;
let rest_start = values_start + values_len;
if data.len() < rest_start {
return Err(brk_error::Error::Deserialization(format!(
"CostBasisDistribution: data too short ({} bytes, need >= {})",
data.len(), rest_start
)));
}
let keys: Vec<u32> = simple_decompress(&data[keys_start..values_start])?;
let values: Vec<u64> = simple_decompress(&data[values_start..rest_start])?;
@@ -44,7 +57,7 @@ impl CostBasisDistribution {
.map(|(k, v)| (CentsCompact::new(k), Sats::from(v)))
.collect();
assert_eq!(map.len(), entry_count);
debug_assert_eq!(map.len(), entry_count);
Ok((Self { map }, &data[rest_start..]))
}
+4
View File
@@ -213,6 +213,10 @@ impl<'de> Deserialize<'de> for Date {
.parse()
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))?;
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
return Err(E::invalid_value(serde::de::Unexpected::Str(v), &self));
}
Ok(Date::new(year, month, day))
}
}
+8 -7
View File
@@ -196,10 +196,11 @@ impl Div<f64> for Dollars {
impl Div<Bitcoin> for Dollars {
type Output = Self;
fn div(self, rhs: Bitcoin) -> Self::Output {
if self.is_nan() {
self
let rhs = f64::from(rhs);
if self.is_nan() || rhs == 0.0 {
Dollars::NAN
} else {
Self(f64::from(self) / f64::from(rhs))
Self(f64::from(self) / rhs)
}
}
}
@@ -435,7 +436,7 @@ impl std::fmt::Display for Dollars {
impl Formattable for Dollars {
#[inline(always)]
fn write_to(&self, buf: &mut Vec<u8>) {
if !self.0.is_nan() {
if self.0.is_finite() {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
}
@@ -443,10 +444,10 @@ impl Formattable for Dollars {
#[inline(always)]
fn fmt_json(&self, buf: &mut Vec<u8>) {
if self.0.is_nan() {
buf.extend_from_slice(b"null");
} else {
if self.0.is_finite() {
self.write_to(buf);
} else {
buf.extend_from_slice(b"null");
}
}
}
+13 -2
View File
@@ -135,7 +135,18 @@ impl std::fmt::Display for FeeRate {
impl Formattable for FeeRate {
#[inline(always)]
fn write_to(&self, buf: &mut Vec<u8>) {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
if self.0.is_finite() {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
}
}
#[inline(always)]
fn fmt_json(&self, buf: &mut Vec<u8>) {
if self.0.is_finite() {
self.write_to(buf);
} else {
buf.extend_from_slice(b"null");
}
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ impl<'de> Deserialize<'de> for Metrics {
} else if let Some(vec) = value.as_array() {
if vec.len() <= MAX_VECS {
Ok(Self(
sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string()))
sanitize(vec.iter().filter_map(|s| s.as_str().map(String::from)))
.into_iter()
.map(Metric::from)
.collect(),
+6 -2
View File
@@ -29,10 +29,14 @@ impl Pagination {
}
pub fn start(&self, len: usize) -> usize {
(self.page() * self.per_page()).clamp(0, len)
self.page()
.saturating_mul(self.per_page())
.min(len)
}
pub fn end(&self, len: usize) -> usize {
((self.page() + 1) * self.per_page()).clamp(0, len)
(self.page().saturating_add(1))
.saturating_mul(self.per_page())
.min(len)
}
}
+7 -1
View File
@@ -56,7 +56,13 @@ impl<'de> Deserialize<'de> for RangeIndex {
return Ok(Self::Date(date));
}
if let Ok(ts) = s.parse::<jiff::Timestamp>() {
return Ok(Self::Timestamp(Timestamp::new(ts.as_second() as u32)));
let secs = ts.as_second();
if secs < 0 || secs > u32::MAX as i64 {
return Err(serde::de::Error::custom(format!(
"timestamp out of range: {s}"
)));
}
return Ok(Self::Timestamp(Timestamp::new(secs as u32)));
}
Err(serde::de::Error::custom(format!(
"expected integer, YYYY-MM-DD, or ISO 8601 timestamp: {s}"
+4 -4
View File
@@ -189,7 +189,7 @@ impl std::fmt::Display for SatsFract {
impl Formattable for SatsFract {
#[inline(always)]
fn write_to(&self, buf: &mut Vec<u8>) {
if !self.0.is_nan() {
if self.0.is_finite() {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
}
@@ -197,10 +197,10 @@ impl Formattable for SatsFract {
#[inline(always)]
fn fmt_json(&self, buf: &mut Vec<u8>) {
if self.0.is_nan() {
buf.extend_from_slice(b"null");
} else {
if self.0.is_finite() {
self.write_to(buf);
} else {
buf.extend_from_slice(b"null");
}
}
}
+11 -2
View File
@@ -1,5 +1,5 @@
use derive_more::Deref;
use schemars::JsonSchema;
use schemars::{JsonSchema, SchemaGenerator};
use serde::{Deserialize, Serialize};
use vecdb::{Formattable, Pco, PrintableIndex};
@@ -17,10 +17,19 @@ use vecdb::{Formattable, Pco, PrintableIndex};
Serialize,
Deserialize,
Pco,
JsonSchema,
)]
pub struct StoredBool(u8);
impl JsonSchema for StoredBool {
fn schema_name() -> std::borrow::Cow<'static, str> {
"StoredBool".into()
}
fn json_schema(generator: &mut SchemaGenerator) -> schemars::Schema {
bool::json_schema(generator)
}
}
impl StoredBool {
pub const FALSE: Self = Self(0);
pub const TRUE: Self = Self(1);
+4 -4
View File
@@ -267,7 +267,7 @@ impl std::fmt::Display for StoredF32 {
impl Formattable for StoredF32 {
#[inline(always)]
fn write_to(&self, buf: &mut Vec<u8>) {
if !self.0.is_nan() {
if self.0.is_finite() {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
}
@@ -275,10 +275,10 @@ impl Formattable for StoredF32 {
#[inline(always)]
fn fmt_json(&self, buf: &mut Vec<u8>) {
if self.0.is_nan() {
buf.extend_from_slice(b"null");
} else {
if self.0.is_finite() {
self.write_to(buf);
} else {
buf.extend_from_slice(b"null");
}
}
}
+4 -4
View File
@@ -247,7 +247,7 @@ impl std::fmt::Display for StoredF64 {
impl Formattable for StoredF64 {
#[inline(always)]
fn write_to(&self, buf: &mut Vec<u8>) {
if !self.0.is_nan() {
if self.0.is_finite() {
let mut b = ryu::Buffer::new();
buf.extend_from_slice(b.format(self.0).as_bytes());
}
@@ -255,10 +255,10 @@ impl Formattable for StoredF64 {
#[inline(always)]
fn fmt_json(&self, buf: &mut Vec<u8>) {
if self.0.is_nan() {
buf.extend_from_slice(b"null");
} else {
if self.0.is_finite() {
self.write_to(buf);
} else {
buf.extend_from_slice(b"null");
}
}
}