mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-12 19:48:15 -07:00
heatmaps: part 21
This commit is contained in:
@@ -51,7 +51,7 @@ fn generate_get_method(output: &mut String, endpoint: &Endpoint) {
|
||||
}
|
||||
writeln!(
|
||||
output,
|
||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void }}}} [options]",
|
||||
" * @param {{{{ signal?: AbortSignal, onValue?: (value: {}) => void, cache?: 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 } = {}".to_string()
|
||||
"{ signal, onValue, cache } = {}".to_string()
|
||||
} else {
|
||||
format!("{}, {{ signal, onValue }} = {{}}", params)
|
||||
format!("{}, {{ signal, onValue, cache }} = {{}}", 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 })".to_string()
|
||||
"this.getBytes(path, { signal, onValue, cache })".to_string()
|
||||
} else if endpoint.returns_json() {
|
||||
"this.getJson(path, { signal, onValue })".to_string()
|
||||
"this.getJson(path, { signal, onValue, cache })".to_string()
|
||||
} else if endpoint.response_kind.text_is_numeric() {
|
||||
"Number(await this.getText(path, { signal, onValue: onValue ? (v) => onValue(Number(v)) : undefined }))".to_string()
|
||||
"Number(await this.getText(path, { signal, cache, onValue: onValue ? (v) => onValue(Number(v)) : undefined }))".to_string()
|
||||
} else {
|
||||
"this.getText(path, { signal, onValue })".to_string()
|
||||
"this.getText(path, { signal, onValue, cache })".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 }});"
|
||||
" if (format === 'csv') return this.getText(path, {{ signal, onValue, cache }});"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -448,14 +448,17 @@ class BrkClientBase {{
|
||||
|
||||
/**
|
||||
* @param {{string}} path
|
||||
* @param {{{{ signal?: AbortSignal }}}} [options]
|
||||
* @param {{{{ signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @returns {{Promise<Response>}}
|
||||
*/
|
||||
async get(path, {{ signal }} = {{}}) {{
|
||||
async get(path, {{ signal, cache = true }} = {{}}) {{
|
||||
const url = `${{this.baseUrl}}${{path}}`;
|
||||
const signals = [AbortSignal.timeout(this.timeout)];
|
||||
if (signal) signals.push(signal);
|
||||
const res = await fetch(url, {{ signal: AbortSignal.any(signals) }});
|
||||
/** @type {{RequestInit}} */
|
||||
const init = {{ signal: AbortSignal.any(signals) }};
|
||||
if (!cache) init.cache = 'no-store';
|
||||
const res = await fetch(url, init);
|
||||
if (!res.ok) throw new BrkError(`HTTP ${{res.status}}: ${{url}}`, res.status);
|
||||
return res;
|
||||
}}
|
||||
@@ -475,10 +478,17 @@ class BrkClientBase {{
|
||||
* @template T
|
||||
* @param {{string}} path
|
||||
* @param {{(res: Response) => Promise<T>}} parse - Response body reader
|
||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal }}}} [options]
|
||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @returns {{Promise<T>}}
|
||||
*/
|
||||
async _getCached(path, parse, {{ onValue, signal }} = {{}}) {{
|
||||
async _getCached(path, parse, {{ onValue, signal, cache = true }} = {{}}) {{
|
||||
if (!cache) {{
|
||||
const res = await this.get(path, {{ signal, cache }});
|
||||
const value = await parse(res);
|
||||
if (onValue) onValue(value);
|
||||
return value;
|
||||
}}
|
||||
|
||||
const url = `${{this.baseUrl}}${{path}}`;
|
||||
/** @type {{_MemEntry<T> | undefined}} */
|
||||
const memHit = this._memGet(url);
|
||||
@@ -497,8 +507,8 @@ class BrkClientBase {{
|
||||
this._memSet(url, netEtag, value);
|
||||
if (onValue) onValue(value);
|
||||
if (cloned && browserCache) {{
|
||||
const cache = browserCache;
|
||||
_runIdle(() => cache.put(url, cloned));
|
||||
const cacheStore = browserCache;
|
||||
_runIdle(() => cacheStore.put(url, cloned));
|
||||
}}
|
||||
return value;
|
||||
}} catch {{
|
||||
@@ -531,8 +541,8 @@ class BrkClientBase {{
|
||||
this._memSet(url, netEtag, value);
|
||||
if (onValue) onValue(value);
|
||||
if (cloned && browserCache) {{
|
||||
const cache = browserCache;
|
||||
_runIdle(() => cache.put(url, cloned));
|
||||
const cacheStore = browserCache;
|
||||
_runIdle(() => cacheStore.put(url, cloned));
|
||||
}}
|
||||
return value;
|
||||
}} catch (e) {{
|
||||
@@ -546,7 +556,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 }}}} [options]
|
||||
* @param {{{{ onValue?: (value: T) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @returns {{Promise<T>}}
|
||||
*/
|
||||
getJson(path, options) {{
|
||||
@@ -557,7 +567,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 }}}} [options]
|
||||
* @param {{{{ onValue?: (value: string) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @returns {{Promise<string>}}
|
||||
*/
|
||||
getText(path, options) {{
|
||||
@@ -568,7 +578,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 }}}} [options]
|
||||
* @param {{{{ onValue?: (value: Uint8Array) => void, signal?: AbortSignal, cache?: boolean }}}} [options]
|
||||
* @returns {{Promise<Uint8Array>}}
|
||||
*/
|
||||
getBytes(path, options) {{
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::Cents;
|
||||
|
||||
/// Aggregation strategy for URPD buckets.
|
||||
/// Options: raw (no aggregation), lin200/lin500/lin1000 (linear $200/$500/$1000),
|
||||
/// log10/log50/log100/log200/log500/log1000 (logarithmic with 10/50/100/200/500/1000 buckets per decade).
|
||||
/// log10/log50/log100/log200/log500/log1000/log2000 (logarithmic with 10/50/100/200/500/1000/2000 buckets per decade).
|
||||
#[derive(
|
||||
Debug, Display, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize, JsonSchema,
|
||||
)]
|
||||
@@ -24,6 +24,7 @@ pub enum UrpdAggregation {
|
||||
Log200,
|
||||
Log500,
|
||||
Log1000,
|
||||
Log2000,
|
||||
}
|
||||
|
||||
impl UrpdAggregation {
|
||||
@@ -46,6 +47,7 @@ impl UrpdAggregation {
|
||||
Self::Log200 => Some(200),
|
||||
Self::Log500 => Some(500),
|
||||
Self::Log1000 => Some(1000),
|
||||
Self::Log2000 => Some(2000),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -64,7 +66,8 @@ impl UrpdAggregation {
|
||||
| Self::Log100
|
||||
| Self::Log200
|
||||
| Self::Log500
|
||||
| Self::Log1000 => {
|
||||
| Self::Log1000
|
||||
| Self::Log2000 => {
|
||||
if price_cents == Cents::ZERO {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Type Alias: UrpdAggregation
|
||||
|
||||
> **UrpdAggregation** = `"raw"` \| `"lin200"` \| `"lin500"` \| `"lin1000"` \| `"log10"` \| `"log50"` \| `"log100"` \| `"log200"`
|
||||
> **UrpdAggregation** = `"raw"` \| `"lin200"` \| `"lin500"` \| `"lin1000"` \| `"log10"` \| `"log50"` \| `"log100"` \| `"log200"` \| `"log500"` \| `"log1000"` \| `"log2000"`
|
||||
|
||||
Defined in: [Developer/brk/modules/brk-client/index.js:1354](https://github.com/bitcoinresearchkit/brk/blob/0b871e86004ed9dd0c54dd9336049531d6fe4d23/modules/brk-client/index.js#L1354)
|
||||
|
||||
|
||||
+312
-302
File diff suppressed because it is too large
Load Diff
@@ -144,8 +144,8 @@ CoinbaseTag = str
|
||||
CostBasisValue = Literal["supply", "realized", "unrealized"]
|
||||
# Aggregation strategy for URPD buckets.
|
||||
# Options: raw (no aggregation), lin200/lin500/lin1000 (linear $200/$500/$1000),
|
||||
# log10/log50/log100/log200/log500/log1000 (logarithmic with 10/50/100/200/500/1000 buckets per decade).
|
||||
UrpdAggregation = Literal["raw", "lin200", "lin500", "lin1000", "log10", "log50", "log100", "log200", "log500", "log1000"]
|
||||
# log10/log50/log100/log200/log500/log1000/log2000 (logarithmic with 10/50/100/200/500/1000/2000 buckets per decade).
|
||||
UrpdAggregation = Literal["raw", "lin200", "lin500", "lin1000", "log10", "log50", "log100", "log200", "log500", "log1000", "log2000"]
|
||||
# Position of a transaction inside a `CpfpCluster.txs` array. Cluster-local,
|
||||
# has no meaning outside the enclosing cluster.
|
||||
CpfpClusterTxIndex = int
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @import { IChartApi, ISeriesApi as _ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData as _BaselineData, HistogramData as _HistogramData, SeriesType as LCSeriesType, IPaneApi, LineSeriesPartialOptions as _LineSeriesPartialOptions, HistogramSeriesPartialOptions as _HistogramSeriesPartialOptions, BaselineSeriesPartialOptions as _BaselineSeriesPartialOptions, CandlestickSeriesPartialOptions as _CandlestickSeriesPartialOptions, WhitespaceData, DeepPartial, ChartOptions, Time, LineData as _LineData, createChart as CreateLCChart, LineStyle, createSeriesMarkers as CreateSeriesMarkers, SeriesMarker, ISeriesMarkersPluginApi } from './modules/lightweight-charts/5.2.0/dist/typings.js'
|
||||
*
|
||||
* @import * as Brk from "./modules/brk-client/index.js"
|
||||
* @import { BrkClient, Index, SeriesData } from "./modules/brk-client/index.js"
|
||||
* @import { BrkClient, Index, SeriesData, Urpd } from "./modules/brk-client/index.js"
|
||||
*
|
||||
* @import { Options } from './options/full.js'
|
||||
*
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
oracleOutputsHeatmapOption,
|
||||
oraclePaymentsHeatmapOption,
|
||||
} from "../../src/heatmap/oracle.js";
|
||||
import { urpdSupplyHeatmapOption } from "../../src/heatmap/urpd.js";
|
||||
|
||||
// Re-export types for external consumers
|
||||
export * from "./types.js";
|
||||
@@ -309,6 +310,10 @@ export function createPartialOptions() {
|
||||
name: "oracle histograms",
|
||||
tree: [oracleOutputsHeatmapOption, oraclePaymentsHeatmapOption],
|
||||
},
|
||||
{
|
||||
name: "URPD",
|
||||
tree: [urpdSupplyHeatmapOption],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -4,11 +4,13 @@ import { numberToShortUSFormat } from "../../../scripts/utils/format.js";
|
||||
* @param {Object} [args]
|
||||
* @param {string} [args.valueLabel]
|
||||
* @param {string} [args.averageLabel]
|
||||
* @param {(value: number) => string} [args.formatValue]
|
||||
* @returns {HeatmapTooltipFn}
|
||||
*/
|
||||
export function defaultTooltip({
|
||||
valueLabel = "Value",
|
||||
averageLabel = "Avg value",
|
||||
formatValue = formatNumber,
|
||||
} = {}) {
|
||||
return ({ option, grid, col, row }) => {
|
||||
const dateRange = grid.getDateIndexRange(col);
|
||||
@@ -25,7 +27,7 @@ export function defaultTooltip({
|
||||
return [
|
||||
date,
|
||||
`${capitalize(yLabel)}: ${formatY(yRange.start)} to ${formatY(yRange.end)}`,
|
||||
`${label}: ${formatNumber(value)}`,
|
||||
`${label}: ${formatValue(value)}`,
|
||||
].join("\n");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { brk } from "../../scripts/utils/client.js";
|
||||
import { numberToShortUSFormat } from "../../scripts/utils/format.js";
|
||||
import { createAverageGrid } from "./grid.js";
|
||||
import { INFERNO_LUT, logIntensityColor } from "./lut.js";
|
||||
import { defaultTooltip } from "./tooltip/index.js";
|
||||
|
||||
const COHORT = "all";
|
||||
const AGGREGATION = "raw";
|
||||
const MIN_LOG = -2;
|
||||
const MAX_LOG = 6;
|
||||
const DEFAULT_MIN_LOG = Math.log10(1_000);
|
||||
const DEFAULT_MAX_LOG = Math.log10(250_000);
|
||||
const PRICE_CHOICES = [
|
||||
{ label: "$0.01", value: Math.log10(0.01) },
|
||||
{ label: "$0.1", value: Math.log10(0.1) },
|
||||
{ label: "$1", value: 0 },
|
||||
{ label: "$10", value: 1 },
|
||||
{ label: "$100", value: 2 },
|
||||
{ label: "$250", value: Math.log10(250) },
|
||||
{ label: "$1k", value: Math.log10(1_000) },
|
||||
{ label: "$2.5k", value: Math.log10(2_500) },
|
||||
{ label: "$5k", value: Math.log10(5_000) },
|
||||
{ label: "$10k", value: Math.log10(10_000) },
|
||||
{ label: "$25k", value: Math.log10(25_000) },
|
||||
{ label: "$50k", value: Math.log10(50_000) },
|
||||
{ label: "$100k", value: Math.log10(100_000) },
|
||||
{ label: "$250k", value: Math.log10(250_000) },
|
||||
{ label: "$500k", value: Math.log10(500_000) },
|
||||
{ label: "$1M", value: Math.log10(1_000_000) },
|
||||
];
|
||||
|
||||
/** @satisfies {PartialHeatmapOption} */
|
||||
export const urpdSupplyHeatmapOption = {
|
||||
kind: "heatmap",
|
||||
name: "Supply",
|
||||
title: "URPD Supply",
|
||||
points: {
|
||||
fetch: (date, signal, onPoints) =>
|
||||
fetchUrpdSupplyPoints(date, signal, onPoints),
|
||||
},
|
||||
grid: createAverageGrid({
|
||||
yMin: MIN_LOG,
|
||||
yMax: MAX_LOG,
|
||||
}),
|
||||
color: logIntensityColor(INFERNO_LUT),
|
||||
axis: {
|
||||
y: {
|
||||
label: "price",
|
||||
choices: PRICE_CHOICES,
|
||||
format: formatPrice,
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
from: "2017",
|
||||
to: "today",
|
||||
yMin: DEFAULT_MIN_LOG,
|
||||
yMax: DEFAULT_MAX_LOG,
|
||||
},
|
||||
tooltip: defaultTooltip({
|
||||
valueLabel: "Supply",
|
||||
averageLabel: "Avg supply",
|
||||
formatValue: formatBitcoin,
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} date
|
||||
* @param {AbortSignal} signal
|
||||
* @param {(points: HeatmapPoints) => void} [onPoints]
|
||||
* @returns {Promise<HeatmapPoints>}
|
||||
*/
|
||||
async function fetchUrpdSupplyPoints(date, signal, onPoints) {
|
||||
/** @type {HeatmapPoints | undefined} */
|
||||
let points;
|
||||
const urpd = await brk.getUrpdAt(COHORT, date, AGGREGATION, {
|
||||
signal,
|
||||
cache: false,
|
||||
onValue: onPoints
|
||||
? (value) => {
|
||||
points = toSupplyPoints(value);
|
||||
onPoints(points);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return points ?? toSupplyPoints(urpd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Urpd} urpd
|
||||
* @returns {HeatmapPoints}
|
||||
*/
|
||||
function toSupplyPoints(urpd) {
|
||||
const buckets = urpd.buckets;
|
||||
const y = new Float64Array(buckets.length);
|
||||
const values = new Float64Array(buckets.length);
|
||||
let length = 0;
|
||||
|
||||
for (let i = 0; i < buckets.length; i++) {
|
||||
const bucket = buckets[i];
|
||||
if (bucket.priceFloor <= 0 || !Number.isFinite(bucket.supply)) continue;
|
||||
y[length] = Math.log10(bucket.priceFloor);
|
||||
values[length] = bucket.supply;
|
||||
length++;
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "explicit",
|
||||
y: y.subarray(0, length),
|
||||
values: values.subarray(0, length),
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {number} value */
|
||||
function formatPrice(value) {
|
||||
const rounded = Math.round(value);
|
||||
if (Math.abs(value - rounded) < 0.001) {
|
||||
const choice = PRICE_CHOICES.find((choice) => choice.value === rounded);
|
||||
if (choice) return choice.label;
|
||||
}
|
||||
|
||||
const price = 10 ** value;
|
||||
if (price >= 1_000_000) return `$${formatCompact(price / 1_000_000)}M`;
|
||||
if (price >= 1_000) return `$${formatCompact(price / 1_000)}k`;
|
||||
return `$${formatCompact(price)}`;
|
||||
}
|
||||
|
||||
/** @param {number} value */
|
||||
function formatBitcoin(value) {
|
||||
return `${numberToShortUSFormat(value)} BTC`;
|
||||
}
|
||||
|
||||
/** @param {number} value */
|
||||
function formatCompact(value) {
|
||||
if (value >= 1000) return `${formatNumber(value / 1000)}k`;
|
||||
return formatNumber(value);
|
||||
}
|
||||
|
||||
/** @param {number} value */
|
||||
function formatNumber(value) {
|
||||
if (value >= 100) return String(Math.round(value));
|
||||
if (value >= 10) return trimNumber(value.toFixed(1));
|
||||
return trimNumber(value.toFixed(2));
|
||||
}
|
||||
|
||||
/** @param {string} value */
|
||||
function trimNumber(value) {
|
||||
return value.replace(/\.?0+$/, "");
|
||||
}
|
||||
Reference in New Issue
Block a user