mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -26,7 +26,7 @@ owo-colors = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
toml = "1.0.7"
|
||||
toml = "1.1.0"
|
||||
vecdb = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -1247,7 +1247,7 @@ impl GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 {
|
||||
pub struct BpsCentsPercentilesRatioSatsSmaStdUsdPattern {
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub cents: SeriesPattern1<Cents>,
|
||||
pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub sma: _1m1w1y2y4yAllPattern,
|
||||
@@ -1255,6 +1255,34 @@ pub struct BpsCentsPercentilesRatioSatsSmaStdUsdPattern {
|
||||
pub usd: SeriesPattern1<Dollars>,
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern {
|
||||
pub pct0_5: BpsPriceRatioPattern,
|
||||
pub pct1: BpsPriceRatioPattern,
|
||||
pub pct2: BpsPriceRatioPattern,
|
||||
pub pct5: BpsPriceRatioPattern,
|
||||
pub pct95: BpsPriceRatioPattern,
|
||||
pub pct98: BpsPriceRatioPattern,
|
||||
pub pct99: BpsPriceRatioPattern,
|
||||
pub pct99_5: BpsPriceRatioPattern,
|
||||
}
|
||||
|
||||
impl Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern {
|
||||
/// Create a new pattern node with accumulated series name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
pct0_5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct0_5".to_string()),
|
||||
pct1: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct1".to_string()),
|
||||
pct2: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct2".to_string()),
|
||||
pct5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct5".to_string()),
|
||||
pct95: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct95".to_string()),
|
||||
pct98: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct98".to_string()),
|
||||
pct99: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99".to_string()),
|
||||
pct99_5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99_5".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _10y2y3y4y5y6y8yPattern {
|
||||
pub _10y: BpsPercentRatioPattern,
|
||||
@@ -1487,7 +1515,7 @@ impl AverageBlockCumulativeInSumPattern {
|
||||
pub struct BpsCentsPercentilesRatioSatsUsdPattern {
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub cents: SeriesPattern1<Cents>,
|
||||
pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub usd: SeriesPattern1<Dollars>,
|
||||
@@ -1499,7 +1527,7 @@ impl BpsCentsPercentilesRatioSatsUsdPattern {
|
||||
Self {
|
||||
bps: SeriesPattern1::new(client.clone(), _m(&acc, "ratio_bps")),
|
||||
cents: SeriesPattern1::new(client.clone(), _m(&acc, "cents")),
|
||||
percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), acc.clone()),
|
||||
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), acc.clone()),
|
||||
ratio: SeriesPattern1::new(client.clone(), _m(&acc, "ratio")),
|
||||
sats: SeriesPattern1::new(client.clone(), _m(&acc, "sats")),
|
||||
usd: SeriesPattern1::new(client.clone(), acc.clone()),
|
||||
@@ -1603,30 +1631,6 @@ impl DeltaHalfInToTotalPattern2 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct Pct1Pct2Pct5Pct95Pct98Pct99Pattern {
|
||||
pub pct1: BpsPriceRatioPattern,
|
||||
pub pct2: BpsPriceRatioPattern,
|
||||
pub pct5: BpsPriceRatioPattern,
|
||||
pub pct95: BpsPriceRatioPattern,
|
||||
pub pct98: BpsPriceRatioPattern,
|
||||
pub pct99: BpsPriceRatioPattern,
|
||||
}
|
||||
|
||||
impl Pct1Pct2Pct5Pct95Pct98Pct99Pattern {
|
||||
/// Create a new pattern node with accumulated series name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
pct1: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct1".to_string()),
|
||||
pct2: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct2".to_string()),
|
||||
pct5: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct5".to_string()),
|
||||
pct95: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct95".to_string()),
|
||||
pct98: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct98".to_string()),
|
||||
pct99: BpsPriceRatioPattern::new(client.clone(), acc.clone(), "pct99".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct _1m1w1y24hBlockPattern {
|
||||
pub _1m: SeriesPattern1<StoredF32>,
|
||||
@@ -3126,6 +3130,7 @@ pub struct SeriesTree {
|
||||
pub constants: SeriesTree_Constants,
|
||||
pub indexes: SeriesTree_Indexes,
|
||||
pub indicators: SeriesTree_Indicators,
|
||||
pub investing: SeriesTree_Investing,
|
||||
pub market: SeriesTree_Market,
|
||||
pub pools: SeriesTree_Pools,
|
||||
pub prices: SeriesTree_Prices,
|
||||
@@ -3148,6 +3153,7 @@ impl SeriesTree {
|
||||
constants: SeriesTree_Constants::new(client.clone(), format!("{base_path}_constants")),
|
||||
indexes: SeriesTree_Indexes::new(client.clone(), format!("{base_path}_indexes")),
|
||||
indicators: SeriesTree_Indicators::new(client.clone(), format!("{base_path}_indicators")),
|
||||
investing: SeriesTree_Investing::new(client.clone(), format!("{base_path}_investing")),
|
||||
market: SeriesTree_Market::new(client.clone(), format!("{base_path}_market")),
|
||||
pools: SeriesTree_Pools::new(client.clone(), format!("{base_path}_pools")),
|
||||
prices: SeriesTree_Prices::new(client.clone(), format!("{base_path}_prices")),
|
||||
@@ -3215,17 +3221,13 @@ impl SeriesTree_Blocks_Difficulty {
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Blocks_Time {
|
||||
pub timestamp: SeriesPattern1<Timestamp>,
|
||||
pub date: SeriesPattern18<Date>,
|
||||
pub timestamp_monotonic: SeriesPattern18<Timestamp>,
|
||||
pub timestamp: SeriesPattern18<Timestamp>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Blocks_Time {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
timestamp: SeriesPattern1::new(client.clone(), "timestamp".to_string()),
|
||||
date: SeriesPattern18::new(client.clone(), "date".to_string()),
|
||||
timestamp_monotonic: SeriesPattern18::new(client.clone(), "timestamp_monotonic".to_string()),
|
||||
timestamp: SeriesPattern18::new(client.clone(), "timestamp".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4562,6 +4564,7 @@ pub struct SeriesTree_Indexes {
|
||||
pub tx_index: SeriesTree_Indexes_TxIndex,
|
||||
pub txin_index: SeriesTree_Indexes_TxinIndex,
|
||||
pub txout_index: SeriesTree_Indexes_TxoutIndex,
|
||||
pub timestamp: SeriesTree_Indexes_Timestamp,
|
||||
}
|
||||
|
||||
impl SeriesTree_Indexes {
|
||||
@@ -4587,6 +4590,7 @@ impl SeriesTree_Indexes {
|
||||
tx_index: SeriesTree_Indexes_TxIndex::new(client.clone(), format!("{base_path}_tx_index")),
|
||||
txin_index: SeriesTree_Indexes_TxinIndex::new(client.clone(), format!("{base_path}_txin_index")),
|
||||
txout_index: SeriesTree_Indexes_TxoutIndex::new(client.clone(), format!("{base_path}_txout_index")),
|
||||
timestamp: SeriesTree_Indexes_Timestamp::new(client.clone(), format!("{base_path}_timestamp")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5129,6 +5133,21 @@ impl SeriesTree_Indexes_TxoutIndex {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Indexes_Timestamp {
|
||||
pub monotonic: SeriesPattern18<Timestamp>,
|
||||
pub resolutions: SeriesPattern2<Timestamp>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Indexes_Timestamp {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
monotonic: SeriesPattern18::new(client.clone(), "timestamp_monotonic".to_string()),
|
||||
resolutions: SeriesPattern2::new(client.clone(), "timestamp".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Indicators {
|
||||
pub puell_multiple: BpsRatioPattern2,
|
||||
@@ -5141,6 +5160,7 @@ pub struct SeriesTree_Indicators {
|
||||
pub dormancy: SeriesTree_Indicators_Dormancy,
|
||||
pub stock_to_flow: SeriesPattern1<StoredF32>,
|
||||
pub seller_exhaustion: SeriesPattern1<StoredF32>,
|
||||
pub thermometer: SeriesTree_Indicators_Thermometer,
|
||||
}
|
||||
|
||||
impl SeriesTree_Indicators {
|
||||
@@ -5156,6 +5176,7 @@ impl SeriesTree_Indicators {
|
||||
dormancy: SeriesTree_Indicators_Dormancy::new(client.clone(), format!("{base_path}_dormancy")),
|
||||
stock_to_flow: SeriesPattern1::new(client.clone(), "stock_to_flow".to_string()),
|
||||
seller_exhaustion: SeriesPattern1::new(client.clone(), "seller_exhaustion".to_string()),
|
||||
thermometer: SeriesTree_Indicators_Thermometer::new(client.clone(), format!("{base_path}_thermometer")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5175,6 +5196,234 @@ impl SeriesTree_Indicators_Dormancy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Indicators_Thermometer {
|
||||
pub pct0_5: CentsSatsUsdPattern,
|
||||
pub pct1: CentsSatsUsdPattern,
|
||||
pub pct2: CentsSatsUsdPattern,
|
||||
pub pct5: CentsSatsUsdPattern,
|
||||
pub pct95: CentsSatsUsdPattern,
|
||||
pub pct98: CentsSatsUsdPattern,
|
||||
pub pct99: CentsSatsUsdPattern,
|
||||
pub pct99_5: CentsSatsUsdPattern,
|
||||
pub zone: SeriesPattern1<StoredI8>,
|
||||
pub score: SeriesPattern1<StoredI8>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Indicators_Thermometer {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
pct0_5: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct0_5".to_string()),
|
||||
pct1: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct01".to_string()),
|
||||
pct2: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct02".to_string()),
|
||||
pct5: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct05".to_string()),
|
||||
pct95: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct95".to_string()),
|
||||
pct98: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct98".to_string()),
|
||||
pct99: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct99".to_string()),
|
||||
pct99_5: CentsSatsUsdPattern::new(client.clone(), "thermometer_pct99_5".to_string()),
|
||||
zone: SeriesPattern1::new(client.clone(), "thermometer_zone".to_string()),
|
||||
score: SeriesPattern1::new(client.clone(), "thermometer_score".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing {
|
||||
pub sats_per_day: SeriesPattern18<Sats>,
|
||||
pub period: SeriesTree_Investing_Period,
|
||||
pub class: SeriesTree_Investing_Class,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sats_per_day: SeriesPattern18::new(client.clone(), "dca_sats_per_day".to_string()),
|
||||
period: SeriesTree_Investing_Period::new(client.clone(), format!("{base_path}_period")),
|
||||
class: SeriesTree_Investing_Class::new(client.clone(), format!("{base_path}_class")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing_Period {
|
||||
pub stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3,
|
||||
pub cost_basis: SeriesTree_Investing_Period_CostBasis,
|
||||
pub return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2,
|
||||
pub cagr: _10y2y3y4y5y6y8yPattern,
|
||||
pub lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3,
|
||||
pub lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing_Period {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "dca_stack".to_string()),
|
||||
cost_basis: SeriesTree_Investing_Period_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")),
|
||||
return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_return".to_string()),
|
||||
cagr: _10y2y3y4y5y6y8yPattern::new(client.clone(), "dca_cagr".to_string()),
|
||||
lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "lump_sum_stack".to_string()),
|
||||
lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_return".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing_Period_CostBasis {
|
||||
pub _1w: CentsSatsUsdPattern,
|
||||
pub _1m: CentsSatsUsdPattern,
|
||||
pub _3m: CentsSatsUsdPattern,
|
||||
pub _6m: CentsSatsUsdPattern,
|
||||
pub _1y: CentsSatsUsdPattern,
|
||||
pub _2y: CentsSatsUsdPattern,
|
||||
pub _3y: CentsSatsUsdPattern,
|
||||
pub _4y: CentsSatsUsdPattern,
|
||||
pub _5y: CentsSatsUsdPattern,
|
||||
pub _6y: CentsSatsUsdPattern,
|
||||
pub _8y: CentsSatsUsdPattern,
|
||||
pub _10y: CentsSatsUsdPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing_Period_CostBasis {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
_1w: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1w".to_string()),
|
||||
_1m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1m".to_string()),
|
||||
_3m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3m".to_string()),
|
||||
_6m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6m".to_string()),
|
||||
_1y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1y".to_string()),
|
||||
_2y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_2y".to_string()),
|
||||
_3y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3y".to_string()),
|
||||
_4y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_4y".to_string()),
|
||||
_5y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_5y".to_string()),
|
||||
_6y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6y".to_string()),
|
||||
_8y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_8y".to_string()),
|
||||
_10y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_10y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing_Class {
|
||||
pub stack: SeriesTree_Investing_Class_Stack,
|
||||
pub cost_basis: SeriesTree_Investing_Class_CostBasis,
|
||||
pub return_: SeriesTree_Investing_Class_Return,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing_Class {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
stack: SeriesTree_Investing_Class_Stack::new(client.clone(), format!("{base_path}_stack")),
|
||||
cost_basis: SeriesTree_Investing_Class_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")),
|
||||
return_: SeriesTree_Investing_Class_Return::new(client.clone(), format!("{base_path}_return")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing_Class_Stack {
|
||||
pub from_2015: BtcCentsSatsUsdPattern3,
|
||||
pub from_2016: BtcCentsSatsUsdPattern3,
|
||||
pub from_2017: BtcCentsSatsUsdPattern3,
|
||||
pub from_2018: BtcCentsSatsUsdPattern3,
|
||||
pub from_2019: BtcCentsSatsUsdPattern3,
|
||||
pub from_2020: BtcCentsSatsUsdPattern3,
|
||||
pub from_2021: BtcCentsSatsUsdPattern3,
|
||||
pub from_2022: BtcCentsSatsUsdPattern3,
|
||||
pub from_2023: BtcCentsSatsUsdPattern3,
|
||||
pub from_2024: BtcCentsSatsUsdPattern3,
|
||||
pub from_2025: BtcCentsSatsUsdPattern3,
|
||||
pub from_2026: BtcCentsSatsUsdPattern3,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing_Class_Stack {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
from_2015: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2015".to_string()),
|
||||
from_2016: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2016".to_string()),
|
||||
from_2017: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2017".to_string()),
|
||||
from_2018: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2018".to_string()),
|
||||
from_2019: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2019".to_string()),
|
||||
from_2020: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2020".to_string()),
|
||||
from_2021: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2021".to_string()),
|
||||
from_2022: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2022".to_string()),
|
||||
from_2023: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2023".to_string()),
|
||||
from_2024: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2024".to_string()),
|
||||
from_2025: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2025".to_string()),
|
||||
from_2026: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2026".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing_Class_CostBasis {
|
||||
pub from_2015: CentsSatsUsdPattern,
|
||||
pub from_2016: CentsSatsUsdPattern,
|
||||
pub from_2017: CentsSatsUsdPattern,
|
||||
pub from_2018: CentsSatsUsdPattern,
|
||||
pub from_2019: CentsSatsUsdPattern,
|
||||
pub from_2020: CentsSatsUsdPattern,
|
||||
pub from_2021: CentsSatsUsdPattern,
|
||||
pub from_2022: CentsSatsUsdPattern,
|
||||
pub from_2023: CentsSatsUsdPattern,
|
||||
pub from_2024: CentsSatsUsdPattern,
|
||||
pub from_2025: CentsSatsUsdPattern,
|
||||
pub from_2026: CentsSatsUsdPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing_Class_CostBasis {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
from_2015: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2015".to_string()),
|
||||
from_2016: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2016".to_string()),
|
||||
from_2017: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2017".to_string()),
|
||||
from_2018: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2018".to_string()),
|
||||
from_2019: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2019".to_string()),
|
||||
from_2020: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2020".to_string()),
|
||||
from_2021: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2021".to_string()),
|
||||
from_2022: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2022".to_string()),
|
||||
from_2023: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2023".to_string()),
|
||||
from_2024: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2024".to_string()),
|
||||
from_2025: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2025".to_string()),
|
||||
from_2026: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2026".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Investing_Class_Return {
|
||||
pub from_2015: BpsPercentRatioPattern,
|
||||
pub from_2016: BpsPercentRatioPattern,
|
||||
pub from_2017: BpsPercentRatioPattern,
|
||||
pub from_2018: BpsPercentRatioPattern,
|
||||
pub from_2019: BpsPercentRatioPattern,
|
||||
pub from_2020: BpsPercentRatioPattern,
|
||||
pub from_2021: BpsPercentRatioPattern,
|
||||
pub from_2022: BpsPercentRatioPattern,
|
||||
pub from_2023: BpsPercentRatioPattern,
|
||||
pub from_2024: BpsPercentRatioPattern,
|
||||
pub from_2025: BpsPercentRatioPattern,
|
||||
pub from_2026: BpsPercentRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Investing_Class_Return {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
from_2015: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2015".to_string()),
|
||||
from_2016: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2016".to_string()),
|
||||
from_2017: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2017".to_string()),
|
||||
from_2018: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2018".to_string()),
|
||||
from_2019: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2019".to_string()),
|
||||
from_2020: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2020".to_string()),
|
||||
from_2021: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2021".to_string()),
|
||||
from_2022: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2022".to_string()),
|
||||
from_2023: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2023".to_string()),
|
||||
from_2024: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2024".to_string()),
|
||||
from_2025: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2025".to_string()),
|
||||
from_2026: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2026".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market {
|
||||
pub ath: SeriesTree_Market_Ath,
|
||||
@@ -5183,7 +5432,6 @@ pub struct SeriesTree_Market {
|
||||
pub volatility: _1m1w1y24hPattern<StoredF32>,
|
||||
pub range: SeriesTree_Market_Range,
|
||||
pub moving_average: SeriesTree_Market_MovingAverage,
|
||||
pub dca: SeriesTree_Market_Dca,
|
||||
pub technical: SeriesTree_Market_Technical,
|
||||
}
|
||||
|
||||
@@ -5196,7 +5444,6 @@ impl SeriesTree_Market {
|
||||
volatility: _1m1w1y24hPattern::new(client.clone(), "price_volatility".to_string()),
|
||||
range: SeriesTree_Market_Range::new(client.clone(), format!("{base_path}_range")),
|
||||
moving_average: SeriesTree_Market_MovingAverage::new(client.clone(), format!("{base_path}_moving_average")),
|
||||
dca: SeriesTree_Market_Dca::new(client.clone(), format!("{base_path}_dca")),
|
||||
technical: SeriesTree_Market_Technical::new(client.clone(), format!("{base_path}_technical")),
|
||||
}
|
||||
}
|
||||
@@ -5565,203 +5812,6 @@ impl SeriesTree_Market_MovingAverage_Ema {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca {
|
||||
pub sats_per_day: SeriesPattern18<Sats>,
|
||||
pub period: SeriesTree_Market_Dca_Period,
|
||||
pub class: SeriesTree_Market_Dca_Class,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sats_per_day: SeriesPattern18::new(client.clone(), "dca_sats_per_day".to_string()),
|
||||
period: SeriesTree_Market_Dca_Period::new(client.clone(), format!("{base_path}_period")),
|
||||
class: SeriesTree_Market_Dca_Class::new(client.clone(), format!("{base_path}_class")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca_Period {
|
||||
pub stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3,
|
||||
pub cost_basis: SeriesTree_Market_Dca_Period_CostBasis,
|
||||
pub return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2,
|
||||
pub cagr: _10y2y3y4y5y6y8yPattern,
|
||||
pub lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3,
|
||||
pub lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca_Period {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "dca_stack".to_string()),
|
||||
cost_basis: SeriesTree_Market_Dca_Period_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")),
|
||||
return_: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "dca_return".to_string()),
|
||||
cagr: _10y2y3y4y5y6y8yPattern::new(client.clone(), "dca_cagr".to_string()),
|
||||
lump_sum_stack: _10y1m1w1y2y3m3y4y5y6m6y8yPattern3::new(client.clone(), "lump_sum_stack".to_string()),
|
||||
lump_sum_return: _10y1m1w1y2y3m3y4y5y6m6y8yPattern2::new(client.clone(), "lump_sum_return".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca_Period_CostBasis {
|
||||
pub _1w: CentsSatsUsdPattern,
|
||||
pub _1m: CentsSatsUsdPattern,
|
||||
pub _3m: CentsSatsUsdPattern,
|
||||
pub _6m: CentsSatsUsdPattern,
|
||||
pub _1y: CentsSatsUsdPattern,
|
||||
pub _2y: CentsSatsUsdPattern,
|
||||
pub _3y: CentsSatsUsdPattern,
|
||||
pub _4y: CentsSatsUsdPattern,
|
||||
pub _5y: CentsSatsUsdPattern,
|
||||
pub _6y: CentsSatsUsdPattern,
|
||||
pub _8y: CentsSatsUsdPattern,
|
||||
pub _10y: CentsSatsUsdPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca_Period_CostBasis {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
_1w: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1w".to_string()),
|
||||
_1m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1m".to_string()),
|
||||
_3m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3m".to_string()),
|
||||
_6m: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6m".to_string()),
|
||||
_1y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_1y".to_string()),
|
||||
_2y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_2y".to_string()),
|
||||
_3y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_3y".to_string()),
|
||||
_4y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_4y".to_string()),
|
||||
_5y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_5y".to_string()),
|
||||
_6y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_6y".to_string()),
|
||||
_8y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_8y".to_string()),
|
||||
_10y: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_10y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca_Class {
|
||||
pub stack: SeriesTree_Market_Dca_Class_Stack,
|
||||
pub cost_basis: SeriesTree_Market_Dca_Class_CostBasis,
|
||||
pub return_: SeriesTree_Market_Dca_Class_Return,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca_Class {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
stack: SeriesTree_Market_Dca_Class_Stack::new(client.clone(), format!("{base_path}_stack")),
|
||||
cost_basis: SeriesTree_Market_Dca_Class_CostBasis::new(client.clone(), format!("{base_path}_cost_basis")),
|
||||
return_: SeriesTree_Market_Dca_Class_Return::new(client.clone(), format!("{base_path}_return")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca_Class_Stack {
|
||||
pub from_2015: BtcCentsSatsUsdPattern3,
|
||||
pub from_2016: BtcCentsSatsUsdPattern3,
|
||||
pub from_2017: BtcCentsSatsUsdPattern3,
|
||||
pub from_2018: BtcCentsSatsUsdPattern3,
|
||||
pub from_2019: BtcCentsSatsUsdPattern3,
|
||||
pub from_2020: BtcCentsSatsUsdPattern3,
|
||||
pub from_2021: BtcCentsSatsUsdPattern3,
|
||||
pub from_2022: BtcCentsSatsUsdPattern3,
|
||||
pub from_2023: BtcCentsSatsUsdPattern3,
|
||||
pub from_2024: BtcCentsSatsUsdPattern3,
|
||||
pub from_2025: BtcCentsSatsUsdPattern3,
|
||||
pub from_2026: BtcCentsSatsUsdPattern3,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca_Class_Stack {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
from_2015: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2015".to_string()),
|
||||
from_2016: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2016".to_string()),
|
||||
from_2017: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2017".to_string()),
|
||||
from_2018: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2018".to_string()),
|
||||
from_2019: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2019".to_string()),
|
||||
from_2020: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2020".to_string()),
|
||||
from_2021: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2021".to_string()),
|
||||
from_2022: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2022".to_string()),
|
||||
from_2023: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2023".to_string()),
|
||||
from_2024: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2024".to_string()),
|
||||
from_2025: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2025".to_string()),
|
||||
from_2026: BtcCentsSatsUsdPattern3::new(client.clone(), "dca_stack_from_2026".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca_Class_CostBasis {
|
||||
pub from_2015: CentsSatsUsdPattern,
|
||||
pub from_2016: CentsSatsUsdPattern,
|
||||
pub from_2017: CentsSatsUsdPattern,
|
||||
pub from_2018: CentsSatsUsdPattern,
|
||||
pub from_2019: CentsSatsUsdPattern,
|
||||
pub from_2020: CentsSatsUsdPattern,
|
||||
pub from_2021: CentsSatsUsdPattern,
|
||||
pub from_2022: CentsSatsUsdPattern,
|
||||
pub from_2023: CentsSatsUsdPattern,
|
||||
pub from_2024: CentsSatsUsdPattern,
|
||||
pub from_2025: CentsSatsUsdPattern,
|
||||
pub from_2026: CentsSatsUsdPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca_Class_CostBasis {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
from_2015: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2015".to_string()),
|
||||
from_2016: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2016".to_string()),
|
||||
from_2017: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2017".to_string()),
|
||||
from_2018: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2018".to_string()),
|
||||
from_2019: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2019".to_string()),
|
||||
from_2020: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2020".to_string()),
|
||||
from_2021: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2021".to_string()),
|
||||
from_2022: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2022".to_string()),
|
||||
from_2023: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2023".to_string()),
|
||||
from_2024: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2024".to_string()),
|
||||
from_2025: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2025".to_string()),
|
||||
from_2026: CentsSatsUsdPattern::new(client.clone(), "dca_cost_basis_from_2026".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Dca_Class_Return {
|
||||
pub from_2015: BpsPercentRatioPattern,
|
||||
pub from_2016: BpsPercentRatioPattern,
|
||||
pub from_2017: BpsPercentRatioPattern,
|
||||
pub from_2018: BpsPercentRatioPattern,
|
||||
pub from_2019: BpsPercentRatioPattern,
|
||||
pub from_2020: BpsPercentRatioPattern,
|
||||
pub from_2021: BpsPercentRatioPattern,
|
||||
pub from_2022: BpsPercentRatioPattern,
|
||||
pub from_2023: BpsPercentRatioPattern,
|
||||
pub from_2024: BpsPercentRatioPattern,
|
||||
pub from_2025: BpsPercentRatioPattern,
|
||||
pub from_2026: BpsPercentRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Market_Dca_Class_Return {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
from_2015: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2015".to_string()),
|
||||
from_2016: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2016".to_string()),
|
||||
from_2017: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2017".to_string()),
|
||||
from_2018: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2018".to_string()),
|
||||
from_2019: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2019".to_string()),
|
||||
from_2020: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2020".to_string()),
|
||||
from_2021: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2021".to_string()),
|
||||
from_2022: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2022".to_string()),
|
||||
from_2023: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2023".to_string()),
|
||||
from_2024: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2024".to_string()),
|
||||
from_2025: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2025".to_string()),
|
||||
from_2026: BpsPercentRatioPattern::new(client.clone(), "dca_return_from_2026".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Market_Technical {
|
||||
pub rsi: SeriesTree_Market_Technical_Rsi,
|
||||
@@ -6527,7 +6577,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized_Price {
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub sma: _1m1w1y2y4yAllPattern,
|
||||
pub std_dev: SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev,
|
||||
}
|
||||
@@ -6540,7 +6590,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized_Price {
|
||||
sats: SeriesPattern1::new(client.clone(), "realized_price_sats".to_string()),
|
||||
bps: SeriesPattern1::new(client.clone(), "realized_price_ratio_bps".to_string()),
|
||||
ratio: SeriesPattern1::new(client.clone(), "realized_price_ratio".to_string()),
|
||||
percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "realized_price".to_string()),
|
||||
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "realized_price".to_string()),
|
||||
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "realized_price_ratio_sma".to_string()),
|
||||
std_dev: SeriesTree_Cohorts_Utxo_All_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
|
||||
}
|
||||
@@ -6957,7 +7007,7 @@ pub struct SeriesTree_Cohorts_Utxo_Sth_Realized_Price {
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub sma: _1m1w1y2y4yAllPattern,
|
||||
pub std_dev: SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev,
|
||||
}
|
||||
@@ -6970,7 +7020,7 @@ impl SeriesTree_Cohorts_Utxo_Sth_Realized_Price {
|
||||
sats: SeriesPattern1::new(client.clone(), "sth_realized_price_sats".to_string()),
|
||||
bps: SeriesPattern1::new(client.clone(), "sth_realized_price_ratio_bps".to_string()),
|
||||
ratio: SeriesPattern1::new(client.clone(), "sth_realized_price_ratio".to_string()),
|
||||
percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "sth_realized_price".to_string()),
|
||||
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "sth_realized_price".to_string()),
|
||||
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "sth_realized_price_ratio_sma".to_string()),
|
||||
std_dev: SeriesTree_Cohorts_Utxo_Sth_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
|
||||
}
|
||||
@@ -7225,7 +7275,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Price {
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub sma: _1m1w1y2y4yAllPattern,
|
||||
pub std_dev: SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev,
|
||||
}
|
||||
@@ -7238,7 +7288,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized_Price {
|
||||
sats: SeriesPattern1::new(client.clone(), "lth_realized_price_sats".to_string()),
|
||||
bps: SeriesPattern1::new(client.clone(), "lth_realized_price_ratio_bps".to_string()),
|
||||
ratio: SeriesPattern1::new(client.clone(), "lth_realized_price_ratio".to_string()),
|
||||
percentiles: Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "lth_realized_price".to_string()),
|
||||
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "lth_realized_price".to_string()),
|
||||
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "lth_realized_price_ratio_sma".to_string()),
|
||||
std_dev: SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
|
||||
}
|
||||
@@ -8145,7 +8195,7 @@ pub struct BrkClient {
|
||||
|
||||
impl BrkClient {
|
||||
/// Client version.
|
||||
pub const VERSION: &'static str = "v0.1.9";
|
||||
pub const VERSION: &'static str = "v0.2.2";
|
||||
|
||||
/// Create a new client with the given base URL.
|
||||
pub fn new(base_url: impl Into<String>) -> Self {
|
||||
|
||||
@@ -17,12 +17,10 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// Sequential: time → lookback (dependency chain)
|
||||
self.time
|
||||
.timestamp
|
||||
.compute(indexer, indexes, starting_indexes, exit)?;
|
||||
self.lookback
|
||||
.compute(&self.time, starting_indexes, exit)?;
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// lookback depends on indexes.timestamp.monotonic
|
||||
self.lookback.compute(indexes, starting_indexes, exit)?;
|
||||
|
||||
// Parallel: remaining sub-modules are independent of each other.
|
||||
// size depends on lookback (already computed above).
|
||||
@@ -52,8 +50,11 @@ impl Vecs {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
CountVecs, DifficultyVecs, HalvingVecs, IntervalVecs, LookbackVecs, SizeVecs, TimeVecs, Vecs,
|
||||
CountVecs, DifficultyVecs, HalvingVecs, IntervalVecs, LookbackVecs, SizeVecs, Vecs,
|
||||
WeightVecs,
|
||||
};
|
||||
|
||||
@@ -30,7 +30,6 @@ impl Vecs {
|
||||
let interval = IntervalVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
let size = SizeVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
let weight = WeightVecs::forced_import(&db, version, indexes, cached_starts, &size)?;
|
||||
let time = TimeVecs::forced_import(&db, version, indexes)?;
|
||||
let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?;
|
||||
let halving = HalvingVecs::forced_import(&db, version, indexes)?;
|
||||
|
||||
@@ -41,7 +40,6 @@ impl Vecs {
|
||||
interval,
|
||||
size,
|
||||
weight,
|
||||
time,
|
||||
difficulty,
|
||||
halving,
|
||||
};
|
||||
|
||||
@@ -3,9 +3,10 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Indexes, Timestamp, Version};
|
||||
use vecdb::{AnyVec, CachedVec, Cursor, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode, VecIndex};
|
||||
|
||||
use crate::internal::{CachedWindowStarts, Windows, WindowStarts};
|
||||
|
||||
use super::time;
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{CachedWindowStarts, Windows, WindowStarts},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
@@ -178,71 +179,71 @@ impl Vecs {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
time: &time::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.compute_rolling_start_hours(time, starting_indexes, exit, 1, |s| {
|
||||
self.compute_rolling_start_hours(indexes, starting_indexes, exit, 1, |s| {
|
||||
&mut s._1h
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 1, |s| &mut s._24h)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 3, |s| &mut s._3d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 7, |s| &mut s._1w)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 8, |s| &mut s._8d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 9, |s| &mut s._9d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 12, |s| &mut s._12d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 13, |s| &mut s._13d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 14, |s| &mut s._2w)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 21, |s| &mut s._21d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 26, |s| &mut s._26d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 30, |s| &mut s._1m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 34, |s| &mut s._34d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 55, |s| &mut s._55d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 60, |s| &mut s._2m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 63, |s| &mut s._9w)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 84, |s| &mut s._12w)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 89, |s| &mut s._89d)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 90, |s| &mut s._3m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 98, |s| &mut s._14w)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 111, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 1, |s| &mut s._24h)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 3, |s| &mut s._3d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 7, |s| &mut s._1w)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 8, |s| &mut s._8d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 9, |s| &mut s._9d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 12, |s| &mut s._12d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 13, |s| &mut s._13d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 14, |s| &mut s._2w)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 21, |s| &mut s._21d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 26, |s| &mut s._26d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 30, |s| &mut s._1m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 34, |s| &mut s._34d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 55, |s| &mut s._55d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 60, |s| &mut s._2m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 63, |s| &mut s._9w)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 84, |s| &mut s._12w)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 89, |s| &mut s._89d)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 90, |s| &mut s._3m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 98, |s| &mut s._14w)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 111, |s| {
|
||||
&mut s._111d
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 144, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 144, |s| {
|
||||
&mut s._144d
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 180, |s| &mut s._6m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 182, |s| &mut s._26w)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 200, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 180, |s| &mut s._6m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 182, |s| &mut s._26w)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 200, |s| {
|
||||
&mut s._200d
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 270, |s| &mut s._9m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 350, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 270, |s| &mut s._9m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 350, |s| {
|
||||
&mut s._350d
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 360, |s| &mut s._12m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 365, |s| &mut s._1y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 420, |s| &mut s._14m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 730, |s| &mut s._2y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 780, |s| &mut s._26m)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 1095, |s| &mut s._3y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 1400, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 360, |s| &mut s._12m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 365, |s| &mut s._1y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 420, |s| &mut s._14m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 730, |s| &mut s._2y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 780, |s| &mut s._26m)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 1095, |s| &mut s._3y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 1400, |s| {
|
||||
&mut s._200w
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 1460, |s| &mut s._4y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 1825, |s| &mut s._5y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 2190, |s| &mut s._6y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 2920, |s| &mut s._8y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 3285, |s| &mut s._9y)?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 3650, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 1460, |s| &mut s._4y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 1825, |s| &mut s._5y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 2190, |s| &mut s._6y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 2920, |s| &mut s._8y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 3285, |s| &mut s._9y)?;
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 3650, |s| {
|
||||
&mut s._10y
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 4380, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 4380, |s| {
|
||||
&mut s._12y
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 5110, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 5110, |s| {
|
||||
&mut s._14y
|
||||
})?;
|
||||
self.compute_rolling_start(time, starting_indexes, exit, 9490, |s| {
|
||||
self.compute_rolling_start(indexes, starting_indexes, exit, 9490, |s| {
|
||||
&mut s._26y
|
||||
})?;
|
||||
|
||||
@@ -251,7 +252,7 @@ impl Vecs {
|
||||
|
||||
fn compute_rolling_start<F>(
|
||||
&mut self,
|
||||
time: &time::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
days: usize,
|
||||
@@ -260,14 +261,14 @@ impl Vecs {
|
||||
where
|
||||
F: FnOnce(&mut Self) -> &mut EagerVec<PcoVec<Height, Height>>,
|
||||
{
|
||||
self.compute_rolling_start_inner(time, starting_indexes, exit, get_field, |t, prev_ts| {
|
||||
self.compute_rolling_start_inner(indexes, starting_indexes, exit, get_field, |t, prev_ts| {
|
||||
t.difference_in_days_between(prev_ts) >= days
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_rolling_start_hours<F>(
|
||||
&mut self,
|
||||
time: &time::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
hours: usize,
|
||||
@@ -276,14 +277,14 @@ impl Vecs {
|
||||
where
|
||||
F: FnOnce(&mut Self) -> &mut EagerVec<PcoVec<Height, Height>>,
|
||||
{
|
||||
self.compute_rolling_start_inner(time, starting_indexes, exit, get_field, |t, prev_ts| {
|
||||
self.compute_rolling_start_inner(indexes, starting_indexes, exit, get_field, |t, prev_ts| {
|
||||
t.difference_in_hours_between(prev_ts) >= hours
|
||||
})
|
||||
}
|
||||
|
||||
fn compute_rolling_start_inner<F, D>(
|
||||
&mut self,
|
||||
time: &time::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
get_field: F,
|
||||
@@ -300,12 +301,12 @@ impl Vecs {
|
||||
} else {
|
||||
Height::ZERO
|
||||
};
|
||||
let mut cursor = Cursor::new(&time.timestamp_monotonic);
|
||||
let mut cursor = Cursor::new(&indexes.timestamp.monotonic);
|
||||
cursor.advance(prev.to_usize());
|
||||
let mut prev_ts = cursor.next().unwrap();
|
||||
Ok(field.compute_transform(
|
||||
starting_indexes.height,
|
||||
&time.timestamp_monotonic,
|
||||
&indexes.timestamp.monotonic,
|
||||
|(h, t, ..)| {
|
||||
while expired(t, prev_ts) {
|
||||
prev.increment();
|
||||
|
||||
@@ -4,7 +4,6 @@ pub mod halving;
|
||||
pub mod interval;
|
||||
pub mod lookback;
|
||||
pub mod size;
|
||||
pub mod time;
|
||||
pub mod weight;
|
||||
|
||||
mod compute;
|
||||
@@ -19,7 +18,6 @@ pub use halving::Vecs as HalvingVecs;
|
||||
pub use interval::Vecs as IntervalVecs;
|
||||
pub use lookback::Vecs as LookbackVecs;
|
||||
pub use size::Vecs as SizeVecs;
|
||||
pub use time::Vecs as TimeVecs;
|
||||
pub use weight::Vecs as WeightVecs;
|
||||
|
||||
pub const DB_NAME: &str = "blocks";
|
||||
@@ -37,7 +35,7 @@ pub(crate) const ONE_TERA_HASH: f64 = 1_000_000_000_000.0;
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub(crate) db: Database,
|
||||
pub db: Database,
|
||||
|
||||
pub count: CountVecs<M>,
|
||||
pub lookback: LookbackVecs<M>,
|
||||
@@ -46,7 +44,6 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub size: SizeVecs<M>,
|
||||
#[traversable(flatten)]
|
||||
pub weight: WeightVecs<M>,
|
||||
pub time: TimeVecs<M>,
|
||||
pub difficulty: DifficultyVecs<M>,
|
||||
pub halving: HalvingVecs<M>,
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use super::Vecs;
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
starting_height: brk_types::Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let mut prev_timestamp_monotonic = None;
|
||||
self.timestamp_monotonic.compute_transform(
|
||||
starting_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
|(h, timestamp, this)| {
|
||||
if prev_timestamp_monotonic.is_none()
|
||||
&& let Some(prev_h) = h.decremented()
|
||||
{
|
||||
prev_timestamp_monotonic.replace(this.collect_one(prev_h).unwrap());
|
||||
}
|
||||
let timestamp_monotonic =
|
||||
prev_timestamp_monotonic.map_or(timestamp, |prev_d| prev_d.max(timestamp));
|
||||
prev_timestamp_monotonic.replace(timestamp_monotonic);
|
||||
(h, timestamp_monotonic)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Date, Height, Version};
|
||||
use vecdb::{Database, EagerVec, ImportableVec, LazyVecFrom1, ReadableCloneableVec};
|
||||
|
||||
use super::{TimestampIndexes, Vecs};
|
||||
use crate::indexes;
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let timestamp_monotonic = EagerVec::forced_import(db, "timestamp_monotonic", version)?;
|
||||
|
||||
Ok(Self {
|
||||
date: LazyVecFrom1::init(
|
||||
"date",
|
||||
version,
|
||||
timestamp_monotonic.read_only_boxed_clone(),
|
||||
|_height: Height, timestamp| Date::from(timestamp),
|
||||
),
|
||||
timestamp_monotonic,
|
||||
timestamp: TimestampIndexes::forced_import(db, version, indexes)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TimestampIndexes {
|
||||
fn forced_import(db: &Database, version: Version, indexes: &indexes::Vecs) -> Result<Self> {
|
||||
macro_rules! period {
|
||||
($field:ident) => {
|
||||
LazyVecFrom1::init(
|
||||
"timestamp",
|
||||
version,
|
||||
indexes.$field.first_height.read_only_boxed_clone(),
|
||||
|idx, _: Height| idx.to_timestamp(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! epoch {
|
||||
($field:ident) => {
|
||||
ImportableVec::forced_import(db, "timestamp", version)?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self(crate::internal::PerResolution {
|
||||
minute10: period!(minute10),
|
||||
minute30: period!(minute30),
|
||||
hour1: period!(hour1),
|
||||
hour4: period!(hour4),
|
||||
hour12: period!(hour12),
|
||||
day1: period!(day1),
|
||||
day3: period!(day3),
|
||||
week1: period!(week1),
|
||||
month1: period!(month1),
|
||||
month3: period!(month3),
|
||||
month6: period!(month6),
|
||||
year1: period!(year1),
|
||||
year10: period!(year10),
|
||||
halving: epoch!(halving),
|
||||
epoch: epoch!(difficulty),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mod compute;
|
||||
mod import;
|
||||
mod vecs;
|
||||
|
||||
pub use vecs::{TimestampIndexes, Vecs};
|
||||
@@ -1,80 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
Date, Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Indexes,
|
||||
Minute10, Minute30, Month1, Month3, Month6, Timestamp, Week1, Year1, Year10,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{EagerVec, Exit, LazyVecFrom1, PcoVec, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{indexes, internal::PerResolution};
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub date: LazyVecFrom1<Height, Date, Height, Timestamp>,
|
||||
pub timestamp_monotonic: M::Stored<EagerVec<PcoVec<Height, Timestamp>>>,
|
||||
pub timestamp: TimestampIndexes<M>,
|
||||
}
|
||||
|
||||
/// Per-period timestamp indexes.
|
||||
///
|
||||
/// Time-based periods (minute10–year10) are lazy: `idx.to_timestamp()` is a pure
|
||||
/// function of the index, so no storage or decompression is needed.
|
||||
/// Epoch-based periods (halving, difficulty) are eager: their timestamps
|
||||
/// come from block data via `compute_indirect_sequential`.
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct TimestampIndexes<M: StorageMode = Rw>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub PerResolution<
|
||||
LazyVecFrom1<Minute10, Timestamp, Minute10, Height>,
|
||||
LazyVecFrom1<Minute30, Timestamp, Minute30, Height>,
|
||||
LazyVecFrom1<Hour1, Timestamp, Hour1, Height>,
|
||||
LazyVecFrom1<Hour4, Timestamp, Hour4, Height>,
|
||||
LazyVecFrom1<Hour12, Timestamp, Hour12, Height>,
|
||||
LazyVecFrom1<Day1, Timestamp, Day1, Height>,
|
||||
LazyVecFrom1<Day3, Timestamp, Day3, Height>,
|
||||
LazyVecFrom1<Week1, Timestamp, Week1, Height>,
|
||||
LazyVecFrom1<Month1, Timestamp, Month1, Height>,
|
||||
LazyVecFrom1<Month3, Timestamp, Month3, Height>,
|
||||
LazyVecFrom1<Month6, Timestamp, Month6, Height>,
|
||||
LazyVecFrom1<Year1, Timestamp, Year1, Height>,
|
||||
LazyVecFrom1<Year10, Timestamp, Year10, Height>,
|
||||
M::Stored<EagerVec<PcoVec<Halving, Timestamp>>>,
|
||||
M::Stored<EagerVec<PcoVec<Epoch, Timestamp>>>,
|
||||
>,
|
||||
);
|
||||
|
||||
impl TimestampIndexes {
|
||||
/// Compute epoch timestamps via indirect lookup from block timestamps.
|
||||
/// Time-based periods are lazy (idx.to_timestamp()) and need no compute.
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &brk_indexer::Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let prev_height = starting_indexes.height.decremented().unwrap_or_default();
|
||||
self.halving.compute_indirect_sequential(
|
||||
indexes
|
||||
.height
|
||||
.halving
|
||||
.collect_one(prev_height)
|
||||
.unwrap_or_default(),
|
||||
&indexes.halving.first_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
exit,
|
||||
)?;
|
||||
self.epoch.compute_indirect_sequential(
|
||||
indexes
|
||||
.height
|
||||
.epoch
|
||||
.collect_one(prev_height)
|
||||
.unwrap_or_default(),
|
||||
&indexes.epoch.first_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ impl Vecs {
|
||||
distribution: &distribution::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// Activity computes first (liveliness, vaultedness, etc.)
|
||||
self.activity
|
||||
.compute(starting_indexes, distribution, exit)?;
|
||||
@@ -80,8 +82,11 @@ impl Vecs {
|
||||
r3?;
|
||||
r4?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,8 @@ impl Vecs {
|
||||
starting_indexes: &mut Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// 1. Find minimum height we have data for across stateful vecs
|
||||
let current_height = Height::from(self.supply_state.len());
|
||||
let min_stateful = self.min_stateful_len();
|
||||
@@ -300,7 +302,7 @@ impl Vecs {
|
||||
.cents
|
||||
.height
|
||||
.len()
|
||||
.min(blocks.time.timestamp_monotonic.len());
|
||||
.min(indexes.timestamp.monotonic.len());
|
||||
let cache_current_len = self.caches.prices.len();
|
||||
if cache_target_len < cache_current_len {
|
||||
self.caches.prices.truncate(cache_target_len);
|
||||
@@ -312,9 +314,9 @@ impl Vecs {
|
||||
.cents
|
||||
.height
|
||||
.collect_range_at(cache_current_len, cache_target_len);
|
||||
let new_timestamps = blocks
|
||||
.time
|
||||
.timestamp_monotonic
|
||||
let new_timestamps = indexes
|
||||
.timestamp
|
||||
.monotonic
|
||||
.collect_range_at(cache_current_len, cache_target_len);
|
||||
self.caches.prices.extend(new_prices);
|
||||
self.caches.timestamps.extend(new_timestamps);
|
||||
@@ -499,8 +501,11 @@ impl Vecs {
|
||||
self.addr_cohorts
|
||||
.compute_rest_part2(prices, starting_indexes, &all_utxo_count, exit)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ mod minute30;
|
||||
mod month1;
|
||||
mod month3;
|
||||
mod month6;
|
||||
pub mod timestamp;
|
||||
mod tx_index;
|
||||
mod txin_index;
|
||||
mod txout_index;
|
||||
@@ -31,13 +32,11 @@ use brk_types::{
|
||||
};
|
||||
use vecdb::{CachedVec, Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
blocks,
|
||||
internal::db_utils::{finalize_db, open_db},
|
||||
};
|
||||
use crate::internal::db_utils::{finalize_db, open_db};
|
||||
|
||||
pub use cached_mappings::CachedMappings;
|
||||
pub use addr::Vecs as AddrVecs;
|
||||
pub use timestamp::Timestamps;
|
||||
pub use cached_mappings::CachedMappings;
|
||||
pub use day1::Vecs as Day1Vecs;
|
||||
pub use day3::Vecs as Day3Vecs;
|
||||
pub use epoch::Vecs as EpochVecs;
|
||||
@@ -85,6 +84,7 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub tx_index: TxIndexVecs<M>,
|
||||
pub txin_index: TxInIndexVecs,
|
||||
pub txout_index: TxOutIndexVecs,
|
||||
pub timestamp: Timestamps<M>,
|
||||
}
|
||||
|
||||
impl Vecs {
|
||||
@@ -136,6 +136,11 @@ impl Vecs {
|
||||
epoch_identity: CachedVec::new(&epoch.identity),
|
||||
};
|
||||
|
||||
let timestamp = Timestamps::forced_import_from_locals(
|
||||
&db, version, &minute10, &minute30, &hour1, &hour4, &hour12, &day1, &day3, &week1,
|
||||
&month1, &month3, &month6, &year1, &year10,
|
||||
)?;
|
||||
|
||||
let this = Self {
|
||||
cached_mappings,
|
||||
addr,
|
||||
@@ -158,6 +163,7 @@ impl Vecs {
|
||||
tx_index,
|
||||
txin_index,
|
||||
txout_index,
|
||||
timestamp,
|
||||
db,
|
||||
};
|
||||
|
||||
@@ -168,36 +174,24 @@ impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks: &mut blocks::Vecs,
|
||||
starting_indexes: Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<Indexes> {
|
||||
blocks
|
||||
.time
|
||||
.compute(indexer, starting_indexes.height, exit)?;
|
||||
let indexes = self.compute_(indexer, &blocks.time, starting_indexes, exit)?;
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
Ok(indexes)
|
||||
}
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// timestamp_monotonic must be computed first — other mappings read it
|
||||
self.timestamp
|
||||
.compute_monotonic(indexer, starting_indexes.height, exit)?;
|
||||
|
||||
fn compute_(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks_time: &blocks::time::Vecs,
|
||||
starting_indexes: Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<Indexes> {
|
||||
self.compute_tx_indexes(indexer, &starting_indexes, exit)?;
|
||||
self.compute_height_indexes(indexer, &starting_indexes, exit)?;
|
||||
|
||||
let prev_height = starting_indexes.height.decremented().unwrap_or_default();
|
||||
|
||||
self.compute_timestamp_mappings(blocks_time, &starting_indexes, exit)?;
|
||||
self.compute_timestamp_mappings(&starting_indexes, exit)?;
|
||||
|
||||
let starting_day1 = self.compute_calendar_mappings(
|
||||
indexer,
|
||||
blocks_time,
|
||||
&starting_indexes,
|
||||
prev_height,
|
||||
exit,
|
||||
@@ -205,13 +199,26 @@ impl Vecs {
|
||||
|
||||
self.compute_period_vecs(
|
||||
indexer,
|
||||
blocks_time,
|
||||
&starting_indexes,
|
||||
prev_height,
|
||||
starting_day1,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.timestamp.compute_per_resolution(
|
||||
indexer,
|
||||
&self.height,
|
||||
&self.halving,
|
||||
&self.epoch,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(starting_indexes)
|
||||
}
|
||||
|
||||
@@ -266,7 +273,6 @@ impl Vecs {
|
||||
|
||||
fn compute_timestamp_mappings(
|
||||
&mut self,
|
||||
blocks_time: &blocks::time::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -274,7 +280,7 @@ impl Vecs {
|
||||
($field:ident, $period:ty) => {
|
||||
self.height.$field.compute_transform(
|
||||
starting_indexes.height,
|
||||
&blocks_time.timestamp_monotonic,
|
||||
&self.timestamp.monotonic,
|
||||
|(h, ts, _)| (h, <$period>::from_timestamp(ts)),
|
||||
exit,
|
||||
)?;
|
||||
@@ -294,7 +300,6 @@ impl Vecs {
|
||||
fn compute_calendar_mappings(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks_time: &blocks::time::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
prev_height: Height,
|
||||
exit: &Exit,
|
||||
@@ -307,8 +312,8 @@ impl Vecs {
|
||||
|
||||
self.height.day1.compute_transform(
|
||||
starting_indexes.height,
|
||||
&blocks_time.date,
|
||||
|(h, d, ..)| (h, Day1::try_from(d).unwrap()),
|
||||
&self.timestamp.monotonic,
|
||||
|(h, ts, ..)| (h, Day1::try_from(Date::from(ts)).unwrap()),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
@@ -318,7 +323,7 @@ impl Vecs {
|
||||
starting_day1
|
||||
};
|
||||
|
||||
self.compute_epoch(indexer, blocks_time, starting_indexes, prev_height, exit)?;
|
||||
self.compute_epoch(indexer, starting_indexes, prev_height, exit)?;
|
||||
|
||||
self.height.week1.compute_transform(
|
||||
starting_indexes.height,
|
||||
@@ -363,7 +368,6 @@ impl Vecs {
|
||||
fn compute_epoch(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks_time: &blocks::time::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
prev_height: Height,
|
||||
exit: &Exit,
|
||||
@@ -389,14 +393,12 @@ impl Vecs {
|
||||
&self.epoch.first_height,
|
||||
exit,
|
||||
)?;
|
||||
self.epoch
|
||||
.height_count
|
||||
.compute_count_from_indexes(
|
||||
starting_difficulty,
|
||||
&self.epoch.first_height,
|
||||
&blocks_time.date,
|
||||
exit,
|
||||
)?;
|
||||
self.epoch.height_count.compute_count_from_indexes(
|
||||
starting_difficulty,
|
||||
&self.epoch.first_height,
|
||||
&self.timestamp.monotonic,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let starting_halving = self
|
||||
.height
|
||||
@@ -426,7 +428,6 @@ impl Vecs {
|
||||
fn compute_period_vecs(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks_time: &blocks::time::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
prev_height: Height,
|
||||
starting_day1: Day1,
|
||||
@@ -478,7 +479,7 @@ impl Vecs {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let date = &blocks_time.date;
|
||||
let ts = &self.timestamp.monotonic;
|
||||
|
||||
macro_rules! dated_period {
|
||||
($period:ident) => {{
|
||||
@@ -500,7 +501,7 @@ impl Vecs {
|
||||
self.$period.date.compute_transform(
|
||||
start,
|
||||
&self.$period.first_height,
|
||||
|(idx, first_h, _)| (idx, date.collect_one(first_h).unwrap()),
|
||||
|(idx, first_h, _)| (idx, Date::from(ts.collect_one(first_h).unwrap())),
|
||||
exit,
|
||||
)?;
|
||||
}};
|
||||
|
||||
150
crates/brk_computer/src/indexes/timestamp.rs
Normal file
150
crates/brk_computer/src/indexes/timestamp.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Indexes, Minute10, Minute30, Month1,
|
||||
Month3, Month6, Timestamp, Week1, Year1, Year10,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{
|
||||
Database, EagerVec, Exit, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec,
|
||||
ReadableVec, Rw, StorageMode, Version,
|
||||
};
|
||||
|
||||
use crate::internal::PerResolution;
|
||||
|
||||
/// Timestamps: monotonic height→timestamp + per-period timestamp lookups.
|
||||
///
|
||||
/// Time-based periods (minute10–year10) are lazy: `idx.to_timestamp()` is a pure
|
||||
/// function of the index, so no storage or decompression is needed.
|
||||
/// Epoch-based periods (halving, difficulty) are eager: their timestamps
|
||||
/// come from block data via `compute_indirect_sequential`.
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct Timestamps<M: StorageMode = Rw> {
|
||||
pub monotonic: M::Stored<EagerVec<PcoVec<Height, Timestamp>>>,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub resolutions: PerResolution<
|
||||
LazyVecFrom1<Minute10, Timestamp, Minute10, Height>,
|
||||
LazyVecFrom1<Minute30, Timestamp, Minute30, Height>,
|
||||
LazyVecFrom1<Hour1, Timestamp, Hour1, Height>,
|
||||
LazyVecFrom1<Hour4, Timestamp, Hour4, Height>,
|
||||
LazyVecFrom1<Hour12, Timestamp, Hour12, Height>,
|
||||
LazyVecFrom1<Day1, Timestamp, Day1, Height>,
|
||||
LazyVecFrom1<Day3, Timestamp, Day3, Height>,
|
||||
LazyVecFrom1<Week1, Timestamp, Week1, Height>,
|
||||
LazyVecFrom1<Month1, Timestamp, Month1, Height>,
|
||||
LazyVecFrom1<Month3, Timestamp, Month3, Height>,
|
||||
LazyVecFrom1<Month6, Timestamp, Month6, Height>,
|
||||
LazyVecFrom1<Year1, Timestamp, Year1, Height>,
|
||||
LazyVecFrom1<Year10, Timestamp, Year10, Height>,
|
||||
M::Stored<EagerVec<PcoVec<Halving, Timestamp>>>,
|
||||
M::Stored<EagerVec<PcoVec<Epoch, Timestamp>>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl Timestamps {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn forced_import_from_locals(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
minute10: &super::Minute10Vecs,
|
||||
minute30: &super::Minute30Vecs,
|
||||
hour1: &super::Hour1Vecs,
|
||||
hour4: &super::Hour4Vecs,
|
||||
hour12: &super::Hour12Vecs,
|
||||
day1: &super::Day1Vecs,
|
||||
day3: &super::Day3Vecs,
|
||||
week1: &super::Week1Vecs,
|
||||
month1: &super::Month1Vecs,
|
||||
month3: &super::Month3Vecs,
|
||||
month6: &super::Month6Vecs,
|
||||
year1: &super::Year1Vecs,
|
||||
year10: &super::Year10Vecs,
|
||||
) -> Result<Self> {
|
||||
let monotonic = EagerVec::forced_import(db, "timestamp_monotonic", version)?;
|
||||
|
||||
macro_rules! period {
|
||||
($field:ident) => {
|
||||
LazyVecFrom1::init(
|
||||
"timestamp",
|
||||
version,
|
||||
$field.first_height.read_only_boxed_clone(),
|
||||
|idx, _: Height| idx.to_timestamp(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
monotonic,
|
||||
resolutions: PerResolution {
|
||||
minute10: period!(minute10),
|
||||
minute30: period!(minute30),
|
||||
hour1: period!(hour1),
|
||||
hour4: period!(hour4),
|
||||
hour12: period!(hour12),
|
||||
day1: period!(day1),
|
||||
day3: period!(day3),
|
||||
week1: period!(week1),
|
||||
month1: period!(month1),
|
||||
month3: period!(month3),
|
||||
month6: period!(month6),
|
||||
year1: period!(year1),
|
||||
year10: period!(year10),
|
||||
halving: ImportableVec::forced_import(db, "timestamp", version)?,
|
||||
epoch: ImportableVec::forced_import(db, "timestamp", version)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_monotonic(
|
||||
&mut self,
|
||||
indexer: &brk_indexer::Indexer,
|
||||
starting_height: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let mut prev = None;
|
||||
self.monotonic.compute_transform(
|
||||
starting_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
|(h, timestamp, this)| {
|
||||
if prev.is_none()
|
||||
&& let Some(prev_h) = h.decremented()
|
||||
{
|
||||
prev.replace(this.collect_one(prev_h).unwrap());
|
||||
}
|
||||
let monotonic = prev.map_or(timestamp, |p| p.max(timestamp));
|
||||
prev.replace(monotonic);
|
||||
(h, monotonic)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_per_resolution(
|
||||
&mut self,
|
||||
indexer: &brk_indexer::Indexer,
|
||||
height: &super::HeightVecs,
|
||||
halving_vecs: &super::HalvingVecs,
|
||||
epoch_vecs: &super::EpochVecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let prev_height = starting_indexes.height.decremented().unwrap_or_default();
|
||||
self.resolutions.halving.compute_indirect_sequential(
|
||||
height.halving.collect_one(prev_height).unwrap_or_default(),
|
||||
&halving_vecs.first_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
exit,
|
||||
)?;
|
||||
self.resolutions.epoch.compute_indirect_sequential(
|
||||
height.epoch.collect_one(prev_height).unwrap_or_default(),
|
||||
&epoch_vecs.first_height,
|
||||
&indexer.vecs.blocks.timestamp,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// Puell Multiple: daily_subsidy_usd / sma_365d_subsidy_usd
|
||||
self.puell_multiple
|
||||
.bps
|
||||
@@ -199,8 +201,11 @@ impl Vecs {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use brk_error::Result;
|
||||
use brk_types::Version;
|
||||
|
||||
use super::Vecs;
|
||||
use super::{Vecs, thermometer::Thermometer};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{PerBlock, PercentPerBlock, RatioPerBlock, db_utils::{finalize_db, open_db}},
|
||||
@@ -38,6 +38,8 @@ impl Vecs {
|
||||
let seller_exhaustion =
|
||||
PerBlock::forced_import(&db, "seller_exhaustion", v, indexes)?;
|
||||
|
||||
let thermometer = Thermometer::forced_import(&db, v, indexes)?;
|
||||
|
||||
let this = Self {
|
||||
db,
|
||||
puell_multiple,
|
||||
@@ -50,6 +52,7 @@ impl Vecs {
|
||||
dormancy,
|
||||
stock_to_flow,
|
||||
seller_exhaustion,
|
||||
thermometer,
|
||||
};
|
||||
finalize_db(&this.db, &this)?;
|
||||
Ok(this)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod compute;
|
||||
mod gini;
|
||||
mod import;
|
||||
pub mod thermometer;
|
||||
mod vecs;
|
||||
|
||||
pub use vecs::Vecs;
|
||||
|
||||
254
crates/brk_computer/src/indicators/thermometer.rs
Normal file
254
crates/brk_computer/src/indicators/thermometer.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Height, Indexes, StoredI8, Version};
|
||||
use vecdb::{AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
cointime,
|
||||
distribution,
|
||||
indexes,
|
||||
internal::{PerBlock, Price, RatioPerBlockPercentiles},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct Thermometer<M: StorageMode = Rw> {
|
||||
pub pct0_5: Price<PerBlock<Cents, M>>,
|
||||
pub pct1: Price<PerBlock<Cents, M>>,
|
||||
pub pct2: Price<PerBlock<Cents, M>>,
|
||||
pub pct5: Price<PerBlock<Cents, M>>,
|
||||
pub pct95: Price<PerBlock<Cents, M>>,
|
||||
pub pct98: Price<PerBlock<Cents, M>>,
|
||||
pub pct99: Price<PerBlock<Cents, M>>,
|
||||
pub pct99_5: Price<PerBlock<Cents, M>>,
|
||||
pub zone: PerBlock<StoredI8, M>,
|
||||
pub score: PerBlock<StoredI8, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::new(2);
|
||||
|
||||
impl Thermometer {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
pct0_5: Price::forced_import(db, "thermometer_pct0_5", v, indexes)?,
|
||||
pct1: Price::forced_import(db, "thermometer_pct01", v, indexes)?,
|
||||
pct2: Price::forced_import(db, "thermometer_pct02", v, indexes)?,
|
||||
pct5: Price::forced_import(db, "thermometer_pct05", v, indexes)?,
|
||||
pct95: Price::forced_import(db, "thermometer_pct95", v, indexes)?,
|
||||
pct98: Price::forced_import(db, "thermometer_pct98", v, indexes)?,
|
||||
pct99: Price::forced_import(db, "thermometer_pct99", v, indexes)?,
|
||||
pct99_5: Price::forced_import(db, "thermometer_pct99_5", v, indexes)?,
|
||||
zone: PerBlock::forced_import(db, "thermometer_zone", v, indexes)?,
|
||||
score: PerBlock::forced_import(db, "thermometer_score", v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
distribution: &distribution::Vecs,
|
||||
cointime: &cointime::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let realized = &distribution.utxo_cohorts.all.metrics.realized;
|
||||
let ct = &cointime.prices;
|
||||
|
||||
let sth_realized = &distribution.utxo_cohorts.sth.metrics.realized;
|
||||
let lth_realized = &distribution.utxo_cohorts.lth.metrics.realized;
|
||||
|
||||
let models: [&RatioPerBlockPercentiles; 10] = [
|
||||
&realized.price_ratio_percentiles,
|
||||
&realized.investor.price.percentiles,
|
||||
&sth_realized.price_ratio_percentiles,
|
||||
&sth_realized.investor.price.percentiles,
|
||||
<h_realized.price_ratio_percentiles,
|
||||
<h_realized.investor.price.percentiles,
|
||||
&ct.vaulted.percentiles,
|
||||
&ct.active.percentiles,
|
||||
&ct.true_market_mean.percentiles,
|
||||
&ct.cointime.percentiles,
|
||||
];
|
||||
|
||||
macro_rules! sources {
|
||||
($pct:ident) => {
|
||||
models.each_ref().map(|m| &m.$pct.price.cents.height)
|
||||
};
|
||||
}
|
||||
|
||||
// Lower percentiles: max across all models (tightest lower bound)
|
||||
self.pct0_5.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct0_5), exit)?;
|
||||
self.pct1.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct1), exit)?;
|
||||
self.pct2.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct2), exit)?;
|
||||
self.pct5.cents.height.compute_max_of_others(starting_indexes.height, &sources!(pct5), exit)?;
|
||||
|
||||
// Upper percentiles: min across all models (tightest upper bound)
|
||||
self.pct95.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct95), exit)?;
|
||||
self.pct98.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct98), exit)?;
|
||||
self.pct99.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct99), exit)?;
|
||||
self.pct99_5.cents.height.compute_min_of_others(starting_indexes.height, &sources!(pct99_5), exit)?;
|
||||
|
||||
let spot = &prices.spot.cents.height;
|
||||
|
||||
// Zone: spot vs own envelope bands (-4 to +4)
|
||||
self.compute_zone(spot, starting_indexes, exit)?;
|
||||
|
||||
// Temperature: per-model band crossings (-40 to +40)
|
||||
self.compute_score(&models, spot, starting_indexes, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_zone(
|
||||
&mut self,
|
||||
spot: &EagerVec<PcoVec<Height, Cents>>,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let bands: [&_; 8] = [
|
||||
&self.pct0_5.cents.height,
|
||||
&self.pct1.cents.height,
|
||||
&self.pct2.cents.height,
|
||||
&self.pct5.cents.height,
|
||||
&self.pct95.cents.height,
|
||||
&self.pct98.cents.height,
|
||||
&self.pct99.cents.height,
|
||||
&self.pct99_5.cents.height,
|
||||
];
|
||||
|
||||
let dep_version: Version = bands.iter().map(|b| b.version()).sum::<Version>() + spot.version();
|
||||
|
||||
self.zone.height.validate_computed_version_or_reset(dep_version)?;
|
||||
self.zone.height.truncate_if_needed(starting_indexes.height)?;
|
||||
|
||||
self.zone.height.repeat_until_complete(exit, |vec| {
|
||||
let skip = vec.len();
|
||||
let source_end = bands.iter().map(|b| b.len()).min().unwrap().min(spot.len());
|
||||
let end = vec.batch_end(source_end);
|
||||
|
||||
if skip >= end {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let spot_batch = spot.collect_range_at(skip, end);
|
||||
let b: [Vec<Cents>; 8] = bands.each_ref().map(|v| v.collect_range_at(skip, end));
|
||||
|
||||
for j in 0..(end - skip) {
|
||||
let price = spot_batch[j];
|
||||
let mut score: i8 = 0;
|
||||
|
||||
if price < b[3][j] { score -= 1; }
|
||||
if price < b[2][j] { score -= 1; }
|
||||
if price < b[1][j] { score -= 1; }
|
||||
if price < b[0][j] { score -= 1; }
|
||||
if price > b[4][j] { score += 1; }
|
||||
if price > b[5][j] { score += 1; }
|
||||
if price > b[6][j] { score += 1; }
|
||||
if price > b[7][j] { score += 1; }
|
||||
|
||||
vec.push(StoredI8::new(score));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_score(
|
||||
&mut self,
|
||||
models: &[&RatioPerBlockPercentiles; 10],
|
||||
spot: &EagerVec<PcoVec<Height, Cents>>,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let dep_version: Version = models
|
||||
.iter()
|
||||
.map(|p| {
|
||||
p.pct0_5.price.cents.height.version()
|
||||
+ p.pct1.price.cents.height.version()
|
||||
+ p.pct2.price.cents.height.version()
|
||||
+ p.pct5.price.cents.height.version()
|
||||
+ p.pct95.price.cents.height.version()
|
||||
+ p.pct98.price.cents.height.version()
|
||||
+ p.pct99.price.cents.height.version()
|
||||
+ p.pct99_5.price.cents.height.version()
|
||||
})
|
||||
.sum::<Version>()
|
||||
+ spot.version();
|
||||
|
||||
self.score.height.validate_computed_version_or_reset(dep_version)?;
|
||||
self.score.height.truncate_if_needed(starting_indexes.height)?;
|
||||
|
||||
self.score.height.repeat_until_complete(exit, |vec| {
|
||||
let skip = vec.len();
|
||||
let source_end = models
|
||||
.iter()
|
||||
.flat_map(|p| {
|
||||
[
|
||||
p.pct0_5.price.cents.height.len(),
|
||||
p.pct1.price.cents.height.len(),
|
||||
p.pct2.price.cents.height.len(),
|
||||
p.pct5.price.cents.height.len(),
|
||||
p.pct95.price.cents.height.len(),
|
||||
p.pct98.price.cents.height.len(),
|
||||
p.pct99.price.cents.height.len(),
|
||||
p.pct99_5.price.cents.height.len(),
|
||||
]
|
||||
})
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(spot.len());
|
||||
let end = vec.batch_end(source_end);
|
||||
|
||||
if skip >= end {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let spot_batch = spot.collect_range_at(skip, end);
|
||||
|
||||
let bands: Vec<[Vec<Cents>; 8]> = models
|
||||
.iter()
|
||||
.map(|p| {
|
||||
[
|
||||
p.pct0_5.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct1.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct2.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct5.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct95.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct98.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct99.price.cents.height.collect_range_at(skip, end),
|
||||
p.pct99_5.price.cents.height.collect_range_at(skip, end),
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
|
||||
for j in 0..(end - skip) {
|
||||
let price = spot_batch[j];
|
||||
let mut total: i8 = 0;
|
||||
|
||||
for model in &bands {
|
||||
if price < model[3][j] { total -= 1; }
|
||||
if price < model[2][j] { total -= 1; }
|
||||
if price < model[1][j] { total -= 1; }
|
||||
if price < model[0][j] { total -= 1; }
|
||||
if price > model[4][j] { total += 1; }
|
||||
if price > model[5][j] { total += 1; }
|
||||
if price > model[6][j] { total += 1; }
|
||||
if price > model[7][j] { total += 1; }
|
||||
}
|
||||
|
||||
vec.push(StoredI8::new(total));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
use super::thermometer::Thermometer;
|
||||
use crate::internal::{PerBlock, PercentPerBlock, RatioPerBlock};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -24,4 +25,5 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub dormancy: DormancyVecs<M>,
|
||||
pub stock_to_flow: PerBlock<StoredF32, M>,
|
||||
pub seller_exhaustion: PerBlock<StoredF32, M>,
|
||||
pub thermometer: Thermometer<M>,
|
||||
}
|
||||
|
||||
@@ -15,13 +15,18 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
self.spent
|
||||
.compute(indexer, starting_indexes, exit)?;
|
||||
self.count
|
||||
.compute(indexer, indexes, blocks, starting_indexes, exit)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,20 +71,20 @@ impl ExpandingPercentiles {
|
||||
self.tree.add(Self::to_bucket(value), &1);
|
||||
}
|
||||
|
||||
/// Compute 6 percentiles in one call via kth. O(6 × log N) but with
|
||||
/// shared tree traversal across all 6 targets for better cache locality.
|
||||
/// Compute 8 percentiles in one call via kth. O(8 × log N) but with
|
||||
/// shared tree traversal across all 8 targets for better cache locality.
|
||||
/// Quantiles q must be sorted ascending in (0, 1). Output is in BPS.
|
||||
pub fn quantiles(&self, qs: &[f64; 6], out: &mut [u32; 6]) {
|
||||
pub fn quantiles(&self, qs: &[f64; 8], out: &mut [u32; 8]) {
|
||||
if self.count == 0 {
|
||||
out.iter_mut().for_each(|o| *o = 0);
|
||||
return;
|
||||
}
|
||||
let mut targets = [0u32; 6];
|
||||
let mut targets = [0u32; 8];
|
||||
for (i, &q) in qs.iter().enumerate() {
|
||||
let k = ((q * self.count as f64).ceil() as u32).clamp(1, self.count);
|
||||
targets[i] = k - 1; // 0-indexed
|
||||
}
|
||||
let mut buckets = [0usize; 6];
|
||||
let mut buckets = [0usize; 8];
|
||||
self.tree.kth(&targets, &|n: &u32| *n, &mut buckets);
|
||||
for (i, bucket) in buckets.iter().enumerate() {
|
||||
out[i] = *bucket as u32 * BUCKET_BPS as u32;
|
||||
@@ -97,8 +97,8 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn quantile(ep: &ExpandingPercentiles, q: f64) -> u32 {
|
||||
let mut out = [0u32; 6];
|
||||
ep.quantiles(&[q, q, q, q, q, q], &mut out);
|
||||
let mut out = [0u32; 8];
|
||||
ep.quantiles(&[q, q, q, q, q, q, q, q], &mut out);
|
||||
out[0]
|
||||
}
|
||||
|
||||
|
||||
@@ -22,18 +22,20 @@ pub struct RatioBand<M: StorageMode = Rw> {
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct RatioPerBlockPercentiles<M: StorageMode = Rw> {
|
||||
pub pct99_5: RatioBand<M>,
|
||||
pub pct99: RatioBand<M>,
|
||||
pub pct98: RatioBand<M>,
|
||||
pub pct95: RatioBand<M>,
|
||||
pub pct5: RatioBand<M>,
|
||||
pub pct2: RatioBand<M>,
|
||||
pub pct1: RatioBand<M>,
|
||||
pub pct0_5: RatioBand<M>,
|
||||
|
||||
#[traversable(skip)]
|
||||
expanding_pct: ExpandingPercentiles,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::new(5);
|
||||
const VERSION: Version = Version::new(6);
|
||||
|
||||
/// First height included in ratio percentile computation (first halving).
|
||||
/// Earlier blocks lack meaningful market data and pollute the distribution.
|
||||
@@ -70,12 +72,14 @@ impl RatioPerBlockPercentiles {
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
pct99_5: import_band!("pct99_5"),
|
||||
pct99: import_band!("pct99"),
|
||||
pct98: import_band!("pct98"),
|
||||
pct95: import_band!("pct95"),
|
||||
pct5: import_band!("pct5"),
|
||||
pct2: import_band!("pct2"),
|
||||
pct1: import_band!("pct1"),
|
||||
pct0_5: import_band!("pct0_5"),
|
||||
expanding_pct: ExpandingPercentiles::default(),
|
||||
})
|
||||
}
|
||||
@@ -114,16 +118,18 @@ impl RatioPerBlockPercentiles {
|
||||
}
|
||||
|
||||
let new_ratios = ratio_source.collect_range_at(start, ratio_len);
|
||||
let mut pct_vecs: [&mut EagerVec<PcoVec<Height, BasisPoints32>>; 6] = [
|
||||
let mut pct_vecs: [&mut EagerVec<PcoVec<Height, BasisPoints32>>; 8] = [
|
||||
&mut self.pct0_5.ratio.bps.height,
|
||||
&mut self.pct1.ratio.bps.height,
|
||||
&mut self.pct2.ratio.bps.height,
|
||||
&mut self.pct5.ratio.bps.height,
|
||||
&mut self.pct95.ratio.bps.height,
|
||||
&mut self.pct98.ratio.bps.height,
|
||||
&mut self.pct99.ratio.bps.height,
|
||||
&mut self.pct99_5.ratio.bps.height,
|
||||
];
|
||||
const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99];
|
||||
let mut out = [0u32; 6];
|
||||
const PCTS: [f64; 8] = [0.005, 0.01, 0.02, 0.05, 0.95, 0.98, 0.99, 0.995];
|
||||
let mut out = [0u32; 8];
|
||||
|
||||
for vec in pct_vecs.iter_mut() {
|
||||
vec.truncate_if_needed_at(start)?;
|
||||
@@ -160,12 +166,14 @@ impl RatioPerBlockPercentiles {
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(pct99_5);
|
||||
compute_band!(pct99);
|
||||
compute_band!(pct98);
|
||||
compute_band!(pct95);
|
||||
compute_band!(pct5);
|
||||
compute_band!(pct2);
|
||||
compute_band!(pct1);
|
||||
compute_band!(pct0_5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -174,12 +182,14 @@ impl RatioPerBlockPercentiles {
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, BasisPoints32>>> {
|
||||
[
|
||||
&mut self.pct0_5.ratio.bps.height,
|
||||
&mut self.pct1.ratio.bps.height,
|
||||
&mut self.pct2.ratio.bps.height,
|
||||
&mut self.pct5.ratio.bps.height,
|
||||
&mut self.pct95.ratio.bps.height,
|
||||
&mut self.pct98.ratio.bps.height,
|
||||
&mut self.pct99.ratio.bps.height,
|
||||
&mut self.pct99_5.ratio.bps.height,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use brk_traversable::Traversable;
|
||||
|
||||
use crate::market::lookback::ByLookbackPeriod;
|
||||
|
||||
/// DCA period identifiers with their day counts
|
||||
pub const DCA_PERIOD_DAYS: ByDcaPeriod<u32> = ByDcaPeriod {
|
||||
_1w: 7,
|
||||
@@ -173,6 +175,26 @@ impl<T> ByDcaPeriod<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByDcaPeriod<&T> {
|
||||
/// Get the DCA-matching subset from lookback (excludes 24h)
|
||||
pub(crate) fn from_lookback(lookback: &ByLookbackPeriod<T>) -> ByDcaPeriod<&T> {
|
||||
ByDcaPeriod {
|
||||
_1w: &lookback._1w,
|
||||
_1m: &lookback._1m,
|
||||
_3m: &lookback._3m,
|
||||
_6m: &lookback._6m,
|
||||
_1y: &lookback._1y,
|
||||
_2y: &lookback._2y,
|
||||
_3y: &lookback._3y,
|
||||
_4y: &lookback._4y,
|
||||
_5y: &lookback._5y,
|
||||
_6y: &lookback._6y,
|
||||
_8y: &lookback._8y,
|
||||
_10y: &lookback._10y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic wrapper for DCA CAGR data (periods >= 2 years)
|
||||
#[derive(Clone, Default, Traversable)]
|
||||
pub struct ByDcaCagr<T> {
|
||||
@@ -2,8 +2,8 @@ use brk_error::Result;
|
||||
use brk_types::{BasisPointsSigned32, Bitcoin, Cents, Date, Day1, Dollars, Indexes, Sats};
|
||||
use vecdb::{AnyVec, Exit, ReadableOptionVec, ReadableVec, VecIndex};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market::lookback, prices};
|
||||
use super::{ByDcaPeriod, Vecs};
|
||||
use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market, prices};
|
||||
|
||||
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
|
||||
|
||||
@@ -13,10 +13,12 @@ impl Vecs {
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
lookback: &lookback::Vecs,
|
||||
lookback: &market::lookback::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
let h2d = &indexes.height.day1;
|
||||
let close = &prices.split.close.usd.day1;
|
||||
|
||||
@@ -71,17 +73,12 @@ impl Vecs {
|
||||
self.period.cost_basis.zip_mut_with_days(&self.period.stack)
|
||||
{
|
||||
let days = days as usize;
|
||||
let start = average_price.cents.height.len().min(starting_height);
|
||||
let stack_data = stack
|
||||
.sats
|
||||
.height
|
||||
.collect_range_at(start, stack.sats.height.len());
|
||||
average_price.cents.height.compute_transform(
|
||||
average_price.cents.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
h2d,
|
||||
|(h, di, _)| {
|
||||
&stack.sats.height,
|
||||
|(h, di, stack_sats, ..)| {
|
||||
let di_usize = di.to_usize();
|
||||
let stack_sats = stack_data[h.to_usize() - start];
|
||||
let avg = if di_usize > first_price_di {
|
||||
let num_days = days.min(di_usize + 1 - first_price_di);
|
||||
Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats))
|
||||
@@ -125,21 +122,16 @@ impl Vecs {
|
||||
}
|
||||
|
||||
// Lump sum by period - stack
|
||||
let lookback_dca = lookback.price_past.as_dca_period();
|
||||
let lookback_dca = ByDcaPeriod::from_lookback(&lookback.price_past);
|
||||
for (stack, lookback_price, days) in
|
||||
self.period.lump_sum_stack.zip_mut_with_days(&lookback_dca)
|
||||
{
|
||||
let total_invested = DCA_AMOUNT * days as usize;
|
||||
let ls_start = stack.sats.height.len().min(starting_height);
|
||||
let lookback_data = lookback_price
|
||||
.cents
|
||||
.height
|
||||
.collect_range_at(ls_start, lookback_price.cents.height.len());
|
||||
stack.sats.height.compute_transform(
|
||||
stack.sats.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
h2d,
|
||||
|(h, _di, _)| {
|
||||
let lp = lookback_data[h.to_usize() - ls_start];
|
||||
&lookback_price.cents.height,
|
||||
|(h, _di, lp, ..)| {
|
||||
let sats = if lp == Cents::ZERO {
|
||||
Sats::ZERO
|
||||
} else {
|
||||
@@ -238,20 +230,15 @@ impl Vecs {
|
||||
.zip(start_days)
|
||||
{
|
||||
let from_usize = from.to_usize();
|
||||
let cls_start = average_price.cents.height.len().min(starting_height);
|
||||
let stack_data = stack
|
||||
.sats
|
||||
.height
|
||||
.collect_range_at(cls_start, stack.sats.height.len());
|
||||
average_price.cents.height.compute_transform(
|
||||
average_price.cents.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
h2d,
|
||||
|(h, di, _)| {
|
||||
&stack.sats.height,
|
||||
|(h, di, stack_sats, ..)| {
|
||||
let di_usize = di.to_usize();
|
||||
if di_usize < from_usize {
|
||||
return (h, Cents::ZERO);
|
||||
}
|
||||
let stack_sats = stack_data[h.to_usize() - cls_start];
|
||||
let num_days = di_usize + 1 - from_usize;
|
||||
let avg = Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats));
|
||||
(h, avg)
|
||||
@@ -275,6 +262,11 @@ impl Vecs {
|
||||
)?;
|
||||
}
|
||||
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,50 @@
|
||||
use std::path::Path;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::Version;
|
||||
use vecdb::{Database, ImportableVec};
|
||||
use vecdb::ImportableVec;
|
||||
|
||||
use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod, Vecs};
|
||||
use super::vecs::{ClassVecs, PeriodVecs};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{AmountPerBlock, PercentPerBlock, Price},
|
||||
internal::{
|
||||
db_utils::{finalize_db, open_db},
|
||||
AmountPerBlock, PercentPerBlock, Price,
|
||||
},
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
parent_path: &Path,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let db = open_db(parent_path, super::DB_NAME, 50_000)?;
|
||||
let version = parent_version;
|
||||
let stack = ByDcaPeriod::try_new(|name, _days| {
|
||||
AmountPerBlock::forced_import(db, &format!("dca_stack_{name}"), version, indexes)
|
||||
AmountPerBlock::forced_import(&db, &format!("dca_stack_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let cost_basis = ByDcaPeriod::try_new(|name, _days| {
|
||||
Price::forced_import(db, &format!("dca_cost_basis_{name}"), version, indexes)
|
||||
Price::forced_import(&db, &format!("dca_cost_basis_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let r#return = ByDcaPeriod::try_new(|name, _days| {
|
||||
PercentPerBlock::forced_import(db, &format!("dca_return_{name}"), version, indexes)
|
||||
PercentPerBlock::forced_import(&db, &format!("dca_return_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let cagr = ByDcaCagr::try_new(|name, _days| {
|
||||
PercentPerBlock::forced_import(db, &format!("dca_cagr_{name}"), version, indexes)
|
||||
PercentPerBlock::forced_import(&db, &format!("dca_cagr_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let lump_sum_stack = ByDcaPeriod::try_new(|name, _days| {
|
||||
AmountPerBlock::forced_import(db, &format!("lump_sum_stack_{name}"), version, indexes)
|
||||
AmountPerBlock::forced_import(&db, &format!("lump_sum_stack_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let lump_sum_return = ByDcaPeriod::try_new(|name, _days| {
|
||||
PercentPerBlock::forced_import(
|
||||
db,
|
||||
&db,
|
||||
&format!("lump_sum_return_{name}"),
|
||||
version,
|
||||
indexes,
|
||||
@@ -45,19 +52,19 @@ impl Vecs {
|
||||
})?;
|
||||
|
||||
let class_stack = ByDcaClass::try_new(|name, _year, _day1| {
|
||||
AmountPerBlock::forced_import(db, &format!("dca_stack_{name}"), version, indexes)
|
||||
AmountPerBlock::forced_import(&db, &format!("dca_stack_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let class_cost_basis = ByDcaClass::try_new(|name, _year, _day1| {
|
||||
Price::forced_import(db, &format!("dca_cost_basis_{name}"), version, indexes)
|
||||
Price::forced_import(&db, &format!("dca_cost_basis_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
let class_return = ByDcaClass::try_new(|name, _year, _day1| {
|
||||
PercentPerBlock::forced_import(db, &format!("dca_return_{name}"), version, indexes)
|
||||
PercentPerBlock::forced_import(&db, &format!("dca_return_{name}"), version, indexes)
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
sats_per_day: ImportableVec::forced_import(db, "dca_sats_per_day", version)?,
|
||||
let this = Self {
|
||||
sats_per_day: ImportableVec::forced_import(&db, "dca_sats_per_day", version)?,
|
||||
period: PeriodVecs {
|
||||
stack,
|
||||
cost_basis,
|
||||
@@ -71,6 +78,9 @@ impl Vecs {
|
||||
cost_basis: class_cost_basis,
|
||||
r#return: class_return,
|
||||
},
|
||||
})
|
||||
db,
|
||||
};
|
||||
finalize_db(&this.db, &this)?;
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,5 @@ mod vecs;
|
||||
pub use by_class::*;
|
||||
pub use by_period::*;
|
||||
pub use vecs::Vecs;
|
||||
|
||||
pub const DB_NAME: &str = "investing";
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPointsSigned32, Cents, Height, Sats};
|
||||
use vecdb::{EagerVec, PcoVec, Rw, StorageMode};
|
||||
use vecdb::{Database, EagerVec, PcoVec, Rw, StorageMode};
|
||||
|
||||
use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod};
|
||||
use crate::internal::{AmountPerBlock, PerBlock, PercentPerBlock, Price};
|
||||
@@ -24,6 +24,8 @@ pub struct ClassVecs<M: StorageMode = Rw> {
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub(crate) db: Database,
|
||||
pub sats_per_day: M::Stored<EagerVec<PcoVec<Height, Sats>>>,
|
||||
pub period: PeriodVecs<M>,
|
||||
pub class: ClassVecs<M>,
|
||||
@@ -18,6 +18,7 @@ mod indicators;
|
||||
pub mod indexes;
|
||||
mod inputs;
|
||||
mod internal;
|
||||
mod investing;
|
||||
mod market;
|
||||
mod mining;
|
||||
mod outputs;
|
||||
@@ -39,6 +40,7 @@ pub struct Computer<M: StorageMode = Rw> {
|
||||
pub constants: Box<constants::Vecs>,
|
||||
pub indexes: Box<indexes::Vecs<M>>,
|
||||
pub indicators: Box<indicators::Vecs<M>>,
|
||||
pub investing: Box<investing::Vecs<M>>,
|
||||
pub market: Box<market::Vecs<M>>,
|
||||
pub pools: Box<pools::Vecs<M>>,
|
||||
pub prices: Box<prices::Vecs<M>>,
|
||||
@@ -180,8 +182,8 @@ impl Computer {
|
||||
|
||||
// Market, indicators, and distribution are independent; import in parallel.
|
||||
// Supply depends on distribution so it runs after.
|
||||
let (distribution, market, indicators) =
|
||||
timed("Imported distribution/market/indicators", || {
|
||||
let (distribution, market, indicators, investing) =
|
||||
timed("Imported distribution/market/indicators/investing", || {
|
||||
thread::scope(|s| -> Result<_> {
|
||||
let market_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(market::Vecs::forced_import(
|
||||
@@ -199,6 +201,14 @@ impl Computer {
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let investing_handle = big_thread().spawn_scoped(s, || -> Result<_> {
|
||||
Ok(Box::new(investing::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let distribution = Box::new(distribution::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
@@ -208,7 +218,8 @@ impl Computer {
|
||||
|
||||
let market = market_handle.join().unwrap()?;
|
||||
let indicators = indicators_handle.join().unwrap()?;
|
||||
Ok((distribution, market, indicators))
|
||||
let investing = investing_handle.join().unwrap()?;
|
||||
Ok((distribution, market, indicators, investing))
|
||||
})
|
||||
})?;
|
||||
|
||||
@@ -232,6 +243,7 @@ impl Computer {
|
||||
scripts,
|
||||
constants,
|
||||
indicators,
|
||||
investing,
|
||||
market,
|
||||
distribution,
|
||||
supply,
|
||||
@@ -260,6 +272,7 @@ impl Computer {
|
||||
cointime::DB_NAME,
|
||||
indicators::DB_NAME,
|
||||
indexes::DB_NAME,
|
||||
investing::DB_NAME,
|
||||
market::DB_NAME,
|
||||
pools::DB_NAME,
|
||||
prices::DB_NAME,
|
||||
@@ -305,7 +318,7 @@ impl Computer {
|
||||
|
||||
let mut starting_indexes = timed("Computed indexes", || {
|
||||
self.indexes
|
||||
.compute(indexer, &mut self.blocks, starting_indexes, exit)
|
||||
.compute(indexer, starting_indexes, exit)
|
||||
})?;
|
||||
|
||||
thread::scope(|scope| -> Result<()> {
|
||||
@@ -339,8 +352,8 @@ impl Computer {
|
||||
let market = scope.spawn(|| {
|
||||
timed("Computed market", || {
|
||||
self.market.compute(
|
||||
&self.indexes,
|
||||
&self.prices,
|
||||
&self.indexes,
|
||||
&self.blocks,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
@@ -422,6 +435,19 @@ impl Computer {
|
||||
})
|
||||
});
|
||||
|
||||
let investing = scope.spawn(|| {
|
||||
timed("Computed investing", || {
|
||||
self.investing.compute(
|
||||
&self.indexes,
|
||||
&self.prices,
|
||||
&self.blocks,
|
||||
&self.market.lookback,
|
||||
&starting_indexes_clone,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
timed("Computed distribution", || {
|
||||
self.distribution.compute(
|
||||
indexer,
|
||||
@@ -437,6 +463,7 @@ impl Computer {
|
||||
})?;
|
||||
|
||||
pools.join().unwrap()?;
|
||||
investing.join().unwrap()?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
@@ -485,6 +512,14 @@ impl Computer {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
self.indicators.thermometer.compute(
|
||||
&self.distribution,
|
||||
&self.cointime,
|
||||
&self.prices,
|
||||
&starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
info!("Total compute time: {:?}", compute_start.elapsed());
|
||||
Ok(())
|
||||
}
|
||||
@@ -517,7 +552,7 @@ macro_rules! impl_iter_named {
|
||||
}
|
||||
|
||||
impl_iter_named!(blocks, mining, transactions, scripts, positions, cointime,
|
||||
constants, indicators, indexes, market, pools, prices, distribution, supply, inputs, outputs);
|
||||
constants, indicators, indexes, investing, market, pools, prices, distribution, supply, inputs, outputs);
|
||||
|
||||
fn timed<T>(label: &str, f: impl FnOnce() -> T) -> T {
|
||||
let start = Instant::now();
|
||||
|
||||
@@ -3,13 +3,13 @@ use brk_types::{Indexes, StoredF32, Timestamp};
|
||||
use vecdb::{Exit, ReadableVec, VecIndex};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, prices};
|
||||
use crate::{indexes, prices};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -24,7 +24,7 @@ impl Vecs {
|
||||
starting_indexes.height,
|
||||
&self.high.cents.height,
|
||||
&prices.spot.cents.height,
|
||||
&blocks.time.timestamp_monotonic,
|
||||
&indexes.timestamp.monotonic,
|
||||
|(i, ath, price, ts, slf)| {
|
||||
if ath_ts.is_none() {
|
||||
let idx = i.to_usize();
|
||||
|
||||
@@ -9,17 +9,19 @@ use super::Vecs;
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// Phase 1: Independent sub-modules in parallel
|
||||
let (r1, r2) = rayon::join(
|
||||
|| {
|
||||
rayon::join(
|
||||
|| self.ath.compute(prices, blocks, starting_indexes, exit),
|
||||
|| self.ath.compute(prices, indexes, starting_indexes, exit),
|
||||
|| self.lookback.compute(blocks, prices, starting_indexes, exit),
|
||||
)
|
||||
},
|
||||
@@ -39,24 +41,8 @@ impl Vecs {
|
||||
r2.1?;
|
||||
|
||||
// Phase 2: Depend on lookback
|
||||
let (r3, r4) = rayon::join(
|
||||
|| {
|
||||
self.returns
|
||||
.compute(prices, blocks, &self.lookback, starting_indexes, exit)
|
||||
},
|
||||
|| {
|
||||
self.dca.compute(
|
||||
indexes,
|
||||
prices,
|
||||
blocks,
|
||||
&self.lookback,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)
|
||||
},
|
||||
);
|
||||
r3?;
|
||||
r4?;
|
||||
self.returns
|
||||
.compute(prices, blocks, &self.lookback, starting_indexes, exit)?;
|
||||
|
||||
// Phase 3: Depends on returns, moving_average
|
||||
self.technical.compute(
|
||||
@@ -68,8 +54,11 @@ impl Vecs {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
AthVecs, DcaVecs, TechnicalVecs, LookbackVecs, MovingAverageVecs, RangeVecs, ReturnsVecs,
|
||||
AthVecs, TechnicalVecs, LookbackVecs, MovingAverageVecs, RangeVecs, ReturnsVecs,
|
||||
Vecs, VolatilityVecs,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,6 @@ impl Vecs {
|
||||
let volatility = VolatilityVecs::forced_import(version, &returns)?;
|
||||
let range = RangeVecs::forced_import(&db, version, indexes)?;
|
||||
let moving_average = MovingAverageVecs::forced_import(&db, version, indexes)?;
|
||||
let dca = DcaVecs::forced_import(&db, version, indexes)?;
|
||||
let technical = TechnicalVecs::forced_import(&db, version, indexes)?;
|
||||
|
||||
let this = Self {
|
||||
@@ -39,7 +38,6 @@ impl Vecs {
|
||||
volatility,
|
||||
range,
|
||||
moving_average,
|
||||
dca,
|
||||
technical,
|
||||
};
|
||||
finalize_db(&this.db, &this)?;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use brk_traversable::Traversable;
|
||||
|
||||
use crate::market::dca::ByDcaPeriod;
|
||||
|
||||
/// Lookback period days (includes 24h, unlike DCA)
|
||||
pub const LOOKBACK_PERIOD_DAYS: ByLookbackPeriod<u32> = ByLookbackPeriod {
|
||||
_24h: 1,
|
||||
@@ -117,22 +115,4 @@ impl<T> ByLookbackPeriod<T> {
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
/// Get the DCA-matching subset (excludes 24h)
|
||||
pub(crate) fn as_dca_period(&self) -> ByDcaPeriod<&T> {
|
||||
ByDcaPeriod {
|
||||
_1w: &self._1w,
|
||||
_1m: &self._1m,
|
||||
_3m: &self._3m,
|
||||
_6m: &self._6m,
|
||||
_1y: &self._1y,
|
||||
_2y: &self._2y,
|
||||
_3y: &self._3y,
|
||||
_4y: &self._4y,
|
||||
_5y: &self._5y,
|
||||
_6y: &self._6y,
|
||||
_8y: &self._8y,
|
||||
_10y: &self._10y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod ath;
|
||||
mod compute;
|
||||
pub mod dca;
|
||||
mod import;
|
||||
pub mod technical;
|
||||
pub mod lookback;
|
||||
@@ -13,7 +12,6 @@ use brk_traversable::Traversable;
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
pub use ath::Vecs as AthVecs;
|
||||
pub use dca::Vecs as DcaVecs;
|
||||
pub use technical::Vecs as TechnicalVecs;
|
||||
pub use lookback::Vecs as LookbackVecs;
|
||||
pub use moving_average::Vecs as MovingAverageVecs;
|
||||
@@ -32,6 +30,5 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub volatility: VolatilityVecs,
|
||||
pub range: RangeVecs<M>,
|
||||
pub moving_average: MovingAverageVecs<M>,
|
||||
pub dca: DcaVecs<M>,
|
||||
pub technical: TechnicalVecs<M>,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use brk_types::{BasisPointsSigned32, Dollars, Indexes};
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, internal::RatioDiffDollarsBps32, market::lookback, prices};
|
||||
use crate::{blocks, internal::RatioDiffDollarsBps32, investing::ByDcaPeriod, market::lookback, prices};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
@@ -29,7 +29,7 @@ impl Vecs {
|
||||
}
|
||||
|
||||
// CAGR computed from returns at height level (2y+ periods only)
|
||||
let price_return_dca = self.periods.as_dca_period();
|
||||
let price_return_dca = ByDcaPeriod::from_lookback(&self.periods);
|
||||
for (cagr, returns, days) in self.cagr.zip_mut_with_period(&price_return_dca) {
|
||||
let years = days as f64 / 365.0;
|
||||
cagr.bps.height.compute_transform(
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::Vecs;
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{StdDevPerBlock, PercentPerBlock, Windows},
|
||||
market::dca::ByDcaCagr,
|
||||
investing::ByDcaCagr,
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
|
||||
@@ -4,7 +4,8 @@ use vecdb::{Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
internal::{PercentPerBlock, StdDevPerBlock, Windows},
|
||||
market::{dca::ByDcaCagr, lookback::ByLookbackPeriod},
|
||||
investing::ByDcaCagr,
|
||||
market::lookback::ByLookbackPeriod,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
|
||||
@@ -18,6 +18,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// Block rewards (coinbase, subsidy, fee_dominance, etc.)
|
||||
self.rewards.compute(
|
||||
indexer,
|
||||
@@ -39,8 +41,11 @@ impl Vecs {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
self.count.compute(
|
||||
indexer,
|
||||
indexes,
|
||||
@@ -27,10 +29,13 @@ impl Vecs {
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
let _lock = self
|
||||
let lock = self
|
||||
.spent
|
||||
.compute(indexer, inputs, starting_indexes, exit)?;
|
||||
self.db.compact()?;
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = lock;
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ use crate::inputs;
|
||||
const HEIGHT_BATCH: u32 = 10_000;
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute<'a>(
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
inputs: &inputs::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &'a Exit,
|
||||
) -> Result<ExitGuard<'a>> {
|
||||
exit: &Exit,
|
||||
) -> Result<ExitGuard> {
|
||||
let target_height = indexer.vecs.blocks.blockhash.len();
|
||||
if target_height == 0 {
|
||||
return Ok(exit.lock());
|
||||
|
||||
@@ -86,6 +86,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
self.compute_pool(indexer, indexes, starting_indexes, exit)?;
|
||||
|
||||
self.major.par_iter_mut().try_for_each(|(_, vecs)| {
|
||||
@@ -103,8 +105,11 @@ impl Vecs {
|
||||
vecs.compute(starting_indexes, &self.pool, blocks, exit)
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,14 @@ impl Vecs {
|
||||
reader: &Reader,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
self.compute_(indexer, starting_indexes, reader, exit)?;
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
self.compute_prices(indexer, starting_indexes, exit)?;
|
||||
self.split.open.cents.compute_first(
|
||||
starting_indexes,
|
||||
@@ -47,8 +49,11 @@ impl Vecs {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db().compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub const DB_NAME: &str = "prices";
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub(crate) db: Database,
|
||||
pub db: Database,
|
||||
|
||||
pub split: SplitByUnit<M>,
|
||||
pub ohlc: OhlcByUnit<M>,
|
||||
@@ -183,7 +183,4 @@ impl Vecs {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn db(&self) -> &Database {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,18 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
self.count.compute(indexer, starting_indexes, exit)?;
|
||||
|
||||
self.value
|
||||
.compute(indexer, prices, starting_indexes, exit)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
// 1. Compute burned/unspendable supply
|
||||
self.burned.compute(
|
||||
scripts,
|
||||
@@ -76,8 +78,11 @@ impl Vecs {
|
||||
)?;
|
||||
}
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ impl Vecs {
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
let (r1, (r2, r3)) = rayon::join(
|
||||
|| {
|
||||
self.count
|
||||
@@ -57,8 +59,11 @@ impl Vecs {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.db.compact()?;
|
||||
let exit = exit.clone();
|
||||
self.db.run_bg(move |db| {
|
||||
let _lock = exit.lock();
|
||||
db.compact()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use brk_error::Result;
|
||||
use brk_iterator::Blocks;
|
||||
use brk_rpc::Client;
|
||||
use brk_types::Height;
|
||||
use fjall::PersistMode;
|
||||
use tracing::{debug, info};
|
||||
use vecdb::{Exit, ReadOnlyClone, ReadableVec, Ro, Rw, StorageMode};
|
||||
mod constants;
|
||||
@@ -107,6 +108,8 @@ impl Indexer {
|
||||
exit: &Exit,
|
||||
check_collisions: bool,
|
||||
) -> Result<Indexes> {
|
||||
self.vecs.db.sync_bg_tasks()?;
|
||||
|
||||
debug!("Starting indexing...");
|
||||
|
||||
let last_blockhash = self.vecs.blocks.blockhash.collect_last();
|
||||
@@ -248,11 +251,32 @@ impl Indexer {
|
||||
|
||||
drop(readers);
|
||||
|
||||
if !is_export_height(indexes.height) {
|
||||
export(stores, vecs, indexes.height)?;
|
||||
}
|
||||
let lock = exit.lock();
|
||||
let tasks = self.stores.take_all_pending_ingests(indexes.height)?;
|
||||
self.vecs.stamped_write(indexes.height)?;
|
||||
let fjall_db = self.stores.db.clone();
|
||||
|
||||
self.vecs.compact()?;
|
||||
self.vecs.db.run_bg(move |db| {
|
||||
let _lock = lock;
|
||||
|
||||
if !tasks.is_empty() {
|
||||
let i = Instant::now();
|
||||
for task in tasks {
|
||||
task().map_err(vecdb::RawDBError::other)?;
|
||||
}
|
||||
info!("Stores committed in {:?}", i.elapsed());
|
||||
|
||||
let i = Instant::now();
|
||||
fjall_db
|
||||
.persist(PersistMode::SyncData)
|
||||
.map_err(vecdb::RawDBError::other)?;
|
||||
info!("Stores persisted in {:?}", i.elapsed());
|
||||
}
|
||||
|
||||
db.flush()?;
|
||||
db.compact()?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Ok(starting_indexes)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use brk_cohort::ByAddrType;
|
||||
use brk_error::Result;
|
||||
use brk_store::{AnyStore, Kind, Mode, Store};
|
||||
use brk_types::{
|
||||
AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, BlockHashPrefix, Height, OutPoint,
|
||||
OutputType, StoredString, TxIndex, TxOutIndex, TxidPrefix, TypeIndex, Unit, Version, Vout,
|
||||
AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, BlockHashPrefix, Height, OutPoint, OutputType,
|
||||
StoredString, TxIndex, TxOutIndex, TxidPrefix, TypeIndex, Unit, Version, Vout,
|
||||
};
|
||||
use fjall::{Database, PersistMode};
|
||||
use rayon::prelude::*;
|
||||
@@ -24,8 +24,7 @@ pub struct Stores {
|
||||
|
||||
pub addr_type_to_addr_hash_to_addr_index: ByAddrType<Store<AddrHash, TypeIndex>>,
|
||||
pub addr_type_to_addr_index_and_tx_index: ByAddrType<Store<AddrIndexTxIndex, Unit>>,
|
||||
pub addr_type_to_addr_index_and_unspent_outpoint:
|
||||
ByAddrType<Store<AddrIndexOutPoint, Unit>>,
|
||||
pub addr_type_to_addr_index_and_unspent_outpoint: ByAddrType<Store<AddrIndexOutPoint, Unit>>,
|
||||
pub blockhash_prefix_to_height: Store<BlockHashPrefix, Height>,
|
||||
pub height_to_coinbase_tag: Store<Height, StoredString>,
|
||||
pub txid_prefix_to_tx_index: Store<TxidPrefix, TxIndex>,
|
||||
@@ -194,6 +193,39 @@ impl Stores {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Takes all pending puts/dels from every store and returns closures
|
||||
/// that can ingest them on a background thread.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn take_all_pending_ingests(
|
||||
&mut self,
|
||||
height: Height,
|
||||
) -> Result<Vec<Box<dyn FnOnce() -> Result<()> + Send>>> {
|
||||
let h = height;
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
macro_rules! take {
|
||||
($store:expr) => {
|
||||
tasks.extend($store.take_pending_ingest(h)?);
|
||||
};
|
||||
}
|
||||
|
||||
take!(self.blockhash_prefix_to_height);
|
||||
take!(self.height_to_coinbase_tag);
|
||||
take!(self.txid_prefix_to_tx_index);
|
||||
|
||||
for store in self.addr_type_to_addr_hash_to_addr_index.values_mut() {
|
||||
take!(store);
|
||||
}
|
||||
for store in self.addr_type_to_addr_index_and_tx_index.values_mut() {
|
||||
take!(store);
|
||||
}
|
||||
for store in self.addr_type_to_addr_index_and_unspent_outpoint.values_mut() {
|
||||
take!(store);
|
||||
}
|
||||
|
||||
Ok(tasks)
|
||||
}
|
||||
|
||||
pub fn rollback_if_needed(
|
||||
&mut self,
|
||||
vecs: &mut Vecs,
|
||||
@@ -368,11 +400,7 @@ impl Stores {
|
||||
let addr_type = output_type;
|
||||
let addr_index = type_index;
|
||||
|
||||
addr_index_tx_index_to_remove.insert((
|
||||
addr_type,
|
||||
addr_index,
|
||||
spending_tx_index,
|
||||
));
|
||||
addr_index_tx_index_to_remove.insert((addr_type, addr_index, spending_tx_index));
|
||||
|
||||
self.addr_type_to_addr_index_and_unspent_outpoint
|
||||
.get_mut_unwrap(addr_type)
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::Indexes;
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
db: Database,
|
||||
pub db: Database,
|
||||
pub blocks: BlocksVecs<M>,
|
||||
#[traversable(wrap = "transactions", rename = "raw")]
|
||||
pub transactions: TransactionsVecs<M>,
|
||||
@@ -121,8 +121,7 @@ impl Vecs {
|
||||
}
|
||||
|
||||
pub fn flush(&mut self, height: Height) -> Result<()> {
|
||||
self.par_iter_mut_any_stored_vec()
|
||||
.try_for_each(|vec| vec.stamped_write(Stamp::from(height)))?;
|
||||
self.stamped_write(height)?;
|
||||
self.db.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -137,6 +136,12 @@ impl Vecs {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn stamped_write(&mut self, height: Height) -> Result<()> {
|
||||
self.par_iter_mut_any_stored_vec()
|
||||
.try_for_each(|vec| vec.stamped_write(Stamp::from(height)))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compact(&self) -> Result<()> {
|
||||
self.db.compact()?;
|
||||
Ok(())
|
||||
@@ -168,7 +173,4 @@ impl Vecs {
|
||||
.chain(self.scripts.par_iter_mut_any())
|
||||
}
|
||||
|
||||
pub fn db(&self) -> &Database {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ impl<'a> Day1Iter<'a> {
|
||||
.to_usize()
|
||||
.saturating_sub(self.start_di.to_usize())
|
||||
+ 1;
|
||||
let timestamps = &self.computer.blocks.time.timestamp.day1;
|
||||
let timestamps = &self.computer.indexes.timestamp.day1;
|
||||
let heights = &self.computer.indexes.day1.first_height;
|
||||
|
||||
let mut entries = Vec::with_capacity(total / self.step + 1);
|
||||
|
||||
@@ -45,8 +45,7 @@ impl Query {
|
||||
|
||||
// Get timestamps using difficulty_to_timestamp for epoch start
|
||||
let epoch_start_timestamp = computer
|
||||
.blocks
|
||||
.time
|
||||
.indexes
|
||||
.timestamp
|
||||
.epoch
|
||||
.collect_one(current_epoch)
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn iter_difficulty_epochs(
|
||||
.unwrap_or_default();
|
||||
|
||||
let epoch_to_height = &computer.indexes.epoch.first_height;
|
||||
let epoch_to_timestamp = &computer.blocks.time.timestamp.epoch;
|
||||
let epoch_to_timestamp = &computer.indexes.timestamp.epoch;
|
||||
let epoch_to_difficulty = &computer.blocks.difficulty.value.epoch;
|
||||
|
||||
let mut results = Vec::with_capacity(end_epoch.to_usize() - start_epoch.to_usize() + 1);
|
||||
|
||||
@@ -56,7 +56,7 @@ impl Query {
|
||||
let step = (total_days / 200).max(1); // Max ~200 data points
|
||||
|
||||
let hashrate_vec = &computer.mining.hashrate.rate.base.day1;
|
||||
let timestamp_vec = &computer.blocks.time.timestamp.day1;
|
||||
let timestamp_vec = &computer.indexes.timestamp.day1;
|
||||
|
||||
let mut hashrates = Vec::with_capacity(total_days / step + 1);
|
||||
let mut di = start_day1.to_usize();
|
||||
|
||||
@@ -373,7 +373,7 @@ impl Query {
|
||||
// Slow path: rebuild from computer's precomputed monotonic timestamps
|
||||
let mut map = HEIGHT_BY_MONOTONIC_TIMESTAMP.write();
|
||||
if map.len() <= current_height {
|
||||
*map = RangeMap::from(self.computer().blocks.time.timestamp_monotonic.collect());
|
||||
*map = RangeMap::from(self.computer().indexes.timestamp.monotonic.collect());
|
||||
}
|
||||
map.ceil(ts).map(usize::from).unwrap_or(current_height)
|
||||
}
|
||||
|
||||
@@ -206,6 +206,34 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes buffered puts/dels and returns a closure that ingests them into the keyspace.
|
||||
/// The store is left with empty buffers, ready for the next batch.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn take_pending_ingest(
|
||||
&mut self,
|
||||
height: Height,
|
||||
) -> Result<Option<Box<dyn FnOnce() -> Result<()> + Send>>>
|
||||
where
|
||||
K: Send + 'static,
|
||||
V: Send + 'static,
|
||||
for<'a> ByteView: From<&'a K> + From<&'a V>,
|
||||
{
|
||||
self.export_meta_if_needed(height)?;
|
||||
|
||||
let puts = mem::take(&mut self.puts);
|
||||
let dels = mem::take(&mut self.dels);
|
||||
|
||||
if puts.is_empty() && dels.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let keyspace = self.keyspace.clone();
|
||||
|
||||
Ok(Some(Box::new(move || {
|
||||
Self::ingest(&keyspace, puts.iter(), dels.iter())
|
||||
})))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (K, V)> {
|
||||
self.keyspace
|
||||
|
||||
Reference in New Issue
Block a user