mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-17 22:18:14 -07:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d43f340a0 | |||
| e279116bff | |||
| 5c376acb9f | |||
| a43b00c12c | |||
| 15fbdc168f | |||
| 046c624abf | |||
| 4d7212fb63 | |||
| 2207ec1b7e | |||
| 3f2a48f248 | |||
| 3f9edb211e | |||
| eee1a10d2a | |||
| 0bf2cd77dc | |||
| a99c06013b | |||
| 10ef95497f | |||
| e0a618837e | |||
| 5bd1f0b625 | |||
| 4d3e8bbcaf | |||
| 153fcdf4e0 | |||
| 5d83ee4d70 | |||
| 10d29aa2ea | |||
| 528880bd04 | |||
| 5937fbe724 | |||
| 7a718293c0 | |||
| 75f37b6695 | |||
| f9688629ca | |||
| c9e704bdf4 | |||
| 1e0a7d6d0f | |||
| 565251f12f | |||
| 9879a986aa | |||
| 8d7ef8da61 | |||
| 17478a4ac4 | |||
| b3031b3375 | |||
| 2e401379a0 | |||
| 45ab6ebf71 | |||
| 00f7d69ea6 | |||
| 408d83c350 | |||
| 43df9e098c | |||
| 0c7861071d | |||
| 6f430bdb8c | |||
| 4b415b215d | |||
| 8614e9eded | |||
| c85da92cbc | |||
| 297fc3b855 | |||
| c9d5a62fcb | |||
| 90b3b51c48 | |||
| 5966ab05e4 | |||
| c3506339cd | |||
| e54843291e | |||
| b0b261fe9f | |||
| 6786be296d | |||
| e5068bbbf3 | |||
| 36cfe49b20 | |||
| 33cc13708a | |||
| 2389632812 | |||
| e0bcdb8105 | |||
| 45e83c98b9 | |||
| 753bbf3e7e | |||
| 54cc0cb446 | |||
| d64dcb75a9 | |||
| f599115f6c | |||
| 9fc45625ad | |||
| c68d1d1fda | |||
| 6cbe09af23 | |||
| 96d35d1d29 | |||
| e23554811b | |||
| 041c542046 | |||
| 66dc7cd8f5 | |||
| b00692249c | |||
| ff2c04a100 | |||
| 7cee0e2c5a | |||
| 744032f1f1 | |||
| 99b171bad6 | |||
| 37e2b6eae2 | |||
| a967fe8f35 | |||
| a3f3c54675 | |||
| f41874f438 | |||
| 98bbfec525 | |||
| 1bcf3235b6 | |||
| 07734b8bab | |||
| a2fd1e03ad | |||
| 90e8741fb7 | |||
| 5f5563fece | |||
| c7edfce481 | |||
| 7b3dd83b93 |
@@ -18,6 +18,7 @@ _*
|
||||
/*.py
|
||||
/*.json
|
||||
/*.html
|
||||
!/btc-cycle-sim.html
|
||||
/research
|
||||
/filter_*
|
||||
/heatmaps*
|
||||
|
||||
Generated
+222
-309
File diff suppressed because it is too large
Load Diff
+31
-30
@@ -4,7 +4,7 @@ members = ["crates/*"]
|
||||
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
|
||||
package.license = "MIT"
|
||||
package.edition = "2024"
|
||||
package.version = "0.3.1"
|
||||
package.version = "0.3.6"
|
||||
package.homepage = "https://bitcoinresearchkit.org"
|
||||
package.repository = "https://github.com/bitcoinresearchkit/brk"
|
||||
package.readme = "README.md"
|
||||
@@ -35,51 +35,52 @@ debug = true
|
||||
[workspace.dependencies]
|
||||
aide = { version = "0.16.0-alpha.4", features = ["axum-json", "axum-query"] }
|
||||
axum = { version = "0.8.9", default-features = false, features = ["http1", "json", "query", "tokio", "tracing"] }
|
||||
bitcoin = { version = "0.32.100", features = ["serde"] }
|
||||
brk_alloc = { version = "0.3.1", path = "crates/brk_alloc" }
|
||||
brk_bencher = { version = "0.3.1", path = "crates/brk_bencher" }
|
||||
brk_bindgen = { version = "0.3.1", path = "crates/brk_bindgen" }
|
||||
brk_cli = { version = "0.3.1", path = "crates/brk_cli" }
|
||||
brk_client = { version = "0.3.1", path = "crates/brk_client" }
|
||||
brk_cohort = { version = "0.3.1", path = "crates/brk_cohort" }
|
||||
brk_computer = { version = "0.3.1", path = "crates/brk_computer" }
|
||||
brk_error = { version = "0.3.1", path = "crates/brk_error" }
|
||||
brk_fetcher = { version = "0.3.1", path = "crates/brk_fetcher" }
|
||||
brk_indexer = { version = "0.3.1", path = "crates/brk_indexer" }
|
||||
brk_iterator = { version = "0.3.1", path = "crates/brk_iterator" }
|
||||
brk_logger = { version = "0.3.1", path = "crates/brk_logger" }
|
||||
brk_mempool = { version = "0.3.1", path = "crates/brk_mempool" }
|
||||
brk_oracle = { version = "0.3.1", path = "crates/brk_oracle" }
|
||||
brk_query = { version = "0.3.1", path = "crates/brk_query", features = ["tokio"] }
|
||||
brk_reader = { version = "0.3.1", path = "crates/brk_reader" }
|
||||
brk_rpc = { version = "0.3.1", path = "crates/brk_rpc" }
|
||||
brk_server = { version = "0.3.1", path = "crates/brk_server" }
|
||||
brk_store = { version = "0.3.1", path = "crates/brk_store" }
|
||||
brk_traversable = { version = "0.3.1", path = "crates/brk_traversable", features = ["pco", "derive"] }
|
||||
brk_traversable_derive = { version = "0.3.1", path = "crates/brk_traversable_derive" }
|
||||
brk_types = { version = "0.3.1", path = "crates/brk_types" }
|
||||
brk_website = { version = "0.3.1", path = "crates/brk_website" }
|
||||
bitcoin = { version = "0.32.101", features = ["serde"] }
|
||||
brk_alloc = { version = "0.3.6", path = "crates/brk_alloc" }
|
||||
brk_bencher = { version = "0.3.6", path = "crates/brk_bencher" }
|
||||
brk_bindgen = { version = "0.3.6", path = "crates/brk_bindgen" }
|
||||
brk_cli = { version = "0.3.6", path = "crates/brk_cli" }
|
||||
brk_client = { version = "0.3.6", path = "crates/brk_client" }
|
||||
brk_cohort = { version = "0.3.6", path = "crates/brk_cohort" }
|
||||
brk_computer = { version = "0.3.6", path = "crates/brk_computer" }
|
||||
brk_error = { version = "0.3.6", path = "crates/brk_error" }
|
||||
brk_fetcher = { version = "0.3.6", path = "crates/brk_fetcher" }
|
||||
brk_indexer = { version = "0.3.6", path = "crates/brk_indexer" }
|
||||
brk_iterator = { version = "0.3.6", path = "crates/brk_iterator" }
|
||||
brk_logger = { version = "0.3.6", path = "crates/brk_logger" }
|
||||
brk_mempool = { version = "0.3.6", path = "crates/brk_mempool" }
|
||||
brk_oracle = { version = "0.3.6", path = "crates/brk_oracle" }
|
||||
brk_query = { version = "0.3.6", path = "crates/brk_query", features = ["tokio"] }
|
||||
brk_reader = { version = "0.3.6", path = "crates/brk_reader" }
|
||||
brk_rpc = { version = "0.3.6", path = "crates/brk_rpc" }
|
||||
brk_server = { version = "0.3.6", path = "crates/brk_server" }
|
||||
brk_store = { version = "0.3.6", path = "crates/brk_store" }
|
||||
brk_traversable = { version = "0.3.6", path = "crates/brk_traversable", features = ["pco", "derive"] }
|
||||
brk_traversable_derive = { version = "0.3.6", path = "crates/brk_traversable_derive" }
|
||||
brk_types = { version = "0.3.6", path = "crates/brk_types" }
|
||||
brk_website = { version = "0.3.6", path = "crates/brk_website" }
|
||||
byteview = "0.10.1"
|
||||
color-eyre = "0.6.5"
|
||||
corepc-jsonrpc = { package = "jsonrpc", version = "0.19.0", features = ["simple_http"], default-features = false }
|
||||
corepc-types = { version = "0.14.0", features = ["std"], default-features = false }
|
||||
corepc-types = { version = "0.15.0", features = ["std"], default-features = false }
|
||||
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
||||
fjall = "3.1.4"
|
||||
fjall = "3.1.5"
|
||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||
jiff = { version = "0.2.28", features = ["perf-inline", "tz-system"], default-features = false }
|
||||
jiff = { version = "0.2.31", features = ["perf-inline", "tz-system"], default-features = false }
|
||||
owo-colors = "4.3.0"
|
||||
parking_lot = "0.12.5"
|
||||
pco = "1.0.2"
|
||||
rayon = "1.12.0"
|
||||
rapidhash = "4.4.2"
|
||||
rustc-hash = "2.1.2"
|
||||
schemars = { version = "1.2.1", features = ["indexmap2"] }
|
||||
serde = "1.0.228"
|
||||
serde_bytes = "0.11.19"
|
||||
serde_derive = "1.0.228"
|
||||
serde_json = { version = "1.0.150", features = ["float_roundtrip", "preserve_order"] }
|
||||
smallvec = "1.15.1"
|
||||
smallvec = "1.15.2"
|
||||
tokio = { version = "1.52.3", features = ["rt-multi-thread"] }
|
||||
tower-http = { version = "0.6.11", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
|
||||
tower-http = { version = "0.7.0", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] }
|
||||
tower-layer = "0.3"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
ureq = { version = "3.3.0", features = ["json"] }
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use brk_cohort::{
|
||||
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, EPOCH_NAMES, LOSS_NAMES, OVER_AGE_NAMES,
|
||||
OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES,
|
||||
UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
|
||||
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, ENTRY_NAMES, EPOCH_NAMES, LOSS_NAMES,
|
||||
OVER_AGE_NAMES, OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES,
|
||||
SPENDABLE_TYPE_NAMES, TERM_NAMES, UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
|
||||
};
|
||||
use brk_types::{Index, pools};
|
||||
use serde::Serialize;
|
||||
@@ -59,6 +59,7 @@ impl CohortConstants {
|
||||
("TERM_NAMES", to_value(&TERM_NAMES)),
|
||||
("EPOCH_NAMES", to_value(&EPOCH_NAMES)),
|
||||
("CLASS_NAMES", to_value(&CLASS_NAMES)),
|
||||
("ENTRY_NAMES", to_value(&ENTRY_NAMES)),
|
||||
("SPENDABLE_TYPE_NAMES", to_value(&SPENDABLE_TYPE_NAMES)),
|
||||
("AGE_RANGE_NAMES", to_value(&AGE_RANGE_NAMES)),
|
||||
("UNDER_AGE_NAMES", to_value(&UNDER_AGE_NAMES)),
|
||||
|
||||
@@ -51,7 +51,7 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
}
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void, cache?: boolean }}}} [options]",
|
||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void, cache?: boolean, memCache?: boolean }}}} [options]",
|
||||
return_type
|
||||
)
|
||||
.unwrap();
|
||||
@@ -60,22 +60,22 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
|
||||
let params = build_method_params(endpoint);
|
||||
let params_with_opts = if params.is_empty() {
|
||||
"{ signal, onValue, cache } = {}".to_string()
|
||||
"{ signal, onValue, cache, memCache } = {}".to_string()
|
||||
} else {
|
||||
format!("{}, {{ signal, onValue, cache }} = {{}}", params)
|
||||
format!("{}, {{ signal, onValue, cache, memCache }} = {{}}", params)
|
||||
};
|
||||
writeln!(output, " async {}({}) {{", method_name, params_with_opts).unwrap();
|
||||
|
||||
let path = build_path_template(&endpoint.path, &endpoint.path_params);
|
||||
|
||||
let fetch_call: String = if endpoint.returns_binary() {
|
||||
"this.getBytes(path, { signal, onValue, cache })".to_string()
|
||||
"this.getBytes(path, { signal, onValue, cache, memCache })".to_string()
|
||||
} else if endpoint.returns_json() {
|
||||
"this.getJson(path, { signal, onValue, cache })".to_string()
|
||||
"this.getJson(path, { signal, onValue, cache, memCache })".to_string()
|
||||
} else if endpoint.response_kind.text_is_numeric() {
|
||||
"Number(await this.getText(path, { signal, cache, onValue: onValue ? (v) => onValue(Number(v)) : undefined }))".to_string()
|
||||
"Number(await this.getText(path, { signal, cache, memCache, onValue: onValue ? (v) => onValue(Number(v)) : undefined }))".to_string()
|
||||
} else {
|
||||
"this.getText(path, { signal, onValue, cache })".to_string()
|
||||
"this.getText(path, { signal, onValue, cache, memCache })".to_string()
|
||||
};
|
||||
|
||||
write_path_assignment(output, endpoint, &path);
|
||||
@@ -83,7 +83,7 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
if endpoint.supports_csv {
|
||||
writeln!(
|
||||
output,
|
||||
" if (format === 'csv') return this.getText(path, {{ signal, onValue, cache }});"
|
||||
" if (format === 'csv') return this.getText(path, {{ signal, onValue, cache, memCache }});"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -51,6 +51,12 @@ const _openBrowserCache = (option) => {{
|
||||
return caches.open(name).catch(() => null);
|
||||
}};
|
||||
|
||||
/**
|
||||
* @param {{string}} url
|
||||
* @returns {{URL}}
|
||||
*/
|
||||
const _parseBaseUrl = (url) => new URL(url, typeof location === 'undefined' ? undefined : location.href);
|
||||
|
||||
/**
|
||||
* Custom error class for BRK client errors
|
||||
*/
|
||||
@@ -237,8 +243,8 @@ function _wrapSeriesData(raw) {{
|
||||
* @property {{(n: number) => RangeBuilder<T>}} first - Get first n items
|
||||
* @property {{(n: number) => RangeBuilder<T>}} last - Get last n items
|
||||
* @property {{(n: number) => SkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch all data
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch all data
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||
* @property {{() => Promise<number>}} len - Get total number of data points
|
||||
* @property {{() => Promise<Version>}} version - Get the current version of the series
|
||||
* @property {{Thenable<T>}} then - Thenable (await endpoint)
|
||||
@@ -253,8 +259,8 @@ function _wrapSeriesData(raw) {{
|
||||
* @property {{(n: number) => DateRangeBuilder<T>}} first - Get first n items
|
||||
* @property {{(n: number) => DateRangeBuilder<T>}} last - Get last n items
|
||||
* @property {{(n: number) => DateSkippedBuilder<T>}} skip - Skip first n items, chain with take()
|
||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch all data
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch all data
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch all data as CSV
|
||||
* @property {{() => Promise<number>}} len - Get total number of data points
|
||||
* @property {{() => Promise<Version>}} version - Get the current version of the series
|
||||
* @property {{DateThenable<T>}} then - Thenable (await endpoint)
|
||||
@@ -263,41 +269,53 @@ function _wrapSeriesData(raw) {{
|
||||
|
||||
/** @typedef {{SeriesEndpoint<any>}} AnySeriesEndpoint */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} ClientFetchOptions
|
||||
* @property {{AbortSignal}} [signal] - Abort this request
|
||||
* @property {{boolean}} [cache] - Use HTTP/browser/client caches. Set false for a no-store network fetch.
|
||||
* @property {{boolean}} [memCache] - Use the parsed in-memory response cache. Set false for large one-shot reads.
|
||||
* @property {{(value: T) => void}} [onValue] - Receive stale/fresh values as they arrive
|
||||
*/
|
||||
|
||||
/** @template T @typedef {{ClientFetchOptions<SeriesData<T>> | ((value: SeriesData<T>) => void)}} SeriesFetchArg */
|
||||
/** @template T @typedef {{ClientFetchOptions<DateSeriesData<T>> | ((value: DateSeriesData<T>) => void)}} DateSeriesFetchArg */
|
||||
|
||||
/** @template T @typedef {{Object}} SingleItemBuilder
|
||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch the item
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch the item
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/** @template T @typedef {{Object}} DateSingleItemBuilder
|
||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch the item
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch the item
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{DateThenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/** @template T @typedef {{Object}} SkippedBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items after skipped position
|
||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch from skipped position to end
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch from skipped position to end
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/** @template T @typedef {{Object}} DateSkippedBuilder
|
||||
* @property {{(n: number) => DateRangeBuilder<T>}} take - Take n items after skipped position
|
||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch from skipped position to end
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch from skipped position to end
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{DateThenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/** @template T @typedef {{Object}} RangeBuilder
|
||||
* @property {{(onValue?: (value: SeriesData<T>) => void) => Promise<SeriesData<T>>}} fetch - Fetch the range
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{(arg?: SeriesFetchArg<T>, options?: ClientFetchOptions<SeriesData<T>>) => Promise<SeriesData<T>>}} fetch - Fetch the range
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{Thenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
/** @template T @typedef {{Object}} DateRangeBuilder
|
||||
* @property {{(onValue?: (value: DateSeriesData<T>) => void) => Promise<DateSeriesData<T>>}} fetch - Fetch the range
|
||||
* @property {{() => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{(arg?: DateSeriesFetchArg<T>, options?: ClientFetchOptions<DateSeriesData<T>>) => Promise<DateSeriesData<T>>}} fetch - Fetch the range
|
||||
* @property {{(options?: ClientFetchOptions<string>) => Promise<string>}} fetchCsv - Fetch as CSV
|
||||
* @property {{DateThenable<T>}} then - Thenable
|
||||
*/
|
||||
|
||||
@@ -344,8 +362,8 @@ function _endpoint(client, name, index) {{
|
||||
* @returns {{DateRangeBuilder<T>}}
|
||||
*/
|
||||
const rangeBuilder = (start, end) => ({{
|
||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(start, end), onValue); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(start, end, 'csv')); }},
|
||||
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(start, end), arg, options); }},
|
||||
fetchCsv(options) {{ return client.getText(buildPath(start, end, 'csv'), options); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
}});
|
||||
|
||||
@@ -354,8 +372,8 @@ function _endpoint(client, name, index) {{
|
||||
* @returns {{DateSingleItemBuilder<T>}}
|
||||
*/
|
||||
const singleItemBuilder = (idx) => ({{
|
||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(idx, idx + 1), onValue); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(idx, idx + 1, 'csv')); }},
|
||||
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(idx, idx + 1), arg, options); }},
|
||||
fetchCsv(options) {{ return client.getText(buildPath(idx, idx + 1, 'csv'), options); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
}});
|
||||
|
||||
@@ -365,8 +383,8 @@ function _endpoint(client, name, index) {{
|
||||
*/
|
||||
const skippedBuilder = (start) => ({{
|
||||
take(n) {{ return rangeBuilder(start, start + n); }},
|
||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(start, undefined), onValue); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
|
||||
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(start, undefined), arg, options); }},
|
||||
fetchCsv(options) {{ return client.getText(buildPath(start, undefined, 'csv'), options); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
}});
|
||||
|
||||
@@ -381,8 +399,8 @@ function _endpoint(client, name, index) {{
|
||||
first(n) {{ return rangeBuilder(undefined, n); }},
|
||||
last(n) {{ return n === 0 ? rangeBuilder(undefined, 0) : rangeBuilder(-n, undefined); }},
|
||||
skip(n) {{ return skippedBuilder(n); }},
|
||||
fetch(onValue) {{ return client._fetchSeriesData(buildPath(), onValue); }},
|
||||
fetchCsv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
|
||||
fetch(arg, options) {{ return client._fetchSeriesData(buildPath(), arg, options); }},
|
||||
fetchCsv(options) {{ return client.getText(buildPath(undefined, undefined, 'csv'), options); }},
|
||||
len() {{ return client.getSeriesLen(name, index); }},
|
||||
version() {{ return client.getSeriesVersion(name, index); }},
|
||||
then(resolve, reject) {{ return this.fetch().then(resolve, reject); }},
|
||||
@@ -403,6 +421,9 @@ class BrkClientBase {{
|
||||
const isString = typeof options === 'string';
|
||||
const rawUrl = isString ? options : options.baseUrl;
|
||||
this.baseUrl = rawUrl.endsWith('/') ? rawUrl.slice(0, -1) : rawUrl;
|
||||
const url = _parseBaseUrl(this.baseUrl);
|
||||
this.url = url.href.endsWith('/') ? url.href.slice(0, -1) : url.href;
|
||||
this.domain = url.hostname;
|
||||
this.timeout = isString ? 5000 : (options.timeout ?? 5000);
|
||||
/** @type {{Promise<Cache | null>}} */
|
||||
this._browserCachePromise = _openBrowserCache(isString ? undefined : options.browserCache);
|
||||
@@ -478,10 +499,10 @@ class BrkClientBase {{
|
||||
* @template T
|
||||
* @param {{string}} path
|
||||
* @param {{(res: Response) => Promise<T>}} parse - Response body reader
|
||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @param {{ClientFetchOptions<T>}} [options]
|
||||
* @returns {{Promise<T>}}
|
||||
*/
|
||||
async _getCached(path, parse, {{ onValue, signal, cache = true }} = {{}}) {{
|
||||
async _getCached(path, parse, {{ onValue, signal, cache = true, memCache = true }} = {{}}) {{
|
||||
if (!cache) {{
|
||||
const res = await this.get(path, {{ signal, cache }});
|
||||
const value = await parse(res);
|
||||
@@ -490,8 +511,9 @@ class BrkClientBase {{
|
||||
}}
|
||||
|
||||
const url = `${{this.baseUrl}}${{path}}`;
|
||||
const useMemCache = memCache !== false;
|
||||
/** @type {{_MemEntry<T> | undefined}} */
|
||||
const memHit = this._memGet(url);
|
||||
const memHit = useMemCache ? this._memGet(url) : undefined;
|
||||
const browserCache = this._browserCache;
|
||||
|
||||
// L1 fast path: deliver from memCache, revalidate via network.
|
||||
@@ -504,7 +526,7 @@ class BrkClientBase {{
|
||||
if (netEtag && netEtag === memHit.etag) return memHit.value;
|
||||
const cloned = browserCache ? res.clone() : null;
|
||||
const value = await parse(res);
|
||||
this._memSet(url, netEtag, value);
|
||||
if (useMemCache) this._memSet(url, netEtag, value);
|
||||
if (onValue) onValue(value);
|
||||
if (cloned && browserCache) {{
|
||||
const cacheStore = browserCache;
|
||||
@@ -523,7 +545,7 @@ class BrkClientBase {{
|
||||
if (!res || networkSettled) return null;
|
||||
const value = await parse(res);
|
||||
if (networkSettled) return value;
|
||||
this._memSet(url, res.headers.get('ETag'), value);
|
||||
if (useMemCache) this._memSet(url, res.headers.get('ETag'), value);
|
||||
onValue(value);
|
||||
return value;
|
||||
}}).catch(() => null)
|
||||
@@ -534,11 +556,11 @@ class BrkClientBase {{
|
||||
networkSettled = true;
|
||||
const netEtag = res.headers.get('ETag');
|
||||
// Stale won and populated memCache with matching ETag → reuse, skip parse + second onValue.
|
||||
const populated = /** @type {{_MemEntry<T> | undefined}} */ (this._memGet(url));
|
||||
const populated = useMemCache ? /** @type {{_MemEntry<T> | undefined}} */ (this._memGet(url)) : undefined;
|
||||
if (populated && netEtag && netEtag === populated.etag) return populated.value;
|
||||
const cloned = browserCache ? res.clone() : null;
|
||||
const value = await parse(res);
|
||||
this._memSet(url, netEtag, value);
|
||||
if (useMemCache) this._memSet(url, netEtag, value);
|
||||
if (onValue) onValue(value);
|
||||
if (cloned && browserCache) {{
|
||||
const cacheStore = browserCache;
|
||||
@@ -556,7 +578,7 @@ class BrkClientBase {{
|
||||
* Make a GET request expecting a JSON response. Cached and supports `onValue`.
|
||||
* @template T
|
||||
* @param {{string}} path
|
||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @param {{ClientFetchOptions<T>}} [options]
|
||||
* @returns {{Promise<T>}}
|
||||
*/
|
||||
getJson(path, options) {{
|
||||
@@ -567,7 +589,7 @@ class BrkClientBase {{
|
||||
* Make a GET request expecting a text response (text/plain, text/csv, ...).
|
||||
* Cached and supports `onValue`, same as `getJson`.
|
||||
* @param {{string}} path
|
||||
* @param {{{{ onValue?: (value: string) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @param {{ClientFetchOptions<string>}} [options]
|
||||
* @returns {{Promise<string>}}
|
||||
*/
|
||||
getText(path, options) {{
|
||||
@@ -578,7 +600,7 @@ class BrkClientBase {{
|
||||
* Make a GET request expecting binary data (application/octet-stream).
|
||||
* Cached and supports `onValue`, same as `getJson`.
|
||||
* @param {{string}} path
|
||||
* @param {{{{ onValue?: (value: Uint8Array) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @param {{ClientFetchOptions<Uint8Array>}} [options]
|
||||
* @returns {{Promise<Uint8Array>}}
|
||||
*/
|
||||
getBytes(path, options) {{
|
||||
@@ -650,12 +672,17 @@ class BrkClientBase {{
|
||||
* Fetch series data and wrap with helper methods (internal)
|
||||
* @template T
|
||||
* @param {{string}} path
|
||||
* @param {{(value: DateSeriesData<T>) => void}} [onValue]
|
||||
* @param {{DateSeriesFetchArg<T>}} [arg]
|
||||
* @param {{ClientFetchOptions<DateSeriesData<T>>}} [options]
|
||||
* @returns {{Promise<DateSeriesData<T>>}}
|
||||
*/
|
||||
async _fetchSeriesData(path, onValue) {{
|
||||
async _fetchSeriesData(path, arg, options) {{
|
||||
const requestOptions = typeof arg === 'function'
|
||||
? {{ ...(options ?? {{}}), onValue: arg }}
|
||||
: {{ ...(arg ?? {{}}), ...(options ?? {{}}) }};
|
||||
const onValue = requestOptions.onValue;
|
||||
const wrappedOnValue = onValue ? (/** @type {{SeriesData<T>}} */ raw) => onValue(_wrapSeriesData(raw)) : undefined;
|
||||
const raw = await this.getJson(path, {{ onValue: wrappedOnValue }});
|
||||
const raw = await this.getJson(path, {{ ...requestOptions, onValue: wrappedOnValue }});
|
||||
return _wrapSeriesData(raw);
|
||||
}}
|
||||
}}
|
||||
@@ -679,6 +706,160 @@ const _p = (prefix, acc) => acc ? `${{prefix}}_${{acc}}` : prefix;
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
output.push_str(r##"
|
||||
const _MASK_64 = 0xffffffffffffffffn;
|
||||
const _RAPIDHASH_SECRETS = /** @type {const} */ ([
|
||||
0x2d358dccaa6c78a5n,
|
||||
0x8bb84b93962eacc9n,
|
||||
0x4b33a62ed433d4a3n,
|
||||
0x4d5a2da51de1aa47n,
|
||||
0xa0761d6478bd642fn,
|
||||
0xe7037ed1a0b428dbn,
|
||||
0x90ed1765281c388cn,
|
||||
]);
|
||||
const _RAPIDHASH_SEED = _rapidHashSeed(0n);
|
||||
|
||||
/** @param {bigint} value */
|
||||
function _u64(value) {
|
||||
return value & _MASK_64;
|
||||
}
|
||||
|
||||
/** @param {bigint} left @param {bigint} right */
|
||||
function _rapidMix(left, right) {
|
||||
const result = _u64(left) * _u64(right);
|
||||
return _u64(result) ^ _u64(result >> 64n);
|
||||
}
|
||||
|
||||
/** @param {bigint} left @param {bigint} right @returns {[bigint, bigint]} */
|
||||
function _rapidMum(left, right) {
|
||||
const result = _u64(left) * _u64(right);
|
||||
return [_u64(result), _u64(result >> 64n)];
|
||||
}
|
||||
|
||||
/** @param {bigint} seed */
|
||||
function _rapidHashSeed(seed) {
|
||||
return _u64(seed ^ _rapidMix(seed ^ _RAPIDHASH_SECRETS[2], _RAPIDHASH_SECRETS[1]));
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} bytes @param {number} offset */
|
||||
function _readU32(bytes, offset) {
|
||||
return (
|
||||
BigInt(bytes[offset]) |
|
||||
(BigInt(bytes[offset + 1]) << 8n) |
|
||||
(BigInt(bytes[offset + 2]) << 16n) |
|
||||
(BigInt(bytes[offset + 3]) << 24n)
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} bytes @param {number} offset */
|
||||
function _readU64(bytes, offset) {
|
||||
return _readU32(bytes, offset) | (_readU32(bytes, offset + 4) << 32n);
|
||||
}
|
||||
|
||||
/** @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload */
|
||||
function _asUint8Array(payload) {
|
||||
if (payload instanceof Uint8Array) return payload;
|
||||
if (payload instanceof ArrayBuffer) return new Uint8Array(payload);
|
||||
if (ArrayBuffer.isView(payload)) return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
|
||||
if (Array.isArray(payload)) return new Uint8Array(payload);
|
||||
throw new Error("Expected address payload bytes");
|
||||
}
|
||||
|
||||
/** @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload */
|
||||
function _rapidHashV3(payload) {
|
||||
const bytes = _asUint8Array(payload);
|
||||
const length = bytes.length;
|
||||
if (length === 0) throw new Error("Expected a non-empty address payload");
|
||||
if (length > 65) throw new Error("Expected at most 65 address payload bytes");
|
||||
|
||||
let seed = _RAPIDHASH_SEED;
|
||||
let a = 0n;
|
||||
let b = 0n;
|
||||
let remainder;
|
||||
|
||||
if (length <= 16) {
|
||||
if (length >= 4) {
|
||||
seed ^= BigInt(length);
|
||||
if (length >= 8) {
|
||||
a ^= _readU64(bytes, 0);
|
||||
b ^= _readU64(bytes, length - 8);
|
||||
} else {
|
||||
a ^= _readU32(bytes, 0);
|
||||
b ^= _readU32(bytes, length - 4);
|
||||
}
|
||||
} else if (length > 0) {
|
||||
a ^= (BigInt(bytes[0]) << 45n) | BigInt(bytes[length - 1]);
|
||||
b ^= BigInt(bytes[length >> 1]);
|
||||
}
|
||||
remainder = BigInt(length);
|
||||
} else {
|
||||
seed = _rapidMix(_readU64(bytes, 0) ^ _RAPIDHASH_SECRETS[2], _readU64(bytes, 8) ^ seed);
|
||||
if (length > 32) {
|
||||
seed = _rapidMix(_readU64(bytes, 16) ^ _RAPIDHASH_SECRETS[2], _readU64(bytes, 24) ^ seed);
|
||||
if (length > 48) {
|
||||
seed = _rapidMix(_readU64(bytes, 32) ^ _RAPIDHASH_SECRETS[1], _readU64(bytes, 40) ^ seed);
|
||||
if (length > 64) {
|
||||
seed = _rapidMix(_readU64(bytes, 48) ^ _RAPIDHASH_SECRETS[1], _readU64(bytes, 56) ^ seed);
|
||||
}
|
||||
}
|
||||
}
|
||||
remainder = BigInt(length);
|
||||
a ^= _readU64(bytes, length - 16) ^ remainder;
|
||||
b ^= _readU64(bytes, length - 8);
|
||||
}
|
||||
|
||||
a ^= _RAPIDHASH_SECRETS[1];
|
||||
b ^= seed;
|
||||
[a, b] = _rapidMum(a, b);
|
||||
return _rapidMix(a ^ 0xaaaaaaaaaaaaaaaan, b ^ _RAPIDHASH_SECRETS[1] ^ remainder);
|
||||
}
|
||||
|
||||
/** @param {number} nibbles */
|
||||
function _validateHashPrefixNibbles(nibbles) {
|
||||
if (!Number.isInteger(nibbles) || nibbles < 1 || nibbles > 16) {
|
||||
throw new Error("Expected hash-prefix length from 1 to 16 hex nibbles");
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {OutputType} addrType @returns {number[]} */
|
||||
function _addressPayloadLengths(addrType) {
|
||||
switch (addrType) {
|
||||
case "p2a": return [2];
|
||||
case "p2pk": return [33, 65];
|
||||
case "p2pkh":
|
||||
case "p2sh":
|
||||
case "v0_p2wpkh": return [20];
|
||||
case "v0_p2wsh":
|
||||
case "v1_p2tr": return [32];
|
||||
default:
|
||||
throw new Error(`Unsupported address type for address payload hash-prefix: ${addrType}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {OutputType} addrType
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload
|
||||
*/
|
||||
function _validateAddressPayloadForType(addrType, payload) {
|
||||
const length = _asUint8Array(payload).length;
|
||||
const expected = _addressPayloadLengths(addrType);
|
||||
if (!expected.includes(length)) {
|
||||
throw new Error(`Expected ${addrType} address payload length ${expected.join(" or ")} bytes`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`.
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload - Raw address payload bytes
|
||||
* @param {number} nibbles - Prefix length from 1 to 16 hex nibbles
|
||||
* @returns {string}
|
||||
*/
|
||||
function addressPayloadHashPrefix(payload, nibbles) {
|
||||
_validateHashPrefixNibbles(nibbles);
|
||||
return _rapidHashV3(payload).toString(16).padStart(16, "0").slice(0, nibbles);
|
||||
}
|
||||
|
||||
"##);
|
||||
}
|
||||
|
||||
/// Generate static constants for the BrkClient class.
|
||||
|
||||
@@ -111,6 +111,32 @@ pub fn generate_main_client(
|
||||
writeln!(output, " this.series = this._buildTree();").unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
|
||||
output.push_str(r##" /**
|
||||
* Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload
|
||||
* @param {number} nibbles
|
||||
* @returns {string}
|
||||
*/
|
||||
static addressPayloadHashPrefix(payload, nibbles) {
|
||||
return addressPayloadHashPrefix(payload, nibbles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch address hash-prefix matches from raw address payload bytes.
|
||||
* @param {OutputType} addrType
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload - Raw payload bytes matching addrType length
|
||||
* @param {number} nibbles
|
||||
* @param {{ signal?: AbortSignal, onValue?: (value: AddrHashPrefixMatches) => void, cache?: boolean, memCache?: boolean }} [options]
|
||||
* @returns {Promise<AddrHashPrefixMatches>}
|
||||
*/
|
||||
getAddressPayloadHashPrefixMatches(addrType, payload, nibbles, options = {}) {
|
||||
_validateAddressPayloadForType(addrType, payload);
|
||||
const prefix = addressPayloadHashPrefix(payload, nibbles);
|
||||
return this.getAddressHashPrefixMatches(addrType, prefix, options);
|
||||
}
|
||||
|
||||
"##);
|
||||
|
||||
writeln!(output, " /**").unwrap();
|
||||
writeln!(output, " * @private").unwrap();
|
||||
writeln!(output, " * @returns {{SeriesTree}}").unwrap();
|
||||
@@ -161,7 +187,11 @@ pub fn generate_main_client(
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
writeln!(output, "export {{ BrkClient, BrkError }};").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"export {{ BrkClient, BrkError, addressPayloadHashPrefix }};"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -84,6 +84,23 @@ pub fn generate_main_client(output: &mut String, endpoints: &[Endpoint]) {
|
||||
.unwrap();
|
||||
writeln!(output, " return _date_to_index(index, d)").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
|
||||
output.push_str(r#" @staticmethod
|
||||
def address_payload_hash_prefix(payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str:
|
||||
"""Compute the RapidHash v3 hash-prefix for raw address payload bytes."""
|
||||
return address_payload_hash_prefix(payload, nibbles)
|
||||
|
||||
def get_address_payload_hash_prefix_matches(
|
||||
self,
|
||||
addr_type: OutputType,
|
||||
payload: Union[bytes, bytearray, memoryview],
|
||||
nibbles: int,
|
||||
) -> AddrHashPrefixMatches:
|
||||
"""Fetch address hash-prefix matches from raw payload bytes matching addr_type length."""
|
||||
_validate_address_payload_for_type(addr_type, payload)
|
||||
return self.get_address_hash_prefix_matches(addr_type, address_payload_hash_prefix(payload, nibbles))
|
||||
|
||||
"#);
|
||||
// Generate API methods
|
||||
generate_api_methods(output, endpoints);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,125 @@ def _p(prefix: str, acc: str) -> str:
|
||||
"#
|
||||
)
|
||||
.unwrap();
|
||||
output.push_str(r#"
|
||||
_MASK_64 = (1 << 64) - 1
|
||||
_RAPIDHASH_SECRETS = (
|
||||
0x2d358dccaa6c78a5,
|
||||
0x8bb84b93962eacc9,
|
||||
0x4b33a62ed433d4a3,
|
||||
0x4d5a2da51de1aa47,
|
||||
0xa0761d6478bd642f,
|
||||
0xe7037ed1a0b428db,
|
||||
0x90ed1765281c388c,
|
||||
)
|
||||
_RAPIDHASH_SEED = 0
|
||||
|
||||
|
||||
def _u64(value: int) -> int:
|
||||
return value & _MASK_64
|
||||
|
||||
|
||||
def _rapid_mix(left: int, right: int) -> int:
|
||||
result = _u64(left) * _u64(right)
|
||||
return _u64(result) ^ _u64(result >> 64)
|
||||
|
||||
|
||||
def _rapid_mum(left: int, right: int) -> Tuple[int, int]:
|
||||
result = _u64(left) * _u64(right)
|
||||
return _u64(result), _u64(result >> 64)
|
||||
|
||||
|
||||
def _rapid_hash_seed(seed: int) -> int:
|
||||
return _u64(seed ^ _rapid_mix(seed ^ _RAPIDHASH_SECRETS[2], _RAPIDHASH_SECRETS[1]))
|
||||
|
||||
|
||||
_RAPIDHASH_SEED = _rapid_hash_seed(0)
|
||||
|
||||
|
||||
def _read_u32(data: bytes, offset: int) -> int:
|
||||
return int.from_bytes(data[offset:offset + 4], "little")
|
||||
|
||||
|
||||
def _read_u64(data: bytes, offset: int) -> int:
|
||||
return int.from_bytes(data[offset:offset + 8], "little")
|
||||
|
||||
|
||||
def _rapid_hash_v3(payload: Union[bytes, bytearray, memoryview]) -> int:
|
||||
data = bytes(payload)
|
||||
length = len(data)
|
||||
if length == 0:
|
||||
raise ValueError("Expected a non-empty address payload")
|
||||
if length > 65:
|
||||
raise ValueError("Expected at most 65 address payload bytes")
|
||||
|
||||
seed = _RAPIDHASH_SEED
|
||||
a = 0
|
||||
b = 0
|
||||
|
||||
if length <= 16:
|
||||
if length >= 4:
|
||||
seed ^= length
|
||||
if length >= 8:
|
||||
a ^= _read_u64(data, 0)
|
||||
b ^= _read_u64(data, length - 8)
|
||||
else:
|
||||
a ^= _read_u32(data, 0)
|
||||
b ^= _read_u32(data, length - 4)
|
||||
elif length > 0:
|
||||
a ^= (data[0] << 45) | data[length - 1]
|
||||
b ^= data[length >> 1]
|
||||
remainder = length
|
||||
else:
|
||||
if length > 16:
|
||||
seed = _rapid_mix(_read_u64(data, 0) ^ _RAPIDHASH_SECRETS[2], _read_u64(data, 8) ^ seed)
|
||||
if length > 32:
|
||||
seed = _rapid_mix(_read_u64(data, 16) ^ _RAPIDHASH_SECRETS[2], _read_u64(data, 24) ^ seed)
|
||||
if length > 48:
|
||||
seed = _rapid_mix(_read_u64(data, 32) ^ _RAPIDHASH_SECRETS[1], _read_u64(data, 40) ^ seed)
|
||||
if length > 64:
|
||||
seed = _rapid_mix(_read_u64(data, 48) ^ _RAPIDHASH_SECRETS[1], _read_u64(data, 56) ^ seed)
|
||||
remainder = length
|
||||
a ^= _read_u64(data, length - 16) ^ remainder
|
||||
b ^= _read_u64(data, length - 8)
|
||||
|
||||
a ^= _RAPIDHASH_SECRETS[1]
|
||||
b ^= seed
|
||||
a, b = _rapid_mum(a, b)
|
||||
return _rapid_mix(a ^ 0xaaaaaaaaaaaaaaaa, b ^ _RAPIDHASH_SECRETS[1] ^ remainder)
|
||||
|
||||
|
||||
def _validate_hash_prefix_nibbles(nibbles: int) -> None:
|
||||
if isinstance(nibbles, bool) or not isinstance(nibbles, int) or nibbles < 1 or nibbles > 16:
|
||||
raise ValueError("Expected hash-prefix length from 1 to 16 hex nibbles")
|
||||
|
||||
|
||||
def _address_payload_lengths(addr_type: OutputType) -> Tuple[int, ...]:
|
||||
if addr_type == "p2a":
|
||||
return (2,)
|
||||
if addr_type == "p2pk":
|
||||
return (33, 65)
|
||||
if addr_type in ("p2pkh", "p2sh", "v0_p2wpkh"):
|
||||
return (20,)
|
||||
if addr_type in ("v0_p2wsh", "v1_p2tr"):
|
||||
return (32,)
|
||||
raise ValueError(f"Unsupported address type for address payload hash-prefix: {addr_type}")
|
||||
|
||||
|
||||
def _validate_address_payload_for_type(addr_type: OutputType, payload: Union[bytes, bytearray, memoryview]) -> None:
|
||||
length = len(bytes(payload))
|
||||
expected = _address_payload_lengths(addr_type)
|
||||
if length not in expected:
|
||||
joined = " or ".join(str(value) for value in expected)
|
||||
raise ValueError(f"Expected {addr_type} address payload length {joined} bytes")
|
||||
|
||||
|
||||
def address_payload_hash_prefix(payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str:
|
||||
"""Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`."""
|
||||
_validate_hash_prefix_nibbles(nibbles)
|
||||
return f"{_rapid_hash_v3(payload):016x}"[:nibbles]
|
||||
|
||||
|
||||
"#);
|
||||
}
|
||||
|
||||
/// Generate the SeriesData and SeriesEndpoint classes
|
||||
|
||||
@@ -76,6 +76,36 @@ impl BrkClient {{
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
output.push_str(r#" /// Decode a mainnet Bitcoin address into the BRK address type and raw payload bytes.
|
||||
pub fn decode_address_payload(address: &str) -> Result<AddressPayload> {
|
||||
decode_address_payload(address)
|
||||
}
|
||||
|
||||
/// Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||
pub fn address_payload_hash_prefix(payload: &[u8], nibbles: usize) -> Result<String> {
|
||||
address_payload_hash_prefix(payload, nibbles)
|
||||
}
|
||||
|
||||
/// Decode a mainnet Bitcoin address and compute its hash prefix.
|
||||
pub fn address_hash_prefix(address: &str, nibbles: usize) -> Result<AddressHashPrefix> {
|
||||
address_hash_prefix(address, nibbles)
|
||||
}
|
||||
|
||||
/// Fetch address hash-prefix matches from raw payload bytes matching `addr_type` length.
|
||||
pub fn get_address_payload_hash_prefix_matches(&self, addr_type: OutputType, payload: &[u8], nibbles: usize) -> Result<AddrHashPrefixMatches> {
|
||||
validate_address_payload_for_type(addr_type, payload)?;
|
||||
let prefix = address_payload_hash_prefix(payload, nibbles)?;
|
||||
self.get_address_hash_prefix_matches(addr_type, &prefix)
|
||||
}
|
||||
|
||||
/// Fetch address hash-prefix matches for a mainnet Bitcoin address.
|
||||
pub fn get_address_hash_prefix_matches_for_address(&self, address: &str, nibbles: usize) -> Result<AddrHashPrefixMatches> {
|
||||
let hashed = address_hash_prefix(address, nibbles)?;
|
||||
self.get_address_hash_prefix_matches(hashed.addr_type, &hashed.prefix)
|
||||
}
|
||||
|
||||
"#);
|
||||
|
||||
generate_api_methods(output, endpoints);
|
||||
|
||||
writeln!(output, "}}").unwrap();
|
||||
@@ -118,6 +148,23 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
"get_text"
|
||||
};
|
||||
|
||||
if endpoint.path == "/api/address/hash-prefix/{addr_type}/{prefix}" {
|
||||
writeln!(
|
||||
output,
|
||||
" let addr_type = address_payload_type_path(addr_type)?;"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" self.base.{}(&format!(\"{}\"{}))",
|
||||
fetch_method, path, index_arg
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " }}").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
if endpoint.query_params.is_empty() {
|
||||
writeln!(
|
||||
output,
|
||||
|
||||
@@ -11,7 +11,8 @@ use crate::{
|
||||
pub fn generate_imports(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
r#"use std::sync::Arc;
|
||||
r#"use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::ops::{{Bound, RangeBounds}};
|
||||
use serde::de::DeserializeOwned;
|
||||
pub use brk_cohort::*;
|
||||
@@ -43,6 +44,97 @@ impl std::error::Error for BrkError {{}}
|
||||
/// Result type for BRK client operations.
|
||||
pub type Result<T> = std::result::Result<T, BrkError>;
|
||||
|
||||
/// BRK address type and raw payload bytes used by the hash-prefix index.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AddressPayload {{
|
||||
pub addr_type: OutputType,
|
||||
pub payload: Vec<u8>,
|
||||
}}
|
||||
|
||||
/// BRK address type and leading hex nibbles of the address-payload hash.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AddressHashPrefix {{
|
||||
pub addr_type: OutputType,
|
||||
pub prefix: String,
|
||||
}}
|
||||
|
||||
/// Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{{addr_type}}/{{prefix}}`.
|
||||
pub fn address_payload_hash_prefix(payload: &[u8], nibbles: usize) -> Result<String> {{
|
||||
if payload.is_empty() {{
|
||||
return Err(BrkError {{ message: "Expected a non-empty address payload".to_string() }});
|
||||
}}
|
||||
if payload.len() > 65 {{
|
||||
return Err(BrkError {{ message: "Expected at most 65 address payload bytes".to_string() }});
|
||||
}}
|
||||
if !(1..=16).contains(&nibbles) {{
|
||||
return Err(BrkError {{ message: "Expected hash-prefix length from 1 to 16 hex nibbles".to_string() }});
|
||||
}}
|
||||
Ok(format!("{{:016x}}", rapidhash::v3::rapidhash_v3(payload))[..nibbles].to_string())
|
||||
}}
|
||||
|
||||
fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> Result<()> {{
|
||||
let expected: &[usize] = match addr_type {{
|
||||
OutputType::P2A => &[2],
|
||||
OutputType::P2PK33 => &[33],
|
||||
OutputType::P2PK65 => &[65],
|
||||
OutputType::P2PKH | OutputType::P2SH | OutputType::P2WPKH => &[20],
|
||||
OutputType::P2WSH | OutputType::P2TR => &[32],
|
||||
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {{
|
||||
return Err(BrkError {{ message: format!("Unsupported address type for address payload hash-prefix: {{addr_type:?}}") }});
|
||||
}},
|
||||
}};
|
||||
let addr_type = address_payload_type_path(addr_type)?;
|
||||
|
||||
if !expected.contains(&payload.len()) {{
|
||||
let joined = expected
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or ");
|
||||
return Err(BrkError {{ message: format!("Expected {{addr_type}} address payload length {{joined}} bytes") }});
|
||||
}}
|
||||
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
fn address_payload_type_path(addr_type: OutputType) -> Result<&'static str> {{
|
||||
match addr_type {{
|
||||
OutputType::P2A => Ok("p2a"),
|
||||
OutputType::P2PK33 | OutputType::P2PK65 => Ok("p2pk"),
|
||||
OutputType::P2PKH => Ok("p2pkh"),
|
||||
OutputType::P2SH => Ok("p2sh"),
|
||||
OutputType::P2WPKH => Ok("v0_p2wpkh"),
|
||||
OutputType::P2WSH => Ok("v0_p2wsh"),
|
||||
OutputType::P2TR => Ok("v1_p2tr"),
|
||||
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {{
|
||||
Err(BrkError {{ message: format!("Unsupported address type for address payload hash-prefix: {{addr_type:?}}") }})
|
||||
}},
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Decode a mainnet Bitcoin address into the BRK address type and raw payload bytes.
|
||||
pub fn decode_address_payload(address: &str) -> Result<AddressPayload> {{
|
||||
if address.is_empty() {{
|
||||
return Err(BrkError {{ message: "Expected an address string".to_string() }});
|
||||
}}
|
||||
let addr_bytes = AddrBytes::from_str(address).map_err(|e| BrkError {{ message: e.to_string() }})?;
|
||||
let addr_type = OutputType::from(&addr_bytes);
|
||||
|
||||
Ok(AddressPayload {{
|
||||
addr_type,
|
||||
payload: addr_bytes.as_slice().to_vec(),
|
||||
}})
|
||||
}}
|
||||
|
||||
/// Decode a mainnet Bitcoin address and compute its hash prefix.
|
||||
pub fn address_hash_prefix(address: &str, nibbles: usize) -> Result<AddressHashPrefix> {{
|
||||
let decoded = decode_address_payload(address)?;
|
||||
Ok(AddressHashPrefix {{
|
||||
addr_type: decoded.addr_type,
|
||||
prefix: address_payload_hash_prefix(&decoded.payload, nibbles)?,
|
||||
}})
|
||||
}}
|
||||
|
||||
/// Options for configuring the BRK client.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrkClientOptions {{
|
||||
|
||||
@@ -13,6 +13,7 @@ exclude = ["examples/"]
|
||||
[dependencies]
|
||||
brk_cohort = { workspace = true }
|
||||
brk_types = { workspace = true }
|
||||
rapidhash = { workspace = true }
|
||||
ureq = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -17,7 +17,7 @@ fn main() -> brk_client::Result<()> {
|
||||
// day1() returns DateMetricEndpointBuilder, so fetch() returns DateMetricData
|
||||
let price_close = client
|
||||
.series()
|
||||
.prices
|
||||
.price
|
||||
.split
|
||||
.close
|
||||
.usd
|
||||
|
||||
+749
-34
@@ -8,6 +8,7 @@
|
||||
#![allow(clippy::useless_format)]
|
||||
#![allow(clippy::unnecessary_to_owned)]
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::ops::{Bound, RangeBounds};
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -32,6 +33,97 @@ impl std::error::Error for BrkError {}
|
||||
/// Result type for BRK client operations.
|
||||
pub type Result<T> = std::result::Result<T, BrkError>;
|
||||
|
||||
/// BRK address type and raw payload bytes used by the hash-prefix index.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AddressPayload {
|
||||
pub addr_type: OutputType,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
/// BRK address type and leading hex nibbles of the address-payload hash.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AddressHashPrefix {
|
||||
pub addr_type: OutputType,
|
||||
pub prefix: String,
|
||||
}
|
||||
|
||||
/// Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`.
|
||||
pub fn address_payload_hash_prefix(payload: &[u8], nibbles: usize) -> Result<String> {
|
||||
if payload.is_empty() {
|
||||
return Err(BrkError { message: "Expected a non-empty address payload".to_string() });
|
||||
}
|
||||
if payload.len() > 65 {
|
||||
return Err(BrkError { message: "Expected at most 65 address payload bytes".to_string() });
|
||||
}
|
||||
if !(1..=16).contains(&nibbles) {
|
||||
return Err(BrkError { message: "Expected hash-prefix length from 1 to 16 hex nibbles".to_string() });
|
||||
}
|
||||
Ok(format!("{:016x}", rapidhash::v3::rapidhash_v3(payload))[..nibbles].to_string())
|
||||
}
|
||||
|
||||
fn validate_address_payload_for_type(addr_type: OutputType, payload: &[u8]) -> Result<()> {
|
||||
let expected: &[usize] = match addr_type {
|
||||
OutputType::P2A => &[2],
|
||||
OutputType::P2PK33 => &[33],
|
||||
OutputType::P2PK65 => &[65],
|
||||
OutputType::P2PKH | OutputType::P2SH | OutputType::P2WPKH => &[20],
|
||||
OutputType::P2WSH | OutputType::P2TR => &[32],
|
||||
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {
|
||||
return Err(BrkError { message: format!("Unsupported address type for address payload hash-prefix: {addr_type:?}") });
|
||||
},
|
||||
};
|
||||
let addr_type = address_payload_type_path(addr_type)?;
|
||||
|
||||
if !expected.contains(&payload.len()) {
|
||||
let joined = expected
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" or ");
|
||||
return Err(BrkError { message: format!("Expected {addr_type} address payload length {joined} bytes") });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn address_payload_type_path(addr_type: OutputType) -> Result<&'static str> {
|
||||
match addr_type {
|
||||
OutputType::P2A => Ok("p2a"),
|
||||
OutputType::P2PK33 | OutputType::P2PK65 => Ok("p2pk"),
|
||||
OutputType::P2PKH => Ok("p2pkh"),
|
||||
OutputType::P2SH => Ok("p2sh"),
|
||||
OutputType::P2WPKH => Ok("v0_p2wpkh"),
|
||||
OutputType::P2WSH => Ok("v0_p2wsh"),
|
||||
OutputType::P2TR => Ok("v1_p2tr"),
|
||||
OutputType::P2MS | OutputType::OpReturn | OutputType::Empty | OutputType::Unknown => {
|
||||
Err(BrkError { message: format!("Unsupported address type for address payload hash-prefix: {addr_type:?}") })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode a mainnet Bitcoin address into the BRK address type and raw payload bytes.
|
||||
pub fn decode_address_payload(address: &str) -> Result<AddressPayload> {
|
||||
if address.is_empty() {
|
||||
return Err(BrkError { message: "Expected an address string".to_string() });
|
||||
}
|
||||
let addr_bytes = AddrBytes::from_str(address).map_err(|e| BrkError { message: e.to_string() })?;
|
||||
let addr_type = OutputType::from(&addr_bytes);
|
||||
|
||||
Ok(AddressPayload {
|
||||
addr_type,
|
||||
payload: addr_bytes.as_slice().to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Decode a mainnet Bitcoin address and compute its hash prefix.
|
||||
pub fn address_hash_prefix(address: &str, nibbles: usize) -> Result<AddressHashPrefix> {
|
||||
let decoded = decode_address_payload(address)?;
|
||||
Ok(AddressHashPrefix {
|
||||
addr_type: decoded.addr_type,
|
||||
prefix: address_payload_hash_prefix(&decoded.payload, nibbles)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Options for configuring the BRK client.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrkClientOptions {
|
||||
@@ -1191,6 +1283,22 @@ pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern {
|
||||
pub sopr: AdjustedRatioValuePattern,
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2 {
|
||||
pub cap: CentsDeltaToUsdPattern,
|
||||
pub capitalized: PricePattern,
|
||||
pub gross_pnl: BlockCumulativeSumPattern,
|
||||
pub loss: BlockCumulativeNegativeSumPattern,
|
||||
pub mvrv: SeriesPattern1<StoredF32>,
|
||||
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
|
||||
pub peak_regret: BlockCumulativeSumPattern,
|
||||
pub price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern,
|
||||
pub profit: BlockCumulativeSumPattern,
|
||||
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
|
||||
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
|
||||
pub sopr: RatioValuePattern2,
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2 {
|
||||
pub empty: _1m1w1y24hBpsPercentRatioPattern,
|
||||
@@ -1658,6 +1766,17 @@ pub struct ActiveInputOutputSpendablePattern {
|
||||
pub spendable_output_to_reused_addr_share: _1m1w1y24hBpsPercentRatioPattern,
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2 {
|
||||
pub activity: CoindaysCoinyearsDormancyTransferPattern,
|
||||
pub cost_basis: InMaxMinPerSupplyPattern,
|
||||
pub invested_capital: InPattern,
|
||||
pub outputs: SpendingSpentUnspentPattern,
|
||||
pub realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2,
|
||||
pub supply: DeltaDominanceHalfInTotalPattern2,
|
||||
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct CapLossMvrvNetPriceProfitSoprPattern {
|
||||
pub cap: CentsDeltaUsdPattern,
|
||||
@@ -3408,6 +3527,22 @@ impl PriceRatioPattern {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct RatioValuePattern2 {
|
||||
pub ratio: _1m1w1y24hPattern<StoredF64>,
|
||||
pub value_destroyed: AverageBlockCumulativeSumPattern<Cents>,
|
||||
}
|
||||
|
||||
impl RatioValuePattern2 {
|
||||
/// Create a new pattern node with accumulated series name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "sopr")),
|
||||
value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "value_destroyed")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern struct for repeated tree structure.
|
||||
pub struct RatioValuePattern {
|
||||
pub ratio: _24hPattern,
|
||||
@@ -3534,7 +3669,7 @@ pub struct SeriesTree {
|
||||
pub investing: SeriesTree_Investing,
|
||||
pub market: SeriesTree_Market,
|
||||
pub pools: SeriesTree_Pools,
|
||||
pub prices: SeriesTree_Prices,
|
||||
pub price: SeriesTree_Price,
|
||||
pub supply: SeriesTree_Supply,
|
||||
pub cohorts: SeriesTree_Cohorts,
|
||||
}
|
||||
@@ -3556,7 +3691,7 @@ impl SeriesTree {
|
||||
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")),
|
||||
price: SeriesTree_Price::new(client.clone(), format!("{base_path}_price")),
|
||||
supply: SeriesTree_Supply::new(client.clone(), format!("{base_path}_supply")),
|
||||
cohorts: SeriesTree_Cohorts::new(client.clone(), format!("{base_path}_cohorts")),
|
||||
}
|
||||
@@ -7063,31 +7198,31 @@ impl SeriesTree_Pools_Minor {
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Prices {
|
||||
pub split: SeriesTree_Prices_Split,
|
||||
pub ohlc: SeriesTree_Prices_Ohlc,
|
||||
pub spot: SeriesTree_Prices_Spot,
|
||||
pub struct SeriesTree_Price {
|
||||
pub split: SeriesTree_Price_Split,
|
||||
pub ohlc: SeriesTree_Price_Ohlc,
|
||||
pub spot: SeriesTree_Price_Spot,
|
||||
}
|
||||
|
||||
impl SeriesTree_Prices {
|
||||
impl SeriesTree_Price {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
split: SeriesTree_Prices_Split::new(client.clone(), format!("{base_path}_split")),
|
||||
ohlc: SeriesTree_Prices_Ohlc::new(client.clone(), format!("{base_path}_ohlc")),
|
||||
spot: SeriesTree_Prices_Spot::new(client.clone(), format!("{base_path}_spot")),
|
||||
split: SeriesTree_Price_Split::new(client.clone(), format!("{base_path}_split")),
|
||||
ohlc: SeriesTree_Price_Ohlc::new(client.clone(), format!("{base_path}_ohlc")),
|
||||
spot: SeriesTree_Price_Spot::new(client.clone(), format!("{base_path}_spot")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Prices_Split {
|
||||
pub struct SeriesTree_Price_Split {
|
||||
pub open: CentsSatsUsdPattern3,
|
||||
pub high: CentsSatsUsdPattern3,
|
||||
pub low: CentsSatsUsdPattern3,
|
||||
pub close: CentsSatsUsdPattern3,
|
||||
}
|
||||
|
||||
impl SeriesTree_Prices_Split {
|
||||
impl SeriesTree_Price_Split {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
open: CentsSatsUsdPattern3::new(client.clone(), "price_open".to_string()),
|
||||
@@ -7099,13 +7234,13 @@ impl SeriesTree_Prices_Split {
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Prices_Ohlc {
|
||||
pub struct SeriesTree_Price_Ohlc {
|
||||
pub usd: SeriesPattern2<OHLCDollars>,
|
||||
pub cents: SeriesPattern2<OHLCCents>,
|
||||
pub sats: SeriesPattern2<OHLCSats>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Prices_Ohlc {
|
||||
impl SeriesTree_Price_Ohlc {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
usd: SeriesPattern2::new(client.clone(), "price_ohlc".to_string()),
|
||||
@@ -7116,13 +7251,13 @@ impl SeriesTree_Prices_Ohlc {
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Prices_Spot {
|
||||
pub struct SeriesTree_Price_Spot {
|
||||
pub usd: SeriesPattern1<Dollars>,
|
||||
pub cents: SeriesPattern1<Cents>,
|
||||
pub sats: SeriesPattern1<Sats>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Prices_Spot {
|
||||
impl SeriesTree_Price_Spot {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
usd: SeriesPattern1::new(client.clone(), "price".to_string()),
|
||||
@@ -7199,6 +7334,7 @@ pub struct SeriesTree_Cohorts_Utxo {
|
||||
pub over_age: SeriesTree_Cohorts_Utxo_OverAge,
|
||||
pub epoch: SeriesTree_Cohorts_Utxo_Epoch,
|
||||
pub class: SeriesTree_Cohorts_Utxo_Class,
|
||||
pub entry: SeriesTree_Cohorts_Utxo_Entry,
|
||||
pub over_amount: SeriesTree_Cohorts_Utxo_OverAmount,
|
||||
pub amount_range: SeriesTree_Cohorts_Utxo_AmountRange,
|
||||
pub under_amount: SeriesTree_Cohorts_Utxo_UnderAmount,
|
||||
@@ -7218,6 +7354,7 @@ impl SeriesTree_Cohorts_Utxo {
|
||||
over_age: SeriesTree_Cohorts_Utxo_OverAge::new(client.clone(), format!("{base_path}_over_age")),
|
||||
epoch: SeriesTree_Cohorts_Utxo_Epoch::new(client.clone(), format!("{base_path}_epoch")),
|
||||
class: SeriesTree_Cohorts_Utxo_Class::new(client.clone(), format!("{base_path}_class")),
|
||||
entry: SeriesTree_Cohorts_Utxo_Entry::new(client.clone(), format!("{base_path}_entry")),
|
||||
over_amount: SeriesTree_Cohorts_Utxo_OverAmount::new(client.clone(), format!("{base_path}_over_amount")),
|
||||
amount_range: SeriesTree_Cohorts_Utxo_AmountRange::new(client.clone(), format!("{base_path}_amount_range")),
|
||||
under_amount: SeriesTree_Cohorts_Utxo_UnderAmount::new(client.clone(), format!("{base_path}_under_amount")),
|
||||
@@ -7999,7 +8136,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized {
|
||||
pub price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price,
|
||||
pub mvrv: SeriesPattern1<StoredF32>,
|
||||
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
|
||||
pub sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr,
|
||||
pub sopr: RatioValuePattern2,
|
||||
pub gross_pnl: BlockCumulativeSumPattern,
|
||||
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
|
||||
pub peak_regret: BlockCumulativeSumPattern,
|
||||
@@ -8016,7 +8153,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized {
|
||||
price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price::new(client.clone(), format!("{base_path}_price")),
|
||||
mvrv: SeriesPattern1::new(client.clone(), "lth_mvrv".to_string()),
|
||||
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "lth_net".to_string()),
|
||||
sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr::new(client.clone(), format!("{base_path}_sopr")),
|
||||
sopr: RatioValuePattern2::new(client.clone(), "lth".to_string()),
|
||||
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_gross_pnl".to_string()),
|
||||
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "lth_sell_side_risk_ratio".to_string()),
|
||||
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_peak_regret".to_string()),
|
||||
@@ -8236,21 +8373,6 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev_1y {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr {
|
||||
pub value_destroyed: AverageBlockCumulativeSumPattern<Cents>,
|
||||
pub ratio: _1m1w1y24hPattern<StoredF64>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), "lth_value_destroyed".to_string()),
|
||||
ratio: _1m1w1y24hPattern::new(client.clone(), "lth_sopr".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_AgeRange {
|
||||
pub under_1h: ActivityOutputsRealizedSupplyUnrealizedPattern,
|
||||
@@ -8466,6 +8588,561 @@ impl SeriesTree_Cohorts_Utxo_Class {
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry {
|
||||
pub discount: SeriesTree_Cohorts_Utxo_Entry_Discount,
|
||||
pub premium: SeriesTree_Cohorts_Utxo_Entry_Premium,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
discount: SeriesTree_Cohorts_Utxo_Entry_Discount::new(client.clone(), format!("{base_path}_discount")),
|
||||
premium: SeriesTree_Cohorts_Utxo_Entry_Premium::new(client.clone(), format!("{base_path}_premium")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount {
|
||||
pub supply: DeltaDominanceHalfInTotalPattern2,
|
||||
pub outputs: SpendingSpentUnspentPattern,
|
||||
pub activity: CoindaysCoinyearsDormancyTransferPattern,
|
||||
pub realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized,
|
||||
pub cost_basis: InMaxMinPerSupplyPattern,
|
||||
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
|
||||
pub invested_capital: InPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "veteran_supply".to_string()),
|
||||
outputs: SpendingSpentUnspentPattern::new(client.clone(), "veteran".to_string()),
|
||||
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "veteran".to_string()),
|
||||
realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized::new(client.clone(), format!("{base_path}_realized")),
|
||||
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "veteran".to_string()),
|
||||
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "veteran".to_string()),
|
||||
invested_capital: InPattern::new(client.clone(), "veteran_invested_capital_in".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized {
|
||||
pub cap: CentsDeltaToUsdPattern,
|
||||
pub profit: BlockCumulativeSumPattern,
|
||||
pub loss: BlockCumulativeNegativeSumPattern,
|
||||
pub price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price,
|
||||
pub mvrv: SeriesPattern1<StoredF32>,
|
||||
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
|
||||
pub sopr: RatioValuePattern2,
|
||||
pub gross_pnl: BlockCumulativeSumPattern,
|
||||
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
|
||||
pub peak_regret: BlockCumulativeSumPattern,
|
||||
pub capitalized: PricePattern,
|
||||
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
cap: CentsDeltaToUsdPattern::new(client.clone(), "veteran_realized_cap".to_string()),
|
||||
profit: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_profit".to_string()),
|
||||
loss: BlockCumulativeNegativeSumPattern::new(client.clone(), "veteran_realized_loss".to_string()),
|
||||
price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price::new(client.clone(), format!("{base_path}_price")),
|
||||
mvrv: SeriesPattern1::new(client.clone(), "veteran_mvrv".to_string()),
|
||||
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "veteran_net".to_string()),
|
||||
sopr: RatioValuePattern2::new(client.clone(), "veteran".to_string()),
|
||||
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_gross_pnl".to_string()),
|
||||
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "veteran_sell_side_risk_ratio".to_string()),
|
||||
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_peak_regret".to_string()),
|
||||
capitalized: PricePattern::new(client.clone(), "veteran_capitalized_price".to_string()),
|
||||
profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "veteran_realized_profit_to_loss_ratio".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price {
|
||||
pub usd: SeriesPattern1<Dollars>,
|
||||
pub cents: SeriesPattern1<Cents>,
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub sma: _1m1w1y2y4yAllPattern,
|
||||
pub std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
usd: SeriesPattern1::new(client.clone(), "veteran_realized_price".to_string()),
|
||||
cents: SeriesPattern1::new(client.clone(), "veteran_realized_price_cents".to_string()),
|
||||
sats: SeriesPattern1::new(client.clone(), "veteran_realized_price_sats".to_string()),
|
||||
bps: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_bps".to_string()),
|
||||
ratio: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio".to_string()),
|
||||
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "veteran_realized_price".to_string()),
|
||||
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "veteran_realized_price_ratio_sma".to_string()),
|
||||
std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev {
|
||||
pub all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All,
|
||||
pub _4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y,
|
||||
pub _2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y,
|
||||
pub _1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All::new(client.clone(), format!("{base_path}_all")),
|
||||
_4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y::new(client.clone(), format!("{base_path}_4y")),
|
||||
_2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y::new(client.clone(), format!("{base_path}_2y")),
|
||||
_1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y::new(client.clone(), format!("{base_path}_1y")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_4y".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_4y".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_4y".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_4y".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_4y".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_4y".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_4y".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_4y".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_4y".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_4y".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_4y".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_4y".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_4y".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_4y".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_4y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_2y".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_2y".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_2y".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_2y".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_2y".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_2y".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_2y".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_2y".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_2y".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_2y".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_2y".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_2y".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_2y".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_2y".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_2y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_1y".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_1y".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_1y".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_1y".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_1y".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_1y".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_1y".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_1y".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_1y".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_1y".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_1y".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_1y".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_1y".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_1y".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_1y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium {
|
||||
pub supply: DeltaDominanceHalfInTotalPattern2,
|
||||
pub outputs: SpendingSpentUnspentPattern,
|
||||
pub activity: CoindaysCoinyearsDormancyTransferPattern,
|
||||
pub realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized,
|
||||
pub cost_basis: InMaxMinPerSupplyPattern,
|
||||
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
|
||||
pub invested_capital: InPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "rookie_supply".to_string()),
|
||||
outputs: SpendingSpentUnspentPattern::new(client.clone(), "rookie".to_string()),
|
||||
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "rookie".to_string()),
|
||||
realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized::new(client.clone(), format!("{base_path}_realized")),
|
||||
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "rookie".to_string()),
|
||||
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "rookie".to_string()),
|
||||
invested_capital: InPattern::new(client.clone(), "rookie_invested_capital_in".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized {
|
||||
pub cap: CentsDeltaToUsdPattern,
|
||||
pub profit: BlockCumulativeSumPattern,
|
||||
pub loss: BlockCumulativeNegativeSumPattern,
|
||||
pub price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price,
|
||||
pub mvrv: SeriesPattern1<StoredF32>,
|
||||
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
|
||||
pub sopr: RatioValuePattern2,
|
||||
pub gross_pnl: BlockCumulativeSumPattern,
|
||||
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
|
||||
pub peak_regret: BlockCumulativeSumPattern,
|
||||
pub capitalized: PricePattern,
|
||||
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
cap: CentsDeltaToUsdPattern::new(client.clone(), "rookie_realized_cap".to_string()),
|
||||
profit: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_profit".to_string()),
|
||||
loss: BlockCumulativeNegativeSumPattern::new(client.clone(), "rookie_realized_loss".to_string()),
|
||||
price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price::new(client.clone(), format!("{base_path}_price")),
|
||||
mvrv: SeriesPattern1::new(client.clone(), "rookie_mvrv".to_string()),
|
||||
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "rookie_net".to_string()),
|
||||
sopr: RatioValuePattern2::new(client.clone(), "rookie".to_string()),
|
||||
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_gross_pnl".to_string()),
|
||||
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "rookie_sell_side_risk_ratio".to_string()),
|
||||
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_peak_regret".to_string()),
|
||||
capitalized: PricePattern::new(client.clone(), "rookie_capitalized_price".to_string()),
|
||||
profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "rookie_realized_profit_to_loss_ratio".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price {
|
||||
pub usd: SeriesPattern1<Dollars>,
|
||||
pub cents: SeriesPattern1<Cents>,
|
||||
pub sats: SeriesPattern1<SatsFract>,
|
||||
pub bps: SeriesPattern1<BasisPoints32>,
|
||||
pub ratio: SeriesPattern1<StoredF32>,
|
||||
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
|
||||
pub sma: _1m1w1y2y4yAllPattern,
|
||||
pub std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
usd: SeriesPattern1::new(client.clone(), "rookie_realized_price".to_string()),
|
||||
cents: SeriesPattern1::new(client.clone(), "rookie_realized_price_cents".to_string()),
|
||||
sats: SeriesPattern1::new(client.clone(), "rookie_realized_price_sats".to_string()),
|
||||
bps: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_bps".to_string()),
|
||||
ratio: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio".to_string()),
|
||||
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "rookie_realized_price".to_string()),
|
||||
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "rookie_realized_price_ratio_sma".to_string()),
|
||||
std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev {
|
||||
pub all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All,
|
||||
pub _4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y,
|
||||
pub _2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y,
|
||||
pub _1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All::new(client.clone(), format!("{base_path}_all")),
|
||||
_4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y::new(client.clone(), format!("{base_path}_4y")),
|
||||
_2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y::new(client.clone(), format!("{base_path}_2y")),
|
||||
_1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y::new(client.clone(), format!("{base_path}_1y")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_4y".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_4y".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_4y".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_4y".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_4y".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_4y".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_4y".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_4y".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_4y".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_4y".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_4y".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_4y".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_4y".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_4y".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_4y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_2y".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_2y".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_2y".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_2y".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_2y".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_2y".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_2y".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_2y".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_2y".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_2y".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_2y".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_2y".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_2y".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_2y".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_2y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y {
|
||||
pub sd: SeriesPattern1<StoredF32>,
|
||||
pub zscore: SeriesPattern1<StoredF32>,
|
||||
pub _0sd: CentsSatsUsdPattern,
|
||||
pub p0_5sd: PriceRatioPattern,
|
||||
pub p1sd: PriceRatioPattern,
|
||||
pub p1_5sd: PriceRatioPattern,
|
||||
pub p2sd: PriceRatioPattern,
|
||||
pub p2_5sd: PriceRatioPattern,
|
||||
pub p3sd: PriceRatioPattern,
|
||||
pub m0_5sd: PriceRatioPattern,
|
||||
pub m1sd: PriceRatioPattern,
|
||||
pub m1_5sd: PriceRatioPattern,
|
||||
pub m2sd: PriceRatioPattern,
|
||||
pub m2_5sd: PriceRatioPattern,
|
||||
pub m3sd: PriceRatioPattern,
|
||||
}
|
||||
|
||||
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_1y".to_string()),
|
||||
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_1y".to_string()),
|
||||
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_1y".to_string()),
|
||||
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_1y".to_string()),
|
||||
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_1y".to_string()),
|
||||
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_1y".to_string()),
|
||||
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_1y".to_string()),
|
||||
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_1y".to_string()),
|
||||
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_1y".to_string()),
|
||||
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_1y".to_string()),
|
||||
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_1y".to_string()),
|
||||
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_1y".to_string()),
|
||||
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_1y".to_string()),
|
||||
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_1y".to_string()),
|
||||
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_1y".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Cohorts_Utxo_OverAmount {
|
||||
pub _1sat: ActivityOutputsRealizedSupplyUnrealizedPattern2,
|
||||
@@ -8953,7 +9630,7 @@ pub struct BrkClient {
|
||||
|
||||
impl BrkClient {
|
||||
/// Client version.
|
||||
pub const VERSION: &'static str = "v0.3.0";
|
||||
pub const VERSION: &'static str = "v0.3.6";
|
||||
|
||||
/// Create a new client with the given base URL.
|
||||
pub fn new(base_url: impl Into<String>) -> Self {
|
||||
@@ -9007,6 +9684,34 @@ impl BrkClient {
|
||||
))
|
||||
}
|
||||
|
||||
/// Decode a mainnet Bitcoin address into the BRK address type and raw payload bytes.
|
||||
pub fn decode_address_payload(address: &str) -> Result<AddressPayload> {
|
||||
decode_address_payload(address)
|
||||
}
|
||||
|
||||
/// Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||
pub fn address_payload_hash_prefix(payload: &[u8], nibbles: usize) -> Result<String> {
|
||||
address_payload_hash_prefix(payload, nibbles)
|
||||
}
|
||||
|
||||
/// Decode a mainnet Bitcoin address and compute its hash prefix.
|
||||
pub fn address_hash_prefix(address: &str, nibbles: usize) -> Result<AddressHashPrefix> {
|
||||
address_hash_prefix(address, nibbles)
|
||||
}
|
||||
|
||||
/// Fetch address hash-prefix matches from raw payload bytes matching `addr_type` length.
|
||||
pub fn get_address_payload_hash_prefix_matches(&self, addr_type: OutputType, payload: &[u8], nibbles: usize) -> Result<AddrHashPrefixMatches> {
|
||||
validate_address_payload_for_type(addr_type, payload)?;
|
||||
let prefix = address_payload_hash_prefix(payload, nibbles)?;
|
||||
self.get_address_hash_prefix_matches(addr_type, &prefix)
|
||||
}
|
||||
|
||||
/// Fetch address hash-prefix matches for a mainnet Bitcoin address.
|
||||
pub fn get_address_hash_prefix_matches_for_address(&self, address: &str, nibbles: usize) -> Result<AddrHashPrefixMatches> {
|
||||
let hashed = address_hash_prefix(address, nibbles)?;
|
||||
self.get_address_hash_prefix_matches(hashed.addr_type, &hashed.prefix)
|
||||
}
|
||||
|
||||
/// Health check
|
||||
///
|
||||
/// Liveness probe. Returns server identity, uptime, and indexed/computed heights from local state only (no bitcoind round-trip). For real chain-tip catch-up, see `/api/server/sync`.
|
||||
@@ -9281,6 +9986,16 @@ impl BrkClient {
|
||||
self.base.get_json(&path)
|
||||
}
|
||||
|
||||
/// Address hash-prefix matches
|
||||
///
|
||||
/// Find addresses by address type and by the first 1-16 hex nibbles of RapidHash v3 over the raw address payload bytes. Intended for privacy-preserving client-side wallet discovery without sending raw addresses or xpubs. Fetch metadata for the returned addresses through `/api/address/{address}`.
|
||||
///
|
||||
/// Endpoint: `GET /api/address/hash-prefix/{addr_type}/{prefix}`
|
||||
pub fn get_address_hash_prefix_matches(&self, addr_type: OutputType, prefix: &str) -> Result<AddrHashPrefixMatches> {
|
||||
let addr_type = address_payload_type_path(addr_type)?;
|
||||
self.base.get_json(&format!("/api/address/hash-prefix/{addr_type}/{prefix}"))
|
||||
}
|
||||
|
||||
/// Address information
|
||||
///
|
||||
/// Retrieve address information including balance and transaction counts. Supports all standard Bitcoin address types (P2PKH, P2SH, P2WPKH, P2WSH, P2TR).
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use brk_client::{
|
||||
BrkClient, OutputType, address_hash_prefix, address_payload_hash_prefix, decode_address_payload,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn address_payload_hash_prefix_vectors() {
|
||||
let vectors = [
|
||||
(vec![0x4e, 0x73], "58101afa51a1ecfd"),
|
||||
((0_u8..20).collect::<Vec<_>>(), "c3327ecb8ae1ff23"),
|
||||
((0_u8..32).collect::<Vec<_>>(), "c0186990f026b180"),
|
||||
((0_u8..65).collect::<Vec<_>>(), "0d4b77027ae7d700"),
|
||||
];
|
||||
|
||||
for (payload, expected) in vectors.iter() {
|
||||
assert_eq!(address_payload_hash_prefix(payload, 16).unwrap(), *expected);
|
||||
assert_eq!(
|
||||
BrkClient::address_payload_hash_prefix(payload, 8).unwrap(),
|
||||
&expected[..8]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_payload_hash_prefix_validation() {
|
||||
assert!(address_payload_hash_prefix(&[], 16).is_err());
|
||||
assert!(address_payload_hash_prefix(&[0; 66], 16).is_err());
|
||||
assert!(address_payload_hash_prefix(&[1, 2], 0).is_err());
|
||||
assert!(address_payload_hash_prefix(&[1, 2], 17).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_hash_prefix_uses_brk_address_parser() {
|
||||
let address = "1BoatSLRHtKNngkdXEeobR76b53LETtpyT";
|
||||
let decoded = decode_address_payload(address).unwrap();
|
||||
assert_eq!(decoded.addr_type, OutputType::P2PKH);
|
||||
assert_eq!(decoded.payload.len(), 20);
|
||||
|
||||
let hashed = address_hash_prefix(address, 8).unwrap();
|
||||
assert_eq!(hashed.addr_type, OutputType::P2PKH);
|
||||
assert_eq!(
|
||||
hashed.prefix,
|
||||
address_payload_hash_prefix(&decoded.payload, 8).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use brk_traversable::Traversable;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{CohortName, Filter};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EntryPrice {
|
||||
Discount,
|
||||
Premium,
|
||||
}
|
||||
|
||||
impl EntryPrice {
|
||||
#[inline]
|
||||
pub const fn from_is_discount(is_discount: bool) -> Self {
|
||||
if is_discount {
|
||||
Self::Discount
|
||||
} else {
|
||||
Self::Premium
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn is_discount(self) -> bool {
|
||||
matches!(self, Self::Discount)
|
||||
}
|
||||
}
|
||||
|
||||
pub const ENTRY_FILTERS: ByEntry<Filter> = ByEntry {
|
||||
discount: Filter::Entry(EntryPrice::Discount),
|
||||
premium: Filter::Entry(EntryPrice::Premium),
|
||||
};
|
||||
|
||||
pub const ENTRY_NAMES: ByEntry<CohortName> = ByEntry {
|
||||
discount: CohortName::new("veteran", "Veteran", "Veteran Coins"),
|
||||
premium: CohortName::new("rookie", "Rookie", "Rookie Coins"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByEntry<T> {
|
||||
pub discount: T,
|
||||
pub premium: T,
|
||||
}
|
||||
|
||||
impl ByEntry<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&ENTRY_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByEntry<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = ENTRY_FILTERS;
|
||||
let n = ENTRY_NAMES;
|
||||
Self {
|
||||
discount: create(f.discount, n.discount.id),
|
||||
premium: create(f.premium, n.premium.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = ENTRY_FILTERS;
|
||||
let n = ENTRY_NAMES;
|
||||
Ok(Self {
|
||||
discount: create(f.discount, n.discount.id)?,
|
||||
premium: create(f.premium, n.premium.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, entry: EntryPrice) -> &T {
|
||||
match entry {
|
||||
EntryPrice::Discount => &self.discount,
|
||||
EntryPrice::Premium => &self.premium,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, entry: EntryPrice) -> &mut T {
|
||||
match entry {
|
||||
EntryPrice::Discount => &mut self.discount,
|
||||
EntryPrice::Premium => &mut self.premium,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[&self.discount, &self.premium].into_iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||
[&mut self.discount, &mut self.premium].into_iter()
|
||||
}
|
||||
|
||||
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
[&mut self.discount, &mut self.premium].into_par_iter()
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ impl CohortContext {
|
||||
/// Build full name for a filter, adding prefix only for Time/Amount filters.
|
||||
///
|
||||
/// Prefix rules:
|
||||
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Type`
|
||||
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Entry`, `Type`
|
||||
/// - Context prefix: `Time`, `Amount`
|
||||
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
|
||||
match filter {
|
||||
@@ -32,6 +32,7 @@ impl CohortContext {
|
||||
| Filter::Term(_)
|
||||
| Filter::Epoch(_)
|
||||
| Filter::Class(_)
|
||||
| Filter::Entry(_)
|
||||
| Filter::Type(_) => name.to_string(),
|
||||
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_types::{Halving, OutputType, Sats, Year};
|
||||
|
||||
use super::{AmountFilter, CohortContext, Term, TimeFilter};
|
||||
use super::{AmountFilter, CohortContext, EntryPrice, Term, TimeFilter};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Filter {
|
||||
@@ -10,6 +10,7 @@ pub enum Filter {
|
||||
Amount(AmountFilter),
|
||||
Epoch(Halving),
|
||||
Class(Year),
|
||||
Entry(EntryPrice),
|
||||
Type(OutputType),
|
||||
}
|
||||
|
||||
@@ -68,7 +69,8 @@ impl Filter {
|
||||
}
|
||||
|
||||
/// Whether to compute extended metrics (realized cap ratios, profit/loss ratios, percentiles)
|
||||
/// For UTXO context: true only for age range cohorts (Range) and aggregate cohorts (All, Term)
|
||||
/// For UTXO context: true for age range cohorts (Range), aggregate cohorts (All, Term),
|
||||
/// and immutable entry valuation cohorts.
|
||||
/// For address context: always false
|
||||
pub fn is_extended(&self, context: CohortContext) -> bool {
|
||||
match context {
|
||||
@@ -76,7 +78,10 @@ impl Filter {
|
||||
CohortContext::Utxo => {
|
||||
matches!(
|
||||
self,
|
||||
Filter::All | Filter::Term(_) | Filter::Time(TimeFilter::Range(_))
|
||||
Filter::All
|
||||
| Filter::Term(_)
|
||||
| Filter::Time(TimeFilter::Range(_))
|
||||
| Filter::Entry(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ mod amount_range;
|
||||
mod by_addr_type;
|
||||
mod by_any_addr;
|
||||
mod by_epoch;
|
||||
mod by_entry;
|
||||
mod by_term;
|
||||
mod by_type;
|
||||
mod class;
|
||||
@@ -36,6 +37,7 @@ pub use amount_range::*;
|
||||
pub use by_addr_type::*;
|
||||
pub use by_any_addr::*;
|
||||
pub use by_epoch::*;
|
||||
pub use by_entry::*;
|
||||
pub use by_term::*;
|
||||
pub use by_type::*;
|
||||
pub use class::*;
|
||||
|
||||
@@ -2,8 +2,8 @@ use brk_traversable::Traversable;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::{
|
||||
AgeRange, AmountRange, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount, SpendableType,
|
||||
UnderAge, UnderAmount,
|
||||
AgeRange, AmountRange, ByEntry, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount,
|
||||
SpendableType, UnderAge, UnderAmount,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
@@ -12,6 +12,7 @@ pub struct UTXOGroups<T> {
|
||||
pub age_range: AgeRange<T>,
|
||||
pub epoch: ByEpoch<T>,
|
||||
pub class: Class<T>,
|
||||
pub entry: ByEntry<T>,
|
||||
pub over_age: OverAge<T>,
|
||||
pub over_amount: OverAmount<T>,
|
||||
pub amount_range: AmountRange<T>,
|
||||
@@ -31,6 +32,7 @@ impl<T> UTXOGroups<T> {
|
||||
age_range: AgeRange::new(&mut create),
|
||||
epoch: ByEpoch::new(&mut create),
|
||||
class: Class::new(&mut create),
|
||||
entry: ByEntry::new(&mut create),
|
||||
over_age: OverAge::new(&mut create),
|
||||
over_amount: OverAmount::new(&mut create),
|
||||
amount_range: AmountRange::new(&mut create),
|
||||
@@ -51,6 +53,7 @@ impl<T> UTXOGroups<T> {
|
||||
.chain(self.age_range.iter())
|
||||
.chain(self.epoch.iter())
|
||||
.chain(self.class.iter())
|
||||
.chain(self.entry.iter())
|
||||
.chain(self.amount_range.iter())
|
||||
.chain(self.under_amount.iter())
|
||||
.chain(self.type_.iter())
|
||||
@@ -66,6 +69,7 @@ impl<T> UTXOGroups<T> {
|
||||
.chain(self.age_range.iter_mut())
|
||||
.chain(self.epoch.iter_mut())
|
||||
.chain(self.class.iter_mut())
|
||||
.chain(self.entry.iter_mut())
|
||||
.chain(self.amount_range.iter_mut())
|
||||
.chain(self.under_amount.iter_mut())
|
||||
.chain(self.type_.iter_mut())
|
||||
@@ -84,6 +88,7 @@ impl<T> UTXOGroups<T> {
|
||||
.chain(self.age_range.par_iter_mut())
|
||||
.chain(self.epoch.par_iter_mut())
|
||||
.chain(self.class.par_iter_mut())
|
||||
.chain(self.entry.par_iter_mut())
|
||||
.chain(self.amount_range.par_iter_mut())
|
||||
.chain(self.under_amount.par_iter_mut())
|
||||
.chain(self.type_.par_iter_mut())
|
||||
@@ -94,6 +99,7 @@ impl<T> UTXOGroups<T> {
|
||||
.iter()
|
||||
.chain(self.epoch.iter())
|
||||
.chain(self.class.iter())
|
||||
.chain(self.entry.iter())
|
||||
.chain(self.amount_range.iter())
|
||||
.chain(self.type_.iter())
|
||||
}
|
||||
@@ -103,6 +109,7 @@ impl<T> UTXOGroups<T> {
|
||||
.iter_mut()
|
||||
.chain(self.epoch.iter_mut())
|
||||
.chain(self.class.iter_mut())
|
||||
.chain(self.entry.iter_mut())
|
||||
.chain(self.amount_range.iter_mut())
|
||||
.chain(self.type_.iter_mut())
|
||||
}
|
||||
@@ -115,6 +122,7 @@ impl<T> UTXOGroups<T> {
|
||||
.par_iter_mut()
|
||||
.chain(self.epoch.par_iter_mut())
|
||||
.chain(self.class.par_iter_mut())
|
||||
.chain(self.entry.par_iter_mut())
|
||||
.chain(self.amount_range.par_iter_mut())
|
||||
.chain(self.type_.par_iter_mut())
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, distribution, mining, prices, supply};
|
||||
use crate::{blocks, distribution, mining, price, supply};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
mining: &mining::Vecs,
|
||||
supply_vecs: &supply::Vecs,
|
||||
|
||||
@@ -5,14 +5,14 @@ use vecdb::Exit;
|
||||
|
||||
use super::super::{activity, cap, supply};
|
||||
use super::Vecs;
|
||||
use crate::{distribution, prices};
|
||||
use crate::{distribution, price};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
distribution: &distribution::Vecs,
|
||||
activity: &activity::Vecs,
|
||||
supply: &supply::Vecs,
|
||||
|
||||
@@ -4,14 +4,14 @@ use brk_types::StoredF64;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::{super::value, Vecs};
|
||||
use crate::{blocks, internal::algo::ComputeRollingMedianFromStarts, prices};
|
||||
use crate::{blocks, internal::algo::ComputeRollingMedianFromStarts, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
value: &value::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -4,13 +4,13 @@ use vecdb::Exit;
|
||||
|
||||
use super::super::activity;
|
||||
use super::Vecs;
|
||||
use crate::{distribution, prices};
|
||||
use crate::{distribution, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
distribution: &distribution::Vecs,
|
||||
activity: &activity::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -5,13 +5,13 @@ use vecdb::Exit;
|
||||
|
||||
use super::super::activity;
|
||||
use super::Vecs;
|
||||
use crate::{distribution, prices};
|
||||
use crate::{distribution, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
distribution: &distribution::Vecs,
|
||||
activity: &activity::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -45,7 +45,7 @@ use super::{
|
||||
count::AddrCountFundedTotalVecs,
|
||||
supply::{AddrSupplyShareVecs, AddrSupplyVecs},
|
||||
};
|
||||
use crate::{indexes, prices};
|
||||
use crate::{indexes, price};
|
||||
|
||||
mod state;
|
||||
|
||||
@@ -104,7 +104,7 @@ impl ExposedAddrVecs {
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
starting_lengths: &Lengths,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -35,7 +35,7 @@ use super::{
|
||||
use crate::{
|
||||
indexes, inputs,
|
||||
internal::{WindowStartVec, Windows},
|
||||
outputs, prices,
|
||||
outputs, price,
|
||||
};
|
||||
|
||||
mod state;
|
||||
@@ -112,7 +112,7 @@ impl ReusedAddrVecs {
|
||||
starting_lengths: &Lengths,
|
||||
outputs_by_type: &outputs::ByTypeVecs,
|
||||
inputs_by_type: &inputs::ByTypeVecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
distribution::DynCohortVecs,
|
||||
indexes,
|
||||
internal::{WindowStartVec, Windows},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::{super::traits::CohortVecs, vecs::AddrCohortVecs};
|
||||
@@ -95,7 +95,7 @@ impl AddrCohorts {
|
||||
/// First phase of post-processing: compute index transforms.
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -108,7 +108,7 @@ impl AddrCohorts {
|
||||
/// Second phase of post-processing: compute relative metrics.
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
distribution::state::{AddrCohortState, MinimalRealizedState},
|
||||
indexes,
|
||||
internal::{PerBlockWithDeltas, WindowStartVec, Windows},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, MinimalCohortMetrics};
|
||||
@@ -174,7 +174,7 @@ impl DynCohortVecs for AddrCohortVecs {
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -229,7 +229,7 @@ impl CohortVecs for AddrCohortVecs {
|
||||
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use brk_indexer::Lengths;
|
||||
use brk_types::{Cents, Height, Sats, StoredU64, Version};
|
||||
use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use crate::prices;
|
||||
use crate::price;
|
||||
|
||||
/// Dynamic dispatch trait for cohort vectors.
|
||||
///
|
||||
@@ -31,7 +31,7 @@ pub trait DynCohortVecs: Send + Sync {
|
||||
/// First phase of post-processing computations.
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
@@ -61,7 +61,7 @@ pub trait CohortVecs: DynCohortVecs {
|
||||
/// Second phase of post-processing computations.
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -30,18 +30,34 @@ const TREE_SIZE: usize = TIER0_COUNT + TIER1_COUNT + OVERFLOW; // 190,001
|
||||
pub(super) struct CostBasisNode {
|
||||
all_sats: i64,
|
||||
sth_sats: i64,
|
||||
discount_sats: i64,
|
||||
all_usd: i128,
|
||||
sth_usd: i128,
|
||||
discount_usd: i128,
|
||||
}
|
||||
|
||||
impl CostBasisNode {
|
||||
#[inline(always)]
|
||||
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
|
||||
fn new_supply(sats: i64, usd: i128, is_sth: bool) -> Self {
|
||||
Self {
|
||||
all_sats: sats,
|
||||
sth_sats: if is_sth { sats } else { 0 },
|
||||
discount_sats: 0,
|
||||
all_usd: usd,
|
||||
sth_usd: if is_sth { usd } else { 0 },
|
||||
discount_usd: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn new_discount(sats: i64, usd: i128) -> Self {
|
||||
Self {
|
||||
all_sats: 0,
|
||||
sth_sats: 0,
|
||||
discount_sats: sats,
|
||||
all_usd: 0,
|
||||
sth_usd: 0,
|
||||
discount_usd: usd,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,8 +67,10 @@ impl FenwickNode for CostBasisNode {
|
||||
fn add_assign(&mut self, other: &Self) {
|
||||
self.all_sats += other.all_sats;
|
||||
self.sth_sats += other.sth_sats;
|
||||
self.discount_sats += other.discount_sats;
|
||||
self.all_usd += other.all_usd;
|
||||
self.sth_usd += other.sth_usd;
|
||||
self.discount_usd += other.discount_usd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,16 +169,34 @@ impl CostBasisFenwick {
|
||||
}
|
||||
let bucket = price_to_bucket(price);
|
||||
let delta =
|
||||
CostBasisNode::new(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
|
||||
CostBasisNode::new_supply(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
|
||||
self.tree.add(bucket, &delta);
|
||||
self.totals.add_assign(&delta);
|
||||
}
|
||||
|
||||
/// Bulk-initialize from BTreeMaps (one per age-range cohort).
|
||||
/// Call after state import when all pending maps have been drained.
|
||||
pub(super) fn bulk_init<'a>(
|
||||
/// Apply a net delta from the discount-entry cohort.
|
||||
///
|
||||
/// Supply totals are maintained from the age-range cohorts; this updates
|
||||
/// only the discount-entry partition so premium can be derived as all - discount.
|
||||
pub(super) fn apply_discount_delta(&mut self, price: CentsCompact, pending: &PendingDelta) {
|
||||
let net_sats = u64::from(pending.inc) as i64 - u64::from(pending.dec) as i64;
|
||||
if net_sats == 0 {
|
||||
return;
|
||||
}
|
||||
let bucket = price_to_bucket(price);
|
||||
let delta =
|
||||
CostBasisNode::new_discount(net_sats, price.as_u128() as i128 * net_sats as i128);
|
||||
self.tree.add(bucket, &delta);
|
||||
self.totals.add_assign(&delta);
|
||||
}
|
||||
|
||||
/// Bulk-initialize from age-range maps plus the discount-entry map.
|
||||
/// Age-range maps maintain all/STH/LTH totals; the discount-entry map
|
||||
/// maintains only the discount partition used to derive premium.
|
||||
pub(super) fn bulk_init_with_discount<'a>(
|
||||
&mut self,
|
||||
maps: impl Iterator<Item = (&'a std::collections::BTreeMap<CentsCompact, Sats>, bool)>,
|
||||
discount_maps: impl Iterator<Item = &'a std::collections::BTreeMap<CentsCompact, Sats>>,
|
||||
) {
|
||||
self.tree.reset();
|
||||
self.totals = CostBasisNode::default();
|
||||
@@ -169,7 +205,18 @@ impl CostBasisFenwick {
|
||||
for (&price, &sats) in map.iter() {
|
||||
let bucket = price_to_bucket(price);
|
||||
let s = u64::from(sats) as i64;
|
||||
let node = CostBasisNode::new(s, price.as_u128() as i128 * s as i128, is_sth);
|
||||
let node =
|
||||
CostBasisNode::new_supply(s, price.as_u128() as i128 * s as i128, is_sth);
|
||||
self.tree.add_raw(bucket, &node);
|
||||
self.totals.add_assign(&node);
|
||||
}
|
||||
}
|
||||
|
||||
for map in discount_maps {
|
||||
for (&price, &sats) in map.iter() {
|
||||
let bucket = price_to_bucket(price);
|
||||
let s = u64::from(sats) as i64;
|
||||
let node = CostBasisNode::new_discount(s, price.as_u128() as i128 * s as i128);
|
||||
self.tree.add_raw(bucket, &node);
|
||||
self.totals.add_assign(&node);
|
||||
}
|
||||
@@ -212,6 +259,26 @@ impl CostBasisFenwick {
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute percentile prices for discount-entry cohort.
|
||||
pub(super) fn percentiles_discount_entry(&self) -> PercentileResult {
|
||||
self.compute_percentiles(
|
||||
self.totals.discount_sats,
|
||||
self.totals.discount_usd,
|
||||
|n| n.discount_sats,
|
||||
|n| n.discount_usd,
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute percentile prices for premium-entry cohort (all - discount).
|
||||
pub(super) fn percentiles_premium_entry(&self) -> PercentileResult {
|
||||
self.compute_percentiles(
|
||||
self.totals.all_sats - self.totals.discount_sats,
|
||||
self.totals.all_usd - self.totals.discount_usd,
|
||||
|n| n.all_sats - n.discount_sats,
|
||||
|n| n.all_usd - n.discount_usd,
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_percentiles(
|
||||
&self,
|
||||
total_sats: i64,
|
||||
@@ -271,6 +338,37 @@ impl CostBasisFenwick {
|
||||
return (0, 0, 0);
|
||||
}
|
||||
|
||||
let range = self.density_range(spot_price);
|
||||
let all_range = range.all_sats.max(0);
|
||||
let sth_range = range.sth_sats.max(0);
|
||||
let lth_range = all_range - sth_range;
|
||||
|
||||
let lth_total = self.totals.all_sats - self.totals.sth_sats;
|
||||
(
|
||||
Self::to_bps(all_range, self.totals.all_sats),
|
||||
Self::to_bps(sth_range, self.totals.sth_sats),
|
||||
Self::to_bps(lth_range, lth_total),
|
||||
)
|
||||
}
|
||||
|
||||
/// Compute supply density for entry cohorts: (discount_bps, premium_bps).
|
||||
pub(super) fn entry_density(&self, spot_price: Cents) -> (u16, u16) {
|
||||
if self.totals.all_sats <= 0 {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let range = self.density_range(spot_price);
|
||||
let discount_range = range.discount_sats.max(0);
|
||||
let premium_range = range.all_sats.max(0) - discount_range;
|
||||
let premium_total = self.totals.all_sats - self.totals.discount_sats;
|
||||
|
||||
(
|
||||
Self::to_bps(discount_range, self.totals.discount_sats),
|
||||
Self::to_bps(premium_range, premium_total),
|
||||
)
|
||||
}
|
||||
|
||||
fn density_range(&self, spot_price: Cents) -> CostBasisNode {
|
||||
let spot_f64 = u64::from(spot_price) as f64;
|
||||
let low = Cents::from((spot_f64 * 0.95) as u64);
|
||||
let high = Cents::from((spot_f64 * 1.05) as u64);
|
||||
@@ -285,24 +383,23 @@ impl CostBasisFenwick {
|
||||
CostBasisNode::default()
|
||||
};
|
||||
|
||||
let all_range = (cum_high.all_sats - cum_low.all_sats).max(0);
|
||||
let sth_range = (cum_high.sth_sats - cum_low.sth_sats).max(0);
|
||||
let lth_range = all_range - sth_range;
|
||||
CostBasisNode {
|
||||
all_sats: cum_high.all_sats - cum_low.all_sats,
|
||||
sth_sats: cum_high.sth_sats - cum_low.sth_sats,
|
||||
discount_sats: cum_high.discount_sats - cum_low.discount_sats,
|
||||
all_usd: cum_high.all_usd - cum_low.all_usd,
|
||||
sth_usd: cum_high.sth_usd - cum_low.sth_usd,
|
||||
discount_usd: cum_high.discount_usd - cum_low.discount_usd,
|
||||
}
|
||||
}
|
||||
|
||||
let to_bps = |range: i64, total: i64| -> u16 {
|
||||
if total <= 0 {
|
||||
0
|
||||
} else {
|
||||
(range as f64 / total as f64 * 10000.0).round() as u16
|
||||
}
|
||||
};
|
||||
|
||||
let lth_total = self.totals.all_sats - self.totals.sth_sats;
|
||||
(
|
||||
to_bps(all_range, self.totals.all_sats),
|
||||
to_bps(sth_range, self.totals.sth_sats),
|
||||
to_bps(lth_range, lth_total),
|
||||
)
|
||||
#[inline(always)]
|
||||
fn to_bps(range: i64, total: i64) -> u16 {
|
||||
if total <= 0 {
|
||||
0
|
||||
} else {
|
||||
(range as f64 / total as f64 * 10000.0).round() as u16
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::path::Path;
|
||||
|
||||
use brk_cohort::{
|
||||
AgeRange, AmountRange, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge, OverAmount,
|
||||
SpendableType, Term, UnderAge, UnderAmount,
|
||||
AgeRange, AmountRange, ByEntry, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge,
|
||||
OverAmount, SpendableType, Term, UnderAge, UnderAmount,
|
||||
};
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Lengths;
|
||||
@@ -16,7 +16,6 @@ use vecdb::{
|
||||
use crate::{
|
||||
blocks,
|
||||
distribution::{
|
||||
DynCohortVecs,
|
||||
metrics::{
|
||||
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
|
||||
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
|
||||
@@ -24,10 +23,11 @@ use crate::{
|
||||
TypeCohortMetrics,
|
||||
},
|
||||
state::UTXOCohortState,
|
||||
DynCohortVecs,
|
||||
},
|
||||
indexes,
|
||||
internal::{ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::{fenwick::CostBasisFenwick, vecs::UTXOCohortVecs};
|
||||
@@ -45,6 +45,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
|
||||
pub over_age: OverAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub class: Class<UTXOCohortVecs<CoreCohortMetrics<M>>>,
|
||||
pub entry: ByEntry<UTXOCohortVecs<ExtendedCohortMetrics<M>>>,
|
||||
pub over_amount: OverAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
pub amount_range: AmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
pub under_amount: UnderAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
|
||||
@@ -67,8 +68,10 @@ pub(crate) struct UTXOCohortsTransientState {
|
||||
}
|
||||
|
||||
impl UTXOCohorts<Rw> {
|
||||
/// ~71 separate cohorts (21 age + 5 epoch + 18 class + 15 amount + 12 type)
|
||||
const SEPARATE_COHORT_CAPACITY: usize = 80;
|
||||
/// Separate cohorts currently total 72:
|
||||
/// 21 age + 5 epoch + 18 class + 2 entry + 15 amount + 11 spendable type.
|
||||
/// Keep small headroom because this is only Vec allocation capacity.
|
||||
const SEPARATE_COHORT_CAPACITY: usize = 82;
|
||||
|
||||
/// Import all UTXO cohorts from database.
|
||||
pub(crate) fn forced_import(
|
||||
@@ -136,6 +139,26 @@ impl UTXOCohorts<Rw> {
|
||||
let epoch = ByEpoch::try_new(&core_separate)?;
|
||||
let class = Class::try_new(&core_separate)?;
|
||||
|
||||
let extended_separate =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<ExtendedCohortMetrics>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
filter: &f,
|
||||
full_name: &full_name,
|
||||
version: v,
|
||||
indexes,
|
||||
cached_starts,
|
||||
};
|
||||
let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name)));
|
||||
Ok(UTXOCohortVecs::new(
|
||||
state,
|
||||
ExtendedCohortMetrics::forced_import(&cfg)?,
|
||||
))
|
||||
};
|
||||
|
||||
let entry = ByEntry::try_new(&extended_separate)?;
|
||||
|
||||
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
|
||||
let minimal_separate =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
|
||||
@@ -281,6 +304,7 @@ impl UTXOCohorts<Rw> {
|
||||
lth,
|
||||
epoch,
|
||||
class,
|
||||
entry,
|
||||
type_,
|
||||
under_age,
|
||||
over_age,
|
||||
@@ -309,6 +333,7 @@ impl UTXOCohorts<Rw> {
|
||||
sth,
|
||||
caches,
|
||||
age_range,
|
||||
entry,
|
||||
..
|
||||
} = self;
|
||||
caches
|
||||
@@ -327,7 +352,15 @@ impl UTXOCohorts<Rw> {
|
||||
Some((map, caches.fenwick.is_sth_at(i)))
|
||||
})
|
||||
.collect();
|
||||
caches.fenwick.bulk_init(maps.into_iter());
|
||||
let discount_maps = entry
|
||||
.discount
|
||||
.state
|
||||
.as_ref()
|
||||
.map(|state| state.cost_basis_map())
|
||||
.into_iter();
|
||||
caches
|
||||
.fenwick
|
||||
.bulk_init_with_discount(maps.into_iter(), discount_maps);
|
||||
}
|
||||
|
||||
/// Apply pending deltas from all age-range cohorts to the Fenwick tree.
|
||||
@@ -338,7 +371,10 @@ impl UTXOCohorts<Rw> {
|
||||
}
|
||||
// Destructure to get separate borrows on caches and age_range
|
||||
let Self {
|
||||
caches, age_range, ..
|
||||
caches,
|
||||
age_range,
|
||||
entry,
|
||||
..
|
||||
} = self;
|
||||
for (i, sub) in age_range.iter().enumerate() {
|
||||
if let Some(state) = sub.state.as_ref() {
|
||||
@@ -348,6 +384,11 @@ impl UTXOCohorts<Rw> {
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(state) = entry.discount.state.as_ref() {
|
||||
state.for_each_cost_basis_pending(|&price, delta| {
|
||||
caches.fenwick.apply_discount_delta(price, delta);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Push maturation sats to the matured vecs for the given height.
|
||||
@@ -365,6 +406,7 @@ impl UTXOCohorts<Rw> {
|
||||
age_range,
|
||||
epoch,
|
||||
class,
|
||||
entry,
|
||||
amount_range,
|
||||
type_,
|
||||
..
|
||||
@@ -374,6 +416,7 @@ impl UTXOCohorts<Rw> {
|
||||
.map(|x| x as &mut dyn DynCohortVecs)
|
||||
.chain(epoch.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(class.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(entry.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(
|
||||
amount_range
|
||||
.par_iter_mut()
|
||||
@@ -389,6 +432,7 @@ impl UTXOCohorts<Rw> {
|
||||
age_range,
|
||||
epoch,
|
||||
class,
|
||||
entry,
|
||||
amount_range,
|
||||
type_,
|
||||
..
|
||||
@@ -398,6 +442,7 @@ impl UTXOCohorts<Rw> {
|
||||
.map(|x| x as &mut dyn DynCohortVecs)
|
||||
.chain(epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(class.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(amount_range.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
.chain(type_.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
|
||||
}
|
||||
@@ -409,6 +454,7 @@ impl UTXOCohorts<Rw> {
|
||||
.map(|x| x as &dyn DynCohortVecs)
|
||||
.chain(self.epoch.iter().map(|x| x as &dyn DynCohortVecs))
|
||||
.chain(self.class.iter().map(|x| x as &dyn DynCohortVecs))
|
||||
.chain(self.entry.iter().map(|x| x as &dyn DynCohortVecs))
|
||||
.chain(self.amount_range.iter().map(|x| x as &dyn DynCohortVecs))
|
||||
.chain(self.type_.iter().map(|x| x as &dyn DynCohortVecs))
|
||||
}
|
||||
@@ -483,7 +529,7 @@ impl UTXOCohorts<Rw> {
|
||||
/// First phase of post-processing: compute index transforms.
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -516,6 +562,7 @@ impl UTXOCohorts<Rw> {
|
||||
);
|
||||
all.extend(self.epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
||||
all.extend(self.class.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
||||
all.extend(self.entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
|
||||
all.extend(
|
||||
self.amount_range
|
||||
.iter_mut()
|
||||
@@ -546,7 +593,7 @@ impl UTXOCohorts<Rw> {
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
@@ -604,6 +651,7 @@ impl UTXOCohorts<Rw> {
|
||||
under_amount,
|
||||
epoch,
|
||||
class,
|
||||
entry,
|
||||
type_,
|
||||
..
|
||||
} = self;
|
||||
@@ -676,6 +724,19 @@ impl UTXOCohorts<Rw> {
|
||||
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
entry.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_lengths,
|
||||
height_to_market_cap,
|
||||
ss,
|
||||
au,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
amount_range.par_iter_mut().try_for_each(|v| {
|
||||
v.metrics
|
||||
@@ -730,6 +791,9 @@ impl UTXOCohorts<Rw> {
|
||||
for v in self.class.iter_mut() {
|
||||
vecs.extend(v.metrics.collect_all_vecs_mut());
|
||||
}
|
||||
for v in self.entry.iter_mut() {
|
||||
vecs.extend(v.metrics.collect_all_vecs_mut());
|
||||
}
|
||||
for v in self.amount_range.iter_mut() {
|
||||
vecs.extend(v.metrics.collect_all_vecs_mut());
|
||||
}
|
||||
@@ -813,7 +877,7 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
|
||||
/// Called during the block loop after separate cohorts' push_state but before reset.
|
||||
pub(crate) fn push_overlapping(&mut self, height_price: Cents) {
|
||||
pub(crate) fn push_overlapping(&mut self, height_price: Cents) -> Cents {
|
||||
let Self {
|
||||
all,
|
||||
sth,
|
||||
@@ -852,7 +916,7 @@ impl UTXOCohorts<Rw> {
|
||||
}
|
||||
}
|
||||
|
||||
all.metrics.realized.push_accum(&all_acc);
|
||||
let all_capitalized_price = all.metrics.realized.push_accum(&all_acc);
|
||||
sth.metrics.realized.push_accum(&sth_acc);
|
||||
lth.metrics.realized.push_accum(<h_acc);
|
||||
|
||||
@@ -880,6 +944,8 @@ impl UTXOCohorts<Rw> {
|
||||
.unrealized
|
||||
.capitalized_cap_in_loss_raw
|
||||
.push(CentsSquaredSats::new(lth_ccap.1));
|
||||
|
||||
all_capitalized_price
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,22 @@ impl UTXOCohorts {
|
||||
let lth = self.caches.fenwick.percentiles_lth();
|
||||
push_cost_basis(<h, lth_d, &mut self.lth.metrics.cost_basis);
|
||||
|
||||
let (discount_d, premium_d) = self.caches.fenwick.entry_density(spot_price);
|
||||
|
||||
let discount = self.caches.fenwick.percentiles_discount_entry();
|
||||
push_cost_basis(
|
||||
&discount,
|
||||
discount_d,
|
||||
&mut self.entry.discount.metrics.cost_basis,
|
||||
);
|
||||
|
||||
let premium = self.caches.fenwick.percentiles_premium_entry();
|
||||
push_cost_basis(
|
||||
&premium,
|
||||
premium_d,
|
||||
&mut self.entry.premium.metrics.cost_basis,
|
||||
);
|
||||
|
||||
let prof = self.caches.fenwick.profitability(spot_price);
|
||||
push_profitability(&prof, &mut self.profitability);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use brk_cohort::EntryPrice;
|
||||
use brk_types::{Cents, CostBasisSnapshot, Height, Timestamp};
|
||||
use vecdb::Rw;
|
||||
|
||||
@@ -12,6 +13,7 @@ impl UTXOCohorts<Rw> {
|
||||
/// - The "under_1h" age cohort (all new UTXOs start at 0 hours old)
|
||||
/// - The appropriate epoch cohort based on block height
|
||||
/// - The appropriate class cohort based on block timestamp
|
||||
/// - The immutable entry valuation cohort based on creation price versus anchor
|
||||
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
|
||||
/// - The appropriate amount range cohort based on value
|
||||
pub(crate) fn receive(
|
||||
@@ -20,13 +22,14 @@ impl UTXOCohorts<Rw> {
|
||||
height: Height,
|
||||
timestamp: Timestamp,
|
||||
price: Cents,
|
||||
entry: EntryPrice,
|
||||
) {
|
||||
let supply_state = received.spendable_supply;
|
||||
|
||||
// Pre-compute snapshot once for the 3 cohorts sharing the same supply_state
|
||||
// Pre-compute snapshot once for cohorts sharing the block-level supply_state
|
||||
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
|
||||
|
||||
// New UTXOs go into under_1h, current epoch, and current class
|
||||
// New UTXOs go into under_1h plus immutable creation cohorts
|
||||
self.age_range
|
||||
.under_1h
|
||||
.state
|
||||
@@ -45,6 +48,12 @@ impl UTXOCohorts<Rw> {
|
||||
.unwrap()
|
||||
.receive_utxo_snapshot(&supply_state, &snapshot);
|
||||
}
|
||||
self.entry
|
||||
.get_mut(entry)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.receive_utxo_snapshot(&supply_state, &snapshot);
|
||||
|
||||
// Update output type cohorts (skip types with no outputs this block)
|
||||
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
|
||||
|
||||
@@ -49,7 +49,7 @@ impl UTXOCohorts<Rw> {
|
||||
// This is the max price between receive and send heights
|
||||
let peak_price = price_range_max.max_between(receive_height, send_height);
|
||||
|
||||
// Pre-compute once for age_range, epoch, year (all share sent.spendable_supply)
|
||||
// Pre-compute once for cohorts sharing the sent supply.
|
||||
if let Some(pre) = SendPrecomputed::new(
|
||||
&sent.spendable_supply,
|
||||
current_price,
|
||||
@@ -75,6 +75,12 @@ impl UTXOCohorts<Rw> {
|
||||
.unwrap()
|
||||
.send_utxo_precomputed(&sent.spendable_supply, &pre);
|
||||
}
|
||||
self.entry
|
||||
.get_mut(block_state.entry)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send_utxo_precomputed(&sent.spendable_supply, &pre);
|
||||
} else if sent.spendable_supply.utxo_count > 0 {
|
||||
// Zero-value UTXOs: just subtract supply
|
||||
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
|
||||
@@ -85,6 +91,12 @@ impl UTXOCohorts<Rw> {
|
||||
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
|
||||
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
|
||||
}
|
||||
self.entry
|
||||
.get_mut(block_state.entry)
|
||||
.state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.supply -= &sent.spendable_supply;
|
||||
}
|
||||
|
||||
// Update output type cohorts (skip zero-supply entries)
|
||||
|
||||
@@ -6,7 +6,7 @@ use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use crate::{
|
||||
distribution::{cohorts::traits::DynCohortVecs, metrics::CoreCohortMetrics},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::UTXOCohortVecs;
|
||||
@@ -56,7 +56,7 @@ impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use crate::{
|
||||
distribution::{cohorts::traits::DynCohortVecs, metrics::MinimalCohortMetrics},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::UTXOCohortVecs;
|
||||
@@ -49,7 +49,7 @@ impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -55,7 +55,7 @@ use crate::{
|
||||
metrics::{CohortMetricsBase, CohortMetricsState},
|
||||
state::UTXOCohortState,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -186,7 +186,7 @@ impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -5,7 +5,7 @@ use brk_types::{Cents, Height, Version};
|
||||
use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use crate::{
|
||||
distribution::cohorts::traits::DynCohortVecs, distribution::metrics::TypeCohortMetrics, prices,
|
||||
distribution::cohorts::traits::DynCohortVecs, distribution::metrics::TypeCohortMetrics, price,
|
||||
};
|
||||
|
||||
use super::UTXOCohortVecs;
|
||||
@@ -55,7 +55,7 @@ impl DynCohortVecs for UTXOCohortVecs<TypeCohortMetrics> {
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_cohort::{ByAddrType, EntryPrice};
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_types::{
|
||||
@@ -46,6 +46,7 @@ pub(crate) fn process_blocks(
|
||||
last_height: Height,
|
||||
chain_state: &mut Vec<BlockState>,
|
||||
tx_index_to_height: &mut RangeMap<TxIndex, Height>,
|
||||
mut entry_anchor: Cents,
|
||||
cached_prices: &[Cents],
|
||||
cached_timestamps: &[Timestamp],
|
||||
cached_price_range_max: &PriceRangeMax,
|
||||
@@ -370,9 +371,14 @@ pub(crate) fn process_blocks(
|
||||
.iterate(Sats::FIFTY_BTC, OutputType::P2PK65);
|
||||
}
|
||||
|
||||
let entry = EntryPrice::from_is_discount(
|
||||
entry_anchor == Cents::ZERO || block_price <= entry_anchor,
|
||||
);
|
||||
|
||||
// Push current block state before processing cohort updates
|
||||
chain_state.push(BlockState {
|
||||
supply: transacted.spendable_supply,
|
||||
entry,
|
||||
price: block_price,
|
||||
timestamp,
|
||||
});
|
||||
@@ -411,7 +417,7 @@ pub(crate) fn process_blocks(
|
||||
|| {
|
||||
// UTXO cohorts receive/send
|
||||
vecs.utxo_cohorts
|
||||
.receive(transacted, height, timestamp, block_price);
|
||||
.receive(transacted, height, timestamp, block_price, entry);
|
||||
if let Some(min_h) =
|
||||
vecs.utxo_cohorts
|
||||
.send(height_to_sent, chain_state, ctx.price_range_max)
|
||||
@@ -460,7 +466,7 @@ pub(crate) fn process_blocks(
|
||||
let is_last_of_day = is_last_of_day[offset];
|
||||
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
|
||||
|
||||
push_cohort_states(
|
||||
entry_anchor = push_cohort_states(
|
||||
&mut vecs.utxo_cohorts,
|
||||
&mut vecs.addr_cohorts,
|
||||
height,
|
||||
@@ -527,7 +533,7 @@ fn push_cohort_states(
|
||||
addr_cohorts: &mut AddrCohorts,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
) {
|
||||
) -> Cents {
|
||||
// Phase 1: push + unrealized (no reset yet, states still needed for aggregation)
|
||||
rayon::join(
|
||||
|| {
|
||||
@@ -545,7 +551,7 @@ fn push_cohort_states(
|
||||
);
|
||||
|
||||
// Phase 2: aggregate age_range states → push to overlapping cohorts
|
||||
utxo_cohorts.push_overlapping(height_price);
|
||||
let all_capitalized_price = utxo_cohorts.push_overlapping(height_price);
|
||||
|
||||
// Phase 3: reset per-block values
|
||||
utxo_cohorts
|
||||
@@ -554,4 +560,6 @@ fn push_cohort_states(
|
||||
addr_cohorts
|
||||
.iter_separate_mut()
|
||||
.for_each(|v| v.reset_single_iteration_values());
|
||||
|
||||
all_capitalized_price
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
state::{CohortState, CostBasisOps, RealizedOps},
|
||||
},
|
||||
internal::{PerBlockCumulativeRolling, ValuePerBlockCumulativeRolling},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::ActivityMinimal;
|
||||
@@ -98,7 +98,7 @@ impl ActivityCore {
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
metrics::ImportConfig,
|
||||
state::{CohortState, CostBasisOps, RealizedOps},
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::ActivityCore;
|
||||
@@ -89,7 +89,7 @@ impl ActivityFull {
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
state::{CohortState, CostBasisOps, RealizedOps},
|
||||
},
|
||||
internal::ValuePerBlockCumulativeRolling,
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -63,7 +63,7 @@ impl ActivityMinimal {
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -13,7 +13,7 @@ use vecdb::Exit;
|
||||
|
||||
use crate::{
|
||||
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
pub trait ActivityLike: Send + Sync {
|
||||
@@ -30,7 +30,7 @@ pub trait ActivityLike: Send + Sync {
|
||||
) -> Result<()>;
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
@@ -62,7 +62,7 @@ impl ActivityLike for ActivityCore {
|
||||
}
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -96,7 +96,7 @@ impl ActivityLike for ActivityFull {
|
||||
}
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
ActivityFull, AdjustedSopr, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase,
|
||||
RealizedFull, RelativeForAll, SupplyCore, UnrealizedFull,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// All-cohort metrics: extended realized + adjusted (as composable add-on),
|
||||
@@ -100,7 +100,7 @@ impl AllCohortMetrics {
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
under_1h_value_created: &impl ReadableVec<Height, Cents>,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
|
||||
UnrealizedCore,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// Basic cohort metrics: no extensions, used by age_range cohorts.
|
||||
@@ -61,7 +61,7 @@ impl BasicCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
ActivityCore, CohortMetricsBase, ImportConfig, OutputsBase, RealizedCore, SupplyCore,
|
||||
UnrealizedCore,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -102,7 +102,7 @@ impl CoreCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -122,7 +122,7 @@ impl CoreCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsBase, RealizedFull,
|
||||
RelativeWithExtended, SupplyCore, UnrealizedFull,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// Cohort metrics with extended realized + extended cost basis (no adjusted).
|
||||
@@ -90,7 +90,7 @@ impl ExtendedCohortMetrics {
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
distribution::metrics::{
|
||||
ActivityFull, AdjustedSopr, CohortMetricsBase, ImportConfig, RealizedFull, UnrealizedFull,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use super::ExtendedCohortMetrics;
|
||||
@@ -62,7 +62,7 @@ impl ExtendedAdjustedCohortMetrics {
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
under_1h_value_created: &impl ReadableVec<Height, Cents>,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
distribution::metrics::{
|
||||
ActivityMinimal, ImportConfig, OutputsBase, RealizedMinimal, SupplyBase, UnrealizedMinimal,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// MinimalCohortMetrics: supply, outputs, realized cap/price/mvrv/profit/loss + value_created/destroyed.
|
||||
@@ -97,7 +97,7 @@ impl MinimalCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -111,7 +111,7 @@ impl MinimalCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
distribution::metrics::{
|
||||
ActivityMinimal, ImportConfig, OutputsBase, RealizedMinimal, SupplyCore, UnrealizedBasic,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// TypeCohortMetrics: supply(core), outputs(base), realized(minimal), unrealized(basic).
|
||||
@@ -59,7 +59,7 @@ impl TypeCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -73,7 +73,7 @@ impl TypeCohortMetrics {
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -149,7 +149,7 @@ use crate::{
|
||||
CohortState, CoreRealizedState, CostBasisData, CostBasisOps, CostBasisRaw,
|
||||
MinimalRealizedState, RealizedOps, RealizedState, WithCapital, WithoutCapital,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
pub trait CohortMetricsState {
|
||||
@@ -270,7 +270,7 @@ pub trait CohortMetricsBase:
|
||||
/// First phase of computed metrics (indexes from height).
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
internal::{
|
||||
PerBlock, RatioPerBlock, ValuePerBlock, ValuePerBlockWithDeltas, WindowStartVec, Windows,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -115,7 +115,7 @@ impl ProfitabilityBucket {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
is_profit: bool,
|
||||
exit: &Exit,
|
||||
@@ -176,7 +176,7 @@ impl ProfitabilityBucket {
|
||||
|
||||
pub(crate) fn compute_from_ranges(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
is_profit: bool,
|
||||
sources: &[&ProfitabilityBucket],
|
||||
@@ -293,7 +293,7 @@ impl ProfitabilityMetrics {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
FiatPerBlockCumulativeWithSumsAndDeltas, LazyPerBlock, NegCentsUnsignedToDollars,
|
||||
PerBlockCumulativeRolling, RatioCents64, RollingWindow24hPerBlock, Windows,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
@@ -166,7 +166,7 @@ impl RealizedCore {
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
transfer_volume_sum_24h_cents: &impl ReadableVec<Height, Cents>,
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
RatioPerBlockStdDevBands, RatioSma, RollingWindows, RollingWindowsFrom1w,
|
||||
ValuePerBlockCumulativeRolling,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
@@ -206,7 +206,7 @@ impl RealizedFull {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) {
|
||||
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) -> Cents {
|
||||
self.cap_raw.push(accum.cap_raw);
|
||||
self.capitalized.cap_raw.push(accum.capitalized_cap_raw);
|
||||
|
||||
@@ -221,6 +221,8 @@ impl RealizedFull {
|
||||
self.capitalized.price.cents.height.push(capitalized_price);
|
||||
|
||||
self.peak_regret.value.block.cents.push(accum.peak_regret());
|
||||
|
||||
capitalized_price
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
@@ -240,7 +242,7 @@ impl RealizedFull {
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
FiatPerBlockCumulativeWithSums, FiatPerBlockWithDeltas, Identity, LazyPerBlock,
|
||||
PriceWithRatioPerBlock,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
@@ -104,7 +104,7 @@ impl RealizedMinimal {
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -3,7 +3,7 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Sats, StoredU64, Version};
|
||||
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{indexes, internal::ValuePerBlock, prices};
|
||||
use crate::{indexes, internal::ValuePerBlock, price};
|
||||
|
||||
/// Average amount held per UTXO and per funded address.
|
||||
///
|
||||
@@ -53,7 +53,7 @@ impl AvgAmountMetrics {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
utxo_count: &impl ReadableVec<Height, StoredU64>,
|
||||
funded_addr_count: &impl ReadableVec<Height, StoredU64>,
|
||||
|
||||
@@ -6,7 +6,7 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVe
|
||||
|
||||
use crate::{
|
||||
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
use crate::internal::{
|
||||
@@ -64,7 +64,7 @@ impl SupplyBase {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -5,7 +5,7 @@ use brk_types::{Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{distribution::state::UnrealizedState, prices};
|
||||
use crate::{distribution::state::UnrealizedState, price};
|
||||
|
||||
use crate::internal::{
|
||||
HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValuePerBlock, ValuePerBlock,
|
||||
@@ -72,7 +72,7 @@ impl SupplyCore {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode,
|
||||
|
||||
use crate::distribution::state::UnrealizedState;
|
||||
use crate::internal::{CentsSubtractToCentsSigned, FiatPerBlock};
|
||||
use crate::{distribution::metrics::ImportConfig, prices};
|
||||
use crate::{distribution::metrics::ImportConfig, price};
|
||||
|
||||
use super::UnrealizedCore;
|
||||
|
||||
@@ -99,7 +99,7 @@ impl UnrealizedFull {
|
||||
|
||||
pub(crate) fn compute_rest_all(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
|
||||
@@ -13,7 +13,7 @@ use brk_indexer::Lengths;
|
||||
use brk_types::{Height, Sats};
|
||||
use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use crate::{distribution::state::UnrealizedState, prices};
|
||||
use crate::{distribution::state::UnrealizedState, price};
|
||||
|
||||
pub trait UnrealizedLike: Send + Sync {
|
||||
fn as_core(&self) -> &UnrealizedCore;
|
||||
@@ -22,7 +22,7 @@ pub trait UnrealizedLike: Send + Sync {
|
||||
fn push_state(&mut self, state: &UnrealizedState);
|
||||
fn compute_rest(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
@@ -46,7 +46,7 @@ impl UnrealizedLike for UnrealizedCore {
|
||||
}
|
||||
fn compute_rest(
|
||||
&mut self,
|
||||
_prices: &prices::Vecs,
|
||||
_prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
_supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
_supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
@@ -72,7 +72,7 @@ impl UnrealizedLike for UnrealizedFull {
|
||||
}
|
||||
fn compute_rest(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
supply_in_profit_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
supply_in_loss_sats: &(impl ReadableVec<Height, Sats> + Sync),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::ops::{Add, AddAssign, SubAssign};
|
||||
|
||||
use brk_cohort::EntryPrice;
|
||||
use brk_types::{Cents, SupplyState, Timestamp};
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -8,6 +9,8 @@ pub struct BlockState {
|
||||
#[serde(flatten)]
|
||||
pub supply: SupplyState,
|
||||
#[serde(skip)]
|
||||
pub entry: EntryPrice,
|
||||
#[serde(skip)]
|
||||
pub price: Cents,
|
||||
#[serde(skip)]
|
||||
pub timestamp: Timestamp,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use brk_cohort::{ByAddrType, Filter};
|
||||
use brk_cohort::{ByAddrType, EntryPrice, Filter};
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_traversable::Traversable;
|
||||
@@ -29,7 +29,7 @@ use crate::{
|
||||
PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes,
|
||||
db_utils::{finalize_db, open_db},
|
||||
},
|
||||
outputs, prices, transactions,
|
||||
outputs, price, transactions,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -316,7 +316,7 @@ impl Vecs {
|
||||
outputs: &outputs::Vecs,
|
||||
transactions: &transactions::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
@@ -436,13 +436,34 @@ impl Vecs {
|
||||
let end = usize::from(recovered_height);
|
||||
debug!("building supply_state vec for {} heights", recovered_height);
|
||||
let supply_state_data: Vec<_> = self.supply_state.collect_range_at(0, end);
|
||||
let capitalized_price_data: Vec<_> = self
|
||||
.utxo_cohorts
|
||||
.all
|
||||
.metrics
|
||||
.realized
|
||||
.capitalized
|
||||
.price
|
||||
.cents
|
||||
.height
|
||||
.collect_range_at(0, end);
|
||||
|
||||
let mut entry_anchor = Cents::ZERO;
|
||||
chain_state = supply_state_data
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(h, supply)| BlockState {
|
||||
supply,
|
||||
price: self.caches.prices[h],
|
||||
timestamp: self.caches.timestamps[h],
|
||||
.map(|(h, supply)| {
|
||||
let price = self.caches.prices[h];
|
||||
let entry = EntryPrice::from_is_discount(
|
||||
entry_anchor == Cents::ZERO || price <= entry_anchor,
|
||||
);
|
||||
entry_anchor = capitalized_price_data[h];
|
||||
|
||||
BlockState {
|
||||
supply,
|
||||
entry,
|
||||
price,
|
||||
timestamp: self.caches.timestamps[h],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
debug!("chain_state rebuilt");
|
||||
@@ -474,6 +495,20 @@ impl Vecs {
|
||||
let prices = std::mem::take(&mut self.caches.prices);
|
||||
let timestamps = std::mem::take(&mut self.caches.timestamps);
|
||||
let price_range_max = std::mem::take(&mut self.caches.price_range_max);
|
||||
let entry_anchor = starting_height
|
||||
.decremented()
|
||||
.and_then(|height| {
|
||||
self.utxo_cohorts
|
||||
.all
|
||||
.metrics
|
||||
.realized
|
||||
.capitalized
|
||||
.price
|
||||
.cents
|
||||
.height
|
||||
.collect_one(height)
|
||||
})
|
||||
.unwrap_or(Cents::ZERO);
|
||||
|
||||
process_blocks(
|
||||
self,
|
||||
@@ -486,6 +521,7 @@ impl Vecs {
|
||||
last_height,
|
||||
&mut chain_state,
|
||||
&mut tx_index_to_height,
|
||||
entry_anchor,
|
||||
&prices,
|
||||
×tamps,
|
||||
&price_range_max,
|
||||
|
||||
@@ -6,7 +6,7 @@ use brk_traversable::Traversable;
|
||||
use brk_types::Version;
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{distribution, indexes, prices};
|
||||
use crate::{distribution, indexes, price};
|
||||
|
||||
pub use inner::RarityMeterInner;
|
||||
|
||||
@@ -37,7 +37,7 @@ impl RarityMeter {
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
distribution: &distribution::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let realized = &distribution.utxo_cohorts.all.metrics.realized;
|
||||
|
||||
@@ -6,7 +6,7 @@ use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::internal::{LazyPerBlock, PerBlock, Price};
|
||||
use crate::{indexes, prices};
|
||||
use crate::{indexes, price};
|
||||
|
||||
use super::{RatioPerBlock, RatioPerBlockPercentiles};
|
||||
|
||||
@@ -63,7 +63,7 @@ impl PriceWithRatioPerBlock {
|
||||
/// Compute price via closure (in cents), then compute ratio.
|
||||
pub(crate) fn compute_all<F>(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
mut compute_price: F,
|
||||
@@ -101,7 +101,7 @@ impl PriceWithRatioExtendedPerBlock {
|
||||
/// Compute ratio and percentiles from already-computed price cents.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -120,7 +120,7 @@ impl PriceWithRatioExtendedPerBlock {
|
||||
/// Compute price via closure (in cents), then compute ratio and percentiles.
|
||||
pub(crate) fn compute_all<F>(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
starting_lengths: &Lengths,
|
||||
exit: &Exit,
|
||||
mut compute_price: F,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
CentsUnsignedToDollars, LazyPerBlock, NumericValue, PerBlock, SatsSignedToBitcoin,
|
||||
SatsToBitcoin, SatsToCents,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// Trait that associates a sats type with its transform to Bitcoin.
|
||||
@@ -69,7 +69,7 @@ impl ValuePerBlock {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -8,7 +8,7 @@ use vecdb::{
|
||||
|
||||
use crate::{
|
||||
internal::{CentsUnsignedToDollars, SatsToBitcoin, SatsToCents},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
/// Raw per-block amount data: sats + cents (stored), btc + usd (lazy), no resolutions.
|
||||
@@ -44,7 +44,7 @@ impl ValueBlock {
|
||||
pub(crate) fn compute_cents(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.cents.compute_binary::<Sats, Cents, SatsToCents>(
|
||||
|
||||
@@ -6,7 +6,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ValueBlock, ValuePerBlock},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -39,7 +39,7 @@ impl ValuePerBlockCumulative {
|
||||
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
@@ -61,7 +61,7 @@ impl ValuePerBlockCumulative {
|
||||
pub(crate) fn compute_with(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
LazyRollingAvgsAmountFromHeight, LazyRollingSumsAmountFromHeight, ValuePerBlockCumulative,
|
||||
WindowStartVec, Windows,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
@@ -63,7 +63,7 @@ impl ValuePerBlockCumulativeRolling {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
@@ -74,7 +74,7 @@ impl ValuePerBlockCumulativeRolling {
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.inner.compute(prices, max_from, exit)
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
RollingDistributionValuePerBlock, ValuePerBlockCumulativeRolling, WindowStartVec,
|
||||
WindowStarts, Windows,
|
||||
},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
@@ -49,7 +49,7 @@ impl ValuePerBlockFull {
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
windows: &WindowStarts<'_>,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -11,7 +11,7 @@ use rayon::prelude::*;
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, WritableVec};
|
||||
|
||||
use crate::{indexes, prices};
|
||||
use crate::{indexes, price};
|
||||
|
||||
use super::{
|
||||
BpsType, NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, ValuePerBlock,
|
||||
@@ -229,7 +229,7 @@ impl WithAddrTypes<ValuePerBlock> {
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.all.compute(prices, max_from, exit)?;
|
||||
|
||||
@@ -4,7 +4,7 @@ use brk_types::{BasisPointsSigned32, Bitcoin, Cents, Date, Day1, Dollars, Sats};
|
||||
use vecdb::{AnyVec, Exit, ReadableOptionVec, ReadableVec, VecIndex};
|
||||
|
||||
use super::{ByDcaPeriod, Vecs};
|
||||
use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market, prices};
|
||||
use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market, price};
|
||||
|
||||
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
|
||||
|
||||
@@ -13,7 +13,7 @@ impl Vecs {
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
lookback: &market::lookback::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -22,7 +22,7 @@ mod market;
|
||||
mod mining;
|
||||
mod outputs;
|
||||
mod pools;
|
||||
pub mod prices;
|
||||
pub mod price;
|
||||
mod supply;
|
||||
mod transactions;
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct Computer<M: StorageMode = Rw> {
|
||||
pub investing: Box<investing::Vecs<M>>,
|
||||
pub market: Box<market::Vecs<M>>,
|
||||
pub pools: Box<pools::Vecs<M>>,
|
||||
pub prices: Box<prices::Vecs<M>>,
|
||||
pub price: Box<price::Vecs<M>>,
|
||||
#[traversable(flatten)]
|
||||
pub distribution: Box<distribution::Vecs<M>>,
|
||||
pub supply: Box<supply::Vecs<M>>,
|
||||
@@ -66,14 +66,14 @@ impl Computer {
|
||||
)?))
|
||||
})?;
|
||||
|
||||
let (constants, prices) = timed("Imported prices/constants", || -> Result<_> {
|
||||
let (constants, price) = timed("Imported price/constants", || -> Result<_> {
|
||||
let constants = Box::new(constants::Vecs::new(VERSION, &indexes));
|
||||
let prices = Box::new(prices::Vecs::forced_import(
|
||||
let price = Box::new(price::Vecs::forced_import(
|
||||
&computed_path,
|
||||
VERSION,
|
||||
&indexes,
|
||||
)?);
|
||||
Ok((constants, prices))
|
||||
Ok((constants, price))
|
||||
})?;
|
||||
|
||||
let blocks = timed("Imported blocks", || -> Result<_> {
|
||||
@@ -223,7 +223,7 @@ impl Computer {
|
||||
cointime,
|
||||
indexes,
|
||||
inputs,
|
||||
prices,
|
||||
price,
|
||||
outputs,
|
||||
};
|
||||
|
||||
@@ -244,7 +244,7 @@ impl Computer {
|
||||
investing::DB_NAME,
|
||||
market::DB_NAME,
|
||||
pools::DB_NAME,
|
||||
prices::DB_NAME,
|
||||
price::DB_NAME,
|
||||
distribution::DB_NAME,
|
||||
supply::DB_NAME,
|
||||
inputs::DB_NAME,
|
||||
@@ -297,8 +297,8 @@ impl Computer {
|
||||
})
|
||||
},
|
||||
|| {
|
||||
timed("Computed prices", || {
|
||||
self.prices.compute(indexer, &self.indexes, exit)
|
||||
timed("Computed price", || {
|
||||
self.price.compute(indexer, &self.indexes, exit)
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -310,7 +310,7 @@ impl Computer {
|
||||
let market = scope.spawn(|| {
|
||||
timed("Computed market", || {
|
||||
self.market
|
||||
.compute(indexer, &self.prices, &self.indexes, &self.blocks, exit)
|
||||
.compute(indexer, &self.price, &self.indexes, &self.blocks, exit)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -321,7 +321,7 @@ impl Computer {
|
||||
&self.indexes,
|
||||
&self.blocks,
|
||||
&self.inputs,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
exit,
|
||||
)
|
||||
})?;
|
||||
@@ -331,7 +331,7 @@ impl Computer {
|
||||
&self.indexes,
|
||||
&self.blocks,
|
||||
&self.transactions,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
@@ -343,7 +343,7 @@ impl Computer {
|
||||
&self.indexes,
|
||||
&self.inputs,
|
||||
&self.blocks,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
exit,
|
||||
)
|
||||
})?;
|
||||
@@ -360,7 +360,7 @@ impl Computer {
|
||||
indexer,
|
||||
&self.indexes,
|
||||
&self.blocks,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
&self.mining,
|
||||
exit,
|
||||
)
|
||||
@@ -372,7 +372,7 @@ impl Computer {
|
||||
self.investing.compute(
|
||||
indexer,
|
||||
&self.indexes,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
&self.blocks,
|
||||
&self.market.lookback,
|
||||
exit,
|
||||
@@ -388,7 +388,7 @@ impl Computer {
|
||||
&self.outputs,
|
||||
&self.transactions,
|
||||
&self.blocks,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
exit,
|
||||
)
|
||||
})?;
|
||||
@@ -421,7 +421,7 @@ impl Computer {
|
||||
&self.blocks,
|
||||
&self.mining,
|
||||
&self.transactions,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
&self.distribution,
|
||||
exit,
|
||||
)
|
||||
@@ -430,7 +430,7 @@ impl Computer {
|
||||
timed("Computed cointime", || {
|
||||
self.cointime.compute(
|
||||
indexer,
|
||||
&self.prices,
|
||||
&self.price,
|
||||
&self.blocks,
|
||||
&self.mining,
|
||||
&self.supply,
|
||||
@@ -445,7 +445,7 @@ impl Computer {
|
||||
|
||||
self.indicators
|
||||
.rarity_meter
|
||||
.compute(indexer, &self.distribution, &self.prices, exit)?;
|
||||
.compute(indexer, &self.distribution, &self.price, exit)?;
|
||||
|
||||
info!("Total compute time: {:?}", compute_start.elapsed());
|
||||
Ok(())
|
||||
@@ -498,7 +498,7 @@ impl_iter_named!(
|
||||
investing,
|
||||
market,
|
||||
pools,
|
||||
prices,
|
||||
price,
|
||||
distribution,
|
||||
supply,
|
||||
inputs,
|
||||
|
||||
@@ -4,13 +4,13 @@ use brk_types::{StoredF32, Timestamp};
|
||||
use vecdb::{Exit, ReadableVec, VecIndex};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{indexes, prices};
|
||||
use crate::{indexes, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -2,7 +2,7 @@ use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use crate::{blocks, indexes, prices};
|
||||
use crate::{blocks, indexes, price};
|
||||
|
||||
use super::Vecs;
|
||||
|
||||
@@ -10,7 +10,7 @@ impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
indexes: &indexes::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -3,14 +3,14 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, prices};
|
||||
use crate::{blocks, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let starting_height = indexer.safe_lengths().height;
|
||||
|
||||
@@ -3,14 +3,14 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, prices};
|
||||
use crate::{blocks, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let starting_lengths = indexer.safe_lengths();
|
||||
|
||||
@@ -4,13 +4,13 @@ use brk_types::{BasisPoints16, StoredF32};
|
||||
use vecdb::{Exit, ReadableVec, VecIndex};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, prices};
|
||||
use crate::{blocks, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -5,14 +5,14 @@ use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{
|
||||
blocks, internal::RatioDiffDollarsBps32, investing::ByDcaPeriod, market::lookback, prices,
|
||||
blocks, internal::RatioDiffDollarsBps32, investing::ByDcaPeriod, market::lookback, price,
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
lookback: &lookback::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -10,7 +10,7 @@ use super::{
|
||||
use crate::{
|
||||
blocks,
|
||||
internal::{RatioDollarsBp32, WindowsTo1m},
|
||||
prices,
|
||||
price,
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
@@ -19,7 +19,7 @@ impl Vecs {
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
returns: &returns::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
moving_average: &moving_average::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -3,14 +3,14 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::MacdChain;
|
||||
use crate::{blocks, prices};
|
||||
use crate::{blocks, price};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn compute(
|
||||
chain: &mut MacdChain,
|
||||
indexer: &Indexer,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
fast_days: usize,
|
||||
slow_days: usize,
|
||||
signal_days: usize,
|
||||
|
||||
@@ -3,7 +3,7 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, indexes, prices, transactions};
|
||||
use crate::{blocks, indexes, price, transactions};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -13,7 +13,7 @@ impl Vecs {
|
||||
indexes: &indexes::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
transactions: &transactions::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::Vecs;
|
||||
use crate::{
|
||||
blocks, indexes,
|
||||
internal::{RatioDollarsBp32, RatioSatsBp16},
|
||||
prices, transactions,
|
||||
price, transactions,
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
@@ -18,7 +18,7 @@ impl Vecs {
|
||||
indexes: &indexes::Vecs,
|
||||
lookback: &blocks::LookbackVecs,
|
||||
transactions: &transactions::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let starting_height = indexer.safe_lengths().height;
|
||||
|
||||
@@ -3,7 +3,7 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, indexes, inputs, prices};
|
||||
use crate::{blocks, indexes, inputs, price};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -13,7 +13,7 @@ impl Vecs {
|
||||
indexes: &indexes::Vecs,
|
||||
inputs: &inputs::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
@@ -4,13 +4,13 @@ use brk_types::{Height, OutputType, Sats, TxOutIndex};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::prices;
|
||||
use crate::price;
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let starting_lengths = indexer.safe_lengths();
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
MaskSats, PercentRollingWindows, RatioU64Bp16, ValuePerBlockCumulativeRolling,
|
||||
WindowStartVec, Windows,
|
||||
},
|
||||
mining, prices,
|
||||
mining, price,
|
||||
};
|
||||
|
||||
use super::minor;
|
||||
@@ -63,7 +63,7 @@ impl Vecs {
|
||||
indexer: &Indexer,
|
||||
pool: &impl ReadableVec<Height, PoolSlug>,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
mining: &mining::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
WindowStartVec, Windows,
|
||||
db_utils::{finalize_db, open_db},
|
||||
},
|
||||
mining, prices,
|
||||
mining, price,
|
||||
};
|
||||
|
||||
pub const DB_NAME: &str = "pools";
|
||||
@@ -90,7 +90,7 @@ impl Vecs {
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
mining: &mining::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
+9
-19
@@ -3,8 +3,7 @@ use std::ops::Range;
|
||||
use brk_error::Result;
|
||||
use brk_indexer::{Indexer, Lengths};
|
||||
use brk_oracle::{
|
||||
bin_to_cents, cents_to_bin, for_each_round_dollar_bin, Config, HistogramRaw, Oracle,
|
||||
START_HEIGHT_FAST, START_HEIGHT_SLOW,
|
||||
bin_to_cents, cents_to_bin, Config, Oracle, PaymentFilter, START_HEIGHT_FAST, START_HEIGHT_SLOW,
|
||||
};
|
||||
use brk_types::{Cents, OutputType, Sats, TxIndex, TxOutIndex};
|
||||
use tracing::info;
|
||||
@@ -87,16 +86,11 @@ impl Vecs {
|
||||
.truncate_if_needed_at(truncate_to)?;
|
||||
|
||||
if self.spot.cents.height.len() < START_HEIGHT_SLOW {
|
||||
for line in brk_oracle::PRICES
|
||||
.lines()
|
||||
.skip(self.spot.cents.height.len())
|
||||
{
|
||||
for cents in brk_oracle::pre_oracle_prices_from(self.spot.cents.height.len()) {
|
||||
if self.spot.cents.height.len() >= START_HEIGHT_SLOW {
|
||||
break;
|
||||
}
|
||||
let dollars: f64 = line.parse().unwrap_or(0.0);
|
||||
let cents = (dollars * 100.0).round() as u64;
|
||||
self.spot.cents.height.inner.push(Cents::new(cents));
|
||||
self.spot.cents.height.inner.push(cents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,8 +177,7 @@ impl Vecs {
|
||||
}
|
||||
|
||||
/// Feed a range of blocks from the indexer into an Oracle (skipping coinbase),
|
||||
/// returning per-block ref_bin values. Outputs are grouped per transaction
|
||||
/// because `for_each_round_dollar_bin` drops a whole tx on any OP_RETURN.
|
||||
/// returning per-block ref_bin values.
|
||||
///
|
||||
/// Pass `cap = None` from compute paths, when the indexer is quiescent and
|
||||
/// raw vec lengths are authoritative. Pass `cap = Some(&safe_lengths)` from
|
||||
@@ -293,21 +286,18 @@ impl Vecs {
|
||||
&mut output_types,
|
||||
);
|
||||
|
||||
let mut hist = HistogramRaw::zeros();
|
||||
for tx in 0..tx_count {
|
||||
let tx_outputs = (0..tx_count).map(|tx| {
|
||||
let lo = tx_starts[tx] - out_start;
|
||||
let hi = tx_starts
|
||||
.get(tx + 1)
|
||||
.map(|s| s - out_start)
|
||||
.unwrap_or(out_end - out_start);
|
||||
let outputs = values[lo..hi]
|
||||
values[lo..hi]
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(output_types[lo..hi].iter().copied());
|
||||
for_each_round_dollar_bin(range.start + idx, outputs, |bin| {
|
||||
hist.increment(bin as usize)
|
||||
});
|
||||
}
|
||||
.zip(output_types[lo..hi].iter().copied())
|
||||
});
|
||||
let hist = PaymentFilter::for_height(range.start + idx).histogram(tx_outputs);
|
||||
|
||||
let ref_bin = oracle.process_histogram(&hist);
|
||||
on_block(range.start + idx, oracle, ref_bin);
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
use by_unit::{OhlcByUnit, PriceByUnit, SplitByUnit, SplitCloseByUnit, SplitIndexesByUnit};
|
||||
use ohlcs::{LazyOhlcVecs, OhlcVecs};
|
||||
|
||||
pub const DB_NAME: &str = "prices";
|
||||
pub const DB_NAME: &str = "price";
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
@@ -4,7 +4,7 @@ use brk_types::Sats;
|
||||
use vecdb::{Exit, VecIndex};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{mining, outputs, prices};
|
||||
use crate::{mining, outputs, price};
|
||||
|
||||
impl Vecs {
|
||||
pub(crate) fn compute(
|
||||
@@ -12,7 +12,7 @@ impl Vecs {
|
||||
indexer: &Indexer,
|
||||
outputs: &outputs::Vecs,
|
||||
mining: &mining::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let starting_height = indexer.safe_lengths().height;
|
||||
|
||||
@@ -7,7 +7,7 @@ use vecdb::Exit;
|
||||
const INITIAL_SUBSIDY: f64 = Sats::ONE_BTC_U64 as f64 * 50.0;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, distribution, mining, outputs, prices, transactions};
|
||||
use crate::{blocks, distribution, mining, outputs, price, transactions};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -18,7 +18,7 @@ impl Vecs {
|
||||
blocks: &blocks::Vecs,
|
||||
mining: &mining::Vecs,
|
||||
transactions: &transactions::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
distribution: &distribution::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
|
||||
@@ -3,7 +3,7 @@ use brk_indexer::Indexer;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{blocks, indexes, inputs, prices};
|
||||
use crate::{blocks, indexes, inputs, price};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -13,7 +13,7 @@ impl Vecs {
|
||||
indexes: &indexes::Vecs,
|
||||
blocks: &blocks::Vecs,
|
||||
inputs: &inputs::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.db.sync_bg_tasks()?;
|
||||
|
||||
@@ -5,7 +5,7 @@ use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::transactions::{count, fees};
|
||||
use crate::{indexes, internal::Windows, prices};
|
||||
use crate::{indexes, internal::Windows, price};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -13,7 +13,7 @@ impl Vecs {
|
||||
&mut self,
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
prices: &price::Vecs,
|
||||
count_vecs: &count::Vecs,
|
||||
fees_vecs: &fees::Vecs,
|
||||
exit: &Exit,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use brk_oracle::{HistogramRaw, for_each_round_dollar_bin, sats_to_bin};
|
||||
use brk_oracle::{sats_to_bin, HistogramRaw, PaymentFilter};
|
||||
use brk_types::Transaction;
|
||||
|
||||
use crate::stores::tx_store::TxRecord;
|
||||
@@ -43,14 +43,9 @@ impl LiveHistograms {
|
||||
/// Round-dollar-eligible bins, applying the oracle payment filter. Calls
|
||||
/// `emit(bin)` per eligible output. Deterministic over a tx's outputs,
|
||||
/// which are never mutated after insert, so add and remove recompute it
|
||||
/// identically rather than caching. Live mempool txs are post-tip, always
|
||||
/// above the historical max-outputs cap window, so the cap never applies.
|
||||
/// identically rather than caching.
|
||||
fn eligible_bins(tx: &Transaction, emit: impl FnMut(u16)) {
|
||||
for_each_round_dollar_bin(
|
||||
usize::MAX,
|
||||
tx.output.iter().map(|o| (o.value, o.type_())),
|
||||
emit,
|
||||
);
|
||||
PaymentFilter::MODERN.for_each_bin(tx.output.iter().map(|o| (o.value, o.type_())), emit);
|
||||
}
|
||||
|
||||
/// Raw bin index per output, dropping only values outside the bin domain
|
||||
|
||||
+18
-10
@@ -48,7 +48,7 @@ For each new block:
|
||||
|
||||
### 1. Filter outputs
|
||||
|
||||
Skip the coinbase transaction, and skip every output of a transaction carrying an `OP_RETURN`: that transaction is protocol machinery, not a dollar-denominated payment, so its payout amounts are not price signal. Below height 630,000, also skip every output of a transaction with more than 100 outputs: a large fan-out is a batch payout (exchange sweep, mixer), not a round-dollar payment, and the thin early signal needs it removed. Then exclude noisy outputs: script types dominated by protocol activity (P2TR by default), dust below 1,000 sats, and round BTC amounts (0.01, 0.1, 1.0 BTC, etc.) that create false spikes unrelated to dollar purchases.
|
||||
Skip the coinbase transaction, and skip every output of a transaction carrying an `OP_RETURN`: that transaction is protocol machinery, not a dollar-denominated payment, so its payout amounts are not price signal. Below height 630,000, also skip every output of a transaction with more than 100 outputs: a large fan-out is a batch payout (exchange sweep, mixer), not a round-dollar payment, and the thin early signal needs it removed. At and above height 630,000, the transaction fan-out cap relaxes to 250 outputs so dense-chain payment activity remains visible while very large fan-outs cannot dominate one EMA slot. Then exclude noisy outputs: script types dominated by protocol activity (P2TR by default), dust below 1,000 sats, and round BTC amounts (0.01, 0.1, 1.0 BTC, etc.) that create false spikes unrelated to dollar purchases.
|
||||
|
||||
### 2. Build a log-scale histogram
|
||||
|
||||
@@ -122,9 +122,9 @@ Parabolic interpolation between the best bin and its two neighbors refines the e
|
||||
|
||||
The oracle consumes one pre-built histogram per block via `process_histogram(&hist)`, a `[u32; 2400]` bin-count array, and returns the updated reference bin.
|
||||
|
||||
The caller filters as it builds the histogram, applying the [step 1](#1-filter-outputs) rules. Two helpers are exported for this: `eligible_bin(sats, output_type)` returns an output's bin index, or `None` if filtered, and `for_each_round_dollar_bin` wraps it with the per-transaction drops (coinbase, OP_RETURN, the >100-output cap below height 630,000) for callers holding a whole transaction's outputs.
|
||||
The caller filters as it builds the histogram, applying the [step 1](#1-filter-outputs) rules. `PaymentFilter::for_height(height).histogram(txs)` builds a fresh block histogram from non-coinbase transaction outputs. Incremental live callers use `PaymentFilter::MODERN.for_each_bin(outputs, emit)`, which applies the modern fan-out cap without requiring a height. `PaymentFilter::eligible_bin(sats, output_type)` returns an individual output's bin index, or `None` if filtered. The transaction-level rules include the OP_RETURN drop, the >100 transaction-output fan-out cap below height 630,000, and the >250 cap from height 630,000 onward.
|
||||
|
||||
The initial seed must be close to the real price at the starting height. The crate includes a `PRICES` constant with exchange prices for heights 0..340,000. Its last entry, height 339,999 (one below `START_HEIGHT_SLOW`), seeds the oracle's first on-chain computation at height 340,000.
|
||||
The initial seed must be close to the real price at the starting height. The crate includes typed pre-oracle helpers for exchange prices at heights 0..340,000. `Oracle::from_seed()` uses the last baked price, height 339,999 (one below `START_HEIGHT_SLOW`), and the slow cold-start config to seed the oracle's first on-chain computation at height 340,000.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -161,7 +161,7 @@ Between heights 340,000 and 508,000 the oracle runs a slower cold-start configur
|
||||
|
||||
## Accuracy
|
||||
|
||||
Tested over 596,251 blocks (heights 340,000 to 950,800, as of May 2026) against exchange OHLC data. Error is measured per block as distance from the oracle estimate to the exchange high/low range at that height. If the oracle falls within the range, the error is zero.
|
||||
Tested over 596,251 exchange-covered blocks after running the oracle from height 340,000 through height 952,314. Error is measured per block as distance from the oracle estimate to the exchange high/low range at that height. If the oracle falls within the range, the error is zero.
|
||||
|
||||
### Per-block
|
||||
|
||||
@@ -173,9 +173,9 @@ Tested over 596,251 blocks (heights 340,000 to 950,800, as of May 2026) against
|
||||
| 99.9th percentile | 15.6% |
|
||||
| RMSE | 0.97% |
|
||||
| Max error | 33.8% |
|
||||
| Bias | +0.05 bins (essentially zero) |
|
||||
| Blocks > 5% error | 3,235 (0.543%) |
|
||||
| Blocks > 10% error | 1,324 |
|
||||
| Bias | +0.06 bins (essentially zero) |
|
||||
| Blocks > 5% error | 3,233 (0.542%) |
|
||||
| Blocks > 10% error | 1,323 |
|
||||
| Blocks > 20% error | 154 |
|
||||
|
||||
### Daily candles
|
||||
@@ -185,7 +185,7 @@ Oracle daily OHLC built from per-block prices vs exchange daily OHLC:
|
||||
| | Median | RMSE | Max |
|
||||
|-------|--------|------|-----|
|
||||
| Open | 0.24% | 1.07% | 29.1% |
|
||||
| High | 0.58% | 1.48% | 27.3% |
|
||||
| High | 0.58% | 1.47% | 27.3% |
|
||||
| Low | 0.53% | 1.95% | 55.1% |
|
||||
| Close | 0.27% | 1.18% | 29.2% |
|
||||
|
||||
@@ -199,10 +199,10 @@ Oracle daily OHLC built from per-block prices vs exchange daily OHLC:
|
||||
| 2018 | 54,531 | 0.18% | 1.31% | 31.6% | 411 | 207 | 62 | $3,129–$17,178 |
|
||||
| 2019 | 54,272 | 0.16% | 0.59% | 17.4% | 100 | 16 | 0 | $3,338–$13,868 |
|
||||
| 2020 | 53,102 | 0.10% | 0.42% | 11.6% | 61 | 3 | 0 | $3,858–$29,322 |
|
||||
| 2021 | 52,733 | 0.07% | 0.47% | 14.4% | 43 | 10 | 0 | $27,678–$69,000 |
|
||||
| 2021 | 52,733 | 0.07% | 0.47% | 14.4% | 42 | 9 | 0 | $27,678–$69,000 |
|
||||
| 2022 | 53,230 | 0.07% | 0.32% | 6.8% | 10 | 0 | 0 | $15,460–$48,240 |
|
||||
| 2023 | 54,032 | 0.10% | 0.25% | 6.6% | 5 | 0 | 0 | $16,490–$44,700 |
|
||||
| 2024 | 53,367 | 0.10% | 0.28% | 7.1% | 8 | 0 | 0 | $38,555–$108,298 |
|
||||
| 2024 | 53,367 | 0.10% | 0.28% | 6.7% | 7 | 0 | 0 | $38,555–$108,298 |
|
||||
| 2025 | 53,113 | 0.11% | 0.25% | 5.8% | 4 | 0 | 0 | $74,409–$126,198 |
|
||||
| 2026 | 5,910 | 0.10% | 0.27% | 3.2% | 0 | 0 | 0 | $60,000–$97,900 |
|
||||
|
||||
@@ -217,6 +217,14 @@ Post-hoc smoothing, for example correcting any block whose price deviates more t
|
||||
|
||||
## Changelog
|
||||
|
||||
### v4
|
||||
|
||||
Changes from v3:
|
||||
|
||||
- **Modern fan-out cap**: below height 630,000 the oracle keeps the strict >100-output transaction drop introduced in v3. At and above 630,000 the cap now relaxes to 250 outputs instead of being fully lifted. This preserves dense-chain payment signal while preventing very large modern fan-outs from dominating a single EMA slot and creating a transient false round-dollar ladder.
|
||||
|
||||
`VERSION` is bumped to 4 so downstream consumers invalidate prices computed by an earlier algorithm.
|
||||
|
||||
### v3
|
||||
|
||||
Changes from v2:
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
//! values matching a continuously-running oracle from the restart height
|
||||
//! onward.
|
||||
//!
|
||||
//! Mirrors the production filter exactly (per-tx OP_RETURN drop + per-output
|
||||
//! `eligible_bin`), so it exercises the same code path
|
||||
//! `brk_computer::prices::compute::feed_blocks` uses at runtime.
|
||||
//! Mirrors the production transaction filter exactly, so it exercises the same code path
|
||||
//! `brk_computer::price::compute::feed_blocks` uses at runtime.
|
||||
//!
|
||||
//! Run with: cargo run -p brk_oracle --example determinism --release
|
||||
|
||||
@@ -14,22 +13,12 @@ use std::path::PathBuf;
|
||||
|
||||
use brk_indexer::Indexer;
|
||||
use brk_oracle::{
|
||||
Config, HistogramRaw, Oracle, PRICES, START_HEIGHT_FAST, bin_to_cents, cents_to_bin,
|
||||
for_each_round_dollar_bin,
|
||||
bin_to_cents, cents_to_bin, Config, HistogramRaw, Oracle, PaymentFilter, START_HEIGHT_FAST,
|
||||
START_HEIGHT_SLOW,
|
||||
};
|
||||
use brk_types::{OutputType, Sats, TxIndex, TxOutIndex};
|
||||
use vecdb::{AnyVec, ReadableVec, VecIndex};
|
||||
|
||||
fn seed_bin_for_start_height() -> f64 {
|
||||
let price: f64 = PRICES
|
||||
.lines()
|
||||
.nth(START_HEIGHT_FAST - 1)
|
||||
.expect("prices.txt too short for START_HEIGHT_FAST")
|
||||
.parse()
|
||||
.expect("Failed to parse seed price");
|
||||
cents_to_bin(price * 100.0)
|
||||
}
|
||||
|
||||
struct Block {
|
||||
height: usize,
|
||||
values: Vec<Sats>,
|
||||
@@ -40,21 +29,19 @@ struct Block {
|
||||
}
|
||||
|
||||
fn build_histogram(block: &Block) -> HistogramRaw {
|
||||
let mut hist = HistogramRaw::zeros();
|
||||
for tx in 0..block.tx_starts.len() {
|
||||
let tx_outputs = (0..block.tx_starts.len()).map(|tx| {
|
||||
let lo = block.tx_starts[tx] - block.out_start;
|
||||
let hi = block
|
||||
.tx_starts
|
||||
.get(tx + 1)
|
||||
.map(|s| s - block.out_start)
|
||||
.unwrap_or(block.out_end - block.out_start);
|
||||
let outputs = block.values[lo..hi]
|
||||
block.values[lo..hi]
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(block.output_types[lo..hi].iter().copied());
|
||||
for_each_round_dollar_bin(block.height, outputs, |bin| hist.increment(bin as usize));
|
||||
}
|
||||
hist
|
||||
.zip(block.output_types[lo..hi].iter().copied())
|
||||
});
|
||||
PaymentFilter::for_height(block.height).histogram(tx_outputs)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -68,14 +55,15 @@ fn main() {
|
||||
let indexer = Indexer::forced_import(&data_dir).expect("Failed to load indexer");
|
||||
let total_heights = indexer.vecs.blocks.timestamp.len();
|
||||
|
||||
let config = Config::default();
|
||||
let window_size = config.window_size;
|
||||
let fast_config = Config::default();
|
||||
let window_size = fast_config.window_size;
|
||||
|
||||
let restart_offset = 1000;
|
||||
let end_offset = restart_offset + window_size * 4;
|
||||
let end_height = (START_HEIGHT_FAST + end_offset).min(total_heights);
|
||||
let restart_at = START_HEIGHT_FAST + restart_offset;
|
||||
let warmup_start = restart_at - window_size;
|
||||
let load_start = START_HEIGHT_SLOW;
|
||||
|
||||
assert!(
|
||||
end_height > restart_at,
|
||||
@@ -84,8 +72,8 @@ fn main() {
|
||||
);
|
||||
|
||||
println!(
|
||||
"Loading {} blocks ({START_HEIGHT_FAST}..{end_height})...",
|
||||
end_height - START_HEIGHT_FAST
|
||||
"Loading {} blocks ({load_start}..{end_height})...",
|
||||
end_height - load_start
|
||||
);
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
@@ -93,8 +81,8 @@ fn main() {
|
||||
let out_first: Vec<TxOutIndex> = indexer.vecs.outputs.first_txout_index.collect();
|
||||
let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor();
|
||||
|
||||
let mut blocks: Vec<Block> = Vec::with_capacity(end_height - START_HEIGHT_FAST);
|
||||
for h in START_HEIGHT_FAST..end_height {
|
||||
let mut blocks: Vec<Block> = Vec::with_capacity(end_height - load_start);
|
||||
for h in load_start..end_height {
|
||||
let ft = first_tx_index[h];
|
||||
let next_ft = first_tx_index
|
||||
.get(h + 1)
|
||||
@@ -136,31 +124,36 @@ fn main() {
|
||||
});
|
||||
}
|
||||
|
||||
let mut continuous = Oracle::new(seed_bin_for_start_height(), config.clone());
|
||||
let mut continuous = Oracle::from_seed();
|
||||
let continuous_bins: Vec<f64> = blocks
|
||||
.iter()
|
||||
.map(|b| continuous.process_histogram(&build_histogram(b)))
|
||||
.map(|b| {
|
||||
if b.height == START_HEIGHT_FAST {
|
||||
continuous.reconfigure(fast_config);
|
||||
}
|
||||
continuous.process_histogram(&build_histogram(b))
|
||||
})
|
||||
.collect();
|
||||
println!(
|
||||
"Continuous oracle: {} blocks processed",
|
||||
continuous_bins.len()
|
||||
);
|
||||
|
||||
let prev_bin = continuous_bins[restart_at - START_HEIGHT_FAST - 1];
|
||||
let prev_bin = continuous_bins[restart_at - load_start - 1];
|
||||
let seed_bin = cents_to_bin(bin_to_cents(prev_bin) as f64);
|
||||
println!(
|
||||
"Restart at {restart_at}: prev_bin={prev_bin:.4} -> cents -> seed_bin={seed_bin:.4} (delta {:.6})",
|
||||
seed_bin - prev_bin
|
||||
);
|
||||
|
||||
let warmup_slice = &blocks[warmup_start - START_HEIGHT_FAST..restart_at - START_HEIGHT_FAST];
|
||||
let mut restored = Oracle::from_checkpoint(seed_bin, config.clone(), |o| {
|
||||
let warmup_slice = &blocks[warmup_start - load_start..restart_at - load_start];
|
||||
let mut restored = Oracle::from_checkpoint(seed_bin, fast_config, |o| {
|
||||
for b in warmup_slice {
|
||||
o.process_histogram(&build_histogram(b));
|
||||
}
|
||||
});
|
||||
|
||||
let restored_bins: Vec<f64> = blocks[restart_at - START_HEIGHT_FAST..]
|
||||
let restored_bins: Vec<f64> = blocks[restart_at - load_start..]
|
||||
.iter()
|
||||
.map(|b| restored.process_histogram(&build_histogram(b)))
|
||||
.collect();
|
||||
@@ -168,7 +161,7 @@ fn main() {
|
||||
|
||||
let mut mismatches: Vec<(usize, f64, f64)> = Vec::new();
|
||||
for (i, &r) in restored_bins.iter().enumerate() {
|
||||
let c = continuous_bins[restart_at - START_HEIGHT_FAST + i];
|
||||
let c = continuous_bins[restart_at - load_start + i];
|
||||
if r != c {
|
||||
mismatches.push((restart_at + i, c, r));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,690 @@
|
||||
//! Compare oracle filter/EMA variants against the historical OHLC set.
|
||||
//!
|
||||
//! This is a diagnostic harness, not production code. It mirrors the production
|
||||
//! state machine closely enough to compare candidate changes in one pass over
|
||||
//! the indexed chain while recording the recent bad-lock heights.
|
||||
//!
|
||||
//! Run:
|
||||
//! cargo run -p brk_oracle --example experiment --release
|
||||
|
||||
use std::{cmp::Ordering, env, path::PathBuf};
|
||||
|
||||
use brk_indexer::Indexer;
|
||||
use brk_oracle::{
|
||||
bin_to_cents, cents_to_bin, seed_bin as oracle_seed_bin, Config, PaymentFilter,
|
||||
BINS_PER_DECADE, NUM_BINS, START_HEIGHT_FAST, START_HEIGHT_SLOW,
|
||||
};
|
||||
use brk_types::{OutputType, Sats, TxIndex, TxOutIndex};
|
||||
use vecdb::{AnyVec, ReadableVec, VecIndex};
|
||||
|
||||
const GENESIS_DAY: u32 = 14252;
|
||||
const BINS_5PCT: f64 = 4.24;
|
||||
const BINS_10PCT: f64 = 8.28;
|
||||
const BINS_20PCT: f64 = 15.84;
|
||||
const STENCIL_OFFSETS: [i32; 19] = [
|
||||
-400, -340, -305, -260, -200, -165, -140, -120, -105, -60, 0, 35, 60, 95, 140, 200, 260, 340,
|
||||
400,
|
||||
];
|
||||
const N_ARMS: usize = STENCIL_OFFSETS.len();
|
||||
const TARGET_HEIGHTS: &[usize] = &[952_286, 952_287, 952_288, 952_289, 952_290];
|
||||
|
||||
fn bins_to_pct(bins: f64) -> f64 {
|
||||
(10.0_f64.powf(bins / BINS_PER_DECADE as f64) - 1.0) * 100.0
|
||||
}
|
||||
|
||||
fn timestamp_to_year(ts: u32) -> u16 {
|
||||
let years_since_1970 = ts as f64 / 31_557_600.0;
|
||||
(1970.0 + years_since_1970) as u16
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ShapeAnchor {
|
||||
weight: f64,
|
||||
profile: [f64; N_ARMS],
|
||||
}
|
||||
|
||||
impl ShapeAnchor {
|
||||
fn new(weight: f64) -> Self {
|
||||
Self {
|
||||
weight,
|
||||
profile: [1.0 / N_ARMS as f64; N_ARMS],
|
||||
}
|
||||
}
|
||||
|
||||
fn score(&self, state: &OracleState, center: i64) -> f64 {
|
||||
if self.weight == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
self.weight
|
||||
* normalized_arms_at(state, center)
|
||||
.map(|arms| {
|
||||
1.0 - (0..N_ARMS)
|
||||
.map(|i| (arms[i] - self.profile[i]).abs())
|
||||
.sum::<f64>()
|
||||
})
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
fn update(&mut self, state: &OracleState, pick: i64) {
|
||||
const BETA: f64 = 0.004;
|
||||
if self.weight == 0.0 {
|
||||
return;
|
||||
}
|
||||
if let Some(arms) = normalized_arms_at(state, pick) {
|
||||
for (p, arm) in self.profile.iter_mut().zip(arms) {
|
||||
*p = (1.0 - BETA) * *p + BETA * arm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct OracleState {
|
||||
config: Config,
|
||||
ring: Vec<Vec<f64>>,
|
||||
nonzero: Vec<Vec<usize>>,
|
||||
weights: Vec<f64>,
|
||||
cursor: usize,
|
||||
filled: usize,
|
||||
ref_bin: f64,
|
||||
warmup: bool,
|
||||
shape: ShapeAnchor,
|
||||
}
|
||||
|
||||
impl OracleState {
|
||||
fn new(ref_bin: f64, config: Config) -> Self {
|
||||
let weights = weights(config.window_size, config.alpha);
|
||||
Self {
|
||||
ring: vec![vec![0.0; NUM_BINS]; config.window_size],
|
||||
nonzero: vec![Vec::new(); config.window_size],
|
||||
weights,
|
||||
cursor: 0,
|
||||
filled: 0,
|
||||
ref_bin,
|
||||
warmup: false,
|
||||
shape: ShapeAnchor::new(config.shape_weight),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
fn reconfigure(&mut self, config: Config) {
|
||||
let kept = self.recent(config.window_size);
|
||||
let mut next = Self::new(self.ref_bin, config);
|
||||
next.warmup = true;
|
||||
for hist in kept {
|
||||
next.push_existing(hist);
|
||||
}
|
||||
next.warmup = false;
|
||||
*self = next;
|
||||
}
|
||||
|
||||
fn recent(&self, n: usize) -> Vec<Vec<f64>> {
|
||||
(0..self.filled.min(n))
|
||||
.rev()
|
||||
.map(|age| self.ring[self.index_at_age(age)].clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn index_at_age(&self, age: usize) -> usize {
|
||||
(self.cursor + self.ring.len() - 1 - age) % self.ring.len()
|
||||
}
|
||||
|
||||
fn start_block(&mut self) {
|
||||
for bin in self.nonzero[self.cursor].drain(..) {
|
||||
self.ring[self.cursor][bin] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, bin: usize, weight: f64) {
|
||||
let slot = &mut self.ring[self.cursor];
|
||||
if slot[bin] == 0.0 {
|
||||
self.nonzero[self.cursor].push(bin);
|
||||
}
|
||||
slot[bin] += weight;
|
||||
}
|
||||
|
||||
fn push_existing(&mut self, hist: Vec<f64>) {
|
||||
self.start_block();
|
||||
for (bin, value) in hist
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, value)| *value != 0.0)
|
||||
{
|
||||
self.add(bin, value);
|
||||
}
|
||||
self.finish_block();
|
||||
}
|
||||
|
||||
fn finish_block(&mut self) {
|
||||
self.cursor = (self.cursor + 1) % self.ring.len();
|
||||
self.filled = (self.filled + 1).min(self.ring.len());
|
||||
if self.warmup {
|
||||
return;
|
||||
}
|
||||
self.ref_bin = find_best_bin(self);
|
||||
let mut shape = self.shape.clone();
|
||||
shape.update(self, self.ref_bin.round() as i64);
|
||||
self.shape = shape;
|
||||
}
|
||||
|
||||
fn value_at(&self, bin: i64) -> f64 {
|
||||
if bin < 0 || bin as usize >= NUM_BINS {
|
||||
return 0.0;
|
||||
}
|
||||
let bin = bin as usize;
|
||||
(0..self.filled)
|
||||
.map(|age| self.weights[age] * self.ring[self.index_at_age(age)][bin])
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
fn weights(window_size: usize, alpha: f64) -> Vec<f64> {
|
||||
let decay = 1.0 - alpha;
|
||||
(0..window_size)
|
||||
.map(|i| alpha * decay.powi(i as i32))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalized_arms_at(state: &OracleState, center: i64) -> Option<[f64; N_ARMS]> {
|
||||
let mut arms = STENCIL_OFFSETS.map(|offset| state.value_at(center + offset as i64));
|
||||
let sum: f64 = arms.iter().sum();
|
||||
if sum <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
for arm in &mut arms {
|
||||
*arm /= sum;
|
||||
}
|
||||
Some(arms)
|
||||
}
|
||||
|
||||
fn find_best_bin(state: &OracleState) -> f64 {
|
||||
let center = state.ref_bin.round() as usize;
|
||||
let search_start = center.saturating_sub(state.config.search_below);
|
||||
let search_end = (center + state.config.search_above + 1).min(NUM_BINS);
|
||||
if search_start >= search_end {
|
||||
return state.ref_bin;
|
||||
}
|
||||
|
||||
let mut arm_peaks = [0.0f64; N_ARMS];
|
||||
for (i, &offset) in STENCIL_OFFSETS.iter().enumerate() {
|
||||
for bin in search_start..search_end {
|
||||
arm_peaks[i] = arm_peaks[i].max(state.value_at(bin as i64 + offset as i64));
|
||||
}
|
||||
}
|
||||
|
||||
let score = |bin: usize| -> f64 {
|
||||
let mut total = 0.0;
|
||||
for (i, &offset) in STENCIL_OFFSETS.iter().enumerate() {
|
||||
if arm_peaks[i] > 0.0 {
|
||||
total += state.value_at(bin as i64 + offset as i64) / arm_peaks[i];
|
||||
}
|
||||
}
|
||||
total + state.shape.score(state, bin as i64)
|
||||
};
|
||||
|
||||
let mut best_bin = search_start;
|
||||
let mut best_score = score(search_start);
|
||||
for bin in (search_start + 1)..search_end {
|
||||
let candidate = score(bin);
|
||||
if candidate > best_score {
|
||||
best_score = candidate;
|
||||
best_bin = bin;
|
||||
}
|
||||
}
|
||||
|
||||
let score_center = best_score;
|
||||
let score_left = if best_bin > search_start {
|
||||
score(best_bin - 1)
|
||||
} else {
|
||||
score_center
|
||||
};
|
||||
let score_right = if best_bin + 1 < search_end {
|
||||
score(best_bin + 1)
|
||||
} else {
|
||||
score_center
|
||||
};
|
||||
let denom = score_left - 2.0 * score_center + score_right;
|
||||
let sub_bin = if denom.abs() > 1e-10 {
|
||||
(0.5 * (score_left - score_right) / denom).clamp(-0.5, 0.5)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
best_bin as f64 + sub_bin
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct VariantCfg {
|
||||
name: String,
|
||||
fast_alpha: f64,
|
||||
fast_window: usize,
|
||||
max_outputs: Option<usize>,
|
||||
max_outputs_until: usize,
|
||||
max_outputs_after: Option<usize>,
|
||||
}
|
||||
|
||||
struct Variant {
|
||||
cfg: VariantCfg,
|
||||
state: OracleState,
|
||||
overall: YearStats,
|
||||
years: Vec<YearStats>,
|
||||
bias: f64,
|
||||
target_prices: Vec<(usize, f64)>,
|
||||
}
|
||||
|
||||
fn cap_label(cap: Option<usize>) -> String {
|
||||
cap.map(|cap| cap.to_string())
|
||||
.unwrap_or_else(|| "none".to_string())
|
||||
}
|
||||
|
||||
fn target_price(target_prices: &[(usize, f64)], height: usize) -> Option<f64> {
|
||||
target_prices
|
||||
.iter()
|
||||
.find(|(h, _)| *h == height)
|
||||
.map(|(_, price)| *price)
|
||||
}
|
||||
|
||||
fn fixes_bad_lock(target_prices: &[(usize, f64)]) -> bool {
|
||||
target_price(target_prices, 952_287).is_some_and(|price| price > 62_000.0)
|
||||
&& target_price(target_prices, 952_288).is_some_and(|price| price > 62_000.0)
|
||||
}
|
||||
|
||||
impl Variant {
|
||||
fn new(cfg: VariantCfg, seed_bin: f64) -> Self {
|
||||
Self {
|
||||
cfg,
|
||||
state: OracleState::new(seed_bin, Config::slow()),
|
||||
overall: YearStats::new(0),
|
||||
years: Vec::new(),
|
||||
bias: 0.0,
|
||||
target_prices: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn fast_config(&self) -> Config {
|
||||
Config {
|
||||
alpha: self.cfg.fast_alpha,
|
||||
window_size: self.cfg.fast_window,
|
||||
..Config::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_reconfigure(&mut self, height: usize) {
|
||||
if height == START_HEIGHT_FAST {
|
||||
self.state.reconfigure(self.fast_config());
|
||||
}
|
||||
}
|
||||
|
||||
fn should_drop_tx(&self, height: usize, output_count: usize) -> bool {
|
||||
if height < self.cfg.max_outputs_until {
|
||||
self.cfg.max_outputs.is_some_and(|max| output_count > max)
|
||||
} else {
|
||||
self.cfg
|
||||
.max_outputs_after
|
||||
.is_some_and(|max| output_count > max)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_tx(&mut self, bins: &[u16], height: usize, output_count: usize) {
|
||||
if bins.is_empty() || self.should_drop_tx(height, output_count) {
|
||||
return;
|
||||
}
|
||||
for &bin in bins {
|
||||
self.state.add(bin as usize, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_block(&mut self, height: usize) {
|
||||
self.state.finish_block();
|
||||
if TARGET_HEIGHTS.contains(&height) {
|
||||
self.target_prices
|
||||
.push((height, bin_to_cents(self.state.ref_bin) as f64 / 100.0));
|
||||
}
|
||||
}
|
||||
|
||||
fn update_stats(
|
||||
&mut self,
|
||||
height: usize,
|
||||
height_bands: &[(f64, f64)],
|
||||
height_ohlc: &[[f64; 4]],
|
||||
height_years: &[u16],
|
||||
) {
|
||||
if height >= height_bands.len() {
|
||||
return;
|
||||
}
|
||||
let (high_bin, low_bin) = height_bands[height];
|
||||
if high_bin <= 0.0 || low_bin <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let err = if self.state.ref_bin < high_bin {
|
||||
self.state.ref_bin - high_bin
|
||||
} else if self.state.ref_bin > low_bin {
|
||||
self.state.ref_bin - low_bin
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let exchange_high = height_ohlc[height][1];
|
||||
let exchange_low = height_ohlc[height][2];
|
||||
self.overall.update(err, exchange_high, exchange_low);
|
||||
self.bias += err;
|
||||
|
||||
let year = height_years[height];
|
||||
if self.years.last().is_none_or(|stats| stats.year != year) {
|
||||
self.years.push(YearStats::new(year));
|
||||
}
|
||||
self.years
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.update(err, exchange_high, exchange_low);
|
||||
}
|
||||
}
|
||||
|
||||
struct YearStats {
|
||||
year: u16,
|
||||
total_sq_err: f64,
|
||||
max_err: f64,
|
||||
total_blocks: u64,
|
||||
gt_5pct: u64,
|
||||
gt_10pct: u64,
|
||||
gt_20pct: u64,
|
||||
errors: Vec<f64>,
|
||||
}
|
||||
|
||||
impl YearStats {
|
||||
fn new(year: u16) -> Self {
|
||||
Self {
|
||||
year,
|
||||
total_sq_err: 0.0,
|
||||
max_err: 0.0,
|
||||
total_blocks: 0,
|
||||
gt_5pct: 0,
|
||||
gt_10pct: 0,
|
||||
gt_20pct: 0,
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, err: f64, _exchange_high: f64, _exchange_low: f64) {
|
||||
let abs_err = err.abs();
|
||||
self.total_sq_err += err * err;
|
||||
self.total_blocks += 1;
|
||||
self.errors.push(bins_to_pct(abs_err));
|
||||
self.max_err = self.max_err.max(abs_err);
|
||||
if abs_err > BINS_5PCT {
|
||||
self.gt_5pct += 1;
|
||||
}
|
||||
if abs_err > BINS_10PCT {
|
||||
self.gt_10pct += 1;
|
||||
}
|
||||
if abs_err > BINS_20PCT {
|
||||
self.gt_20pct += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn rmse_pct(&self) -> f64 {
|
||||
if self.total_blocks == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
bins_to_pct((self.total_sq_err / self.total_blocks as f64).sqrt())
|
||||
}
|
||||
|
||||
fn max_pct(&self) -> f64 {
|
||||
bins_to_pct(self.max_err)
|
||||
}
|
||||
|
||||
fn percentile(&self, p: f64) -> f64 {
|
||||
if self.errors.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let mut errors = self.errors.clone();
|
||||
errors.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
let idx = ((p / 100.0) * (errors.len() - 1) as f64).round() as usize;
|
||||
errors[idx.min(errors.len() - 1)]
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let data_dir = std::env::var("BRK_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from(std::env::var("HOME").unwrap()).join(".brk"));
|
||||
let end_override = std::env::var("ORACLE_END")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<usize>().ok());
|
||||
let stats_start = std::env::var("ORACLE_STATS_START")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.unwrap_or(START_HEIGHT_SLOW)
|
||||
.max(START_HEIGHT_SLOW);
|
||||
|
||||
let indexer = Indexer::forced_import(&data_dir).expect("Failed to load indexer");
|
||||
let total_heights = indexer.vecs.blocks.timestamp.len();
|
||||
let end = end_override.unwrap_or(total_heights).min(total_heights);
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
let height_ohlc: Vec<[f64; 4]> = serde_json::from_str(
|
||||
&std::fs::read_to_string(format!("{manifest_dir}/examples/height_price_ohlc.json"))
|
||||
.expect("read height_price_ohlc.json"),
|
||||
)
|
||||
.expect("parse height OHLC");
|
||||
let height_bands: Vec<(f64, f64)> = height_ohlc
|
||||
.iter()
|
||||
.map(|ohlc| {
|
||||
let high = ohlc[1];
|
||||
let low = ohlc[2];
|
||||
if high > 0.0 && low > 0.0 {
|
||||
(cents_to_bin(high * 100.0), cents_to_bin(low * 100.0))
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let timestamps: Vec<brk_types::Timestamp> = indexer.vecs.blocks.timestamp.collect();
|
||||
let height_years: Vec<u16> = timestamps
|
||||
.iter()
|
||||
.map(|ts| timestamp_to_year(**ts))
|
||||
.collect();
|
||||
let _height_day1s: Vec<usize> = timestamps
|
||||
.iter()
|
||||
.map(|ts| (**ts / 86_400).saturating_sub(GENESIS_DAY) as usize)
|
||||
.collect();
|
||||
|
||||
let seed_bin = oracle_seed_bin();
|
||||
|
||||
let current_alpha = 2.0 / 7.0;
|
||||
let current_window = 12;
|
||||
let mut cfgs = Vec::<VariantCfg>::new();
|
||||
let mut add_cfg = |name: String,
|
||||
max_outputs: Option<usize>,
|
||||
max_outputs_until: usize,
|
||||
max_outputs_after: Option<usize>| {
|
||||
cfgs.push(VariantCfg {
|
||||
name,
|
||||
fast_alpha: current_alpha,
|
||||
fast_window: current_window,
|
||||
max_outputs,
|
||||
max_outputs_until,
|
||||
max_outputs_after,
|
||||
});
|
||||
};
|
||||
|
||||
for post in [200, 250] {
|
||||
add_cfg(
|
||||
format!("pre100_post{post}"),
|
||||
Some(100),
|
||||
PaymentFilter::MODERN_TX_OUTPUT_FANOUT_CAP_START_HEIGHT,
|
||||
Some(post),
|
||||
);
|
||||
}
|
||||
|
||||
cfgs.dedup_by(|a, b| a.name == b.name);
|
||||
if let Ok(only) = env::var("BRK_ORACLE_EXPERIMENT_ONLY") {
|
||||
let names = only
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|name| !name.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
cfgs.retain(|cfg| names.iter().any(|name| *name == cfg.name));
|
||||
}
|
||||
let mut variants: Vec<Variant> = cfgs
|
||||
.into_iter()
|
||||
.map(|cfg| Variant::new(cfg, seed_bin))
|
||||
.collect();
|
||||
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
let first_tx_index: Vec<TxIndex> = indexer.vecs.transactions.first_tx_index.collect();
|
||||
let out_first: Vec<TxOutIndex> = indexer.vecs.outputs.first_txout_index.collect();
|
||||
let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor();
|
||||
let mut tx_starts: Vec<usize> = Vec::new();
|
||||
let mut values: Vec<Sats> = Vec::new();
|
||||
let mut output_types: Vec<OutputType> = Vec::new();
|
||||
let mut bins: Vec<u16> = Vec::new();
|
||||
|
||||
eprintln!(
|
||||
"running {} variants over heights {START_HEIGHT_SLOW}..{end}; stats from {stats_start}",
|
||||
variants.len()
|
||||
);
|
||||
|
||||
for h in START_HEIGHT_SLOW..end {
|
||||
if h % 25_000 == 0 {
|
||||
eprintln!("height {h}");
|
||||
}
|
||||
for variant in &mut variants {
|
||||
variant.maybe_reconfigure(h);
|
||||
variant.state.start_block();
|
||||
}
|
||||
|
||||
let ft = first_tx_index[h];
|
||||
let next_ft = first_tx_index
|
||||
.get(h + 1)
|
||||
.copied()
|
||||
.unwrap_or(TxIndex::from(total_txs));
|
||||
let block_first_tx = ft.to_usize() + 1;
|
||||
let tx_count = next_ft.to_usize() - block_first_tx;
|
||||
let out_end = out_first
|
||||
.get(h + 1)
|
||||
.copied()
|
||||
.unwrap_or(TxOutIndex::from(total_outputs))
|
||||
.to_usize();
|
||||
|
||||
txout_cursor.advance(block_first_tx - txout_cursor.position());
|
||||
tx_starts.clear();
|
||||
for _ in 0..tx_count {
|
||||
tx_starts.push(txout_cursor.next().unwrap().to_usize());
|
||||
}
|
||||
let out_start = tx_starts.first().copied().unwrap_or(out_end);
|
||||
|
||||
indexer
|
||||
.vecs
|
||||
.outputs
|
||||
.value
|
||||
.collect_range_into_at(out_start, out_end, &mut values);
|
||||
indexer.vecs.outputs.output_type.collect_range_into_at(
|
||||
out_start,
|
||||
out_end,
|
||||
&mut output_types,
|
||||
);
|
||||
|
||||
for tx in 0..tx_count {
|
||||
let lo = tx_starts[tx] - out_start;
|
||||
let hi = tx_starts
|
||||
.get(tx + 1)
|
||||
.map(|s| s - out_start)
|
||||
.unwrap_or(out_end - out_start);
|
||||
if output_types[lo..hi].contains(&OutputType::OpReturn) {
|
||||
continue;
|
||||
}
|
||||
bins.clear();
|
||||
for i in lo..hi {
|
||||
if let Some(bin) = PaymentFilter::eligible_bin(values[i], output_types[i]) {
|
||||
bins.push(bin);
|
||||
}
|
||||
}
|
||||
for variant in &mut variants {
|
||||
variant.add_tx(&bins, h, hi - lo);
|
||||
}
|
||||
}
|
||||
|
||||
for variant in &mut variants {
|
||||
variant.finish_block(h);
|
||||
if h >= stats_start {
|
||||
variant.update_stats(h, &height_bands, &height_ohlc, &height_years);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variants.sort_by(|a, b| {
|
||||
fixes_bad_lock(&b.target_prices)
|
||||
.cmp(&fixes_bad_lock(&a.target_prices))
|
||||
.then_with(|| {
|
||||
a.overall
|
||||
.rmse_pct()
|
||||
.partial_cmp(&b.overall.rmse_pct())
|
||||
.unwrap_or(Ordering::Equal)
|
||||
})
|
||||
.then_with(|| a.overall.gt_5pct.cmp(&b.overall.gt_5pct))
|
||||
});
|
||||
|
||||
println!(
|
||||
"variant\tpre_cap\tpost_cap\tfixed\tmedian\tp95\tp99\tp999\trmse\tmax\tbias_bins\tgt5\tgt10\tgt20\tp952287\tp952288\ttarget_prices\trmse_by_year\tgt5_by_year"
|
||||
);
|
||||
for variant in &variants {
|
||||
let overall = &variant.overall;
|
||||
let bias = if overall.total_blocks > 0 {
|
||||
variant.bias / overall.total_blocks as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let rmse_by_year = (2015..=2026)
|
||||
.map(|year| {
|
||||
let rmse = variant
|
||||
.years
|
||||
.iter()
|
||||
.find(|stats| stats.year == year)
|
||||
.map(YearStats::rmse_pct)
|
||||
.unwrap_or(0.0);
|
||||
format!("{year}:{rmse:.3}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let gt5_by_year = (2015..=2026)
|
||||
.map(|year| {
|
||||
let gt5 = variant
|
||||
.years
|
||||
.iter()
|
||||
.find(|stats| stats.year == year)
|
||||
.map(|stats| stats.gt_5pct)
|
||||
.unwrap_or(0);
|
||||
format!("{year}:{gt5}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
println!(
|
||||
"{}\t{}\t{}\t{}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{:.3}\t{}\t{}\t{}\t{:.2}\t{:.2}\t{}\t{}\t{}",
|
||||
variant.cfg.name,
|
||||
cap_label(variant.cfg.max_outputs),
|
||||
cap_label(variant.cfg.max_outputs_after),
|
||||
fixes_bad_lock(&variant.target_prices),
|
||||
overall.percentile(50.0),
|
||||
overall.percentile(95.0),
|
||||
overall.percentile(99.0),
|
||||
overall.percentile(99.9),
|
||||
overall.rmse_pct(),
|
||||
overall.max_pct(),
|
||||
bias,
|
||||
overall.gt_5pct,
|
||||
overall.gt_10pct,
|
||||
overall.gt_20pct,
|
||||
target_price(&variant.target_prices, 952_287).unwrap_or(0.0),
|
||||
target_price(&variant.target_prices, 952_288).unwrap_or(0.0),
|
||||
variant
|
||||
.target_prices
|
||||
.iter()
|
||||
.map(|(height, price)| format!("{height}:{price:.2}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
rmse_by_year,
|
||||
gt5_by_year
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@ use std::path::PathBuf;
|
||||
|
||||
use brk_indexer::Indexer;
|
||||
use brk_oracle::{
|
||||
Config, HistogramRaw, Oracle, PRICES, START_HEIGHT_FAST, bin_to_cents, cents_to_bin,
|
||||
eligible_bin,
|
||||
bin_to_cents, cents_to_bin, Config, Oracle, PaymentFilter, START_HEIGHT_FAST, START_HEIGHT_SLOW,
|
||||
};
|
||||
use brk_types::{OutputType, Sats, TxIndex, TxOutIndex};
|
||||
use vecdb::{AnyVec, ReadableVec, VecIndex};
|
||||
@@ -94,7 +93,11 @@ impl YearStats {
|
||||
fn median_pct(&mut self) -> f64 {
|
||||
self.errors.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
let n = self.errors.len();
|
||||
if n == 0 { 0.0 } else { self.errors[n / 2] }
|
||||
if n == 0 {
|
||||
0.0
|
||||
} else {
|
||||
self.errors[n / 2]
|
||||
}
|
||||
}
|
||||
|
||||
fn percentile(&self, p: f64) -> f64 {
|
||||
@@ -172,15 +175,7 @@ fn main() {
|
||||
.map(|ts| (**ts / 86400).saturating_sub(GENESIS_DAY) as usize)
|
||||
.collect();
|
||||
|
||||
let start_price: f64 = PRICES
|
||||
.lines()
|
||||
.nth(START_HEIGHT_FAST - 1)
|
||||
.expect("prices.txt too short")
|
||||
.parse()
|
||||
.expect("Failed to parse seed price");
|
||||
|
||||
let config = Config::default();
|
||||
let mut oracle = Oracle::new(cents_to_bin(start_price * 100.0), config);
|
||||
let mut oracle = Oracle::from_seed();
|
||||
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
@@ -201,7 +196,11 @@ fn main() {
|
||||
let mut oracle_candles: Vec<DayCandle> = Vec::new();
|
||||
let mut current_di: Option<usize> = None;
|
||||
|
||||
for h in START_HEIGHT_FAST..total_heights {
|
||||
for h in START_HEIGHT_SLOW..total_heights {
|
||||
if h == START_HEIGHT_FAST {
|
||||
oracle.reconfigure(Config::default());
|
||||
}
|
||||
|
||||
let ft = first_tx_index[h];
|
||||
let next_ft = first_tx_index
|
||||
.get(h + 1)
|
||||
@@ -235,23 +234,18 @@ fn main() {
|
||||
.output_type
|
||||
.collect_range_at(out_start, out_end);
|
||||
|
||||
// Drop every output of a tx carrying an OP_RETURN (protocol machinery).
|
||||
let mut hist = HistogramRaw::zeros();
|
||||
for tx in 0..tx_count {
|
||||
let tx_outputs = (0..tx_count).map(|tx| {
|
||||
let lo = tx_starts[tx] - out_start;
|
||||
let hi = tx_starts
|
||||
.get(tx + 1)
|
||||
.map(|s| s - out_start)
|
||||
.unwrap_or(out_end - out_start);
|
||||
if output_types[lo..hi].contains(&OutputType::OpReturn) {
|
||||
continue;
|
||||
}
|
||||
for i in lo..hi {
|
||||
if let Some(bin) = eligible_bin(values[i], output_types[i]) {
|
||||
hist.increment(bin as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
values[lo..hi]
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(output_types[lo..hi].iter().copied())
|
||||
});
|
||||
let hist = PaymentFilter::for_height(h).histogram(tx_outputs);
|
||||
|
||||
let ref_bin = oracle.process_histogram(&hist);
|
||||
let oracle_price = bin_to_cents(ref_bin) as f64 / 100.0;
|
||||
@@ -373,10 +367,12 @@ fn main() {
|
||||
println!(" brk_oracle accuracy report");
|
||||
println!(" ══════════════════════════");
|
||||
println!();
|
||||
println!(" Config: w12, alpha=2/7, search -9/+11, noisy/dust/round-btc filtered");
|
||||
println!(
|
||||
" Test range: height {} .. {} ({} blocks)",
|
||||
START_HEIGHT_FAST,
|
||||
" Config: slow w40 alpha=0.10 until {START_HEIGHT_FAST}, then w12 alpha=2/7; shared payment filter"
|
||||
);
|
||||
println!(
|
||||
" Test range: height {} .. {} ({} exchange-covered blocks)",
|
||||
START_HEIGHT_SLOW,
|
||||
total_heights - 1,
|
||||
overall.total_blocks
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user