mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -649,7 +649,9 @@ dependencies = [
|
|||||||
"brk_traversable",
|
"brk_traversable",
|
||||||
"brk_types",
|
"brk_types",
|
||||||
"brk_website",
|
"brk_website",
|
||||||
|
"brotli",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"flate2",
|
||||||
"jiff",
|
"jiff",
|
||||||
"quick_cache",
|
"quick_cache",
|
||||||
"schemars",
|
"schemars",
|
||||||
@@ -660,6 +662,7 @@ dependencies = [
|
|||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tracing",
|
"tracing",
|
||||||
"vecdb",
|
"vecdb",
|
||||||
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1124,7 +1127,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1202,7 +1205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1238,9 +1241,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fjall"
|
name = "fjall"
|
||||||
version = "3.0.4"
|
version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ebf22b812878dcd767879cb19e03124fd62563dce6410f96538175fba0c132d"
|
checksum = "40cb1eb0cef3792900897b32c8282f6417bc978f6af46400a2f14bf0e649ae30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
"byteview",
|
"byteview",
|
||||||
@@ -1865,7 +1868,7 @@ dependencies = [
|
|||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"portable-atomic-util",
|
"portable-atomic-util",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2020,9 +2023,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsm-tree"
|
name = "lsm-tree"
|
||||||
version = "3.0.4"
|
version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9bfd2a6ea0c1d430c13643002f35800a87f200fc8ac4827f18a2db9d9fd0644"
|
checksum = "fc5fa40c207eed45c811085aaa1b0a25fead22e298e286081cd4b98785fe759b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
"byteview",
|
"byteview",
|
||||||
@@ -2398,8 +2401,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "quickmatch"
|
name = "quickmatch"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba3b8437da19ff13b614fc03438308e7d642b4e625e31f8e3b61e1843bffbc02"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
]
|
]
|
||||||
@@ -2629,7 +2630,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2988,7 +2989,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3070,9 +3071,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "1.0.4+spec-1.1.0"
|
version = "1.0.6+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c94c3321114413476740df133f0d8862c61d87c8d26f04c6841e033c8c80db47"
|
checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -3479,7 +3480,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ brk_website = { version = "0.1.9", path = "crates/brk_website" }
|
|||||||
byteview = "0.10.1"
|
byteview = "0.10.1"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
||||||
fjall = "3.0.4"
|
fjall = "3.1.0"
|
||||||
indexmap = { version = "2.13.0", features = ["serde"] }
|
indexmap = { version = "2.13.0", features = ["serde"] }
|
||||||
jiff = { version = "0.2.23", features = ["perf-inline", "tz-system"], default-features = false }
|
jiff = { version = "0.2.23", features = ["perf-inline", "tz-system"], default-features = false }
|
||||||
minreq = { version = "2.14.1", features = ["https", "json-using-serde"] }
|
minreq = { version = "2.14.1", features = ["https", "json-using-serde"] }
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ owo-colors = { workspace = true }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
toml = "1.0.4"
|
toml = "1.0.6"
|
||||||
vecdb = { workspace = true }
|
vecdb = { workspace = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -66,7 +66,6 @@ impl Vecs {
|
|||||||
self.pricing.compute(
|
self.pricing.compute(
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
prices,
|
prices,
|
||||||
blocks,
|
|
||||||
distribution,
|
distribution,
|
||||||
&self.activity,
|
&self.activity,
|
||||||
&self.supply,
|
&self.supply,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use vecdb::{Exit, VecIndex};
|
|||||||
|
|
||||||
use super::super::{activity, cap, supply};
|
use super::super::{activity, cap, supply};
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{blocks, distribution, prices};
|
use crate::{distribution, prices};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -12,7 +12,6 @@ impl Vecs {
|
|||||||
&mut self,
|
&mut self,
|
||||||
starting_indexes: &Indexes,
|
starting_indexes: &Indexes,
|
||||||
prices: &prices::Vecs,
|
prices: &prices::Vecs,
|
||||||
blocks: &blocks::Vecs,
|
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
activity: &activity::Vecs,
|
activity: &activity::Vecs,
|
||||||
supply: &supply::Vecs,
|
supply: &supply::Vecs,
|
||||||
@@ -24,74 +23,70 @@ impl Vecs {
|
|||||||
let realized_price = &all_metrics.realized.price.cents.height;
|
let realized_price = &all_metrics.realized.price.cents.height;
|
||||||
let realized_cap = &all_metrics.realized.cap.cents.height;
|
let realized_cap = &all_metrics.realized.cap.cents.height;
|
||||||
|
|
||||||
self.vaulted_price.cents.height.compute_transform2(
|
self.vaulted_price.compute_all(
|
||||||
starting_indexes.height,
|
prices,
|
||||||
realized_price,
|
starting_indexes,
|
||||||
&activity.vaultedness.height,
|
exit,
|
||||||
|(i, price, vaultedness, ..)| {
|
|v| {
|
||||||
(i, Cents::from(f64::from(price) / f64::from(vaultedness)))
|
Ok(v.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
realized_price,
|
||||||
|
&activity.vaultedness.height,
|
||||||
|
|(i, price, vaultedness, ..)| {
|
||||||
|
(i, Cents::from(f64::from(price) / f64::from(vaultedness)))
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?)
|
||||||
},
|
},
|
||||||
exit,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.vaulted_price_ratio.compute_rest(
|
self.active_price.compute_all(
|
||||||
blocks,
|
|
||||||
prices,
|
prices,
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
exit,
|
exit,
|
||||||
&self.vaulted_price.cents.height,
|
|v| {
|
||||||
)?;
|
Ok(v.compute_multiply(
|
||||||
|
starting_indexes.height,
|
||||||
self.active_price.cents.height.compute_multiply(
|
realized_price,
|
||||||
starting_indexes.height,
|
&activity.liveliness.height,
|
||||||
realized_price,
|
exit,
|
||||||
&activity.liveliness.height,
|
)?)
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.active_price_ratio.compute_rest(
|
|
||||||
blocks,
|
|
||||||
prices,
|
|
||||||
starting_indexes,
|
|
||||||
exit,
|
|
||||||
&self.active_price.cents.height,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.true_market_mean.cents.height.compute_transform2(
|
|
||||||
starting_indexes.height,
|
|
||||||
&cap.investor_cap.cents.height,
|
|
||||||
&supply.active_supply.btc.height,
|
|
||||||
|(i, cap_cents, supply_btc, ..)| {
|
|
||||||
(i, Cents::from(f64::from(cap_cents) / f64::from(supply_btc)))
|
|
||||||
},
|
},
|
||||||
exit,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.true_market_mean_ratio.compute_rest(
|
self.true_market_mean.compute_all(
|
||||||
blocks,
|
|
||||||
prices,
|
prices,
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
exit,
|
exit,
|
||||||
&self.true_market_mean.cents.height,
|
|v| {
|
||||||
|
Ok(v.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
&cap.investor_cap.cents.height,
|
||||||
|
&supply.active_supply.btc.height,
|
||||||
|
|(i, cap_cents, supply_btc, ..)| {
|
||||||
|
(i, Cents::from(f64::from(cap_cents) / f64::from(supply_btc)))
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?)
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// cointime_price = cointime_cap / circulating_supply
|
// cointime_price = cointime_cap / circulating_supply
|
||||||
self.cointime_price.cents.height.compute_transform2(
|
self.cointime_price.compute_all(
|
||||||
starting_indexes.height,
|
|
||||||
&cap.cointime_cap.cents.height,
|
|
||||||
circulating_supply,
|
|
||||||
|(i, cap_cents, supply_btc, ..)| {
|
|
||||||
(i, Cents::from(f64::from(cap_cents) / f64::from(supply_btc)))
|
|
||||||
},
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.cointime_price_ratio.compute_rest(
|
|
||||||
blocks,
|
|
||||||
prices,
|
prices,
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
exit,
|
exit,
|
||||||
&self.cointime_price.cents.height,
|
|v| {
|
||||||
|
Ok(v.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
&cap.cointime_cap.cents.height,
|
||||||
|
circulating_supply,
|
||||||
|
|(i, cap_cents, supply_btc, ..)| {
|
||||||
|
(i, Cents::from(f64::from(cap_cents) / f64::from(supply_btc)))
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?)
|
||||||
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// transfer_price = cointime_price - vaulted_price
|
// transfer_price = cointime_price - vaulted_price
|
||||||
@@ -99,38 +94,20 @@ impl Vecs {
|
|||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&self.cointime_price.cents.height,
|
&self.cointime_price.cents.height,
|
||||||
&self.vaulted_price.cents.height,
|
&self.vaulted_price.cents.height,
|
||||||
|(i, cointime, vaulted, ..)| {
|
|(i, cointime, vaulted, ..)| (i, cointime.saturating_sub(vaulted)),
|
||||||
(i, cointime.saturating_sub(vaulted))
|
|
||||||
},
|
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
self.transfer_price.compute_rest(prices, starting_indexes, exit)?;
|
||||||
self.transfer_price_ratio.compute_rest(
|
|
||||||
blocks,
|
|
||||||
prices,
|
|
||||||
starting_indexes,
|
|
||||||
exit,
|
|
||||||
&self.transfer_price.cents.height,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// balanced_price = (realized_price + transfer_price) / 2
|
// balanced_price = (realized_price + transfer_price) / 2
|
||||||
self.balanced_price.cents.height.compute_transform2(
|
self.balanced_price.cents.height.compute_transform2(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
realized_price,
|
realized_price,
|
||||||
&self.transfer_price.cents.height,
|
&self.transfer_price.cents.height,
|
||||||
|(i, realized, transfer, ..)| {
|
|(i, realized, transfer, ..)| (i, (realized + transfer) / 2u64),
|
||||||
(i, (realized + transfer) / 2u64)
|
|
||||||
},
|
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
self.balanced_price.compute_rest(prices, starting_indexes, exit)?;
|
||||||
self.balanced_price_ratio.compute_rest(
|
|
||||||
blocks,
|
|
||||||
prices,
|
|
||||||
starting_indexes,
|
|
||||||
exit,
|
|
||||||
&self.balanced_price.cents.height,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// terminal_price = 21M × transfer_price / circulating_supply_btc
|
// terminal_price = 21M × transfer_price / circulating_supply_btc
|
||||||
self.terminal_price.cents.height.compute_transform2(
|
self.terminal_price.cents.height.compute_transform2(
|
||||||
@@ -147,14 +124,7 @@ impl Vecs {
|
|||||||
},
|
},
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
self.terminal_price.compute_rest(prices, starting_indexes, exit)?;
|
||||||
self.terminal_price_ratio.compute_rest(
|
|
||||||
blocks,
|
|
||||||
prices,
|
|
||||||
starting_indexes,
|
|
||||||
exit,
|
|
||||||
&self.terminal_price.cents.height,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// cumulative_market_cap = Σ(market_cap) in dollars
|
// cumulative_market_cap = Σ(market_cap) in dollars
|
||||||
self.cumulative_market_cap
|
self.cumulative_market_cap
|
||||||
@@ -183,14 +153,7 @@ impl Vecs {
|
|||||||
},
|
},
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
self.delta_price.compute_rest(prices, starting_indexes, exit)?;
|
||||||
self.delta_price_ratio.compute_rest(
|
|
||||||
blocks,
|
|
||||||
prices,
|
|
||||||
starting_indexes,
|
|
||||||
exit,
|
|
||||||
&self.delta_price.cents.height,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use vecdb::Database;
|
|||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{
|
use crate::{
|
||||||
indexes,
|
indexes,
|
||||||
internal::{ComputedPerBlock, RatioPerBlockExtended, Price},
|
internal::{ComputedPerBlock, PriceWithRatioExtendedPerBlock},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
@@ -14,63 +14,27 @@ impl Vecs {
|
|||||||
version: Version,
|
version: Version,
|
||||||
indexes: &indexes::Vecs,
|
indexes: &indexes::Vecs,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let vaulted_price = Price::forced_import(db, "vaulted_price", version, indexes)?;
|
macro_rules! import {
|
||||||
let vaulted_price_ratio =
|
($name:expr) => {
|
||||||
RatioPerBlockExtended::forced_import(db, "vaulted_price", version, indexes)?;
|
PriceWithRatioExtendedPerBlock::forced_import(db, $name, version, indexes)?
|
||||||
|
};
|
||||||
let active_price = Price::forced_import(db, "active_price", version, indexes)?;
|
}
|
||||||
let active_price_ratio =
|
|
||||||
RatioPerBlockExtended::forced_import(db, "active_price", version, indexes)?;
|
|
||||||
|
|
||||||
let true_market_mean = Price::forced_import(db, "true_market_mean", version, indexes)?;
|
|
||||||
let true_market_mean_ratio = RatioPerBlockExtended::forced_import(
|
|
||||||
db,
|
|
||||||
"true_market_mean",
|
|
||||||
version,
|
|
||||||
indexes,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let cointime_price = Price::forced_import(db, "cointime_price", version, indexes)?;
|
|
||||||
let cointime_price_ratio =
|
|
||||||
RatioPerBlockExtended::forced_import(db, "cointime_price", version, indexes)?;
|
|
||||||
|
|
||||||
let transfer_price = Price::forced_import(db, "transfer_price", version, indexes)?;
|
|
||||||
let transfer_price_ratio =
|
|
||||||
RatioPerBlockExtended::forced_import(db, "transfer_price", version, indexes)?;
|
|
||||||
|
|
||||||
let balanced_price = Price::forced_import(db, "balanced_price", version, indexes)?;
|
|
||||||
let balanced_price_ratio =
|
|
||||||
RatioPerBlockExtended::forced_import(db, "balanced_price", version, indexes)?;
|
|
||||||
|
|
||||||
let terminal_price = Price::forced_import(db, "terminal_price", version, indexes)?;
|
|
||||||
let terminal_price_ratio =
|
|
||||||
RatioPerBlockExtended::forced_import(db, "terminal_price", version, indexes)?;
|
|
||||||
|
|
||||||
let delta_price = Price::forced_import(db, "delta_price", version, indexes)?;
|
|
||||||
let delta_price_ratio =
|
|
||||||
RatioPerBlockExtended::forced_import(db, "delta_price", version, indexes)?;
|
|
||||||
|
|
||||||
let cumulative_market_cap =
|
|
||||||
ComputedPerBlock::forced_import(db, "cumulative_market_cap", version, indexes)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
vaulted_price,
|
vaulted_price: import!("vaulted_price"),
|
||||||
vaulted_price_ratio,
|
active_price: import!("active_price"),
|
||||||
active_price,
|
true_market_mean: import!("true_market_mean"),
|
||||||
active_price_ratio,
|
cointime_price: import!("cointime_price"),
|
||||||
true_market_mean,
|
transfer_price: import!("transfer_price"),
|
||||||
true_market_mean_ratio,
|
balanced_price: import!("balanced_price"),
|
||||||
cointime_price,
|
terminal_price: import!("terminal_price"),
|
||||||
cointime_price_ratio,
|
delta_price: import!("delta_price"),
|
||||||
transfer_price,
|
cumulative_market_cap: ComputedPerBlock::forced_import(
|
||||||
transfer_price_ratio,
|
db,
|
||||||
balanced_price,
|
"cumulative_market_cap",
|
||||||
balanced_price_ratio,
|
version,
|
||||||
terminal_price,
|
indexes,
|
||||||
terminal_price_ratio,
|
)?,
|
||||||
delta_price,
|
|
||||||
delta_price_ratio,
|
|
||||||
cumulative_market_cap,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{Cents, Dollars};
|
use brk_types::Dollars;
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{ComputedPerBlock, RatioPerBlockExtended, Price};
|
use crate::internal::{ComputedPerBlock, PriceWithRatioExtendedPerBlock};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
pub vaulted_price: Price<ComputedPerBlock<Cents, M>>,
|
pub vaulted_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub vaulted_price_ratio: RatioPerBlockExtended<M>,
|
pub active_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub active_price: Price<ComputedPerBlock<Cents, M>>,
|
pub true_market_mean: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub active_price_ratio: RatioPerBlockExtended<M>,
|
pub cointime_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub true_market_mean: Price<ComputedPerBlock<Cents, M>>,
|
pub transfer_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub true_market_mean_ratio: RatioPerBlockExtended<M>,
|
pub balanced_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub cointime_price: Price<ComputedPerBlock<Cents, M>>,
|
pub terminal_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub cointime_price_ratio: RatioPerBlockExtended<M>,
|
pub delta_price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub transfer_price: Price<ComputedPerBlock<Cents, M>>,
|
|
||||||
pub transfer_price_ratio: RatioPerBlockExtended<M>,
|
|
||||||
pub balanced_price: Price<ComputedPerBlock<Cents, M>>,
|
|
||||||
pub balanced_price_ratio: RatioPerBlockExtended<M>,
|
|
||||||
pub terminal_price: Price<ComputedPerBlock<Cents, M>>,
|
|
||||||
pub terminal_price_ratio: RatioPerBlockExtended<M>,
|
|
||||||
pub delta_price: Price<ComputedPerBlock<Cents, M>>,
|
|
||||||
pub delta_price_ratio: RatioPerBlockExtended<M>,
|
|
||||||
|
|
||||||
pub cumulative_market_cap: ComputedPerBlock<Dollars, M>,
|
pub cumulative_market_cap: ComputedPerBlock<Dollars, M>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub struct AddressCohortVecs<M: StorageMode = Rw> {
|
|||||||
pub metrics: MinimalCohortMetrics<M>,
|
pub metrics: MinimalCohortMetrics<M>,
|
||||||
|
|
||||||
pub addr_count: ComputedPerBlock<StoredU64, M>,
|
pub addr_count: ComputedPerBlock<StoredU64, M>,
|
||||||
|
#[traversable(wrap = "addr_count", rename = "delta")]
|
||||||
pub addr_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
|
pub addr_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,18 @@ pub(super) struct CostBasisNode {
|
|||||||
sth_usd: i128,
|
sth_usd: i128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CostBasisNode {
|
||||||
|
#[inline]
|
||||||
|
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
all_sats: sats,
|
||||||
|
sth_sats: if is_sth { sats } else { 0 },
|
||||||
|
all_usd: usd,
|
||||||
|
sth_usd: if is_sth { usd } else { 0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FenwickNode for CostBasisNode {
|
impl FenwickNode for CostBasisNode {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn add_assign(&mut self, other: &Self) {
|
fn add_assign(&mut self, other: &Self) {
|
||||||
@@ -87,15 +99,13 @@ fn bucket_to_cents(bucket: usize) -> Cents {
|
|||||||
/// Map a CentsCompact price to a bucket index.
|
/// Map a CentsCompact price to a bucket index.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn price_to_bucket(price: CentsCompact) -> usize {
|
fn price_to_bucket(price: CentsCompact) -> usize {
|
||||||
let rounded = Cents::from(price).round_to_dollar(COST_BASIS_PRICE_DIGITS);
|
cents_to_bucket(price.into())
|
||||||
dollars_to_bucket(u64::from(rounded) / 100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map a Cents price to a bucket index.
|
/// Map a Cents price to a bucket index.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cents_to_bucket(price: Cents) -> usize {
|
fn cents_to_bucket(price: Cents) -> usize {
|
||||||
let rounded = price.round_to_dollar(COST_BASIS_PRICE_DIGITS);
|
dollars_to_bucket(u64::from(price.round_to_dollar(COST_BASIS_PRICE_DIGITS)) / 100)
|
||||||
dollars_to_bucket(u64::from(rounded) / 100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -143,13 +153,7 @@ impl CostBasisFenwick {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let bucket = price_to_bucket(price);
|
let bucket = price_to_bucket(price);
|
||||||
let net_usd = price.as_u128() as i128 * net_sats as i128;
|
let delta = CostBasisNode::new(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
|
||||||
let delta = CostBasisNode {
|
|
||||||
all_sats: net_sats,
|
|
||||||
sth_sats: if is_sth { net_sats } else { 0 },
|
|
||||||
all_usd: net_usd,
|
|
||||||
sth_usd: if is_sth { net_usd } else { 0 },
|
|
||||||
};
|
|
||||||
self.tree.add(bucket, &delta);
|
self.tree.add(bucket, &delta);
|
||||||
self.totals.add_assign(&delta);
|
self.totals.add_assign(&delta);
|
||||||
}
|
}
|
||||||
@@ -167,13 +171,7 @@ impl CostBasisFenwick {
|
|||||||
for (&price, &sats) in map.iter() {
|
for (&price, &sats) in map.iter() {
|
||||||
let bucket = price_to_bucket(price);
|
let bucket = price_to_bucket(price);
|
||||||
let s = u64::from(sats) as i64;
|
let s = u64::from(sats) as i64;
|
||||||
let usd = price.as_u128() as i128 * s as i128;
|
let node = CostBasisNode::new(s, price.as_u128() as i128 * s as i128, is_sth);
|
||||||
let node = CostBasisNode {
|
|
||||||
all_sats: s,
|
|
||||||
sth_sats: if is_sth { s } else { 0 },
|
|
||||||
all_usd: usd,
|
|
||||||
sth_usd: if is_sth { usd } else { 0 },
|
|
||||||
};
|
|
||||||
self.tree.add_raw(bucket, &node);
|
self.tree.add_raw(bucket, &node);
|
||||||
self.totals.add_assign(&node);
|
self.totals.add_assign(&node);
|
||||||
}
|
}
|
||||||
@@ -242,9 +240,9 @@ impl CostBasisFenwick {
|
|||||||
.batch_kth(&sat_targets, &sat_field, &mut sat_buckets);
|
.batch_kth(&sat_targets, &sat_field, &mut sat_buckets);
|
||||||
|
|
||||||
result.min_price = bucket_to_cents(sat_buckets[0]);
|
result.min_price = bucket_to_cents(sat_buckets[0]);
|
||||||
for i in 0..PERCENTILES_LEN {
|
(0..PERCENTILES_LEN).for_each(|i| {
|
||||||
result.sat_prices[i] = bucket_to_cents(sat_buckets[i + 1]);
|
result.sat_prices[i] = bucket_to_cents(sat_buckets[i + 1]);
|
||||||
}
|
});
|
||||||
result.max_price = bucket_to_cents(sat_buckets[PERCENTILES_LEN + 1]);
|
result.max_price = bucket_to_cents(sat_buckets[PERCENTILES_LEN + 1]);
|
||||||
|
|
||||||
// USD-weighted percentiles (batch)
|
// USD-weighted percentiles (batch)
|
||||||
@@ -266,6 +264,51 @@ impl CostBasisFenwick {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Supply density queries (±5% of spot price)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Compute supply density: % of supply with cost basis within ±5% of spot.
|
||||||
|
/// Returns (all_bps, sth_bps, lth_bps) as basis points (0-10000).
|
||||||
|
pub(super) fn density(&self, spot_price: Cents) -> (u16, u16, u16) {
|
||||||
|
if self.totals.all_sats <= 0 {
|
||||||
|
return (0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let spot_f64 = u64::from(spot_price) as f64;
|
||||||
|
let low = Cents::from((spot_f64 * 0.95) as u64);
|
||||||
|
let high = Cents::from((spot_f64 * 1.05) as u64);
|
||||||
|
|
||||||
|
let low_bucket = cents_to_bucket(low);
|
||||||
|
let high_bucket = cents_to_bucket(high);
|
||||||
|
|
||||||
|
let cum_high = self.tree.prefix_sum(high_bucket);
|
||||||
|
let cum_low = if low_bucket > 0 {
|
||||||
|
self.tree.prefix_sum(low_bucket - 1)
|
||||||
|
} else {
|
||||||
|
CostBasisNode::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let all_range = (cum_high.all_sats - cum_low.all_sats).max(0);
|
||||||
|
let sth_range = (cum_high.sth_sats - cum_low.sth_sats).max(0);
|
||||||
|
let lth_range = all_range - sth_range;
|
||||||
|
|
||||||
|
let to_bps = |range: i64, total: i64| -> u16 {
|
||||||
|
if total <= 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(range as f64 / total as f64 * 10000.0) as u16
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lth_total = self.totals.all_sats - self.totals.sth_sats;
|
||||||
|
(
|
||||||
|
to_bps(all_range, self.totals.all_sats),
|
||||||
|
to_bps(sth_range, self.totals.sth_sats),
|
||||||
|
to_bps(lth_range, lth_total),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Profitability queries (all cohort only)
|
// Profitability queries (all cohort only)
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
|
|||||||
|
|
||||||
use brk_cohort::{Filtered, PROFITABILITY_RANGE_COUNT, PROFIT_COUNT, TERM_NAMES};
|
use brk_cohort::{Filtered, PROFITABILITY_RANGE_COUNT, PROFIT_COUNT, TERM_NAMES};
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Height, Sats};
|
use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Height, Sats};
|
||||||
|
|
||||||
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
|
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
|
||||||
|
|
||||||
@@ -24,12 +24,7 @@ impl UTXOCohorts {
|
|||||||
states_path: &Path,
|
states_path: &Path,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.fenwick.is_initialized() {
|
if self.fenwick.is_initialized() {
|
||||||
// Per-block accurate percentiles from Fenwick tree
|
self.push_fenwick_results(height, spot_price)?;
|
||||||
self.push_percentiles_from_fenwick(height)?;
|
|
||||||
|
|
||||||
// Per-block accurate profitability from Fenwick tree with current spot_price
|
|
||||||
let prof = self.fenwick.profitability(spot_price);
|
|
||||||
push_profitability(height, &prof, &mut self.profitability)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disk distributions only at day boundaries
|
// Disk distributions only at day boundaries
|
||||||
@@ -40,18 +35,21 @@ impl UTXOCohorts {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push Fenwick-computed percentiles for all/sth/lth to vecs.
|
/// Push all Fenwick-derived per-block results: percentiles, density, profitability.
|
||||||
fn push_percentiles_from_fenwick(&mut self, height: Height) -> Result<()> {
|
fn push_fenwick_results(&mut self, height: Height, spot_price: Cents) -> Result<()> {
|
||||||
|
let (all_d, sth_d, lth_d) = self.fenwick.density(spot_price);
|
||||||
|
|
||||||
let all = self.fenwick.percentiles_all();
|
let all = self.fenwick.percentiles_all();
|
||||||
push_percentile_result(height, &all, &mut self.all.metrics.cost_basis)?;
|
push_cost_basis(height, &all, all_d, &mut self.all.metrics.cost_basis)?;
|
||||||
|
|
||||||
let sth = self.fenwick.percentiles_sth();
|
let sth = self.fenwick.percentiles_sth();
|
||||||
push_percentile_result(height, &sth, &mut self.sth.metrics.cost_basis)?;
|
push_cost_basis(height, &sth, sth_d, &mut self.sth.metrics.cost_basis)?;
|
||||||
|
|
||||||
let lth = self.fenwick.percentiles_lth();
|
let lth = self.fenwick.percentiles_lth();
|
||||||
push_percentile_result(height, <h, &mut self.lth.metrics.cost_basis)?;
|
push_cost_basis(height, <h, lth_d, &mut self.lth.metrics.cost_basis)?;
|
||||||
|
|
||||||
Ok(())
|
let prof = self.fenwick.profitability(spot_price);
|
||||||
|
push_profitability(height, &prof, &mut self.profitability)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// K-way merge only for writing daily cost basis distributions to disk.
|
/// K-way merge only for writing daily cost basis distributions to disk.
|
||||||
@@ -93,14 +91,16 @@ impl UTXOCohorts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a PercentileResult to cost basis vecs.
|
/// Push percentiles + density to cost basis vecs.
|
||||||
fn push_percentile_result(
|
fn push_cost_basis(
|
||||||
height: Height,
|
height: Height,
|
||||||
result: &PercentileResult,
|
percentiles: &PercentileResult,
|
||||||
|
density_bps: u16,
|
||||||
cost_basis: &mut CostBasis,
|
cost_basis: &mut CostBasis,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
cost_basis.truncate_push_minmax(height, result.min_price, result.max_price)?;
|
cost_basis.truncate_push_minmax(height, percentiles.min_price, percentiles.max_price)?;
|
||||||
cost_basis.truncate_push_percentiles(height, &result.sat_prices, &result.usd_prices)
|
cost_basis.truncate_push_percentiles(height, &percentiles.sat_prices, &percentiles.usd_prices)?;
|
||||||
|
cost_basis.truncate_push_density(height, BasisPoints16::from(density_bps))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert raw (cents × sats) accumulator to Dollars (÷ 100 for cents→dollars, ÷ 1e8 for sats).
|
/// Convert raw (cents × sats) accumulator to Dollars (÷ 100 for cents→dollars, ÷ 1e8 for sats).
|
||||||
|
|||||||
@@ -6,13 +6,18 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
|||||||
use crate::{
|
use crate::{
|
||||||
blocks,
|
blocks,
|
||||||
distribution::{metrics::ImportConfig, state::{CohortState, CostBasisOps, RealizedOps}},
|
distribution::{metrics::ImportConfig, state::{CohortState, CostBasisOps, RealizedOps}},
|
||||||
internal::PerBlockWithSum24h,
|
internal::{AmountPerBlockWithSum24h, PerBlockWithSum24h},
|
||||||
|
prices,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct ActivityCore<M: StorageMode = Rw> {
|
pub struct ActivityCore<M: StorageMode = Rw> {
|
||||||
pub sent: PerBlockWithSum24h<Sats, M>,
|
pub sent: PerBlockWithSum24h<Sats, M>,
|
||||||
pub coindays_destroyed: PerBlockWithSum24h<StoredF64, M>,
|
pub coindays_destroyed: PerBlockWithSum24h<StoredF64, M>,
|
||||||
|
#[traversable(wrap = "sent", rename = "in_profit")]
|
||||||
|
pub sent_in_profit: AmountPerBlockWithSum24h<M>,
|
||||||
|
#[traversable(wrap = "sent", rename = "in_loss")]
|
||||||
|
pub sent_in_loss: AmountPerBlockWithSum24h<M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivityCore {
|
impl ActivityCore {
|
||||||
@@ -21,6 +26,8 @@ impl ActivityCore {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
sent: cfg.import("sent", v1)?,
|
sent: cfg.import("sent", v1)?,
|
||||||
coindays_destroyed: cfg.import("coindays_destroyed", v1)?,
|
coindays_destroyed: cfg.import("coindays_destroyed", v1)?,
|
||||||
|
sent_in_profit: cfg.import("sent_in_profit", v1)?,
|
||||||
|
sent_in_loss: cfg.import("sent_in_loss", v1)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +37,8 @@ impl ActivityCore {
|
|||||||
.height
|
.height
|
||||||
.len()
|
.len()
|
||||||
.min(self.coindays_destroyed.raw.height.len())
|
.min(self.coindays_destroyed.raw.height.len())
|
||||||
|
.min(self.sent_in_profit.raw.sats.height.len())
|
||||||
|
.min(self.sent_in_loss.raw.sats.height.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push(
|
pub(crate) fn truncate_push(
|
||||||
@@ -42,6 +51,16 @@ impl ActivityCore {
|
|||||||
height,
|
height,
|
||||||
StoredF64::from(Bitcoin::from(state.satdays_destroyed)),
|
StoredF64::from(Bitcoin::from(state.satdays_destroyed)),
|
||||||
)?;
|
)?;
|
||||||
|
self.sent_in_profit
|
||||||
|
.raw
|
||||||
|
.sats
|
||||||
|
.height
|
||||||
|
.truncate_push(height, state.realized.sent_in_profit())?;
|
||||||
|
self.sent_in_loss
|
||||||
|
.raw
|
||||||
|
.sats
|
||||||
|
.height
|
||||||
|
.truncate_push(height, state.realized.sent_in_loss())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +68,10 @@ impl ActivityCore {
|
|||||||
vec![
|
vec![
|
||||||
&mut self.sent.raw.height as &mut dyn AnyStoredVec,
|
&mut self.sent.raw.height as &mut dyn AnyStoredVec,
|
||||||
&mut self.coindays_destroyed.raw.height,
|
&mut self.coindays_destroyed.raw.height,
|
||||||
|
&mut self.sent_in_profit.raw.sats.height,
|
||||||
|
&mut self.sent_in_profit.raw.cents.height,
|
||||||
|
&mut self.sent_in_loss.raw.sats.height,
|
||||||
|
&mut self.sent_in_loss.raw.cents.height,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +95,10 @@ impl ActivityCore {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
sum_others!(self, starting_indexes, others, exit; coindays_destroyed.raw.height);
|
sum_others!(self, starting_indexes, others, exit; coindays_destroyed.raw.height);
|
||||||
|
sum_others!(self, starting_indexes, others, exit; sent_in_profit.raw.sats.height);
|
||||||
|
sum_others!(self, starting_indexes, others, exit; sent_in_profit.raw.cents.height);
|
||||||
|
sum_others!(self, starting_indexes, others, exit; sent_in_loss.raw.sats.height);
|
||||||
|
sum_others!(self, starting_indexes, others, exit; sent_in_loss.raw.cents.height);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -96,4 +123,36 @@ impl ActivityCore {
|
|||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compute_sent_profitability(
|
||||||
|
&mut self,
|
||||||
|
blocks: &blocks::Vecs,
|
||||||
|
prices: &prices::Vecs,
|
||||||
|
starting_indexes: &Indexes,
|
||||||
|
exit: &Exit,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.sent_in_profit
|
||||||
|
.raw
|
||||||
|
.compute(prices, starting_indexes.height, exit)?;
|
||||||
|
self.sent_in_loss
|
||||||
|
.raw
|
||||||
|
.compute(prices, starting_indexes.height, exit)?;
|
||||||
|
|
||||||
|
self.sent_in_profit.sum.compute_rolling_sum(
|
||||||
|
starting_indexes.height,
|
||||||
|
&blocks.lookback.height_24h_ago,
|
||||||
|
&self.sent_in_profit.raw.sats.height,
|
||||||
|
&self.sent_in_profit.raw.cents.height,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
self.sent_in_loss.sum.compute_rolling_sum(
|
||||||
|
starting_indexes.height,
|
||||||
|
&blocks.lookback.height_24h_ago,
|
||||||
|
&self.sent_in_loss.raw.sats.height,
|
||||||
|
&self.sent_in_loss.raw.cents.height,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ pub struct ActivityFull<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[traversable(wrap = "sent", rename = "sum")]
|
#[traversable(wrap = "sent", rename = "sum")]
|
||||||
pub sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
pub sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
||||||
|
#[traversable(wrap = "sent/in_profit", rename = "sum")]
|
||||||
|
pub sent_in_profit_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
||||||
|
#[traversable(wrap = "sent/in_loss", rename = "sum")]
|
||||||
|
pub sent_in_loss_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
||||||
|
|
||||||
pub coinyears_destroyed: LazyPerBlock<StoredF64, StoredF64>,
|
pub coinyears_destroyed: LazyPerBlock<StoredF64, StoredF64>,
|
||||||
|
|
||||||
@@ -49,6 +53,8 @@ impl ActivityFull {
|
|||||||
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
|
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
|
||||||
coindays_destroyed_sum,
|
coindays_destroyed_sum,
|
||||||
sent_sum_extended: cfg.import("sent", v1)?,
|
sent_sum_extended: cfg.import("sent", v1)?,
|
||||||
|
sent_in_profit_sum_extended: cfg.import("sent_in_profit", v1)?,
|
||||||
|
sent_in_loss_sum_extended: cfg.import("sent_in_loss", v1)?,
|
||||||
coinyears_destroyed,
|
coinyears_destroyed,
|
||||||
dormancy: cfg.import("dormancy", v1)?,
|
dormancy: cfg.import("dormancy", v1)?,
|
||||||
velocity: cfg.import("velocity", v1)?,
|
velocity: cfg.import("velocity", v1)?,
|
||||||
@@ -116,6 +122,19 @@ impl ActivityFull {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
self.sent_in_profit_sum_extended.compute_rolling_sum(
|
||||||
|
starting_indexes.height,
|
||||||
|
&window_starts,
|
||||||
|
&self.inner.sent_in_profit.raw.sats.height,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
self.sent_in_loss_sum_extended.compute_rolling_sum(
|
||||||
|
starting_indexes.height,
|
||||||
|
&window_starts,
|
||||||
|
&self.inner.sent_in_loss.raw.sats.height,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub struct AllCohortMetrics<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[traversable(wrap = "supply", rename = "delta")]
|
#[traversable(wrap = "supply", rename = "delta")]
|
||||||
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
|
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
|
||||||
#[traversable(wrap = "outputs", rename = "utxo_count_delta")]
|
#[traversable(wrap = "outputs/utxo_count", rename = "delta")]
|
||||||
pub utxo_count_delta_extended: RollingDeltaExcept1m<StoredU64, StoredI64, M>,
|
pub utxo_count_delta_extended: RollingDeltaExcept1m<StoredU64, StoredI64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ impl CoreCohortMetrics {
|
|||||||
|
|
||||||
self.activity
|
self.activity
|
||||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||||
|
self.activity
|
||||||
|
.compute_sent_profitability(blocks, prices, starting_indexes, exit)?;
|
||||||
|
|
||||||
self.realized
|
self.realized
|
||||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[traversable(wrap = "supply", rename = "delta")]
|
#[traversable(wrap = "supply", rename = "delta")]
|
||||||
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
|
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
|
||||||
#[traversable(wrap = "outputs", rename = "utxo_count_delta")]
|
#[traversable(wrap = "outputs/utxo_count", rename = "delta")]
|
||||||
pub utxo_count_delta_extended: RollingDeltaExcept1m<StoredU64, StoredI64, M>,
|
pub utxo_count_delta_extended: RollingDeltaExcept1m<StoredU64, StoredI64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
internal::{
|
internal::{
|
||||||
AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockWithSum24h, CentsType, ComputedPerBlock,
|
AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockWithSum24h, CentsType, ComputedPerBlock,
|
||||||
ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, FiatPerBlockWithSum24h,
|
ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, FiatPerBlockWithSum24h,
|
||||||
PerBlockWithSum24h, RatioPerBlock, RollingWindow24hAmountPerBlock,
|
PerBlockWithSum24h, PriceWithRatioExtendedPerBlock, PriceWithRatioPerBlock, RatioPerBlock, RollingWindow24hAmountPerBlock,
|
||||||
RollingWindow24hFiatPerBlock, RollingWindow24hPerBlock,
|
RollingWindow24hFiatPerBlock, RollingWindow24hPerBlock,
|
||||||
FiatPerBlock, FiatRollingDelta1m, FiatRollingDeltaExcept1m, NumericValue,
|
FiatPerBlock, FiatRollingDelta1m, FiatRollingDeltaExcept1m, NumericValue,
|
||||||
PercentPerBlock, PercentRollingWindows, Price, RollingDelta1m, RollingDeltaExcept1m,
|
PercentPerBlock, PercentRollingWindows, Price, RollingDelta1m, RollingDeltaExcept1m,
|
||||||
@@ -40,6 +40,8 @@ impl_config_import!(
|
|||||||
AmountPerBlock,
|
AmountPerBlock,
|
||||||
AmountPerBlockCumulative,
|
AmountPerBlockCumulative,
|
||||||
RollingWindow24hAmountPerBlock,
|
RollingWindow24hAmountPerBlock,
|
||||||
|
PriceWithRatioPerBlock,
|
||||||
|
PriceWithRatioExtendedPerBlock,
|
||||||
RatioPerBlock<BasisPoints32>,
|
RatioPerBlock<BasisPoints32>,
|
||||||
RatioPerBlock<BasisPointsSigned32>,
|
RatioPerBlock<BasisPointsSigned32>,
|
||||||
PercentPerBlock<BasisPoints16>,
|
PercentPerBlock<BasisPoints16>,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{Cents, Height, Version};
|
use brk_types::{BasisPoints16, Cents, Height, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::internal::{ComputedPerBlock, PercentilesVecs, Price, PERCENTILES_LEN};
|
use crate::internal::{ComputedPerBlock, PercentPerBlock, PercentilesVecs, Price, PERCENTILES_LEN};
|
||||||
|
|
||||||
use super::ImportConfig;
|
use super::ImportConfig;
|
||||||
|
|
||||||
/// Cost basis metrics: min/max + percentiles.
|
/// Cost basis metrics: min/max + percentiles + supply density.
|
||||||
/// Used by all/sth/lth cohorts only.
|
/// Used by all/sth/lth cohorts only.
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct CostBasis<M: StorageMode = Rw> {
|
pub struct CostBasis<M: StorageMode = Rw> {
|
||||||
@@ -15,6 +15,7 @@ pub struct CostBasis<M: StorageMode = Rw> {
|
|||||||
pub max: Price<ComputedPerBlock<Cents, M>>,
|
pub max: Price<ComputedPerBlock<Cents, M>>,
|
||||||
pub percentiles: PercentilesVecs<M>,
|
pub percentiles: PercentilesVecs<M>,
|
||||||
pub invested_capital: PercentilesVecs<M>,
|
pub invested_capital: PercentilesVecs<M>,
|
||||||
|
pub supply_density: PercentPerBlock<BasisPoints16, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CostBasis {
|
impl CostBasis {
|
||||||
@@ -34,11 +35,22 @@ impl CostBasis {
|
|||||||
cfg.version,
|
cfg.version,
|
||||||
cfg.indexes,
|
cfg.indexes,
|
||||||
)?,
|
)?,
|
||||||
|
supply_density: PercentPerBlock::forced_import(
|
||||||
|
cfg.db,
|
||||||
|
&cfg.name("supply_density"),
|
||||||
|
cfg.version,
|
||||||
|
cfg.indexes,
|
||||||
|
)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||||
self.min.cents.height.len().min(self.max.cents.height.len())
|
self.min
|
||||||
|
.cents
|
||||||
|
.height
|
||||||
|
.len()
|
||||||
|
.min(self.max.cents.height.len())
|
||||||
|
.min(self.supply_density.bps.height.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push_minmax(
|
pub(crate) fn truncate_push_minmax(
|
||||||
@@ -63,6 +75,14 @@ impl CostBasis {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn truncate_push_density(
|
||||||
|
&mut self,
|
||||||
|
height: Height,
|
||||||
|
density_bps: BasisPoints16,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(self.supply_density.bps.height.truncate_push(height, density_bps)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||||
self.percentiles
|
self.percentiles
|
||||||
.validate_computed_version_or_reset(base_version)?;
|
.validate_computed_version_or_reset(base_version)?;
|
||||||
@@ -75,6 +95,7 @@ impl CostBasis {
|
|||||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![
|
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![
|
||||||
&mut self.min.cents.height,
|
&mut self.min.cents.height,
|
||||||
&mut self.max.cents.height,
|
&mut self.max.cents.height,
|
||||||
|
&mut self.supply_density.bps.height,
|
||||||
];
|
];
|
||||||
vecs.extend(
|
vecs.extend(
|
||||||
self.percentiles
|
self.percentiles
|
||||||
|
|||||||
@@ -188,6 +188,8 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState, CostBa
|
|||||||
.compute_rest(blocks, starting_indexes, exit)?;
|
.compute_rest(blocks, starting_indexes, exit)?;
|
||||||
self.activity_mut()
|
self.activity_mut()
|
||||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||||
|
self.activity_core_mut()
|
||||||
|
.compute_sent_profitability(blocks, prices, starting_indexes, exit)?;
|
||||||
|
|
||||||
self.realized_mut()
|
self.realized_mut()
|
||||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct OutputsFull<M: StorageMode = Rw> {
|
|||||||
#[traversable(flatten)]
|
#[traversable(flatten)]
|
||||||
pub base: OutputsBase<M>,
|
pub base: OutputsBase<M>,
|
||||||
|
|
||||||
|
#[traversable(wrap = "utxo_count", rename = "delta")]
|
||||||
pub utxo_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
|
pub utxo_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ use brk_traversable::Traversable;
|
|||||||
use brk_types::{Bitcoin, Cents, CentsSigned, Dollars, Height, Indexes, StoredF64, Version};
|
use brk_types::{Bitcoin, Cents, CentsSigned, Dollars, Height, Indexes, StoredF64, Version};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{
|
use vecdb::{
|
||||||
AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, WritableVec,
|
AnyStoredVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks,
|
blocks,
|
||||||
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
internal::{
|
internal::{
|
||||||
AmountPerBlockWithSum24h, ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
|
ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
|
||||||
NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
|
NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
|
||||||
RollingWindow24hPerBlock,
|
RollingWindow24hPerBlock,
|
||||||
},
|
},
|
||||||
@@ -26,12 +26,6 @@ pub struct RealizedSoprCore<M: StorageMode = Rw> {
|
|||||||
pub ratio: RollingWindow24hPerBlock<StoredF64, M>,
|
pub ratio: RollingWindow24hPerBlock<StoredF64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Traversable)]
|
|
||||||
pub struct RealizedSentCore<M: StorageMode = Rw> {
|
|
||||||
pub in_profit: AmountPerBlockWithSum24h<M>,
|
|
||||||
pub in_loss: AmountPerBlockWithSum24h<M>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deref, DerefMut, Traversable)]
|
#[derive(Deref, DerefMut, Traversable)]
|
||||||
pub struct RealizedCore<M: StorageMode = Rw> {
|
pub struct RealizedCore<M: StorageMode = Rw> {
|
||||||
#[deref]
|
#[deref]
|
||||||
@@ -51,7 +45,6 @@ pub struct RealizedCore<M: StorageMode = Rw> {
|
|||||||
pub neg_loss: LazyPerBlock<Dollars, Cents>,
|
pub neg_loss: LazyPerBlock<Dollars, Cents>,
|
||||||
pub net_pnl: PerBlockWithSum24h<CentsSigned, M>,
|
pub net_pnl: PerBlockWithSum24h<CentsSigned, M>,
|
||||||
pub sopr: RealizedSoprCore<M>,
|
pub sopr: RealizedSoprCore<M>,
|
||||||
pub sent: RealizedSentCore<M>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealizedCore {
|
impl RealizedCore {
|
||||||
@@ -78,44 +71,20 @@ impl RealizedCore {
|
|||||||
sopr: RealizedSoprCore {
|
sopr: RealizedSoprCore {
|
||||||
ratio: cfg.import("sopr", v1)?,
|
ratio: cfg.import("sopr", v1)?,
|
||||||
},
|
},
|
||||||
sent: RealizedSentCore {
|
|
||||||
in_profit: cfg.import("sent_in_profit", v1)?,
|
|
||||||
in_loss: cfg.import("sent_in_loss", v1)?,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||||
self.minimal
|
self.minimal.min_stateful_height_len()
|
||||||
.min_stateful_height_len()
|
|
||||||
.min(self.sent.in_profit.raw.sats.height.len())
|
|
||||||
.min(self.sent.in_loss.raw.sats.height.len())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||||
self.minimal.truncate_push(height, state)?;
|
self.minimal.truncate_push(height, state)?;
|
||||||
self.sent
|
|
||||||
.in_profit
|
|
||||||
.raw
|
|
||||||
.sats
|
|
||||||
.height
|
|
||||||
.truncate_push(height, state.realized.sent_in_profit())?;
|
|
||||||
self.sent
|
|
||||||
.in_loss
|
|
||||||
.raw
|
|
||||||
.sats
|
|
||||||
.height
|
|
||||||
.truncate_push(height, state.realized.sent_in_loss())?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||||
let mut vecs = self.minimal.collect_vecs_mut();
|
self.minimal.collect_vecs_mut()
|
||||||
vecs.push(&mut self.sent.in_profit.raw.sats.height as &mut dyn AnyStoredVec);
|
|
||||||
vecs.push(&mut self.sent.in_profit.raw.cents.height);
|
|
||||||
vecs.push(&mut self.sent.in_loss.raw.sats.height);
|
|
||||||
vecs.push(&mut self.sent.in_loss.raw.cents.height);
|
|
||||||
vecs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_from_stateful(
|
pub(crate) fn compute_from_stateful(
|
||||||
@@ -128,11 +97,6 @@ impl RealizedCore {
|
|||||||
self.minimal
|
self.minimal
|
||||||
.compute_from_stateful(starting_indexes, &minimal_refs, exit)?;
|
.compute_from_stateful(starting_indexes, &minimal_refs, exit)?;
|
||||||
|
|
||||||
sum_others!(self, starting_indexes, others, exit; sent.in_profit.raw.sats.height);
|
|
||||||
sum_others!(self, starting_indexes, others, exit; sent.in_profit.raw.cents.height);
|
|
||||||
sum_others!(self, starting_indexes, others, exit; sent.in_loss.raw.sats.height);
|
|
||||||
sum_others!(self, starting_indexes, others, exit; sent.in_loss.raw.cents.height);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,30 +171,6 @@ impl RealizedCore {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.sent
|
|
||||||
.in_profit
|
|
||||||
.raw
|
|
||||||
.compute(prices, starting_indexes.height, exit)?;
|
|
||||||
self.sent
|
|
||||||
.in_loss
|
|
||||||
.raw
|
|
||||||
.compute(prices, starting_indexes.height, exit)?;
|
|
||||||
|
|
||||||
self.sent.in_profit.sum.compute_rolling_sum(
|
|
||||||
starting_indexes.height,
|
|
||||||
&blocks.lookback.height_24h_ago,
|
|
||||||
&self.sent.in_profit.raw.sats.height,
|
|
||||||
&self.sent.in_profit.raw.cents.height,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
self.sent.in_loss.sum.compute_rolling_sum(
|
|
||||||
starting_indexes.height,
|
|
||||||
&blocks.lookback.height_24h_ago,
|
|
||||||
&self.sent.in_loss.raw.sats.height,
|
|
||||||
&self.sent.in_loss.raw.cents.height,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use brk_error::Result;
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats,
|
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats,
|
||||||
Dollars, Height, Indexes, Sats, StoredF64, Version,
|
Dollars, Height, Indexes, StoredF64, Version,
|
||||||
};
|
};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{
|
use vecdb::{
|
||||||
@@ -16,9 +16,10 @@ use crate::{
|
|||||||
internal::{
|
internal::{
|
||||||
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, FiatPerBlock,
|
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, FiatPerBlock,
|
||||||
FiatRollingDelta1m, FiatRollingDeltaExcept1m, LazyPerBlock, PercentPerBlock,
|
FiatRollingDelta1m, FiatRollingDeltaExcept1m, LazyPerBlock, PercentPerBlock,
|
||||||
PercentRollingWindows, Price, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32,
|
PercentRollingWindows, Price, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32,
|
||||||
RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlock, RatioPerBlockPercentiles,
|
RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDollarsBp32,
|
||||||
RatioPerBlockStdDevBands, RollingWindows, RollingWindowsFrom1w,
|
RatioPerBlockPercentiles, RatioPerBlockStdDevBands, RatioSma, RollingWindows,
|
||||||
|
RollingWindowsFrom1w,
|
||||||
},
|
},
|
||||||
prices,
|
prices,
|
||||||
};
|
};
|
||||||
@@ -87,14 +88,6 @@ pub struct RealizedSopr<M: StorageMode = Rw> {
|
|||||||
pub ratio_extended: RollingWindowsFrom1w<StoredF64, M>,
|
pub ratio_extended: RollingWindowsFrom1w<StoredF64, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Traversable)]
|
|
||||||
pub struct RealizedSentFull<M: StorageMode = Rw> {
|
|
||||||
#[traversable(wrap = "in_profit", rename = "sum")]
|
|
||||||
pub in_profit_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
|
||||||
#[traversable(wrap = "in_loss", rename = "sum")]
|
|
||||||
pub in_loss_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RealizedPeakRegret<M: StorageMode = Rw> {
|
pub struct RealizedPeakRegret<M: StorageMode = Rw> {
|
||||||
#[traversable(flatten)]
|
#[traversable(flatten)]
|
||||||
@@ -104,13 +97,11 @@ pub struct RealizedPeakRegret<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RealizedInvestor<M: StorageMode = Rw> {
|
pub struct RealizedInvestor<M: StorageMode = Rw> {
|
||||||
pub price: Price<ComputedPerBlock<Cents, M>>,
|
pub price: PriceWithRatioExtendedPerBlock<M>,
|
||||||
pub price_ratio: RatioPerBlock<BasisPoints32, M>,
|
|
||||||
pub lower_price_band: Price<ComputedPerBlock<Cents, M>>,
|
pub lower_price_band: Price<ComputedPerBlock<Cents, M>>,
|
||||||
pub upper_price_band: Price<ComputedPerBlock<Cents, M>>,
|
pub upper_price_band: Price<ComputedPerBlock<Cents, M>>,
|
||||||
#[traversable(wrap = "cap", rename = "raw")]
|
#[traversable(wrap = "cap", rename = "raw")]
|
||||||
pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||||
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deref, DerefMut, Traversable)]
|
#[derive(Deref, DerefMut, Traversable)]
|
||||||
@@ -125,7 +116,6 @@ pub struct RealizedFull<M: StorageMode = Rw> {
|
|||||||
pub gross_pnl: RealizedGrossPnl<M>,
|
pub gross_pnl: RealizedGrossPnl<M>,
|
||||||
pub net_pnl: RealizedNetPnl<M>,
|
pub net_pnl: RealizedNetPnl<M>,
|
||||||
pub sopr: RealizedSopr<M>,
|
pub sopr: RealizedSopr<M>,
|
||||||
pub sent: RealizedSentFull<M>,
|
|
||||||
pub peak_regret: RealizedPeakRegret<M>,
|
pub peak_regret: RealizedPeakRegret<M>,
|
||||||
pub investor: RealizedInvestor<M>,
|
pub investor: RealizedInvestor<M>,
|
||||||
|
|
||||||
@@ -139,9 +129,11 @@ pub struct RealizedFull<M: StorageMode = Rw> {
|
|||||||
#[traversable(wrap = "cap", rename = "rel_to_own_mcap")]
|
#[traversable(wrap = "cap", rename = "rel_to_own_mcap")]
|
||||||
pub cap_rel_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
|
pub cap_rel_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
|
||||||
|
|
||||||
#[traversable(wrap = "price_ratio", rename = "percentiles")]
|
#[traversable(wrap = "price", rename = "percentiles")]
|
||||||
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
|
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
|
||||||
#[traversable(wrap = "price_ratio", rename = "std_dev")]
|
#[traversable(wrap = "price", rename = "sma")]
|
||||||
|
pub price_ratio_sma: RatioSma<M>,
|
||||||
|
#[traversable(wrap = "price", rename = "std_dev")]
|
||||||
pub price_ratio_std_dev: RatioPerBlockStdDevBands<M>,
|
pub price_ratio_std_dev: RatioPerBlockStdDevBands<M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,12 +210,6 @@ impl RealizedFull {
|
|||||||
ratio_extended: cfg.import("sopr", v1)?,
|
ratio_extended: cfg.import("sopr", v1)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sent
|
|
||||||
let sent = RealizedSentFull {
|
|
||||||
in_profit_sum_extended: cfg.import("sent_in_profit", v1)?,
|
|
||||||
in_loss_sum_extended: cfg.import("sent_in_loss", v1)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Peak regret
|
// Peak regret
|
||||||
let peak_regret = RealizedPeakRegret {
|
let peak_regret = RealizedPeakRegret {
|
||||||
value: cfg.import("realized_peak_regret", Version::new(2))?,
|
value: cfg.import("realized_peak_regret", Version::new(2))?,
|
||||||
@@ -234,16 +220,9 @@ impl RealizedFull {
|
|||||||
// Investor
|
// Investor
|
||||||
let investor = RealizedInvestor {
|
let investor = RealizedInvestor {
|
||||||
price: cfg.import("investor_price", v0)?,
|
price: cfg.import("investor_price", v0)?,
|
||||||
price_ratio: cfg.import("investor_price", v0)?,
|
|
||||||
lower_price_band: cfg.import("lower_price_band", v0)?,
|
lower_price_band: cfg.import("lower_price_band", v0)?,
|
||||||
upper_price_band: cfg.import("upper_price_band", v0)?,
|
upper_price_band: cfg.import("upper_price_band", v0)?,
|
||||||
cap_raw: cfg.import("investor_cap_raw", v0)?,
|
cap_raw: cfg.import("investor_cap_raw", v0)?,
|
||||||
price_ratio_percentiles: RatioPerBlockPercentiles::forced_import(
|
|
||||||
cfg.db,
|
|
||||||
&cfg.name("investor_price"),
|
|
||||||
cfg.version,
|
|
||||||
cfg.indexes,
|
|
||||||
)?,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Price ratio stats
|
// Price ratio stats
|
||||||
@@ -257,7 +236,6 @@ impl RealizedFull {
|
|||||||
gross_pnl,
|
gross_pnl,
|
||||||
net_pnl,
|
net_pnl,
|
||||||
sopr,
|
sopr,
|
||||||
sent,
|
|
||||||
peak_regret,
|
peak_regret,
|
||||||
investor,
|
investor,
|
||||||
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
|
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
|
||||||
@@ -270,6 +248,12 @@ impl RealizedFull {
|
|||||||
realized_price_version,
|
realized_price_version,
|
||||||
cfg.indexes,
|
cfg.indexes,
|
||||||
)?,
|
)?,
|
||||||
|
price_ratio_sma: RatioSma::forced_import(
|
||||||
|
cfg.db,
|
||||||
|
&realized_price_name,
|
||||||
|
realized_price_version,
|
||||||
|
cfg.indexes,
|
||||||
|
)?,
|
||||||
price_ratio_std_dev: RatioPerBlockStdDevBands::forced_import(
|
price_ratio_std_dev: RatioPerBlockStdDevBands::forced_import(
|
||||||
cfg.db,
|
cfg.db,
|
||||||
&realized_price_name,
|
&realized_price_name,
|
||||||
@@ -513,20 +497,6 @@ impl RealizedFull {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Sent rolling sums (1w, 1m, 1y)
|
|
||||||
self.sent.in_profit_sum_extended.compute_rolling_sum(
|
|
||||||
starting_indexes.height,
|
|
||||||
&window_starts,
|
|
||||||
&self.core.sent.in_profit.raw.sats.height,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
self.sent.in_loss_sum_extended.compute_rolling_sum(
|
|
||||||
starting_indexes.height,
|
|
||||||
&window_starts,
|
|
||||||
&self.core.sent.in_loss.raw.sats.height,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Profit/loss value created/destroyed rolling sums
|
// Profit/loss value created/destroyed rolling sums
|
||||||
self.profit.value_created_sum.compute_rolling_sum(
|
self.profit.value_created_sum.compute_rolling_sum(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
@@ -616,11 +586,10 @@ impl RealizedFull {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Investor price ratio and bands
|
// Investor price ratio, percentiles and bands
|
||||||
self.investor.price_ratio.compute_ratio(
|
self.investor.price.compute_rest(
|
||||||
|
prices,
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
&prices.price.cents.height,
|
|
||||||
&self.investor.price.cents.height,
|
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -727,31 +696,28 @@ impl RealizedFull {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Price ratio: percentiles and std dev bands
|
// Price ratio: percentiles, sma and std dev bands
|
||||||
self.price_ratio_percentiles.compute(
|
self.price_ratio_percentiles.compute(
|
||||||
|
starting_indexes,
|
||||||
|
exit,
|
||||||
|
&self.core.minimal.price.ratio.height,
|
||||||
|
&self.core.minimal.price.cents.height,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.price_ratio_sma.compute(
|
||||||
blocks,
|
blocks,
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
exit,
|
exit,
|
||||||
&self.core.minimal.price_ratio.ratio.height,
|
&self.core.minimal.price.ratio.height,
|
||||||
&self.core.minimal.price.cents.height,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.price_ratio_std_dev.compute(
|
self.price_ratio_std_dev.compute(
|
||||||
blocks,
|
blocks,
|
||||||
starting_indexes,
|
starting_indexes,
|
||||||
exit,
|
exit,
|
||||||
&self.core.minimal.price_ratio.ratio.height,
|
&self.core.minimal.price.ratio.height,
|
||||||
&self.core.minimal.price.cents.height,
|
&self.core.minimal.price.cents.height,
|
||||||
)?;
|
&self.price_ratio_sma,
|
||||||
|
|
||||||
// Investor price ratio: percentiles
|
|
||||||
let investor_price = &self.investor.price.cents.height;
|
|
||||||
self.investor.price_ratio_percentiles.compute(
|
|
||||||
blocks,
|
|
||||||
starting_indexes,
|
|
||||||
exit,
|
|
||||||
&self.investor.price_ratio.ratio.height,
|
|
||||||
investor_price,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ use crate::{
|
|||||||
blocks,
|
blocks,
|
||||||
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
internal::{
|
internal::{
|
||||||
ComputedPerBlock, FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
|
FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
|
||||||
PerBlockWithSum24h, Price, RatioPerBlock,
|
PerBlockWithSum24h, PriceWithRatioPerBlock, RatioPerBlock,
|
||||||
},
|
},
|
||||||
prices,
|
prices,
|
||||||
};
|
};
|
||||||
@@ -33,8 +33,7 @@ pub struct RealizedMinimal<M: StorageMode = Rw> {
|
|||||||
pub cap: FiatPerBlock<Cents, M>,
|
pub cap: FiatPerBlock<Cents, M>,
|
||||||
pub profit: FiatPerBlockWithSum24h<Cents, M>,
|
pub profit: FiatPerBlockWithSum24h<Cents, M>,
|
||||||
pub loss: FiatPerBlockWithSum24h<Cents, M>,
|
pub loss: FiatPerBlockWithSum24h<Cents, M>,
|
||||||
pub price: Price<ComputedPerBlock<Cents, M>>,
|
pub price: PriceWithRatioPerBlock<M>,
|
||||||
pub price_ratio: RatioPerBlock<BasisPoints32, M>,
|
|
||||||
pub mvrv: LazyPerBlock<StoredF32>,
|
pub mvrv: LazyPerBlock<StoredF32>,
|
||||||
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
|
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
|
||||||
|
|
||||||
@@ -47,12 +46,11 @@ impl RealizedMinimal {
|
|||||||
|
|
||||||
let cap: FiatPerBlock<Cents> = cfg.import("realized_cap", Version::ZERO)?;
|
let cap: FiatPerBlock<Cents> = cfg.import("realized_cap", Version::ZERO)?;
|
||||||
|
|
||||||
let realized_price = cfg.import("realized_price", v1)?;
|
let price: PriceWithRatioPerBlock = cfg.import("realized_price", v1)?;
|
||||||
let realized_price_ratio: RatioPerBlock = cfg.import("realized_price", v1)?;
|
|
||||||
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
|
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
|
||||||
&cfg.name("mvrv"),
|
&cfg.name("mvrv"),
|
||||||
cfg.version,
|
cfg.version,
|
||||||
&realized_price_ratio.ratio,
|
&price.ratio,
|
||||||
);
|
);
|
||||||
|
|
||||||
let nupl = cfg.import("nupl", v1)?;
|
let nupl = cfg.import("nupl", v1)?;
|
||||||
@@ -61,8 +59,7 @@ impl RealizedMinimal {
|
|||||||
cap,
|
cap,
|
||||||
profit: cfg.import("realized_profit", v1)?,
|
profit: cfg.import("realized_profit", v1)?,
|
||||||
loss: cfg.import("realized_loss", v1)?,
|
loss: cfg.import("realized_loss", v1)?,
|
||||||
price: realized_price,
|
price,
|
||||||
price_ratio: realized_price_ratio,
|
|
||||||
mvrv,
|
mvrv,
|
||||||
nupl,
|
nupl,
|
||||||
sopr: RealizedSoprMinimal {
|
sopr: RealizedSoprMinimal {
|
||||||
@@ -164,28 +161,24 @@ impl RealizedMinimal {
|
|||||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.price.cents.height.compute_transform2(
|
let cap = &self.cap.cents.height;
|
||||||
starting_indexes.height,
|
self.price.compute_all(prices, starting_indexes, exit, |v| {
|
||||||
&self.cap.cents.height,
|
Ok(v.compute_transform2(
|
||||||
height_to_supply,
|
starting_indexes.height,
|
||||||
|(i, cap_cents, supply, ..)| {
|
cap,
|
||||||
let cap = cap_cents.as_u128();
|
height_to_supply,
|
||||||
let supply_sats = Sats::from(supply).as_u128();
|
|(i, cap_cents, supply, ..)| {
|
||||||
if supply_sats == 0 {
|
let cap = cap_cents.as_u128();
|
||||||
(i, Cents::ZERO)
|
let supply_sats = Sats::from(supply).as_u128();
|
||||||
} else {
|
if supply_sats == 0 {
|
||||||
(i, Cents::from(cap * Sats::ONE_BTC_U128 / supply_sats))
|
(i, Cents::ZERO)
|
||||||
}
|
} else {
|
||||||
},
|
(i, Cents::from(cap * Sats::ONE_BTC_U128 / supply_sats))
|
||||||
exit,
|
}
|
||||||
)?;
|
},
|
||||||
|
exit,
|
||||||
self.price_ratio.compute_ratio(
|
)?)
|
||||||
starting_indexes,
|
})?;
|
||||||
&prices.price.cents.height,
|
|
||||||
&self.price.cents.height,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.nupl.bps.height.compute_transform2(
|
self.nupl.bps.height.compute_transform2(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{Bitcoin, Dollars, Indexes, StoredF32};
|
use brk_types::{Bitcoin, Dollars, Indexes, Sats, StoredF32};
|
||||||
use vecdb::Exit;
|
use vecdb::{Exit, ReadableVec};
|
||||||
|
|
||||||
use super::{gini, Vecs};
|
use super::{gini, Vecs};
|
||||||
use crate::{distribution, internal::RatioDollarsBp32, mining, transactions};
|
use crate::{distribution, internal::RatioDollarsBp32, market, mining, transactions};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
mining: &mining::Vecs,
|
mining: &mining::Vecs,
|
||||||
distribution: &distribution::Vecs,
|
distribution: &distribution::Vecs,
|
||||||
transactions: &transactions::Vecs,
|
transactions: &transactions::Vecs,
|
||||||
|
market: &market::Vecs,
|
||||||
starting_indexes: &Indexes,
|
starting_indexes: &Indexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -72,7 +74,6 @@ impl Vecs {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Thermocap Multiple: market_cap / thermo_cap
|
// Thermocap Multiple: market_cap / thermo_cap
|
||||||
// thermo_cap = cumulative subsidy in USD
|
|
||||||
self.thermocap_multiple
|
self.thermocap_multiple
|
||||||
.bps
|
.bps
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
||||||
@@ -82,15 +83,9 @@ impl Vecs {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let all_activity = &distribution.utxo_cohorts.all.metrics.activity;
|
let all_metrics = &distribution.utxo_cohorts.all.metrics;
|
||||||
let supply_total_sats = &distribution
|
let all_activity = &all_metrics.activity;
|
||||||
.utxo_cohorts
|
let supply_total_sats = &all_metrics.supply.total.sats.height;
|
||||||
.all
|
|
||||||
.metrics
|
|
||||||
.supply
|
|
||||||
.total
|
|
||||||
.sats
|
|
||||||
.height;
|
|
||||||
|
|
||||||
// Supply-Adjusted CDD = sum_24h(CDD) / circulating_supply_btc
|
// Supply-Adjusted CDD = sum_24h(CDD) / circulating_supply_btc
|
||||||
self.coindays_destroyed_supply_adjusted
|
self.coindays_destroyed_supply_adjusted
|
||||||
@@ -110,7 +105,7 @@ impl Vecs {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Supply-Adjusted CYD = CYD / circulating_supply_btc (CYD = 1y rolling sum of CDD)
|
// Supply-Adjusted CYD = CYD / circulating_supply_btc
|
||||||
self.coinyears_destroyed_supply_adjusted
|
self.coinyears_destroyed_supply_adjusted
|
||||||
.height
|
.height
|
||||||
.compute_transform2(
|
.compute_transform2(
|
||||||
@@ -146,6 +141,66 @@ impl Vecs {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Stock-to-Flow: supply / annual_issuance
|
||||||
|
// annual_issuance ≈ subsidy_per_block × 52560 (blocks/year)
|
||||||
|
self.stock_to_flow.height.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
supply_total_sats,
|
||||||
|
&mining.rewards.subsidy.base.sats.height,
|
||||||
|
|(i, supply_sats, subsidy_sats, ..)| {
|
||||||
|
let annual_flow = subsidy_sats.as_u128() as f64 * 52560.0;
|
||||||
|
if annual_flow == 0.0 {
|
||||||
|
(i, StoredF32::from(0.0f32))
|
||||||
|
} else {
|
||||||
|
(i, StoredF32::from(
|
||||||
|
(supply_sats.as_u128() as f64 / annual_flow) as f32,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Dormancy Flow: supply_btc / dormancy
|
||||||
|
self.dormancy_flow.height.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
supply_total_sats,
|
||||||
|
&all_activity.dormancy.height,
|
||||||
|
|(i, supply_sats, dormancy, ..)| {
|
||||||
|
let d = f64::from(dormancy);
|
||||||
|
if d == 0.0 {
|
||||||
|
(i, StoredF32::from(0.0f32))
|
||||||
|
} else {
|
||||||
|
let supply = f64::from(Bitcoin::from(supply_sats));
|
||||||
|
(i, StoredF32::from((supply / d) as f32))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Seller Exhaustion Constant: % supply_in_profit × 30d_volatility
|
||||||
|
self.seller_exhaustion_constant
|
||||||
|
.height
|
||||||
|
.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
&all_metrics.supply.in_profit.sats.height,
|
||||||
|
&market.volatility._1m.height,
|
||||||
|
|(i, profit_sats, volatility, ..)| {
|
||||||
|
let total_sats: Sats = supply_total_sats
|
||||||
|
.collect_one(i)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let total = total_sats.as_u128() as f64;
|
||||||
|
if total == 0.0 {
|
||||||
|
(i, StoredF32::from(0.0f32))
|
||||||
|
} else {
|
||||||
|
let pct_in_profit = profit_sats.as_u128() as f64 / total;
|
||||||
|
(i, StoredF32::from(
|
||||||
|
(pct_in_profit * f64::from(volatility)) as f32,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
let _lock = exit.lock();
|
let _lock = exit.lock();
|
||||||
self.db.compact()?;
|
self.db.compact()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ impl Vecs {
|
|||||||
ComputedPerBlock::forced_import(&db, "coinyears_destroyed_supply_adjusted", v, indexes)?;
|
ComputedPerBlock::forced_import(&db, "coinyears_destroyed_supply_adjusted", v, indexes)?;
|
||||||
let dormancy_supply_adjusted =
|
let dormancy_supply_adjusted =
|
||||||
ComputedPerBlock::forced_import(&db, "dormancy_supply_adjusted", v, indexes)?;
|
ComputedPerBlock::forced_import(&db, "dormancy_supply_adjusted", v, indexes)?;
|
||||||
|
let stock_to_flow = ComputedPerBlock::forced_import(&db, "stock_to_flow", v, indexes)?;
|
||||||
|
let dormancy_flow = ComputedPerBlock::forced_import(&db, "dormancy_flow", v, indexes)?;
|
||||||
|
let seller_exhaustion_constant =
|
||||||
|
ComputedPerBlock::forced_import(&db, "seller_exhaustion_constant", v, indexes)?;
|
||||||
|
|
||||||
let this = Self {
|
let this = Self {
|
||||||
db,
|
db,
|
||||||
@@ -43,6 +47,9 @@ impl Vecs {
|
|||||||
coindays_destroyed_supply_adjusted,
|
coindays_destroyed_supply_adjusted,
|
||||||
coinyears_destroyed_supply_adjusted,
|
coinyears_destroyed_supply_adjusted,
|
||||||
dormancy_supply_adjusted,
|
dormancy_supply_adjusted,
|
||||||
|
stock_to_flow,
|
||||||
|
dormancy_flow,
|
||||||
|
seller_exhaustion_constant,
|
||||||
};
|
};
|
||||||
finalize_db(&this.db, &this)?;
|
finalize_db(&this.db, &this)?;
|
||||||
Ok(this)
|
Ok(this)
|
||||||
|
|||||||
@@ -16,4 +16,7 @@ pub struct Vecs<M: StorageMode = Rw> {
|
|||||||
pub coindays_destroyed_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
pub coindays_destroyed_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
||||||
pub coinyears_destroyed_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
pub coinyears_destroyed_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
||||||
pub dormancy_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
pub dormancy_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
||||||
|
pub stock_to_flow: ComputedPerBlock<StoredF32, M>,
|
||||||
|
pub dormancy_flow: ComputedPerBlock<StoredF32, M>,
|
||||||
|
pub seller_exhaustion_constant: ComputedPerBlock<StoredF32, M>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
use brk_error::Result;
|
|
||||||
use brk_traversable::Traversable;
|
|
||||||
use brk_types::{BasisPoints32, Cents, Height, Indexes, Version};
|
|
||||||
use derive_more::{Deref, DerefMut};
|
|
||||||
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
|
||||||
|
|
||||||
use crate::{blocks, indexes, prices};
|
|
||||||
|
|
||||||
use super::{RatioPerBlock, RatioPerBlockPercentiles};
|
|
||||||
|
|
||||||
#[derive(Deref, DerefMut, Traversable)]
|
|
||||||
pub struct RatioPerBlockExtended<M: StorageMode = Rw> {
|
|
||||||
#[deref]
|
|
||||||
#[deref_mut]
|
|
||||||
#[traversable(flatten)]
|
|
||||||
pub base: RatioPerBlock<BasisPoints32, M>,
|
|
||||||
#[traversable(flatten)]
|
|
||||||
pub percentiles: RatioPerBlockPercentiles<M>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RatioPerBlockExtended {
|
|
||||||
pub(crate) fn forced_import(
|
|
||||||
db: &Database,
|
|
||||||
name: &str,
|
|
||||||
version: Version,
|
|
||||||
indexes: &indexes::Vecs,
|
|
||||||
) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
base: RatioPerBlock::forced_import(db, name, version, indexes)?,
|
|
||||||
percentiles: RatioPerBlockPercentiles::forced_import(
|
|
||||||
db, name, version, indexes,
|
|
||||||
)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute ratio and all percentile metrics from an externally-provided metric price (in cents).
|
|
||||||
pub(crate) fn compute_rest(
|
|
||||||
&mut self,
|
|
||||||
blocks: &blocks::Vecs,
|
|
||||||
prices: &prices::Vecs,
|
|
||||||
starting_indexes: &Indexes,
|
|
||||||
exit: &Exit,
|
|
||||||
metric_price: &impl ReadableVec<Height, Cents>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let close_price = &prices.price.cents.height;
|
|
||||||
self.base
|
|
||||||
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
|
|
||||||
self.percentiles
|
|
||||||
.compute(blocks, starting_indexes, exit, &self.base.ratio.height, metric_price)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
mod base;
|
mod base;
|
||||||
mod extended;
|
|
||||||
mod percentiles;
|
mod percentiles;
|
||||||
mod price_extended;
|
mod price_extended;
|
||||||
|
mod sma;
|
||||||
mod std_dev_bands;
|
mod std_dev_bands;
|
||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
pub use base::*;
|
pub use base::*;
|
||||||
pub use extended::*;
|
|
||||||
pub use percentiles::*;
|
pub use percentiles::*;
|
||||||
pub use price_extended::*;
|
pub use price_extended::*;
|
||||||
|
pub use sma::*;
|
||||||
pub use std_dev_bands::*;
|
pub use std_dev_bands::*;
|
||||||
pub use windows::*;
|
pub use windows::*;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use vecdb::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks, indexes,
|
indexes,
|
||||||
internal::{ExpandingPercentiles, Price, PriceTimesRatioBp32Cents},
|
internal::{ExpandingPercentiles, Price, PriceTimesRatioBp32Cents},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,8 +22,6 @@ pub struct RatioBand<M: StorageMode = Rw> {
|
|||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RatioPerBlockPercentiles<M: StorageMode = Rw> {
|
pub struct RatioPerBlockPercentiles<M: StorageMode = Rw> {
|
||||||
pub sma_1w: RatioPerBlock<BasisPoints32, M>,
|
|
||||||
pub sma_1m: RatioPerBlock<BasisPoints32, M>,
|
|
||||||
pub pct99: RatioBand<M>,
|
pub pct99: RatioBand<M>,
|
||||||
pub pct98: RatioBand<M>,
|
pub pct98: RatioBand<M>,
|
||||||
pub pct95: RatioBand<M>,
|
pub pct95: RatioBand<M>,
|
||||||
@@ -73,8 +71,6 @@ impl RatioPerBlockPercentiles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
sma_1w: import_ratio!("ratio_sma_1w"),
|
|
||||||
sma_1m: import_ratio!("ratio_sma_1m"),
|
|
||||||
pct99: import_band!("ratio_pct99"),
|
pct99: import_band!("ratio_pct99"),
|
||||||
pct98: import_band!("ratio_pct98"),
|
pct98: import_band!("ratio_pct98"),
|
||||||
pct95: import_band!("ratio_pct95"),
|
pct95: import_band!("ratio_pct95"),
|
||||||
@@ -87,26 +83,11 @@ impl RatioPerBlockPercentiles {
|
|||||||
|
|
||||||
pub(crate) fn compute(
|
pub(crate) fn compute(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: &blocks::Vecs,
|
|
||||||
starting_indexes: &Indexes,
|
starting_indexes: &Indexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
||||||
metric_price: &impl ReadableVec<Height, Cents>,
|
metric_price: &impl ReadableVec<Height, Cents>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.sma_1w.bps.height.compute_rolling_average(
|
|
||||||
starting_indexes.height,
|
|
||||||
&blocks.lookback.height_1w_ago,
|
|
||||||
ratio_source,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.sma_1m.bps.height.compute_rolling_average(
|
|
||||||
starting_indexes.height,
|
|
||||||
&blocks.lookback.height_1m_ago,
|
|
||||||
ratio_source,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let ratio_version = ratio_source.version();
|
let ratio_version = ratio_source.version();
|
||||||
self.mut_pct_vecs().try_for_each(|v| -> Result<()> {
|
self.mut_pct_vecs().try_for_each(|v| -> Result<()> {
|
||||||
v.validate_computed_version_or_reset(ratio_version)?;
|
v.validate_computed_version_or_reset(ratio_version)?;
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{BasisPoints32, Cents, Height, Indexes, Version};
|
use brk_types::{BasisPoints32, Cents, Dollars, Height, Indexes, SatsFract, StoredF32, Version};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{ComputedPerBlock, Price};
|
use crate::internal::{ComputedPerBlock, LazyPerBlock, Price};
|
||||||
use crate::{indexes, prices};
|
use crate::{indexes, prices};
|
||||||
|
|
||||||
use super::RatioPerBlock;
|
use super::{RatioPerBlock, RatioPerBlockPercentiles};
|
||||||
|
|
||||||
#[derive(Deref, DerefMut, Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct PriceWithRatioPerBlock<M: StorageMode = Rw> {
|
pub struct PriceWithRatioPerBlock<M: StorageMode = Rw> {
|
||||||
#[deref]
|
pub cents: ComputedPerBlock<Cents, M>,
|
||||||
#[deref_mut]
|
pub usd: LazyPerBlock<Dollars, Cents>,
|
||||||
#[traversable(flatten)]
|
pub sats: LazyPerBlock<SatsFract, Dollars>,
|
||||||
pub inner: RatioPerBlock<BasisPoints32, M>,
|
pub bps: ComputedPerBlock<BasisPoints32, M>,
|
||||||
pub price: Price<ComputedPerBlock<Cents, M>>,
|
pub ratio: LazyPerBlock<StoredF32, BasisPoints32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceWithRatioPerBlock {
|
impl PriceWithRatioPerBlock {
|
||||||
@@ -25,13 +25,40 @@ impl PriceWithRatioPerBlock {
|
|||||||
version: Version,
|
version: Version,
|
||||||
indexes: &indexes::Vecs,
|
indexes: &indexes::Vecs,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let v = version + Version::TWO;
|
let price = Price::forced_import(db, name, version, indexes)?;
|
||||||
|
let ratio = RatioPerBlock::forced_import(db, name, version, indexes)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: RatioPerBlock::forced_import(db, name, version, indexes)?,
|
cents: price.cents,
|
||||||
price: Price::forced_import(db, name, v, indexes)?,
|
usd: price.usd,
|
||||||
|
sats: price.sats,
|
||||||
|
bps: ratio.bps,
|
||||||
|
ratio: ratio.ratio,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute ratio from close price and this metric's price.
|
||||||
|
pub(crate) fn compute_ratio(
|
||||||
|
&mut self,
|
||||||
|
starting_indexes: &Indexes,
|
||||||
|
close_price: &impl ReadableVec<Height, Cents>,
|
||||||
|
exit: &Exit,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.bps.height.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
close_price,
|
||||||
|
&self.cents.height,
|
||||||
|
|(i, close, price, ..)| {
|
||||||
|
if price == Cents::ZERO {
|
||||||
|
(i, BasisPoints32::from(1.0))
|
||||||
|
} else {
|
||||||
|
(i, BasisPoints32::from(f64::from(close) / f64::from(price)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute price via closure (in cents), then compute ratio.
|
/// Compute price via closure (in cents), then compute ratio.
|
||||||
pub(crate) fn compute_all<F>(
|
pub(crate) fn compute_all<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -43,10 +70,63 @@ impl PriceWithRatioPerBlock {
|
|||||||
where
|
where
|
||||||
F: FnMut(&mut EagerVec<PcoVec<Height, Cents>>) -> Result<()>,
|
F: FnMut(&mut EagerVec<PcoVec<Height, Cents>>) -> Result<()>,
|
||||||
{
|
{
|
||||||
compute_price(&mut self.price.cents.height)?;
|
compute_price(&mut self.cents.height)?;
|
||||||
let close_price = &prices.price.cents.height;
|
self.compute_ratio(starting_indexes, &prices.price.cents.height, exit)
|
||||||
self.inner
|
}
|
||||||
.compute_ratio(starting_indexes, close_price, &self.price.cents.height, exit)?;
|
}
|
||||||
Ok(())
|
|
||||||
|
#[derive(Deref, DerefMut, Traversable)]
|
||||||
|
pub struct PriceWithRatioExtendedPerBlock<M: StorageMode = Rw> {
|
||||||
|
#[deref]
|
||||||
|
#[deref_mut]
|
||||||
|
#[traversable(flatten)]
|
||||||
|
pub base: PriceWithRatioPerBlock<M>,
|
||||||
|
pub percentiles: RatioPerBlockPercentiles<M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PriceWithRatioExtendedPerBlock {
|
||||||
|
pub(crate) fn forced_import(
|
||||||
|
db: &Database,
|
||||||
|
name: &str,
|
||||||
|
version: Version,
|
||||||
|
indexes: &indexes::Vecs,
|
||||||
|
) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
base: PriceWithRatioPerBlock::forced_import(db, name, version, indexes)?,
|
||||||
|
percentiles: RatioPerBlockPercentiles::forced_import(db, name, version, indexes)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute ratio and percentiles from already-computed price cents.
|
||||||
|
pub(crate) fn compute_rest(
|
||||||
|
&mut self,
|
||||||
|
prices: &prices::Vecs,
|
||||||
|
starting_indexes: &Indexes,
|
||||||
|
exit: &Exit,
|
||||||
|
) -> Result<()> {
|
||||||
|
let close_price = &prices.price.cents.height;
|
||||||
|
self.base.compute_ratio(starting_indexes, close_price, exit)?;
|
||||||
|
self.percentiles.compute(
|
||||||
|
starting_indexes,
|
||||||
|
exit,
|
||||||
|
&self.base.ratio.height,
|
||||||
|
&self.base.cents.height,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute price via closure (in cents), then compute ratio and percentiles.
|
||||||
|
pub(crate) fn compute_all<F>(
|
||||||
|
&mut self,
|
||||||
|
prices: &prices::Vecs,
|
||||||
|
starting_indexes: &Indexes,
|
||||||
|
exit: &Exit,
|
||||||
|
mut compute_price: F,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut EagerVec<PcoVec<Height, Cents>>) -> Result<()>,
|
||||||
|
{
|
||||||
|
compute_price(&mut self.base.cents.height)?;
|
||||||
|
self.compute_rest(prices, starting_indexes, exit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
crates/brk_computer/src/internal/per_block/ratio/sma.rs
Normal file
86
crates/brk_computer/src/internal/per_block/ratio/sma.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use brk_error::Result;
|
||||||
|
use brk_traversable::Traversable;
|
||||||
|
use brk_types::{BasisPoints32, Height, Indexes, StoredF32, Version};
|
||||||
|
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
|
use crate::{blocks, indexes};
|
||||||
|
|
||||||
|
use super::RatioPerBlock;
|
||||||
|
|
||||||
|
#[derive(Traversable)]
|
||||||
|
pub struct RatioSma<M: StorageMode = Rw> {
|
||||||
|
pub all: RatioPerBlock<BasisPoints32, M>,
|
||||||
|
pub _1w: RatioPerBlock<BasisPoints32, M>,
|
||||||
|
pub _1m: RatioPerBlock<BasisPoints32, M>,
|
||||||
|
pub _1y: RatioPerBlock<BasisPoints32, M>,
|
||||||
|
pub _2y: RatioPerBlock<BasisPoints32, M>,
|
||||||
|
pub _4y: RatioPerBlock<BasisPoints32, M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERSION: Version = Version::new(4);
|
||||||
|
|
||||||
|
impl RatioSma {
|
||||||
|
pub(crate) fn forced_import(
|
||||||
|
db: &Database,
|
||||||
|
name: &str,
|
||||||
|
version: Version,
|
||||||
|
indexes: &indexes::Vecs,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let v = version + VERSION;
|
||||||
|
|
||||||
|
macro_rules! import {
|
||||||
|
($suffix:expr) => {
|
||||||
|
RatioPerBlock::forced_import_raw(
|
||||||
|
db,
|
||||||
|
&format!("{name}_ratio_sma_{}", $suffix),
|
||||||
|
v,
|
||||||
|
indexes,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
all: import!("all"),
|
||||||
|
_1w: import!("1w"),
|
||||||
|
_1m: import!("1m"),
|
||||||
|
_1y: import!("1y"),
|
||||||
|
_2y: import!("2y"),
|
||||||
|
_4y: import!("4y"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compute(
|
||||||
|
&mut self,
|
||||||
|
blocks: &blocks::Vecs,
|
||||||
|
starting_indexes: &Indexes,
|
||||||
|
exit: &Exit,
|
||||||
|
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Expanding SMA (all history)
|
||||||
|
self.all.bps.height.compute_sma_(
|
||||||
|
starting_indexes.height,
|
||||||
|
ratio_source,
|
||||||
|
usize::MAX,
|
||||||
|
exit,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Rolling SMAs
|
||||||
|
for (sma, lookback) in [
|
||||||
|
(&mut self._1w, &blocks.lookback.height_1w_ago),
|
||||||
|
(&mut self._1m, &blocks.lookback.height_1m_ago),
|
||||||
|
(&mut self._1y, &blocks.lookback.height_1y_ago),
|
||||||
|
(&mut self._2y, &blocks.lookback.height_2y_ago),
|
||||||
|
(&mut self._4y, &blocks.lookback.height_4y_ago),
|
||||||
|
] {
|
||||||
|
sma.bps.height.compute_rolling_average(
|
||||||
|
starting_indexes.height,
|
||||||
|
lookback,
|
||||||
|
ratio_source,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
|||||||
|
|
||||||
use crate::{blocks, indexes, internal::StdDevPerBlockExtended};
|
use crate::{blocks, indexes, internal::StdDevPerBlockExtended};
|
||||||
|
|
||||||
|
use super::RatioSma;
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct RatioPerBlockStdDevBands<M: StorageMode = Rw> {
|
pub struct RatioPerBlockStdDevBands<M: StorageMode = Rw> {
|
||||||
pub all: StdDevPerBlockExtended<M>,
|
pub all: StdDevPerBlockExtended<M>,
|
||||||
@@ -52,15 +54,16 @@ impl RatioPerBlockStdDevBands {
|
|||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
||||||
metric_price: &impl ReadableVec<Height, Cents>,
|
metric_price: &impl ReadableVec<Height, Cents>,
|
||||||
|
sma: &RatioSma,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for sd in [
|
for (sd, sma_ratio) in [
|
||||||
&mut self.all,
|
(&mut self.all, &sma.all.ratio.height),
|
||||||
&mut self._4y,
|
(&mut self._4y, &sma._4y.ratio.height),
|
||||||
&mut self._2y,
|
(&mut self._2y, &sma._2y.ratio.height),
|
||||||
&mut self._1y,
|
(&mut self._1y, &sma._1y.ratio.height),
|
||||||
] {
|
] {
|
||||||
sd.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
sd.compute_all(blocks, starting_indexes, exit, ratio_source, sma_ratio)?;
|
||||||
sd.compute_cents_bands(starting_indexes, metric_price, exit)?;
|
sd.compute_cents_bands(starting_indexes, metric_price, sma_ratio, exit)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -2,17 +2,15 @@ use brk_error::Result;
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{Cents, Height, Indexes, StoredF32, Version};
|
use brk_types::{Cents, Height, Indexes, StoredF32, Version};
|
||||||
use vecdb::{
|
use vecdb::{
|
||||||
AnyStoredVec, AnyVec, Database, EagerVec, Exit, Ident, PcoVec, ReadableCloneableVec,
|
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
|
||||||
ReadableVec, Rw, StorageMode, VecIndex, WritableVec,
|
WritableVec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks, indexes,
|
blocks, indexes,
|
||||||
internal::{ComputedPerBlock, LazyPerBlock, Price, PriceTimesRatioCents},
|
internal::{ComputedPerBlock, Price, PriceTimesRatioCents},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::StdDevPerBlock;
|
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct StdDevBand<M: StorageMode = Rw> {
|
pub struct StdDevBand<M: StorageMode = Rw> {
|
||||||
#[traversable(flatten)]
|
#[traversable(flatten)]
|
||||||
@@ -20,21 +18,13 @@ pub struct StdDevBand<M: StorageMode = Rw> {
|
|||||||
pub price: Price<ComputedPerBlock<Cents, M>>,
|
pub price: Price<ComputedPerBlock<Cents, M>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Traversable)]
|
|
||||||
pub struct LazyStdDevBand<M: StorageMode = Rw> {
|
|
||||||
#[traversable(flatten)]
|
|
||||||
pub value: LazyPerBlock<StoredF32>,
|
|
||||||
pub price: Price<ComputedPerBlock<Cents, M>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct StdDevPerBlockExtended<M: StorageMode = Rw> {
|
pub struct StdDevPerBlockExtended<M: StorageMode = Rw> {
|
||||||
#[traversable(flatten)]
|
days: usize,
|
||||||
pub base: StdDevPerBlock<M>,
|
pub sd: ComputedPerBlock<StoredF32, M>,
|
||||||
|
|
||||||
pub zscore: ComputedPerBlock<StoredF32, M>,
|
pub zscore: ComputedPerBlock<StoredF32, M>,
|
||||||
|
|
||||||
pub _0sd: LazyStdDevBand<M>,
|
pub _0sd: Price<ComputedPerBlock<Cents, M>>,
|
||||||
pub p0_5sd: StdDevBand<M>,
|
pub p0_5sd: StdDevBand<M>,
|
||||||
pub p1sd: StdDevBand<M>,
|
pub p1sd: StdDevBand<M>,
|
||||||
pub p1_5sd: StdDevBand<M>,
|
pub p1_5sd: StdDevBand<M>,
|
||||||
@@ -87,29 +77,11 @@ impl StdDevPerBlockExtended {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let base = StdDevPerBlock::forced_import(
|
|
||||||
db,
|
|
||||||
name,
|
|
||||||
period,
|
|
||||||
days,
|
|
||||||
parent_version,
|
|
||||||
indexes,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let _0sd = LazyStdDevBand {
|
|
||||||
value: LazyPerBlock::from_computed::<Ident>(
|
|
||||||
&format!("{name}_0sd{p}"),
|
|
||||||
version,
|
|
||||||
base.sma.height.read_only_boxed_clone(),
|
|
||||||
&base.sma,
|
|
||||||
),
|
|
||||||
price: import_price!("0sd"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
base,
|
days,
|
||||||
|
sd: import!("sd"),
|
||||||
zscore: import!("zscore"),
|
zscore: import!("zscore"),
|
||||||
_0sd,
|
_0sd: import_price!("0sd"),
|
||||||
p0_5sd: import_band!("p0_5sd"),
|
p0_5sd: import_band!("p0_5sd"),
|
||||||
p1sd: import_band!("p1sd"),
|
p1sd: import_band!("p1sd"),
|
||||||
p1_5sd: import_band!("p1_5sd"),
|
p1_5sd: import_band!("p1_5sd"),
|
||||||
@@ -131,19 +103,34 @@ impl StdDevPerBlockExtended {
|
|||||||
starting_indexes: &Indexes,
|
starting_indexes: &Indexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
source: &impl ReadableVec<Height, StoredF32>,
|
source: &impl ReadableVec<Height, StoredF32>,
|
||||||
|
sma: &impl ReadableVec<Height, StoredF32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.base
|
if self.days == usize::MAX {
|
||||||
.compute_all(blocks, starting_indexes, exit, source)?;
|
self.sd.height.compute_expanding_sd(
|
||||||
|
starting_indexes.height,
|
||||||
|
source,
|
||||||
|
sma,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
let window_starts = blocks.lookback.start_vec(self.days);
|
||||||
|
self.sd.height.compute_rolling_sd(
|
||||||
|
starting_indexes.height,
|
||||||
|
window_starts,
|
||||||
|
source,
|
||||||
|
sma,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
let sma_opt: Option<&EagerVec<PcoVec<Height, StoredF32>>> = None;
|
self.compute_bands(starting_indexes, exit, sma, source)
|
||||||
self.compute_bands(starting_indexes, exit, sma_opt, source)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_bands(
|
fn compute_bands(
|
||||||
&mut self,
|
&mut self,
|
||||||
starting_indexes: &Indexes,
|
starting_indexes: &Indexes,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
sma_opt: Option<&impl ReadableVec<Height, StoredF32>>,
|
sma: &impl ReadableVec<Height, StoredF32>,
|
||||||
source: &impl ReadableVec<Height, StoredF32>,
|
source: &impl ReadableVec<Height, StoredF32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let source_version = source.version();
|
let source_version = source.version();
|
||||||
@@ -166,19 +153,11 @@ impl StdDevPerBlockExtended {
|
|||||||
let source_len = source.len();
|
let source_len = source.len();
|
||||||
let source_data = source.collect_range_at(start, source_len);
|
let source_data = source.collect_range_at(start, source_len);
|
||||||
|
|
||||||
let sma_len = sma_opt
|
let sma_data = sma.collect_range_at(start, sma.len());
|
||||||
.map(|s| s.len())
|
|
||||||
.unwrap_or(self.base.sma.height.len());
|
|
||||||
let sma_data: Vec<StoredF32> = if let Some(sma) = sma_opt {
|
|
||||||
sma.collect_range_at(start, sma_len)
|
|
||||||
} else {
|
|
||||||
self.base.sma.height.collect_range_at(start, sma_len)
|
|
||||||
};
|
|
||||||
let sd_data = self
|
let sd_data = self
|
||||||
.base
|
|
||||||
.sd
|
.sd
|
||||||
.height
|
.height
|
||||||
.collect_range_at(start, self.base.sd.height.len());
|
.collect_range_at(start, self.sd.height.len());
|
||||||
|
|
||||||
const MULTIPLIERS: [f32; 12] = [
|
const MULTIPLIERS: [f32; 12] = [
|
||||||
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0,
|
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0,
|
||||||
@@ -197,23 +176,13 @@ impl StdDevPerBlockExtended {
|
|||||||
self.mut_band_height_vecs().try_for_each(|v| v.flush())?;
|
self.mut_band_height_vecs().try_for_each(|v| v.flush())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sma) = sma_opt {
|
self.zscore.height.compute_zscore(
|
||||||
self.zscore.height.compute_zscore(
|
starting_indexes.height,
|
||||||
starting_indexes.height,
|
source,
|
||||||
source,
|
sma,
|
||||||
sma,
|
&self.sd.height,
|
||||||
&self.base.sd.height,
|
exit,
|
||||||
exit,
|
)?;
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
self.zscore.height.compute_zscore(
|
|
||||||
starting_indexes.height,
|
|
||||||
source,
|
|
||||||
&self.base.sma.height,
|
|
||||||
&self.base.sd.height,
|
|
||||||
exit,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -222,6 +191,7 @@ impl StdDevPerBlockExtended {
|
|||||||
&mut self,
|
&mut self,
|
||||||
starting_indexes: &Indexes,
|
starting_indexes: &Indexes,
|
||||||
metric_price: &impl ReadableVec<Height, Cents>,
|
metric_price: &impl ReadableVec<Height, Cents>,
|
||||||
|
sma: &impl ReadableVec<Height, StoredF32>,
|
||||||
exit: &Exit,
|
exit: &Exit,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
macro_rules! compute_band_price {
|
macro_rules! compute_band_price {
|
||||||
@@ -237,7 +207,7 @@ impl StdDevPerBlockExtended {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_band_price!(&mut self._0sd.price, &self.base.sma.height);
|
compute_band_price!(&mut self._0sd, sma);
|
||||||
compute_band_price!(&mut self.p0_5sd.price, &self.p0_5sd.value.height);
|
compute_band_price!(&mut self.p0_5sd.price, &self.p0_5sd.value.height);
|
||||||
compute_band_price!(&mut self.p1sd.price, &self.p1sd.value.height);
|
compute_band_price!(&mut self.p1sd.price, &self.p1sd.value.height);
|
||||||
compute_band_price!(&mut self.p1_5sd.price, &self.p1_5sd.value.height);
|
compute_band_price!(&mut self.p1_5sd.price, &self.p1_5sd.value.height);
|
||||||
|
|||||||
@@ -429,18 +429,6 @@ impl Computer {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let indicators = scope.spawn(|| {
|
|
||||||
timed("Computed indicators", || {
|
|
||||||
self.indicators.compute(
|
|
||||||
&self.mining,
|
|
||||||
&self.distribution,
|
|
||||||
&self.transactions,
|
|
||||||
&starting_indexes,
|
|
||||||
exit,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
timed("Computed supply", || {
|
timed("Computed supply", || {
|
||||||
self.supply.compute(
|
self.supply.compute(
|
||||||
&self.scripts,
|
&self.scripts,
|
||||||
@@ -455,20 +443,37 @@ impl Computer {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
market.join().unwrap()?;
|
market.join().unwrap()?;
|
||||||
indicators.join().unwrap()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
timed("Computed cointime", || {
|
thread::scope(|scope| -> Result<()> {
|
||||||
self.cointime.compute(
|
let indicators = scope.spawn(|| {
|
||||||
&starting_indexes,
|
timed("Computed indicators", || {
|
||||||
&self.prices,
|
self.indicators.compute(
|
||||||
&self.blocks,
|
&self.mining,
|
||||||
&self.mining,
|
&self.distribution,
|
||||||
&self.supply,
|
&self.transactions,
|
||||||
&self.distribution,
|
&self.market,
|
||||||
exit,
|
&starting_indexes,
|
||||||
)
|
exit,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
timed("Computed cointime", || {
|
||||||
|
self.cointime.compute(
|
||||||
|
&starting_indexes,
|
||||||
|
&self.prices,
|
||||||
|
&self.blocks,
|
||||||
|
&self.mining,
|
||||||
|
&self.supply,
|
||||||
|
&self.distribution,
|
||||||
|
exit,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
indicators.join().unwrap()?;
|
||||||
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
info!("Total compute time: {:?}", compute_start.elapsed());
|
info!("Total compute time: {:?}", compute_start.elapsed());
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ impl Vecs {
|
|||||||
let sma_200d = import!("price_sma_200d");
|
let sma_200d = import!("price_sma_200d");
|
||||||
let sma_350d = import!("price_sma_350d");
|
let sma_350d = import!("price_sma_350d");
|
||||||
|
|
||||||
let price_sma_200d_source = &sma_200d.price.cents;
|
let price_sma_200d_source = &sma_200d.cents;
|
||||||
let _200d_x2_4 = Price::from_cents_source::<CentsTimesTenths<24>>(
|
let _200d_x2_4 = Price::from_cents_source::<CentsTimesTenths<24>>(
|
||||||
"price_sma_200d_x2_4",
|
"price_sma_200d_x2_4",
|
||||||
version,
|
version,
|
||||||
@@ -38,7 +38,7 @@ impl Vecs {
|
|||||||
price_sma_200d_source,
|
price_sma_200d_source,
|
||||||
);
|
);
|
||||||
|
|
||||||
let price_sma_350d_source = &sma_350d.price.cents;
|
let price_sma_350d_source = &sma_350d.cents;
|
||||||
let _350d_x2 = Price::from_cents_source::<CentsTimesTenths<20>>(
|
let _350d_x2 = Price::from_cents_source::<CentsTimesTenths<20>>(
|
||||||
"price_sma_350d_x2",
|
"price_sma_350d_x2",
|
||||||
version,
|
version,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ impl Vecs {
|
|||||||
.bps
|
.bps
|
||||||
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
|
||||||
starting_indexes.height,
|
starting_indexes.height,
|
||||||
&moving_average.sma._111d.price.usd.height,
|
&moving_average.sma._111d.usd.height,
|
||||||
&moving_average.sma._350d_x2.usd.height,
|
&moving_average.sma._350d_x2.usd.height,
|
||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct Vecs<M: StorageMode = Rw> {
|
|||||||
pub inflation_rate: PercentPerBlock<BasisPointsSigned32, M>,
|
pub inflation_rate: PercentPerBlock<BasisPointsSigned32, M>,
|
||||||
pub velocity: velocity::Vecs<M>,
|
pub velocity: velocity::Vecs<M>,
|
||||||
pub market_cap: LazyFiatPerBlock<Cents>,
|
pub market_cap: LazyFiatPerBlock<Cents>,
|
||||||
|
#[traversable(wrap = "market_cap", rename = "delta")]
|
||||||
pub market_cap_delta: FiatRollingDelta<Cents, CentsSigned, M>,
|
pub market_cap_delta: FiatRollingDelta<Cents, CentsSigned, M>,
|
||||||
pub market_minus_realized_cap_growth_rate: RollingWindows<BasisPointsSigned32, M>,
|
pub market_minus_realized_cap_growth_rate: RollingWindows<BasisPointsSigned32, M>,
|
||||||
pub hodled_or_lost_coins: LazyAmountPerBlock,
|
pub hodled_or_lost_coins: LazyAmountPerBlock,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ brk_traversable = { workspace = true }
|
|||||||
brk_types = { workspace = true }
|
brk_types = { workspace = true }
|
||||||
derive_more = { workspace = true }
|
derive_more = { workspace = true }
|
||||||
jiff = { workspace = true }
|
jiff = { workspace = true }
|
||||||
# quickmatch = { path = "../../../quickmatch" }
|
quickmatch = { path = "../../../quickmatch" }
|
||||||
quickmatch = "0.3.1"
|
# quickmatch = "0.3.1"
|
||||||
tokio = { workspace = true, optional = true }
|
tokio = { workspace = true, optional = true }
|
||||||
vecdb = { workspace = true }
|
vecdb = { workspace = true }
|
||||||
|
|||||||
34
crates/brk_query/examples/list.rs
Normal file
34
crates/brk_query/examples/list.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use std::{env, fs, path::Path};
|
||||||
|
|
||||||
|
use brk_computer::Computer;
|
||||||
|
use brk_indexer::Indexer;
|
||||||
|
use brk_query::Vecs;
|
||||||
|
use vecdb::ReadOnlyClone;
|
||||||
|
|
||||||
|
pub fn main() -> color_eyre::Result<()> {
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
let tmp = env::temp_dir().join("brk_search_gen");
|
||||||
|
fs::create_dir_all(&tmp)?;
|
||||||
|
|
||||||
|
let indexer = Indexer::forced_import(&tmp)?;
|
||||||
|
let computer = Computer::forced_import(&tmp, &indexer)?;
|
||||||
|
|
||||||
|
let indexer_ro = indexer.read_only_clone();
|
||||||
|
let computer_ro = computer.read_only_clone();
|
||||||
|
|
||||||
|
let vecs = Vecs::build(&indexer_ro, &computer_ro);
|
||||||
|
|
||||||
|
let out_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("metrics.txt");
|
||||||
|
let content = vecs.metrics.join("\n");
|
||||||
|
fs::write(&out_path, &content)?;
|
||||||
|
eprintln!(
|
||||||
|
"Wrote {} metrics to {}",
|
||||||
|
vecs.metrics.len(),
|
||||||
|
out_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
fs::remove_dir_all(&tmp)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -118,17 +118,8 @@ impl Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate total weight of the vecs for the given range.
|
/// Calculate total weight of the vecs for the given range.
|
||||||
/// Applies index-specific cost multipliers for rate limiting.
|
|
||||||
pub fn weight(vecs: &[&dyn AnyExportableVec], from: Option<i64>, to: Option<i64>) -> usize {
|
pub fn weight(vecs: &[&dyn AnyExportableVec], from: Option<i64>, to: Option<i64>) -> usize {
|
||||||
vecs.iter()
|
vecs.iter().map(|v| v.range_weight(from, to)).sum()
|
||||||
.map(|v| {
|
|
||||||
let base = v.range_weight(from, to);
|
|
||||||
let multiplier = Index::try_from(v.index_type_to_string())
|
|
||||||
.map(|i| i.cost_multiplier())
|
|
||||||
.unwrap_or(1);
|
|
||||||
base * multiplier
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve query metadata without formatting (cheap).
|
/// Resolve query metadata without formatting (cheap).
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ bindgen = ["dep:brk_bindgen"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
aide = { workspace = true }
|
aide = { workspace = true }
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
|
brotli = "8"
|
||||||
|
flate2 = "1"
|
||||||
brk_bindgen = { workspace = true, optional = true }
|
brk_bindgen = { workspace = true, optional = true }
|
||||||
brk_computer = { workspace = true }
|
brk_computer = { workspace = true }
|
||||||
brk_error = { workspace = true, features = ["jiff", "serde_json", "tokio", "vecdb"] }
|
brk_error = { workspace = true, features = ["jiff", "serde_json", "tokio", "vecdb"] }
|
||||||
@@ -27,6 +29,7 @@ brk_traversable = { workspace = true }
|
|||||||
brk_website = { workspace = true }
|
brk_website = { workspace = true }
|
||||||
derive_more = { workspace = true }
|
derive_more = { workspace = true }
|
||||||
vecdb = { workspace = true }
|
vecdb = { workspace = true }
|
||||||
|
zstd = "0.13"
|
||||||
jiff = { workspace = true }
|
jiff = { workspace = true }
|
||||||
quick_cache = "0.6.18"
|
quick_cache = "0.6.18"
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use brk_types::{Format, MetricSelection, Output};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
Result,
|
||||||
api::metrics::{CACHE_CONTROL, max_weight},
|
api::metrics::{CACHE_CONTROL, max_weight},
|
||||||
extended::HeaderMapExtended,
|
extended::{ContentEncoding, HeaderMapExtended},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::AppState;
|
use super::AppState;
|
||||||
@@ -38,15 +38,27 @@ pub async fn handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: Format (expensive, server-side cached)
|
// Phase 2: Format (expensive, server-side cached)
|
||||||
let cache_key = format!("bulk-{}{}{}", uri.path(), uri.query().unwrap_or(""), etag);
|
let encoding = ContentEncoding::negotiate(&headers);
|
||||||
|
let cache_key = format!(
|
||||||
|
"bulk-{}{}{}-{}",
|
||||||
|
uri.path(),
|
||||||
|
uri.query().unwrap_or(""),
|
||||||
|
etag,
|
||||||
|
encoding.as_str()
|
||||||
|
);
|
||||||
let query = &state;
|
let query = &state;
|
||||||
let bytes = state
|
let bytes = state
|
||||||
.get_or_insert(&cache_key, async move {
|
.get_or_insert(&cache_key, async move {
|
||||||
let out = query.run(move |q| q.format(resolved)).await?;
|
query
|
||||||
Ok(match out.output {
|
.run(move |q| {
|
||||||
Output::CSV(s) => Bytes::from(s),
|
let out = q.format(resolved)?;
|
||||||
Output::Json(v) => Bytes::from(v),
|
let raw = match out.output {
|
||||||
})
|
Output::CSV(s) => Bytes::from(s),
|
||||||
|
Output::Json(v) => Bytes::from(v),
|
||||||
|
};
|
||||||
|
Ok(encoding.compress(raw))
|
||||||
|
})
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -54,6 +66,7 @@ pub async fn handler(
|
|||||||
let h = response.headers_mut();
|
let h = response.headers_mut();
|
||||||
h.insert_etag(etag.as_str());
|
h.insert_etag(etag.as_str());
|
||||||
h.insert_cache_control(CACHE_CONTROL);
|
h.insert_cache_control(CACHE_CONTROL);
|
||||||
|
h.insert_content_encoding(encoding);
|
||||||
match format {
|
match format {
|
||||||
Format::CSV => {
|
Format::CSV => {
|
||||||
h.insert_content_disposition_attachment(&csv_filename);
|
h.insert_content_disposition_attachment(&csv_filename);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use brk_types::{Format, MetricSelection, Output};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
Result,
|
||||||
api::metrics::{CACHE_CONTROL, max_weight},
|
api::metrics::{CACHE_CONTROL, max_weight},
|
||||||
extended::HeaderMapExtended,
|
extended::{ContentEncoding, HeaderMapExtended},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::AppState;
|
use super::AppState;
|
||||||
@@ -38,15 +38,27 @@ pub async fn handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: Format (expensive, server-side cached)
|
// Phase 2: Format (expensive, server-side cached)
|
||||||
let cache_key = format!("single-{}{}{}", uri.path(), uri.query().unwrap_or(""), etag);
|
let encoding = ContentEncoding::negotiate(&headers);
|
||||||
|
let cache_key = format!(
|
||||||
|
"single-{}{}{}-{}",
|
||||||
|
uri.path(),
|
||||||
|
uri.query().unwrap_or(""),
|
||||||
|
etag,
|
||||||
|
encoding.as_str()
|
||||||
|
);
|
||||||
let query = &state;
|
let query = &state;
|
||||||
let bytes = state
|
let bytes = state
|
||||||
.get_or_insert(&cache_key, async move {
|
.get_or_insert(&cache_key, async move {
|
||||||
let out = query.run(move |q| q.format(resolved)).await?;
|
query
|
||||||
Ok(match out.output {
|
.run(move |q| {
|
||||||
Output::CSV(s) => Bytes::from(s),
|
let out = q.format(resolved)?;
|
||||||
Output::Json(v) => Bytes::from(v),
|
let raw = match out.output {
|
||||||
})
|
Output::CSV(s) => Bytes::from(s),
|
||||||
|
Output::Json(v) => Bytes::from(v),
|
||||||
|
};
|
||||||
|
Ok(encoding.compress(raw))
|
||||||
|
})
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -54,6 +66,7 @@ pub async fn handler(
|
|||||||
let h = response.headers_mut();
|
let h = response.headers_mut();
|
||||||
h.insert_etag(etag.as_str());
|
h.insert_etag(etag.as_str());
|
||||||
h.insert_cache_control(CACHE_CONTROL);
|
h.insert_cache_control(CACHE_CONTROL);
|
||||||
|
h.insert_content_encoding(encoding);
|
||||||
match format {
|
match format {
|
||||||
Format::CSV => {
|
Format::CSV => {
|
||||||
h.insert_content_disposition_attachment(&csv_filename);
|
h.insert_content_disposition_attachment(&csv_filename);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use brk_types::{Format, MetricSelection, OutputLegacy};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
Result,
|
||||||
api::metrics::{CACHE_CONTROL, max_weight},
|
api::metrics::{CACHE_CONTROL, max_weight},
|
||||||
extended::HeaderMapExtended,
|
extended::{ContentEncoding, HeaderMapExtended},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUNSET: &str = "2027-01-01T00:00:00Z";
|
const SUNSET: &str = "2027-01-01T00:00:00Z";
|
||||||
@@ -40,15 +40,27 @@ pub async fn handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: Format (expensive, server-side cached)
|
// Phase 2: Format (expensive, server-side cached)
|
||||||
let cache_key = format!("legacy-{}{}{}", uri.path(), uri.query().unwrap_or(""), etag);
|
let encoding = ContentEncoding::negotiate(&headers);
|
||||||
|
let cache_key = format!(
|
||||||
|
"legacy-{}{}{}-{}",
|
||||||
|
uri.path(),
|
||||||
|
uri.query().unwrap_or(""),
|
||||||
|
etag,
|
||||||
|
encoding.as_str()
|
||||||
|
);
|
||||||
let query = &state;
|
let query = &state;
|
||||||
let bytes = state
|
let bytes = state
|
||||||
.get_or_insert(&cache_key, async move {
|
.get_or_insert(&cache_key, async move {
|
||||||
let out = query.run(move |q| q.format_legacy(resolved)).await?;
|
query
|
||||||
Ok(match out.output {
|
.run(move |q| {
|
||||||
OutputLegacy::CSV(s) => Bytes::from(s),
|
let out = q.format_legacy(resolved)?;
|
||||||
OutputLegacy::Json(v) => Bytes::from(v.to_vec()),
|
let raw = match out.output {
|
||||||
})
|
OutputLegacy::CSV(s) => Bytes::from(s),
|
||||||
|
OutputLegacy::Json(v) => Bytes::from(v.to_vec()),
|
||||||
|
};
|
||||||
|
Ok(encoding.compress(raw))
|
||||||
|
})
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -56,6 +68,7 @@ pub async fn handler(
|
|||||||
let h = response.headers_mut();
|
let h = response.headers_mut();
|
||||||
h.insert_etag(etag.as_str());
|
h.insert_etag(etag.as_str());
|
||||||
h.insert_cache_control(CACHE_CONTROL);
|
h.insert_cache_control(CACHE_CONTROL);
|
||||||
|
h.insert_content_encoding(encoding);
|
||||||
match format {
|
match format {
|
||||||
Format::CSV => {
|
Format::CSV => {
|
||||||
h.insert_content_disposition_attachment(&csv_filename);
|
h.insert_content_disposition_attachment(&csv_filename);
|
||||||
|
|||||||
88
crates/brk_server/src/extended/encoding.rs
Normal file
88
crates/brk_server/src/extended/encoding.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use axum::{
|
||||||
|
body::Bytes,
|
||||||
|
http::{header, HeaderMap, HeaderValue},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// HTTP content encoding for pre-compressed caching.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ContentEncoding {
|
||||||
|
Brotli,
|
||||||
|
Gzip,
|
||||||
|
Zstd,
|
||||||
|
Identity,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentEncoding {
|
||||||
|
/// Negotiate the best encoding from the Accept-Encoding header.
|
||||||
|
/// Priority: br > zstd > gzip > identity.
|
||||||
|
pub fn negotiate(headers: &HeaderMap) -> Self {
|
||||||
|
let accept = match headers.get(header::ACCEPT_ENCODING) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Self::Identity,
|
||||||
|
};
|
||||||
|
let s = match accept.to_str() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return Self::Identity,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut best = Self::Identity;
|
||||||
|
for part in s.split(',') {
|
||||||
|
let name = part.split(';').next().unwrap_or("").trim();
|
||||||
|
match name {
|
||||||
|
"br" => return Self::Brotli,
|
||||||
|
"zstd" => best = Self::Zstd,
|
||||||
|
"gzip" if matches!(best, Self::Identity) => best = Self::Gzip,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress bytes with this encoding. Identity returns bytes unchanged.
|
||||||
|
pub fn compress(self, bytes: Bytes) -> Bytes {
|
||||||
|
match self {
|
||||||
|
Self::Identity => bytes,
|
||||||
|
Self::Brotli => {
|
||||||
|
use std::io::Write;
|
||||||
|
let mut output = Vec::with_capacity(bytes.len() / 2);
|
||||||
|
{
|
||||||
|
let mut writer = brotli::CompressorWriter::new(&mut output, 4096, 4, 22);
|
||||||
|
writer.write_all(&bytes).expect("brotli compression failed");
|
||||||
|
}
|
||||||
|
Bytes::from(output)
|
||||||
|
}
|
||||||
|
Self::Gzip => {
|
||||||
|
use flate2::write::GzEncoder;
|
||||||
|
use std::io::Write;
|
||||||
|
let mut encoder = GzEncoder::new(
|
||||||
|
Vec::with_capacity(bytes.len() / 2),
|
||||||
|
flate2::Compression::new(3),
|
||||||
|
);
|
||||||
|
encoder.write_all(&bytes).expect("gzip compression failed");
|
||||||
|
Bytes::from(encoder.finish().expect("gzip finish failed"))
|
||||||
|
}
|
||||||
|
Self::Zstd => Bytes::from(
|
||||||
|
zstd::encode_all(bytes.as_ref(), 3).expect("zstd compression failed"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wire name used for Content-Encoding header and cache key suffix.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Brotli => "br",
|
||||||
|
Self::Gzip => "gzip",
|
||||||
|
Self::Zstd => "zstd",
|
||||||
|
Self::Identity => "identity",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn header_value(self) -> Option<HeaderValue> {
|
||||||
|
match self {
|
||||||
|
Self::Identity => None,
|
||||||
|
_ => Some(HeaderValue::from_static(self.as_str())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ use axum::http::{
|
|||||||
header::{self, IF_NONE_MATCH},
|
header::{self, IF_NONE_MATCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::ContentEncoding;
|
||||||
|
|
||||||
pub trait HeaderMapExtended {
|
pub trait HeaderMapExtended {
|
||||||
fn has_etag(&self, etag: &str) -> bool;
|
fn has_etag(&self, etag: &str) -> bool;
|
||||||
fn insert_etag(&mut self, etag: &str);
|
fn insert_etag(&mut self, etag: &str);
|
||||||
@@ -12,10 +14,10 @@ pub trait HeaderMapExtended {
|
|||||||
|
|
||||||
fn insert_content_disposition_attachment(&mut self, filename: &str);
|
fn insert_content_disposition_attachment(&mut self, filename: &str);
|
||||||
|
|
||||||
|
fn insert_content_encoding(&mut self, encoding: ContentEncoding);
|
||||||
|
|
||||||
fn insert_content_type_application_json(&mut self);
|
fn insert_content_type_application_json(&mut self);
|
||||||
fn insert_content_type_text_csv(&mut self);
|
fn insert_content_type_text_csv(&mut self);
|
||||||
fn insert_content_type_text_plain(&mut self);
|
|
||||||
fn insert_content_type_octet_stream(&mut self);
|
|
||||||
|
|
||||||
fn insert_deprecation(&mut self, sunset: &'static str);
|
fn insert_deprecation(&mut self, sunset: &'static str);
|
||||||
}
|
}
|
||||||
@@ -45,6 +47,12 @@ impl HeaderMapExtended for HeaderMap {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn insert_content_encoding(&mut self, encoding: ContentEncoding) {
|
||||||
|
if let Some(value) = encoding.header_value() {
|
||||||
|
self.insert(header::CONTENT_ENCODING, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn insert_content_type_application_json(&mut self) {
|
fn insert_content_type_application_json(&mut self) {
|
||||||
self.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
|
self.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
|
||||||
}
|
}
|
||||||
@@ -53,17 +61,6 @@ impl HeaderMapExtended for HeaderMap {
|
|||||||
self.insert(header::CONTENT_TYPE, "text/csv".parse().unwrap());
|
self.insert(header::CONTENT_TYPE, "text/csv".parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_content_type_text_plain(&mut self) {
|
|
||||||
self.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_content_type_octet_stream(&mut self) {
|
|
||||||
self.insert(
|
|
||||||
header::CONTENT_TYPE,
|
|
||||||
"application/octet-stream".parse().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_deprecation(&mut self, sunset: &'static str) {
|
fn insert_deprecation(&mut self, sunset: &'static str) {
|
||||||
self.insert("Deprecation", "true".parse().unwrap());
|
self.insert("Deprecation", "true".parse().unwrap());
|
||||||
self.insert("Sunset", sunset.parse().unwrap());
|
self.insert("Sunset", sunset.parse().unwrap());
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
mod encoding;
|
||||||
mod header_map;
|
mod header_map;
|
||||||
mod response;
|
mod response;
|
||||||
mod result;
|
|
||||||
mod transform_operation;
|
mod transform_operation;
|
||||||
|
|
||||||
|
pub use encoding::*;
|
||||||
pub use header_map::*;
|
pub use header_map::*;
|
||||||
pub use response::*;
|
pub use response::*;
|
||||||
pub use result::*;
|
|
||||||
pub use transform_operation::*;
|
pub use transform_operation::*;
|
||||||
|
|||||||
@@ -13,22 +13,12 @@ where
|
|||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
fn new_not_modified() -> Self;
|
fn new_not_modified() -> Self;
|
||||||
fn new_json<T>(value: T, etag: &str) -> Self
|
|
||||||
where
|
|
||||||
T: Serialize;
|
|
||||||
fn new_json_with<T>(status: StatusCode, value: T, etag: &str) -> Self
|
|
||||||
where
|
|
||||||
T: Serialize;
|
|
||||||
fn new_json_cached<T>(value: T, params: &CacheParams) -> Self
|
fn new_json_cached<T>(value: T, params: &CacheParams) -> Self
|
||||||
where
|
where
|
||||||
T: Serialize;
|
T: Serialize;
|
||||||
fn static_json<T>(headers: &HeaderMap, value: T) -> Self
|
fn static_json<T>(headers: &HeaderMap, value: T) -> Self
|
||||||
where
|
where
|
||||||
T: Serialize;
|
T: Serialize;
|
||||||
fn new_text(value: &str, etag: &str) -> Self;
|
|
||||||
fn new_text_with(status: StatusCode, value: &str, etag: &str) -> Self;
|
|
||||||
fn new_bytes(value: Vec<u8>, etag: &str) -> Self;
|
|
||||||
fn new_bytes_with(status: StatusCode, value: Vec<u8>, etag: &str) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseExtended for Response<Body> {
|
impl ResponseExtended for Response<Body> {
|
||||||
@@ -38,55 +28,6 @@ impl ResponseExtended for Response<Body> {
|
|||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_json<T>(value: T, etag: &str) -> Self
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
Self::new_json_with(StatusCode::default(), value, etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_json_with<T>(status: StatusCode, value: T, etag: &str) -> Self
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
let bytes = serde_json::to_vec(&value).unwrap();
|
|
||||||
let mut response = Response::builder().body(bytes.into()).unwrap();
|
|
||||||
*response.status_mut() = status;
|
|
||||||
let headers = response.headers_mut();
|
|
||||||
headers.insert_content_type_application_json();
|
|
||||||
headers.insert_cache_control_must_revalidate();
|
|
||||||
headers.insert_etag(etag);
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_text(value: &str, etag: &str) -> Self {
|
|
||||||
Self::new_text_with(StatusCode::default(), value, etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_text_with(status: StatusCode, value: &str, etag: &str) -> Self {
|
|
||||||
let mut response = Response::builder().body(value.to_string().into()).unwrap();
|
|
||||||
*response.status_mut() = status;
|
|
||||||
let headers = response.headers_mut();
|
|
||||||
headers.insert_content_type_text_plain();
|
|
||||||
headers.insert_cache_control_must_revalidate();
|
|
||||||
headers.insert_etag(etag);
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_bytes(value: Vec<u8>, etag: &str) -> Self {
|
|
||||||
Self::new_bytes_with(StatusCode::default(), value, etag)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_bytes_with(status: StatusCode, value: Vec<u8>, etag: &str) -> Self {
|
|
||||||
let mut response = Response::builder().body(value.into()).unwrap();
|
|
||||||
*response.status_mut() = status;
|
|
||||||
let headers = response.headers_mut();
|
|
||||||
headers.insert_content_type_octet_stream();
|
|
||||||
headers.insert_cache_control_must_revalidate();
|
|
||||||
headers.insert_etag(etag);
|
|
||||||
response
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_json_cached<T>(value: T, params: &CacheParams) -> Self
|
fn new_json_cached<T>(value: T, params: &CacheParams) -> Self
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
use axum::response::Response;
|
|
||||||
use brk_error::Result;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::{Error, extended::ResponseExtended};
|
|
||||||
|
|
||||||
pub trait ResultExtended<T> {
|
|
||||||
fn to_json_response(self, etag: &str) -> Response
|
|
||||||
where
|
|
||||||
T: Serialize;
|
|
||||||
fn to_text_response(self, etag: &str) -> Response
|
|
||||||
where
|
|
||||||
T: AsRef<str>;
|
|
||||||
fn to_bytes_response(self, etag: &str) -> Response
|
|
||||||
where
|
|
||||||
T: Into<Vec<u8>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ResultExtended<T> for Result<T> {
|
|
||||||
fn to_json_response(self, etag: &str) -> Response
|
|
||||||
where
|
|
||||||
T: Serialize,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Ok(value) => Response::new_json(&value, etag),
|
|
||||||
Err(e) => Error::from(e).into_response_with_etag(etag),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_text_response(self, etag: &str) -> Response
|
|
||||||
where
|
|
||||||
T: AsRef<str>,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Ok(value) => Response::new_text(value.as_ref(), etag),
|
|
||||||
Err(e) => Error::from(e).into_response_with_etag(etag),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_bytes_response(self, etag: &str) -> Response
|
|
||||||
where
|
|
||||||
T: Into<Vec<u8>>,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Ok(value) => Response::new_bytes(value.into(), etag),
|
|
||||||
Err(e) => Error::from(e).into_response_with_etag(etag),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ use derive_more::Deref;
|
|||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::{Body, Bytes},
|
body::{Body, Bytes},
|
||||||
http::{HeaderMap, Response, Uri},
|
http::{HeaderMap, HeaderValue, Response, Uri, header},
|
||||||
};
|
};
|
||||||
use brk_query::AsyncQuery;
|
use brk_query::AsyncQuery;
|
||||||
use brk_rpc::Client;
|
use brk_rpc::Client;
|
||||||
@@ -18,8 +18,8 @@ use quick_cache::sync::{Cache, GuardResult};
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
CacheParams, CacheStrategy, Website,
|
CacheParams, CacheStrategy, Error, Website,
|
||||||
extended::{HeaderMapExtended, ResponseExtended, ResultExtended},
|
extended::{ContentEncoding, HeaderMapExtended, ResponseExtended},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Deref)]
|
#[derive(Clone, Deref)]
|
||||||
@@ -40,6 +40,47 @@ impl AppState {
|
|||||||
CacheStrategy::MempoolHash(hash)
|
CacheStrategy::MempoolHash(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cached + pre-compressed response. Compression runs on the blocking thread.
|
||||||
|
async fn cached<F>(
|
||||||
|
&self,
|
||||||
|
headers: &HeaderMap,
|
||||||
|
strategy: CacheStrategy,
|
||||||
|
uri: &Uri,
|
||||||
|
content_type: &'static str,
|
||||||
|
f: F,
|
||||||
|
) -> Response<Body>
|
||||||
|
where
|
||||||
|
F: FnOnce(&brk_query::Query, ContentEncoding) -> brk_error::Result<Bytes> + Send + 'static,
|
||||||
|
{
|
||||||
|
let encoding = ContentEncoding::negotiate(headers);
|
||||||
|
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
|
||||||
|
if params.matches_etag(headers) {
|
||||||
|
return ResponseExtended::new_not_modified();
|
||||||
|
}
|
||||||
|
|
||||||
|
let full_key = format!("{}-{}-{}", uri, params.etag_str(), encoding.as_str());
|
||||||
|
let result = self
|
||||||
|
.get_or_insert(&full_key, async move {
|
||||||
|
self.run(move |q| f(q, encoding)).await
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(bytes) => {
|
||||||
|
let mut response = Response::new(Body::from(bytes));
|
||||||
|
let h = response.headers_mut();
|
||||||
|
h.insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
|
||||||
|
h.insert_cache_control(¶ms.cache_control);
|
||||||
|
h.insert_content_encoding(encoding);
|
||||||
|
if let Some(etag) = ¶ms.etag {
|
||||||
|
h.insert_etag(etag);
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
||||||
|
Err(e) => Error::from(e).into_response_with_etag(params.etag_str()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// JSON response with HTTP + server-side caching
|
/// JSON response with HTTP + server-side caching
|
||||||
pub async fn cached_json<T, F>(
|
pub async fn cached_json<T, F>(
|
||||||
&self,
|
&self,
|
||||||
@@ -52,32 +93,11 @@ impl AppState {
|
|||||||
T: Serialize + Send + 'static,
|
T: Serialize + Send + 'static,
|
||||||
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
||||||
{
|
{
|
||||||
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
|
self.cached(headers, strategy, uri, "application/json", move |q, enc| {
|
||||||
if params.matches_etag(headers) {
|
let value = f(q)?;
|
||||||
return ResponseExtended::new_not_modified();
|
Ok(enc.compress(Bytes::from(serde_json::to_vec(&value).unwrap())))
|
||||||
}
|
})
|
||||||
|
.await
|
||||||
let full_key = format!("{}-{}", uri, params.etag_str());
|
|
||||||
let result = self
|
|
||||||
.get_or_insert(&full_key, async move {
|
|
||||||
let value = self.run(f).await?;
|
|
||||||
Ok(serde_json::to_vec(&value).unwrap().into())
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(bytes) => {
|
|
||||||
let mut response = Response::new(Body::from(bytes));
|
|
||||||
let h = response.headers_mut();
|
|
||||||
h.insert_content_type_application_json();
|
|
||||||
h.insert_cache_control(¶ms.cache_control);
|
|
||||||
if let Some(etag) = ¶ms.etag {
|
|
||||||
h.insert_etag(etag);
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
Err(e) => ResultExtended::<T>::to_json_response(Err(e), params.etag_str()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text response with HTTP + server-side caching
|
/// Text response with HTTP + server-side caching
|
||||||
@@ -92,32 +112,11 @@ impl AppState {
|
|||||||
T: AsRef<str> + Send + 'static,
|
T: AsRef<str> + Send + 'static,
|
||||||
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
||||||
{
|
{
|
||||||
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
|
self.cached(headers, strategy, uri, "text/plain", move |q, enc| {
|
||||||
if params.matches_etag(headers) {
|
let value = f(q)?;
|
||||||
return ResponseExtended::new_not_modified();
|
Ok(enc.compress(Bytes::from(value.as_ref().as_bytes().to_vec())))
|
||||||
}
|
})
|
||||||
|
.await
|
||||||
let full_key = format!("{}-{}", uri, params.etag_str());
|
|
||||||
let result = self
|
|
||||||
.get_or_insert(&full_key, async move {
|
|
||||||
let value = self.run(f).await?;
|
|
||||||
Ok(Bytes::from(value.as_ref().to_owned()))
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(bytes) => {
|
|
||||||
let mut response = Response::new(Body::from(bytes));
|
|
||||||
let h = response.headers_mut();
|
|
||||||
h.insert_content_type_text_plain();
|
|
||||||
h.insert_cache_control(¶ms.cache_control);
|
|
||||||
if let Some(etag) = ¶ms.etag {
|
|
||||||
h.insert_etag(etag);
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
Err(e) => ResultExtended::<T>::to_text_response(Err(e), params.etag_str()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Binary response with HTTP + server-side caching
|
/// Binary response with HTTP + server-side caching
|
||||||
@@ -132,32 +131,17 @@ impl AppState {
|
|||||||
T: Into<Vec<u8>> + Send + 'static,
|
T: Into<Vec<u8>> + Send + 'static,
|
||||||
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
||||||
{
|
{
|
||||||
let params = CacheParams::resolve(&strategy, || self.sync(|q| q.height().into()));
|
self.cached(
|
||||||
if params.matches_etag(headers) {
|
headers,
|
||||||
return ResponseExtended::new_not_modified();
|
strategy,
|
||||||
}
|
uri,
|
||||||
|
"application/octet-stream",
|
||||||
let full_key = format!("{}-{}", uri, params.etag_str());
|
move |q, enc| {
|
||||||
let result = self
|
let value = f(q)?;
|
||||||
.get_or_insert(&full_key, async move {
|
Ok(enc.compress(Bytes::from(value.into())))
|
||||||
let value = self.run(f).await?;
|
},
|
||||||
Ok(Bytes::from(value.into()))
|
)
|
||||||
})
|
.await
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(bytes) => {
|
|
||||||
let mut response = Response::new(Body::from(bytes));
|
|
||||||
let h = response.headers_mut();
|
|
||||||
h.insert_content_type_octet_stream();
|
|
||||||
h.insert_cache_control(¶ms.cache_control);
|
|
||||||
if let Some(etag) = ¶ms.etag {
|
|
||||||
h.insert_etag(etag);
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
Err(e) => ResultExtended::<T>::to_bytes_response(Err(e), params.etag_str()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check server-side cache, compute on miss
|
/// Check server-side cache, compute on miss
|
||||||
|
|||||||
@@ -90,6 +90,27 @@ impl From<BasisPoints32> for f64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<f32> for BasisPoints32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: f32) -> Self {
|
||||||
|
Self::from(value as f64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StoredF32> for BasisPoints32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: StoredF32) -> Self {
|
||||||
|
Self::from(f64::from(*value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BasisPoints32> for f32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: BasisPoints32) -> Self {
|
||||||
|
value.0 as f32 / 10000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<BasisPoints32> for StoredF32 {
|
impl From<BasisPoints32> for StoredF32 {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value: BasisPoints32) -> Self {
|
fn from(value: BasisPoints32) -> Self {
|
||||||
|
|||||||
@@ -184,14 +184,6 @@ impl Index {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the query cost multiplier for this index type.
|
|
||||||
/// Used for rate limiting to account for expensive lazy computations.
|
|
||||||
pub const fn cost_multiplier(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Self::Epoch => 60,
|
|
||||||
_ => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this index type is date-based.
|
/// Returns true if this index type is date-based.
|
||||||
pub const fn is_date_based(&self) -> bool {
|
pub const fn is_date_based(&self) -> bool {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user