brk: metric trimming part X + 3

This commit is contained in:
nym21
2026-07-30 14:11:40 +02:00
parent c1745793b8
commit cc5b6da341
155 changed files with 3774 additions and 2078 deletions
Generated
-6
View File
@@ -2438,8 +2438,6 @@ dependencies = [
[[package]]
name = "rawdb"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df8d0fa0319c87c369464ffe5ee0baa6659ed37d3426a9ae021fb463d95ad52"
dependencies = [
"libc",
"log",
@@ -3367,8 +3365,6 @@ checksum = "bfa6c38708f6257f1ec2ca7e5a11f9bbf58a27d7060078b6b333624968183d96"
[[package]]
name = "vecdb"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "294fcad993a26e2668dca931c610b8461e66837b5f359a0c1a89bdf357cacf4b"
dependencies = [
"itoa",
"libc",
@@ -3390,8 +3386,6 @@ dependencies = [
[[package]]
name = "vecdb_derive"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e6ddc694828bd6d372573727e02ea07047f4b65f78aaf859dae2fbf2dbbcfe"
dependencies = [
"quote",
"syn 2.0.119",
+2 -2
View File
@@ -84,8 +84,8 @@ tower-http = { version = "0.7.0", features = ["catch-panic", "compression-br", "
tower-layer = "0.3"
tracing = { version = "0.1", default-features = false, features = ["std"] }
ureq = { version = "3.3.0", features = ["json"] }
vecdb = { version = "0.10.4", features = ["derive", "serde_json", "pco", "schemars"] }
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
# vecdb = { version = "0.10.4", features = ["derive", "serde_json", "pco", "schemars"] }
vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
[workspace.metadata.release]
+145 -91
View File
@@ -1758,30 +1758,30 @@ impl CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 {
/// Pattern struct for repeated tree structure.
pub struct Pct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern {
pub pct10: SeriesPattern1<Dollars>,
pub pct20: SeriesPattern1<Dollars>,
pub pct30: SeriesPattern1<Dollars>,
pub pct40: SeriesPattern1<Dollars>,
pub pct50: SeriesPattern1<Dollars>,
pub pct60: SeriesPattern1<Dollars>,
pub pct70: SeriesPattern1<Dollars>,
pub pct80: SeriesPattern1<Dollars>,
pub pct90: SeriesPattern1<Dollars>,
pub pct10: CentsSatsUsdPattern,
pub pct20: CentsSatsUsdPattern,
pub pct30: CentsSatsUsdPattern,
pub pct40: CentsSatsUsdPattern,
pub pct50: CentsSatsUsdPattern,
pub pct60: CentsSatsUsdPattern,
pub pct70: CentsSatsUsdPattern,
pub pct80: CentsSatsUsdPattern,
pub pct90: CentsSatsUsdPattern,
}
impl Pct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
pct10: SeriesPattern1::new(client.clone(), _m(&acc, "pct10")),
pct20: SeriesPattern1::new(client.clone(), _m(&acc, "pct20")),
pct30: SeriesPattern1::new(client.clone(), _m(&acc, "pct30")),
pct40: SeriesPattern1::new(client.clone(), _m(&acc, "pct40")),
pct50: SeriesPattern1::new(client.clone(), _m(&acc, "pct50")),
pct60: SeriesPattern1::new(client.clone(), _m(&acc, "pct60")),
pct70: SeriesPattern1::new(client.clone(), _m(&acc, "pct70")),
pct80: SeriesPattern1::new(client.clone(), _m(&acc, "pct80")),
pct90: SeriesPattern1::new(client.clone(), _m(&acc, "pct90")),
pct10: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct10")),
pct20: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct20")),
pct30: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct30")),
pct40: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct40")),
pct50: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct50")),
pct60: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct60")),
pct70: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct70")),
pct80: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct80")),
pct90: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct90")),
}
}
}
@@ -1882,7 +1882,7 @@ pub struct ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2 {
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub cost_basis: InMaxMinPerSupplyPattern,
pub invested_capital: InPattern,
pub outputs: SpentUnspentUtxoPattern,
pub outputs: SpentUnspentPattern,
pub realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2,
pub supply: DeltaDominanceHalfInTotalPattern2,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
@@ -1895,7 +1895,7 @@ impl ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2 {
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), acc.clone()),
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), acc.clone()),
invested_capital: InPattern::new(client.clone(), _m(&acc, "invested_capital_in")),
outputs: SpentUnspentUtxoPattern::new(client.clone(), acc.clone()),
outputs: SpentUnspentPattern::new(client.clone(), acc.clone()),
realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2::new(client.clone(), acc.clone()),
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), _m(&acc, "supply")),
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), acc.clone()),
@@ -2037,7 +2037,7 @@ impl<T: DeserializeOwned> MaxMedianMinPct10Pct25Pct75Pct90Pattern<T> {
pub struct ActivityAddrOutputsRealizedSupplyUnrealizedPattern {
pub activity: TransferPattern,
pub addr_count: BaseDeltaPattern,
pub outputs: SpentUnspentUtxoPattern,
pub outputs: SpentUnspentPattern,
pub realized: CapLossMvrvPriceProfitPattern,
pub supply: DeltaDominanceTotalPattern,
pub unrealized: NuplPattern,
@@ -2049,7 +2049,7 @@ impl ActivityAddrOutputsRealizedSupplyUnrealizedPattern {
Self {
activity: TransferPattern::new(client.clone(), _m(&acc, "transfer_volume")),
addr_count: BaseDeltaPattern::new(client.clone(), _m(&acc, "addr_count")),
outputs: SpentUnspentUtxoPattern::new(client.clone(), acc.clone()),
outputs: SpentUnspentPattern::new(client.clone(), acc.clone()),
realized: CapLossMvrvPriceProfitPattern::new(client.clone(), acc.clone()),
supply: DeltaDominanceTotalPattern::new(client.clone(), _m(&acc, "supply")),
unrealized: NuplPattern::new(client.clone(), _m(&acc, "nupl")),
@@ -2222,7 +2222,7 @@ impl ActiveBidirectionalReactivatedReceivingSendingPattern {
/// Pattern struct for repeated tree structure.
pub struct ActivityOutputsRealizedSupplyUnrealizedPattern {
pub activity: CoindaysTransferPattern,
pub outputs: SpentUnspentUtxoPattern,
pub outputs: SpentUnspentPattern,
pub realized: CapLossMvrvNetPriceProfitSoprPattern,
pub supply: DeltaDominanceHalfInTotalPattern,
pub unrealized: LossNetNuplProfitPattern,
@@ -2233,7 +2233,7 @@ impl ActivityOutputsRealizedSupplyUnrealizedPattern {
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
activity: CoindaysTransferPattern::new(client.clone(), acc.clone()),
outputs: SpentUnspentUtxoPattern::new(client.clone(), acc.clone()),
outputs: SpentUnspentPattern::new(client.clone(), acc.clone()),
realized: CapLossMvrvNetPriceProfitSoprPattern::new(client.clone(), acc.clone()),
supply: DeltaDominanceHalfInTotalPattern::new(client.clone(), _m(&acc, "supply")),
unrealized: LossNetNuplProfitPattern::new(client.clone(), acc.clone()),
@@ -2244,7 +2244,7 @@ impl ActivityOutputsRealizedSupplyUnrealizedPattern {
/// Pattern struct for repeated tree structure.
pub struct ActivityOutputsRealizedSupplyUnrealizedPattern3 {
pub activity: TransferPattern,
pub outputs: SpentUnspentUtxoPattern,
pub outputs: SpentUnspentPattern,
pub realized: CapLossMvrvPriceProfitPattern,
pub supply: DeltaDominanceHalfInTotalPattern,
pub unrealized: LossNuplProfitPattern,
@@ -2255,7 +2255,7 @@ impl ActivityOutputsRealizedSupplyUnrealizedPattern3 {
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
activity: TransferPattern::new(client.clone(), _m(&acc, "transfer_volume")),
outputs: SpentUnspentUtxoPattern::new(client.clone(), acc.clone()),
outputs: SpentUnspentPattern::new(client.clone(), acc.clone()),
realized: CapLossMvrvPriceProfitPattern::new(client.clone(), acc.clone()),
supply: DeltaDominanceHalfInTotalPattern::new(client.clone(), _m(&acc, "supply")),
unrealized: LossNuplProfitPattern::new(client.clone(), acc.clone()),
@@ -2266,7 +2266,7 @@ impl ActivityOutputsRealizedSupplyUnrealizedPattern3 {
/// Pattern struct for repeated tree structure.
pub struct ActivityOutputsRealizedSupplyUnrealizedPattern2 {
pub activity: TransferPattern,
pub outputs: SpentUnspentUtxoPattern,
pub outputs: SpentUnspentPattern,
pub realized: CapLossMvrvPriceProfitPattern,
pub supply: DeltaDominanceTotalPattern,
pub unrealized: NuplPattern,
@@ -2277,7 +2277,7 @@ impl ActivityOutputsRealizedSupplyUnrealizedPattern2 {
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
activity: TransferPattern::new(client.clone(), _m(&acc, "transfer_volume")),
outputs: SpentUnspentUtxoPattern::new(client.clone(), acc.clone()),
outputs: SpentUnspentPattern::new(client.clone(), acc.clone()),
realized: CapLossMvrvPriceProfitPattern::new(client.clone(), acc.clone()),
supply: DeltaDominanceTotalPattern::new(client.clone(), _m(&acc, "supply")),
unrealized: NuplPattern::new(client.clone(), _m(&acc, "nupl")),
@@ -2435,6 +2435,50 @@ pub struct EmaHistogramLineSignalPattern {
pub signal: SeriesPattern1<StoredF32>,
}
/// Pattern struct for repeated tree structure.
pub struct Pct95Pct98Pct99Pattern {
pub pct95: CentsSatsUsdPattern,
pub pct98: CentsSatsUsdPattern,
pub pct99: CentsSatsUsdPattern,
pub pct99_5: CentsSatsUsdPattern,
pub pct99_9: CentsSatsUsdPattern,
}
impl Pct95Pct98Pct99Pattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
pct95: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct95")),
pct98: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct98")),
pct99: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct99")),
pct99_5: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct99_5")),
pct99_9: CentsSatsUsdPattern::new(client.clone(), _m(&acc, "pct99_9")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct Pct95Pct98Pct99Pattern2 {
pub pct95: SeriesPattern1<StoredF64>,
pub pct98: SeriesPattern1<StoredF64>,
pub pct99: SeriesPattern1<StoredF64>,
pub pct99_5: SeriesPattern1<StoredF64>,
pub pct99_9: SeriesPattern1<StoredF64>,
}
impl Pct95Pct98Pct99Pattern2 {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
pct95: SeriesPattern1::new(client.clone(), _m(&acc, "pct95")),
pct98: SeriesPattern1::new(client.clone(), _m(&acc, "pct98")),
pct99: SeriesPattern1::new(client.clone(), _m(&acc, "pct99")),
pct99_5: SeriesPattern1::new(client.clone(), _m(&acc, "pct99_5")),
pct99_9: SeriesPattern1::new(client.clone(), _m(&acc, "pct99_9")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct PhsReboundThsPattern {
pub phs: SeriesPattern1<StoredF32>,
@@ -2457,28 +2501,6 @@ impl PhsReboundThsPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct Pct95Pct98Pct99Pattern<T> {
pub pct95: SeriesPattern1<T>,
pub pct98: SeriesPattern1<T>,
pub pct99: SeriesPattern1<T>,
pub pct99_5: SeriesPattern1<T>,
pub pct99_9: SeriesPattern1<T>,
}
impl<T: DeserializeOwned> Pct95Pct98Pct99Pattern<T> {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
pct95: SeriesPattern1::new(client.clone(), _m(&acc, "pct95")),
pct98: SeriesPattern1::new(client.clone(), _m(&acc, "pct98")),
pct99: SeriesPattern1::new(client.clone(), _m(&acc, "pct99")),
pct99_5: SeriesPattern1::new(client.clone(), _m(&acc, "pct99_5")),
pct99_9: SeriesPattern1::new(client.clone(), _m(&acc, "pct99_9")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct _1m1w1y24hPattern4 {
pub _1m: BtcCentsSatsUsdPattern,
@@ -2955,9 +2977,27 @@ impl BlockCumulativeSumPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct BlockCumulativeSumPattern2 {
pub block: SeriesPattern18<StoredU64>,
pub cumulative: SeriesPattern1<StoredU64>,
pub sum: _1m1w1y24hPattern<StoredU64>,
}
impl BlockCumulativeSumPattern2 {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
block: SeriesPattern18::new(client.clone(), acc.clone()),
cumulative: SeriesPattern1::new(client.clone(), _m(&acc, "cumulative")),
sum: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "sum")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct BlocksDominanceRewardsPattern {
pub blocks_mined: AverageBlockCumulativeSumPattern<StoredU64>,
pub blocks_mined: BlockCumulativeSumPattern2,
pub dominance: _1m1w1y24hPercentPpmRatioPattern,
pub rewards: AverageBlockCumulativeSumPattern2,
}
@@ -2966,7 +3006,7 @@ impl BlocksDominanceRewardsPattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
blocks_mined: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "blocks_mined")),
blocks_mined: BlockCumulativeSumPattern2::new(client.clone(), _m(&acc, "blocks_mined")),
dominance: _1m1w1y24hPercentPpmRatioPattern::new(client.clone(), _m(&acc, "dominance")),
rewards: AverageBlockCumulativeSumPattern2::new(client.clone(), _m(&acc, "rewards")),
}
@@ -3090,9 +3130,9 @@ impl DeltaDominanceTotalPattern {
/// Pattern struct for repeated tree structure.
pub struct FloorLevelLossPattern {
pub floor: Pct95Pct98Pct99Pattern<Dollars>,
pub floor: Pct95Pct98Pct99Pattern,
pub level: Pct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern,
pub loss_threshold: Pct95Pct98Pct99Pattern<StoredF64>,
pub loss_threshold: Pct95Pct98Pct99Pattern2,
}
impl FloorLevelLossPattern {
@@ -3101,7 +3141,7 @@ impl FloorLevelLossPattern {
Self {
floor: Pct95Pct98Pct99Pattern::new(client.clone(), _m(&acc, "floor")),
level: Pct10Pct20Pct30Pct40Pct50Pct60Pct70Pct80Pct90Pattern::new(client.clone(), _m(&acc, "level")),
loss_threshold: Pct95Pct98Pct99Pattern::new(client.clone(), _m(&acc, "loss_threshold")),
loss_threshold: Pct95Pct98Pct99Pattern2::new(client.clone(), _m(&acc, "loss_threshold")),
}
}
}
@@ -3160,6 +3200,24 @@ impl PercentPpmRatioPattern2 {
}
}
/// Pattern struct for repeated tree structure.
pub struct PercentPpmRatioPattern5 {
pub percent: SeriesPattern1<StoredF32>,
pub ppm: SeriesPattern1<PartsPerMillion64>,
pub ratio: SeriesPattern1<StoredF32>,
}
impl PercentPpmRatioPattern5 {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
percent: SeriesPattern1::new(client.clone(), acc.clone()),
ppm: SeriesPattern1::new(client.clone(), _m(&acc, "ppm")),
ratio: SeriesPattern1::new(client.clone(), _m(&acc, "ratio")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct PercentPpmRatioPattern3 {
pub percent: SeriesPattern1<StoredF32>,
@@ -3250,24 +3308,6 @@ impl RsiStochPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct SpentUnspentUtxoPattern {
pub spent_count: AverageBlockCumulativeSumPattern<StoredU64>,
pub unspent_count: BaseDeltaPattern,
pub utxo_turnover_1y: SeriesPattern1<StoredF32>,
}
impl SpentUnspentUtxoPattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
spent_count: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "spent_utxo_count")),
unspent_count: BaseDeltaPattern::new(client.clone(), _m(&acc, "utxo_count")),
utxo_turnover_1y: SeriesPattern1::new(client.clone(), _m(&acc, "utxo_turnover_1y")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct _6bBlockTxPattern<T> {
pub _6b: MaxMedianMinPct10Pct25Pct75Pct90Pattern<T>,
@@ -3448,7 +3488,7 @@ impl BlockCumulativePattern {
/// Pattern struct for repeated tree structure.
pub struct BlocksDominancePattern {
pub blocks_mined: AverageBlockCumulativeSumPattern<StoredU64>,
pub blocks_mined: BlockCumulativeSumPattern2,
pub dominance: PercentPpmRatioPattern2,
}
@@ -3456,7 +3496,7 @@ impl BlocksDominancePattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
blocks_mined: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "blocks_mined")),
blocks_mined: BlockCumulativeSumPattern2::new(client.clone(), _m(&acc, "blocks_mined")),
dominance: PercentPpmRatioPattern2::new(client.clone(), _m(&acc, "dominance")),
}
}
@@ -3724,6 +3764,22 @@ pub struct SdSmaPattern {
pub sma: SeriesPattern1<StoredF32>,
}
/// Pattern struct for repeated tree structure.
pub struct SpentUnspentPattern {
pub spent_count: AverageBlockCumulativeSumPattern<StoredU64>,
pub unspent_count: BaseDeltaPattern,
}
impl SpentUnspentPattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
spent_count: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "spent_utxo_count")),
unspent_count: BaseDeltaPattern::new(client.clone(), _m(&acc, "utxo_count")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct ToPattern {
pub to_mcap: PercentPpmRatioPattern,
@@ -5738,7 +5794,7 @@ pub struct SeriesTree_Mining_Rewards_Fees {
pub pct75: _1m1w1y24hPattern4,
pub pct90: _1m1w1y24hPattern4,
pub dominance: _1m1w1y24hPercentPpmRatioPattern,
pub to_subsidy_ratio: SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio,
pub to_subsidy: SeriesTree_Mining_Rewards_Fees_ToSubsidy,
}
impl SeriesTree_Mining_Rewards_Fees {
@@ -5756,26 +5812,26 @@ impl SeriesTree_Mining_Rewards_Fees {
pct75: _1m1w1y24hPattern4::new(client.clone(), "fees_pct75".to_string()),
pct90: _1m1w1y24hPattern4::new(client.clone(), "fees_pct90".to_string()),
dominance: _1m1w1y24hPercentPpmRatioPattern::new(client.clone(), "fee_dominance".to_string()),
to_subsidy_ratio: SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio::new(client.clone(), format!("{base_path}_to_subsidy_ratio")),
to_subsidy: SeriesTree_Mining_Rewards_Fees_ToSubsidy::new(client.clone(), format!("{base_path}_to_subsidy")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio {
pub _24h: PpmRatioPattern3,
pub _1w: PpmRatioPattern3,
pub _1m: PpmRatioPattern3,
pub _1y: PpmRatioPattern3,
pub struct SeriesTree_Mining_Rewards_Fees_ToSubsidy {
pub _24h: PercentPpmRatioPattern5,
pub _1w: PercentPpmRatioPattern5,
pub _1m: PercentPpmRatioPattern5,
pub _1y: PercentPpmRatioPattern5,
}
impl SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio {
impl SeriesTree_Mining_Rewards_Fees_ToSubsidy {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
_24h: PpmRatioPattern3::new(client.clone(), "fee_to_subsidy_ratio_24h".to_string()),
_1w: PpmRatioPattern3::new(client.clone(), "fee_to_subsidy_ratio_1w".to_string()),
_1m: PpmRatioPattern3::new(client.clone(), "fee_to_subsidy_ratio_1m".to_string()),
_1y: PpmRatioPattern3::new(client.clone(), "fee_to_subsidy_ratio_1y".to_string()),
_24h: PercentPpmRatioPattern5::new(client.clone(), "fee_to_subsidy_24h".to_string()),
_1w: PercentPpmRatioPattern5::new(client.clone(), "fee_to_subsidy_1w".to_string()),
_1m: PercentPpmRatioPattern5::new(client.clone(), "fee_to_subsidy_1m".to_string()),
_1y: PercentPpmRatioPattern5::new(client.clone(), "fee_to_subsidy_1y".to_string()),
}
}
}
@@ -8229,7 +8285,6 @@ impl SeriesTree_Cohorts_Utxo_All {
pub struct SeriesTree_Cohorts_Utxo_All_Outputs {
pub unspent_count: BaseDeltaPattern,
pub spent_count: AverageBlockCumulativeSumPattern<StoredU64>,
pub utxo_turnover_1y: SeriesPattern1<StoredF32>,
}
impl SeriesTree_Cohorts_Utxo_All_Outputs {
@@ -8237,7 +8292,6 @@ impl SeriesTree_Cohorts_Utxo_All_Outputs {
Self {
unspent_count: BaseDeltaPattern::new(client.clone(), "utxo_count".to_string()),
spent_count: AverageBlockCumulativeSumPattern::new(client.clone(), "spent_utxo_count".to_string()),
utxo_turnover_1y: SeriesPattern1::new(client.clone(), "utxo_turnover_1y".to_string()),
}
}
}
@@ -8461,7 +8515,7 @@ impl SeriesTree_Cohorts_Utxo_All_Unrealized_Sentiment {
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Sth {
pub supply: DeltaDominanceHalfInTotalPattern2,
pub outputs: SpentUnspentUtxoPattern,
pub outputs: SpentUnspentPattern,
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern,
pub cost_basis: InMaxMinPerSupplyPattern,
@@ -8473,7 +8527,7 @@ impl SeriesTree_Cohorts_Utxo_Sth {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "sth_supply".to_string()),
outputs: SpentUnspentUtxoPattern::new(client.clone(), "sth".to_string()),
outputs: SpentUnspentPattern::new(client.clone(), "sth".to_string()),
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "sth".to_string()),
realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern::new(client.clone(), "sth".to_string()),
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "sth".to_string()),
+3 -15
View File
@@ -20,30 +20,18 @@ impl Vecs {
// lookback depends on indexes.timestamp.monotonic
self.lookback.compute(indexer, indexes, exit)?;
// Parallel: remaining sub-modules are independent of each other.
// size depends on lookback (already computed above).
// Interval and size are independent. Size depends on lookback, which
// was already computed above.
let Vecs {
lookback,
count,
interval,
size,
weight,
difficulty,
halving,
..
} = self;
thread::scope(|s| -> Result<()> {
let r1 = s.spawn(|| count.compute(indexer, exit));
let r2 = s.spawn(|| interval.compute(indexer, exit));
let r3 = s.spawn(|| weight.compute(indexer, exit));
let r4 = s.spawn(|| difficulty.compute(indexer, indexes, exit));
let r5 = s.spawn(|| halving.compute(indexer, indexes, exit));
let r1 = s.spawn(|| interval.compute(indexer, exit));
size.compute(indexer, &*lookback, exit)?;
r1.join().unwrap()?;
r2.join().unwrap()?;
r3.join().unwrap()?;
r4.join().unwrap()?;
r5.join().unwrap()?;
Ok(())
})?;
@@ -1,19 +0,0 @@
use brk_error::Result;
use brk_indexer::Indexer;
use vecdb::Exit;
use super::Vecs;
impl Vecs {
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.total.cumulative.height.compute_cumulative_count(
starting_height,
&indexer.vecs.blocks.weight,
|_| true,
exit,
)?;
Ok(())
}
}
+17 -13
View File
@@ -1,24 +1,27 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::Database;
use brk_indexer::Indexer;
use brk_types::{Height, StoredU64, Version, Weight};
use super::Vecs;
use crate::{
indexes,
internal::{
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
ConstantVecs, PerBlockCumulativeRolling, WindowStartVec, Windows,
ConstantVecs, LazyPerBlockCumulativeRolling, WindowStartVec, Windows,
},
};
fn cumulative_block_count(height: Height, _: Weight) -> StoredU64 {
StoredU64::from(u64::from(height) + 1)
}
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
pub(crate) fn new(
version: Version,
indexer: &Indexer,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
Ok(Self {
) -> Self {
Self {
target: Windows {
_24h: ConstantVecs::new::<BlockCountTarget24h>(
"block_count_target_24h",
@@ -41,13 +44,14 @@ impl Vecs {
indexes,
),
},
total: PerBlockCumulativeRolling::forced_import(
db,
total: LazyPerBlockCumulativeRolling::from_indexed_source(
"block_count",
version + Version::ONE,
indexes,
&indexer.vecs.blocks.weight,
cumulative_block_count,
cached_starts,
)?,
})
indexes,
),
}
}
}
@@ -1,4 +1,3 @@
mod compute;
mod import;
mod vecs;
+4 -5
View File
@@ -1,11 +1,10 @@
use brk_traversable::Traversable;
use brk_types::StoredU64;
use vecdb::{Rw, StorageMode};
use crate::internal::{ConstantVecs, PerBlockCumulativeRolling, Windows};
use crate::internal::{ConstantVecs, LazyPerBlockCumulativeRolling, Windows};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
#[derive(Clone, Traversable)]
pub struct Vecs {
pub target: Windows<ConstantVecs<StoredU64>>,
pub total: PerBlockCumulativeRolling<StoredU64, M>,
pub total: LazyPerBlockCumulativeRolling<StoredU64>,
}
@@ -1,40 +0,0 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::StoredU32;
use vecdb::Exit;
use super::Vecs;
use crate::indexes;
impl Vecs {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
exit: &Exit,
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.adjustment.ppm.height.compute_ratio_change(
starting_height,
&indexer.vecs.blocks.difficulty,
2016,
exit,
)?;
self.epoch.height.compute_transform(
starting_height,
&indexes.height.epoch,
|(h, epoch, ..)| (h, epoch),
exit,
)?;
self.blocks_to_retarget.height.compute_transform(
starting_height,
&indexes.height.epoch,
|(h, ..)| (h, StoredU32::from(h.left_before_next_diff_adj())),
exit,
)?;
Ok(())
}
}
@@ -1,23 +1,36 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::Version;
use vecdb::{Database, ReadableCloneableVec};
use brk_types::{Epoch, Height, PartsPerMillionSigned32, StoredF64, StoredU32, Version};
use vecdb::ReadOnlyClone;
use super::Vecs;
use crate::{
indexes,
internal::{
BlocksToDaysF32, DifficultyToHashF64, LazyPerBlock, PerBlock, PercentPerBlock, Resolutions,
BlocksToDaysF32, DifficultyToHashF64, Identity, LazyPerBlock, LazyPercentPerBlock,
Resolutions,
},
};
const DIFFICULTY_ADJUSTMENT_LOOKBACK: usize = 2016;
fn blocks_left_to_retarget(height: Height, _: Epoch) -> StoredU32 {
StoredU32::from(height.left_before_next_diff_adj())
}
fn difficulty_adjustment(
current: StoredF64,
previous: Option<StoredF64>,
) -> PartsPerMillionSigned32 {
match previous {
Some(previous) => {
PartsPerMillionSigned32::from((f32::from(current) / f32::from(previous)) - 1.0)
}
None => PartsPerMillionSigned32::from(f32::NAN),
}
}
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexer: &Indexer,
indexes: &indexes::Vecs,
) -> Result<Self> {
pub(crate) fn new(version: Version, indexer: &Indexer, indexes: &indexes::Vecs) -> Self {
let v2 = Version::TWO;
let hashrate = LazyPerBlock::from_height_source::<DifficultyToHashF64, _>(
@@ -27,17 +40,27 @@ impl Vecs {
indexes,
);
let blocks_to_retarget =
PerBlock::forced_import(db, "blocks_to_retarget", version + v2, indexes)?;
let epoch = LazyPerBlock::from_height_source::<Identity<Epoch>, _>(
"difficulty_epoch",
version,
indexes.height.epoch.read_only_clone(),
indexes,
);
let blocks_to_retarget = LazyPerBlock::from_indexed_source(
"blocks_to_retarget",
version + v2,
&indexes.height.epoch,
blocks_left_to_retarget,
indexes,
);
let days_to_retarget = LazyPerBlock::from_computed::<BlocksToDaysF32>(
let days_to_retarget = LazyPerBlock::from_lazy::<BlocksToDaysF32, StoredU32>(
"days_to_retarget",
version + v2,
blocks_to_retarget.height.read_only_boxed_clone(),
&blocks_to_retarget,
);
Ok(Self {
Self {
value: Resolutions::forced_import(
"difficulty",
indexer.vecs.blocks.difficulty.read_only_clone(),
@@ -45,15 +68,17 @@ impl Vecs {
indexes,
),
hashrate,
adjustment: PercentPerBlock::forced_import(
db,
adjustment: LazyPercentPerBlock::from_lookback_source(
"difficulty_adjustment",
version + Version::ONE,
&indexer.vecs.blocks.difficulty,
DIFFICULTY_ADJUSTMENT_LOOKBACK,
difficulty_adjustment,
indexes,
)?,
epoch: PerBlock::forced_import(db, "difficulty_epoch", version, indexes)?,
),
epoch,
blocks_to_retarget,
days_to_retarget,
})
}
}
}
@@ -1,4 +1,3 @@
mod compute;
mod import;
mod vecs;
@@ -1,14 +1,14 @@
use brk_traversable::Traversable;
use brk_types::{Epoch, PartsPerMillionSigned32, StoredF32, StoredF64, StoredU32};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Resolutions};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
use crate::internal::{LazyPerBlock, LazyPercentPerBlock, Resolutions};
#[derive(Clone, Traversable)]
pub struct Vecs {
pub value: Resolutions<StoredF64>,
pub hashrate: LazyPerBlock<StoredF64>,
pub adjustment: PercentPerBlock<PartsPerMillionSigned32, M>,
pub epoch: PerBlock<Epoch, M>,
pub blocks_to_retarget: PerBlock<StoredU32, M>,
pub adjustment: LazyPercentPerBlock<PartsPerMillionSigned32>,
pub epoch: LazyPerBlock<Epoch>,
pub blocks_to_retarget: LazyPerBlock<StoredU32>,
pub days_to_retarget: LazyPerBlock<StoredF32, StoredU32>,
}
@@ -1,33 +0,0 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::StoredU32;
use vecdb::Exit;
use super::Vecs;
use crate::indexes;
impl Vecs {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
exit: &Exit,
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.epoch.height.compute_transform(
starting_height,
&indexes.height.halving,
|(h, epoch, ..)| (h, epoch),
exit,
)?;
self.blocks_to_halving.height.compute_transform(
starting_height,
&indexes.height.halving,
|(h, ..)| (h, StoredU32::from(h.left_before_next_halving())),
exit,
)?;
Ok(())
}
}
@@ -1,35 +1,44 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::{Database, ReadableCloneableVec};
use brk_types::{Halving, Height, StoredU32, Version};
use vecdb::ReadOnlyClone;
use super::Vecs;
use crate::{
indexes,
internal::{BlocksToDaysF32, LazyPerBlock, PerBlock},
internal::{BlocksToDaysF32, Identity, LazyPerBlock},
};
fn blocks_left_to_halving(height: Height, _: Halving) -> StoredU32 {
StoredU32::from(height.left_before_next_halving())
}
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
pub(crate) fn new(version: Version, indexes: &indexes::Vecs) -> Self {
let v2 = Version::TWO;
let blocks_to_halving =
PerBlock::forced_import(db, "blocks_to_halving", version + v2, indexes)?;
let epoch = LazyPerBlock::from_height_source::<Identity<Halving>, _>(
"halving_epoch",
version,
indexes.height.halving.read_only_clone(),
indexes,
);
let blocks_to_halving = LazyPerBlock::from_indexed_source(
"blocks_to_halving",
version + v2,
&indexes.height.halving,
blocks_left_to_halving,
indexes,
);
let days_to_halving = LazyPerBlock::from_computed::<BlocksToDaysF32>(
let days_to_halving = LazyPerBlock::from_lazy::<BlocksToDaysF32, StoredU32>(
"days_to_halving",
version + v2,
blocks_to_halving.height.read_only_boxed_clone(),
&blocks_to_halving,
);
Ok(Self {
epoch: PerBlock::forced_import(db, "halving_epoch", version, indexes)?,
Self {
epoch,
blocks_to_halving,
days_to_halving,
})
}
}
}
@@ -1,4 +1,3 @@
mod compute;
mod import;
mod vecs;
@@ -1,11 +1,11 @@
use brk_traversable::Traversable;
use brk_types::{Halving, StoredF32, StoredU32};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlock, PerBlock};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub epoch: PerBlock<Halving, M>,
pub blocks_to_halving: PerBlock<StoredU32, M>,
use crate::internal::LazyPerBlock;
#[derive(Clone, Traversable)]
pub struct Vecs {
pub epoch: LazyPerBlock<Halving>,
pub blocks_to_halving: LazyPerBlock<StoredU32>,
pub days_to_halving: LazyPerBlock<StoredF32, StoredU32>,
}
+5 -5
View File
@@ -25,12 +25,12 @@ impl Vecs {
let lookback = LookbackVecs::forced_import(&db, version)?;
let cached_starts = lookback.cached_window_starts();
let count = CountVecs::forced_import(&db, version, indexes, &cached_starts)?;
let count = CountVecs::new(version, indexer, indexes, &cached_starts);
let interval = IntervalVecs::forced_import(&db, version, indexes, &cached_starts)?;
let size = SizeVecs::forced_import(&db, version, indexes, &cached_starts)?;
let weight = WeightVecs::forced_import(&db, version, indexes, &cached_starts, &size)?;
let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?;
let halving = HalvingVecs::forced_import(&db, version, indexes)?;
let size = SizeVecs::forced_import(&db, version, indexer, indexes, &cached_starts)?;
let weight = WeightVecs::new(version, indexer, indexes, &cached_starts, &size);
let difficulty = DifficultyVecs::new(version, indexer, indexes);
let halving = HalvingVecs::new(version, indexes);
let this = Self {
db,
@@ -9,27 +9,27 @@ impl Vecs {
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
let mut prev_timestamp = None;
self.0.compute(starting_height, exit, |vec| {
vec.compute_transform(
starting_height,
&indexer.vecs.blocks.timestamp,
|(h, timestamp, ..)| {
let interval = if let Some(prev_h) = h.decremented() {
let prev = prev_timestamp.unwrap_or_else(|| {
indexer.vecs.blocks.timestamp.collect_one(prev_h).unwrap()
});
timestamp.checked_sub(prev).unwrap_or(Timestamp::ZERO)
} else {
Timestamp::ZERO
};
prev_timestamp = Some(timestamp);
(h, interval)
},
exit,
)?;
Ok(())
})?;
Ok(())
self.0.compute_from(
starting_height,
&indexer.vecs.blocks.timestamp,
|height, timestamp| {
let interval = if let Some(previous_height) = height.decremented() {
let previous = prev_timestamp.unwrap_or_else(|| {
indexer
.vecs
.blocks
.timestamp
.collect_one(previous_height)
.unwrap()
});
timestamp.checked_sub(previous).unwrap_or(Timestamp::ZERO)
} else {
Timestamp::ZERO
};
prev_timestamp = Some(timestamp);
interval
},
exit,
)
}
}
@@ -5,7 +5,7 @@ use vecdb::Database;
use super::Vecs;
use crate::{
indexes,
internal::{PerBlockRollingAverage, WindowStartVec, Windows},
internal::{PerBlockCumulativeAverage, WindowStartVec, Windows},
};
impl Vecs {
@@ -15,7 +15,7 @@ impl Vecs {
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let interval = PerBlockRollingAverage::forced_import(
let interval = PerBlockCumulativeAverage::forced_import(
db,
"block_interval",
version,
@@ -4,9 +4,9 @@ use brk_traversable::Traversable;
use brk_types::Timestamp;
use vecdb::{Rw, StorageMode};
use crate::internal::PerBlockRollingAverage;
use crate::internal::PerBlockCumulativeAverage;
#[derive(Deref, DerefMut, Traversable)]
pub struct Vecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub PerBlockRollingAverage<Timestamp, Timestamp, M>,
#[traversable(flatten)] pub PerBlockCumulativeAverage<Timestamp, Timestamp, M>,
);
+57 -38
View File
@@ -32,28 +32,28 @@ pub struct Vecs<M: StorageMode = Rw> {
pub _9w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 63d
pub _12w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 84d
pub _89d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _3m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 90d
pub _14w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 98d
pub _3m: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 90d
pub _14w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 98d
pub _111d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _144d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _6m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 180d
pub _26w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 182d
pub _6m: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 180d
pub _26w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 182d
pub _200d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _9m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 270d
pub _350d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _12m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 360d
pub _1y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 365d
pub _14m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 420d
pub _2y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 730d
pub _2y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 730d
pub _26m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 780d
pub _3y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 1095d
pub _3y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 1095d
pub _200w: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 1400d
pub _4y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 1460d
pub _5y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 1825d
pub _6y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 2190d
pub _8y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 2920d
pub _4y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 1460d
pub _5y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 1825d
pub _6y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 2190d
pub _8y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 2920d
pub _9y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 3285d
pub _10y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 3650d
pub _10y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 3650d
pub _12y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 4380d
pub _14y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 5110d
pub _26y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 9490d
@@ -124,11 +124,11 @@ impl Vecs {
_9w,
_12w,
_89d,
_3m,
_3m: CachedVec::wrap(_3m),
_14w,
_111d,
_144d,
_6m,
_6m: CachedVec::wrap(_6m),
_26w,
_200d,
_9m,
@@ -136,16 +136,16 @@ impl Vecs {
_12m,
_1y: CachedVec::wrap(_1y),
_14m,
_2y,
_2y: CachedVec::wrap(_2y),
_26m,
_3y,
_3y: CachedVec::wrap(_3y),
_200w,
_4y,
_5y,
_6y,
_8y,
_4y: CachedVec::wrap(_4y),
_5y: CachedVec::wrap(_5y),
_6y: CachedVec::wrap(_6y),
_8y: CachedVec::wrap(_8y),
_9y,
_10y,
_10y: CachedVec::wrap(_10y),
_12y,
_14y,
_26y,
@@ -189,11 +189,11 @@ impl Vecs {
63 => &self._9w,
84 => &self._12w,
89 => &self._89d,
90 => &self._3m,
90 => &self._3m.inner,
98 => &self._14w,
111 => &self._111d,
144 => &self._144d,
180 => &self._6m,
180 => &self._6m.inner,
182 => &self._26w,
200 => &self._200d,
270 => &self._9m,
@@ -201,16 +201,16 @@ impl Vecs {
360 => &self._12m,
365 => &self._1y.inner,
420 => &self._14m,
730 => &self._2y,
730 => &self._2y.inner,
780 => &self._26m,
1095 => &self._3y,
1095 => &self._3y.inner,
1400 => &self._200w,
1460 => &self._4y,
1825 => &self._5y,
2190 => &self._6y,
2920 => &self._8y,
1460 => &self._4y.inner,
1825 => &self._5y.inner,
2190 => &self._6y.inner,
2920 => &self._8y.inner,
3285 => &self._9y,
3650 => &self._10y,
3650 => &self._10y.inner,
4380 => &self._12y,
5110 => &self._14y,
9490 => &self._26y,
@@ -218,6 +218,25 @@ impl Vecs {
}
}
pub fn cached_start_vec(&self, days: usize) -> &WindowStartVec {
match days {
1 => &self._24h,
7 => &self._1w,
30 => &self._1m,
90 => &self._3m,
180 => &self._6m,
365 => &self._1y,
730 => &self._2y,
1095 => &self._3y,
1460 => &self._4y,
1825 => &self._5y,
2190 => &self._6y,
2920 => &self._8y,
3650 => &self._10y,
_ => panic!("No cached start vec for {days} days"),
}
}
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
@@ -243,11 +262,11 @@ impl Vecs {
self.compute_rolling_start(indexes, starting_height, exit, 63, |s| &mut s._9w)?;
self.compute_rolling_start(indexes, starting_height, exit, 84, |s| &mut s._12w)?;
self.compute_rolling_start(indexes, starting_height, exit, 89, |s| &mut s._89d)?;
self.compute_rolling_start(indexes, starting_height, exit, 90, |s| &mut s._3m)?;
self.compute_rolling_start(indexes, starting_height, exit, 90, |s| &mut s._3m.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 98, |s| &mut s._14w)?;
self.compute_rolling_start(indexes, starting_height, exit, 111, |s| &mut s._111d)?;
self.compute_rolling_start(indexes, starting_height, exit, 144, |s| &mut s._144d)?;
self.compute_rolling_start(indexes, starting_height, exit, 180, |s| &mut s._6m)?;
self.compute_rolling_start(indexes, starting_height, exit, 180, |s| &mut s._6m.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 182, |s| &mut s._26w)?;
self.compute_rolling_start(indexes, starting_height, exit, 200, |s| &mut s._200d)?;
self.compute_rolling_start(indexes, starting_height, exit, 270, |s| &mut s._9m)?;
@@ -255,16 +274,16 @@ impl Vecs {
self.compute_rolling_start(indexes, starting_height, exit, 360, |s| &mut s._12m)?;
self.compute_rolling_start(indexes, starting_height, exit, 365, |s| &mut s._1y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 420, |s| &mut s._14m)?;
self.compute_rolling_start(indexes, starting_height, exit, 730, |s| &mut s._2y)?;
self.compute_rolling_start(indexes, starting_height, exit, 730, |s| &mut s._2y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 780, |s| &mut s._26m)?;
self.compute_rolling_start(indexes, starting_height, exit, 1095, |s| &mut s._3y)?;
self.compute_rolling_start(indexes, starting_height, exit, 1095, |s| &mut s._3y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 1400, |s| &mut s._200w)?;
self.compute_rolling_start(indexes, starting_height, exit, 1460, |s| &mut s._4y)?;
self.compute_rolling_start(indexes, starting_height, exit, 1825, |s| &mut s._5y)?;
self.compute_rolling_start(indexes, starting_height, exit, 2190, |s| &mut s._6y)?;
self.compute_rolling_start(indexes, starting_height, exit, 2920, |s| &mut s._8y)?;
self.compute_rolling_start(indexes, starting_height, exit, 1460, |s| &mut s._4y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 1825, |s| &mut s._5y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 2190, |s| &mut s._6y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 2920, |s| &mut s._8y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 3285, |s| &mut s._9y)?;
self.compute_rolling_start(indexes, starting_height, exit, 3650, |s| &mut s._10y)?;
self.compute_rolling_start(indexes, starting_height, exit, 3650, |s| &mut s._10y.inner)?;
self.compute_rolling_start(indexes, starting_height, exit, 4380, |s| &mut s._12y)?;
self.compute_rolling_start(indexes, starting_height, exit, 5110, |s| &mut s._14y)?;
self.compute_rolling_start(indexes, starting_height, exit, 9490, |s| &mut s._26y)?;
+4 -4
View File
@@ -37,13 +37,13 @@ pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
pub db: Database,
pub count: CountVecs<M>,
pub count: CountVecs,
pub lookback: LookbackVecs<M>,
pub interval: IntervalVecs<M>,
#[traversable(flatten)]
pub size: SizeVecs<M>,
#[traversable(flatten)]
pub weight: WeightVecs<M>,
pub difficulty: DifficultyVecs<M>,
pub halving: HalvingVecs<M>,
pub weight: WeightVecs,
pub difficulty: DifficultyVecs,
pub halving: HalvingVecs,
}
+1 -10
View File
@@ -1,6 +1,5 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::StoredU64;
use vecdb::Exit;
use super::Vecs;
@@ -16,15 +15,7 @@ impl Vecs {
let starting_height = indexer.safe_lengths().height;
let window_starts = lookback.window_starts();
self.vbytes
.compute(starting_height, &window_starts, exit, |height| {
Ok(height.compute_transform(
starting_height,
&indexer.vecs.blocks.weight,
|(h, weight, ..)| (h, StoredU64::from(weight.to_vbytes_floor())),
exit,
)?)
})?;
self.vbytes.compute(starting_height, &window_starts, exit)?;
self.size.compute(
starting_height,
@@ -1,5 +1,6 @@
use brk_error::Result;
use brk_types::Version;
use brk_indexer::Indexer;
use brk_types::{Height, StoredU64, Version, Weight};
use vecdb::Database;
use super::Vecs;
@@ -8,10 +9,15 @@ use crate::{
internal::{PerBlockFull, PerBlockRolling, WindowStartVec, Windows},
};
fn block_vbytes(_: Height, weight: Weight) -> StoredU64 {
StoredU64::from(weight.to_vbytes_floor())
}
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexer: &Indexer,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
@@ -20,6 +26,8 @@ impl Vecs {
db,
"block_vbytes",
version,
&indexer.vecs.blocks.weight,
block_vbytes,
indexes,
cached_starts,
)?,
+2 -2
View File
@@ -1,11 +1,11 @@
use brk_traversable::Traversable;
use brk_types::StoredU64;
use brk_types::{StoredU64, Weight};
use vecdb::{Rw, StorageMode};
use crate::internal::{PerBlockFull, PerBlockRolling};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub vbytes: PerBlockFull<StoredU64, M>,
pub vbytes: PerBlockFull<StoredU64, Weight, M>,
pub size: PerBlockRolling<StoredU64, M>,
}
@@ -1,20 +0,0 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::PartsPerMillion32;
use vecdb::Exit;
use super::Vecs;
impl Vecs {
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.fullness.ppm.compute_transform(
starting_height,
&indexer.vecs.blocks.weight,
|(h, weight, ..)| (h, PartsPerMillion32::from(weight.fullness())),
exit,
)?;
Ok(())
}
}
+20 -11
View File
@@ -1,32 +1,41 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::Database;
use brk_indexer::Indexer;
use brk_types::{Height, PartsPerMillion32, Version, Weight};
use super::Vecs;
use crate::{
blocks::SizeVecs,
indexes,
internal::{LazyPerBlockRolling, PercentVec, VBytesToWeight, WindowStartVec, Windows},
internal::{LazyPerBlockRolling, LazyPercentVec, VBytesToWeight, WindowStartVec, Windows},
};
fn block_fullness(_: Height, weight: Weight) -> PartsPerMillion32 {
PartsPerMillion32::from(weight.fullness())
}
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
pub(crate) fn new(
version: Version,
indexer: &Indexer,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
size: &SizeVecs,
) -> Result<Self> {
let weight = LazyPerBlockRolling::from_per_block_full::<VBytesToWeight>(
) -> Self {
let weight = LazyPerBlockRolling::from_full_parts::<VBytesToWeight>(
"block_weight",
version,
&size.vbytes,
&size.vbytes.cumulative,
&size.vbytes.rolling,
cached_starts,
indexes,
);
let fullness = PercentVec::forced_import(db, "block_fullness", version)?;
let fullness = LazyPercentVec::from_indexed_source(
"block_fullness",
version,
&indexer.vecs.blocks.weight,
block_fullness,
);
Ok(Self { weight, fullness })
Self { weight, fullness }
}
}
@@ -1,4 +1,3 @@
mod compute;
mod import;
mod vecs;
@@ -1,11 +1,10 @@
use brk_traversable::Traversable;
use brk_types::{PartsPerMillion32, StoredU64, Weight};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlockRolling, PercentVec};
use crate::internal::{LazyPerBlockRolling, LazyPercentVec};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
#[derive(Clone, Traversable)]
pub struct Vecs {
pub weight: LazyPerBlockRolling<Weight, StoredU64>,
pub fullness: PercentVec<PartsPerMillion32, M>,
pub fullness: LazyPercentVec<PartsPerMillion32, Weight>,
}
@@ -13,14 +13,14 @@
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredU32, StoredU64, Version};
use brk_types::Version;
use derive_more::{Deref, DerefMut};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use vecdb::{AnyStoredVec, AnyVec, Database, Rw, StorageMode};
use crate::{
indexes,
internal::{PerBlockRollingAverage, WindowStartVec, Windows},
internal::{CountPerBlockRollingAverage, WindowStartVec, Windows},
};
/// Per-block activity counts - reset each block.
@@ -66,15 +66,15 @@ impl AddrTypeToActivityCounts {
/// Activity count vectors for a single category (e.g., one address type or "all").
#[derive(Traversable)]
pub struct ActivityCountVecs<M: StorageMode = Rw> {
pub reactivated: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub sending: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub receiving: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub bidirectional: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub reactivated: CountPerBlockRollingAverage<M>,
pub sending: CountPerBlockRollingAverage<M>,
pub receiving: CountPerBlockRollingAverage<M>,
pub bidirectional: CountPerBlockRollingAverage<M>,
/// Distinct addresses involved in this block (sent received),
/// computed at push time as `sending + receiving - bidirectional`
/// via inclusion-exclusion. For per-type instances this is
/// per-type. For the `all` aggregate it's the cross-type total.
pub active: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub active: CountPerBlockRollingAverage<M>,
}
impl ActivityCountVecs {
@@ -90,35 +90,35 @@ impl ActivityCountVecs {
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
Ok(Self {
reactivated: PerBlockRollingAverage::forced_import(
reactivated: CountPerBlockRollingAverage::forced_import(
db,
&format!("{prefix}reactivated_addrs"),
version,
indexes,
cached_starts,
)?,
sending: PerBlockRollingAverage::forced_import(
sending: CountPerBlockRollingAverage::forced_import(
db,
&format!("{prefix}sending_addrs"),
version,
indexes,
cached_starts,
)?,
receiving: PerBlockRollingAverage::forced_import(
receiving: CountPerBlockRollingAverage::forced_import(
db,
&format!("{prefix}receiving_addrs"),
version,
indexes,
cached_starts,
)?,
bidirectional: PerBlockRollingAverage::forced_import(
bidirectional: CountPerBlockRollingAverage::forced_import(
db,
&format!("{prefix}bidirectional_addrs"),
version,
indexes,
cached_starts,
)?,
active: PerBlockRollingAverage::forced_import(
active: CountPerBlockRollingAverage::forced_import(
db,
&format!("{prefix}active_addrs"),
version,
@@ -130,53 +130,44 @@ impl ActivityCountVecs {
pub(crate) fn min_stateful_len(&self) -> usize {
self.reactivated
.block
.cumulative
.len()
.min(self.sending.block.len())
.min(self.receiving.block.len())
.min(self.bidirectional.block.len())
.min(self.active.block.len())
.min(self.sending.cumulative.len())
.min(self.receiving.cumulative.len())
.min(self.bidirectional.cumulative.len())
.min(self.active.cumulative.len())
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
[
&mut self.reactivated.block as &mut dyn AnyStoredVec,
&mut self.sending.block,
&mut self.receiving.block,
&mut self.bidirectional.block,
&mut self.active.block,
self.reactivated.stored_mut(),
self.sending.stored_mut(),
self.receiving.stored_mut(),
self.bidirectional.stored_mut(),
self.active.stored_mut(),
]
.into_par_iter()
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.reactivated.block.reset()?;
self.sending.block.reset()?;
self.receiving.block.reset()?;
self.bidirectional.block.reset()?;
self.active.block.reset()?;
self.reactivated.reset()?;
self.sending.reset()?;
self.receiving.reset()?;
self.bidirectional.reset()?;
self.active.reset()?;
Ok(())
}
#[inline(always)]
pub(crate) fn push_height(&mut self, counts: &BlockActivityCounts) {
self.reactivated.block.push(counts.reactivated.into());
self.sending.block.push(counts.sending.into());
self.receiving.block.push(counts.receiving.into());
self.bidirectional.block.push(counts.bidirectional.into());
self.reactivated.push_block(counts.reactivated.into());
self.sending.push_block(counts.sending.into());
self.receiving.push_block(counts.receiving.into());
self.bidirectional.push_block(counts.bidirectional.into());
let active = counts.sending + counts.receiving - counts.bidirectional;
self.active.block.push(active.into());
}
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
self.reactivated.compute_rest(max_from, exit)?;
self.sending.compute_rest(max_from, exit)?;
self.receiving.compute_rest(max_from, exit)?;
self.bidirectional.compute_rest(max_from, exit)?;
self.active.compute_rest(max_from, exit)?;
Ok(())
self.active.push_block(active.into());
}
}
@@ -224,11 +215,11 @@ impl AddrTypeToActivityCountVecs {
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
for type_vecs in self.0.values_mut() {
vecs.push(&mut type_vecs.reactivated.block);
vecs.push(&mut type_vecs.sending.block);
vecs.push(&mut type_vecs.receiving.block);
vecs.push(&mut type_vecs.bidirectional.block);
vecs.push(&mut type_vecs.active.block);
vecs.push(type_vecs.reactivated.stored_mut());
vecs.push(type_vecs.sending.stored_mut());
vecs.push(type_vecs.receiving.stored_mut());
vecs.push(type_vecs.bidirectional.stored_mut());
vecs.push(type_vecs.active.stored_mut());
}
vecs.into_par_iter()
}
@@ -240,13 +231,6 @@ impl AddrTypeToActivityCountVecs {
Ok(())
}
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
for type_vecs in self.0.values_mut() {
type_vecs.compute_rest(max_from, exit)?;
}
Ok(())
}
#[inline(always)]
pub(crate) fn push_height(&mut self, counts: &AddrTypeToActivityCounts) {
for (vecs, c) in self.0.values_mut().zip(counts.0.values()) {
@@ -301,12 +285,6 @@ impl AddrActivityVecs {
Ok(())
}
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
self.all.compute_rest(max_from, exit)?;
self.by_addr_type.compute_rest(max_from, exit)?;
Ok(())
}
#[inline(always)]
pub(crate) fn push_height(&mut self, counts: &AddrTypeToActivityCounts) {
let totals = counts.totals();
@@ -1,67 +1,44 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredU64, Version};
use brk_types::{StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes},
internal::{LazyPerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes},
};
use super::TotalAddrCountVecs;
/// New address count per block (global + per-type).
#[derive(Deref, DerefMut, Traversable)]
pub struct NewAddrCountVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub WithAddrTypes<PerBlockCumulativeRolling<StoredU64, M>>,
#[derive(Clone, Deref, DerefMut, Traversable)]
pub struct NewAddrCountVecs(
#[traversable(flatten)] pub WithAddrTypes<LazyPerBlockCumulativeRolling<StoredU64>>,
);
impl NewAddrCountVecs {
pub(crate) fn forced_import(
db: &Database,
pub(crate) fn new(
version: Version,
total: &TotalAddrCountVecs,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
Ok(Self(
WithAddrTypes::<PerBlockCumulativeRolling<StoredU64>>::forced_import(
db,
) -> Self {
Self(WithAddrTypes {
all: LazyPerBlockCumulativeRolling::from_source(
"new_addr_count",
version,
indexes,
&total.all,
cached_starts,
)?,
))
}
pub(crate) fn compute(
&mut self,
max_from: Height,
total_addr_count: &TotalAddrCountVecs,
exit: &Exit,
) -> Result<()> {
self.0.all.cumulative.height.compute_transform(
max_from,
&total_addr_count.all.height,
|(height, total, ..)| (height, StoredU64::from(total)),
exit,
)?;
for ((_, new), (_, total)) in self
.0
.by_addr_type
.iter_mut()
.zip(total_addr_count.by_addr_type.iter())
{
new.cumulative.height.compute_transform(
max_from,
&total.height,
|(height, total, ..)| (height, StoredU64::from(total)),
exit,
)?;
}
Ok(())
indexes,
),
by_addr_type: total.by_addr_type.map_with_name(|name, total| {
LazyPerBlockCumulativeRolling::from_source(
&format!("{name}_new_addr_count"),
version,
total,
cached_starts,
indexes,
)
}),
})
}
}
@@ -9,8 +9,8 @@ use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{
indexes, inputs,
internal::{
PerBlockCumulativeRolling, PerBlockRollingAverage, PercentCumulativeRolling,
WindowStartVec, Windows, WithAddrTypes,
CountPerBlockRollingAverage, PerBlockCumulativeRolling, PerBlockRollingAverage,
PercentCumulativeRolling, WindowStartVec, Windows, WithAddrTypes,
},
outputs,
};
@@ -59,10 +59,10 @@ use super::state::AddrTypeToAddrEventCount;
/// empty blocks). The denominator (distinct active addrs per block)
/// lives on `ActivityCountVecs::active` (`addrs.activity.all.active`),
/// derived from `sending + receiving - bidirectional`. Both fields
/// use `PerBlockRollingAverage` so their lazy 24h/1w/1m/1y series are
/// rolling *averages* of the per-block values. Sums and cumulatives of
/// distinct-address counts would be misleading because the same
/// address can appear in multiple blocks.
/// expose lazy 24h/1w/1m/1y rolling *averages* of the per-block values.
/// Sums and cumulatives of distinct-address counts would be misleading
/// because the same address can appear in multiple blocks, so the
/// cumulative count remains an internal source for the lazy views.
#[derive(Traversable)]
pub struct AddrEventsVecs<M: StorageMode = Rw> {
pub output_to_reused_addr_count: WithAddrTypes<PerBlockCumulativeRolling<StoredU64, M>>,
@@ -70,7 +70,7 @@ pub struct AddrEventsVecs<M: StorageMode = Rw> {
pub spendable_output_to_reused_addr_share: PercentCumulativeRolling<PartsPerMillion32, M>,
pub input_from_reused_addr_count: WithAddrTypes<PerBlockCumulativeRolling<StoredU64, M>>,
pub input_from_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32, M>>,
pub active_reused_addr_count: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub active_reused_addr_count: CountPerBlockRollingAverage<M>,
pub active_reused_addr_share: PerBlockRollingAverage<StoredF32, StoredF32, M>,
}
@@ -118,7 +118,7 @@ impl AddrEventsVecs {
let input_from_reused_addr_share =
import_percent(&format!("input_from_{name}_addr_share"))?;
let active_reused_addr_count = PerBlockRollingAverage::forced_import(
let active_reused_addr_count = CountPerBlockRollingAverage::forced_import(
db,
&format!("active_{name}_addr_count"),
version,
@@ -148,7 +148,7 @@ impl AddrEventsVecs {
self.output_to_reused_addr_count
.min_stateful_len()
.min(self.input_from_reused_addr_count.min_stateful_len())
.min(self.active_reused_addr_count.block.len())
.min(self.active_reused_addr_count.cumulative.len())
.min(self.active_reused_addr_share.block.len())
}
@@ -159,7 +159,7 @@ impl AddrEventsVecs {
.par_iter_height_mut()
.chain(self.input_from_reused_addr_count.par_iter_height_mut())
.chain([
&mut self.active_reused_addr_count.block as &mut dyn AnyStoredVec,
self.active_reused_addr_count.stored_mut(),
&mut self.active_reused_addr_share.block as &mut dyn AnyStoredVec,
])
}
@@ -167,7 +167,7 @@ impl AddrEventsVecs {
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.output_to_reused_addr_count.reset_height()?;
self.input_from_reused_addr_count.reset_height()?;
self.active_reused_addr_count.block.reset()?;
self.active_reused_addr_count.reset()?;
self.active_reused_addr_share.block.reset()?;
Ok(())
}
@@ -185,8 +185,7 @@ impl AddrEventsVecs {
self.input_from_reused_addr_count
.push_height(spends.sum(), spends.values().copied());
self.active_reused_addr_count
.block
.push(StoredU32::from(active_reused_addr_count));
.push_block(StoredU32::from(active_reused_addr_count));
// Stored as a percentage in [0, 100] to match the rest of the
// codebase (Unit.percentage on the website expects 0..100). The
// `active_addr_count` denominator lives on `ActivityCountVecs`
@@ -209,8 +208,6 @@ impl AddrEventsVecs {
inputs_by_type: &inputs::ByTypeVecs,
exit: &Exit,
) -> Result<()> {
self.active_reused_addr_count
.compute_rest(starting_lengths.height, exit)?;
self.active_reused_addr_share
.compute_rest(starting_lengths.height, exit)?;
@@ -4,7 +4,7 @@ use brk_cohort::{AddrGroups, AmountRange, Filter, Filtered, OverAmount, UnderAmo
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64, Version};
use brk_types::{Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};
@@ -111,18 +111,11 @@ impl AddrCohorts {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.0.par_iter_mut().try_for_each(|v| {
v.compute_rest_part2(
prices,
starting_lengths,
all_supply_sats,
all_utxo_count,
exit,
)
})
self.0
.par_iter_mut()
.try_for_each(|v| v.compute_rest_part2(prices, starting_lengths, all_supply_sats, exit))
}
/// Returns a parallel iterator over all vecs for parallel writing.
@@ -232,15 +232,9 @@ impl CohortVecs for AddrCohortVecs {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.metrics.compute_rest_part2(
prices,
starting_lengths,
all_supply_sats,
all_utxo_count,
exit,
)
self.metrics
.compute_rest_part2(prices, starting_lengths, all_supply_sats, exit)
}
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_types::{Cents, Height, Sats, StoredU64, Version};
use brk_types::{Cents, Height, Sats, Version};
use vecdb::{Exit, ReadableVec};
use crate::price;
@@ -64,7 +64,6 @@ pub trait CohortVecs: DynCohortVecs {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()>;
}
@@ -627,15 +627,8 @@ impl UTXOCohorts<Rw> {
exit,
)?;
// Clone all_supply_sats and all_utxo_count for non-all cohorts.
// Clone all_supply_sats for non-all cohorts.
let all_supply_sats = self.all.metrics.supply.total.sats.height.read_only_clone();
let all_utxo_count = self
.all
.metrics
.outputs
.unspent_count
.height
.read_only_clone();
// Destructure to allow parallel mutable access to independent fields.
let Self {
@@ -659,7 +652,6 @@ impl UTXOCohorts<Rw> {
let vc = &under_1h_value_created;
let vd = &under_1h_value_destroyed;
let ss = &all_supply_sats;
let au = &all_utxo_count;
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
Box::new(|| {
@@ -670,7 +662,6 @@ impl UTXOCohorts<Rw> {
vc,
vd,
ss,
au,
exit,
)
}),
@@ -680,44 +671,43 @@ impl UTXOCohorts<Rw> {
starting_lengths,
height_to_market_cap,
ss,
au,
exit,
)
}),
Box::new(|| {
age_range.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
under_age.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
over_age.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
over_amount.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
epoch.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
class.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
@@ -727,7 +717,6 @@ impl UTXOCohorts<Rw> {
starting_lengths,
height_to_market_cap,
ss,
au,
exit,
)
})
@@ -735,19 +724,19 @@ impl UTXOCohorts<Rw> {
Box::new(|| {
amount_range.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
under_amount.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
Box::new(|| {
type_.par_iter_mut().try_for_each(|v| {
v.metrics
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
.compute_rest_part2(prices, starting_lengths, ss, exit)
})
}),
];
@@ -76,8 +76,8 @@ impl AllCohortMetrics {
cfg: &ImportConfig,
supply: SupplyCore,
) -> Result<Self> {
let unrealized = UnrealizedFull::forced_import(cfg)?;
let realized = RealizedFull::forced_import(cfg)?;
let unrealized = UnrealizedFull::forced_import(cfg, &realized.price.ppm)?;
let asopr = AdjustedSopr::forced_import(cfg)?;
let relative = RelativeForAll::forced_import(cfg)?;
@@ -114,13 +114,6 @@ impl AllCohortMetrics {
exit,
)?;
self.unrealized.compute(
starting_lengths.height,
&prices.spot.cents.height,
&self.realized.price.cents.height,
exit,
)?;
self.asopr.compute_rest_part2(
starting_lengths,
&self.activity.transfer_volume.inner.cumulative.cents.height,
@@ -130,10 +123,6 @@ impl AllCohortMetrics {
exit,
)?;
let all_utxo_count = self.outputs.unspent_count.height.read_only_clone();
self.outputs
.compute_part2(starting_lengths.height, &all_utxo_count, exit)?;
self.cost_basis.compute_prices(
starting_lengths,
&prices.spot.cents.height,
@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64};
use brk_types::{Height, Sats};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{
@@ -46,8 +46,8 @@ impl CohortMetricsBase for BasicCohortMetrics {
impl BasicCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyCore::forced_import(cfg)?;
let unrealized = UnrealizedCore::forced_import(cfg)?;
let realized = RealizedCore::forced_import(cfg)?;
let unrealized = UnrealizedCore::forced_import(cfg, &realized.price.ppm)?;
Ok(Self {
filter: cfg.filter.clone(),
@@ -64,7 +64,6 @@ impl BasicCohortMetrics {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
@@ -75,19 +74,9 @@ impl BasicCohortMetrics {
exit,
)?;
self.unrealized.compute(
starting_lengths.height,
&prices.spot.cents.height,
&self.realized.price.cents.height,
exit,
)?;
self.supply
.compute_dominance(starting_lengths.height, all_supply_sats, exit)?;
self.outputs
.compute_part2(starting_lengths.height, all_utxo_count, exit)?;
Ok(())
}
}
@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64, Version};
use brk_types::{Height, Sats, Version};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{
@@ -26,13 +26,16 @@ pub struct CoreCohortMetrics<M: StorageMode = Rw> {
impl CoreCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let realized = RealizedCore::forced_import(cfg)?;
let unrealized = UnrealizedCore::forced_import(cfg, &realized.price.ppm)?;
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(SupplyCore::forced_import(cfg)?),
outputs: Box::new(OutputsBase::forced_import(cfg)?),
activity: Box::new(ActivityCore::forced_import(cfg)?),
realized: Box::new(RealizedCore::forced_import(cfg)?),
unrealized: Box::new(UnrealizedCore::forced_import(cfg)?),
realized: Box::new(realized),
unrealized: Box::new(unrealized),
})
}
@@ -108,8 +111,6 @@ impl CoreCohortMetrics {
) -> Result<()> {
self.supply.compute(prices, starting_lengths.height, exit)?;
self.outputs.compute_rest(starting_lengths.height, exit)?;
self.activity
.compute_rest_part1(prices, starting_lengths, exit)?;
@@ -125,7 +126,6 @@ impl CoreCohortMetrics {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
@@ -136,19 +136,9 @@ impl CoreCohortMetrics {
exit,
)?;
self.unrealized.compute(
starting_lengths.height,
&prices.spot.cents.height,
&self.realized.price.cents.height,
exit,
)?;
self.supply
.compute_dominance(starting_lengths.height, all_supply_sats, exit)?;
self.outputs
.compute_part2(starting_lengths.height, all_utxo_count, exit)?;
Ok(())
}
}
@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, StoredU64, Version};
use brk_types::{Dollars, Height, Sats, Version};
use vecdb::AnyStoredVec;
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
@@ -68,10 +68,10 @@ impl CohortMetricsBase for ExtendedCohortMetrics {
impl ExtendedCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyCore::forced_import(cfg)?;
let unrealized = UnrealizedFull::forced_import(cfg)?;
let realized = RealizedFull::forced_import(cfg)?;
let unrealized = UnrealizedFull::forced_import(cfg, &realized.price.ppm)?;
let relative = RelativeWithExtended::forced_import(cfg)?;
let relative = RelativeWithExtended::forced_import(cfg, &unrealized)?;
Ok(Self {
filter: cfg.filter.clone(),
@@ -92,7 +92,6 @@ impl ExtendedCohortMetrics {
starting_lengths: &Lengths,
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
@@ -104,13 +103,6 @@ impl ExtendedCohortMetrics {
exit,
)?;
self.unrealized.compute(
starting_lengths.height,
&prices.spot.cents.height,
&self.realized.price.cents.height,
exit,
)?;
self.cost_basis.compute_prices(
starting_lengths,
&prices.spot.cents.height,
@@ -139,9 +131,6 @@ impl ExtendedCohortMetrics {
exit,
)?;
self.outputs
.compute_part2(starting_lengths.height, all_utxo_count, exit)?;
Ok(())
}
}
@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, Sats, StoredU64, Version};
use brk_types::{Cents, Dollars, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
@@ -66,7 +66,6 @@ impl ExtendedAdjustedCohortMetrics {
under_1h_value_created: &impl ReadableVec<Height, Cents>,
under_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.inner.compute_rest_part2(
@@ -74,7 +73,6 @@ impl ExtendedAdjustedCohortMetrics {
starting_lengths,
height_to_market_cap,
all_supply_sats,
all_utxo_count,
exit,
)?;
@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64};
use brk_types::{Height, Sats};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{
@@ -24,18 +24,21 @@ pub struct MinimalCohortMetrics<M: StorageMode = Rw> {
pub outputs: Box<OutputsBase<M>>,
pub activity: Box<ActivityMinimal<M>>,
pub realized: Box<RealizedMinimal<M>>,
pub unrealized: Box<UnrealizedMinimal<M>>,
pub unrealized: Box<UnrealizedMinimal>,
}
impl MinimalCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let realized = RealizedMinimal::forced_import(cfg)?;
let unrealized = UnrealizedMinimal::new(cfg, &realized.price.ppm);
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(SupplyBase::forced_import(cfg)?),
outputs: Box::new(OutputsBase::forced_import(cfg)?),
activity: Box::new(ActivityMinimal::forced_import(cfg)?),
realized: Box::new(RealizedMinimal::forced_import(cfg)?),
unrealized: Box::new(UnrealizedMinimal::forced_import(cfg)?),
realized: Box::new(realized),
unrealized: Box::new(unrealized),
})
}
@@ -102,7 +105,6 @@ impl MinimalCohortMetrics {
exit: &Exit,
) -> Result<()> {
self.supply.compute(prices, starting_lengths.height, exit)?;
self.outputs.compute_rest(starting_lengths.height, exit)?;
self.activity
.compute_rest_part1(prices, starting_lengths, exit)?;
Ok(())
@@ -113,7 +115,6 @@ impl MinimalCohortMetrics {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
@@ -123,19 +124,9 @@ impl MinimalCohortMetrics {
exit,
)?;
self.unrealized.compute(
starting_lengths.height,
&prices.spot.cents.height,
&self.realized.price.cents.height,
exit,
)?;
self.supply
.compute_dominance(starting_lengths.height, all_supply_sats, exit)?;
self.outputs
.compute_part2(starting_lengths.height, all_utxo_count, exit)?;
Ok(())
}
}
@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64};
use brk_types::{Height, Sats};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{
@@ -28,13 +28,16 @@ pub struct TypeCohortMetrics<M: StorageMode = Rw> {
impl TypeCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let realized = RealizedMinimal::forced_import(cfg)?;
let unrealized = UnrealizedBasic::forced_import(cfg, &realized.price.ppm)?;
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(SupplyCore::forced_import(cfg)?),
outputs: Box::new(OutputsBase::forced_import(cfg)?),
activity: Box::new(ActivityMinimal::forced_import(cfg)?),
realized: Box::new(RealizedMinimal::forced_import(cfg)?),
unrealized: Box::new(UnrealizedBasic::forced_import(cfg)?),
realized: Box::new(realized),
unrealized: Box::new(unrealized),
})
}
@@ -64,7 +67,6 @@ impl TypeCohortMetrics {
exit: &Exit,
) -> Result<()> {
self.supply.compute(prices, starting_lengths.height, exit)?;
self.outputs.compute_rest(starting_lengths.height, exit)?;
self.activity
.compute_rest_part1(prices, starting_lengths, exit)?;
Ok(())
@@ -75,7 +77,6 @@ impl TypeCohortMetrics {
prices: &price::Vecs,
starting_lengths: &Lengths,
all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
@@ -85,19 +86,9 @@ impl TypeCohortMetrics {
exit,
)?;
self.unrealized.compute(
starting_lengths.height,
&prices.spot.cents.height,
&self.realized.price.cents.height,
exit,
)?;
self.supply
.compute_dominance(starting_lengths.height, all_supply_sats, exit)?;
self.outputs
.compute_part2(starting_lengths.height, all_utxo_count, exit)?;
Ok(())
}
}
@@ -276,8 +276,6 @@ pub trait CohortMetricsBase:
) -> Result<()> {
self.supply_mut()
.compute(prices, starting_lengths.height, exit)?;
self.outputs_mut()
.compute_rest(starting_lengths.height, exit)?;
self.activity_mut()
.compute_rest_part1(prices, starting_lengths, exit)?;
@@ -1,15 +1,15 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Height, PartsPerMillionSigned64, StoredF32, StoredI64, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use brk_types::{PartsPerMillionSigned64, StoredI64, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{
distribution::{
metrics::ImportConfig,
state::{CohortState, CostBasisOps, RealizedOps},
},
internal::{PerBlock, PerBlockCumulativeRolling, PerBlockWithDeltas, RatioU64F32},
internal::{PerBlockCumulativeRolling, PerBlockWithDeltas},
};
/// Base output metrics: utxo_count + delta.
@@ -17,7 +17,6 @@ use crate::{
pub struct OutputsBase<M: StorageMode = Rw> {
pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, PartsPerMillionSigned64, M>,
pub spent_count: PerBlockCumulativeRolling<StoredU64, M>,
pub utxo_turnover_1y: PerBlock<StoredF32, M>,
}
impl OutputsBase {
@@ -33,7 +32,6 @@ impl OutputsBase {
cfg.cached_starts,
)?,
spent_count: cfg.import("spent_utxo_count", v1)?,
utxo_turnover_1y: cfg.import("utxo_turnover_1y", Version::TWO)?,
})
}
@@ -60,25 +58,6 @@ impl OutputsBase {
]
}
pub(crate) fn compute_rest(&mut self, _max_from: Height, _exit: &Exit) -> Result<()> {
Ok(())
}
pub(crate) fn compute_part2(
&mut self,
max_from: Height,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.utxo_turnover_1y
.compute_binary::<StoredU64, StoredU64, RatioU64F32>(
max_from,
&self.spent_count.sum.0._1y.height,
all_utxo_count,
exit,
)
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_lengths: &Lengths,
@@ -3,7 +3,7 @@ use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{
Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats, Dollars, Height, PartsPerMillion32,
PartsPerMillionSigned64, StoredF64, Version,
PartsPerMillion64, PartsPerMillionSigned64, StoredF64, Version,
};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
@@ -11,9 +11,9 @@ use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode,
use crate::{
distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital},
internal::{
FiatPerBlockCumulativeWithSums, PercentPerBlock, PercentRollingWindows,
PriceWithRatioPerBlock, RatioCents, RatioCents64, RatioCentsSignedCents,
RatioCentsSignedDollars, RatioDollars, RollingWindows, RollingWindowsFrom1w,
FiatPerBlockCumulativeWithSums, LazyPercentPerBlock, PercentPerBlock,
PercentRollingWindows, PriceWithRatioPerBlock, RatioCents, RatioCents64,
RatioCentsSignedCents, RatioCentsSignedDollars, RollingWindows, RollingWindowsFrom1w,
ValuePerBlockCumulativeRolling,
},
price,
@@ -57,6 +57,8 @@ pub struct RealizedFull<M: StorageMode = Rw> {
#[traversable(flatten)]
pub core: RealizedCore<M>,
#[traversable(wrap = "cap", rename = "to_own_mcap")]
pub cap_to_own_mcap: LazyPercentPerBlock<PartsPerMillion32>,
pub gross_pnl: FiatPerBlockCumulativeWithSums<Cents, M>,
pub sell_side_risk_ratio: PercentRollingWindows<PartsPerMillion32, M>,
pub net_pnl: RealizedNetPnl<M>,
@@ -68,8 +70,6 @@ pub struct RealizedFull<M: StorageMode = Rw> {
#[traversable(hidden)]
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
#[traversable(wrap = "cap", rename = "to_own_mcap")]
pub cap_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
}
impl RealizedFull {
@@ -78,6 +78,13 @@ impl RealizedFull {
let v1 = Version::ONE;
let core = RealizedCore::forced_import(cfg)?;
let cap_to_own_mcap = LazyPercentPerBlock::from_indexed_source(
&cfg.name("realized_cap_to_own_mcap"),
cfg.version + Version::TWO,
&core.minimal.price.ppm.height,
mvrv_to_realized_cap_ratio,
cfg.indexes,
);
// Gross PnL
let gross_pnl: FiatPerBlockCumulativeWithSums<Cents> =
@@ -108,6 +115,7 @@ impl RealizedFull {
Ok(Self {
core,
cap_to_own_mcap,
gross_pnl,
sell_side_risk_ratio,
net_pnl,
@@ -116,7 +124,6 @@ impl RealizedFull {
capitalized,
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
cap_raw: cfg.import("cap_raw", v0)?,
cap_to_own_mcap: cfg.import("realized_cap_to_own_mcap", v1)?,
})
}
@@ -284,15 +291,6 @@ impl RealizedFull {
)?;
}
// Realized cap relative to own market cap
self.cap_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
starting_lengths.height,
&self.core.minimal.cap.usd.height,
height_to_market_cap,
exit,
)?;
// Realized profit to loss ratios
for ((ratio, profit), loss) in self
.profit_to_loss_ratio
@@ -313,6 +311,11 @@ impl RealizedFull {
}
}
#[inline(always)]
fn mvrv_to_realized_cap_ratio(_: Height, mvrv: PartsPerMillion64) -> PartsPerMillion32 {
PartsPerMillion32::from(1.0 / f64::from(mvrv))
}
#[derive(Default)]
pub struct RealizedFullAccum {
pub(crate) cap_raw: CentsSats,
@@ -331,3 +334,22 @@ impl RealizedFullAccum {
self.peak_regret.to_cents()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn realized_cap_ratio_is_inverse_mvrv() {
assert_eq!(
mvrv_to_realized_cap_ratio(Height::ZERO, PartsPerMillion64::from(2.0)),
PartsPerMillion32::from(0.5),
);
assert_eq!(
mvrv_to_realized_cap_ratio(Height::ZERO, PartsPerMillion64::from(1.0)),
PartsPerMillion32::from(1.0),
);
assert!(mvrv_to_realized_cap_ratio(Height::ZERO, PartsPerMillion64::NAN).is_nan());
assert!(mvrv_to_realized_cap_ratio(Height::ZERO, PartsPerMillion64::ZERO).is_nan());
}
}
@@ -3,31 +3,35 @@ use brk_traversable::Traversable;
use brk_types::{Dollars, Height, PartsPerMillion32, PartsPerMillionSigned32, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioDollars};
use crate::internal::{LazyPercentPerBlock, PercentPerBlock, RatioDollars};
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Extended relative metrics for own market cap (extended && rel_to_all).
#[derive(Traversable)]
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_mcap")]
pub net_unrealized_pnl_to_own_mcap: LazyPercentPerBlock<PartsPerMillionSigned32>,
#[traversable(wrap = "unrealized/profit", rename = "to_own_mcap")]
pub unrealized_profit_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/loss", rename = "to_own_mcap")]
pub unrealized_loss_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_mcap")]
pub net_unrealized_pnl_to_own_mcap: PercentPerBlock<PartsPerMillionSigned32, M>,
}
impl RelativeExtendedOwnMarketCap {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
pub(crate) fn forced_import(cfg: &ImportConfig, unrealized: &UnrealizedCore) -> Result<Self> {
let v2 = Version::new(2);
Ok(Self {
net_unrealized_pnl_to_own_mcap: LazyPercentPerBlock::from_height_source(
&cfg.name("net_unrealized_pnl_to_own_mcap"),
cfg.version + Version::new(4),
unrealized.nupl.ppm.height.clone(),
cfg.indexes,
),
unrealized_profit_to_own_mcap: cfg.import("unrealized_profit_to_own_mcap", v2)?,
unrealized_loss_to_own_mcap: cfg
.import("unrealized_loss_to_own_mcap", Version::new(3))?,
net_unrealized_pnl_to_own_mcap: cfg
.import("net_unrealized_pnl_to_own_mcap", Version::new(3))?,
})
}
@@ -52,13 +56,6 @@ impl RelativeExtendedOwnMarketCap {
own_market_cap,
exit,
)?;
self.net_unrealized_pnl_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillionSigned32>>(
max_from,
&unrealized.net_pnl.usd.height,
own_market_cap,
exit,
)?;
Ok(())
}
}
@@ -20,12 +20,11 @@ pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> {
impl RelativeExtendedOwnPnl {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
Ok(Self {
unrealized_profit_to_own_gross_pnl: cfg
.import("unrealized_profit_to_own_gross_pnl", v1)?,
unrealized_loss_to_own_gross_pnl: cfg.import("unrealized_loss_to_own_gross_pnl", v1)?,
.import("unrealized_profit_to_own_gross_pnl", Version::ONE)?,
unrealized_loss_to_own_gross_pnl: cfg
.import("unrealized_loss_to_own_gross_pnl", Version::ONE)?,
net_unrealized_pnl_to_own_gross_pnl: cfg
.import("net_unrealized_pnl_to_own_gross_pnl", Version::new(3))?,
})
@@ -27,10 +27,13 @@ pub struct RelativeWithExtended<M: StorageMode = Rw> {
}
impl RelativeWithExtended {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
pub(crate) fn forced_import(cfg: &ImportConfig, unrealized: &UnrealizedFull) -> Result<Self> {
Ok(Self {
base: RelativeFull::forced_import(cfg)?,
extended_own_market_cap: RelativeExtendedOwnMarketCap::forced_import(cfg)?,
extended_own_market_cap: RelativeExtendedOwnMarketCap::forced_import(
cfg,
&unrealized.inner,
)?,
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?,
invested_capital: RelativeInvestedCapital::forced_import(cfg)?,
})
@@ -1,13 +1,13 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Version};
use brk_types::{Cents, Dollars, PartsPerMillion64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec};
use crate::{
distribution::{metrics::ImportConfig, state::UnrealizedState},
internal::{FiatPerBlock, LazyPerBlock, NegCentsUnsignedToDollars},
internal::{FiatPerBlock, LazyPerBlock, NegCentsUnsignedToDollars, PerBlock},
};
use super::UnrealizedMinimal;
@@ -17,7 +17,7 @@ pub struct UnrealizedBasic<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub minimal: UnrealizedMinimal<M>,
pub minimal: UnrealizedMinimal,
pub profit: FiatPerBlock<Cents, M>,
pub loss: FiatPerBlock<Cents, M>,
#[traversable(wrap = "loss", rename = "negative")]
@@ -25,7 +25,10 @@ pub struct UnrealizedBasic<M: StorageMode = Rw> {
}
impl UnrealizedBasic {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
pub(crate) fn forced_import(
cfg: &ImportConfig,
mvrv: &PerBlock<PartsPerMillion64>,
) -> Result<Self> {
let v1 = Version::ONE;
let loss: FiatPerBlock<Cents> = cfg.import("unrealized_loss", v1)?;
@@ -38,7 +41,7 @@ impl UnrealizedBasic {
);
Ok(Self {
minimal: UnrealizedMinimal::forced_import(cfg)?,
minimal: UnrealizedMinimal::new(cfg, mvrv),
profit: cfg.import("unrealized_profit", v1)?,
loss,
neg_loss,
@@ -1,13 +1,13 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSigned, Version};
use brk_types::{Cents, CentsSigned, PartsPerMillion64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::{
distribution::{metrics::ImportConfig, state::UnrealizedState},
internal::{CentsSubtractToCentsSigned, FiatPerBlock},
internal::{CentsSubtractToCentsSigned, FiatPerBlock, PerBlock},
};
use super::UnrealizedBasic;
@@ -23,8 +23,11 @@ pub struct UnrealizedCore<M: StorageMode = Rw> {
}
impl UnrealizedCore {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let basic = UnrealizedBasic::forced_import(cfg)?;
pub(crate) fn forced_import(
cfg: &ImportConfig,
mvrv: &PerBlock<PartsPerMillion64>,
) -> Result<Self> {
let basic = UnrealizedBasic::forced_import(cfg, mvrv)?;
let net_unrealized_pnl = cfg.import("net_unrealized_pnl", Version::ZERO)?;
Ok(Self {
@@ -1,12 +1,12 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSigned, CentsSquaredSats, Height, Sats, Version};
use brk_types::{Cents, CentsSigned, CentsSquaredSats, Height, PartsPerMillion64, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::distribution::state::UnrealizedState;
use crate::internal::{CentsSubtractToCentsSigned, FiatPerBlock};
use crate::internal::{CentsSubtractToCentsSigned, FiatPerBlock, PerBlock};
use crate::{distribution::metrics::ImportConfig, price};
use super::UnrealizedCore;
@@ -41,9 +41,12 @@ pub struct UnrealizedFull<M: StorageMode = Rw> {
}
impl UnrealizedFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
pub(crate) fn forced_import(
cfg: &ImportConfig,
mvrv: &PerBlock<PartsPerMillion64>,
) -> Result<Self> {
let v0 = Version::ZERO;
let inner = UnrealizedCore::forced_import(cfg)?;
let inner = UnrealizedCore::forced_import(cfg, mvrv)?;
let gross_pnl = cfg.import("unrealized_gross_pnl", v0)?;
@@ -1,52 +1,54 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, PartsPerMillionSigned32, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use brk_types::{PartsPerMillion64, PartsPerMillionSigned32, Version};
use vecdb::UnaryTransform;
use crate::internal::RatioPerBlock;
use crate::internal::{LazyRatioPerBlock, PerBlock};
use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct UnrealizedMinimal<M: StorageMode = Rw> {
pub nupl: RatioPerBlock<PartsPerMillionSigned32, M>,
const VERSION: Version = Version::new(5);
#[derive(Clone, Traversable)]
pub struct UnrealizedMinimal {
pub nupl: LazyRatioPerBlock<PartsPerMillionSigned32, PartsPerMillion64>,
}
impl UnrealizedMinimal {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
nupl: RatioPerBlock::forced_import_ppm(
cfg.db,
pub(crate) fn new(cfg: &ImportConfig, mvrv: &PerBlock<PartsPerMillion64>) -> Self {
Self {
nupl: LazyRatioPerBlock::from_source::<MvrvToNupl>(
&cfg.name("nupl"),
cfg.version + Version::ONE,
cfg.indexes,
)?,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
spot_price: &impl ReadableVec<Height, Cents>,
realized_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.nupl.ppm.height.compute_transform2(
max_from,
spot_price,
realized_price,
|(i, price, realized_price, ..)| {
let p = price.as_u128();
if p == 0 {
(i, PartsPerMillionSigned32::ZERO)
} else {
let rp = realized_price.as_u128();
let ratio = (p as f64 - rp as f64) / p as f64;
(i, PartsPerMillionSigned32::from(ratio))
}
},
exit,
)?;
Ok(())
cfg.version + VERSION,
mvrv,
),
}
}
}
struct MvrvToNupl;
impl UnaryTransform<PartsPerMillion64, PartsPerMillionSigned32> for MvrvToNupl {
#[inline(always)]
fn apply(mvrv: PartsPerMillion64) -> PartsPerMillionSigned32 {
PartsPerMillionSigned32::from(1.0 - 1.0 / f64::from(mvrv))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nupl_is_derived_from_mvrv() {
assert_eq!(
MvrvToNupl::apply(PartsPerMillion64::from(2.0)),
PartsPerMillionSigned32::from(0.5),
);
assert_eq!(
MvrvToNupl::apply(PartsPerMillion64::from(1.0)),
PartsPerMillionSigned32::ZERO,
);
assert!(MvrvToNupl::apply(PartsPerMillion64::NAN).is_nan());
assert!(MvrvToNupl::apply(PartsPerMillion64::ZERO).is_nan());
}
}
+5 -24
View File
@@ -48,7 +48,7 @@ pub struct AddrMetricsVecs<M: StorageMode = Rw> {
pub empty: AddrCountsVecs<M>,
pub activity: AddrActivityVecs<M>,
pub total: TotalAddrCountVecs<M>,
pub new: NewAddrCountVecs<M>,
pub new: NewAddrCountVecs,
pub reused: ReusedAddrVecs<M>,
pub respent: ReusedAddrVecs<M>,
pub exposed: ExposedAddrVecs<M>,
@@ -225,7 +225,8 @@ impl Vecs {
let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?;
// Per-block delta of total (global + per-type)
let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes, cached_starts)?;
let new_addr_count =
NewAddrCountVecs::new(version, &total_addr_count, indexes, cached_starts);
// Reused address tracking (counts + per-block uses + percent).
// `reused_*` uses the receive-side predicate (funded_txo_count > 1,
@@ -642,13 +643,6 @@ impl Vecs {
exit,
)?;
self.addrs
.activity
.compute_rest(starting_lengths.height, exit)?;
self.addrs
.new
.compute(starting_lengths.height, &self.addrs.total, exit)?;
// 7. Compute rest part2 (relative metrics)
let height_to_market_cap = self
.utxo_cohorts
@@ -677,21 +671,8 @@ impl Vecs {
.sats
.height
.read_only_clone();
let all_utxo_count = self
.utxo_cohorts
.all
.metrics
.outputs
.unspent_count
.height
.read_only_clone();
self.addr_cohorts.compute_rest_part2(
prices,
&starting_lengths,
&all_supply_sats,
&all_utxo_count,
exit,
)?;
self.addr_cohorts
.compute_rest_part2(prices, &starting_lengths, &all_supply_sats, exit)?;
let exit = exit.clone();
self.db.run_bg(move |db| {
@@ -231,7 +231,6 @@ impl Vecs {
.spending_exposure
.height
.push(StoredF64::from(exposures[index]));
cohort.mobility.height.push(mobility);
cohort.supply.mobile.sats.height.push(cohort_mobile_supply);
cohort
.supply
@@ -301,7 +300,7 @@ impl Vecs {
}
fn primary_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = Vec::with_capacity(AGE_COHORT_COUNT * 5 + 5 + HORIZON_COUNT);
let mut vecs = Vec::with_capacity(AGE_COHORT_COUNT * 4 + 5 + HORIZON_COUNT);
for cohort in self.age_range.iter_mut() {
vecs.extend(cohort.primary_vecs_mut());
@@ -324,11 +323,10 @@ impl Vecs {
}
impl CohortVecs {
fn primary_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; 5] {
fn primary_vecs_mut(&mut self) -> [&mut dyn AnyStoredVec; 4] {
[
&mut self.spending_rate.height,
&mut self.spending_exposure.height,
&mut self.mobility.height,
&mut self.supply.mobile.sats.height,
&mut self.supply.immobile.sats.height,
]
@@ -2,19 +2,29 @@ use std::path::Path;
use brk_cohort::{AgeRange, CohortContext};
use brk_error::Result;
use brk_types::Version;
use brk_types::{StoredF64, Version};
use vecdb::{ReadableCloneableVec, UnaryTransform};
use super::{CohortVecs, DB_NAME, HorizonVecs, Horizons, Split, Vecs};
use super::{CohortVecs, DB_NAME, HorizonVecs, Horizons, Split, Vecs, mobility};
use crate::{
indexes,
internal::{
FiatPerBlock, PerBlock, PriceWithRatioPerBlock, ValuePerBlock,
FiatPerBlock, LazyPerBlock, PerBlock, PriceWithRatioPerBlock, ValuePerBlock,
db_utils::{finalize_db, open_db},
},
};
const VERSION: Version = Version::TWO;
struct ExposureToMobility;
impl UnaryTransform<StoredF64, StoredF64> for ExposureToMobility {
#[inline(always)]
fn apply(exposure: StoredF64) -> StoredF64 {
StoredF64::from(mobility(*exposure))
}
}
fn import_split<T>(mut import: impl FnMut(&str) -> Result<T>) -> Result<Split<T>> {
Ok(Split {
mobile: import("mobile")?,
@@ -29,20 +39,21 @@ impl CohortVecs {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let spending_rate =
PerBlock::forced_import(db, &format!("{name}_spending_rate"), version, indexes)?;
let spending_exposure =
PerBlock::forced_import(db, &format!("{name}_spending_exposure"), version, indexes)?;
let mobility = LazyPerBlock::from_computed::<ExposureToMobility>(
&format!("{name}_mobility"),
version,
spending_exposure.height.read_only_boxed_clone(),
&spending_exposure,
);
Ok(Self {
spending_rate: PerBlock::forced_import(
db,
&format!("{name}_spending_rate"),
version,
indexes,
)?,
spending_exposure: PerBlock::forced_import(
db,
&format!("{name}_spending_exposure"),
version,
indexes,
)?,
mobility: PerBlock::forced_import(db, &format!("{name}_mobility"), version, indexes)?,
spending_rate,
spending_exposure,
mobility,
supply: import_split(|side| {
ValuePerBlock::forced_import(db, &format!("{name}_{side}_supply"), version, indexes)
})?,
@@ -3,7 +3,9 @@ use brk_traversable::Traversable;
use brk_types::{Cents, StoredF64};
use vecdb::{Database, Rw, StorageMode};
use crate::internal::{FiatPerBlock, PerBlock, PriceWithRatioPerBlock, ValuePerBlock};
use crate::internal::{
FiatPerBlock, LazyPerBlock, PerBlock, PriceWithRatioPerBlock, ValuePerBlock,
};
#[derive(Clone, Copy, Traversable)]
pub struct Horizons<T> {
@@ -110,7 +112,7 @@ pub struct Split<T> {
pub struct CohortVecs<M: StorageMode = Rw> {
pub spending_rate: PerBlock<StoredF64, M>,
pub spending_exposure: PerBlock<StoredF64, M>,
pub mobility: PerBlock<StoredF64, M>,
pub mobility: LazyPerBlock<StoredF64>,
pub supply: Split<ValuePerBlock<M>>,
}
@@ -28,13 +28,6 @@ pub(crate) fn compute_rest(
exit,
)?;
derived.ratio.height.compute_divide(
starting_height,
&derived.liveliness.height,
&derived.vaultedness.height,
exit,
)?;
Ok(())
}
@@ -6,7 +6,8 @@ use super::{DerivedVecs, Vecs};
use crate::{
indexes,
internal::{
LazyPerBlock, OneMinusF64, PerBlock, PerBlockCumulativeRolling, WindowStartVec, Windows,
LazyPerBlock, OddsF64, OneMinusF64, PerBlock, PerBlockCumulativeRolling, WindowStartVec,
Windows,
},
};
@@ -32,11 +33,17 @@ impl DerivedVecs {
liveliness.height.read_only_boxed_clone(),
&liveliness,
);
let ratio = LazyPerBlock::from_computed::<OddsF64>(
&name("activity_to_vaultedness"),
version,
liveliness.height.read_only_boxed_clone(),
&liveliness,
);
Ok(Self {
liveliness,
vaultedness,
ratio: PerBlock::forced_import(db, &name("activity_to_vaultedness"), version, indexes)?,
ratio,
})
}
}
@@ -9,7 +9,7 @@ use crate::internal::{LazyPerBlock, PerBlock, PerBlockCumulativeRolling};
pub struct DerivedVecs<M: StorageMode = Rw> {
pub liveliness: PerBlock<StoredF64, M>,
pub vaultedness: LazyPerBlock<StoredF64>,
pub ratio: PerBlock<StoredF64, M>,
pub ratio: LazyPerBlock<StoredF64>,
}
#[derive(Deref, DerefMut, Traversable)]
+3 -3
View File
@@ -3,7 +3,7 @@ use brk_types::{
Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Minute10, Minute30, Month1, Month3,
Month6, StoredU64, Version, Week1, Year1, Year10,
};
use vecdb::{Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{CachedVec, Database, EagerVec, ImportableVec, PcoVec, Rw, StorageMode};
use brk_error::Result;
@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
pub hour1: M::Stored<EagerVec<PcoVec<Height, Hour1>>>,
pub hour4: M::Stored<EagerVec<PcoVec<Height, Hour4>>>,
pub hour12: M::Stored<EagerVec<PcoVec<Height, Hour12>>>,
pub day1: M::Stored<EagerVec<PcoVec<Height, Day1>>>,
pub day1: CachedVec<M::Stored<EagerVec<PcoVec<Height, Day1>>>>,
pub day3: M::Stored<EagerVec<PcoVec<Height, Day3>>>,
pub epoch: M::Stored<EagerVec<PcoVec<Height, Epoch>>>,
pub halving: M::Stored<EagerVec<PcoVec<Height, Halving>>>,
@@ -35,7 +35,7 @@ impl Vecs {
hour1: EagerVec::forced_import(db, "hour1", version)?,
hour4: EagerVec::forced_import(db, "hour4", version)?,
hour12: EagerVec::forced_import(db, "hour12", version)?,
day1: EagerVec::forced_import(db, "day1", version)?,
day1: CachedVec::wrap(EagerVec::forced_import(db, "day1", version)?),
day3: EagerVec::forced_import(db, "day3", version)?,
epoch: EagerVec::forced_import(db, "epoch", version)?,
halving: EagerVec::forced_import(db, "halving", version)?,
+1 -1
View File
@@ -254,7 +254,7 @@ impl Vecs {
.collect_one(prev_height)
.unwrap_or_default();
self.height.day1.compute_transform(
self.height.day1.inner.compute_transform(
starting_height,
&self.timestamp.monotonic,
|(h, ts, ..)| (h, Day1::try_from(Date::from(ts)).unwrap()),
@@ -14,11 +14,8 @@ impl Vecs {
) -> Result<()> {
self.db.sync_bg_tasks()?;
let starting_lengths = indexer.safe_lengths();
self.spent.compute(indexer, exit)?;
self.count.compute(indexer, blocks, exit)?;
self.per_sec.compute(&self.count, &starting_lengths, exit)?;
self.by_type.compute(indexer, exit)?;
let exit = exit.clone();
+3 -3
View File
@@ -6,12 +6,12 @@ use brk_types::Version;
use crate::{
indexes,
internal::{
WindowStartVec, Windows,
LazyPerSecondWindows, WindowStartVec, Windows,
db_utils::{finalize_db, open_db},
},
};
use super::{ByTypeVecs, CountVecs, PerSecVecs, SpentVecs, Vecs};
use super::{ByTypeVecs, CountVecs, SpentVecs, Vecs};
impl Vecs {
pub(crate) fn forced_import(
@@ -25,7 +25,7 @@ impl Vecs {
let spent = SpentVecs::forced_import(&db, version)?;
let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?;
let per_sec = PerSecVecs::forced_import(&db, version, indexes)?;
let per_sec = LazyPerSecondWindows::new("inputs_per_sec", version, &count.rolling.sum);
let by_type = ByTypeVecs::forced_import(&db, version, indexes, cached_starts)?;
let this = Self {
+3 -3
View File
@@ -1,6 +1,5 @@
pub mod by_type;
pub mod count;
pub mod per_sec;
pub mod spent;
mod compute;
@@ -9,9 +8,10 @@ mod import;
use brk_traversable::Traversable;
use vecdb::{Database, Rw, StorageMode};
use crate::internal::LazyPerSecondWindows;
pub use by_type::Vecs as ByTypeVecs;
pub use count::Vecs as CountVecs;
pub use per_sec::Vecs as PerSecVecs;
pub use spent::Vecs as SpentVecs;
pub const DB_NAME: &str = "inputs";
@@ -23,6 +23,6 @@ pub struct Vecs<M: StorageMode = Rw> {
pub spent: SpentVecs<M>,
pub count: CountVecs<M>,
pub per_sec: PerSecVecs<M>,
pub per_sec: LazyPerSecondWindows,
pub by_type: ByTypeVecs<M>,
}
@@ -1,29 +0,0 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_types::StoredF32;
use vecdb::Exit;
use super::Vecs;
use crate::{inputs::CountVecs, internal::Windows};
impl Vecs {
pub(crate) fn compute(
&mut self,
count: &CountVecs,
starting_lengths: &Lengths,
exit: &Exit,
) -> Result<()> {
let h = starting_lengths.height;
let sums = count.rolling.sum.0.as_array();
let per_sec = self.0.as_mut_array();
for (i, &secs) in Windows::<()>::SECS.iter().enumerate() {
per_sec[i].height.compute_transform(
h,
&sums[i].height,
|(h, sum, ..)| (h, StoredF32::from(*sum as f64 / secs)),
exit,
)?;
}
Ok(())
}
}
@@ -1,21 +0,0 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::Database;
use super::Vecs;
use crate::{
indexes,
internal::{PerBlock, Windows},
};
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Windows::try_from_fn(|suffix| {
PerBlock::forced_import(db, &format!("inputs_per_sec_{suffix}"), version, indexes)
})?))
}
}
@@ -1,5 +0,0 @@
mod compute;
mod import;
mod vecs;
pub use vecs::Vecs;
@@ -1,8 +0,0 @@
use brk_traversable::Traversable;
use brk_types::StoredF32;
use vecdb::{Rw, StorageMode};
use crate::internal::{PerBlock, Windows};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw>(#[traversable(flatten)] pub Windows<PerBlock<StoredF32, M>>);
@@ -8,10 +8,10 @@ use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{
AnyStoredVec, AnyVec, Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode,
VecValue, WritableVec,
AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecValue, WritableVec,
};
use super::lazy_cumulative_rolling::lazy_parts;
use crate::{
indexes,
internal::{
@@ -46,26 +46,12 @@ where
) -> Result<Self> {
let cumulative =
PerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let (block, sum, average) =
lazy_parts(name, version, &cumulative.height, cached_starts, indexes);
let last_cumulative = cumulative
.height
.collect_last()
.map(|value| (cumulative.height.len(), value));
let cumulative_source = cumulative.height.read_only_boxed_clone();
let block = LazyPreviousDeltaVec::new(name, version, cumulative_source);
let sum = LazyRollingSumsFromHeight::new(
&format!("{name}_sum"),
version,
&cumulative.height,
cached_starts,
indexes,
);
let average = LazyRollingAvgsFromHeight::new(
&format!("{name}_average"),
version,
&cumulative.height,
cached_starts,
indexes,
);
Ok(Self {
block,
@@ -166,13 +152,13 @@ where
#[cfg(test)]
mod tests {
use brk_types::{Height, StoredU64, Version};
use brk_types::{Height, StoredU32, StoredU64, Version};
use vecdb::{
AnyStoredVec, Database, EagerVec, ImportableVec, PcoVec, ReadableCloneableVec, ReadableVec,
WritableVec,
};
use crate::internal::LazyPreviousDeltaVec;
use crate::internal::{LazyPreviousDeltaVec, StoredU64ToStoredU32};
#[test]
fn lazy_block_is_the_delta_of_cumulative() {
@@ -203,7 +189,27 @@ mod tests {
block.collect_range_at(0, 3),
[1_u64, 2, 3].map(StoredU64::from)
);
assert_eq!(
block.collect_range_at(1, 3),
[2_u64, 3].map(StoredU64::from)
);
let transformed =
LazyPreviousDeltaVec::<Height, StoredU64, StoredU32, StoredU64ToStoredU32>::transformed(
"transformed",
Version::ONE,
cumulative.read_only_boxed_clone(),
);
assert_eq!(
transformed.collect_range_at(0, 3),
[1_u32, 2, 3].map(StoredU32::from)
);
assert_eq!(
transformed.collect_range_at(1, 3),
[2_u32, 3].map(StoredU32::from)
);
drop(transformed);
drop(block);
drop(cumulative);
drop(db);
@@ -1,12 +1,12 @@
//! PerBlockFull - base EagerVec + cumulative PerBlock + RollingComplete.
//!
//! For metrics with stored per-block data, cumulative sums, and rolling windows.
//! Stored cumulative and rolling statistics with a lazy one-source block view.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{
Database, Exit, LazyVecFrom1, ReadableCloneableVec, ReadableVec, Rw, StorageMode, VecValue,
};
use crate::{
indexes,
@@ -14,28 +14,33 @@ use crate::{
};
#[derive(Traversable)]
pub struct PerBlockFull<T, M: StorageMode = Rw>
pub struct PerBlockFull<T, S, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
S: VecValue,
{
pub block: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub block: LazyVecFrom1<Height, T, Height, S>,
pub cumulative: PerBlock<T, M>,
#[traversable(flatten)]
pub rolling: RollingComplete<T, M>,
}
impl<T> PerBlockFull<T>
impl<T, S> PerBlockFull<T, S>
where
T: NumericValue + JsonSchema,
S: VecValue,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
compute_block: fn(Height, S) -> T,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let block = EagerVec::forced_import(db, name, version)?;
let block =
LazyVecFrom1::init(name, version, source.read_only_boxed_clone(), compute_block);
let cumulative =
PerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let rolling = RollingComplete::forced_import(
@@ -54,23 +59,42 @@ where
})
}
/// Compute base data via closure, then cumulative + rolling distribution.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_base: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: From<f64> + Default + Copy + Ord,
f64: From<T>,
{
compute_base(&mut self.block)?;
self.cumulative
.height
.compute_cumulative(max_from, &self.block, exit)?;
self.rolling.compute(max_from, windows, &self.block, exit)?;
Ok(())
compute_rest(
&mut self.cumulative,
&mut self.rolling,
&self.block,
max_from,
windows,
exit,
)
}
}
fn compute_rest<T>(
cumulative: &mut PerBlock<T>,
rolling: &mut RollingComplete<T>,
source: &impl ReadableVec<Height, T>,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
) -> Result<()>
where
T: NumericValue + JsonSchema + From<f64> + Default + Copy + Ord,
f64: From<T>,
{
cumulative
.height
.compute_cumulative(max_from, source, exit)?;
rolling.compute(max_from, windows, source, exit)?;
Ok(())
}
@@ -0,0 +1,120 @@
//! Lazy counterpart to `PerBlockCumulativeRolling`.
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{ReadableCloneableVec, VecValue};
use crate::{
indexes,
internal::{
Identity, LazyPerBlock, LazyPreviousDeltaVec, LazyRollingAvgsFromHeight,
LazyRollingSumsFromHeight, NumericValue, PerBlock, WindowStartVec, Windows,
},
};
pub(super) fn lazy_parts<T>(
name: &str,
version: Version,
cumulative: &(impl ReadableCloneableVec<Height, T> + 'static),
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> (
LazyPreviousDeltaVec<Height, T>,
LazyRollingSumsFromHeight<T>,
LazyRollingAvgsFromHeight<T>,
)
where
T: NumericValue + JsonSchema,
{
(
LazyPreviousDeltaVec::new(name, version, cumulative.read_only_boxed_clone()),
LazyRollingSumsFromHeight::new(
&format!("{name}_sum"),
version,
cumulative,
cached_starts,
indexes,
),
LazyRollingAvgsFromHeight::new(
&format!("{name}_average"),
version,
cumulative,
cached_starts,
indexes,
),
)
}
#[derive(Clone, Traversable)]
pub struct LazyPerBlockCumulativeRolling<T>
where
T: NumericValue + JsonSchema,
{
pub block: LazyPreviousDeltaVec<Height, T>,
pub cumulative: LazyPerBlock<T>,
pub sum: LazyRollingSumsFromHeight<T>,
pub average: LazyRollingAvgsFromHeight<T>,
}
impl<T> LazyPerBlockCumulativeRolling<T>
where
T: NumericValue + JsonSchema,
{
fn from_cumulative(
name: &str,
version: Version,
cumulative: LazyPerBlock<T>,
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> Self {
let (block, sum, average) =
lazy_parts(name, version, &cumulative.height, cached_starts, indexes);
Self {
block,
cumulative,
sum,
average,
}
}
pub(crate) fn from_source(
name: &str,
version: Version,
source: &PerBlock<T>,
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> Self {
let cumulative = LazyPerBlock::from_computed::<Identity<T>>(
&format!("{name}_cumulative"),
version,
source.height.read_only_boxed_clone(),
source,
);
Self::from_cumulative(name, version, cumulative, cached_starts, indexes)
}
pub(crate) fn from_indexed_source<S>(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
compute_cumulative: fn(Height, S) -> T,
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> Self
where
S: VecValue,
{
let cumulative = LazyPerBlock::from_indexed_source(
&format!("{name}_cumulative"),
version,
source,
compute_cumulative,
indexes,
);
Self::from_cumulative(name, version, cumulative, cached_starts, indexes)
}
}
@@ -6,13 +6,14 @@ use vecdb::{ReadableCloneableVec, UnaryTransform};
use crate::{
indexes,
internal::{
ComputedVecValue, LazyPerBlock, LazyRollingComplete, NumericValue, PerBlockFull,
WindowStartVec, Windows,
ComputedVecValue, LazyPerBlock, LazyRollingComplete, NumericValue, PerBlock,
RollingComplete, WindowStartVec, Windows,
},
};
/// Lazy analog of `PerBlockRolling<T>`: lazy cumulative + lazy rolling complete.
/// Derived by transforming a `PerBlockFull<S1T>`. Zero stored vecs.
/// Derived by transforming another metric's cumulative and rolling parts.
/// Zero stored vecs.
#[derive(Clone, Traversable)]
pub struct LazyPerBlockRolling<T, S1T>
where
@@ -29,25 +30,26 @@ where
T: NumericValue + JsonSchema + 'static,
S1T: NumericValue + JsonSchema,
{
pub(crate) fn from_per_block_full<F: UnaryTransform<S1T, T>>(
pub(crate) fn from_full_parts<F: UnaryTransform<S1T, T>>(
name: &str,
version: Version,
source: &PerBlockFull<S1T>,
source_cumulative: &PerBlock<S1T>,
source_rolling: &RollingComplete<S1T>,
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> Self {
let cumulative = LazyPerBlock::from_computed::<F>(
&format!("{name}_cumulative"),
version,
source.cumulative.height.read_only_boxed_clone(),
&source.cumulative,
source_cumulative.height.read_only_boxed_clone(),
source_cumulative,
);
let rolling = LazyRollingComplete::from_rolling_complete::<F>(
name,
version,
&cumulative.height,
&source.rolling,
source_rolling,
cached_starts,
indexes,
);
@@ -4,6 +4,7 @@ mod cached;
mod cumulative_rolling;
mod distribution;
mod full;
mod lazy_cumulative_rolling;
mod lazy_distribution;
mod lazy_rolling;
mod resolutions;
@@ -17,6 +18,7 @@ pub use cached::*;
pub use cumulative_rolling::*;
pub use distribution::*;
pub use full::*;
pub use lazy_cumulative_rolling::*;
pub use lazy_distribution::*;
pub use lazy_rolling::*;
pub use resolutions::*;
@@ -1,24 +1,142 @@
//! PerBlock with rolling average (no distribution stats).
//! Per-block values with rolling averages derived from cumulative prefix sums.
//!
//! Stored height data + cumulative + lazy 4-window rolling averages.
//! Rolling averages are computed on-the-fly from the cumulative via DeltaAvg.
//!
//! Type parameters:
//! - `T`: per-block value type
//! - `C`: cumulative type, defaults to `T`. Use a wider type (e.g., `StoredU64`)
//! when the prefix sum of `T` values could overflow `T`.
//! Exact integer/timestamp metrics use a cumulative source of truth and a lazy
//! block view. Float metrics retain their stored block values because
//! subtracting cumulative floats would not reproduce them exactly.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use brk_types::{Height, StoredU32, StoredU64, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, Ident, ImportableVec, PcoVec,
ReadableCloneableVec, ReadableVec, Rw, StorageMode, UnaryTransform, VecValue, WritableVec,
};
use crate::indexes;
use crate::internal::{LazyRollingAvgsFromHeight, NumericValue, WindowStartVec, Windows};
use crate::internal::{
LazyPreviousDeltaVec, LazyRollingAvgsFromHeight, NumericValue, StoredU64ToStoredU32,
WindowStartVec, Windows,
};
/// Cumulative source of truth with lazy exact per-block values and rolling averages.
#[derive(Traversable)]
pub struct PerBlockCumulativeAverage<T, C = T, M: StorageMode = Rw, F = Ident>
where
T: NumericValue + JsonSchema,
C: NumericValue + JsonSchema,
F: UnaryTransform<C, T>,
{
pub block: LazyPreviousDeltaVec<Height, C, T, F>,
#[traversable(hidden)]
pub cumulative: M::Stored<EagerVec<PcoVec<Height, C>>>,
#[traversable(flatten)]
pub average: LazyRollingAvgsFromHeight<C>,
#[traversable(skip)]
last_cumulative: Option<(usize, C)>,
}
pub type CountPerBlockRollingAverage<M = Rw> =
PerBlockCumulativeAverage<StoredU32, StoredU64, M, StoredU64ToStoredU32>;
impl<T, C, F> PerBlockCumulativeAverage<T, C, Rw, F>
where
T: NumericValue + JsonSchema + Into<C>,
C: NumericValue + JsonSchema,
F: UnaryTransform<C, T>,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let cumulative_version = version + Version::TWO;
let cumulative: EagerVec<PcoVec<Height, C>> =
EagerVec::forced_import(db, &format!("{name}_cumulative"), cumulative_version)?;
let last_cumulative = cumulative
.collect_last()
.map(|value| (cumulative.len(), value));
let block =
LazyPreviousDeltaVec::transformed(name, version, cumulative.read_only_boxed_clone());
let average = LazyRollingAvgsFromHeight::new(
&format!("{name}_average"),
cumulative_version,
&cumulative,
cached_starts,
indexes,
);
Ok(Self {
block,
cumulative,
average,
last_cumulative,
})
}
#[inline(always)]
pub(crate) fn push_block(&mut self, value: T)
where
C: Copy,
{
let len = self.cumulative.len();
let mut cumulative = match self.last_cumulative {
Some((cached_len, value)) if cached_len == len => value,
_ => self.cumulative.collect_last().unwrap_or_default(),
};
cumulative += value.into();
self.cumulative.push(cumulative);
self.last_cumulative = Some((len + 1, cumulative));
}
pub(crate) fn compute_from<S>(
&mut self,
max_from: Height,
source: &impl ReadableVec<Height, S>,
mut transform: impl FnMut(Height, S) -> T,
exit: &Exit,
) -> Result<()>
where
S: VecValue,
C: Copy,
{
let mut cumulative = None;
self.cumulative.compute_transform(
max_from,
source,
|(height, value, this)| {
let cumulative = cumulative.get_or_insert_with(|| {
height
.decremented()
.and_then(|height| this.collect_one(height))
.unwrap_or_default()
});
*cumulative += transform(height, value).into();
(height, *cumulative)
},
exit,
)?;
self.last_cumulative = None;
Ok(())
}
pub(crate) fn reset(&mut self) -> Result<()> {
self.last_cumulative = None;
self.cumulative.reset()?;
Ok(())
}
pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec {
self.last_cumulative = None;
&mut self.cumulative
}
}
/// Stored-block fallback for values whose cumulative delta is not exact, such as floats.
#[derive(Traversable)]
pub struct PerBlockRollingAverage<T, C = T, M: StorageMode = Rw>
where
@@ -62,17 +180,6 @@ where
})
}
/// Compute height data via closure, then cumulative. Rolling averages are lazy.
pub(crate) fn compute(
&mut self,
max_from: Height,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()> {
compute_height(&mut self.block)?;
self.compute_rest(max_from, exit)
}
/// Compute cumulative from already-populated height data. Rolling averages are lazy.
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
self.cumulative
@@ -4,13 +4,14 @@ use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{
LazyVecFrom1, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec, ReadableVec, TypedVec,
UnaryTransform,
UnaryTransform, VecValue,
};
use crate::{
indexes,
internal::{
CachedPerBlock, ComputedVecValue, DerivedResolutions, NumericValue, PerBlock, Resolutions,
CachedPerBlock, ComputedVecValue, DerivedResolutions, Identity, NumericValue, PerBlock,
Resolutions,
},
};
#[derive(Clone, Deref, DerefMut, Traversable)]
@@ -125,6 +126,31 @@ where
}
}
impl<T> LazyPerBlock<T>
where
T: NumericValue + JsonSchema + 'static,
{
/// Derive a per-block metric from one height-indexed source and the height itself.
pub(crate) fn from_indexed_source<S>(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
compute: fn(Height, S) -> T,
indexes: &indexes::Vecs,
) -> Self
where
S: VecValue,
{
let indexed = LazyVecFrom1::init(
&format!("{name}_source"),
version,
source.read_only_boxed_clone(),
compute,
);
Self::from_height_source::<Identity<T>, _>(name, version, indexed, indexes)
}
}
impl<T, S1T> ReadOnlyClone for LazyPerBlock<T, S1T>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
@@ -0,0 +1,195 @@
use std::sync::Arc;
use brk_traversable::{Traversable, TreeNode, make_leaf};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
AnyExportableVec, AnyVec, CachedBoxedVec, Formattable, ReadableBoxedVec, ReadableVec, TypedVec,
VecIndex, VecValue, Version, short_type_name,
};
/// Lazily transforms one metric source with aligned index metadata.
pub struct LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue,
{
name: Arc<str>,
base_version: Version,
source: ReadableBoxedVec<I, S>,
metadata: CachedBoxedVec<I, M>,
compute: Arc<dyn Fn(I, S, M) -> T + Send + Sync>,
}
impl<I, S, M, T> LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue,
{
pub fn new(
name: &str,
version: Version,
source: ReadableBoxedVec<I, S>,
metadata: CachedBoxedVec<I, M>,
compute: impl Fn(I, S, M) -> T + Send + Sync + 'static,
) -> Self {
Self {
name: Arc::from(name),
base_version: version,
source,
metadata,
compute: Arc::new(compute),
}
}
fn for_each_value(&self, from: usize, to: usize, mut each: impl FnMut(T)) {
let metadata = self.metadata.cached();
let to = to.min(self.len()).min(metadata.len());
if from >= to {
return;
}
let source = self.source.collect_range_dyn(from, to);
for (offset, (source, metadata)) in source
.into_iter()
.zip(metadata[from..to].iter().cloned())
.enumerate()
{
each((self.compute)(I::from(from + offset), source, metadata));
}
}
}
impl<I, S, M, T> Clone for LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue,
{
fn clone(&self) -> Self {
Self {
name: Arc::clone(&self.name),
base_version: self.base_version,
source: self.source.clone(),
metadata: self.metadata.clone(),
compute: Arc::clone(&self.compute),
}
}
}
impl<I, S, M, T> AnyVec for LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue,
{
fn version(&self) -> Version {
self.base_version + self.source.version() + self.metadata.version()
}
fn name(&self) -> &str {
&self.name
}
fn index_type_to_string(&self) -> &'static str {
I::to_string()
}
fn len(&self) -> usize {
self.source.len()
}
fn value_type_to_size_of(&self) -> usize {
size_of::<T>()
}
fn value_type_to_string(&self) -> &'static str {
short_type_name::<T>()
}
fn region_names(&self) -> Vec<String> {
Vec::new()
}
}
impl<I, S, M, T> TypedVec for LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue,
{
type I = I;
type T = T;
}
impl<I, S, M, T> ReadableVec<I, T> for LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue,
{
fn read_into_at(&self, from: usize, to: usize, buf: &mut Vec<T>) {
buf.reserve(to.saturating_sub(from));
self.for_each_value(from, to, |value| buf.push(value));
}
fn for_each_range_dyn_at(&self, from: usize, to: usize, f: &mut dyn FnMut(T)) {
self.for_each_value(from, to, f);
}
fn fold_range_at<B, F: FnMut(B, T) -> B>(&self, from: usize, to: usize, init: B, f: F) -> B {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().fold(init, f)
}
fn try_fold_range_at<B, E, F: FnMut(B, T) -> Result<B, E>>(
&self,
from: usize,
to: usize,
init: B,
f: F,
) -> Result<B, E> {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().try_fold(init, f)
}
fn collect_one_at(&self, index: usize) -> Option<T> {
let source = self.source.collect_one_at(index)?;
let metadata = self.metadata.cached().get(index)?.clone();
Some((self.compute)(I::from(index), source, metadata))
}
fn read_sorted_into_at(&self, indices: &[usize], out: &mut Vec<T>) {
out.reserve(indices.len());
indices
.iter()
.filter_map(|&index| self.collect_one_at(index))
.for_each(|value| out.push(value));
}
}
impl<I, S, M, T> Traversable for LazyIndexedVec<I, S, M, T>
where
I: VecIndex,
S: VecValue,
M: VecValue,
T: VecValue + Formattable + Serialize + JsonSchema,
{
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
std::iter::once(self as &dyn AnyExportableVec)
}
fn to_tree_node(&self) -> TreeNode {
make_leaf::<I, T, _>(self)
}
}
@@ -0,0 +1,203 @@
use std::sync::Arc;
use brk_traversable::{Traversable, TreeNode, make_leaf};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
AnyExportableVec, AnyVec, Formattable, ReadableBoxedVec, ReadableVec, TypedVec, VecIndex,
VecValue, Version, short_type_name,
};
/// Lazily combines values a fixed number of positions apart from one source.
///
/// Current and previous ranges are read separately, so a large lookback does
/// not force reading the gap between them.
pub struct LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
name: Arc<str>,
base_version: Version,
source: ReadableBoxedVec<I, S>,
lookback: usize,
compute: fn(S, Option<S>) -> T,
}
impl<I, S, T> LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
pub(crate) fn new(
name: &str,
version: Version,
source: ReadableBoxedVec<I, S>,
lookback: usize,
compute: fn(S, Option<S>) -> T,
) -> Self {
Self {
name: Arc::from(name),
base_version: version,
source,
lookback,
compute,
}
}
}
impl<I, S, T> LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn for_each_lookback(&self, from: usize, to: usize, mut each: impl FnMut(T)) {
let to = to.min(self.len());
if from >= to {
return;
}
let previous_from = from.saturating_sub(self.lookback);
let previous_to = to.saturating_sub(self.lookback);
let previous = self.source.collect_range_dyn(previous_from, previous_to);
let current = self.source.collect_range_dyn(from, to);
for (offset, current) in current.into_iter().enumerate() {
let index = from + offset;
let previous = index
.checked_sub(self.lookback)
.map(|index| previous[index - previous_from].clone());
each((self.compute)(current, previous));
}
}
}
impl<I, S, T> Clone for LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn clone(&self) -> Self {
Self {
name: Arc::clone(&self.name),
base_version: self.base_version,
source: self.source.clone(),
lookback: self.lookback,
compute: self.compute,
}
}
}
impl<I, S, T> AnyVec for LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn version(&self) -> Version {
self.base_version + self.source.version()
}
fn name(&self) -> &str {
&self.name
}
fn index_type_to_string(&self) -> &'static str {
I::to_string()
}
fn len(&self) -> usize {
self.source.len()
}
fn value_type_to_size_of(&self) -> usize {
size_of::<T>()
}
fn value_type_to_string(&self) -> &'static str {
short_type_name::<T>()
}
fn region_names(&self) -> Vec<String> {
Vec::new()
}
}
impl<I, S, T> TypedVec for LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
type I = I;
type T = T;
}
impl<I, S, T> ReadableVec<I, T> for LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn read_into_at(&self, from: usize, to: usize, buf: &mut Vec<T>) {
buf.reserve(to.saturating_sub(from));
self.for_each_lookback(from, to, |value| buf.push(value));
}
fn for_each_range_dyn_at(&self, from: usize, to: usize, f: &mut dyn FnMut(T)) {
self.for_each_lookback(from, to, f);
}
fn fold_range_at<B, F: FnMut(B, T) -> B>(&self, from: usize, to: usize, init: B, f: F) -> B {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().fold(init, f)
}
fn try_fold_range_at<B, E, F: FnMut(B, T) -> Result<B, E>>(
&self,
from: usize,
to: usize,
init: B,
f: F,
) -> Result<B, E> {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().try_fold(init, f)
}
fn collect_one_at(&self, index: usize) -> Option<T> {
let current = self.source.collect_one_at(index)?;
let previous = index
.checked_sub(self.lookback)
.and_then(|index| self.source.collect_one_at(index));
Some((self.compute)(current, previous))
}
fn read_sorted_into_at(&self, indices: &[usize], out: &mut Vec<T>) {
out.reserve(indices.len());
indices
.iter()
.filter_map(|&index| self.collect_one_at(index))
.for_each(|value| out.push(value));
}
}
impl<I, S, T> Traversable for LazyLookbackVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue + Formattable + Serialize + JsonSchema,
{
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
std::iter::once(self as &dyn AnyExportableVec)
}
fn to_tree_node(&self) -> TreeNode {
make_leaf::<I, T, _>(self)
}
}
@@ -1,11 +1,21 @@
mod base;
mod derived_resolutions;
mod indexed;
mod lookback;
mod map_option;
mod per_second;
mod previous_delta;
mod since_day;
mod transform_last;
mod window;
pub use base::*;
pub use derived_resolutions::*;
pub use indexed::*;
pub use lookback::*;
pub use map_option::*;
pub use per_second::*;
pub use previous_delta::*;
pub use since_day::*;
pub use transform_last::*;
pub use window::*;
@@ -0,0 +1,40 @@
use brk_traversable::Traversable;
use brk_types::{StoredF32, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::ReadableCloneableVec;
use crate::internal::{
LazyPerBlock, LazyRollingSumFromHeight, LazyRollingSumsFromHeight, PerSecond, Windows,
};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyPerSecondWindows(pub Windows<LazyPerBlock<StoredF32, StoredU64>>);
impl LazyPerSecondWindows {
pub(crate) fn new(
name: &str,
version: Version,
source: &LazyRollingSumsFromHeight<StoredU64>,
) -> Self {
fn window<const SECONDS: u32>(
name: &str,
version: Version,
source: &LazyRollingSumFromHeight<StoredU64>,
) -> LazyPerBlock<StoredF32, StoredU64> {
LazyPerBlock::from_resolutions::<PerSecond<SECONDS>>(
name,
version,
source.height.read_only_boxed_clone(),
&source.resolutions,
)
}
Self(Windows {
_24h: window::<86_400>(&format!("{name}_24h"), version, &source._24h),
_1w: window::<604_800>(&format!("{name}_1w"), version, &source._1w),
_1m: window::<2_592_000>(&format!("{name}_1m"), version, &source._1m),
_1y: window::<31_536_000>(&format!("{name}_1y"), version, &source._1y),
})
}
}
@@ -1,44 +1,59 @@
use std::sync::Arc;
use std::{marker::PhantomData, sync::Arc};
use brk_traversable::{Traversable, TreeNode, make_leaf};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
AnyExportableVec, AnyVec, CheckedSub, Formattable, ReadableBoxedVec, ReadableVec, TypedVec,
VecIndex, VecValue, Version, short_type_name,
AnyExportableVec, AnyVec, CheckedSub, Formattable, Ident, ReadableBoxedVec, ReadableVec,
TypedVec, UnaryTransform, VecIndex, VecValue, Version, short_type_name,
};
/// Lazy `source[index] - source[index - 1]`, with zero before the first value.
///
/// This is a single-source view: it follows source growth automatically and
/// stores nothing on disk.
pub struct LazyPreviousDeltaVec<I, T>
pub struct LazyPreviousDeltaVec<I, S, T = S, F = Ident>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
name: Arc<str>,
base_version: Version,
source: ReadableBoxedVec<I, T>,
source: ReadableBoxedVec<I, S>,
_output: PhantomData<fn(S) -> (T, F)>,
}
impl<I, T> LazyPreviousDeltaVec<I, T>
impl<I, S> LazyPreviousDeltaVec<I, S>
where
I: VecIndex,
S: VecValue,
{
pub fn new(name: &str, version: Version, source: ReadableBoxedVec<I, S>) -> Self {
Self::transformed(name, version, source)
}
}
impl<I, S, T, F> LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
pub fn new(name: &str, version: Version, source: ReadableBoxedVec<I, T>) -> Self {
pub fn transformed(name: &str, version: Version, source: ReadableBoxedVec<I, S>) -> Self {
Self {
name: Arc::from(name),
base_version: version,
source,
_output: PhantomData,
}
}
}
impl<I, T> Clone for LazyPreviousDeltaVec<I, T>
impl<I, S, T, F> Clone for LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn clone(&self) -> Self {
@@ -46,13 +61,15 @@ where
name: Arc::clone(&self.name),
base_version: self.base_version,
source: self.source.clone(),
_output: PhantomData,
}
}
}
impl<I, T> AnyVec for LazyPreviousDeltaVec<I, T>
impl<I, S, T, F> AnyVec for LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn version(&self) -> Version {
@@ -84,19 +101,22 @@ where
}
}
impl<I, T> TypedVec for LazyPreviousDeltaVec<I, T>
impl<I, S, T, F> TypedVec for LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
type I = I;
type T = T;
}
impl<I, T> LazyPreviousDeltaVec<I, T>
impl<I, S, T, F> LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
T: VecValue + CheckedSub + Default,
S: VecValue + CheckedSub + Default,
T: VecValue,
F: UnaryTransform<S, T>,
{
fn for_each_delta(&self, from: usize, to: usize, mut each: impl FnMut(T)) {
let to = to.min(self.len());
@@ -108,22 +128,26 @@ where
let values = self.source.collect_range_dyn(read_from, to);
let mut values = values.into_iter();
let mut previous = if from == 0 {
T::default()
S::default()
} else {
values.next().unwrap()
};
for current in values {
each(current.clone().checked_sub(previous).unwrap_or_default());
each(F::apply(
current.clone().checked_sub(previous).unwrap_or_default(),
));
previous = current;
}
}
}
impl<I, T> ReadableVec<I, T> for LazyPreviousDeltaVec<I, T>
impl<I, S, T, F> ReadableVec<I, T> for LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
T: VecValue + CheckedSub + Default,
S: VecValue + CheckedSub + Default,
T: VecValue,
F: UnaryTransform<S, T>,
{
fn read_into_at(&self, from: usize, to: usize, buf: &mut Vec<T>) {
buf.reserve(to.saturating_sub(from));
@@ -134,18 +158,18 @@ where
self.for_each_delta(from, to, f);
}
fn fold_range_at<B, F: FnMut(B, T) -> B>(&self, from: usize, to: usize, init: B, f: F) -> B {
fn fold_range_at<B, G: FnMut(B, T) -> B>(&self, from: usize, to: usize, init: B, f: G) -> B {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().fold(init, f)
}
fn try_fold_range_at<B, E, F: FnMut(B, T) -> Result<B, E>>(
fn try_fold_range_at<B, E, G: FnMut(B, T) -> Result<B, E>>(
&self,
from: usize,
to: usize,
init: B,
f: F,
f: G,
) -> Result<B, E> {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
@@ -158,7 +182,7 @@ where
.checked_sub(1)
.and_then(|index| self.source.collect_one_at(index))
.unwrap_or_default();
Some(current.checked_sub(previous).unwrap_or_default())
Some(F::apply(current.checked_sub(previous).unwrap_or_default()))
}
fn read_sorted_into_at(&self, indices: &[usize], out: &mut Vec<T>) {
@@ -170,10 +194,12 @@ where
}
}
impl<I, T> Traversable for LazyPreviousDeltaVec<I, T>
impl<I, S, T, F> Traversable for LazyPreviousDeltaVec<I, S, T, F>
where
I: VecIndex,
T: VecValue + CheckedSub + Default + Formattable + Serialize + JsonSchema,
S: VecValue + CheckedSub + Default,
T: VecValue + Formattable + Serialize + JsonSchema,
F: UnaryTransform<S, T>,
{
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
std::iter::once(self as &dyn AnyExportableVec)
@@ -0,0 +1,221 @@
use std::sync::Arc;
use brk_traversable::{Traversable, TreeNode, make_leaf};
use brk_types::{Day1, Height};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
AnyExportableVec, AnyVec, CachedBoxedVec, Formattable, PrintableIndex, ReadableBoxedVec,
ReadableVec, TypedVec, VecValue, Version, short_type_name,
};
/// Lazily derives values accumulated since a fixed day from one cumulative source.
pub struct LazySinceDayVec<S, T>
where
S: VecValue,
T: VecValue,
{
name: Arc<str>,
base_version: Version,
source: ReadableBoxedVec<Height, S>,
days: CachedBoxedVec<Height, Day1>,
start_day: Day1,
compute: Arc<dyn Fn(S, S) -> T + Send + Sync>,
}
impl<S, T> LazySinceDayVec<S, T>
where
S: VecValue + Default,
T: VecValue + Default,
{
pub fn new(
name: &str,
version: Version,
source: ReadableBoxedVec<Height, S>,
days: CachedBoxedVec<Height, Day1>,
start_day: Day1,
compute: impl Fn(S, S) -> T + Send + Sync + 'static,
) -> Self {
Self {
name: Arc::from(name),
base_version: version,
source,
days,
start_day,
compute: Arc::new(compute),
}
}
fn start_height(&self) -> usize {
let days = self.days.cached();
let mut left = 0;
let mut right = self.len().min(days.len());
while left < right {
let middle = left + (right - left) / 2;
if days[middle] < self.start_day {
left = middle + 1;
} else {
right = middle;
}
}
left
}
fn for_each_value(&self, from: usize, to: usize, mut each: impl FnMut(T)) {
let to = to.min(self.len());
if from >= to {
return;
}
let start = self.start_height();
let active_from = from.max(start).min(to);
for _ in from..active_from {
each(T::default());
}
if active_from == to {
return;
}
let before = start
.checked_sub(1)
.and_then(|index| self.source.collect_one_at(index))
.unwrap_or_default();
for current in self.source.collect_range_dyn(active_from, to) {
each((self.compute)(current, before.clone()));
}
}
}
impl<S, T> Clone for LazySinceDayVec<S, T>
where
S: VecValue,
T: VecValue,
{
fn clone(&self) -> Self {
Self {
name: Arc::clone(&self.name),
base_version: self.base_version,
source: self.source.clone(),
days: self.days.clone(),
start_day: self.start_day,
compute: Arc::clone(&self.compute),
}
}
}
impl<S, T> AnyVec for LazySinceDayVec<S, T>
where
S: VecValue,
T: VecValue,
{
fn version(&self) -> Version {
self.base_version + self.source.version() + self.days.version()
}
fn name(&self) -> &str {
&self.name
}
fn index_type_to_string(&self) -> &'static str {
<Height as PrintableIndex>::to_string()
}
fn len(&self) -> usize {
self.source.len()
}
fn value_type_to_size_of(&self) -> usize {
size_of::<T>()
}
fn value_type_to_string(&self) -> &'static str {
short_type_name::<T>()
}
fn region_names(&self) -> Vec<String> {
Vec::new()
}
}
impl<S, T> TypedVec for LazySinceDayVec<S, T>
where
S: VecValue,
T: VecValue,
{
type I = Height;
type T = T;
}
impl<S, T> ReadableVec<Height, T> for LazySinceDayVec<S, T>
where
S: VecValue + Default,
T: VecValue + Default,
{
fn read_into_at(&self, from: usize, to: usize, buf: &mut Vec<T>) {
buf.reserve(to.saturating_sub(from));
self.for_each_value(from, to, |value| buf.push(value));
}
fn for_each_range_dyn_at(&self, from: usize, to: usize, f: &mut dyn FnMut(T)) {
self.for_each_value(from, to, f);
}
fn fold_range_at<B, F: FnMut(B, T) -> B>(&self, from: usize, to: usize, init: B, f: F) -> B {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().fold(init, f)
}
fn try_fold_range_at<B, E, F: FnMut(B, T) -> Result<B, E>>(
&self,
from: usize,
to: usize,
init: B,
f: F,
) -> Result<B, E> {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().try_fold(init, f)
}
fn collect_one_at(&self, index: usize) -> Option<T> {
if index >= self.len() {
return None;
}
let start = self.start_height();
if index < start {
return Some(T::default());
}
let current = self.source.collect_one_at(index)?;
let before = start
.checked_sub(1)
.and_then(|index| self.source.collect_one_at(index))
.unwrap_or_default();
Some((self.compute)(current, before))
}
fn read_sorted_into_at(&self, indices: &[usize], out: &mut Vec<T>) {
out.reserve(indices.len());
indices
.iter()
.filter_map(|&index| self.collect_one_at(index))
.for_each(|value| out.push(value));
}
}
impl<S, T> Traversable for LazySinceDayVec<S, T>
where
S: VecValue + Default,
T: VecValue + Default + Formattable + Serialize + JsonSchema,
{
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
std::iter::once(self as &dyn AnyExportableVec)
}
fn to_tree_node(&self) -> TreeNode {
make_leaf::<Height, T, _>(self)
}
}
@@ -0,0 +1,241 @@
use std::sync::Arc;
use brk_traversable::{Traversable, TreeNode, make_leaf};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
AnyExportableVec, AnyVec, CachedBoxedVec, Formattable, ReadableBoxedVec, ReadableVec, TypedVec,
VecIndex, VecValue, Version, short_type_name,
};
/// Lazily combines the current and window-start values of one metric source.
///
/// `window_starts` is index metadata, not a second metric source. Reads are
/// split into current and lookback ranges so long windows do not force reading
/// the entire gap between them.
pub struct LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
name: Arc<str>,
base_version: Version,
source: ReadableBoxedVec<I, S>,
window_starts: CachedBoxedVec<I, I>,
inclusive: bool,
compute: Arc<dyn Fn(S, S, usize) -> T + Send + Sync>,
}
impl<I, S, T> LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue + Default,
T: VecValue,
{
pub fn new(
name: &str,
version: Version,
source: ReadableBoxedVec<I, S>,
window_starts: CachedBoxedVec<I, I>,
inclusive: bool,
compute: impl Fn(S, S, usize) -> T + Send + Sync + 'static,
) -> Self {
Self {
name: Arc::from(name),
base_version: version,
source,
window_starts,
inclusive,
compute: Arc::new(compute),
}
}
#[inline]
fn ago_index(&self, start: usize) -> Option<usize> {
if self.inclusive {
start.checked_sub(1)
} else {
Some(start)
}
}
#[inline]
fn count(&self, current: usize, start: usize) -> usize {
if self.inclusive {
current - start + 1
} else {
current - start
}
}
fn for_each_window(&self, from: usize, to: usize, mut each: impl FnMut(T)) {
let window_starts = self.window_starts.cached();
let to = to.min(self.len()).min(window_starts.len());
if from >= to {
return;
}
let starts = &window_starts[from..to];
let current = self.source.collect_range_dyn(from, to);
let first_ago = starts
.iter()
.find_map(|start| self.ago_index(start.to_usize()));
let last_ago = starts
.iter()
.rev()
.find_map(|start| self.ago_index(start.to_usize()));
let ago = first_ago
.zip(last_ago)
.map(|(first, last)| self.source.collect_range_dyn(first, last + 1));
for (offset, (current, start)) in current.into_iter().zip(starts).enumerate() {
let start = start.to_usize();
let previous = self
.ago_index(start)
.and_then(|index| {
ago.as_ref()
.zip(first_ago)
.map(|(values, first)| values[index - first].clone())
})
.unwrap_or_default();
each((self.compute)(
current,
previous,
self.count(from + offset, start),
));
}
}
}
impl<I, S, T> Clone for LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn clone(&self) -> Self {
Self {
name: Arc::clone(&self.name),
base_version: self.base_version,
source: self.source.clone(),
window_starts: self.window_starts.clone(),
inclusive: self.inclusive,
compute: Arc::clone(&self.compute),
}
}
}
impl<I, S, T> AnyVec for LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
fn version(&self) -> Version {
self.base_version + self.source.version() + self.window_starts.version()
}
fn name(&self) -> &str {
&self.name
}
fn index_type_to_string(&self) -> &'static str {
I::to_string()
}
fn len(&self) -> usize {
self.source.len()
}
fn value_type_to_size_of(&self) -> usize {
size_of::<T>()
}
fn value_type_to_string(&self) -> &'static str {
short_type_name::<T>()
}
fn region_names(&self) -> Vec<String> {
Vec::new()
}
}
impl<I, S, T> TypedVec for LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue,
T: VecValue,
{
type I = I;
type T = T;
}
impl<I, S, T> ReadableVec<I, T> for LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue + Default,
T: VecValue,
{
fn read_into_at(&self, from: usize, to: usize, buf: &mut Vec<T>) {
buf.reserve(to.saturating_sub(from));
self.for_each_window(from, to, |value| buf.push(value));
}
fn for_each_range_dyn_at(&self, from: usize, to: usize, f: &mut dyn FnMut(T)) {
self.for_each_window(from, to, f);
}
fn fold_range_at<B, F: FnMut(B, T) -> B>(&self, from: usize, to: usize, init: B, f: F) -> B {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().fold(init, f)
}
fn try_fold_range_at<B, E, F: FnMut(B, T) -> Result<B, E>>(
&self,
from: usize,
to: usize,
init: B,
f: F,
) -> Result<B, E> {
let mut values = Vec::with_capacity(to.saturating_sub(from));
self.read_into_at(from, to, &mut values);
values.into_iter().try_fold(init, f)
}
fn collect_one_at(&self, index: usize) -> Option<T> {
let window_starts = self.window_starts.cached();
let start = window_starts.get(index)?.to_usize();
let current = self.source.collect_one_at(index)?;
let previous = self
.ago_index(start)
.and_then(|index| self.source.collect_one_at(index))
.unwrap_or_default();
Some((self.compute)(current, previous, self.count(index, start)))
}
fn read_sorted_into_at(&self, indices: &[usize], out: &mut Vec<T>) {
out.reserve(indices.len());
indices
.iter()
.filter_map(|&index| self.collect_one_at(index))
.for_each(|value| out.push(value));
}
}
impl<I, S, T> Traversable for LazyWindowVec<I, S, T>
where
I: VecIndex,
S: VecValue + Default,
T: VecValue + Formattable + Serialize + JsonSchema,
{
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
std::iter::once(self as &dyn AnyExportableVec)
}
fn to_tree_node(&self) -> TreeNode {
make_leaf::<I, T, _>(self)
}
}
@@ -1,14 +1,19 @@
use brk_traversable::Traversable;
use brk_types::{StoredF32, Version};
use brk_types::{Height, StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{ReadableCloneableVec, UnaryTransform};
use vecdb::{ReadableCloneableVec, ReadableVec, TypedVec, UnaryTransform, VecValue};
use crate::internal::{FixedRatio, LazyPerBlock, Percent, PercentPerBlock};
use crate::{
indexes,
internal::{
Cagr, FixedRatio, Identity, LazyLookbackVec, LazyPerBlock, Percent, PercentPerBlock,
},
};
/// Fully lazy variant of `PercentPerBlock` — no stored vecs.
///
/// PPM values are lazily derived from a source `PercentPerBlock` via a unary transform,
/// and ratio/percent float views are chained from them.
/// PPM values are lazily derived from one source, and ratio/percent float views
/// are chained from them.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyPercentPerBlock<B: FixedRatio>(
@@ -16,6 +21,65 @@ pub struct LazyPercentPerBlock<B: FixedRatio>(
);
impl<B: FixedRatio> LazyPercentPerBlock<B> {
pub(crate) fn from_height_source<V>(
name: &str,
version: Version,
source: V,
indexes: &indexes::Vecs,
) -> Self
where
V: TypedVec<I = Height, T = B> + ReadableVec<Height, B> + Clone + 'static,
{
let ppm_name = format!("{name}_{}", B::SUFFIX);
let ppm =
LazyPerBlock::from_height_source::<Identity<B>, _>(&ppm_name, version, source, indexes);
Self::from_ppm(name, version, ppm)
}
/// Create from one height-indexed source. The fixed-point ratio is computed
/// lazily at height, then all coarser resolutions are derived from it.
pub(crate) fn from_indexed_source<S>(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
compute: fn(Height, S) -> B,
indexes: &indexes::Vecs,
) -> Self
where
S: VecValue,
{
let ppm_name = format!("{name}_{}", B::SUFFIX);
let ppm = LazyPerBlock::from_indexed_source(&ppm_name, version, source, compute, indexes);
Self::from_ppm(name, version, ppm)
}
/// Create from two values a fixed distance apart in one height source.
pub(crate) fn from_lookback_source<S>(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
lookback: usize,
compute: fn(S, Option<S>) -> B,
indexes: &indexes::Vecs,
) -> Self
where
S: VecValue,
{
let ppm_name = format!("{name}_{}", B::SUFFIX);
let source = LazyLookbackVec::new(
&format!("{ppm_name}_source"),
version,
source.read_only_boxed_clone(),
lookback,
compute,
);
let ppm =
LazyPerBlock::from_height_source::<Identity<B>, _>(&ppm_name, version, source, indexes);
Self::from_ppm(name, version, ppm)
}
/// Create from a stored `PercentPerBlock` source via a same-unit unary transform.
pub(crate) fn from_percent<F: UnaryTransform<B, B>>(
name: &str,
@@ -29,11 +93,23 @@ impl<B: FixedRatio> LazyPercentPerBlock<B> {
&source.ppm,
);
Self::from_ppm(name, version, ppm)
}
pub(crate) fn from_lazy_percent<F: UnaryTransform<B, B>>(
name: &str,
version: Version,
source: &Self,
) -> Self {
let ppm =
LazyPerBlock::from_lazy::<F, B>(&format!("{name}_{}", B::SUFFIX), version, &source.ppm);
Self::from_ppm(name, version, ppm)
}
fn from_ppm(name: &str, version: Version, ppm: LazyPerBlock<B, B>) -> Self {
let ratio =
LazyPerBlock::from_lazy::<B::ToRatio, B>(&format!("{name}_ratio"), version, &ppm);
let percent = LazyPerBlock::from_lazy::<B::ToPercent, B>(name, version, &ppm);
Self(Percent {
ppm,
ratio,
@@ -41,3 +117,36 @@ impl<B: FixedRatio> LazyPercentPerBlock<B> {
})
}
}
impl LazyPercentPerBlock<brk_types::PartsPerMillionSigned64> {
pub(crate) fn from_cagr(
name: &str,
version: Version,
years: u8,
source: &PercentPerBlock<brk_types::PartsPerMillionSigned64>,
) -> Self {
match years {
2 => Self::from_percent::<Cagr<2>>(name, version, source),
3 => Self::from_percent::<Cagr<3>>(name, version, source),
4 => Self::from_percent::<Cagr<4>>(name, version, source),
5 => Self::from_percent::<Cagr<5>>(name, version, source),
6 => Self::from_percent::<Cagr<6>>(name, version, source),
8 => Self::from_percent::<Cagr<8>>(name, version, source),
10 => Self::from_percent::<Cagr<10>>(name, version, source),
_ => unreachable!("unsupported DCA CAGR period: {years} years"),
}
}
pub(crate) fn from_lazy_cagr(name: &str, version: Version, years: u8, source: &Self) -> Self {
match years {
2 => Self::from_lazy_percent::<Cagr<2>>(name, version, source),
3 => Self::from_lazy_percent::<Cagr<3>>(name, version, source),
4 => Self::from_lazy_percent::<Cagr<4>>(name, version, source),
5 => Self::from_lazy_percent::<Cagr<5>>(name, version, source),
6 => Self::from_lazy_percent::<Cagr<6>>(name, version, source),
8 => Self::from_lazy_percent::<Cagr<8>>(name, version, source),
10 => Self::from_lazy_percent::<Cagr<10>>(name, version, source),
_ => unreachable!("unsupported DCA CAGR period: {years} years"),
}
}
}
@@ -0,0 +1,43 @@
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{LazyVecFrom1, ReadableCloneableVec, VecValue};
use crate::internal::{FixedRatio, Percent};
/// Fully lazy lightweight percent container with no derived resolutions.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
#[allow(clippy::type_complexity)]
pub struct LazyPercentVec<B: FixedRatio, S: VecValue>(
pub Percent<LazyVecFrom1<Height, B, Height, S>, LazyVecFrom1<Height, StoredF32, Height, B>>,
);
impl<B: FixedRatio, S: VecValue> LazyPercentVec<B, S> {
pub(crate) fn from_indexed_source(
name: &str,
version: Version,
source: &(impl ReadableCloneableVec<Height, S> + 'static),
compute: fn(Height, S) -> B,
) -> Self {
let ppm = LazyVecFrom1::init(
&format!("{name}_{}", B::SUFFIX),
version,
source.read_only_boxed_clone(),
compute,
);
let ppm_source = ppm.read_only_boxed_clone();
let ratio = LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{name}_ratio"),
version,
ppm_source.clone(),
);
let percent = LazyVecFrom1::transformed::<B::ToPercent>(name, version, ppm_source);
Self(Percent {
ppm,
ratio,
percent,
})
}
}
@@ -1,21 +1,60 @@
use brk_traversable::Traversable;
use brk_types::Version;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::UnaryTransform;
use schemars::JsonSchema;
use vecdb::{DeltaAvg, LazyDeltaVec, ReadOnlyClone, ReadableCloneableVec, UnaryTransform};
use crate::internal::{FixedRatio, PercentRollingWindows, Windows};
use crate::{
indexes,
internal::{FixedRatio, NumericValue, PercentRollingWindows, WindowStartVec, Windows},
};
use super::LazyPercentPerBlock;
/// Fully lazy rolling percent windows — 4 windows (24h, 1w, 1m, 1y),
/// each with lazy PPM + lazy ratio/percent float views.
///
/// No stored vecs. All values derived from a source `PercentRollingWindows`.
/// No stored vecs. All values are derived from one source.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyPercentRollingWindows<B: FixedRatio>(pub Windows<LazyPercentPerBlock<B>>);
impl<B: FixedRatio> LazyPercentRollingWindows<B> {
/// Rolling percentages derived lazily from a single cumulative source.
pub(crate) fn from_cumulative_average<T>(
name: &str,
version: Version,
cumulative: &(impl ReadableCloneableVec<Height, T> + 'static),
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> Self
where
T: NumericValue + JsonSchema,
{
let cumulative_source = cumulative.read_only_boxed_clone();
Self(cached_starts.map_with_suffix(|suffix, cached_start| {
let full_name = format!("{name}_{suffix}");
let cached = cached_start.read_only_clone();
let starts_version = cached.version();
let average = LazyDeltaVec::<Height, T, B, DeltaAvg>::new(
&format!("{full_name}_{}_source", B::SUFFIX),
version,
cumulative_source.clone(),
starts_version,
move || cached.cached(),
);
LazyPercentPerBlock::from_indexed_source(
&full_name,
version,
&average,
|_, ratio| ratio,
indexes,
)
}))
}
/// Create from a stored source via a same-unit unary transform.
pub(crate) fn from_rolling<F: UnaryTransform<B, B>>(
name: &str,
@@ -2,14 +2,14 @@ mod base;
mod cumulative_rolling;
mod lazy;
mod lazy_cumulative_rolling;
mod lazy_vec;
mod lazy_windows;
mod vec;
mod windows;
pub use base::*;
pub use cumulative_rolling::*;
pub use lazy::*;
pub use lazy_cumulative_rolling::*;
pub use lazy_vec::*;
pub use lazy_windows::*;
pub use vec::*;
pub use windows::*;
@@ -1,43 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{
Database, EagerVec, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode,
};
use crate::internal::{FixedRatio, Percent};
/// Lightweight percent container: fixed-point height vec + lazy ratio + lazy percent.
/// No resolutions, no rolling stats.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
#[allow(clippy::type_complexity)]
pub struct PercentVec<B: FixedRatio, M: StorageMode = Rw>(
pub Percent<M::Stored<EagerVec<PcoVec<Height, B>>>, LazyVecFrom1<Height, StoredF32, Height, B>>,
);
impl<B: FixedRatio> PercentVec<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
) -> brk_error::Result<Self> {
let ppm: EagerVec<PcoVec<Height, B>> =
EagerVec::forced_import(db, &format!("{name}_{}", B::SUFFIX), version)?;
let ppm_clone = ppm.read_only_boxed_clone();
let ratio = LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{name}_ratio"),
version,
ppm_clone.clone(),
);
let percent = LazyVecFrom1::transformed::<B::ToPercent>(name, version, ppm_clone);
Ok(Self(Percent {
ppm,
ratio,
percent,
}))
}
}
@@ -8,7 +8,9 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, SatsFract, Version};
use schemars::JsonSchema;
use vecdb::{Database, ReadableCloneableVec, UnaryTransform};
use vecdb::{
Database, ReadableCloneableVec, ReadableVec, TypedVec, UnaryTransform,
};
use super::{LazyPerBlock, PerBlock};
use crate::{
@@ -73,3 +75,33 @@ where
Self { usd, cents, sats }
}
}
impl Price<LazyPerBlock<Cents>> {
pub(crate) fn from_height_source<V>(
name: &str,
version: Version,
source: V,
indexes: &indexes::Vecs,
) -> Self
where
V: TypedVec<I = brk_types::Height, T = Cents>
+ ReadableVec<brk_types::Height, Cents>
+ Clone
+ 'static,
{
let cents = LazyPerBlock::from_height_source::<crate::internal::Identity<Cents>, _>(
&format!("{name}_cents"),
version,
source,
indexes,
);
let usd =
LazyPerBlock::from_lazy::<CentsUnsignedToDollars, Cents>(name, version, &cents);
let sats = LazyPerBlock::from_lazy::<DollarsToSatsFract, Cents>(
&format!("{name}_sats"),
version,
&usd,
);
Self { usd, cents, sats }
}
}
@@ -0,0 +1,38 @@
use brk_traversable::Traversable;
use brk_types::{StoredF32, Version};
use schemars::JsonSchema;
use vecdb::{ReadableCloneableVec, UnaryTransform};
use crate::internal::{FixedRatio, LazyPerBlock, NumericValue, PerBlock};
/// Fully lazy variant of `RatioPerBlock` derived from one per-block source.
#[derive(Clone, Traversable)]
pub struct LazyRatioPerBlock<R, S = R>
where
R: FixedRatio,
S: NumericValue + JsonSchema,
{
pub ppm: LazyPerBlock<R, S>,
pub ratio: LazyPerBlock<StoredF32, R>,
}
impl<R, S> LazyRatioPerBlock<R, S>
where
R: FixedRatio,
S: NumericValue + JsonSchema,
{
pub(crate) fn from_source<F>(name: &str, version: Version, source: &PerBlock<S>) -> Self
where
F: UnaryTransform<S, R>,
{
let ppm = LazyPerBlock::from_computed::<F>(
&format!("{name}_{}", R::SUFFIX),
version,
source.height.read_only_boxed_clone(),
source,
);
let ratio = LazyPerBlock::from_lazy::<R::ToRatio, S>(name, version, &ppm);
Self { ppm, ratio }
}
}
@@ -1,7 +1,9 @@
mod base;
mod lazy;
mod price;
mod windows;
pub use base::*;
pub use lazy::*;
pub use price::*;
pub use windows::*;
@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, PartsPerMillion64, SatsFract, StoredF32, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, unlikely};
use crate::internal::{LazyPerBlock, PerBlock, Price};
use crate::{indexes, price};
@@ -26,7 +26,7 @@ impl PriceWithRatioPerBlock {
indexes: &indexes::Vecs,
) -> Result<Self> {
let price = Price::forced_import(db, name, version, indexes)?;
let ratio = RatioPerBlock::forced_import(db, name, version, indexes)?;
let ratio = RatioPerBlock::forced_import(db, name, version + Version::ONE, indexes)?;
Ok(Self {
usd: price.usd,
cents: price.cents,
@@ -47,16 +47,7 @@ impl PriceWithRatioPerBlock {
starting_lengths.height,
close_price,
&self.cents.height,
|(i, close, price, ..)| {
if price == Cents::ZERO {
(i, PartsPerMillion64::from(1.0))
} else {
(
i,
PartsPerMillion64::from(f64::from(close) / f64::from(price)),
)
}
},
|(i, close, price, ..)| (i, price_ratio(close, price)),
exit,
)?;
Ok(())
@@ -77,3 +68,25 @@ impl PriceWithRatioPerBlock {
self.compute_ratio(starting_lengths, &prices.spot.cents.height, exit)
}
}
fn price_ratio(close: Cents, price: Cents) -> PartsPerMillion64 {
if unlikely(price == Cents::ZERO) {
PartsPerMillion64::NAN
} else {
PartsPerMillion64::from(f64::from(close) / f64::from(price))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_price_has_no_ratio() {
assert!(price_ratio(Cents::new(100), Cents::ZERO).is_nan());
assert_eq!(
price_ratio(Cents::new(100), Cents::new(50)),
PartsPerMillion64::from(2.0),
);
}
}
@@ -120,6 +120,27 @@ impl UnaryTransform<StoredU32, StoredF32> for BlocksToDaysF32 {
}
}
pub struct StoredU64ToStoredU32;
impl UnaryTransform<StoredU64, StoredU32> for StoredU64ToStoredU32 {
#[inline(always)]
fn apply(value: StoredU64) -> StoredU32 {
StoredU32::new(
u32::try_from(u64::from(value))
.expect("per-block value reconstructed from StoredU64 must fit StoredU32"),
)
}
}
pub struct PerSecond<const SECONDS: u32>;
impl<const SECONDS: u32> UnaryTransform<StoredU64, StoredF32> for PerSecond<SECONDS> {
#[inline(always)]
fn apply(value: StoredU64) -> StoredF32 {
StoredF32::from(u64::from(value) as f64 / SECONDS as f64)
}
}
pub struct OneMinusF64;
impl UnaryTransform<StoredF64, StoredF64> for OneMinusF64 {
@@ -129,6 +150,15 @@ impl UnaryTransform<StoredF64, StoredF64> for OneMinusF64 {
}
}
pub struct OddsF64;
impl UnaryTransform<StoredF64, StoredF64> for OddsF64 {
#[inline(always)]
fn apply(value: StoredF64) -> StoredF64 {
value / StoredF64::from(1.0 - *value)
}
}
pub struct DifficultyToHashF64;
impl UnaryTransform<StoredF64, StoredF64> for DifficultyToHashF64 {
@@ -165,3 +195,16 @@ impl UnaryTransform<Weight, VSize> for WeightToVSize {
VSize::from(weight)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn odds_are_the_ratio_to_the_complement() {
assert_eq!(OddsF64::apply(StoredF64::from(0.0)), StoredF64::from(0.0));
assert_eq!(OddsF64::apply(StoredF64::from(0.5)), StoredF64::from(1.0));
assert_eq!(OddsF64::apply(StoredF64::from(0.75)), StoredF64::from(3.0));
assert_eq!(OddsF64::apply(StoredF64::from(1.0)), StoredF64::NAN);
}
}
@@ -1,5 +1,5 @@
use brk_types::{Bitcoin, Cents, CentsSigned, Dollars, Sats, SatsFract, SatsSigned, StoredF32};
use vecdb::{BinaryTransform, UnaryTransform};
use vecdb::{BinaryTransform, UnaryTransform, unlikely};
pub struct SatsToBitcoin;
@@ -42,7 +42,11 @@ pub struct SatsToCents;
impl BinaryTransform<Sats, Cents, Cents> for SatsToCents {
#[inline(always)]
fn apply(sats: Sats, price_cents: Cents) -> Cents {
Cents::from(sats.as_u128() * price_cents.as_u128() / Sats::ONE_BTC_U128)
if unlikely(price_cents.is_nan()) {
Cents::NAN
} else {
Cents::from(sats.as_u128() * price_cents.as_u128() / Sats::ONE_BTC_U128)
}
}
}
@@ -78,6 +82,9 @@ pub struct CentsUnsignedToSats;
impl UnaryTransform<Cents, Sats> for CentsUnsignedToSats {
#[inline(always)]
fn apply(cents: Cents) -> Sats {
if unlikely(cents.is_nan()) {
panic!("Cents::NAN cannot be converted to whole Sats");
}
let dollars = Dollars::from(cents);
if dollars == Dollars::ZERO {
Sats::ZERO
@@ -92,7 +99,13 @@ pub struct CentsSubtractToCentsSigned;
impl BinaryTransform<Cents, Cents, CentsSigned> for CentsSubtractToCentsSigned {
#[inline(always)]
fn apply(a: Cents, b: Cents) -> CentsSigned {
CentsSigned::from(a.inner() as i64 - b.inner() as i64)
if unlikely(a.is_nan() || b.is_nan()) {
panic!("Cents::NAN cannot be converted to CentsSigned");
}
let difference = i128::from(a.inner()) - i128::from(b.inner());
CentsSigned::new(
i64::try_from(difference).expect("Cents subtraction overflowed CentsSigned"),
)
}
}
@@ -101,7 +114,11 @@ pub struct CentsTimesTenths<const V: u16>;
impl<const V: u16> UnaryTransform<Cents, Cents> for CentsTimesTenths<V> {
#[inline(always)]
fn apply(c: Cents) -> Cents {
Cents::from(c.as_u128() * V as u128 / 10)
if unlikely(c.is_nan()) {
Cents::NAN
} else {
Cents::from(c.as_u128() * V as u128 / 10)
}
}
}
@@ -113,3 +130,26 @@ impl UnaryTransform<Dollars, SatsFract> for DollarsToSatsFract {
SatsFract::ONE_BTC / usd
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cents_outputs_propagate_nan() {
assert!(SatsToCents::apply(Sats::ONE_BTC, Cents::NAN).is_nan());
assert!(CentsTimesTenths::<24>::apply(Cents::NAN).is_nan());
}
#[test]
#[should_panic(expected = "Cents::NAN cannot be converted to whole Sats")]
fn whole_sats_reject_nan() {
CentsUnsignedToSats::apply(Cents::NAN);
}
#[test]
#[should_panic(expected = "Cents::NAN cannot be converted to CentsSigned")]
fn signed_cents_reject_nan() {
CentsSubtractToCentsSigned::apply(Cents::NAN, Cents::ZERO);
}
}
@@ -1,7 +1,7 @@
use std::marker::PhantomData;
use brk_types::{Cents, StoredF32, StoredF64};
use vecdb::{BinaryTransform, UnaryTransform};
use brk_types::{Cents, PartsPerMillionSigned64, StoredF32, StoredF64};
use vecdb::{BinaryTransform, UnaryTransform, unlikely};
use crate::internal::FixedRatio;
@@ -14,6 +14,18 @@ impl UnaryTransform<StoredF32, StoredF32> for DaysToYears {
}
}
pub struct Cagr<const YEARS: u8>;
impl<const YEARS: u8> UnaryTransform<PartsPerMillionSigned64, PartsPerMillionSigned64>
for Cagr<YEARS>
{
#[inline(always)]
fn apply(value: PartsPerMillionSigned64) -> PartsPerMillionSigned64 {
let ratio = f64::from(value);
PartsPerMillionSigned64::from((ratio + 1.0).powf(1.0 / YEARS as f64) - 1.0)
}
}
pub trait SqrtDays {
const FACTOR: f32;
}
@@ -61,10 +73,22 @@ pub struct RatioCents64;
impl BinaryTransform<Cents, Cents, StoredF64> for RatioCents64 {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> StoredF64 {
if denominator == Cents::ZERO {
let denominator = f64::from(denominator);
if unlikely(denominator == 0.0) {
StoredF64::from(1.0)
} else {
StoredF64::from(numerator.inner() as f64 / denominator.inner() as f64)
StoredF64::from(f64::from(numerator) / denominator)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cents_ratio_64_propagates_nan() {
assert!(RatioCents64::apply(Cents::NAN, Cents::new(100)).is_nan());
assert!(RatioCents64::apply(Cents::new(100), Cents::NAN).is_nan());
}
}
@@ -7,8 +7,8 @@ mod specialized;
pub use arithmetic::{
BlocksToDaysF32, DifficultyToHashF64, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
Identity, MaskSats, OneMinusF64, OneMinusPpm, ReturnF32Tenths, ReturnI8, ReturnU16,
ThsToPhsF32, VBytesToWeight, WeightToVSize,
Identity, MaskSats, OddsF64, OneMinusF64, OneMinusPpm, PerSecond, ReturnF32Tenths, ReturnI8,
ReturnU16, StoredU64ToStoredU32, ThsToPhsF32, VBytesToWeight, WeightToVSize,
};
pub use currency::{
AvgCentsToUsd, AvgSatsToBtc, CentsSignedToDollars, CentsSubtractToCentsSigned,
@@ -16,12 +16,12 @@ pub use currency::{
NegCentsUnsignedToDollars, SatsSignedToBitcoin, SatsToBitcoin, SatsToCents,
};
pub use derived::{
Days1, Days7, Days30, Days365, DaysToYears, PriceTimesRatio, RatioCents64, TimesSqrt,
Cagr, Days1, Days7, Days30, Days365, DaysToYears, PriceTimesRatio, RatioCents64, TimesSqrt,
};
pub use fixed_ratio::{FixedToPercent, FixedToRatio};
pub use ratio::{
RatioCents, RatioCentsSignedCents, RatioCentsSignedDollars, RatioDiffCents, RatioDiffDollars,
RatioDiffF32, RatioDollars, RatioSats, RatioU64, RatioU64F32,
RatioDiffF32, RatioDollars, RatioSats, RatioU64,
};
pub use specialized::{
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
@@ -1,7 +1,7 @@
use std::marker::PhantomData;
use brk_types::{Cents, CentsSigned, Dollars, Sats, StoredF32, StoredU64};
use vecdb::BinaryTransform;
use vecdb::{BinaryTransform, unlikely};
use crate::internal::FixedRatio;
@@ -36,10 +36,11 @@ pub struct RatioCents<P>(PhantomData<P>);
impl<P: FixedRatio> BinaryTransform<Cents, Cents, P> for RatioCents<P> {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> P {
if denominator == Cents::ZERO {
let denominator = f64::from(denominator);
if unlikely(denominator == 0.0) {
P::default()
} else {
P::from(numerator.inner() as f64 / denominator.inner() as f64)
P::from(f64::from(numerator) / denominator)
}
}
}
@@ -63,10 +64,11 @@ pub struct RatioCentsSignedCents<P>(PhantomData<P>);
impl<P: FixedRatio> BinaryTransform<CentsSigned, Cents, P> for RatioCentsSignedCents<P> {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Cents) -> P {
if denominator == Cents::ZERO {
let denominator = f64::from(denominator);
if unlikely(denominator == 0.0) {
P::default()
} else {
P::from(numerator.inner() as f64 / denominator.inner() as f64)
P::from(numerator.inner() as f64 / denominator)
}
}
}
@@ -85,19 +87,6 @@ impl<P: FixedRatio> BinaryTransform<CentsSigned, Dollars, P> for RatioCentsSigne
}
}
pub struct RatioU64F32;
impl BinaryTransform<StoredU64, StoredU64, StoredF32> for RatioU64F32 {
#[inline(always)]
fn apply(numerator: StoredU64, denominator: StoredU64) -> StoredF32 {
if *denominator > 0 {
StoredF32::from(*numerator as f64 / *denominator as f64)
} else {
StoredF32::default()
}
}
}
pub struct RatioDiffF32<P>(PhantomData<P>);
impl<P: FixedRatio> BinaryTransform<StoredF32, StoredF32, P> for RatioDiffF32<P> {
@@ -138,3 +127,20 @@ impl<P: FixedRatio> BinaryTransform<Cents, Cents, P> for RatioDiffCents<P> {
}
}
}
#[cfg(test)]
mod tests {
use brk_types::PartsPerMillion32;
use super::*;
#[test]
fn cents_ratios_propagate_nan() {
assert!(RatioCents::<PartsPerMillion32>::apply(Cents::NAN, Cents::new(100)).is_nan());
assert!(RatioCents::<PartsPerMillion32>::apply(Cents::new(100), Cents::NAN).is_nan());
assert!(
RatioCentsSignedCents::<PartsPerMillion32>::apply(CentsSigned::new(100), Cents::NAN,)
.is_nan()
);
}
}
+85 -17
View File
@@ -73,6 +73,91 @@ impl<T> ByDcaClass<T> {
})
}
pub(crate) fn try_from_class<U, F, E>(
class: &ByDcaClass<U>,
mut create: F,
) -> Result<Self, E>
where
F: FnMut(&'static str, u16, Day1, &U) -> Result<T, E>,
{
let n = DCA_CLASS_NAMES;
let y = DCA_CLASS_YEARS;
Ok(Self {
from_2015: create(
n.from_2015,
y.from_2015,
Self::day1(y.from_2015),
&class.from_2015,
)?,
from_2016: create(
n.from_2016,
y.from_2016,
Self::day1(y.from_2016),
&class.from_2016,
)?,
from_2017: create(
n.from_2017,
y.from_2017,
Self::day1(y.from_2017),
&class.from_2017,
)?,
from_2018: create(
n.from_2018,
y.from_2018,
Self::day1(y.from_2018),
&class.from_2018,
)?,
from_2019: create(
n.from_2019,
y.from_2019,
Self::day1(y.from_2019),
&class.from_2019,
)?,
from_2020: create(
n.from_2020,
y.from_2020,
Self::day1(y.from_2020),
&class.from_2020,
)?,
from_2021: create(
n.from_2021,
y.from_2021,
Self::day1(y.from_2021),
&class.from_2021,
)?,
from_2022: create(
n.from_2022,
y.from_2022,
Self::day1(y.from_2022),
&class.from_2022,
)?,
from_2023: create(
n.from_2023,
y.from_2023,
Self::day1(y.from_2023),
&class.from_2023,
)?,
from_2024: create(
n.from_2024,
y.from_2024,
Self::day1(y.from_2024),
&class.from_2024,
)?,
from_2025: create(
n.from_2025,
y.from_2025,
Self::day1(y.from_2025),
&class.from_2025,
)?,
from_2026: create(
n.from_2026,
y.from_2026,
Self::day1(y.from_2026),
&class.from_2026,
)?,
})
}
pub(crate) fn day1(year: u16) -> Day1 {
Day1::try_from(Date::new(year, 1, 1)).unwrap()
}
@@ -113,21 +198,4 @@ impl<T> ByDcaClass<T> {
.into_iter()
}
pub(crate) fn start_days() -> [Day1; 12] {
let y = DCA_CLASS_YEARS;
[
Self::day1(y.from_2015),
Self::day1(y.from_2016),
Self::day1(y.from_2017),
Self::day1(y.from_2018),
Self::day1(y.from_2019),
Self::day1(y.from_2020),
Self::day1(y.from_2021),
Self::day1(y.from_2022),
Self::day1(y.from_2023),
Self::day1(y.from_2024),
Self::day1(y.from_2025),
Self::day1(y.from_2026),
]
}
}
+37 -66
View File
@@ -96,6 +96,31 @@ impl<T> ByDcaPeriod<T> {
})
}
pub(crate) fn try_from_period<U, F, E>(
period: &ByDcaPeriod<U>,
mut create: F,
) -> Result<Self, E>
where
F: FnMut(&'static str, u32, &U) -> Result<T, E>,
{
let n = DCA_PERIOD_NAMES;
let d = DCA_PERIOD_DAYS;
Ok(Self {
_1w: create(n._1w, d._1w, &period._1w)?,
_1m: create(n._1m, d._1m, &period._1m)?,
_3m: create(n._3m, d._3m, &period._3m)?,
_6m: create(n._6m, d._6m, &period._6m)?,
_1y: create(n._1y, d._1y, &period._1y)?,
_2y: create(n._2y, d._2y, &period._2y)?,
_3y: create(n._3y, d._3y, &period._3y)?,
_4y: create(n._4y, d._4y, &period._4y)?,
_5y: create(n._5y, d._5y, &period._5y)?,
_6y: create(n._6y, d._6y, &period._6y)?,
_8y: create(n._8y, d._8y, &period._8y)?,
_10y: create(n._10y, d._10y, &period._10y)?,
})
}
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
[
&mut self._1w,
@@ -133,46 +158,6 @@ impl<T> ByDcaPeriod<T> {
.into_iter()
}
pub(crate) fn iter_mut_with_days(&mut self) -> impl Iterator<Item = (&mut T, u32)> {
let d = DCA_PERIOD_DAYS;
[
(&mut self._1w, d._1w),
(&mut self._1m, d._1m),
(&mut self._3m, d._3m),
(&mut self._6m, d._6m),
(&mut self._1y, d._1y),
(&mut self._2y, d._2y),
(&mut self._3y, d._3y),
(&mut self._4y, d._4y),
(&mut self._5y, d._5y),
(&mut self._6y, d._6y),
(&mut self._8y, d._8y),
(&mut self._10y, d._10y),
]
.into_iter()
}
pub(crate) fn zip_mut_with_days<'a, U>(
&'a mut self,
other: &'a ByDcaPeriod<U>,
) -> impl Iterator<Item = (&'a mut T, &'a U, u32)> {
let d = DCA_PERIOD_DAYS;
[
(&mut self._1w, &other._1w, d._1w),
(&mut self._1m, &other._1m, d._1m),
(&mut self._3m, &other._3m, d._3m),
(&mut self._6m, &other._6m, d._6m),
(&mut self._1y, &other._1y, d._1y),
(&mut self._2y, &other._2y, d._2y),
(&mut self._3y, &other._3y, d._3y),
(&mut self._4y, &other._4y, d._4y),
(&mut self._5y, &other._5y, d._5y),
(&mut self._6y, &other._6y, d._6y),
(&mut self._8y, &other._8y, d._8y),
(&mut self._10y, &other._10y, d._10y),
]
.into_iter()
}
}
impl<T> ByDcaPeriod<&T> {
@@ -208,38 +193,24 @@ pub struct ByDcaCagr<T> {
}
impl<T> ByDcaCagr<T> {
pub(crate) fn try_new<F, E>(mut create: F) -> Result<Self, E>
pub(crate) fn try_new<U, F, E>(
period: &ByDcaPeriod<U>,
mut create: F,
) -> Result<Self, E>
where
F: FnMut(&'static str, u32) -> Result<T, E>,
F: FnMut(&'static str, u32, &U) -> Result<T, E>,
{
let n = DCA_CAGR_NAMES;
let d = DCA_CAGR_DAYS;
Ok(Self {
_2y: create(n._2y, d._2y)?,
_3y: create(n._3y, d._3y)?,
_4y: create(n._4y, d._4y)?,
_5y: create(n._5y, d._5y)?,
_6y: create(n._6y, d._6y)?,
_8y: create(n._8y, d._8y)?,
_10y: create(n._10y, d._10y)?,
_2y: create(n._2y, d._2y, &period._2y)?,
_3y: create(n._3y, d._3y, &period._3y)?,
_4y: create(n._4y, d._4y, &period._4y)?,
_5y: create(n._5y, d._5y, &period._5y)?,
_6y: create(n._6y, d._6y, &period._6y)?,
_8y: create(n._8y, d._8y, &period._8y)?,
_10y: create(n._10y, d._10y, &period._10y)?,
})
}
/// Zip with the matching subset of a ByDcaPeriod
pub(crate) fn zip_mut_with_period<'a, U>(
&'a mut self,
period: &'a ByDcaPeriod<U>,
) -> impl Iterator<Item = (&'a mut T, &'a U, u32)> {
let d = DCA_CAGR_DAYS;
[
(&mut self._2y, &period._2y, d._2y),
(&mut self._3y, &period._3y, d._3y),
(&mut self._4y, &period._4y, d._4y),
(&mut self._5y, &period._5y, d._5y),
(&mut self._6y, &period._6y, d._6y),
(&mut self._8y, &period._8y, d._8y),
(&mut self._10y, &period._10y, d._10y),
]
.into_iter()
}
}
+59 -233
View File
@@ -1,12 +1,11 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{Bitcoin, Cents, Date, Day1, Dollars, PartsPerMillionSigned64, Sats};
use brk_types::{Bitcoin, Cents, PartsPerMillionSigned64, Sats};
use vecdb::{AnyVec, Exit, ReadableOptionVec, ReadableVec, VecIndex};
use super::{ByDcaPeriod, Vecs};
use crate::{blocks, indexes, internal::RatioDiffCents, market, price};
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
use super::vecs::DcaStack;
use super::{DCA_AMOUNT, Vecs};
use crate::{indexes, internal::{RatioDiffCents, SatsToCents}, price};
impl Vecs {
pub(crate) fn compute(
@@ -14,262 +13,73 @@ impl Vecs {
indexer: &Indexer,
indexes: &indexes::Vecs,
prices: &price::Vecs,
blocks: &blocks::Vecs,
lookback: &market::lookback::Vecs,
exit: &Exit,
) -> Result<()> {
self.db.sync_bg_tasks()?;
let starting_lengths = indexer.safe_lengths();
let starting_height = indexer.safe_lengths().height;
let h2d = &indexes.height.day1;
let close = &prices.split.close.usd.day1;
let first_price_di = Day1::try_from(Date::new(2010, 7, 12)).unwrap().to_usize();
let start = self
.sats_cumulative
.len()
.min(starting_height.to_usize());
let mut cumulative = start
.checked_sub(1)
.and_then(|height| self.sats_cumulative.collect_one_at(height))
.unwrap_or_default();
let mut last_day = start
.checked_sub(1)
.and_then(|height| h2d.collect_one_at(height));
// Compute per-height DCA sats contribution once (reused by all periods).
// Value = sats_from_dca(close_price) on day-boundary blocks, Sats::ZERO otherwise.
{
let mut last_di: Option<Day1> = None;
self.sats_per_day.compute_transform(
starting_lengths.height,
h2d,
|(h, di, _)| {
if last_di.is_none() && h.to_usize() > 0 {
last_di = Some(h2d.collect_one_at(h.to_usize() - 1).unwrap());
}
let same_day = last_di.is_some_and(|prev| prev == di);
last_di = Some(di);
if same_day {
(h, Sats::ZERO)
} else {
let s = close
.collect_one_flat(di)
.map(sats_from_dca)
.unwrap_or(Sats::ZERO);
(h, s)
}
},
exit,
)?;
}
self.sats_cumulative.compute_transform(
starting_height,
h2d,
|(height, day, _)| {
if last_day != Some(day) {
cumulative += close
.collect_one_flat(day)
.map(sats_from_dca)
.unwrap_or_default();
}
last_day = Some(day);
(height, cumulative)
},
exit,
)?;
// DCA by period - stack (rolling sum via _start vecs)
for (stack, days) in self.period.dca_stack.iter_mut_with_days() {
let window_starts = blocks.lookback.start_vec(days as usize);
stack.sats.height.compute_rolling_sum(
starting_lengths.height,
window_starts,
&self.sats_per_day,
exit,
)?;
}
// DCA by period - stack cents (sats × price)
for stack in self.period.dca_stack.iter_mut() {
stack.compute(prices, starting_lengths.height, exit)?;
stack.compute_cents(prices, starting_height, exit)?;
}
for stack in self.class.dca_stack.iter_mut() {
stack.compute_cents(prices, starting_height, exit)?;
}
// DCA by period - average price (derived from stack)
let starting_height_usize = starting_lengths.height.to_usize();
for (average_price, stack, days) in self
.period
.dca_cost_basis
.zip_mut_with_days(&self.period.dca_stack)
{
let days = days as usize;
average_price.cents.height.compute_transform2(
starting_lengths.height,
h2d,
&stack.sats.height,
|(h, di, stack_sats, ..)| {
let di_usize = di.to_usize();
let avg = if di_usize > first_price_di {
let num_days = days.min(di_usize + 1 - first_price_di);
Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats))
} else {
Cents::ZERO
};
(h, avg)
},
exit,
)?;
}
// DCA by period - returns (compute from average price)
for (returns, (average_price, _)) in self
for (returns, (cost_basis, _)) in self
.period
.dca_return
.iter_mut()
.zip(self.period.dca_cost_basis.iter_with_days())
{
returns.compute_binary::<Cents, Cents, RatioDiffCents<PartsPerMillionSigned64>>(
starting_lengths.height,
starting_height,
&prices.spot.cents.height,
&average_price.cents.height,
&cost_basis.cents.height,
exit,
)?;
}
// DCA by period - CAGR (computed from returns at height level)
for (cagr, returns, days) in self
.period
.dca_cagr
.zip_mut_with_period(&self.period.dca_return)
{
let years = days as f64 / 365.0;
cagr.ppm.height.compute_transform(
starting_lengths.height,
&returns.ppm.height,
|(h, r, ..)| {
let ratio = f64::from(r);
let v = (ratio + 1.0).powf(1.0 / years) - 1.0;
(h, PartsPerMillionSigned64::from(v))
},
exit,
)?;
}
// Lump sum by period - stack
let lookback_dca = ByDcaPeriod::from_lookback(&lookback.price_past);
for (stack, lookback_price, days) in
self.period.lump_sum_stack.zip_mut_with_days(&lookback_dca)
{
let total_invested = DCA_AMOUNT * days as usize;
stack.sats.height.compute_transform2(
starting_lengths.height,
h2d,
&lookback_price.cents.height,
|(h, _di, lp, ..)| {
let sats = if lp == Cents::ZERO {
Sats::ZERO
} else {
Sats::from(Bitcoin::from(total_invested / Dollars::from(lp)))
};
(h, sats)
},
exit,
)?;
}
// Lump sum by period - stack cents (sats × price)
for stack in self.period.lump_sum_stack.iter_mut() {
stack.compute(prices, starting_lengths.height, exit)?;
}
// Lump sum by period - returns (compute from lookback price)
for (returns, (lookback_price, _)) in self
.period
.lump_sum_return
.iter_mut()
.zip(lookback_dca.iter_with_days())
{
returns.compute_binary::<Cents, Cents, RatioDiffCents<PartsPerMillionSigned64>>(
starting_lengths.height,
&prices.spot.cents.height,
&lookback_price.cents.height,
exit,
)?;
}
// DCA by year class - stack (cumulative sum from class start date)
let start_days = super::ByDcaClass::<()>::start_days();
for (stack, day1) in self.class.dca_stack.iter_mut().zip(start_days) {
let mut last_di: Option<Day1> = None;
let cls_start = stack.sats.height.len().min(starting_height_usize);
let mut prev_value = if cls_start > 0 {
stack
.sats
.height
.collect_one_at(cls_start - 1)
.unwrap_or_default()
} else {
Sats::ZERO
};
stack.sats.height.compute_transform(
starting_lengths.height,
h2d,
|(h, di, _)| {
let hi = h.to_usize();
if last_di.is_none() && hi > 0 {
last_di = Some(h2d.collect_one_at(hi - 1).unwrap());
}
if di < day1 {
last_di = Some(di);
prev_value = Sats::ZERO;
return (h, Sats::ZERO);
}
let prev_di = last_di;
last_di = Some(di);
let same_day = prev_di.is_some_and(|prev| prev == di);
let result = if same_day {
prev_value
} else {
let prev = if hi > 0 && prev_di.is_some_and(|pd| pd >= day1) {
prev_value
} else {
Sats::ZERO
};
let s = close
.collect_one_flat(di)
.map(sats_from_dca)
.unwrap_or(Sats::ZERO);
prev + s
};
prev_value = result;
(h, result)
},
exit,
)?;
}
// DCA by year class - stack cents (sats × price)
for stack in self.class.dca_stack.iter_mut() {
stack.compute(prices, starting_lengths.height, exit)?;
}
// DCA by year class - average price (derived from stack)
let start_days = super::ByDcaClass::<()>::start_days();
for ((average_price, stack), from) in self
.class
.dca_cost_basis
.iter_mut()
.zip(self.class.dca_stack.iter())
.zip(start_days)
{
let from_usize = from.to_usize();
average_price.cents.height.compute_transform2(
starting_lengths.height,
h2d,
&stack.sats.height,
|(h, di, stack_sats, ..)| {
let di_usize = di.to_usize();
if di_usize < from_usize {
return (h, Cents::ZERO);
}
let num_days = di_usize + 1 - from_usize;
let avg = Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats));
(h, avg)
},
exit,
)?;
}
// DCA by year class - returns (compute from average price)
for (returns, average_price) in self
for (returns, cost_basis) in self
.class
.dca_return
.iter_mut()
.zip(self.class.dca_cost_basis.iter())
{
returns.compute_binary::<Cents, Cents, RatioDiffCents<PartsPerMillionSigned64>>(
starting_lengths.height,
starting_height,
&prices.spot.cents.height,
&average_price.cents.height,
&cost_basis.cents.height,
exit,
)?;
}
@@ -283,8 +93,24 @@ impl Vecs {
}
}
fn sats_from_dca(price: Dollars) -> Sats {
if price == Dollars::ZERO {
impl DcaStack {
fn compute_cents(
&mut self,
prices: &price::Vecs,
starting_height: brk_types::Height,
exit: &Exit,
) -> Result<()> {
self.cents.compute_binary::<Sats, Cents, SatsToCents>(
starting_height,
&self.sats.height,
&prices.spot.cents.height,
exit,
)
}
}
fn sats_from_dca(price: brk_types::Dollars) -> Sats {
if price == brk_types::Dollars::ZERO {
Sats::ZERO
} else {
Sats::from(Bitcoin::from(DCA_AMOUNT / price))

Some files were not shown because too many files have changed in this diff Show More