mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -07:00
148 lines
4.0 KiB
JavaScript
148 lines
4.0 KiB
JavaScript
import { createShadow, createHeader } from "../utils/dom.js";
|
|
import { chartElement } from "../utils/elements.js";
|
|
import { serdeChartableIndex } from "../utils/serde.js";
|
|
import { Unit } from "../utils/units.js";
|
|
import { createChart } from "../chart/index.js";
|
|
import { colors } from "../utils/colors.js";
|
|
import { webSockets } from "../utils/ws.js";
|
|
import { brk } from "../client.js";
|
|
|
|
const ONE_BTC_IN_SATS = 100_000_000;
|
|
|
|
/** @type {((opt: ChartOption) => void) | null} */
|
|
let _setOption = null;
|
|
|
|
/**
|
|
* @param {ChartOption} opt
|
|
*/
|
|
export function setOption(opt) {
|
|
if (!_setOption) throw new Error("Chart not initialized");
|
|
_setOption(opt);
|
|
}
|
|
|
|
export function init() {
|
|
chartElement.append(createShadow("left"));
|
|
chartElement.append(createShadow("right"));
|
|
|
|
const { headerElement, headingElement } = createHeader();
|
|
chartElement.append(headerElement);
|
|
|
|
const chart = createChart({
|
|
parent: chartElement,
|
|
id: "charts",
|
|
brk,
|
|
});
|
|
|
|
const setChoices = chart.setIndexChoices;
|
|
|
|
/**
|
|
* Build top blueprints with price series prepended for each unit
|
|
* @param {Map<Unit, AnyFetchedSeriesBlueprint[]>} optionTop
|
|
* @returns {Map<Unit, AnyFetchedSeriesBlueprint[]>}
|
|
*/
|
|
function buildTopBlueprints(optionTop) {
|
|
/** @type {Map<Unit, AnyFetchedSeriesBlueprint[]>} */
|
|
const result = new Map();
|
|
|
|
// USD price + option blueprints
|
|
/** @type {FetchedCandlestickSeriesBlueprint} */
|
|
const usdPrice = {
|
|
type: "Candlestick",
|
|
title: "Price",
|
|
metric: brk.metrics.price.usd.ohlc,
|
|
};
|
|
result.set(Unit.usd, [usdPrice, ...(optionTop.get(Unit.usd) ?? [])]);
|
|
|
|
// Sats price + option blueprints
|
|
/** @type {FetchedCandlestickSeriesBlueprint} */
|
|
const satsPrice = {
|
|
type: "Candlestick",
|
|
title: "Price",
|
|
metric: brk.metrics.price.sats.ohlc,
|
|
colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]),
|
|
};
|
|
result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]);
|
|
|
|
return result;
|
|
}
|
|
|
|
/** @type {ReturnType<typeof chart.setBlueprints> | null} */
|
|
let blueprints = null;
|
|
|
|
function updatePriceWithLatest() {
|
|
const latest = webSockets.kraken1dCandle.latest();
|
|
if (!latest || !blueprints) return;
|
|
|
|
const priceSeries = blueprints.panes[0].series[0];
|
|
const unit = blueprints.panes[0].unit;
|
|
if (!priceSeries?.hasData() || !unit) return;
|
|
|
|
const last = /** @type {CandlestickData | undefined} */ (
|
|
priceSeries.getData().at(-1)
|
|
);
|
|
if (!last) return;
|
|
|
|
// Convert to sats if needed
|
|
const close =
|
|
unit === Unit.sats
|
|
? Math.floor(ONE_BTC_IN_SATS / latest.close)
|
|
: latest.close;
|
|
|
|
priceSeries.update({ ...last, close });
|
|
}
|
|
|
|
// Set up the setOption function
|
|
_setOption = (opt) => {
|
|
headingElement.innerHTML = opt.title;
|
|
|
|
// Set blueprints first so storageId is correct before any index change
|
|
blueprints = chart.setBlueprints({
|
|
name: opt.title,
|
|
top: buildTopBlueprints(opt.top),
|
|
bottom: opt.bottom,
|
|
onDataLoaded: updatePriceWithLatest,
|
|
});
|
|
|
|
// Update index choices (may trigger rebuild if index changes)
|
|
setChoices(computeChoices(opt));
|
|
};
|
|
|
|
// Live price update listener
|
|
webSockets.kraken1dCandle.onLatest(updatePriceWithLatest);
|
|
}
|
|
|
|
const ALL_CHOICES = /** @satisfies {ChartableIndexName[]} */ ([
|
|
"timestamp",
|
|
"date",
|
|
"week",
|
|
"month",
|
|
"quarter",
|
|
"semester",
|
|
"year",
|
|
"decade",
|
|
]);
|
|
|
|
/**
|
|
* @param {ChartOption} opt
|
|
* @returns {ChartableIndexName[]}
|
|
*/
|
|
function computeChoices(opt) {
|
|
if (!opt.top.size && !opt.bottom.size) {
|
|
return [...ALL_CHOICES];
|
|
}
|
|
const rawIndexes = new Set(
|
|
[Array.from(opt.top.values()), Array.from(opt.bottom.values())]
|
|
.flat(2)
|
|
.filter((blueprint) => {
|
|
const path = Object.values(blueprint.metric.by)[0]?.path ?? "";
|
|
return !path.includes("constant_");
|
|
})
|
|
.flatMap((blueprint) => blueprint.metric.indexes()),
|
|
);
|
|
|
|
return ALL_CHOICES.filter((choice) =>
|
|
rawIndexes.has(serdeChartableIndex.deserialize(choice)),
|
|
);
|
|
}
|
|
|