mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-08-04 22:53:05 -07:00
brk: metric trimming part X + 3
This commit is contained in:
Generated
-6
@@ -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
@@ -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
@@ -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()),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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,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,
|
||||
)?,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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)?,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, ¢s);
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user