Compare commits

..

50 Commits

Author SHA1 Message Date
nym21 6207d5335d next: ai part 5 2026-07-23 12:52:48 +02:00
nym21 b6940a1ae7 next: ai part 4 2026-07-22 17:25:46 +02:00
nym21 f0871d895a next: ai part 3 2026-07-22 16:50:32 +02:00
nym21 52e4db5ea6 next: ai part 2 2026-07-22 16:50:27 +02:00
nym21 227a31f64e next: ai part 1 2026-07-21 12:15:41 +02:00
nym21 00984112d8 global: replace most bps with ppm 2026-07-20 18:28:12 +02:00
nym21 3af673132a global: opreturn part 5 2026-07-19 18:19:48 +02:00
nym21 0fc61d7932 global: opreturn part 4 2026-07-19 09:34:03 +02:00
nym21 5161ae2012 global: opreturn part 3 2026-07-18 21:34:21 +02:00
nym21 10c16335ed global: opreturn part 2 2026-07-17 21:45:18 +02:00
nym21 29ad37803e global: opreturn 2026-07-16 17:39:05 +02:00
nym21 8d43f340a0 website_next: part 12 2026-07-10 12:45:55 +02:00
nym21 e279116bff website_next: part 11 2026-07-09 23:08:47 +02:00
nym21 5c376acb9f website_next: part 10 2026-07-09 18:49:01 +02:00
nym21 a43b00c12c website_next: part 9 2026-07-08 11:24:52 +02:00
nym21 15fbdc168f website_next: part 8 2026-07-08 11:24:39 +02:00
nym21 046c624abf website_next: part 7 2026-07-06 18:52:51 +02:00
nym21 4d7212fb63 website_next: part 6 2026-07-06 18:05:31 +02:00
nym21 2207ec1b7e website_next: part 5 2026-07-06 15:54:25 +02:00
nym21 3f2a48f248 website_next: part 4 2026-07-06 13:17:11 +02:00
nym21 3f9edb211e website_next: part 3 2026-07-06 11:15:39 +02:00
nym21 eee1a10d2a website_next: part 2 2026-07-05 14:08:57 +02:00
nym21 0bf2cd77dc website_next: snapshot 2026-07-04 22:09:40 +02:00
nym21 a99c06013b website_next: snapshot 2026-07-04 13:27:57 +02:00
nym21 10ef95497f website_next: snapshot 2026-07-03 21:06:32 +02:00
nym21 e0a618837e website_next: snapshot 2026-07-03 11:29:13 +02:00
nym21 5bd1f0b625 deps: bumped 2026-07-02 16:46:47 +02:00
nym21 4d3e8bbcaf website: auto port selection 2026-07-01 17:23:02 +02:00
nym21 153fcdf4e0 website_next: move hash logic to clients 2026-07-01 17:04:29 +02:00
nym21 5d83ee4d70 changelog: updated 2026-06-29 17:02:34 +02:00
nym21 10d29aa2ea clients: bump versions 2026-06-27 18:03:51 +02:00
nym21 528880bd04 release: v0.3.6 2026-06-27 12:18:42 +02:00
nym21 5937fbe724 docs: update generated docs 2026-06-27 12:18:11 +02:00
nym21 7a718293c0 website: fix capture button part 2 2026-06-27 12:12:38 +02:00
nym21 75f37b6695 website: fix capture button 2026-06-27 12:05:39 +02:00
nym21 f9688629ca deps: bumped 2026-06-26 19:40:43 +02:00
nym21 c9e704bdf4 clients: bump versions 2026-06-25 12:31:07 +02:00
nym21 1e0a7d6d0f release: v0.3.5 2026-06-23 18:54:46 +02:00
nym21 565251f12f docs: update generated docs 2026-06-23 18:54:14 +02:00
nym21 9879a986aa fixes 2026-06-23 18:47:51 +02:00
nym21 8d7ef8da61 fixes 2026-06-23 18:42:28 +02:00
nym21 17478a4ac4 website: redesign part 34 2026-06-22 16:42:14 +02:00
nym21 b3031b3375 website: redesign part 33 2026-06-21 17:12:25 +02:00
nym21 2e401379a0 deps: bumped 2026-06-21 17:11:43 +02:00
nym21 45ab6ebf71 website: redesign part 32 2026-06-19 23:16:27 +02:00
nym21 00f7d69ea6 website: redesign part 31 2026-06-18 22:39:28 +02:00
nym21 408d83c350 global: private xpub support part 3 2026-06-17 21:23:26 +02:00
nym21 43df9e098c global: private xpub support part 2 2026-06-17 11:25:42 +02:00
nym21 0c7861071d global: private xpub support part 1 2026-06-16 23:37:03 +02:00
nym21 6f430bdb8c clients: bump versions 2026-06-15 16:24:56 +02:00
1170 changed files with 38474 additions and 24029 deletions
+1
View File
@@ -26,6 +26,7 @@ _*
/playground
/*.txt
/*.csv
/tmp
# Logs
*.log*
Generated
+329 -450
View File
File diff suppressed because it is too large Load Diff
+37 -35
View File
@@ -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.4"
package.version = "0.3.6"
package.homepage = "https://bitcoinresearchkit.org"
package.repository = "https://github.com/bitcoinresearchkit/brk"
package.readme = "README.md"
@@ -35,57 +35,59 @@ 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.4", path = "crates/brk_alloc" }
brk_bencher = { version = "0.3.4", path = "crates/brk_bencher" }
brk_bindgen = { version = "0.3.4", path = "crates/brk_bindgen" }
brk_cli = { version = "0.3.4", path = "crates/brk_cli" }
brk_client = { version = "0.3.4", path = "crates/brk_client" }
brk_cohort = { version = "0.3.4", path = "crates/brk_cohort" }
brk_computer = { version = "0.3.4", path = "crates/brk_computer" }
brk_error = { version = "0.3.4", path = "crates/brk_error" }
brk_fetcher = { version = "0.3.4", path = "crates/brk_fetcher" }
brk_indexer = { version = "0.3.4", path = "crates/brk_indexer" }
brk_iterator = { version = "0.3.4", path = "crates/brk_iterator" }
brk_logger = { version = "0.3.4", path = "crates/brk_logger" }
brk_mempool = { version = "0.3.4", path = "crates/brk_mempool" }
brk_oracle = { version = "0.3.4", path = "crates/brk_oracle" }
brk_query = { version = "0.3.4", path = "crates/brk_query", features = ["tokio"] }
brk_reader = { version = "0.3.4", path = "crates/brk_reader" }
brk_rpc = { version = "0.3.4", path = "crates/brk_rpc" }
brk_server = { version = "0.3.4", path = "crates/brk_server" }
brk_store = { version = "0.3.4", path = "crates/brk_store" }
brk_traversable = { version = "0.3.4", path = "crates/brk_traversable", features = ["pco", "derive"] }
brk_traversable_derive = { version = "0.3.4", path = "crates/brk_traversable_derive" }
brk_types = { version = "0.3.4", path = "crates/brk_types" }
brk_website = { version = "0.3.4", path = "crates/brk_website" }
bitcoin = { version = "0.32.102", 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.5"
fjall = "3.1.8"
indexmap = { version = "2.14.0", features = ["serde"] }
jiff = { version = "0.2.28", features = ["perf-inline", "tz-system"], default-features = false }
jiff = { version = "0.2.34", 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"
rustc-hash = "2.1.2"
rapidhash = "4.5.1"
rustc-hash = "2.1.3"
schemars = { version = "1.2.1", features = ["indexmap2"] }
serde = "1.0.228"
serde = "1.0.229"
serde_bytes = "0.11.19"
serde_derive = "1.0.228"
serde_json = { version = "1.0.150", features = ["float_roundtrip", "preserve_order"] }
serde_derive = "1.0.229"
serde_json = { version = "1.0.151", features = ["float_roundtrip", "preserve_order"] }
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"] }
tokio = { version = "1.53.0", features = ["rt-multi-thread"] }
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"] }
vecdb = { version = "0.10.3", features = ["derive", "serde_json", "pco", "schemars"] }
vecdb = { version = "0.10.4", features = ["derive", "serde_json", "pco", "schemars"] }
# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] }
[workspace.metadata.release]
shared-version = true
tag-name = "v{{version}}"
+7 -7
View File
@@ -291,7 +291,7 @@ fn collect_instance_analyses(
///
/// Supports two cases:
/// 1. **Embedded discriminator**: a substring varies per instance within field_parts.
/// E.g., `ratio_pct99_bps` vs `ratio_pct1_bps` → template `ratio_{disc}_bps`
/// E.g., `ratio_pct99_ppm` vs `ratio_pct1_ppm` → template `ratio_{disc}_ppm`
/// 2. **Suffix discriminator**: a common suffix is appended to all field_parts.
/// E.g., `ratio_sd` vs `ratio_sd_4y` → template `ratio_sd{disc}`
fn try_detect_template(
@@ -307,11 +307,11 @@ fn try_detect_template(
return Some(mode);
}
// Strategy 2: embedded discriminator (e.g., ratio_pct99_bps vs ratio_pct1_bps)
// Strategy 2: embedded discriminator (e.g., ratio_pct99_ppm vs ratio_pct1_ppm)
try_embedded_disc(majority, fields)
}
/// Strategy 1: embedded discriminator (e.g., pct99 inside ratio_pct99_bps)
/// Strategy 1: embedded discriminator (e.g., pct99 inside ratio_pct99_ppm)
fn try_embedded_disc(
majority: &[&InstanceAnalysis],
fields: &[PatternField],
@@ -772,7 +772,7 @@ mod tests {
use std::collections::BTreeSet;
let fields = vec![
PatternField {
name: "bps".into(),
name: "ppm".into(),
rust_type: "T".into(),
json_type: "n".into(),
indexes: BTreeSet::new(),
@@ -796,7 +796,7 @@ mod tests {
let pct99 = InstanceAnalysis {
base: "realized_price".into(),
field_parts: [
("bps".into(), "ratio_pct99_bps".into()),
("ppm".into(), "ratio_pct99_ppm".into()),
("price".into(), "pct99".into()),
("ratio".into(), "ratio_pct99".into()),
]
@@ -808,7 +808,7 @@ mod tests {
let pct1 = InstanceAnalysis {
base: "realized_price".into(),
field_parts: [
("bps".into(), "ratio_pct1_bps".into()),
("ppm".into(), "ratio_pct1_ppm".into()),
("price".into(), "pct1".into()),
("ratio".into(), "ratio_pct1".into()),
]
@@ -821,7 +821,7 @@ mod tests {
assert!(mode.is_some());
match mode.unwrap() {
PatternMode::Templated { templates } => {
assert_eq!(templates.get("bps").unwrap(), "ratio_{disc}_bps");
assert_eq!(templates.get("ppm").unwrap(), "ratio_{disc}_ppm");
assert_eq!(templates.get("price").unwrap(), "{disc}");
assert_eq!(templates.get("ratio").unwrap(), "ratio_{disc}");
}
@@ -89,7 +89,7 @@ impl LanguageSyntax for JavaScriptSyntax {
format!("_m({}, '{}')", var_name, template)
} else {
// Template with {disc}: use nested _m for proper separator handling
// "ratio_{disc}_bps" → split on {disc} → _m(_m(acc, 'ratio'), disc) then _bps
// "ratio_{disc}_ppm" → split on {disc} → _m(_m(acc, 'ratio'), disc) then _ppm
// But this is complex. For embedded disc, use string interpolation.
// For suffix disc (ends with {disc}), use _m composition.
if let Some(static_part) = template.strip_suffix("{disc}") {
@@ -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 {{
+2 -2
View File
@@ -107,7 +107,7 @@ pub trait LanguageSyntax {
/// - `"pct99"` (static) → `'pct99'` (JS) / `"pct99".to_string()` (Rust)
/// - `""` (empty) → `disc` (pass parent's disc through)
/// - `"p1sd{disc}"` (suffix) → `_m('p1sd', disc)` (composed)
/// - `"ratio_{disc}_bps"` (embedded) → `` `ratio_${disc}_bps` `` (template literal)
/// - `"ratio_{disc}_ppm"` (embedded) → `` `ratio_${disc}_ppm` `` (template literal)
fn disc_arg_expr(&self, template: &str) -> String;
/// Format a templated mode expression: substitute `{disc}` at runtime.
@@ -117,6 +117,6 @@ pub trait LanguageSyntax {
///
/// # Arguments
/// * `acc_var` - The accumulator variable (e.g., "acc")
/// * `template` - Template like `"ratio_{disc}_bps"` or `"{disc}"`
/// * `template` - Template like `"ratio_{disc}_ppm"` or `"{disc}"`
fn template_expr(&self, acc_var: &str, template: &str) -> String;
}
+1 -1
View File
@@ -26,7 +26,7 @@ pub enum PatternMode {
/// Fields construct series names using a template with a discriminator placeholder.
/// Factory takes two params: `acc` (base) and `disc` (discriminator).
/// Formula: `_m(acc, template.replace("{disc}", disc))`
/// Example: template `"ratio_{disc}_bps"` with disc `"pct99"` → `_m(acc, "ratio_pct99_bps")`
/// Example: template `"ratio_{disc}_ppm"` with disc `"pct99"` → `_m(acc, "ratio_pct99_ppm")`
Templated {
/// Maps field name to its template string containing `{disc}` placeholder
templates: BTreeMap<String, String>,
+1 -1
View File
@@ -128,7 +128,7 @@ impl StructuralPattern {
}
/// Extract the discriminator value by matching a template against a concrete string.
/// E.g., template `"ratio_{disc}_bps"` matched against `"ratio_pct99_bps"` yields `"pct99"`.
/// E.g., template `"ratio_{disc}_ppm"` matched against `"ratio_pct99_ppm"` yields `"pct99"`.
fn extract_disc(template: &str, value: &str) -> Option<String> {
let (prefix, suffix) = template.split_once("{disc}")?;
if value.starts_with(prefix) && value.ends_with(suffix) {
+1 -1
View File
@@ -25,7 +25,7 @@ owo-colors = { workspace = true }
tracing = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true }
toml = "1.1.2"
toml = "1.1.3"
vecdb = { workspace = true }
[[bin]]
+1
View File
@@ -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 }
+1209 -718
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -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()
);
}
@@ -14,7 +14,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.adjustment.bps.height.compute_ratio_change(
self.adjustment.raw.height.compute_ratio_change(
starting_height,
&indexer.vecs.blocks.difficulty,
2016,
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Epoch, StoredF32, StoredF64, StoredU32};
use brk_types::{Epoch, PartsPerMillionSigned32, StoredF32, StoredF64, StoredU32};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Resolutions};
@@ -7,7 +7,7 @@ use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Resolutions};
pub struct Vecs<M: StorageMode = Rw> {
pub value: Resolutions<StoredF64>,
pub hashrate: LazyPerBlock<StoredF64>,
pub adjustment: PercentPerBlock<BasisPointsSigned32, M>,
pub adjustment: PercentPerBlock<PartsPerMillionSigned32, M>,
pub epoch: PerBlock<Epoch, M>,
pub blocks_to_retarget: PerBlock<StoredU32, M>,
pub days_to_retarget: LazyPerBlock<StoredF32, StoredU32>,
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::BasisPoints16;
use brk_types::PartsPerMillion32;
use vecdb::Exit;
use super::Vecs;
@@ -8,10 +8,10 @@ use super::Vecs;
impl Vecs {
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.fullness.bps.compute_transform(
self.fullness.raw.compute_transform(
starting_height,
&indexer.vecs.blocks.weight,
|(h, weight, ..)| (h, BasisPoints16::from(weight.fullness())),
|(h, weight, ..)| (h, PartsPerMillion32::from(weight.fullness())),
exit,
)?;
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, StoredU64, Weight};
use brk_types::{PartsPerMillion32, StoredU64, Weight};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlockRolling, PercentVec};
@@ -7,5 +7,5 @@ use crate::internal::{LazyPerBlockRolling, PercentVec};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub weight: LazyPerBlockRolling<Weight, StoredU64>,
pub fullness: PercentVec<BasisPoints16, M>,
pub fullness: PercentVec<PartsPerMillion32, M>,
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::BasisPointsSigned32;
use brk_types::PartsPerMillionSigned64;
use vecdb::Exit;
use super::super::activity;
@@ -17,14 +17,14 @@ impl Vecs {
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.inflation_rate.bps.height.compute_transform2(
self.inflation_rate.raw.height.compute_transform2(
starting_height,
&activity.ratio.height,
&supply.inflation_rate.bps.height,
&supply.inflation_rate.raw.height,
|(h, a2vr, inflation, ..)| {
(
h,
BasisPointsSigned32::from(f64::from(a2vr) * f64::from(inflation)),
PartsPerMillionSigned64::from(f64::from(a2vr) * f64::from(inflation)),
)
},
exit,
@@ -18,7 +18,7 @@ impl Vecs {
inflation_rate: PercentPerBlock::forced_import(
db,
"cointime_adj_inflation_rate",
version + Version::ONE,
version + Version::TWO,
indexes,
)?,
tx_velocity_native: PerBlock::forced_import(
@@ -1,12 +1,12 @@
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, StoredF64};
use brk_types::{PartsPerMillionSigned64, StoredF64};
use vecdb::{Rw, StorageMode};
use crate::internal::{PerBlock, PercentPerBlock};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub inflation_rate: PercentPerBlock<BasisPointsSigned32, M>,
pub inflation_rate: PercentPerBlock<PartsPerMillionSigned64, M>,
pub tx_velocity_native: PerBlock<StoredF64, M>,
pub tx_velocity_fiat: PerBlock<StoredF64, M>,
}
+2 -2
View File
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, Cents};
use brk_types::{Cents, PartsPerMillion32};
use vecdb::{Rw, StorageMode};
use crate::internal::{FiatPerBlock, RatioPerBlock};
@@ -11,5 +11,5 @@ pub struct Vecs<M: StorageMode = Rw> {
pub vaulted: FiatPerBlock<Cents, M>,
pub active: FiatPerBlock<Cents, M>,
pub cointime: FiatPerBlock<Cents, M>,
pub aviv: RatioPerBlock<BasisPoints32, M>,
pub aviv: RatioPerBlock<PartsPerMillion32, M>,
}
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, StoredI64, StoredU64, Version};
use brk_types::{PartsPerMillionSigned64, StoredI64, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use crate::{
@@ -9,7 +9,7 @@ use crate::{
use super::AddrCountsVecs;
type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, BasisPointsSigned32>;
type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, PartsPerMillionSigned64>;
#[derive(Clone, Deref, DerefMut, Traversable)]
pub struct DeltaVecs(#[traversable(flatten)] pub WithAddrTypes<AddrDelta>);
@@ -21,7 +21,7 @@ impl DeltaVecs {
cached_starts: &Windows<&WindowStartVec>,
indexes: &indexes::Vecs,
) -> Self {
let version = version + Version::TWO;
let version = version + Version::new(3);
let all = LazyRollingDeltasFromHeight::new(
"addr_count",
@@ -2,7 +2,7 @@ use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, OutputType, StoredF32, StoredU32, StoredU64, Version};
use brk_types::{OutputType, PartsPerMillion32, StoredF32, StoredU32, StoredU64, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
@@ -67,11 +67,11 @@ use super::state::AddrTypeToAddrEventCount;
pub struct AddrEventsVecs<M: StorageMode = Rw> {
pub output_to_reused_addr_count:
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub output_to_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
pub spendable_output_to_reused_addr_share: PercentCumulativeRolling<BasisPoints16, M>,
pub output_to_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32, M>>,
pub spendable_output_to_reused_addr_share: PercentCumulativeRolling<PartsPerMillion32, M>,
pub input_from_reused_addr_count:
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub input_from_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
pub input_from_reused_addr_share: WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32, M>>,
pub active_reused_addr_count: PerBlockRollingAverage<StoredU32, StoredU64, M>,
pub active_reused_addr_share: PerBlockRollingAverage<StoredF32, StoredF32, M>,
}
@@ -94,7 +94,7 @@ impl AddrEventsVecs {
)
};
let import_percent =
|name: &str| -> Result<WithAddrTypes<PercentCumulativeRolling<BasisPoints16>>> {
|name: &str| -> Result<WithAddrTypes<PercentCumulativeRolling<PartsPerMillion32>>> {
Ok(WithAddrTypes {
all: PercentCumulativeRolling::forced_import(db, name, version, indexes)?,
by_addr_type: ByAddrType::new_with_name(|type_name| {
@@ -1,13 +1,13 @@
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, Sats, Version};
use brk_types::{Height, PartsPerMillion32, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{PercentPerBlock, RatioSatsBp16, WithAddrTypes},
internal::{PercentPerBlock, RatioSats, WithAddrTypes},
};
use super::vecs::AddrSupplyVecs;
@@ -18,7 +18,7 @@ use super::vecs::AddrSupplyVecs;
/// - Per-type: type's category supply / type's total supply
#[derive(Deref, DerefMut, Traversable)]
pub struct AddrSupplyShareVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub WithAddrTypes<PercentPerBlock<BasisPoints16, M>>,
#[traversable(flatten)] pub WithAddrTypes<PercentPerBlock<PartsPerMillion32, M>>,
);
impl AddrSupplyShareVecs {
@@ -29,7 +29,7 @@ impl AddrSupplyShareVecs {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(
WithAddrTypes::<PercentPerBlock<BasisPoints16>>::forced_import(
WithAddrTypes::<PercentPerBlock<PartsPerMillion32>>::forced_import(
db,
&format!("{name}_addr_supply_share"),
version,
@@ -46,7 +46,8 @@ impl AddrSupplyShareVecs {
type_supply_sats: &ByAddrType<&impl ReadableVec<Height, Sats>>,
exit: &Exit,
) -> Result<()> {
self.all.compute_binary::<Sats, Sats, RatioSatsBp16>(
self.all
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
max_from,
&supply.all.sats.height,
all_supply_sats,
@@ -57,7 +58,7 @@ impl AddrSupplyShareVecs {
.iter_mut()
.zip(supply.by_addr_type.iter().zip(type_supply_sats.iter()))
{
share.compute_binary::<Sats, Sats, RatioSatsBp16>(
share.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
max_from,
&cat.sats.height,
*denom,
@@ -4,7 +4,7 @@ use brk_cohort::{CohortContext, Filter, Filtered};
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Cents, Height, Sats, StoredI64, StoredU64, Version};
use brk_types::{Cents, Height, PartsPerMillionSigned64, Sats, StoredI64, StoredU64, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
@@ -28,7 +28,7 @@ pub struct AddrCohortVecs<M: StorageMode = Rw> {
#[traversable(flatten)]
pub metrics: MinimalCohortMetrics<M>,
pub addr_count: PerBlockWithDeltas<StoredU64, StoredI64, BasisPointsSigned32, M>,
pub addr_count: PerBlockWithDeltas<StoredU64, StoredI64, PartsPerMillionSigned64, M>,
}
impl AddrCohortVecs {
@@ -56,7 +56,7 @@ impl AddrCohortVecs {
db,
&cfg.name("addr_count"),
version,
Version::ONE,
Version::TWO,
indexes,
cached_starts,
)?;
@@ -1,5 +1,5 @@
use brk_cohort::{Filter, PROFITABILITY_RANGE_COUNT, compute_profitability_boundaries};
use brk_types::{Cents, CentsCompact, Sats};
use brk_types::{Cents, CentsCompact, PartsPerMillion32, Sats};
use crate::{
distribution::state::PendingDelta,
@@ -332,10 +332,16 @@ impl CostBasisFenwick {
// -----------------------------------------------------------------------
/// Compute supply density: % of supply with cost basis within ±5% of spot.
/// Returns (all_bps, sth_bps, lth_bps) as basis points (0-10000).
pub(super) fn density(&self, spot_price: Cents) -> (u16, u16, u16) {
pub(super) fn density(
&self,
spot_price: Cents,
) -> (PartsPerMillion32, PartsPerMillion32, PartsPerMillion32) {
if self.totals.all_sats <= 0 {
return (0, 0, 0);
return (
PartsPerMillion32::ZERO,
PartsPerMillion32::ZERO,
PartsPerMillion32::ZERO,
);
}
let range = self.density_range(spot_price);
@@ -345,16 +351,19 @@ impl CostBasisFenwick {
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),
Self::to_ppm(all_range, self.totals.all_sats),
Self::to_ppm(sth_range, self.totals.sth_sats),
Self::to_ppm(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) {
/// Compute supply density for entry cohorts: (discount, premium).
pub(super) fn entry_density(
&self,
spot_price: Cents,
) -> (PartsPerMillion32, PartsPerMillion32) {
if self.totals.all_sats <= 0 {
return (0, 0);
return (PartsPerMillion32::ZERO, PartsPerMillion32::ZERO);
}
let range = self.density_range(spot_price);
@@ -363,8 +372,8 @@ impl CostBasisFenwick {
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),
Self::to_ppm(discount_range, self.totals.discount_sats),
Self::to_ppm(premium_range, premium_total),
)
}
@@ -394,11 +403,11 @@ impl CostBasisFenwick {
}
#[inline(always)]
fn to_bps(range: i64, total: i64) -> u16 {
fn to_ppm(range: i64, total: i64) -> PartsPerMillion32 {
if total <= 0 {
0
PartsPerMillion32::ZERO
} else {
(range as f64 / total as f64 * 10000.0).round() as u16
PartsPerMillion32::from(range as f64 / total as f64)
}
}
@@ -16,6 +16,7 @@ use vecdb::{
use crate::{
blocks,
distribution::{
DynCohortVecs,
metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
@@ -23,7 +24,6 @@ use crate::{
TypeCohortMetrics,
},
state::UTXOCohortState,
DynCohortVecs,
},
indexes,
internal::{ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
@@ -2,7 +2,7 @@ use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{AGE_RANGE_NAMES, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES};
use brk_error::Result;
use brk_types::{BasisPoints16, Cents, CentsCompact, Date, Dollars, Sats, UrpdRaw};
use brk_types::{Cents, CentsCompact, Date, Dollars, PartsPerMillion32, Sats, UrpdRaw};
use rayon::prelude::*;
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
@@ -139,10 +139,14 @@ impl UTXOCohorts {
/// Push percentiles + density to cost basis vecs.
#[inline(always)]
fn push_cost_basis(percentiles: &PercentileResult, density_bps: u16, cost_basis: &mut CostBasis) {
fn push_cost_basis(
percentiles: &PercentileResult,
density: PartsPerMillion32,
cost_basis: &mut CostBasis,
) {
cost_basis.push_minmax(percentiles.min_price, percentiles.max_price);
cost_basis.push_percentiles(&percentiles.sat_prices, &percentiles.usd_prices);
cost_basis.push_density(BasisPoints16::from(density_bps));
cost_basis.push_density(density);
}
#[inline(always)]
@@ -1,6 +1,8 @@
use brk_cohort::Filter;
use brk_error::Result;
use brk_types::{BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, Height, Version};
use brk_types::{
Cents, Height, PartsPerMillion32, PartsPerMillionSigned32, PartsPerMillionSigned64, Version,
};
use schemars::JsonSchema;
use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
@@ -39,12 +41,11 @@ impl_config_import!(
ValuePerBlockCumulative,
PriceWithRatioPerBlock,
PriceWithRatioExtendedPerBlock,
RatioPerBlock<BasisPoints32>,
RatioPerBlock<BasisPointsSigned32>,
PercentPerBlock<BasisPoints16>,
PercentPerBlock<BasisPoints32>,
PercentPerBlock<BasisPointsSigned32>,
PercentRollingWindows<BasisPoints32>,
RatioPerBlock<PartsPerMillionSigned32>,
PercentPerBlock<PartsPerMillion32>,
PercentPerBlock<PartsPerMillionSigned32>,
PercentPerBlock<PartsPerMillionSigned64>,
PercentRollingWindows<PartsPerMillion32>,
Price<PerBlock<Cents>>,
);
@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::CentsSquaredSats;
use brk_types::{BasisPoints16, Cents, Height, Sats, Version};
use brk_types::{Cents, Height, PartsPerMillion32, Sats, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::internal::{PERCENTILES_LEN, PerBlock, PercentPerBlock, PercentilesVecs, Price};
@@ -25,7 +25,7 @@ pub struct CostBasis<M: StorageMode = Rw> {
pub max: Price<PerBlock<Cents, M>>,
pub per_coin: PercentilesVecs<M>,
pub per_dollar: PercentilesVecs<M>,
pub supply_density: PercentPerBlock<BasisPoints16, M>,
pub supply_density: PercentPerBlock<PartsPerMillion32, M>,
}
impl CostBasis {
@@ -88,7 +88,7 @@ impl CostBasis {
.height
.len()
.min(self.max.cents.height.len())
.min(self.supply_density.bps.height.len())
.min(self.supply_density.raw.height.len())
}
#[inline(always)]
@@ -108,8 +108,8 @@ impl CostBasis {
}
#[inline(always)]
pub(crate) fn push_density(&mut self, density_bps: BasisPoints16) {
self.supply_density.bps.height.push(density_bps);
pub(crate) fn push_density(&mut self, density: PartsPerMillion32) {
self.supply_density.raw.height.push(density);
}
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
@@ -128,7 +128,7 @@ impl CostBasis {
&mut self.in_loss.per_dollar.cents.height,
&mut self.min.cents.height,
&mut self.max.cents.height,
&mut self.supply_density.bps.height,
&mut self.supply_density.raw.height,
];
vecs.extend(
self.per_coin
@@ -1,7 +1,9 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Height, StoredF32, StoredI64, StoredU32, StoredU64, Version};
use brk_types::{
Height, PartsPerMillionSigned64, StoredF32, StoredI64, StoredU32, StoredU64, Version,
};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{
@@ -15,7 +17,7 @@ use crate::{
/// Base output metrics: utxo_count + delta.
#[derive(Traversable)]
pub struct OutputsBase<M: StorageMode = Rw> {
pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, BasisPointsSigned32, M>,
pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, PartsPerMillionSigned64, M>,
pub spent_count: PerBlockCumulativeRolling<StoredU32, StoredU64, M>,
pub spending_rate: PerBlock<StoredF32, M>,
}
@@ -28,7 +30,7 @@ impl OutputsBase {
cfg.db,
&cfg.name("utxo_count"),
cfg.version,
v1,
Version::TWO,
cfg.indexes,
cfg.cached_starts,
)?,
@@ -2,7 +2,7 @@ use brk_cohort::{Loss, Profit, ProfitabilityRange};
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Bitcoin, Cents, Dollars, Sats, Version};
use brk_types::{Bitcoin, Cents, Dollars, PartsPerMillionSigned32, Sats, Version};
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{
@@ -24,7 +24,7 @@ pub struct ProfitabilityBucket<M: StorageMode = Rw> {
pub supply: WithSth<ValuePerBlockWithDeltas<M>, ValuePerBlock<M>>,
pub realized_cap: WithSth<PerBlock<Dollars, M>>,
pub unrealized_pnl: WithSth<PerBlock<Dollars, M>>,
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
pub nupl: RatioPerBlock<PartsPerMillionSigned32, M>,
}
impl<M: StorageMode> ProfitabilityBucket<M> {
@@ -152,7 +152,7 @@ impl ProfitabilityBucket {
exit,
)?;
self.nupl.bps.height.compute_transform3(
self.nupl.raw.height.compute_transform3(
max_from,
&prices.spot.cents.height,
&self.realized_cap.all.height,
@@ -161,11 +161,11 @@ impl ProfitabilityBucket {
let p = spot.as_u128();
let supply = supply_sats.as_u128();
if p == 0 || supply == 0 {
(i, BasisPointsSigned32::ZERO)
(i, PartsPerMillionSigned32::ZERO)
} else {
let rp = Cents::from(cap_dollars).as_u128() * Sats::ONE_BTC_U128 / supply;
let bps = ((p as i128 - rp as i128) * 10000) / p as i128;
(i, BasisPointsSigned32::from(bps as i32))
let ratio = (p as f64 - rp as f64) / p as f64;
(i, PartsPerMillionSigned32::from(ratio))
}
},
exit,
@@ -230,7 +230,7 @@ impl ProfitabilityBucket {
&mut self.realized_cap.sth.height,
&mut self.unrealized_pnl.all.height,
&mut self.unrealized_pnl.sth.height,
&mut self.nupl.bps.height,
&mut self.nupl.raw.height,
]
}
}
@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{
BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Dollars, Height, StoredF64, Version,
Bitcoin, Cents, CentsSigned, Dollars, Height, PartsPerMillionSigned64, StoredF64, Version,
};
use derive_more::{Deref, DerefMut};
use vecdb::{
@@ -45,8 +45,12 @@ pub struct RealizedCore<M: StorageMode = Rw> {
#[traversable(wrap = "loss", rename = "negative")]
pub neg_loss: NegRealizedLoss,
pub net_pnl:
FiatPerBlockCumulativeWithSumsAndDeltas<CentsSigned, CentsSigned, BasisPointsSigned32, M>,
pub net_pnl: FiatPerBlockCumulativeWithSumsAndDeltas<
CentsSigned,
CentsSigned,
PartsPerMillionSigned64,
M,
>,
pub sopr: RealizedSoprCore<M>,
}
@@ -80,7 +84,7 @@ impl RealizedCore {
cfg.db,
&cfg.name("net_realized_pnl"),
cfg.version + v1,
Version::new(4),
Version::new(5),
cfg.indexes,
cfg.cached_starts,
)?;
@@ -2,8 +2,8 @@ use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats,
Dollars, Height, StoredF64, Version,
Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats, Dollars, Height, PartsPerMillion32,
PartsPerMillionSigned64, StoredF64, Version,
};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
@@ -13,10 +13,9 @@ use crate::{
distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital},
internal::{
FiatPerBlockCumulativeWithSums, PercentPerBlock, PercentRollingWindows,
PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32,
RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlockPercentiles,
RatioPerBlockStdDevBands, RatioSma, RollingWindows, RollingWindowsFrom1w,
ValuePerBlockCumulativeRolling,
PriceWithRatioExtendedPerBlock, RatioCents, RatioCents64, RatioCentsSignedCents,
RatioCentsSignedDollars, RatioDollars, RatioPerBlockPercentiles, RatioPerBlockStdDevBands,
RatioSma, RollingWindows, RollingWindowsFrom1w, ValuePerBlockCumulativeRolling,
},
price,
};
@@ -28,9 +27,9 @@ use super::RealizedCore;
#[derive(Traversable)]
pub struct RealizedNetPnl<M: StorageMode = Rw> {
#[traversable(wrap = "change_1m", rename = "to_rcap")]
pub change_1m_to_rcap: PercentPerBlock<BasisPointsSigned32, M>,
pub change_1m_to_rcap: PercentPerBlock<PartsPerMillionSigned64, M>,
#[traversable(wrap = "change_1m", rename = "to_mcap")]
pub change_1m_to_mcap: PercentPerBlock<BasisPointsSigned32, M>,
pub change_1m_to_mcap: PercentPerBlock<PartsPerMillionSigned64, M>,
}
#[derive(Traversable)]
@@ -60,7 +59,7 @@ pub struct RealizedFull<M: StorageMode = Rw> {
pub core: RealizedCore<M>,
pub gross_pnl: FiatPerBlockCumulativeWithSums<Cents, M>,
pub sell_side_risk_ratio: PercentRollingWindows<BasisPoints32, M>,
pub sell_side_risk_ratio: PercentRollingWindows<PartsPerMillion32, M>,
pub net_pnl: RealizedNetPnl<M>,
pub sopr: RealizedSopr<M>,
pub peak_regret: RealizedPeakRegret<M>,
@@ -71,7 +70,7 @@ pub struct RealizedFull<M: StorageMode = Rw> {
#[traversable(hidden)]
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
#[traversable(wrap = "cap", rename = "to_own_mcap")]
pub cap_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
pub cap_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "price", rename = "percentiles")]
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
@@ -95,8 +94,8 @@ impl RealizedFull {
// Net PnL
let net_pnl = RealizedNetPnl {
change_1m_to_rcap: cfg.import("net_pnl_change_1m_to_rcap", Version::new(4))?,
change_1m_to_mcap: cfg.import("net_pnl_change_1m_to_mcap", Version::new(4))?,
change_1m_to_rcap: cfg.import("net_pnl_change_1m_to_rcap", Version::new(5))?,
change_1m_to_mcap: cfg.import("net_pnl_change_1m_to_mcap", Version::new(5))?,
};
// SOPR
@@ -286,7 +285,7 @@ impl RealizedFull {
// Net PnL 1m change relative to rcap and mcap
self.net_pnl
.change_1m_to_rcap
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps32>(
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCents<PartsPerMillionSigned64>>(
starting_lengths.height,
&self.core.net_pnl.delta.absolute._1m.cents.height,
&self.core.minimal.cap.cents.height,
@@ -294,7 +293,11 @@ impl RealizedFull {
)?;
self.net_pnl
.change_1m_to_mcap
.compute_binary::<CentsSigned, Dollars, RatioCentsSignedDollarsBps32>(
.compute_binary::<
CentsSigned,
Dollars,
RatioCentsSignedDollars<PartsPerMillionSigned64>,
>(
starting_lengths.height,
&self.core.net_pnl.delta.absolute._1m.cents.height,
height_to_market_cap,
@@ -313,7 +316,7 @@ impl RealizedFull {
.into_iter()
.zip(self.gross_pnl.sum.as_array())
{
ssrr.compute_binary::<Cents, Cents, RatioCentsBp32>(
ssrr.compute_binary::<Cents, Cents, RatioCents<PartsPerMillion32>>(
starting_lengths.height,
&rv.cents.height,
&self.core.minimal.cap.cents.height,
@@ -323,7 +326,7 @@ impl RealizedFull {
// Realized cap relative to own market cap
self.cap_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
starting_lengths.height,
&self.core.minimal.cap.usd.height,
height_to_market_cap,
@@ -2,8 +2,8 @@ use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Height, Sats, StoredF32,
Version,
Bitcoin, Cents, CentsSigned, Height, PartsPerMillion64, PartsPerMillionSigned64, Sats,
StoredF32, Version,
};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
@@ -20,7 +20,7 @@ use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct RealizedMinimal<M: StorageMode = Rw> {
pub cap: FiatPerBlockWithDeltas<Cents, CentsSigned, BasisPointsSigned32, M>,
pub cap: FiatPerBlockWithDeltas<Cents, CentsSigned, PartsPerMillionSigned64, M>,
pub profit: FiatPerBlockCumulativeWithSums<Cents, M>,
pub loss: FiatPerBlockCumulativeWithSums<Cents, M>,
pub price: PriceWithRatioPerBlock<M>,
@@ -35,13 +35,13 @@ impl RealizedMinimal {
cfg.db,
&cfg.name("realized_cap"),
cfg.version,
v1,
Version::TWO,
cfg.indexes,
cfg.cached_starts,
)?;
let price: PriceWithRatioPerBlock = cfg.import("realized_price", v1)?;
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, PartsPerMillion64>(
&cfg.name("mvrv"),
cfg.version,
&price.ratio,
@@ -1,9 +1,9 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPoints32, BasisPointsSigned32, Dollars, Height, Version};
use brk_types::{Dollars, Height, PartsPerMillion32, PartsPerMillionSigned32, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32};
use crate::internal::{PercentPerBlock, RatioDollars};
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
@@ -11,11 +11,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
#[derive(Traversable)]
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
#[traversable(wrap = "unrealized/profit", rename = "to_own_mcap")]
pub unrealized_profit_to_own_mcap: PercentPerBlock<BasisPoints16, M>,
pub unrealized_profit_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/loss", rename = "to_own_mcap")]
pub unrealized_loss_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
pub unrealized_loss_to_own_mcap: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_mcap")]
pub net_unrealized_pnl_to_own_mcap: PercentPerBlock<BasisPointsSigned32, M>,
pub net_unrealized_pnl_to_own_mcap: PercentPerBlock<PartsPerMillionSigned32, M>,
}
impl RelativeExtendedOwnMarketCap {
@@ -39,21 +39,21 @@ impl RelativeExtendedOwnMarketCap {
exit: &Exit,
) -> Result<()> {
self.unrealized_profit_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
max_from,
&unrealized.profit.usd.height,
own_market_cap,
exit,
)?;
self.unrealized_loss_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
max_from,
&unrealized.loss.usd.height,
own_market_cap,
exit,
)?;
self.net_unrealized_pnl_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBps32>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillionSigned32>>(
max_from,
&unrealized.net_pnl.usd.height,
own_market_cap,
@@ -1,9 +1,9 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPointsSigned32, Dollars, Height, Version};
use brk_types::{Dollars, Height, PartsPerMillion32, PartsPerMillionSigned32, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioDollarsBp16, RatioDollarsBps32};
use crate::internal::{PercentPerBlock, RatioDollars};
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
@@ -11,11 +11,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
#[derive(Traversable)]
pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> {
#[traversable(wrap = "unrealized/profit", rename = "to_own_gross_pnl")]
pub unrealized_profit_to_own_gross_pnl: PercentPerBlock<BasisPoints16, M>,
pub unrealized_profit_to_own_gross_pnl: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/loss", rename = "to_own_gross_pnl")]
pub unrealized_loss_to_own_gross_pnl: PercentPerBlock<BasisPoints16, M>,
pub unrealized_loss_to_own_gross_pnl: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "to_own_gross_pnl")]
pub net_unrealized_pnl_to_own_gross_pnl: PercentPerBlock<BasisPointsSigned32, M>,
pub net_unrealized_pnl_to_own_gross_pnl: PercentPerBlock<PartsPerMillionSigned32, M>,
}
impl RelativeExtendedOwnPnl {
@@ -39,21 +39,21 @@ impl RelativeExtendedOwnPnl {
exit: &Exit,
) -> Result<()> {
self.unrealized_profit_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
max_from,
&unrealized.profit.usd.height,
gross_pnl_usd,
exit,
)?;
self.unrealized_loss_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
max_from,
&unrealized.loss.usd.height,
gross_pnl_usd,
exit,
)?;
self.net_unrealized_pnl_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBps32>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillionSigned32>>(
max_from,
&unrealized.net_pnl.usd.height,
gross_pnl_usd,
@@ -1,25 +1,25 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Dollars, Height, Sats, Version};
use brk_types::{Dollars, Height, PartsPerMillion32, Sats, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::{
distribution::metrics::{ImportConfig, SupplyCore, UnrealizedBasic},
internal::{PercentPerBlock, RatioDollarsBp16, RatioSatsBp16},
internal::{PercentPerBlock, RatioDollars, RatioSats},
};
/// Full relative metrics (sth/lth/all tier).
#[derive(Traversable)]
pub struct RelativeFull<M: StorageMode = Rw> {
#[traversable(wrap = "supply/in_profit", rename = "share")]
pub supply_in_profit_share: PercentPerBlock<BasisPoints16, M>,
pub supply_in_profit_share: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "supply/in_loss", rename = "share")]
pub supply_in_loss_share: PercentPerBlock<BasisPoints16, M>,
pub supply_in_loss_share: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/profit", rename = "to_mcap")]
pub unrealized_profit_to_mcap: PercentPerBlock<BasisPoints16, M>,
pub unrealized_profit_to_mcap: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "unrealized/loss", rename = "to_mcap")]
pub unrealized_loss_to_mcap: PercentPerBlock<BasisPoints16, M>,
pub unrealized_loss_to_mcap: PercentPerBlock<PartsPerMillion32, M>,
}
impl RelativeFull {
@@ -44,14 +44,14 @@ impl RelativeFull {
exit: &Exit,
) -> Result<()> {
self.supply_in_profit_share
.compute_binary::<Sats, Sats, RatioSatsBp16>(
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
max_from,
&supply.in_profit.sats.height,
&supply.total.sats.height,
exit,
)?;
self.supply_in_loss_share
.compute_binary::<Sats, Sats, RatioSatsBp16>(
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
max_from,
&supply.in_loss.sats.height,
&supply.total.sats.height,
@@ -59,14 +59,14 @@ impl RelativeFull {
)?;
self.unrealized_profit_to_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
max_from,
&unrealized.profit.usd.height,
market_cap,
exit,
)?;
self.unrealized_loss_to_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
max_from,
&unrealized.loss.usd.height,
market_cap,
@@ -1,9 +1,9 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Cents, Height, Version};
use brk_types::{Cents, Height, PartsPerMillion32, Version};
use vecdb::{Exit, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioCentsBp16};
use crate::internal::{PercentPerBlock, RatioCents};
use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
@@ -12,9 +12,9 @@ use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
#[derive(Traversable)]
pub struct RelativeInvestedCapital<M: StorageMode = Rw> {
#[traversable(wrap = "invested_capital/in_profit", rename = "share")]
pub in_profit_share: PercentPerBlock<BasisPoints16, M>,
pub in_profit_share: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(wrap = "invested_capital/in_loss", rename = "share")]
pub in_loss_share: PercentPerBlock<BasisPoints16, M>,
pub in_loss_share: PercentPerBlock<PartsPerMillion32, M>,
}
impl RelativeInvestedCapital {
@@ -35,14 +35,14 @@ impl RelativeInvestedCapital {
) -> Result<()> {
let realized_cap = &realized.core.minimal.cap.cents.height;
self.in_profit_share
.compute_binary::<Cents, Cents, RatioCentsBp16>(
.compute_binary::<Cents, Cents, RatioCents<PartsPerMillion32>>(
max_from,
&unrealized.invested_capital.in_profit.cents.height,
realized_cap,
exit,
)?;
self.in_loss_share
.compute_binary::<Cents, Cents, RatioCentsBp16>(
.compute_binary::<Cents, Cents, RatioCents<PartsPerMillion32>>(
max_from,
&unrealized.invested_capital.in_loss.cents.height,
realized_cap,
@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPointsSigned32, Height, Sats, SatsSigned, Version};
use brk_types::{Height, PartsPerMillion32, PartsPerMillionSigned64, Sats, SatsSigned, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{
@@ -10,7 +10,7 @@ use crate::{
};
use crate::internal::{
LazyRollingDeltasAmountFromHeight, PercentPerBlock, RatioSatsBp16, ValuePerBlock,
LazyRollingDeltasAmountFromHeight, PercentPerBlock, RatioSats, ValuePerBlock,
};
use crate::distribution::metrics::ImportConfig;
@@ -19,9 +19,9 @@ use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct SupplyBase<M: StorageMode = Rw> {
pub total: ValuePerBlock<M>,
pub delta: LazyRollingDeltasAmountFromHeight<Sats, SatsSigned, BasisPointsSigned32>,
pub delta: LazyRollingDeltasAmountFromHeight<Sats, SatsSigned, PartsPerMillionSigned64>,
#[traversable(rename = "dominance")]
pub dominance: PercentPerBlock<BasisPoints16, M>,
pub dominance: PercentPerBlock<PartsPerMillion32, M>,
}
impl SupplyBase {
@@ -30,7 +30,7 @@ impl SupplyBase {
let delta = LazyRollingDeltasAmountFromHeight::new(
&cfg.name("supply_delta"),
cfg.version + Version::ONE,
cfg.version + Version::TWO,
&supply.sats.height,
cfg.cached_starts,
cfg.indexes,
@@ -58,7 +58,7 @@ impl SupplyBase {
vec![
&mut self.total.sats.height as &mut dyn AnyStoredVec,
&mut self.total.cents.height,
&mut self.dominance.bps.height,
&mut self.dominance.raw.height,
]
}
@@ -77,12 +77,13 @@ impl SupplyBase {
all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {
self.dominance.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&self.total.sats.height,
all_supply_sats,
exit,
)
self.dominance
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>>(
max_from,
&self.total.sats.height,
all_supply_sats,
exit,
)
}
pub(crate) fn compute_from_stateful(
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Cents, Height, Version};
use brk_types::{Cents, Height, PartsPerMillionSigned32, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::RatioPerBlock;
@@ -9,7 +9,7 @@ use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct UnrealizedMinimal<M: StorageMode = Rw> {
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
pub nupl: RatioPerBlock<PartsPerMillionSigned32, M>,
}
impl UnrealizedMinimal {
@@ -31,18 +31,18 @@ impl UnrealizedMinimal {
realized_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.nupl.bps.height.compute_transform2(
self.nupl.raw.height.compute_transform2(
max_from,
spot_price,
realized_price,
|(i, price, realized_price, ..)| {
let p = price.as_u128();
if p == 0 {
(i, BasisPointsSigned32::ZERO)
(i, PartsPerMillionSigned32::ZERO)
} else {
let rp = realized_price.as_u128();
let nupl_bps = ((p as i128 - rp as i128) * 10000) / p as i128;
(i, BasisPointsSigned32::from(nupl_bps as i32))
let ratio = (p as f64 - rp as f64) / p as f64;
(i, PartsPerMillionSigned32::from(ratio))
}
},
exit,
+1 -6
View File
@@ -250,12 +250,7 @@ impl Vecs {
identity: LazyVecFrom1::init(
"op_return_index",
version,
indexer
.vecs
.scripts
.op_return
.to_tx_index
.read_only_boxed_clone(),
indexer.vecs.op_return.to_tx_index.read_only_boxed_clone(),
|index, _| index,
),
},
+10 -10
View File
@@ -1,10 +1,10 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{Bitcoin, Dollars, StoredF32};
use brk_types::{Bitcoin, Dollars, PartsPerMillion64, StoredF32};
use vecdb::Exit;
use super::{Vecs, gini};
use crate::{distribution, internal::RatioDollarsBp32, market, mining, transactions};
use crate::{distribution, internal::RatioDollars, market, mining, transactions};
impl Vecs {
#[allow(clippy::too_many_arguments)]
@@ -23,8 +23,8 @@ impl Vecs {
// Puell Multiple: daily_subsidy_usd / sma_365d_subsidy_usd
self.puell_multiple
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.raw
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
starting_lengths.height,
&mining.rewards.subsidy.block.usd,
&mining.rewards.subsidy.average._1y.usd.height,
@@ -36,8 +36,8 @@ impl Vecs {
// RHODL Ratio: 1d-1w realized cap / 1y-2y realized cap
self.rhodl_ratio
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.raw
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
starting_lengths.height,
&distribution
.utxo_cohorts
@@ -70,8 +70,8 @@ impl Vecs {
.usd
.height;
self.nvt
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.raw
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
starting_lengths.height,
market_cap,
&transactions.volume.transfer_volume.sum._24h.usd.height,
@@ -80,8 +80,8 @@ impl Vecs {
// Thermocap Multiple: market_cap / thermo_cap
self.thermo_cap_multiple
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.raw
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion64>>(
starting_lengths.height,
market_cap,
&mining.rewards.subsidy.cumulative.usd.height,
+11 -11
View File
@@ -1,12 +1,12 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{BasisPoints16, Sats, StoredU64, Version};
use brk_types::{PartsPerMillion32, Sats, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
use crate::{distribution, internal::PercentPerBlock};
pub(super) fn compute(
gini: &mut PercentPerBlock<BasisPoints16>,
gini: &mut PercentPerBlock<PartsPerMillion32>,
distribution: &distribution::Vecs,
indexer: &Indexer,
exit: &Exit,
@@ -34,13 +34,13 @@ pub(super) fn compute(
.iter()
.fold(Version::ZERO, |acc, v| acc + v.version());
gini.bps
gini.raw
.height
.validate_computed_version_or_reset(source_version)?;
let min_len = gini.bps.height.len().min(starting_height.to_usize());
let min_len = gini.raw.height.len().min(starting_height.to_usize());
gini.bps.height.truncate_if_needed_at(min_len)?;
gini.raw.height.truncate_if_needed_at(min_len)?;
let total_heights = supply_vecs
.iter()
@@ -49,7 +49,7 @@ pub(super) fn compute(
.unwrap_or(0)
.min(count_vecs.iter().map(|v| v.len()).min().unwrap_or(0));
let start_height = gini.bps.height.len();
let start_height = gini.raw.height.len();
if start_height >= total_heights {
return Ok(());
}
@@ -73,23 +73,23 @@ pub(super) fn compute(
let count: u64 = count_data[c][offset].into();
buckets.push((count, supply));
}
gini.bps.height.push(gini_from_lorenz(&buckets));
gini.raw.height.push(gini_from_lorenz(&buckets));
}
{
let _lock = exit.lock();
gini.bps.height.write()?;
gini.raw.height.write()?;
}
Ok(())
}
fn gini_from_lorenz(buckets: &[(u64, u64)]) -> BasisPoints16 {
fn gini_from_lorenz(buckets: &[(u64, u64)]) -> PartsPerMillion32 {
let total_count: u64 = buckets.iter().map(|(c, _)| c).sum();
let total_supply: u64 = buckets.iter().map(|(_, s)| s).sum();
if total_count == 0 || total_supply == 0 {
return BasisPoints16::ZERO;
return PartsPerMillion32::ZERO;
}
let (mut cumulative_count, mut cumulative_supply, mut area) = (0u64, 0u64, 0.0f64);
@@ -103,5 +103,5 @@ fn gini_from_lorenz(buckets: &[(u64, u64)]) -> BasisPoints16 {
area += (p1 - p0) * (w0 + w1) / 2.0;
}
BasisPoints16::from(1.0 - 2.0 * area)
PartsPerMillion32::from(1.0 - 2.0 * area)
}
+6 -6
View File
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
use brk_types::{PartsPerMillion32, PartsPerMillion64, StoredF32};
use vecdb::{Database, Rw, StorageMode};
use super::rarity_meter::RarityMeter;
@@ -15,11 +15,11 @@ pub struct DormancyVecs<M: StorageMode = Rw> {
pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
pub(crate) db: Database,
pub puell_multiple: RatioPerBlock<BasisPoints32, M>,
pub nvt: RatioPerBlock<BasisPoints32, M>,
pub gini: PercentPerBlock<BasisPoints16, M>,
pub rhodl_ratio: RatioPerBlock<BasisPoints32, M>,
pub thermo_cap_multiple: RatioPerBlock<BasisPoints32, M>,
pub puell_multiple: RatioPerBlock<PartsPerMillion64, M>,
pub nvt: RatioPerBlock<PartsPerMillion64, M>,
pub gini: PercentPerBlock<PartsPerMillion32, M>,
pub rhodl_ratio: RatioPerBlock<PartsPerMillion64, M>,
pub thermo_cap_multiple: RatioPerBlock<PartsPerMillion64, M>,
pub coindays_destroyed_supply_adj: PerBlock<StoredF32, M>,
pub coinyears_destroyed_supply_adj: PerBlock<StoredF32, M>,
pub dormancy: DormancyVecs<M>,
@@ -6,6 +6,8 @@ use vecdb::{AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
use super::{Vecs, WithInputTypes};
use crate::internal::{CoinbasePolicy, PerBlockCumulativeRolling, walk_blocks};
const WRITE_INTERVAL: usize = 10_000;
impl Vecs {
pub(crate) fn compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()> {
let starting_lengths = indexer.safe_lengths();
@@ -37,6 +39,7 @@ impl Vecs {
let mut itype_cursor = indexer.vecs.inputs.output_type.cursor();
let mut fi_in_cursor = indexer.vecs.transactions.first_txin_index.cursor();
let mut height = skip;
walk_blocks(
&fi_batch,
@@ -65,7 +68,8 @@ impl Vecs {
);
push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type);
if self.input_count.all.block.batch_limit_reached() {
height += 1;
if height.is_multiple_of(WRITE_INTERVAL) {
let _lock = exit.lock();
self.input_count.write()?;
self.tx_count.write()?;
@@ -79,12 +83,12 @@ impl Vecs {
self.input_count.write()?;
self.tx_count.write()?;
}
self.input_count
.compute_rest(starting_lengths.height, exit)?;
self.tx_count.compute_rest(starting_lengths.height, exit)?;
}
self.input_count
.compute_rest(starting_lengths.height, exit)?;
self.tx_count.compute_rest(starting_lengths.height, exit)?;
for (otype, source) in self.input_count.by_type.iter_typed() {
self.input_share.get_mut(otype).compute_count_ratio(
source,
@@ -1,6 +1,6 @@
use brk_cohort::SpendableType;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, StoredU64};
use brk_types::{PartsPerMillion32, StoredU64};
use vecdb::{Rw, StorageMode};
use super::WithInputTypes;
@@ -9,7 +9,7 @@ use crate::internal::{PerBlockCumulativeRolling, PercentCumulativeRolling};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub input_count: WithInputTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub input_share: SpendableType<PercentCumulativeRolling<BasisPoints16, M>>,
pub input_share: SpendableType<PercentCumulativeRolling<PartsPerMillion32, M>>,
pub tx_count: WithInputTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub tx_share: SpendableType<PercentCumulativeRolling<BasisPoints16, M>>,
pub tx_share: SpendableType<PercentCumulativeRolling<PartsPerMillion32, M>>,
}
+2 -3
View File
@@ -3,13 +3,12 @@ use brk_indexer::Indexer;
use vecdb::Exit;
use super::Vecs;
use crate::{blocks, indexes};
use crate::blocks;
impl Vecs {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
blocks: &blocks::Vecs,
exit: &Exit,
) -> Result<()> {
@@ -18,7 +17,7 @@ impl Vecs {
let starting_lengths = indexer.safe_lengths();
self.spent.compute(indexer, exit)?;
self.count.compute(indexer, indexes, blocks, exit)?;
self.count.compute(indexer, blocks, exit)?;
self.per_sec.compute(&self.count, &starting_lengths, exit)?;
self.by_type.compute(indexer, exit)?;
@@ -3,27 +3,25 @@ use brk_indexer::Indexer;
use vecdb::Exit;
use super::Vecs;
use crate::{blocks, indexes};
use crate::blocks;
impl Vecs {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
blocks: &blocks::Vecs,
exit: &Exit,
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
let window_starts = blocks.lookback.window_starts();
self.0.compute(
self.sum.compute_count_from_indexes(
starting_height,
&indexes.tx_index.input_count,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&window_starts,
&indexer.vecs.inputs.first_txin_index,
&indexer.vecs.inputs.outpoint,
exit,
0,
)?;
self.compute_rest(starting_height, &window_starts, exit)?;
Ok(())
}
@@ -1,5 +1,5 @@
use brk_error::Result;
use brk_types::BasisPointsSigned16;
use brk_types::PartsPerMillionSigned32;
use vecdb::{EagerVec, Exit, PcoVec, ReadableVec, VecIndex, VecValue};
pub trait ComputeDrawdown<I: VecIndex> {
@@ -16,7 +16,7 @@ pub trait ComputeDrawdown<I: VecIndex> {
f64: From<C> + From<A>;
}
impl<I> ComputeDrawdown<I> for EagerVec<PcoVec<I, BasisPointsSigned16>>
impl<I> ComputeDrawdown<I> for EagerVec<PcoVec<I, PartsPerMillionSigned32>>
where
I: VecIndex,
{
@@ -39,9 +39,9 @@ where
|(i, current, ath, _)| {
let ath_f64 = f64::from(ath);
let drawdown = if ath_f64 == 0.0 {
BasisPointsSigned16::default()
PartsPerMillionSigned32::default()
} else {
BasisPointsSigned16::from((f64::from(current) - ath_f64) / ath_f64)
PartsPerMillionSigned32::from((f64::from(current) - ath_f64) / ath_f64)
};
(i, drawdown)
},
@@ -4,7 +4,7 @@ use super::fenwick::FenwickTree;
/// Fast expanding percentile tracker using a Fenwick tree (Binary Indexed Tree).
///
/// Values are discretized to 10 BPS (0.1%) resolution and tracked in
/// Values are discretized to 0.001 ratio resolution and tracked in
/// a fixed-size frequency array with Fenwick prefix sums. This gives:
/// - O(log N) insert (N = tree size, ~16 ops for 43k buckets)
/// - O(log N) percentile query via prefix-sum walk
@@ -15,11 +15,9 @@ pub(crate) struct ExpandingPercentiles {
count: u32,
}
/// Bucket granularity in BPS. 10 BPS = 0.1% = 0.001 ratio.
const BUCKET_BPS: i32 = 10;
/// Max ratio supported: 43.0 = 430,000 BPS.
const MAX_BPS: i32 = 430_000;
const TREE_SIZE: usize = (MAX_BPS / BUCKET_BPS) as usize + 1;
const BUCKET_WIDTH: f64 = 0.001;
const MAX_RATIO: f64 = 43.0;
const TREE_SIZE: usize = (MAX_RATIO / BUCKET_WIDTH) as usize + 1;
impl Default for ExpandingPercentiles {
fn default() -> Self {
@@ -43,8 +41,9 @@ impl ExpandingPercentiles {
/// Convert f32 ratio to 0-indexed bucket.
#[inline]
fn to_bucket(value: f32) -> usize {
let bps = (value as f64 * 10000.0).round() as i32;
(bps / BUCKET_BPS).clamp(0, TREE_SIZE as i32 - 1) as usize
(value as f64 / BUCKET_WIDTH)
.round()
.clamp(0.0, (TREE_SIZE - 1) as f64) as usize
}
/// Bulk-load values in O(n + N) instead of O(n log N).
@@ -73,10 +72,10 @@ impl ExpandingPercentiles {
/// Compute 8 percentiles in one call via kth. O(8 × log N) but with
/// shared tree traversal across all 8 targets for better cache locality.
/// Quantiles q must be sorted ascending in (0, 1). Output is in BPS.
pub fn quantiles(&self, qs: &[f64; 8], out: &mut [u32; 8]) {
/// Quantiles q must be sorted ascending in (0, 1). Output values are ratios.
pub fn quantiles(&self, qs: &[f64; 8], out: &mut [f64; 8]) {
if self.count == 0 {
out.iter_mut().for_each(|o| *o = 0);
out.fill(0.0);
return;
}
let mut targets = [0u32; 8];
@@ -87,7 +86,7 @@ impl ExpandingPercentiles {
let mut buckets = [0usize; 8];
self.tree.kth(&targets, &|n: &u32| *n, &mut buckets);
for (i, bucket) in buckets.iter().enumerate() {
out[i] = *bucket as u32 * BUCKET_BPS as u32;
out[i] = *bucket as f64 * BUCKET_WIDTH;
}
}
}
@@ -96,8 +95,8 @@ impl ExpandingPercentiles {
mod tests {
use super::*;
fn quantile(ep: &ExpandingPercentiles, q: f64) -> u32 {
let mut out = [0u32; 8];
fn quantile(ep: &ExpandingPercentiles, q: f64) -> f64 {
let mut out = [0.0; 8];
ep.quantiles(&[q, q, q, q, q, q, q, q], &mut out);
out[0]
}
@@ -111,20 +110,20 @@ mod tests {
assert_eq!(ep.count(), 1000);
let median = quantile(&ep, 0.5);
assert!((median as i32 - 5000).abs() < 100, "median was {median}");
assert!((median - 0.5).abs() < 0.01, "median was {median}");
let p99 = quantile(&ep, 0.99);
assert!((p99 as i32 - 9900).abs() < 100, "p99 was {p99}");
assert!((p99 - 0.99).abs() < 0.01, "p99 was {p99}");
let p01 = quantile(&ep, 0.01);
assert!((p01 as i32 - 100).abs() < 100, "p01 was {p01}");
assert!((p01 - 0.01).abs() < 0.01, "p01 was {p01}");
}
#[test]
fn empty() {
let ep = ExpandingPercentiles::default();
assert_eq!(ep.count(), 0);
assert_eq!(quantile(&ep, 0.5), 0);
assert_eq!(quantile(&ep, 0.5), 0.0);
}
#[test]
@@ -132,7 +131,7 @@ mod tests {
let mut ep = ExpandingPercentiles::default();
ep.add(0.42);
let v = quantile(&ep, 0.5);
assert!((v as i32 - 4200).abs() <= BUCKET_BPS, "got {v}");
assert!((v - 0.42).abs() <= BUCKET_WIDTH, "got {v}");
}
#[test]
@@ -144,6 +143,6 @@ mod tests {
assert_eq!(ep.count(), 100);
ep.reset();
assert_eq!(ep.count(), 0);
assert_eq!(quantile(&ep, 0.5), 0);
assert_eq!(quantile(&ep, 0.5), 0.0);
}
}
@@ -2,7 +2,7 @@ use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
pub struct Percent<A, B = A, C = B> {
pub bps: A,
pub raw: A,
pub ratio: B,
pub percent: C,
}
@@ -2,10 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Height;
use schemars::JsonSchema;
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw,
StorageMode, VecIndex, VecValue, Version, WritableVec,
};
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode, Version};
use crate::{
indexes,
@@ -52,82 +49,19 @@ where
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute<A>(
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
source: &impl ReadableVec<A, T>,
first_indexes: &impl ReadableVec<Height, A>,
count_indexes: &impl ReadableVec<Height, brk_types::StoredU64>,
windows: &WindowStarts<'_>,
exit: &Exit,
skip_count: usize,
) -> Result<()>
where
T: From<f64> + Default + Copy + Ord,
f64: From<T>,
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
let combined_version = source.version() + first_indexes.version() + count_indexes.version();
let mut index = max_from;
index = {
self.sum
.validate_computed_version_or_reset(combined_version)?;
index.min(Height::from(self.sum.len()))
};
index = {
self.cumulative
.height
.validate_computed_version_or_reset(combined_version)?;
index.min(Height::from(self.cumulative.height.len()))
};
let start = index.to_usize();
self.sum.truncate_if_needed_at(start)?;
self.cumulative.height.truncate_if_needed_at(start)?;
let mut cumulative_val = index.decremented().map_or(T::from(0_usize), |idx| {
self.cumulative
.height
.collect_one_at(idx.to_usize())
.unwrap_or(T::from(0_usize))
});
let fi_len = first_indexes.len();
let first_indexes_batch: Vec<A> = first_indexes.collect_range_at(start, fi_len);
let count_indexes_batch: Vec<brk_types::StoredU64> =
count_indexes.collect_range_at(start, fi_len);
first_indexes_batch
.into_iter()
.zip(count_indexes_batch)
.try_for_each(|(first_index, count_index)| -> Result<()> {
let count = u64::from(count_index) as usize;
let effective_count = count.saturating_sub(skip_count);
let effective_first_index = first_index + skip_count.min(count);
let efi = effective_first_index.to_usize();
let sum_val = source.fold_range_at(
efi,
efi + effective_count,
T::from(0_usize),
|acc, val| acc + val,
);
self.sum.push(sum_val);
cumulative_val += sum_val;
self.cumulative.height.push(cumulative_val);
Ok(())
})?;
let _lock = exit.lock();
self.sum.write()?;
self.cumulative.height.write()?;
drop(_lock);
self.cumulative
.height
.compute_cumulative(max_from, &self.sum, exit)?;
self.rolling.compute(max_from, windows, &self.sum, exit)?;
Ok(())
}
@@ -8,7 +8,7 @@ use vecdb::{Rw, StorageMode};
use crate::{
indexes,
internal::{
BpsType, LazyRollingDeltasFromHeight, NumericValue, PerBlock, WindowStartVec, Windows,
FixedRatio, LazyRollingDeltasFromHeight, NumericValue, PerBlock, WindowStartVec, Windows,
},
};
@@ -17,7 +17,7 @@ pub struct PerBlockWithDeltas<S, C, B, M: StorageMode = Rw>
where
S: NumericValue + JsonSchema + Into<f64>,
C: NumericValue + JsonSchema + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
#[deref]
#[deref_mut]
@@ -29,7 +29,7 @@ impl<S, C, B> PerBlockWithDeltas<S, C, B>
where
S: NumericValue + JsonSchema + Into<f64>,
C: NumericValue + JsonSchema + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
pub(crate) fn forced_import(
db: &vecdb::Database,
@@ -6,7 +6,7 @@ use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows},
internal::{FixedRatio, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows},
};
use super::{FiatPerBlockCumulativeWithSums, FiatType};
@@ -16,7 +16,7 @@ pub struct FiatPerBlockCumulativeWithSumsAndDeltas<C, CS, B, M: StorageMode = Rw
where
C: FiatType + Into<f64>,
CS: FiatType + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
#[deref]
#[deref_mut]
@@ -29,7 +29,7 @@ impl<C, CS, B> FiatPerBlockCumulativeWithSumsAndDeltas<C, CS, B>
where
C: FiatType + Into<f64>,
CS: FiatType + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
pub(crate) fn forced_import(
db: &Database,
@@ -7,7 +7,7 @@ use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows},
internal::{FixedRatio, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows},
};
use super::{FiatPerBlock, FiatType};
@@ -17,7 +17,7 @@ pub struct FiatPerBlockWithDeltas<C, CS, B, M: StorageMode = Rw>
where
C: FiatType + Into<f64>,
CS: FiatType + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
#[deref]
#[deref_mut]
@@ -30,7 +30,7 @@ impl<C, CS, B> FiatPerBlockWithDeltas<C, CS, B>
where
C: FiatType + JsonSchema + Into<f64>,
CS: FiatType + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
pub(crate) fn forced_import(
db: &Database,
@@ -8,44 +8,39 @@ use vecdb::{
use crate::{
indexes,
internal::{BpsType, Percent, algo::ComputeDrawdown},
internal::{FixedRatio, Percent, algo::ComputeDrawdown},
};
use crate::internal::{LazyPerBlock, PerBlock};
/// Basis-point storage with both ratio and percentage float views.
///
/// Stores integer basis points on disk (Pco-compressed),
/// exposes two lazy StoredF32 views:
/// - `ratio`: bps / 10000 (e.g., 4523 bps -> 0.4523)
/// - `percent`: bps / 100 (e.g., 4523 bps -> 45.23%)
/// Fixed-point storage with lazy ratio and percentage float views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct PercentPerBlock<B: BpsType, M: StorageMode = Rw>(
pub struct PercentPerBlock<B: FixedRatio, M: StorageMode = Rw>(
pub Percent<PerBlock<B, M>, LazyPerBlock<StoredF32, B>>,
);
impl<B: BpsType> PercentPerBlock<B> {
impl<B: FixedRatio> PercentPerBlock<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let bps = PerBlock::forced_import(db, &format!("{name}_bps"), version, indexes)?;
let bps_clone = bps.height.read_only_boxed_clone();
let raw = PerBlock::forced_import(db, &format!("{name}_{}", B::SUFFIX), version, indexes)?;
let raw_clone = raw.height.read_only_boxed_clone();
let ratio = LazyPerBlock::from_computed::<B::ToRatio>(
&format!("{name}_ratio"),
version,
bps_clone.clone(),
&bps,
raw_clone.clone(),
&raw,
);
let percent = LazyPerBlock::from_computed::<B::ToPercent>(name, version, bps_clone, &bps);
let percent = LazyPerBlock::from_computed::<B::ToPercent>(name, version, raw_clone, &raw);
Ok(Self(Percent {
bps,
raw,
ratio,
percent,
}))
@@ -63,7 +58,7 @@ impl<B: BpsType> PercentPerBlock<B> {
S2T: VecValue,
F: BinaryTransform<S1T, S2T, B>,
{
self.bps
self.raw
.compute_binary::<S1T, S2T, F>(max_from, source1, source2, exit)
}
@@ -80,7 +75,7 @@ impl<B: BpsType> PercentPerBlock<B> {
f64: From<C> + From<A>,
vecdb::EagerVec<vecdb::PcoVec<Height, B>>: ComputeDrawdown<Height>,
{
self.bps
self.raw
.height
.compute_drawdown(max_from, current, ath, exit)
}
@@ -5,25 +5,25 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, StoredU64, Version};
use brk_types::{Height, PartsPerMillion32, StoredU64, Version};
use vecdb::{BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, VecValue};
use crate::{
indexes,
internal::{
BpsType, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, RatioU64Bp16,
FixedRatio, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, RatioU64,
},
};
#[derive(Traversable)]
pub struct PercentCumulativeRolling<B: BpsType, M: StorageMode = Rw> {
pub struct PercentCumulativeRolling<B: FixedRatio, M: StorageMode = Rw> {
#[traversable(flatten)]
pub cumulative: PercentPerBlock<B, M>,
#[traversable(flatten)]
pub rolling: PercentRollingWindows<B, M>,
}
impl<B: BpsType> PercentCumulativeRolling<B> {
impl<B: FixedRatio> PercentCumulativeRolling<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -73,7 +73,7 @@ impl<B: BpsType> PercentCumulativeRolling<B> {
}
}
impl PercentCumulativeRolling<BasisPoints16> {
impl PercentCumulativeRolling<PartsPerMillion32> {
/// Derive a percent from two `PerBlockCumulativeRolling<StoredU64>`
/// sources (numerator and denominator). Both sources must already have
/// their cumulative and rolling sums computed.
@@ -85,7 +85,15 @@ impl PercentCumulativeRolling<BasisPoints16> {
starting_height: Height,
exit: &Exit,
) -> Result<()> {
self.compute_binary::<StoredU64, StoredU64, RatioU64Bp16, _, _, _, _>(
self.compute_binary::<
StoredU64,
StoredU64,
RatioU64<PartsPerMillion32>,
_,
_,
_,
_,
>(
starting_height,
&numerator.cumulative.height,
&denominator.cumulative.height,
@@ -3,39 +3,39 @@ use brk_types::{StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{ReadableCloneableVec, UnaryTransform};
use crate::internal::{BpsType, LazyPerBlock, Percent, PercentPerBlock};
use crate::internal::{FixedRatio, LazyPerBlock, Percent, PercentPerBlock};
/// Fully lazy variant of `PercentPerBlock` — no stored vecs.
///
/// BPS values are lazily derived from a source `PercentPerBlock` via a unary transform,
/// and ratio/percent float views are chained from the lazy BPS.
/// Raw values are lazily derived from a source `PercentPerBlock` via a unary transform,
/// and ratio/percent float views are chained from them.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyPercentPerBlock<B: BpsType>(
pub struct LazyPercentPerBlock<B: FixedRatio>(
pub Percent<LazyPerBlock<B, B>, LazyPerBlock<StoredF32, B>>,
);
impl<B: BpsType> LazyPercentPerBlock<B> {
/// Create from a stored `PercentPerBlock` source via a BPS-to-BPS unary transform.
impl<B: FixedRatio> LazyPercentPerBlock<B> {
/// Create from a stored `PercentPerBlock` source via a same-unit unary transform.
pub(crate) fn from_percent<F: UnaryTransform<B, B>>(
name: &str,
version: Version,
source: &PercentPerBlock<B>,
) -> Self {
let bps = LazyPerBlock::from_computed::<F>(
&format!("{name}_bps"),
let raw = LazyPerBlock::from_computed::<F>(
&format!("{name}_{}", B::SUFFIX),
version,
source.bps.height.read_only_boxed_clone(),
&source.bps,
source.raw.height.read_only_boxed_clone(),
&source.raw,
);
let ratio =
LazyPerBlock::from_lazy::<B::ToRatio, B>(&format!("{name}_ratio"), version, &bps);
LazyPerBlock::from_lazy::<B::ToRatio, B>(&format!("{name}_ratio"), version, &raw);
let percent = LazyPerBlock::from_lazy::<B::ToPercent, B>(name, version, &bps);
let percent = LazyPerBlock::from_lazy::<B::ToPercent, B>(name, version, &raw);
Self(Percent {
bps,
raw,
ratio,
percent,
})
@@ -3,25 +3,25 @@ use brk_types::Version;
use vecdb::UnaryTransform;
use crate::internal::{
BpsType, LazyPercentPerBlock, LazyPercentRollingWindows, PercentCumulativeRolling,
FixedRatio, LazyPercentPerBlock, LazyPercentRollingWindows, PercentCumulativeRolling,
};
/// Fully lazy variant of `PercentCumulativeRolling` — no stored vecs.
///
/// Mirrors the flat shape of `PercentCumulativeRolling`: cumulative and
/// rolling window fields are both flattened to the same tree level, so
/// consumers see `{ bps, percent, ratio, _24h, _1w, _1m, _1y }`.
/// consumers see `{ raw, percent, ratio, _24h, _1w, _1m, _1y }`.
#[derive(Clone, Traversable)]
pub struct LazyPercentCumulativeRolling<B: BpsType> {
pub struct LazyPercentCumulativeRolling<B: FixedRatio> {
#[traversable(flatten)]
pub cumulative: LazyPercentPerBlock<B>,
#[traversable(flatten)]
pub rolling: LazyPercentRollingWindows<B>,
}
impl<B: BpsType> LazyPercentCumulativeRolling<B> {
impl<B: FixedRatio> LazyPercentCumulativeRolling<B> {
/// Derive from a stored `PercentCumulativeRolling` source via a
/// BPS-to-BPS unary transform applied to both cumulative and rolling.
/// same-unit unary transform applied to both cumulative and rolling.
pub(crate) fn from_source<F: UnaryTransform<B, B>>(
name: &str,
version: Version,
@@ -3,20 +3,20 @@ use brk_types::Version;
use derive_more::{Deref, DerefMut};
use vecdb::UnaryTransform;
use crate::internal::{BpsType, PercentRollingWindows, Windows};
use crate::internal::{FixedRatio, PercentRollingWindows, Windows};
use super::LazyPercentPerBlock;
/// Fully lazy rolling percent windows — 4 windows (24h, 1w, 1m, 1y),
/// each with lazy BPS + lazy ratio/percent float views.
/// each with lazy raw + lazy ratio/percent float views.
///
/// No stored vecs. All values derived from a source `PercentRollingWindows`.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyPercentRollingWindows<B: BpsType>(pub Windows<LazyPercentPerBlock<B>>);
pub struct LazyPercentRollingWindows<B: FixedRatio>(pub Windows<LazyPercentPerBlock<B>>);
impl<B: BpsType> LazyPercentRollingWindows<B> {
/// Create from a stored `PercentRollingWindows` source via a BPS-to-BPS unary transform.
impl<B: FixedRatio> LazyPercentRollingWindows<B> {
/// Create from a stored source via a same-unit unary transform.
pub(crate) fn from_rolling<F: UnaryTransform<B, B>>(
name: &str,
version: Version,
@@ -5,37 +5,37 @@ use vecdb::{
Database, EagerVec, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode,
};
use crate::internal::{BpsType, Percent};
use crate::internal::{FixedRatio, Percent};
/// Lightweight percent container: BPS height vec + lazy ratio + lazy percent.
/// Lightweight percent container: fixed-point height vec + lazy ratio + lazy percent.
/// No resolutions, no rolling stats.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
#[allow(clippy::type_complexity)]
pub struct PercentVec<B: BpsType, M: StorageMode = Rw>(
pub struct PercentVec<B: FixedRatio, M: StorageMode = Rw>(
pub Percent<M::Stored<EagerVec<PcoVec<Height, B>>>, LazyVecFrom1<Height, StoredF32, Height, B>>,
);
impl<B: BpsType> PercentVec<B> {
impl<B: FixedRatio> PercentVec<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
) -> brk_error::Result<Self> {
let bps: EagerVec<PcoVec<Height, B>> =
EagerVec::forced_import(db, &format!("{name}_bps"), version)?;
let bps_clone = bps.read_only_boxed_clone();
let raw: EagerVec<PcoVec<Height, B>> =
EagerVec::forced_import(db, &format!("{name}_{}", B::SUFFIX), version)?;
let raw_clone = raw.read_only_boxed_clone();
let ratio = LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{name}_ratio"),
version,
bps_clone.clone(),
raw_clone.clone(),
);
let percent = LazyVecFrom1::transformed::<B::ToPercent>(name, version, bps_clone);
let percent = LazyVecFrom1::transformed::<B::ToPercent>(name, version, raw_clone);
Ok(Self(Percent {
bps,
raw,
ratio,
percent,
}))
@@ -6,18 +6,18 @@ use vecdb::{BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, VecVa
use crate::{
indexes,
internal::{BpsType, PercentPerBlock, Windows},
internal::{FixedRatio, PercentPerBlock, Windows},
};
/// 4 rolling window vecs (24h, 1w, 1m, 1y), each storing basis points
/// 4 rolling window vecs (24h, 1w, 1m, 1y), each storing fixed-point values
/// with lazy ratio and percent float views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct PercentRollingWindows<B: BpsType, M: StorageMode = Rw>(
pub struct PercentRollingWindows<B: FixedRatio, M: StorageMode = Rw>(
pub Windows<PercentPerBlock<B, M>>,
);
impl<B: BpsType> PercentRollingWindows<B> {
impl<B: FixedRatio> PercentRollingWindows<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -1,23 +1,23 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, Cents, Height, StoredF32, Version};
use brk_types::{Cents, Height, StoredF32, Version};
use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, LazyPerBlock, PerBlock},
internal::{FixedRatio, LazyPerBlock, PerBlock},
};
#[derive(Traversable)]
pub struct RatioPerBlock<B: BpsType = BasisPoints32, M: StorageMode = Rw> {
pub bps: PerBlock<B, M>,
pub ratio: LazyPerBlock<StoredF32, B>,
pub struct RatioPerBlock<R: FixedRatio, M: StorageMode = Rw> {
pub raw: PerBlock<R, M>,
pub ratio: LazyPerBlock<StoredF32, R>,
}
const VERSION: Version = Version::TWO;
const VERSION: Version = Version::new(3);
impl<B: BpsType> RatioPerBlock<B> {
impl<R: FixedRatio> RatioPerBlock<R> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -35,20 +35,18 @@ impl<B: BpsType> RatioPerBlock<B> {
) -> Result<Self> {
let v = version + VERSION;
let bps = PerBlock::forced_import(db, &format!("{name}_bps"), v, indexes)?;
let raw = PerBlock::forced_import(db, &format!("{name}_{}", R::SUFFIX), v, indexes)?;
let ratio = LazyPerBlock::from_computed::<B::ToRatio>(
let ratio = LazyPerBlock::from_computed::<R::ToRatio>(
name,
v,
bps.height.read_only_boxed_clone(),
&bps,
raw.height.read_only_boxed_clone(),
&raw,
);
Ok(Self { bps, ratio })
Ok(Self { raw, ratio })
}
}
impl RatioPerBlock<BasisPoints32> {
pub(crate) fn compute_ratio(
&mut self,
starting_lengths: &Lengths,
@@ -56,15 +54,15 @@ impl RatioPerBlock<BasisPoints32> {
series_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.bps.height.compute_transform2(
self.raw.height.compute_transform2(
starting_lengths.height,
close_price,
series_price,
|(i, close, price, ..)| {
if price == Cents::ZERO {
(i, BasisPoints32::from(1.0))
(i, R::from(1.0))
} else {
(i, BasisPoints32::from(f64::from(close) / f64::from(price)))
(i, R::from(f64::from(close) / f64::from(price)))
}
},
exit,
@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, Cents, Height, StoredF32, Version};
use brk_types::{Cents, Height, PartsPerMillion32, StoredF32, Version};
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
WritableVec,
@@ -9,7 +9,7 @@ use vecdb::{
use crate::{
indexes,
internal::{Price, PriceTimesRatioBp32Cents, algo::ExpandingPercentiles},
internal::{Price, PriceTimesRatio, algo::ExpandingPercentiles},
};
use super::{super::PerBlock, RatioPerBlock};
@@ -17,7 +17,7 @@ use super::{super::PerBlock, RatioPerBlock};
#[derive(Traversable)]
pub struct RatioBand<M: StorageMode = Rw> {
#[traversable(flatten)]
pub ratio: RatioPerBlock<BasisPoints32, M>,
pub ratio: RatioPerBlock<PartsPerMillion32, M>,
pub price: Price<PerBlock<Cents, M>>,
}
@@ -119,18 +119,18 @@ impl RatioPerBlockPercentiles {
}
let new_ratios = ratio_source.collect_range_at(start, ratio_len);
let mut pct_vecs: [&mut EagerVec<PcoVec<Height, BasisPoints32>>; 8] = [
&mut self.pct0_5.ratio.bps.height,
&mut self.pct1.ratio.bps.height,
&mut self.pct2.ratio.bps.height,
&mut self.pct5.ratio.bps.height,
&mut self.pct95.ratio.bps.height,
&mut self.pct98.ratio.bps.height,
&mut self.pct99.ratio.bps.height,
&mut self.pct99_5.ratio.bps.height,
let mut pct_vecs: [&mut EagerVec<PcoVec<Height, PartsPerMillion32>>; 8] = [
&mut self.pct0_5.ratio.raw.height,
&mut self.pct1.ratio.raw.height,
&mut self.pct2.ratio.raw.height,
&mut self.pct5.ratio.raw.height,
&mut self.pct95.ratio.raw.height,
&mut self.pct98.ratio.raw.height,
&mut self.pct99.ratio.raw.height,
&mut self.pct99_5.ratio.raw.height,
];
const PCTS: [f64; 8] = [0.005, 0.01, 0.02, 0.05, 0.95, 0.98, 0.99, 0.995];
let mut out = [0u32; 8];
let mut out = [0.0; 8];
for vec in pct_vecs.iter_mut() {
vec.truncate_if_needed_at(start)?;
@@ -142,7 +142,7 @@ impl RatioPerBlockPercentiles {
}
self.expanding_pct.quantiles(&PCTS, &mut out);
for (vec, &val) in pct_vecs.iter_mut().zip(out.iter()) {
vec.push(BasisPoints32::from(val));
vec.push(PartsPerMillion32::from(val));
}
}
}
@@ -159,10 +159,14 @@ impl RatioPerBlockPercentiles {
self.$band
.price
.cents
.compute_binary::<Cents, BasisPoints32, PriceTimesRatioBp32Cents>(
.compute_binary::<
Cents,
PartsPerMillion32,
PriceTimesRatio<PartsPerMillion32>,
>(
starting_lengths.height,
series_price,
&self.$band.ratio.bps.height,
&self.$band.ratio.raw.height,
exit,
)?;
};
@@ -182,16 +186,16 @@ impl RatioPerBlockPercentiles {
fn mut_pct_vecs(
&mut self,
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, BasisPoints32>>> {
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, PartsPerMillion32>>> {
[
&mut self.pct0_5.ratio.bps.height,
&mut self.pct1.ratio.bps.height,
&mut self.pct2.ratio.bps.height,
&mut self.pct5.ratio.bps.height,
&mut self.pct95.ratio.bps.height,
&mut self.pct98.ratio.bps.height,
&mut self.pct99.ratio.bps.height,
&mut self.pct99_5.ratio.bps.height,
&mut self.pct0_5.ratio.raw.height,
&mut self.pct1.ratio.raw.height,
&mut self.pct2.ratio.raw.height,
&mut self.pct5.ratio.raw.height,
&mut self.pct95.ratio.raw.height,
&mut self.pct98.ratio.raw.height,
&mut self.pct99.ratio.raw.height,
&mut self.pct99_5.ratio.raw.height,
]
.into_iter()
}
@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, Cents, Dollars, Height, SatsFract, StoredF32, Version};
use brk_types::{Cents, Dollars, Height, PartsPerMillion64, SatsFract, StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
@@ -15,8 +15,8 @@ pub struct PriceWithRatioPerBlock<M: StorageMode = Rw> {
pub usd: LazyPerBlock<Dollars, Cents>,
pub cents: PerBlock<Cents, M>,
pub sats: LazyPerBlock<SatsFract, Dollars>,
pub bps: PerBlock<BasisPoints32, M>,
pub ratio: LazyPerBlock<StoredF32, BasisPoints32>,
pub raw: PerBlock<PartsPerMillion64, M>,
pub ratio: LazyPerBlock<StoredF32, PartsPerMillion64>,
}
impl PriceWithRatioPerBlock {
@@ -32,7 +32,7 @@ impl PriceWithRatioPerBlock {
usd: price.usd,
cents: price.cents,
sats: price.sats,
bps: ratio.bps,
raw: ratio.raw,
ratio: ratio.ratio,
})
}
@@ -44,15 +44,18 @@ impl PriceWithRatioPerBlock {
close_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.bps.height.compute_transform2(
self.raw.height.compute_transform2(
starting_lengths.height,
close_price,
&self.cents.height,
|(i, close, price, ..)| {
if price == Cents::ZERO {
(i, BasisPoints32::from(1.0))
(i, PartsPerMillion64::from(1.0))
} else {
(i, BasisPoints32::from(f64::from(close) / f64::from(price)))
(
i,
PartsPerMillion64::from(f64::from(close) / f64::from(price)),
)
}
},
exit,
@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, Height, StoredF32, Version};
use brk_types::{Height, PartsPerMillion32, StoredF32, Version};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, indexes};
@@ -10,12 +10,12 @@ use super::RatioPerBlock;
#[derive(Traversable)]
pub struct RatioSma<M: StorageMode = Rw> {
pub all: RatioPerBlock<BasisPoints32, M>,
pub _1w: RatioPerBlock<BasisPoints32, M>,
pub _1m: RatioPerBlock<BasisPoints32, M>,
pub _1y: RatioPerBlock<BasisPoints32, M>,
pub _2y: RatioPerBlock<BasisPoints32, M>,
pub _4y: RatioPerBlock<BasisPoints32, M>,
pub all: RatioPerBlock<PartsPerMillion32, M>,
pub _1w: RatioPerBlock<PartsPerMillion32, M>,
pub _1m: RatioPerBlock<PartsPerMillion32, M>,
pub _1y: RatioPerBlock<PartsPerMillion32, M>,
pub _2y: RatioPerBlock<PartsPerMillion32, M>,
pub _4y: RatioPerBlock<PartsPerMillion32, M>,
}
const VERSION: Version = Version::new(4);
@@ -58,7 +58,7 @@ impl RatioSma {
ratio_source: &impl ReadableVec<Height, StoredF32>,
) -> Result<()> {
// Expanding SMA (all history)
self.all.bps.height.compute_sma_(
self.all.raw.height.compute_sma_(
starting_lengths.height,
ratio_source,
usize::MAX,
@@ -74,7 +74,7 @@ impl RatioSma {
(&mut self._2y, &blocks.lookback._2y),
(&mut self._4y, &blocks.lookback._4y),
] {
sma.bps.height.compute_rolling_average(
sma.raw.height.compute_rolling_average(
starting_lengths.height,
lookback,
ratio_source,
@@ -6,16 +6,17 @@ use vecdb::{BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, VecVa
use crate::{
indexes,
internal::{BpsType, RatioPerBlock, Windows},
internal::{FixedRatio, RatioPerBlock, Windows},
};
/// 4 rolling window vecs (24h, 1w, 1m, 1y), each storing basis points
/// with a lazy ratio float view.
/// Four rolling-window ratios with fixed-point storage and lazy float views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RatioRollingWindows<B: BpsType, M: StorageMode = Rw>(pub Windows<RatioPerBlock<B, M>>);
pub struct RatioRollingWindows<B: FixedRatio, M: StorageMode = Rw>(
pub Windows<RatioPerBlock<B, M>>,
);
impl<B: BpsType> RatioRollingWindows<B> {
impl<B: FixedRatio> RatioRollingWindows<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -49,7 +50,7 @@ impl<B: BpsType> RatioRollingWindows<B> {
.zip(sources2)
{
target
.bps
.raw
.compute_binary::<S1T, S2T, F>(max_from, s1, s2, exit)?;
}
Ok(())
@@ -10,7 +10,7 @@ use vecdb::{
use crate::{
indexes,
internal::{
AmountType, BpsType, DerivedResolutions, FiatType, LazyPerBlock, NumericValue, Percent,
AmountType, DerivedResolutions, FiatType, FixedRatio, LazyPerBlock, NumericValue, Percent,
Resolutions, WindowStartVec, Windows,
},
};
@@ -19,7 +19,7 @@ use crate::{
///
/// Used as building block for both change and rate deltas.
/// - Change: `LazyDeltaFromHeight<S, C, DeltaChange>`
/// - Rate BPS: `LazyDeltaFromHeight<S, B, DeltaRate>`
/// - Rate: `LazyDeltaFromHeight<S, B, DeltaRate>`
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct LazyDeltaFromHeight<S, T, Op: 'static>
@@ -32,9 +32,9 @@ where
pub resolutions: Box<Resolutions<T>>,
}
/// Single-slot lazy delta percent: BPS delta + lazy ratio + lazy percent views.
/// Single-slot lazy delta percent: fixed-point delta + lazy ratio + lazy percent views.
///
/// Mirrors `PercentPerBlock<B>` but with lazy delta for the BPS source.
/// Mirrors `PercentPerBlock<B>` but with a lazy delta source.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyDeltaPercentFromHeight<S, B>(
@@ -42,7 +42,7 @@ pub struct LazyDeltaPercentFromHeight<S, B>(
)
where
S: VecValue,
B: BpsType;
B: FixedRatio;
/// Lazy rolling deltas for all 4 window durations (24h, 1w, 1m, 1y).
///
@@ -55,7 +55,7 @@ pub struct LazyRollingDeltasFromHeight<S, C, B>
where
S: VecValue,
C: NumericValue + JsonSchema,
B: BpsType,
B: FixedRatio,
{
pub absolute: Windows<LazyDeltaFromHeight<S, C, DeltaChange>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
@@ -65,7 +65,7 @@ impl<S, C, B> LazyRollingDeltasFromHeight<S, C, B>
where
S: VecValue + Into<f64>,
C: NumericValue + JsonSchema + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
pub fn new(
name: &str,
@@ -99,54 +99,52 @@ where
resolutions: Box::new(change_resolutions),
};
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_bps_name = format!("{full_name}_rate_bps");
// Rate: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_raw_name = format!("{full_name}_rate_{}", B::SUFFIX);
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&rate_bps_name,
&rate_raw_name,
version,
src.clone(),
starts_version,
move || cached.cached(),
);
let rate_resolutions =
Resolutions::forced_import(&rate_bps_name, rate_vec.clone(), version, indexes);
let bps = LazyDeltaFromHeight {
Resolutions::forced_import(&rate_raw_name, rate_vec.clone(), version, indexes);
let raw = LazyDeltaFromHeight {
height: rate_vec,
resolutions: Box::new(rate_resolutions),
};
// Ratio: bps / 10000
let rate_ratio_name = format!("{full_name}_rate_ratio");
let ratio = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToRatio>(
&rate_ratio_name,
version,
bps.height.read_only_boxed_clone(),
raw.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&rate_ratio_name,
version,
&bps.resolutions,
&raw.resolutions,
)),
};
// Percent: bps / 100
let rate_name = format!("{full_name}_rate");
let percent = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToPercent>(
&rate_name,
version,
bps.height.read_only_boxed_clone(),
raw.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&rate_name,
version,
&bps.resolutions,
&raw.resolutions,
)),
};
let rate = LazyDeltaPercentFromHeight(Percent {
bps,
raw,
ratio,
percent,
});
@@ -186,7 +184,7 @@ pub struct LazyRollingDeltasAmountFromHeight<S, C, B>
where
S: VecValue,
C: AmountType,
B: BpsType,
B: FixedRatio,
{
pub absolute: Windows<LazyDeltaAmountFromHeight<S, C>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
@@ -196,7 +194,7 @@ impl<S, C, B> LazyRollingDeltasAmountFromHeight<S, C, B>
where
S: VecValue + Into<f64>,
C: AmountType + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
pub fn new(
name: &str,
@@ -247,18 +245,18 @@ where
let absolute = LazyDeltaAmountFromHeight { btc, sats };
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_bps_name = format!("{full_name}_rate_bps");
// Rate: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_raw_name = format!("{full_name}_rate_{}", B::SUFFIX);
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&rate_bps_name,
&rate_raw_name,
version,
src.clone(),
starts_version,
move || cached.cached(),
);
let rate_resolutions =
Resolutions::forced_import(&rate_bps_name, rate_vec.clone(), version, indexes);
let bps = LazyDeltaFromHeight {
Resolutions::forced_import(&rate_raw_name, rate_vec.clone(), version, indexes);
let raw = LazyDeltaFromHeight {
height: rate_vec,
resolutions: Box::new(rate_resolutions),
};
@@ -268,12 +266,12 @@ where
height: LazyVecFrom1::transformed::<B::ToRatio>(
&rate_ratio_name,
version,
bps.height.read_only_boxed_clone(),
raw.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&rate_ratio_name,
version,
&bps.resolutions,
&raw.resolutions,
)),
};
@@ -282,17 +280,17 @@ where
height: LazyVecFrom1::transformed::<B::ToPercent>(
&rate_name,
version,
bps.height.read_only_boxed_clone(),
raw.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&rate_name,
version,
&bps.resolutions,
&raw.resolutions,
)),
};
let rate = LazyDeltaPercentFromHeight(Percent {
bps,
raw,
ratio,
percent,
});
@@ -331,7 +329,7 @@ pub struct LazyRollingDeltasFiatFromHeight<S, C, B>
where
S: VecValue,
C: FiatType,
B: BpsType,
B: FixedRatio,
{
pub absolute: Windows<LazyDeltaFiatFromHeight<S, C>>,
pub rate: Windows<LazyDeltaPercentFromHeight<S, B>>,
@@ -341,7 +339,7 @@ impl<S, C, B> LazyRollingDeltasFiatFromHeight<S, C, B>
where
S: VecValue + Into<f64>,
C: FiatType + From<f64>,
B: BpsType + From<f64>,
B: FixedRatio + From<f64>,
{
pub fn new(
name: &str,
@@ -392,18 +390,18 @@ where
let absolute = LazyDeltaFiatFromHeight { usd, cents };
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_bps_name = format!("{full_name}_rate_bps");
// Rate: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_raw_name = format!("{full_name}_rate_{}", B::SUFFIX);
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&rate_bps_name,
&rate_raw_name,
version,
src.clone(),
starts_version,
move || cached.cached(),
);
let rate_resolutions =
Resolutions::forced_import(&rate_bps_name, rate_vec.clone(), version, indexes);
let bps = LazyDeltaFromHeight {
Resolutions::forced_import(&rate_raw_name, rate_vec.clone(), version, indexes);
let raw = LazyDeltaFromHeight {
height: rate_vec,
resolutions: Box::new(rate_resolutions),
};
@@ -413,12 +411,12 @@ where
height: LazyVecFrom1::transformed::<B::ToRatio>(
&rate_ratio_name,
version,
bps.height.read_only_boxed_clone(),
raw.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&rate_ratio_name,
version,
&bps.resolutions,
&raw.resolutions,
)),
};
@@ -427,17 +425,17 @@ where
height: LazyVecFrom1::transformed::<B::ToPercent>(
&rate_name,
version,
bps.height.read_only_boxed_clone(),
raw.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&rate_name,
version,
&bps.resolutions,
&raw.resolutions,
)),
};
let rate = LazyDeltaPercentFromHeight(Percent {
bps,
raw,
ratio,
percent,
});
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Sats, SatsSigned, Version};
use brk_types::{PartsPerMillionSigned64, Sats, SatsSigned, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
@@ -15,7 +15,7 @@ pub struct ValuePerBlockWithDeltas<M: StorageMode = Rw> {
#[deref_mut]
#[traversable(flatten)]
pub inner: ValuePerBlock<M>,
pub delta: LazyRollingDeltasAmountFromHeight<Sats, SatsSigned, BasisPointsSigned32>,
pub delta: LazyRollingDeltasAmountFromHeight<Sats, SatsSigned, PartsPerMillionSigned64>,
}
impl ValuePerBlockWithDeltas {
@@ -30,7 +30,7 @@ impl ValuePerBlockWithDeltas {
let delta = LazyRollingDeltasAmountFromHeight::new(
&format!("{name}_delta"),
version + Version::ONE,
version + Version::TWO,
&inner.sats.height,
cached_starts,
indexes,
@@ -1,60 +0,0 @@
use brk_error::Result;
use brk_indexer::{Indexer, Lengths};
use brk_traversable::Traversable;
use brk_types::TxIndex;
use schemars::JsonSchema;
use vecdb::{Database, Exit, LazyVecFrom2, ReadableVec, Rw, StorageMode, Version};
use crate::{
indexes,
internal::{ComputedVecValue, NumericValue, TxDerivedDistribution},
};
#[derive(Traversable)]
pub struct LazyPerTxDistribution<T, S1, S2, M: StorageMode = Rw>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
S1: ComputedVecValue,
S2: ComputedVecValue,
{
pub tx_index: LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>,
#[traversable(flatten)]
pub distribution: TxDerivedDistribution<T, M>,
}
impl<T, S1, S2> LazyPerTxDistribution<T, S1, S2>
where
T: NumericValue + JsonSchema,
S1: ComputedVecValue + JsonSchema,
S2: ComputedVecValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
tx_index: LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>,
) -> Result<Self> {
let distribution = TxDerivedDistribution::forced_import(db, name, version, indexes)?;
Ok(Self {
tx_index,
distribution,
})
}
pub(crate) fn derive_from(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_lengths: &Lengths,
exit: &Exit,
) -> Result<()>
where
T: Copy + Ord + From<f64> + Default,
f64: From<T>,
LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>: ReadableVec<TxIndex, T>,
{
self.distribution
.derive_from(indexer, indexes, starting_lengths, &self.tx_index, exit)
}
}
@@ -1,36 +1,33 @@
use brk_traversable::Traversable;
use brk_types::{TxIndex, Version};
use schemars::JsonSchema;
use vecdb::{LazyVecFrom2, UnaryTransform};
use vecdb::{LazyVecFrom1, UnaryTransform};
use crate::internal::{ComputedVecValue, LazyTxDerivedDistribution, TxDerivedDistribution};
/// Like `LazyPerTxDistribution` but with a lazy-derived distribution
/// (transformed from another type's distribution rather than eagerly computed).
/// Per-transaction lazy values with a distribution transformed from another type's distribution.
#[derive(Clone, Traversable)]
pub struct LazyPerTxDistributionTransformed<T, S1, S2, DSource>
pub struct LazyPerTxDistributionTransformed<T, S, DSource>
where
T: ComputedVecValue + JsonSchema,
S1: ComputedVecValue,
S2: ComputedVecValue,
S: ComputedVecValue,
DSource: ComputedVecValue,
{
pub tx_index: LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>,
pub tx_index: LazyVecFrom1<TxIndex, T, TxIndex, S>,
#[traversable(flatten)]
pub distribution: LazyTxDerivedDistribution<T, DSource>,
}
impl<T, S1, S2, DSource> LazyPerTxDistributionTransformed<T, S1, S2, DSource>
impl<T, S, DSource> LazyPerTxDistributionTransformed<T, S, DSource>
where
T: ComputedVecValue + JsonSchema + 'static,
S1: ComputedVecValue + JsonSchema,
S2: ComputedVecValue + JsonSchema,
S: ComputedVecValue + JsonSchema,
DSource: ComputedVecValue + JsonSchema,
{
pub(crate) fn new<F: UnaryTransform<DSource, T>>(
name: &str,
version: Version,
tx_index: LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>,
tx_index: LazyVecFrom1<TxIndex, T, TxIndex, S>,
source_distribution: &TxDerivedDistribution<DSource>,
) -> Self {
let distribution =
@@ -1,11 +1,9 @@
mod derived;
mod distribution;
mod lazy_derived;
mod lazy_distribution;
mod lazy_distribution_transformed;
pub use derived::*;
pub use distribution::*;
pub use lazy_derived::*;
pub use lazy_distribution::*;
pub use lazy_distribution_transformed::*;
+27 -19
View File
@@ -1,16 +1,14 @@
use std::ops::{Add, AddAssign, Div};
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned16, BasisPointsSigned32, StoredF32,
PartsPerMillion32, PartsPerMillion64, PartsPerMillionSigned32, PartsPerMillionSigned64,
StoredF32,
};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{CheckedSub, Formattable, PcoVecValue, UnaryTransform};
use crate::internal::{
Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent,
Bps32ToFloat, Bps32ToPercent,
};
use crate::internal::{FixedToPercent, FixedToRatio};
pub trait ComputedVecValue
where
@@ -40,28 +38,38 @@ pub trait NumericValue: ComputedVecValue + CheckedSub + Default + From<f64> + In
impl<T> NumericValue for T where T: ComputedVecValue + CheckedSub + Default + From<f64> + Into<f64> {}
/// Trait that associates a basis-point type with its transforms to ratio and percent.
pub trait BpsType: NumericValue + JsonSchema {
/// A stored fixed-point ratio and its public representations.
pub trait FixedRatio: NumericValue + JsonSchema {
const SUFFIX: &'static str;
type ToRatio: UnaryTransform<Self, StoredF32>;
type ToPercent: UnaryTransform<Self, StoredF32>;
}
impl BpsType for BasisPoints16 {
type ToRatio = Bp16ToFloat;
type ToPercent = Bp16ToPercent;
impl FixedRatio for PartsPerMillion32 {
const SUFFIX: &'static str = "ppm";
type ToRatio = FixedToRatio;
type ToPercent = FixedToPercent;
}
impl BpsType for BasisPoints32 {
type ToRatio = Bp32ToFloat;
type ToPercent = Bp32ToPercent;
impl FixedRatio for PartsPerMillionSigned32 {
const SUFFIX: &'static str = "ppm";
type ToRatio = FixedToRatio;
type ToPercent = FixedToPercent;
}
impl BpsType for BasisPointsSigned16 {
type ToRatio = Bps16ToFloat;
type ToPercent = Bps16ToPercent;
impl FixedRatio for PartsPerMillion64 {
const SUFFIX: &'static str = "ppm";
type ToRatio = FixedToRatio;
type ToPercent = FixedToPercent;
}
impl BpsType for BasisPointsSigned32 {
type ToRatio = Bps32ToFloat;
type ToPercent = Bps32ToPercent;
impl FixedRatio for PartsPerMillionSigned64 {
const SUFFIX: &'static str = "ppm";
type ToRatio = FixedToRatio;
type ToPercent = FixedToPercent;
}
@@ -1,7 +1,7 @@
use std::marker::PhantomData;
use brk_types::{
BasisPoints16, Bitcoin, Cents, Dollars, Sats, StoredF32, StoredF64, StoredI8, StoredU16,
Bitcoin, Cents, Dollars, PartsPerMillion32, Sats, StoredF32, StoredF64, StoredI8, StoredU16,
StoredU32, StoredU64, VSize, Weight,
};
use vecdb::{BinaryTransform, UnaryTransform, VecValue};
@@ -128,12 +128,12 @@ impl UnaryTransform<StoredF64, StoredF64> for DifficultyToHashF64 {
}
}
pub struct OneMinusBp16;
pub struct OneMinusPpm;
impl UnaryTransform<BasisPoints16, BasisPoints16> for OneMinusBp16 {
impl UnaryTransform<PartsPerMillion32, PartsPerMillion32> for OneMinusPpm {
#[inline(always)]
fn apply(v: BasisPoints16) -> BasisPoints16 {
BasisPoints16::ONE - v
fn apply(value: PartsPerMillion32) -> PartsPerMillion32 {
PartsPerMillion32::ONE - value
}
}
@@ -146,11 +146,11 @@ impl UnaryTransform<StoredU64, Weight> for VBytesToWeight {
}
}
pub struct VSizeToWeight;
pub struct WeightToVSize;
impl UnaryTransform<VSize, Weight> for VSizeToWeight {
impl UnaryTransform<Weight, VSize> for WeightToVSize {
#[inline(always)]
fn apply(vsize: VSize) -> Weight {
Weight::from(vsize)
fn apply(weight: Weight) -> VSize {
VSize::from(weight)
}
}
@@ -1,76 +0,0 @@
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned16, BasisPointsSigned32, StoredF32,
};
use vecdb::UnaryTransform;
pub struct Bp16ToFloat;
impl UnaryTransform<BasisPoints16, StoredF32> for Bp16ToFloat {
#[inline(always)]
fn apply(bp: BasisPoints16) -> StoredF32 {
StoredF32::from(bp.to_f32())
}
}
pub struct Bp32ToFloat;
impl UnaryTransform<BasisPoints32, StoredF32> for Bp32ToFloat {
#[inline(always)]
fn apply(bp: BasisPoints32) -> StoredF32 {
StoredF32::from(bp.to_f32())
}
}
pub struct Bps16ToFloat;
impl UnaryTransform<BasisPointsSigned16, StoredF32> for Bps16ToFloat {
#[inline(always)]
fn apply(bp: BasisPointsSigned16) -> StoredF32 {
StoredF32::from(bp.to_f32())
}
}
pub struct Bps32ToFloat;
impl UnaryTransform<BasisPointsSigned32, StoredF32> for Bps32ToFloat {
#[inline(always)]
fn apply(bp: BasisPointsSigned32) -> StoredF32 {
StoredF32::from(bp.to_f32())
}
}
pub struct Bp16ToPercent;
impl UnaryTransform<BasisPoints16, StoredF32> for Bp16ToPercent {
#[inline(always)]
fn apply(bp: BasisPoints16) -> StoredF32 {
StoredF32::from(bp.to_f32() * 100.0)
}
}
pub struct Bp32ToPercent;
impl UnaryTransform<BasisPoints32, StoredF32> for Bp32ToPercent {
#[inline(always)]
fn apply(bp: BasisPoints32) -> StoredF32 {
StoredF32::from(bp.to_f32() * 100.0)
}
}
pub struct Bps16ToPercent;
impl UnaryTransform<BasisPointsSigned16, StoredF32> for Bps16ToPercent {
#[inline(always)]
fn apply(bp: BasisPointsSigned16) -> StoredF32 {
StoredF32::from(bp.to_f32() * 100.0)
}
}
pub struct Bps32ToPercent;
impl UnaryTransform<BasisPointsSigned32, StoredF32> for Bps32ToPercent {
#[inline(always)]
fn apply(bp: BasisPointsSigned32) -> StoredF32 {
StoredF32::from(bp.to_f32() * 100.0)
}
}
@@ -1,8 +1,10 @@
use std::marker::PhantomData;
use brk_types::{BasisPoints32, Cents, StoredF32, StoredF64};
use brk_types::{Cents, StoredF32, StoredF64};
use vecdb::{BinaryTransform, UnaryTransform};
use crate::internal::FixedRatio;
pub struct DaysToYears;
impl UnaryTransform<StoredF32, StoredF32> for DaysToYears {
@@ -54,12 +56,12 @@ impl BinaryTransform<Cents, StoredF32, Cents> for PriceTimesRatioCents {
}
}
pub struct PriceTimesRatioBp32Cents;
pub struct PriceTimesRatio<R>(PhantomData<R>);
impl BinaryTransform<Cents, BasisPoints32, Cents> for PriceTimesRatioBp32Cents {
impl<R: FixedRatio> BinaryTransform<Cents, R, Cents> for PriceTimesRatio<R> {
#[inline(always)]
fn apply(price: Cents, ratio: BasisPoints32) -> Cents {
Cents::from(f64::from(price) * f64::from(ratio))
fn apply(price: Cents, ratio: R) -> Cents {
Cents::from(f64::from(price) * ratio.into())
}
}
@@ -0,0 +1,35 @@
use brk_types::{
PartsPerMillion32, PartsPerMillion64, PartsPerMillionSigned32, PartsPerMillionSigned64,
StoredF32,
};
use vecdb::UnaryTransform;
pub struct FixedToRatio;
pub struct FixedToPercent;
macro_rules! impl_fixed_ratio {
($($type:ty),+ $(,)?) => {
$(
impl UnaryTransform<$type, StoredF32> for FixedToRatio {
#[inline(always)]
fn apply(value: $type) -> StoredF32 {
StoredF32::from(value.to_f32())
}
}
impl UnaryTransform<$type, StoredF32> for FixedToPercent {
#[inline(always)]
fn apply(value: $type) -> StoredF32 {
StoredF32::from(value.to_f32() * 100.0)
}
}
)+
};
}
impl_fixed_ratio!(
PartsPerMillion32,
PartsPerMillionSigned32,
PartsPerMillion64,
PartsPerMillionSigned64,
);
@@ -1,18 +1,14 @@
mod arithmetic;
mod bps;
mod currency;
mod derived;
mod fixed_ratio;
mod ratio;
mod specialized;
pub use arithmetic::{
BlocksToDaysF32, DifficultyToHashF64, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
Identity, MaskSats, OneMinusBp16, OneMinusF64, ReturnF32Tenths, ReturnI8, ReturnU16,
ThsToPhsF32, VBytesToWeight, VSizeToWeight,
};
pub use bps::{
Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent,
Bps32ToFloat, Bps32ToPercent,
Identity, MaskSats, OneMinusF64, OneMinusPpm, ReturnF32Tenths, ReturnI8, ReturnU16,
ThsToPhsF32, VBytesToWeight, WeightToVSize,
};
pub use currency::{
AvgCentsToUsd, AvgSatsToBtc, CentsSignedToDollars, CentsSubtractToCentsSigned,
@@ -20,13 +16,13 @@ pub use currency::{
NegCentsUnsignedToDollars, SatsSignedToBitcoin, SatsToBitcoin, SatsToCents,
};
pub use derived::{
Days1, Days7, Days30, Days365, DaysToYears, PriceTimesRatioBp32Cents, PriceTimesRatioCents,
RatioCents64, TimesSqrt,
Days1, Days7, Days30, Days365, DaysToYears, PriceTimesRatio, PriceTimesRatioCents, RatioCents64,
TimesSqrt,
};
pub use fixed_ratio::{FixedToPercent, FixedToRatio};
pub use ratio::{
RatioCentsBp16, RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32,
RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16,
RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, RatioU64F32,
RatioCents, RatioCentsSignedCents, RatioCentsSignedDollars, RatioDiffCents,
RatioDiffDollars, RatioDiffF32, RatioDollars, RatioSats, RatioU64, RatioU64F32,
};
pub use specialized::{
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,
@@ -1,126 +1,86 @@
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats,
StoredF32, StoredU64,
};
use std::marker::PhantomData;
use brk_types::{Cents, CentsSigned, Dollars, Sats, StoredF32, StoredU64};
use vecdb::BinaryTransform;
pub struct RatioU64Bp16;
use crate::internal::FixedRatio;
impl BinaryTransform<StoredU64, StoredU64, BasisPoints16> for RatioU64Bp16 {
pub struct RatioU64<P>(PhantomData<P>);
impl<P: FixedRatio> BinaryTransform<StoredU64, StoredU64, P> for RatioU64<P> {
#[inline(always)]
fn apply(numerator: StoredU64, denominator: StoredU64) -> BasisPoints16 {
fn apply(numerator: StoredU64, denominator: StoredU64) -> P {
if *denominator > 0 {
BasisPoints16::from(*numerator as f64 / *denominator as f64)
P::from(*numerator as f64 / *denominator as f64)
} else {
BasisPoints16::ZERO
P::default()
}
}
}
pub struct RatioSatsBp16;
pub struct RatioSats<P>(PhantomData<P>);
impl BinaryTransform<Sats, Sats, BasisPoints16> for RatioSatsBp16 {
impl<P: FixedRatio> BinaryTransform<Sats, Sats, P> for RatioSats<P> {
#[inline(always)]
fn apply(numerator: Sats, denominator: Sats) -> BasisPoints16 {
fn apply(numerator: Sats, denominator: Sats) -> P {
if *denominator > 0 {
BasisPoints16::from(*numerator as f64 / *denominator as f64)
P::from(*numerator as f64 / *denominator as f64)
} else {
BasisPoints16::ZERO
P::default()
}
}
}
pub struct RatioCentsBp32;
pub struct RatioCents<P>(PhantomData<P>);
impl BinaryTransform<Cents, Cents, BasisPoints32> for RatioCentsBp32 {
impl<P: FixedRatio> BinaryTransform<Cents, Cents, P> for RatioCents<P> {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> BasisPoints32 {
fn apply(numerator: Cents, denominator: Cents) -> P {
if denominator == Cents::ZERO {
BasisPoints32::ZERO
P::default()
} else {
BasisPoints32::from(numerator.inner() as f64 / denominator.inner() as f64)
P::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
pub struct RatioCentsBp16;
pub struct RatioDollars<P>(PhantomData<P>);
impl BinaryTransform<Cents, Cents, BasisPoints16> for RatioCentsBp16 {
impl<P: FixedRatio> BinaryTransform<Dollars, Dollars, P> for RatioDollars<P> {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> BasisPoints16 {
if denominator == Cents::ZERO {
BasisPoints16::ZERO
} else {
BasisPoints16::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
pub struct RatioDollarsBp16;
impl BinaryTransform<Dollars, Dollars, BasisPoints16> for RatioDollarsBp16 {
#[inline(always)]
fn apply(numerator: Dollars, denominator: Dollars) -> BasisPoints16 {
let ratio = *(numerator / denominator);
if ratio.is_finite() {
BasisPoints16::from(ratio)
} else {
BasisPoints16::ZERO
}
}
}
pub struct RatioDollarsBps32;
impl BinaryTransform<Dollars, Dollars, BasisPointsSigned32> for RatioDollarsBps32 {
#[inline(always)]
fn apply(numerator: Dollars, denominator: Dollars) -> BasisPointsSigned32 {
let ratio = *(numerator / denominator);
if ratio.is_finite() {
BasisPointsSigned32::from(ratio)
} else {
BasisPointsSigned32::ZERO
}
}
}
pub struct RatioCentsSignedCentsBps32;
impl BinaryTransform<CentsSigned, Cents, BasisPointsSigned32> for RatioCentsSignedCentsBps32 {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Cents) -> BasisPointsSigned32 {
if denominator == Cents::ZERO {
BasisPointsSigned32::ZERO
} else {
BasisPointsSigned32::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
pub struct RatioCentsSignedDollarsBps32;
impl BinaryTransform<CentsSigned, Dollars, BasisPointsSigned32> for RatioCentsSignedDollarsBps32 {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Dollars) -> BasisPointsSigned32 {
let d: f64 = denominator.into();
if d > 0.0 {
BasisPointsSigned32::from(numerator.inner() as f64 / 100.0 / d)
} else {
BasisPointsSigned32::ZERO
}
}
}
pub struct RatioDollarsBp32;
impl BinaryTransform<Dollars, Dollars, BasisPoints32> for RatioDollarsBp32 {
#[inline(always)]
fn apply(numerator: Dollars, denominator: Dollars) -> BasisPoints32 {
fn apply(numerator: Dollars, denominator: Dollars) -> P {
let ratio = f64::from(numerator) / f64::from(denominator);
if ratio.is_finite() {
BasisPoints32::from(ratio)
P::from(ratio)
} else {
BasisPoints32::ZERO
P::default()
}
}
}
pub struct RatioCentsSignedCents<P>(PhantomData<P>);
impl<P: FixedRatio> BinaryTransform<CentsSigned, Cents, P> for RatioCentsSignedCents<P> {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Cents) -> P {
if denominator == Cents::ZERO {
P::default()
} else {
P::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
pub struct RatioCentsSignedDollars<P>(PhantomData<P>);
impl<P: FixedRatio> BinaryTransform<CentsSigned, Dollars, P> for RatioCentsSignedDollars<P> {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Dollars) -> P {
let denominator = f64::from(denominator);
if denominator > 0.0 {
P::from(numerator.inner() as f64 / 100.0 / denominator)
} else {
P::default()
}
}
}
@@ -138,43 +98,43 @@ impl BinaryTransform<StoredU64, StoredU64, StoredF32> for RatioU64F32 {
}
}
pub struct RatioDiffF32Bps32;
pub struct RatioDiffF32<P>(PhantomData<P>);
impl BinaryTransform<StoredF32, StoredF32, BasisPointsSigned32> for RatioDiffF32Bps32 {
impl<P: FixedRatio> BinaryTransform<StoredF32, StoredF32, P> for RatioDiffF32<P> {
#[inline(always)]
fn apply(value: StoredF32, base: StoredF32) -> BasisPointsSigned32 {
fn apply(value: StoredF32, base: StoredF32) -> P {
if base.is_nan() || *base == 0.0 {
BasisPointsSigned32::ZERO
P::default()
} else {
BasisPointsSigned32::from((*value / *base - 1.0) as f64)
P::from((*value / *base - 1.0) as f64)
}
}
}
pub struct RatioDiffDollarsBps32;
pub struct RatioDiffDollars<P>(PhantomData<P>);
impl BinaryTransform<Dollars, Dollars, BasisPointsSigned32> for RatioDiffDollarsBps32 {
impl<P: FixedRatio> BinaryTransform<Dollars, Dollars, P> for RatioDiffDollars<P> {
#[inline(always)]
fn apply(close: Dollars, base: Dollars) -> BasisPointsSigned32 {
let base_f64: f64 = base.into();
if base_f64 == 0.0 {
BasisPointsSigned32::ZERO
fn apply(close: Dollars, base: Dollars) -> P {
let base = f64::from(base);
if base == 0.0 {
P::default()
} else {
BasisPointsSigned32::from(f64::from(close) / base_f64 - 1.0)
P::from(f64::from(close) / base - 1.0)
}
}
}
pub struct RatioDiffCentsBps32;
pub struct RatioDiffCents<P>(PhantomData<P>);
impl BinaryTransform<Cents, Cents, BasisPointsSigned32> for RatioDiffCentsBps32 {
impl<P: FixedRatio> BinaryTransform<Cents, Cents, P> for RatioDiffCents<P> {
#[inline(always)]
fn apply(close: Cents, base: Cents) -> BasisPointsSigned32 {
let base_f64 = f64::from(base);
if base_f64 == 0.0 {
BasisPointsSigned32::ZERO
fn apply(close: Cents, base: Cents) -> P {
let base = f64::from(base);
if base == 0.0 {
P::default()
} else {
BasisPointsSigned32::from(f64::from(close) / base_f64 - 1.0)
P::from(f64::from(close) / base - 1.0)
}
}
}
@@ -14,7 +14,7 @@ use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, WritableVec}
use crate::{indexes, price};
use super::{
BpsType, NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, ValuePerBlock,
FixedRatio, NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, ValuePerBlock,
WindowStartVec, Windows,
};
@@ -276,7 +276,7 @@ impl WithAddrTypes<AvgAmountMetrics> {
}
}
impl<B: BpsType> WithAddrTypes<PercentPerBlock<B>> {
impl<B: FixedRatio> WithAddrTypes<PercentPerBlock<B>> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -291,9 +291,9 @@ impl<B: BpsType> WithAddrTypes<PercentPerBlock<B>> {
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.all.bps.height.reset()?;
self.all.raw.height.reset()?;
for v in self.by_addr_type.values_mut() {
v.bps.height.reset()?;
v.raw.height.reset()?;
}
Ok(())
}
+8 -8
View File
@@ -1,10 +1,10 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{BasisPointsSigned32, Bitcoin, Cents, Date, Day1, Dollars, Sats};
use brk_types::{Bitcoin, Cents, Date, Day1, Dollars, PartsPerMillionSigned64, Sats};
use vecdb::{AnyVec, Exit, ReadableOptionVec, ReadableVec, VecIndex};
use super::{ByDcaPeriod, Vecs};
use crate::{blocks, indexes, internal::RatioDiffCentsBps32, market, price};
use crate::{blocks, indexes, internal::RatioDiffCents, market, price};
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
@@ -102,7 +102,7 @@ impl Vecs {
.iter_mut()
.zip(self.period.dca_cost_basis.iter_with_days())
{
returns.compute_binary::<Cents, Cents, RatioDiffCentsBps32>(
returns.compute_binary::<Cents, Cents, RatioDiffCents<PartsPerMillionSigned64>>(
starting_lengths.height,
&prices.spot.cents.height,
&average_price.cents.height,
@@ -117,13 +117,13 @@ impl Vecs {
.zip_mut_with_period(&self.period.dca_return)
{
let years = days as f64 / 365.0;
cagr.bps.height.compute_transform(
cagr.raw.height.compute_transform(
starting_lengths.height,
&returns.bps.height,
&returns.raw.height,
|(h, r, ..)| {
let ratio = f64::from(r);
let v = (ratio + 1.0).powf(1.0 / years) - 1.0;
(h, BasisPointsSigned32::from(v))
(h, PartsPerMillionSigned64::from(v))
},
exit,
)?;
@@ -163,7 +163,7 @@ impl Vecs {
.iter_mut()
.zip(lookback_dca.iter_with_days())
{
returns.compute_binary::<Cents, Cents, RatioDiffCentsBps32>(
returns.compute_binary::<Cents, Cents, RatioDiffCents<PartsPerMillionSigned64>>(
starting_lengths.height,
&prices.spot.cents.height,
&lookback_price.cents.height,
@@ -266,7 +266,7 @@ impl Vecs {
.iter_mut()
.zip(self.class.dca_cost_basis.iter())
{
returns.compute_binary::<Cents, Cents, RatioDiffCentsBps32>(
returns.compute_binary::<Cents, Cents, RatioDiffCents<PartsPerMillionSigned64>>(
starting_lengths.height,
&prices.spot.cents.height,
&average_price.cents.height,
+5 -5
View File
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Cents, Height, Sats};
use brk_types::{Cents, Height, PartsPerMillionSigned64, Sats};
use vecdb::{Database, EagerVec, PcoVec, Rw, StorageMode};
use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod};
@@ -9,17 +9,17 @@ use crate::internal::{PerBlock, PercentPerBlock, Price, ValuePerBlock};
pub struct PeriodVecs<M: StorageMode = Rw> {
pub dca_stack: ByDcaPeriod<ValuePerBlock<M>>,
pub dca_cost_basis: ByDcaPeriod<Price<PerBlock<Cents, M>>>,
pub dca_return: ByDcaPeriod<PercentPerBlock<BasisPointsSigned32, M>>,
pub dca_cagr: ByDcaCagr<PercentPerBlock<BasisPointsSigned32, M>>,
pub dca_return: ByDcaPeriod<PercentPerBlock<PartsPerMillionSigned64, M>>,
pub dca_cagr: ByDcaCagr<PercentPerBlock<PartsPerMillionSigned64, M>>,
pub lump_sum_stack: ByDcaPeriod<ValuePerBlock<M>>,
pub lump_sum_return: ByDcaPeriod<PercentPerBlock<BasisPointsSigned32, M>>,
pub lump_sum_return: ByDcaPeriod<PercentPerBlock<PartsPerMillionSigned64, M>>,
}
#[derive(Traversable)]
pub struct ClassVecs<M: StorageMode = Rw> {
pub dca_stack: ByDcaClass<ValuePerBlock<M>>,
pub dca_cost_basis: ByDcaClass<Price<PerBlock<Cents, M>>>,
pub dca_return: ByDcaClass<PercentPerBlock<BasisPointsSigned32, M>>,
pub dca_return: ByDcaClass<PercentPerBlock<PartsPerMillionSigned64, M>>,
}
#[derive(Traversable)]
+41 -16
View File
@@ -20,6 +20,7 @@ mod internal;
mod investing;
mod market;
mod mining;
mod op_return;
mod outputs;
mod pools;
pub mod price;
@@ -44,9 +45,10 @@ pub struct Computer<M: StorageMode = Rw> {
pub supply: Box<supply::Vecs<M>>,
pub inputs: Box<inputs::Vecs<M>>,
pub outputs: Box<outputs::Vecs<M>>,
pub op_return: Box<op_return::Vecs<M>>,
}
const VERSION: Version = Version::new(6);
const VERSION: Version = Version::new(7);
impl Computer {
pub fn forced_import(outputs_path: &Path, indexer: &Indexer) -> Result<Self> {
@@ -87,8 +89,9 @@ impl Computer {
let cached_starts = blocks.lookback.cached_window_starts();
let (inputs, outputs, mining, transactions, pools, cointime) =
timed("Imported inputs/outputs/mining/tx/pools/cointime", || {
let (inputs, outputs, mining, transactions, pools, cointime, op_return) = timed(
"Imported inputs/outputs/mining/tx/pools/cointime/op_return",
|| {
thread::scope(|s| -> Result<_> {
let inputs_handle = big_thread().spawn_scoped(s, || -> Result<_> {
Ok(Box::new(inputs::Vecs::forced_import(
@@ -143,15 +146,34 @@ impl Computer {
&cached_starts,
)?);
let op_return_handle = big_thread().spawn_scoped(s, || -> Result<_> {
Ok(Box::new(op_return::Vecs::forced_import(
&computed_path,
VERSION,
&indexes,
&cached_starts,
)?))
})?;
let inputs = inputs_handle.join().unwrap()?;
let outputs = outputs_handle.join().unwrap()?;
let mining = mining_handle.join().unwrap()?;
let transactions = transactions_handle.join().unwrap()?;
let pools = pools_handle.join().unwrap()?;
let op_return = op_return_handle.join().unwrap()?;
Ok((inputs, outputs, mining, transactions, pools, cointime))
Ok((
inputs,
outputs,
mining,
transactions,
pools,
cointime,
op_return,
))
})
})?;
},
)?;
// Market, indicators, and distribution are independent; import in parallel.
// Supply depends on distribution so it runs after.
@@ -225,6 +247,7 @@ impl Computer {
inputs,
price,
outputs,
op_return,
};
Self::retain_databases(&computed_path)?;
@@ -249,6 +272,7 @@ impl Computer {
supply::DB_NAME,
inputs::DB_NAME,
outputs::DB_NAME,
op_return::DB_NAME,
];
if !computed_path.exists() {
@@ -292,8 +316,7 @@ impl Computer {
let (inputs_result, prices_result) = rayon::join(
|| {
timed("Computed inputs", || {
self.inputs
.compute(indexer, &self.indexes, &self.blocks, exit)
self.inputs.compute(indexer, &self.blocks, exit)
})
},
|| {
@@ -337,18 +360,19 @@ impl Computer {
})
});
let op_return = scope.spawn(|| {
timed("Computed OP_RETURN", || {
self.op_return.compute(indexer, &self.blocks, exit)
})
});
timed("Computed outputs", || {
self.outputs.compute(
indexer,
&self.indexes,
&self.inputs,
&self.blocks,
&self.price,
exit,
)
self.outputs
.compute(indexer, &self.inputs, &self.blocks, &self.price, exit)
})?;
tx_mining.join().unwrap()?;
op_return.join().unwrap()?;
market.join().unwrap()?;
Ok(())
})?;
@@ -502,7 +526,8 @@ impl_iter_named!(
distribution,
supply,
inputs,
outputs
outputs,
op_return
);
fn timed<T>(label: &str, f: impl FnOnce() -> T) -> T {
+2 -2
View File
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned16, Cents, StoredF32};
use brk_types::{Cents, PartsPerMillionSigned32, StoredF32};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Price};
@@ -7,7 +7,7 @@ use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Price};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub high: Price<PerBlock<Cents, M>>,
pub drawdown: PercentPerBlock<BasisPointsSigned16, M>,
pub drawdown: PercentPerBlock<PartsPerMillionSigned32, M>,
pub days_since: PerBlock<StoredF32, M>,
pub years_since: LazyPerBlock<StoredF32>,
pub max_days_between: PerBlock<StoredF32, M>,
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{BasisPoints16, StoredF32};
use brk_types::{PartsPerMillion32, StoredF32};
use vecdb::{Exit, ReadableVec, VecIndex};
use super::Vecs;
@@ -72,7 +72,7 @@ impl Vecs {
exit,
)?;
self.choppiness_index_2w.bps.height.compute_transform4(
self.choppiness_index_2w.raw.height.compute_transform4(
starting_height,
&self.true_range_sum_2w.height,
&self.max._2w.cents.height,
@@ -82,9 +82,11 @@ impl Vecs {
let range = f64::from(max) - f64::from(min);
let n = (h.to_usize() - window_start.to_usize() + 1) as f32;
let ci = if range > 0.0 && n > 1.0 {
BasisPoints16::from((*tr_sum / range as f32).log10() as f64 / n.log10() as f64)
PartsPerMillion32::from(
(*tr_sum / range as f32).log10() as f64 / n.log10() as f64,
)
} else {
BasisPoints16::ZERO
PartsPerMillion32::ZERO
};
(h, ci)
},
+2 -2
View File
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Cents, StoredF32};
use brk_types::{Cents, PartsPerMillion32, StoredF32};
use vecdb::{Rw, StorageMode};
use crate::internal::{PerBlock, PercentPerBlock, Price};
@@ -18,5 +18,5 @@ pub struct Vecs<M: StorageMode = Rw> {
pub max: PriceMinMaxVecs<M>,
pub true_range: PerBlock<StoredF32, M>,
pub true_range_sum_2w: PerBlock<StoredF32, M>,
pub choppiness_index_2w: PercentPerBlock<BasisPoints16, M>,
pub choppiness_index_2w: PercentPerBlock<PartsPerMillion32, M>,
}
@@ -1,12 +1,10 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{BasisPointsSigned32, Dollars};
use brk_types::{Dollars, PartsPerMillionSigned64};
use vecdb::Exit;
use super::Vecs;
use crate::{
blocks, internal::RatioDiffDollarsBps32, investing::ByDcaPeriod, market::lookback, price,
};
use crate::{blocks, internal::RatioDiffDollars, investing::ByDcaPeriod, market::lookback, price};
impl Vecs {
pub(crate) fn compute(
@@ -25,7 +23,7 @@ impl Vecs {
.iter_mut_with_days()
.zip(lookback.price_past.iter_with_days())
{
returns.compute_binary::<Dollars, Dollars, RatioDiffDollarsBps32>(
returns.compute_binary::<Dollars, Dollars, RatioDiffDollars<PartsPerMillionSigned64>>(
starting_lengths.height,
&prices.spot.usd.height,
&lookback_price.usd.height,
@@ -37,13 +35,13 @@ impl Vecs {
let price_return_dca = ByDcaPeriod::from_lookback(&self.periods);
for (cagr, returns, days) in self.cagr.zip_mut_with_period(&price_return_dca) {
let years = days as f64 / 365.0;
cagr.bps.height.compute_transform(
cagr.raw.height.compute_transform(
starting_lengths.height,
&returns.bps.height,
&returns.raw.height,
|(h, r, ..)| {
let ratio = f64::from(r);
let v = (ratio + 1.0).powf(1.0 / years) - 1.0;
(h, BasisPointsSigned32::from(v))
(h, PartsPerMillionSigned64::from(v))
},
exit,
)?;
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::BasisPointsSigned32;
use brk_types::PartsPerMillionSigned64;
use vecdb::{Rw, StorageMode};
use crate::{
@@ -10,7 +10,7 @@ use crate::{
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub periods: ByLookbackPeriod<PercentPerBlock<BasisPointsSigned32, M>>,
pub cagr: ByDcaCagr<PercentPerBlock<BasisPointsSigned32, M>>,
pub periods: ByLookbackPeriod<PercentPerBlock<PartsPerMillionSigned64, M>>,
pub cagr: ByDcaCagr<PercentPerBlock<PartsPerMillionSigned64, M>>,
pub sd_24h: Windows<StdDevPerBlock<M>>,
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::Dollars;
use brk_types::{Dollars, PartsPerMillion32};
use vecdb::Exit;
use super::{
@@ -9,7 +9,7 @@ use super::{
};
use crate::{
blocks,
internal::{RatioDollarsBp32, WindowsTo1m},
internal::{RatioDollars, WindowsTo1m},
price,
};
@@ -62,8 +62,8 @@ impl Vecs {
}
self.pi_cycle
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
.raw
.compute_binary::<Dollars, Dollars, RatioDollars<PartsPerMillion32>>(
starting_height,
&moving_average.sma._111d.usd.height,
&moving_average.sma._350d_x2.usd.height,
+17 -17
View File
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{BasisPoints16, Height, StoredF32};
use brk_types::{Height, PartsPerMillion32, StoredF32};
use vecdb::{Exit, ReadableVec};
use super::RsiChain;
@@ -47,60 +47,60 @@ pub(super) fn compute(
exit,
)?;
chain.rsi.bps.height.compute_transform2(
chain.rsi.raw.height.compute_transform2(
starting_height,
&chain.average_gain.height,
&chain.average_loss.height,
|(h, g, l, ..)| {
let sum = *g + *l;
let rsi = if sum == 0.0 { 0.5 } else { *g / sum };
(h, BasisPoints16::from(rsi as f64))
(h, PartsPerMillion32::from(rsi as f64))
},
exit,
)?;
chain.rsi_min.bps.height.compute_rolling_min_from_starts(
chain.rsi_min.raw.height.compute_rolling_min_from_starts(
starting_height,
ws_rma,
&chain.rsi.bps.height,
&chain.rsi.raw.height,
exit,
)?;
chain.rsi_max.bps.height.compute_rolling_max_from_starts(
chain.rsi_max.raw.height.compute_rolling_max_from_starts(
starting_height,
ws_rma,
&chain.rsi.bps.height,
&chain.rsi.raw.height,
exit,
)?;
chain.stoch_rsi.bps.height.compute_transform3(
chain.stoch_rsi.raw.height.compute_transform3(
starting_height,
&chain.rsi.bps.height,
&chain.rsi_min.bps.height,
&chain.rsi_max.bps.height,
&chain.rsi.raw.height,
&chain.rsi_min.raw.height,
&chain.rsi_max.raw.height,
|(h, r, mn, mx, ..)| {
let range = f64::from(*mx) - f64::from(*mn);
let stoch = if range == 0.0 {
BasisPoints16::ZERO
PartsPerMillion32::ZERO
} else {
BasisPoints16::from((f64::from(*r) - f64::from(*mn)) / range)
PartsPerMillion32::from((f64::from(*r) - f64::from(*mn)) / range)
};
(h, stoch)
},
exit,
)?;
chain.stoch_rsi_k.bps.height.compute_rolling_average(
chain.stoch_rsi_k.raw.height.compute_rolling_average(
starting_height,
ws_sma,
&chain.stoch_rsi.bps.height,
&chain.stoch_rsi.raw.height,
exit,
)?;
chain.stoch_rsi_d.bps.height.compute_rolling_average(
chain.stoch_rsi_d.raw.height.compute_rolling_average(
starting_height,
ws_sma,
&chain.stoch_rsi_k.bps.height,
&chain.stoch_rsi_k.raw.height,
exit,
)?;
@@ -1,5 +1,5 @@
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
use brk_types::{PartsPerMillion32, StoredF32};
use vecdb::{Rw, StorageMode};
use crate::internal::{PerBlock, PercentPerBlock, RatioPerBlock, WindowsTo1m};
@@ -14,15 +14,15 @@ pub struct RsiChain<M: StorageMode = Rw> {
pub average_gain: PerBlock<StoredF32, M>,
#[traversable(hidden)]
pub average_loss: PerBlock<StoredF32, M>,
pub rsi: PercentPerBlock<BasisPoints16, M>,
pub rsi: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(hidden)]
pub rsi_min: PercentPerBlock<BasisPoints16, M>,
pub rsi_min: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(hidden)]
pub rsi_max: PercentPerBlock<BasisPoints16, M>,
pub rsi_max: PercentPerBlock<PartsPerMillion32, M>,
#[traversable(hidden)]
pub stoch_rsi: PercentPerBlock<BasisPoints16, M>,
pub stoch_rsi_k: PercentPerBlock<BasisPoints16, M>,
pub stoch_rsi_d: PercentPerBlock<BasisPoints16, M>,
pub stoch_rsi: PercentPerBlock<PartsPerMillion32, M>,
pub stoch_rsi_k: PercentPerBlock<PartsPerMillion32, M>,
pub stoch_rsi_d: PercentPerBlock<PartsPerMillion32, M>,
}
#[derive(Traversable)]
@@ -38,7 +38,7 @@ pub struct MacdChain<M: StorageMode = Rw> {
pub struct Vecs<M: StorageMode = Rw> {
pub rsi: WindowsTo1m<RsiChain<M>>,
pub pi_cycle: RatioPerBlock<BasisPoints32, M>,
pub pi_cycle: RatioPerBlock<PartsPerMillion32, M>,
pub macd: WindowsTo1m<MacdChain<M>>,
}
@@ -1,12 +1,12 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{Dollars, Height, Sats, StoredF32, StoredF64};
use brk_types::{Dollars, Height, PartsPerMillionSigned64, Sats, StoredF32, StoredF64};
use vecdb::{Exit, ReadableVec};
use super::Vecs;
use crate::{
blocks::{self, ONE_TERA_HASH, TARGET_BLOCKS_PER_DAY_F64},
internal::RatioDiffF32Bps32,
internal::RatioDiffF32,
};
impl Vecs {
@@ -103,7 +103,7 @@ impl Vecs {
self.price
.rebound
.compute_binary::<StoredF32, StoredF32, RatioDiffF32Bps32>(
.compute_binary::<StoredF32, StoredF32, RatioDiffF32<PartsPerMillionSigned64>>(
starting_height,
&self.price.phs.height,
&self.price.phs_min.height,
@@ -112,7 +112,7 @@ impl Vecs {
self.value
.rebound
.compute_binary::<StoredF32, StoredF32, RatioDiffF32Bps32>(
.compute_binary::<StoredF32, StoredF32, RatioDiffF32<PartsPerMillionSigned64>>(
starting_height,
&self.value.phs.height,
&self.value.phs_min.height,
@@ -77,7 +77,7 @@ impl Vecs {
rebound: PercentPerBlock::forced_import(
db,
"hash_price_rebound",
version + v4,
version + v5,
indexes,
)?,
},
@@ -89,7 +89,7 @@ impl Vecs {
rebound: PercentPerBlock::forced_import(
db,
"hash_value_rebound",
version + v4,
version + v5,
indexes,
)?,
},

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