mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 06:01:57 -07:00
website: snapshot
This commit is contained in:
@@ -22,6 +22,7 @@ _*
|
||||
/filter_*
|
||||
/heatmaps*
|
||||
/oracle*
|
||||
/playground
|
||||
|
||||
# Logs
|
||||
*.log*
|
||||
|
||||
@@ -37,6 +37,8 @@ brk
|
||||
|
||||
Indexes the blockchain, computes datasets, starts the server on `localhost:3110`, and waits for new blocks.
|
||||
|
||||
**Note:** When more than 10,000 blocks behind, indexing completes before the server starts to free up memory from fragmentation that occurs during large syncs. The web interface at `localhost:3110` won't be available until sync finishes.
|
||||
|
||||
## Options
|
||||
|
||||
```bash
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn run() -> anyhow::Result<()> {
|
||||
let chain_height = client.get_last_height()?;
|
||||
let indexed_height = indexer.vecs.starting_height();
|
||||
let blocks_behind = chain_height.saturating_sub(*indexed_height);
|
||||
if blocks_behind > 1000 {
|
||||
if blocks_behind > 10_000 {
|
||||
info!("Indexing {blocks_behind} blocks before starting server...");
|
||||
sleep(Duration::from_secs(3));
|
||||
indexer.index(&blocks, &client, &exit)?;
|
||||
|
||||
+7
-1
@@ -1533,6 +1533,11 @@
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
|
||||
let storedTheme;
|
||||
try { storedTheme = localStorage.getItem("theme"); } catch (_) {}
|
||||
const isDark = storedTheme ? storedTheme === "dark" : preferredColorSchemeMatchMedia.matches;
|
||||
document.documentElement.style.colorScheme = isDark ? "dark" : "light";
|
||||
|
||||
const themeColor = window.document.createElement("meta");
|
||||
themeColor.name = "theme-color";
|
||||
window.document.getElementsByTagName("head")[0].appendChild(themeColor);
|
||||
@@ -1545,7 +1550,7 @@
|
||||
themeColor.content = theme;
|
||||
}
|
||||
|
||||
updateThemeColor(preferredColorSchemeMatchMedia.matches);
|
||||
updateThemeColor(isDark);
|
||||
preferredColorSchemeMatchMedia.addEventListener(
|
||||
"change",
|
||||
({ matches }) => {
|
||||
@@ -1832,6 +1837,7 @@
|
||||
</label>
|
||||
|
||||
<button id="share-button" title="Share">Share</button>
|
||||
<button id="invert-button" title="Invert">Invert</button>
|
||||
</fieldset>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
@@ -33,16 +33,18 @@ import { resources } from "../resources.js";
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Object} Series
|
||||
* @property {string} key
|
||||
* @property {string} id
|
||||
* @property {number} paneIndex
|
||||
* @property {Signal<boolean>} active
|
||||
* @property {(value: boolean) => void} setActive
|
||||
* @property {() => void} show
|
||||
* @property {() => void} hide
|
||||
* @property {(order: number) => void} setOrder
|
||||
* @property {() => void} highlight
|
||||
* @property {() => void} tame
|
||||
* @property {() => boolean} hasData
|
||||
* @property {Signal<string | null>} url
|
||||
* @property {string | null} url
|
||||
* @property {() => readonly T[]} getData
|
||||
* @property {(data: T) => void} update
|
||||
* @property {VoidFunction} remove
|
||||
@@ -102,14 +104,18 @@ export function createChart({
|
||||
/** @type {Map<string, Signal<boolean>>} */
|
||||
const sharedActiveSignals = new Map();
|
||||
|
||||
const legendTop = createLegend(signals);
|
||||
// Registry for linked series (same key = linked across panes)
|
||||
/** @type {Map<string, Set<AnySeries>>} */
|
||||
const seriesByKey = new Map();
|
||||
|
||||
const legendTop = createLegend();
|
||||
div.append(legendTop.element);
|
||||
|
||||
const chartDiv = window.document.createElement("div");
|
||||
chartDiv.classList.add("lightweight-chart");
|
||||
div.append(chartDiv);
|
||||
|
||||
const legendBottom = createLegend(signals);
|
||||
const legendBottom = createLegend();
|
||||
div.append(legendBottom.element);
|
||||
|
||||
const ichart = lcCreateChart(
|
||||
@@ -175,15 +181,18 @@ export function createChart({
|
||||
/** @type {Set<ZoomChangeCallback>} */
|
||||
const onZoomChange = new Set();
|
||||
|
||||
ichart.timeScale().subscribeVisibleLogicalRangeChange(
|
||||
throttle((range) => {
|
||||
if (!range) return;
|
||||
const count = range.to - range.from;
|
||||
if (count === visibleBarsCount) return;
|
||||
visibleBarsCount = count;
|
||||
onZoomChange.forEach((cb) => cb(count));
|
||||
}, 100),
|
||||
);
|
||||
/** @param {{ from: number, to: number } | null} range */
|
||||
function updateVisibleBarsCount(range) {
|
||||
if (!range) return;
|
||||
const count = range.to - range.from;
|
||||
if (count === visibleBarsCount) return;
|
||||
visibleBarsCount = count;
|
||||
onZoomChange.forEach((cb) => cb(count));
|
||||
}
|
||||
|
||||
ichart
|
||||
.timeScale()
|
||||
.subscribeVisibleLogicalRangeChange(throttle(updateVisibleBarsCount, 100));
|
||||
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
@@ -391,27 +400,24 @@ export function createChart({
|
||||
onRemove,
|
||||
}) {
|
||||
return signals.createRoot((dispose) => {
|
||||
const id = `${stringToId(name)}-${paneIndex}`;
|
||||
const urlId = stringToId(name);
|
||||
const key = stringToId(name);
|
||||
const id = `${key}-${paneIndex}`;
|
||||
|
||||
// Reuse existing signal if same name (links legends across panes)
|
||||
let active = sharedActiveSignals.get(urlId);
|
||||
let active = sharedActiveSignals.get(key);
|
||||
if (!active) {
|
||||
active = signals.createPersistedSignal({
|
||||
defaultValue: defaultActive ?? true,
|
||||
storageKey: id,
|
||||
urlKey: urlId,
|
||||
urlKey: key,
|
||||
...serdeBool,
|
||||
});
|
||||
sharedActiveSignals.set(urlId, active);
|
||||
sharedActiveSignals.set(key, active);
|
||||
}
|
||||
|
||||
setOrder(-order);
|
||||
|
||||
// Bridge signal to series methods
|
||||
signals.createEffect(active, (isActive) => {
|
||||
isActive ? show() : hide();
|
||||
});
|
||||
active() ? show() : hide();
|
||||
|
||||
let hasData = false;
|
||||
let lastTime = -Infinity;
|
||||
@@ -422,26 +428,47 @@ export function createChart({
|
||||
/** @type {AnySeries} */
|
||||
const series = {
|
||||
active,
|
||||
setActive(value) {
|
||||
active.set(value);
|
||||
seriesByKey.get(key)?.forEach((s) => {
|
||||
value ? s.show() : s.hide();
|
||||
});
|
||||
document.querySelectorAll(`[data-series="${key}"]`).forEach((el) => {
|
||||
if (el instanceof HTMLInputElement && el.type === "checkbox") {
|
||||
el.checked = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
setOrder,
|
||||
show,
|
||||
hide,
|
||||
highlight,
|
||||
tame,
|
||||
hasData: () => hasData,
|
||||
key,
|
||||
id,
|
||||
paneIndex,
|
||||
url: signals.createSignal(/** @type {string | null} */ (null)),
|
||||
url: null,
|
||||
getData,
|
||||
update,
|
||||
remove() {
|
||||
dispose();
|
||||
onRemove();
|
||||
seriesByKey.get(key)?.delete(series);
|
||||
if (_valuesResource) {
|
||||
activeResources.delete(_valuesResource);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Register series for cross-pane linking
|
||||
let keySet = seriesByKey.get(key);
|
||||
if (!keySet) {
|
||||
keySet = new Set();
|
||||
seriesByKey.set(key, keySet);
|
||||
}
|
||||
keySet.add(series);
|
||||
|
||||
if (metric) {
|
||||
signals.createScopedEffect(index, (index) => {
|
||||
// Get timestamp metric from tree based on index type
|
||||
@@ -462,11 +489,15 @@ export function createChart({
|
||||
const valuesResource = resources.useMetricEndpoint(valuesNode);
|
||||
_valuesResource = valuesResource;
|
||||
|
||||
series.url.set(() => {
|
||||
const base = brk.baseUrl.endsWith("/")
|
||||
? brk.baseUrl.slice(0, -1)
|
||||
: brk.baseUrl;
|
||||
return `${base}${valuesResource.path}`;
|
||||
series.url = `${
|
||||
brk.baseUrl.endsWith("/") ? brk.baseUrl.slice(0, -1) : brk.baseUrl
|
||||
}${valuesResource.path}`;
|
||||
|
||||
(paneIndex ? legendBottom : legendTop).addOrReplace({
|
||||
series,
|
||||
name,
|
||||
colors,
|
||||
order,
|
||||
});
|
||||
|
||||
// Create memo outside active check (cheap, just checks data existence)
|
||||
@@ -608,22 +639,24 @@ export function createChart({
|
||||
},
|
||||
);
|
||||
});
|
||||
} else if (data) {
|
||||
signals.createEffect(data, (data) => {
|
||||
setData(data);
|
||||
hasData = true;
|
||||
if (fitContent) {
|
||||
ichart.timeScale().fitContent();
|
||||
}
|
||||
} else {
|
||||
(paneIndex ? legendBottom : legendTop).addOrReplace({
|
||||
series,
|
||||
name,
|
||||
colors,
|
||||
order,
|
||||
});
|
||||
}
|
||||
|
||||
(paneIndex ? legendBottom : legendTop).addOrReplace({
|
||||
series,
|
||||
name,
|
||||
colors,
|
||||
order,
|
||||
});
|
||||
if (data) {
|
||||
signals.createEffect(data, (data) => {
|
||||
setData(data);
|
||||
hasData = true;
|
||||
if (fitContent) {
|
||||
ichart.timeScale().fitContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addPriceScaleSelectorIfNeeded({
|
||||
paneIndex,
|
||||
@@ -693,7 +726,6 @@ export function createChart({
|
||||
wickUpColor: upColor(),
|
||||
wickDownColor: downColor(),
|
||||
borderVisible: false,
|
||||
visible: defaultActive !== false,
|
||||
...options,
|
||||
},
|
||||
paneIndex,
|
||||
@@ -707,14 +739,13 @@ export function createChart({
|
||||
{
|
||||
color: colors.default(),
|
||||
lineWidth,
|
||||
visible: false,
|
||||
priceLineVisible: true,
|
||||
},
|
||||
paneIndex,
|
||||
)
|
||||
);
|
||||
|
||||
let active = true;
|
||||
let active = defaultActive !== false;
|
||||
let highlighted = true;
|
||||
let showLine = visibleBarsCount > 500;
|
||||
|
||||
@@ -733,11 +764,11 @@ export function createChart({
|
||||
color: colors.default.highlight(highlighted),
|
||||
});
|
||||
}
|
||||
update();
|
||||
|
||||
/** @type {ZoomChangeCallback} */
|
||||
function handleZoom(count) {
|
||||
const newShowLine = count > 500;
|
||||
if (newShowLine === showLine) return;
|
||||
showLine = newShowLine;
|
||||
update();
|
||||
}
|
||||
@@ -827,8 +858,6 @@ export function createChart({
|
||||
ichart.addSeries(
|
||||
/** @type {SeriesDefinition<'Histogram'>} */ (HistogramSeries),
|
||||
{
|
||||
color: positiveColor(),
|
||||
visible: defaultActive !== false,
|
||||
priceLineVisible: false,
|
||||
...options,
|
||||
},
|
||||
@@ -836,7 +865,7 @@ export function createChart({
|
||||
)
|
||||
);
|
||||
|
||||
let active = true;
|
||||
let active = defaultActive !== false;
|
||||
let highlighted = true;
|
||||
|
||||
function update() {
|
||||
@@ -846,6 +875,7 @@ export function createChart({
|
||||
color: positiveColor.highlight(highlighted),
|
||||
});
|
||||
}
|
||||
update();
|
||||
|
||||
const series = addSeries({
|
||||
colors: isDualColor ? [positiveColor, negativeColor] : [positiveColor],
|
||||
@@ -931,16 +961,14 @@ export function createChart({
|
||||
/** @type {SeriesDefinition<'Line'>} */ (LineSeries),
|
||||
{
|
||||
lineWidth,
|
||||
visible: defaultActive !== false,
|
||||
priceLineVisible: false,
|
||||
color: color(),
|
||||
...options,
|
||||
},
|
||||
paneIndex,
|
||||
)
|
||||
);
|
||||
|
||||
let active = true;
|
||||
let active = defaultActive !== false;
|
||||
let highlighted = true;
|
||||
|
||||
function update() {
|
||||
@@ -950,6 +978,7 @@ export function createChart({
|
||||
color: color.highlight(highlighted),
|
||||
});
|
||||
}
|
||||
update();
|
||||
|
||||
const series = addSeries({
|
||||
colors: [color],
|
||||
@@ -1020,9 +1049,7 @@ export function createChart({
|
||||
ichart.addSeries(
|
||||
/** @type {SeriesDefinition<'Line'>} */ (LineSeries),
|
||||
{
|
||||
visible: defaultActive !== false,
|
||||
priceLineVisible: false,
|
||||
color: color(),
|
||||
lineVisible: false,
|
||||
pointMarkersVisible: true,
|
||||
pointMarkersRadius: 1,
|
||||
@@ -1032,7 +1059,7 @@ export function createChart({
|
||||
)
|
||||
);
|
||||
|
||||
let active = true;
|
||||
let active = defaultActive !== false;
|
||||
let highlighted = true;
|
||||
let radius =
|
||||
visibleBarsCount > 1000 ? 1 : visibleBarsCount > 200 ? 1.5 : 2;
|
||||
@@ -1044,6 +1071,7 @@ export function createChart({
|
||||
color: color.highlight(highlighted),
|
||||
});
|
||||
}
|
||||
update();
|
||||
|
||||
/** @type {ZoomChangeCallback} */
|
||||
function handleZoom(count) {
|
||||
@@ -1128,13 +1156,10 @@ export function createChart({
|
||||
/** @type {SeriesDefinition<'Baseline'>} */ (BaselineSeries),
|
||||
{
|
||||
lineWidth,
|
||||
visible: defaultActive !== false,
|
||||
baseValue: {
|
||||
price: options?.baseValue?.price ?? 0,
|
||||
},
|
||||
...options,
|
||||
topLineColor: topColor(),
|
||||
bottomLineColor: bottomColor(),
|
||||
priceLineVisible: false,
|
||||
bottomFillColor1: "transparent",
|
||||
bottomFillColor2: "transparent",
|
||||
@@ -1146,7 +1171,7 @@ export function createChart({
|
||||
)
|
||||
);
|
||||
|
||||
let active = true;
|
||||
let active = defaultActive !== false;
|
||||
let highlighted = true;
|
||||
|
||||
function update() {
|
||||
@@ -1157,6 +1182,7 @@ export function createChart({
|
||||
bottomLineColor: bottomColor.highlight(highlighted),
|
||||
});
|
||||
}
|
||||
update();
|
||||
|
||||
const series = addSeries({
|
||||
colors: [topColor, bottomColor],
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLabeledInput, createSpanName } from "../utils/dom.js";
|
||||
import { stringToId } from "../utils/format.js";
|
||||
|
||||
/**
|
||||
* @param {Signals} signals
|
||||
*/
|
||||
export function createLegend(signals) {
|
||||
export function createLegend() {
|
||||
const element = window.document.createElement("legend");
|
||||
|
||||
/** @type {AnySeries | null} */
|
||||
@@ -60,15 +57,11 @@ export function createLegend(signals) {
|
||||
title: "Click to toggle",
|
||||
inputChecked: series.active(),
|
||||
onClick: () => {
|
||||
series.active.set(input.checked);
|
||||
series.setActive(input.checked);
|
||||
},
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
// Sync checkbox with signal (for shared signals across panes)
|
||||
signals.createEffect(series.active, (active) => {
|
||||
input.checked = active;
|
||||
});
|
||||
input.dataset.series = series.key;
|
||||
|
||||
const spanMain = window.document.createElement("span");
|
||||
spanMain.classList.add("main");
|
||||
@@ -94,17 +87,14 @@ export function createLegend(signals) {
|
||||
});
|
||||
seriesColorSpans.set(series, colorSpans);
|
||||
|
||||
const anchor = window.document.createElement("a");
|
||||
|
||||
signals.createEffect(series.url, (url) => {
|
||||
if (url) {
|
||||
anchor.href = url;
|
||||
anchor.target = "_blank";
|
||||
anchor.rel = "noopener noreferrer";
|
||||
anchor.title = "Click to view data";
|
||||
div.append(anchor);
|
||||
}
|
||||
});
|
||||
if (series.url) {
|
||||
const anchor = window.document.createElement("a");
|
||||
anchor.href = series.url;
|
||||
anchor.target = "_blank";
|
||||
anchor.rel = "noopener noreferrer";
|
||||
anchor.title = "Click to view data";
|
||||
div.append(anchor);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {number} start
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
*
|
||||
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupBasic, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint } from "./options/partial.js"
|
||||
*
|
||||
* @import { line as LineSeriesFn, dots as DotsSeriesFn, candlestick as CandlestickSeriesFn, baseline as BaselineSeriesFn, histogram as HistogramSeriesFn } from "./options/series.js"
|
||||
*
|
||||
* @import { UnitObject as Unit } from "./utils/units.js"
|
||||
*
|
||||
|
||||
+2
-12
@@ -14,6 +14,7 @@ import { init as initSimulation } from "./panes/_simulation.js";
|
||||
import { next } from "./utils/timing.js";
|
||||
import { replaceHistory } from "./utils/url.js";
|
||||
import { removeStored, writeToStorage } from "./utils/storage.js";
|
||||
import { dark } from "./utils/theme.js";
|
||||
import {
|
||||
asideElement,
|
||||
asideLabelElement,
|
||||
@@ -121,18 +122,6 @@ signals.createRoot(() => {
|
||||
|
||||
console.log(`VERSION = ${brk.VERSION}`);
|
||||
|
||||
function initDark() {
|
||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
const dark = signals.createSignal(preferredColorSchemeMatchMedia.matches);
|
||||
preferredColorSchemeMatchMedia.addEventListener("change", ({ matches }) => {
|
||||
dark.set(matches);
|
||||
});
|
||||
return dark;
|
||||
}
|
||||
const dark = initDark();
|
||||
|
||||
const qrcode = signals.createSignal(/** @type {string | null} */ (null));
|
||||
|
||||
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
|
||||
@@ -552,6 +541,7 @@ signals.createRoot(() => {
|
||||
qrcode.set(window.location.href);
|
||||
});
|
||||
|
||||
|
||||
shareDiv.addEventListener("click", () => {
|
||||
qrcode.set(null);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Chain section builder - typed tree-based patterns */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line, baseline, dots } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -12,9 +13,6 @@ export function createChainSection(ctx) {
|
||||
const {
|
||||
colors,
|
||||
brk,
|
||||
line,
|
||||
baseline,
|
||||
dots,
|
||||
createPriceLine,
|
||||
fromSizePattern,
|
||||
fromFullnessPattern,
|
||||
@@ -240,7 +238,7 @@ export function createChainSection(ctx) {
|
||||
name: "Volume",
|
||||
title: "Transaction Volume",
|
||||
bottom: [
|
||||
...satsBtcUsd(ctx, transactions.volume.sentSum, "Sent"),
|
||||
...satsBtcUsd( transactions.volume.sentSum, "Sent"),
|
||||
line({
|
||||
metric: transactions.volume.annualizedVolume.sats,
|
||||
name: "annualized",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import {
|
||||
createSingleSupplySeries,
|
||||
createGroupedSupplyTotalSeries,
|
||||
@@ -54,12 +55,12 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
{
|
||||
name: "in profit",
|
||||
title: `Supply In Profit ${title}`,
|
||||
bottom: createGroupedSupplyInProfitSeries(ctx, list),
|
||||
bottom: createGroupedSupplyInProfitSeries(list),
|
||||
},
|
||||
{
|
||||
name: "in loss",
|
||||
title: `Supply In Loss ${title}`,
|
||||
bottom: createGroupedSupplyInLossSeries(ctx, list),
|
||||
bottom: createGroupedSupplyInLossSeries(list),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -68,7 +69,7 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
{
|
||||
name: "utxo count",
|
||||
title: `UTXO Count ${title}`,
|
||||
bottom: createUtxoCountSeries(ctx, list, useGroupName),
|
||||
bottom: createUtxoCountSeries(list, useGroupName),
|
||||
},
|
||||
|
||||
// Address count (ADDRESS COHORTS ONLY - fully type safe!)
|
||||
@@ -87,7 +88,7 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
{
|
||||
name: "Price",
|
||||
title: `Realized Price ${title}`,
|
||||
top: createRealizedPriceSeries(ctx, list),
|
||||
top: createRealizedPriceSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
@@ -96,7 +97,6 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
},
|
||||
]
|
||||
: createRealizedPriceOptions(
|
||||
ctx,
|
||||
/** @type {AddressCohortObject} */ (args),
|
||||
title,
|
||||
)),
|
||||
@@ -119,24 +119,22 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
...createUnrealizedSection(ctx, list, useGroupName, title),
|
||||
|
||||
// Cost basis section (no percentiles for address cohorts)
|
||||
...createCostBasisSection(ctx, list, useGroupName, title),
|
||||
...createCostBasisSection(list, useGroupName, title),
|
||||
|
||||
// Activity section
|
||||
...createActivitySection(ctx, list, useGroupName, title),
|
||||
...createActivitySection(list, useGroupName, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create realized price options for single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {AddressCohortObject} args
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createRealizedPriceOptions(ctx, args, title) {
|
||||
const { line } = ctx;
|
||||
const { tree, color } = args;
|
||||
function createRealizedPriceOptions(args, title) {
|
||||
const { tree, color } = args;
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -163,7 +161,7 @@ function createRealizedPriceOptions(ctx, args, title) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
|
||||
const { line, baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
const isSingle = !("list" in args);
|
||||
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
@@ -196,7 +194,7 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createRealizedPnlSection(ctx, args, title) {
|
||||
const { colors, line } = ctx;
|
||||
const { colors } = ctx;
|
||||
const { realized } = args.tree;
|
||||
|
||||
return [
|
||||
@@ -251,7 +249,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
const { colors, line, baseline } = ctx;
|
||||
const { colors } = ctx;
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -301,15 +299,12 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section (no percentiles for address cohorts)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly AddressCohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createCostBasisSection(ctx, list, useGroupName, title) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createCostBasisSection(list, useGroupName, title) {
|
||||
return [
|
||||
{
|
||||
name: "Cost Basis",
|
||||
@@ -345,15 +340,12 @@ function createCostBasisSection(ctx, list, useGroupName, title) {
|
||||
|
||||
/**
|
||||
* Create activity section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly AddressCohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createActivitySection(ctx, list, useGroupName, title) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createActivitySection(list, useGroupName, title) {
|
||||
return [
|
||||
{
|
||||
name: "Activity",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Shared cohort chart section builders */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
|
||||
/**
|
||||
@@ -10,11 +11,11 @@ import { satsBtcUsd } from "../shared.js";
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleSupplySeries(ctx, cohort) {
|
||||
const { colors, line, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
return [
|
||||
...satsBtcUsd(ctx, tree.supply.total, "Supply", colors.default),
|
||||
...satsBtcUsd( tree.supply.total, "Supply", colors.default),
|
||||
...("supplyRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
@@ -25,9 +26,9 @@ export function createSingleSupplySeries(ctx, cohort) {
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...satsBtcUsd(ctx, tree.unrealized.supplyInProfit, "In Profit", colors.green),
|
||||
...satsBtcUsd(ctx, tree.unrealized.supplyInLoss, "In Loss", colors.red),
|
||||
...satsBtcUsd(ctx, tree.supply.halved, "half", colors.gray).map((s) => ({
|
||||
...satsBtcUsd( tree.unrealized.supplyInProfit, "In Profit", colors.green),
|
||||
...satsBtcUsd( tree.unrealized.supplyInLoss, "In Loss", colors.red),
|
||||
...satsBtcUsd( tree.supply.halved, "half", colors.gray).map((s) => ({
|
||||
...s,
|
||||
options: { lineStyle: 4 },
|
||||
})),
|
||||
@@ -76,11 +77,11 @@ export function createSingleSupplySeries(ctx, cohort) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyTotalSeries(ctx, list) {
|
||||
const { line, brk } = ctx;
|
||||
const { brk } = ctx;
|
||||
const constant100 = brk.metrics.constants.constant100;
|
||||
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
...satsBtcUsd(ctx, tree.supply.total, name, color),
|
||||
...satsBtcUsd( tree.supply.total, name, color),
|
||||
line({
|
||||
metric:
|
||||
"supplyRelToCirculatingSupply" in tree.relative
|
||||
@@ -95,15 +96,13 @@ export function createGroupedSupplyTotalSeries(ctx, list) {
|
||||
|
||||
/**
|
||||
* Create supply in profit series for grouped cohorts
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyInProfitSeries(ctx, list) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createGroupedSupplyInProfitSeries(list) {
|
||||
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
...satsBtcUsd(ctx, tree.unrealized.supplyInProfit, name, color),
|
||||
...satsBtcUsd( tree.unrealized.supplyInProfit, name, color),
|
||||
...("supplyInProfitRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
@@ -119,15 +118,13 @@ export function createGroupedSupplyInProfitSeries(ctx, list) {
|
||||
|
||||
/**
|
||||
* Create supply in loss series for grouped cohorts
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyInLossSeries(ctx, list) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createGroupedSupplyInLossSeries(list) {
|
||||
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
...satsBtcUsd(ctx, tree.unrealized.supplyInLoss, name, color),
|
||||
...satsBtcUsd( tree.unrealized.supplyInLoss, name, color),
|
||||
...("supplyInLossRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
@@ -143,14 +140,11 @@ export function createGroupedSupplyInLossSeries(ctx, list) {
|
||||
|
||||
/**
|
||||
* Create UTXO count series
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createUtxoCountSeries(ctx, list, useGroupName) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createUtxoCountSeries(list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.outputs.utxoCount,
|
||||
@@ -169,7 +163,7 @@ export function createUtxoCountSeries(ctx, list, useGroupName) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createAddressCountSeries(ctx, list, useGroupName) {
|
||||
const { line, colors } = ctx;
|
||||
const { colors } = ctx;
|
||||
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
@@ -183,13 +177,10 @@ export function createAddressCountSeries(ctx, list, useGroupName) {
|
||||
|
||||
/**
|
||||
* Create realized price series for grouped cohorts
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createRealizedPriceSeries(ctx, list) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createRealizedPriceSeries(list) {
|
||||
return list.map(({ color, name, tree }) =>
|
||||
line({ metric: tree.realized.realizedPrice, name, color, unit: Unit.usd }),
|
||||
);
|
||||
@@ -202,7 +193,7 @@ export function createRealizedPriceSeries(ctx, list) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createRealizedPriceRatioSeries(ctx, list) {
|
||||
const { line, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
|
||||
return [
|
||||
...list.map(({ color, name, tree }) =>
|
||||
@@ -219,14 +210,11 @@ export function createRealizedPriceRatioSeries(ctx, list) {
|
||||
|
||||
/**
|
||||
* Create realized capitalization series
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createRealizedCapSeries(ctx, list, useGroupName) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createRealizedCapSeries(list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.realizedCap,
|
||||
@@ -239,14 +227,11 @@ export function createRealizedCapSeries(ctx, list, useGroupName) {
|
||||
|
||||
/**
|
||||
* Create cost basis min/max series (available on all cohorts)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createCostBasisMinMaxSeries(ctx, list, useGroupName) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createCostBasisMinMaxSeries(list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.costBasis.min,
|
||||
@@ -265,14 +250,11 @@ export function createCostBasisMinMaxSeries(ctx, list, useGroupName) {
|
||||
|
||||
/**
|
||||
* Create cost basis percentile series (only for cohorts with CostBasisPattern2)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortWithCostBasisPercentiles[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createCostBasisPercentilesSeries(ctx, list, useGroupName) {
|
||||
const { line } = ctx;
|
||||
|
||||
export function createCostBasisPercentilesSeries(list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => {
|
||||
const percentiles = tree.costBasis.percentiles;
|
||||
return [
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
createCostBasisPercentilesSeries,
|
||||
} from "./shared.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
|
||||
// ============================================================================
|
||||
// Folder Builders (4 variants based on pattern capabilities)
|
||||
@@ -51,10 +52,10 @@ export function createCohortFolderAll(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
||||
createSingleUnrealizedSectionAll(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(args, title),
|
||||
...createSingleActivitySectionWithAdjusted(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -74,11 +75,11 @@ export function createCohortFolderFull(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(ctx, list, title),
|
||||
createGroupedUtxoCountChart(ctx, list, title),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionWithAdjusted(ctx, list, title),
|
||||
createGroupedUnrealizedSectionFull(ctx, list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(ctx, list, title),
|
||||
...createGroupedActivitySectionWithAdjusted(ctx, list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(list, title),
|
||||
...createGroupedActivitySectionWithAdjusted(list, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -87,10 +88,10 @@ export function createCohortFolderFull(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
||||
createSingleUnrealizedSectionFull(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(args, title),
|
||||
...createSingleActivitySectionWithAdjusted(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -110,11 +111,11 @@ export function createCohortFolderWithAdjusted(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(ctx, list, title),
|
||||
createGroupedUtxoCountChart(ctx, list, title),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionWithAdjusted(ctx, list, title),
|
||||
createGroupedUnrealizedSectionWithMarketCap(ctx, list, title),
|
||||
createGroupedCostBasisSection(ctx, list, title),
|
||||
...createGroupedActivitySectionWithAdjusted(ctx, list, title),
|
||||
createGroupedCostBasisSection(list, title),
|
||||
...createGroupedActivitySectionWithAdjusted(list, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -123,10 +124,10 @@ export function createCohortFolderWithAdjusted(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithMarketCap(ctx, args, title),
|
||||
createSingleCostBasisSection(ctx, args, title),
|
||||
createSingleCostBasisSection(args, title),
|
||||
...createSingleActivitySectionWithAdjusted(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -146,11 +147,11 @@ export function createCohortFolderWithPercentiles(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(ctx, list, title),
|
||||
createGroupedUtxoCountChart(ctx, list, title),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionBasic(ctx, list, title),
|
||||
createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(ctx, list, title),
|
||||
...createGroupedActivitySectionBasic(ctx, list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(list, title),
|
||||
...createGroupedActivitySectionBasic(list, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -159,10 +160,10 @@ export function createCohortFolderWithPercentiles(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionBasic(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithOwnCaps(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(args, title),
|
||||
...createSingleActivitySectionBasic(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -182,11 +183,11 @@ export function createCohortFolderBasic(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(ctx, list, title),
|
||||
createGroupedUtxoCountChart(ctx, list, title),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionBasic(ctx, list, title),
|
||||
createGroupedUnrealizedSectionBase(ctx, list, title),
|
||||
createGroupedCostBasisSection(ctx, list, title),
|
||||
...createGroupedActivitySectionBasic(ctx, list, title),
|
||||
createGroupedCostBasisSection(list, title),
|
||||
...createGroupedActivitySectionBasic(list, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -195,10 +196,10 @@ export function createCohortFolderBasic(ctx, args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionBasic(ctx, args, title),
|
||||
createSingleUnrealizedSectionBase(ctx, args, title),
|
||||
createSingleCostBasisSection(ctx, args, title),
|
||||
createSingleCostBasisSection(args, title),
|
||||
...createSingleActivitySectionBasic(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -238,12 +239,12 @@ function createGroupedSupplySection(ctx, list, title) {
|
||||
{
|
||||
name: "in profit",
|
||||
title: `Supply In Profit ${title}`,
|
||||
bottom: createGroupedSupplyInProfitSeries(ctx, list),
|
||||
bottom: createGroupedSupplyInProfitSeries(list),
|
||||
},
|
||||
{
|
||||
name: "in loss",
|
||||
title: `Supply In Loss ${title}`,
|
||||
bottom: createGroupedSupplyInLossSeries(ctx, list),
|
||||
bottom: createGroupedSupplyInLossSeries(list),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -251,31 +252,29 @@ function createGroupedSupplySection(ctx, list, title) {
|
||||
|
||||
/**
|
||||
* Create UTXO count chart for single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {UtxoCohortObject} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createSingleUtxoCountChart(ctx, cohort, title) {
|
||||
function createSingleUtxoCountChart(cohort, title) {
|
||||
return {
|
||||
name: "utxo count",
|
||||
title: `UTXO Count ${title}`,
|
||||
bottom: createUtxoCountSeries(ctx, [cohort], false),
|
||||
bottom: createUtxoCountSeries( [cohort], false),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create UTXO count chart for grouped cohorts
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly UtxoCohortObject[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createGroupedUtxoCountChart(ctx, list, title) {
|
||||
function createGroupedUtxoCountChart(list, title) {
|
||||
return {
|
||||
name: "utxo count",
|
||||
title: `UTXO Count ${title}`,
|
||||
bottom: createUtxoCountSeries(ctx, list, true),
|
||||
bottom: createUtxoCountSeries( list, true),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -290,7 +289,7 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) {
|
||||
return {
|
||||
name: "Realized",
|
||||
tree: [
|
||||
createSingleRealizedPriceChart(ctx, cohort, title),
|
||||
createSingleRealizedPriceChart(cohort, title),
|
||||
{
|
||||
name: "capitalization",
|
||||
title: `Realized Capitalization ${title}`,
|
||||
@@ -316,7 +315,7 @@ function createGroupedRealizedSectionWithAdjusted(ctx, list, title) {
|
||||
{
|
||||
name: "Price",
|
||||
title: `Realized Price ${title}`,
|
||||
top: createRealizedPriceSeries(ctx, list),
|
||||
top: createRealizedPriceSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
@@ -326,7 +325,7 @@ function createGroupedRealizedSectionWithAdjusted(ctx, list, title) {
|
||||
{
|
||||
name: "capitalization",
|
||||
title: `Realized Capitalization ${title}`,
|
||||
bottom: createGroupedRealizedCapSeries(ctx, list),
|
||||
bottom: createGroupedRealizedCapSeries(list),
|
||||
},
|
||||
...createGroupedRealizedPnlSections(ctx, list, title),
|
||||
createGroupedSoprSectionWithAdjusted(ctx, list, title),
|
||||
@@ -345,7 +344,7 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) {
|
||||
return {
|
||||
name: "Realized",
|
||||
tree: [
|
||||
createSingleRealizedPriceChart(ctx, cohort, title),
|
||||
createSingleRealizedPriceChart(cohort, title),
|
||||
{
|
||||
name: "capitalization",
|
||||
title: `Realized Capitalization ${title}`,
|
||||
@@ -371,7 +370,7 @@ function createGroupedRealizedSectionBasic(ctx, list, title) {
|
||||
{
|
||||
name: "Price",
|
||||
title: `Realized Price ${title}`,
|
||||
top: createRealizedPriceSeries(ctx, list),
|
||||
top: createRealizedPriceSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
@@ -381,7 +380,7 @@ function createGroupedRealizedSectionBasic(ctx, list, title) {
|
||||
{
|
||||
name: "capitalization",
|
||||
title: `Realized Capitalization ${title}`,
|
||||
bottom: createGroupedRealizedCapSeries(ctx, list),
|
||||
bottom: createGroupedRealizedCapSeries(list),
|
||||
},
|
||||
...createGroupedRealizedPnlSections(ctx, list, title),
|
||||
createGroupedSoprSectionBasic(ctx, list, title),
|
||||
@@ -391,14 +390,12 @@ function createGroupedRealizedSectionBasic(ctx, list, title) {
|
||||
|
||||
/**
|
||||
* Create realized price chart for single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {UtxoCohortObject} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createSingleRealizedPriceChart(ctx, cohort, title) {
|
||||
const { line } = ctx;
|
||||
const { tree, color } = cohort;
|
||||
function createSingleRealizedPriceChart(cohort, title) {
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return {
|
||||
name: "price",
|
||||
@@ -421,7 +418,7 @@ function createSingleRealizedPriceChart(ctx, cohort, title) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingleRealizedCapSeries(ctx, cohort) {
|
||||
const { colors, line, baseline, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
const { color, tree } = cohort;
|
||||
|
||||
return [
|
||||
@@ -459,13 +456,10 @@ function createSingleRealizedCapSeries(ctx, cohort) {
|
||||
|
||||
/**
|
||||
* Create realized cap series for grouped cohorts
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly UtxoCohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createGroupedRealizedCapSeries(ctx, list) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createGroupedRealizedCapSeries(list) {
|
||||
return list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedCap,
|
||||
@@ -486,8 +480,6 @@ function createGroupedRealizedCapSeries(ctx, list) {
|
||||
function createSingleRealizedPnlSection(ctx, cohort, title) {
|
||||
const {
|
||||
colors,
|
||||
line,
|
||||
baseline,
|
||||
createPriceLine,
|
||||
fromBlockCountWithUnit,
|
||||
fromBitcoinPatternWithUnit,
|
||||
@@ -598,7 +590,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title) {
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedRealizedPnlSections(ctx, list, title) {
|
||||
const { line, baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -795,7 +787,7 @@ function createGroupedRealizedPnlSections(ctx, list, title) {
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createSingleBaseSoprChart(ctx, cohort, title) {
|
||||
const { colors, baseline, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
return {
|
||||
@@ -837,7 +829,7 @@ function createSingleBaseSoprChart(ctx, cohort, title) {
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createSingleAdjustedSoprChart(ctx, cohort, title) {
|
||||
const { colors, baseline, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
return {
|
||||
@@ -880,7 +872,7 @@ function createSingleAdjustedSoprChart(ctx, cohort, title) {
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createGroupedBaseSoprChart(ctx, list, title) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Normal",
|
||||
@@ -924,7 +916,7 @@ function createGroupedBaseSoprChart(ctx, list, title) {
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createGroupedAdjustedSoprChart(ctx, list, title) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Adjusted",
|
||||
@@ -1035,7 +1027,7 @@ function createGroupedSoprSectionBasic(ctx, list, title) {
|
||||
* @param {RelativeWithMarketCap} rel
|
||||
*/
|
||||
function createUnrealizedPnlRelToMarketCapMetrics(ctx, rel) {
|
||||
const { colors, line } = ctx;
|
||||
const { colors } = ctx;
|
||||
return [
|
||||
line({
|
||||
metric: rel.unrealizedProfitRelToMarketCap,
|
||||
@@ -1064,7 +1056,7 @@ function createUnrealizedPnlRelToMarketCapMetrics(ctx, rel) {
|
||||
* @param {RelativeWithOwnMarketCap} rel
|
||||
*/
|
||||
function createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, rel) {
|
||||
const { colors, line, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
return [
|
||||
line({
|
||||
metric: rel.unrealizedProfitRelToOwnMarketCap,
|
||||
@@ -1095,7 +1087,7 @@ function createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, rel) {
|
||||
* @param {RelativeWithOwnPnl} rel
|
||||
*/
|
||||
function createUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
||||
const { colors, line, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
return [
|
||||
line({
|
||||
metric: rel.unrealizedProfitRelToOwnTotalUnrealizedPnl,
|
||||
@@ -1122,12 +1114,10 @@ function createUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PartialContext} ctx
|
||||
* @param {RelativeWithMarketCap} rel
|
||||
*/
|
||||
function createNetUnrealizedPnlRelToMarketCapMetrics(ctx, rel) {
|
||||
const { baseline } = ctx;
|
||||
return [
|
||||
function createNetUnrealizedPnlRelToMarketCapMetrics(rel) {
|
||||
return [
|
||||
baseline({
|
||||
metric: rel.netUnrealizedPnlRelToMarketCap,
|
||||
name: "Net",
|
||||
@@ -1141,7 +1131,7 @@ function createNetUnrealizedPnlRelToMarketCapMetrics(ctx, rel) {
|
||||
* @param {RelativeWithOwnMarketCap} rel
|
||||
*/
|
||||
function createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, rel) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
return [
|
||||
baseline({
|
||||
metric: rel.netUnrealizedPnlRelToOwnMarketCap,
|
||||
@@ -1157,7 +1147,7 @@ function createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, rel) {
|
||||
* @param {RelativeWithOwnPnl} rel
|
||||
*/
|
||||
function createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
return [
|
||||
baseline({
|
||||
metric: rel.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
||||
@@ -1174,7 +1164,7 @@ function createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
||||
* @param {{ unrealized: { totalUnrealizedPnl: AnyMetricPattern, unrealizedProfit: AnyMetricPattern, unrealizedLoss: AnyMetricPattern, negUnrealizedLoss: AnyMetricPattern } }} tree
|
||||
*/
|
||||
function createUnrealizedPnlBaseMetrics(ctx, tree) {
|
||||
const { colors, line } = ctx;
|
||||
const { colors } = ctx;
|
||||
return [
|
||||
line({
|
||||
metric: tree.unrealized.totalUnrealizedPnl,
|
||||
@@ -1206,12 +1196,10 @@ function createUnrealizedPnlBaseMetrics(ctx, tree) {
|
||||
|
||||
/**
|
||||
* Base net unrealized metric (always present)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {{ unrealized: { netUnrealizedPnl: AnyMetricPattern } }} tree
|
||||
*/
|
||||
function createNetUnrealizedPnlBaseMetric(ctx, tree) {
|
||||
const { baseline } = ctx;
|
||||
return baseline({
|
||||
function createNetUnrealizedPnlBaseMetric(tree) {
|
||||
return baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name: "Net",
|
||||
unit: Unit.ratio,
|
||||
@@ -1249,7 +1237,7 @@ function createSingleUnrealizedSectionAll(ctx, cohort, title) {
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(ctx, tree),
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
createPriceLine({ unit: Unit.usd }),
|
||||
],
|
||||
@@ -1287,8 +1275,8 @@ function createSingleUnrealizedSectionFull(ctx, cohort, title) {
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(ctx, tree),
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
createPriceLine({ unit: Unit.usd }),
|
||||
@@ -1326,8 +1314,8 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(ctx, tree),
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
createPriceLine({ unit: Unit.usd }),
|
||||
createPriceLine({ unit: Unit.pctMcap }),
|
||||
],
|
||||
@@ -1364,7 +1352,7 @@ function createSingleUnrealizedSectionWithOwnCaps(ctx, cohort, title) {
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(ctx, tree),
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
||||
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
||||
createPriceLine({ unit: Unit.usd }),
|
||||
@@ -1400,7 +1388,7 @@ function createSingleUnrealizedSectionBase(ctx, cohort, title) {
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(ctx, tree),
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
createPriceLine({ unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
@@ -1410,13 +1398,11 @@ function createSingleUnrealizedSectionBase(ctx, cohort, title) {
|
||||
|
||||
/**
|
||||
* Grouped unrealized base charts (profit, loss, total pnl)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly { color: Color, name: string, tree: { unrealized: PatternAll["unrealized"] } }[]} list
|
||||
* @param {string} title
|
||||
*/
|
||||
function createGroupedUnrealizedBaseCharts(ctx, list, title) {
|
||||
const { line } = ctx;
|
||||
return [
|
||||
function createGroupedUnrealizedBaseCharts(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "profit",
|
||||
title: `Unrealized Profit ${title}`,
|
||||
@@ -1464,11 +1450,11 @@ function createGroupedUnrealizedBaseCharts(ctx, list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSectionFull(ctx, list, title) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(ctx, list, title),
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
@@ -1517,11 +1503,11 @@ function createGroupedUnrealizedSectionFull(ctx, list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(ctx, list, title),
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
@@ -1556,11 +1542,11 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(ctx, list, title),
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
@@ -1602,11 +1588,11 @@ function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSectionBase(ctx, list, title) {
|
||||
const { baseline, createPriceLine } = ctx;
|
||||
const { createPriceLine } = ctx;
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(ctx, list, title),
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized Profit And Loss ${title}`,
|
||||
@@ -1628,14 +1614,12 @@ function createGroupedUnrealizedSectionBase(ctx, list, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for single cohort WITH percentiles
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) {
|
||||
const { line } = ctx;
|
||||
const { color, tree } = cohort;
|
||||
function createSingleCostBasisSectionWithPercentiles(cohort, title) {
|
||||
const { color, tree } = cohort;
|
||||
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
@@ -1668,7 +1652,7 @@ function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) {
|
||||
{
|
||||
name: "percentiles",
|
||||
title: `Cost Basis Percentiles ${title}`,
|
||||
top: createCostBasisPercentilesSeries(ctx, [cohort], false),
|
||||
top: createCostBasisPercentilesSeries( [cohort], false),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1676,14 +1660,11 @@ function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for grouped cohorts WITH percentiles
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortFull | CohortWithPercentiles)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createGroupedCostBasisSectionWithPercentiles(list, title) {
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
tree: [
|
||||
@@ -1719,14 +1700,12 @@ function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for single cohort (no percentiles)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithAdjusted | CohortBasic} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleCostBasisSection(ctx, cohort, title) {
|
||||
const { line } = ctx;
|
||||
const { color, tree } = cohort;
|
||||
function createSingleCostBasisSection(cohort, title) {
|
||||
const { color, tree } = cohort;
|
||||
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
@@ -1762,14 +1741,11 @@ function createSingleCostBasisSection(ctx, cohort, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for grouped cohorts (no percentiles)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortWithAdjusted | CohortBasic)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedCostBasisSection(ctx, list, title) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createGroupedCostBasisSection(list, title) {
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
tree: [
|
||||
@@ -1811,7 +1787,7 @@ function createGroupedCostBasisSection(ctx, list, title) {
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
||||
const { colors, line } = ctx;
|
||||
const { colors } = ctx;
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return [
|
||||
@@ -1925,7 +1901,7 @@ function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createSingleActivitySectionBasic(ctx, cohort, title) {
|
||||
const { colors, line } = ctx;
|
||||
const { colors } = ctx;
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return [
|
||||
@@ -2021,14 +1997,11 @@ function createSingleActivitySectionBasic(ctx, cohort, title) {
|
||||
|
||||
/**
|
||||
* Create activity section for grouped cohorts with adjusted values (for cohorts with RealizedPattern3/4)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortFull | CohortWithAdjusted)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedActivitySectionWithAdjusted(ctx, list, title) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createGroupedActivitySectionWithAdjusted(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
@@ -2151,14 +2124,11 @@ function createGroupedActivitySectionWithAdjusted(ctx, list, title) {
|
||||
|
||||
/**
|
||||
* Create activity section for grouped cohorts without adjusted values (for cohorts with RealizedPattern/2)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedActivitySectionBasic(ctx, list, title) {
|
||||
const { line } = ctx;
|
||||
|
||||
function createGroupedActivitySectionBasic(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Cointime section builder - typed tree-based patterns */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line, baseline } from "./series.js";
|
||||
import {
|
||||
satsBtcUsd,
|
||||
priceLines,
|
||||
@@ -25,7 +26,7 @@ function createCointimePriceWithRatioOptions(
|
||||
ctx,
|
||||
{ title, legend, price, ratio, color },
|
||||
) {
|
||||
const { line, colors, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
|
||||
const pctUsdMap = percentileUsdMap(colors, ratio);
|
||||
const pctMap = percentileMap(colors, ratio);
|
||||
@@ -54,42 +55,53 @@ function createCointimePriceWithRatioOptions(
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: ratio.ratio, name: "Ratio", color, unit: Unit.ratio }),
|
||||
baseline({
|
||||
metric: ratio.ratio,
|
||||
name: "Ratio",
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1wSma,
|
||||
name: "1w SMA",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1mSma,
|
||||
name: "1m SMA",
|
||||
color: colors.teal,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1ySd.sma,
|
||||
name: "1y SMA",
|
||||
color: colors.sky,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio2ySd.sma,
|
||||
name: "2y SMA",
|
||||
color: colors.indigo,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio4ySd.sma,
|
||||
name: "4y SMA",
|
||||
color: colors.purple,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratioSd.sma,
|
||||
name: "All SMA",
|
||||
color: colors.rose,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...pctMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
line({
|
||||
@@ -174,13 +186,14 @@ function createCointimePriceWithRatioOptions(
|
||||
...sdPats.map(({ nameAddon, titleAddon, sd }) => ({
|
||||
name: nameAddon,
|
||||
title: `${title} ${titleAddon} Z-Score`,
|
||||
top: sdBands(colors, sd).map(({ name: bandName, prop, color: bandColor }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: bandName,
|
||||
color: bandColor,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
top: sdBands(colors, sd).map(
|
||||
({ name: bandName, prop, color: bandColor }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: bandName,
|
||||
color: bandColor,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
bottom: [
|
||||
line({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
|
||||
@@ -198,7 +211,7 @@ function createCointimePriceWithRatioOptions(
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCointimeSection(ctx) {
|
||||
const { colors, brk, line } = ctx;
|
||||
const { colors, brk } = ctx;
|
||||
const { cointime, distribution, supply } = brk.metrics;
|
||||
const { pricing, cap, activity, supply: cointimeSupply, adjusted } = cointime;
|
||||
const { all } = distribution.utxoCohorts;
|
||||
@@ -348,9 +361,17 @@ export function createCointimeSection(ctx) {
|
||||
name: "Supply",
|
||||
title: "Cointime Supply",
|
||||
bottom: [
|
||||
...satsBtcUsd(ctx, all.supply.total, "All", colors.orange),
|
||||
...satsBtcUsd(ctx, cointimeSupply.vaultedSupply, "Vaulted", colors.lime),
|
||||
...satsBtcUsd(ctx, cointimeSupply.activeSupply, "Active", colors.rose),
|
||||
...satsBtcUsd( all.supply.total, "All", colors.orange),
|
||||
...satsBtcUsd(
|
||||
cointimeSupply.vaultedSupply,
|
||||
"Vaulted",
|
||||
colors.lime,
|
||||
),
|
||||
...satsBtcUsd(
|
||||
cointimeSupply.activeSupply,
|
||||
"Active",
|
||||
colors.rose,
|
||||
),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
line,
|
||||
dots,
|
||||
candlestick,
|
||||
baseline,
|
||||
histogram,
|
||||
fromBlockCount,
|
||||
fromBitcoin,
|
||||
fromBlockSize,
|
||||
@@ -37,12 +32,6 @@ export function createContext({ colors, brk }) {
|
||||
colors,
|
||||
brk,
|
||||
|
||||
// Series helpers
|
||||
line,
|
||||
dots,
|
||||
candlestick,
|
||||
baseline,
|
||||
histogram,
|
||||
fromBlockCount: (pattern, title, color) =>
|
||||
fromBlockCount(colors, pattern, title, color),
|
||||
fromBitcoin: (pattern, title, color) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Moving averages section */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import {
|
||||
priceLines,
|
||||
percentileUsdMap,
|
||||
@@ -55,7 +56,7 @@ export function createPriceWithRatioOptions(
|
||||
ctx,
|
||||
{ title, legend, ratio, color },
|
||||
) {
|
||||
const { line, colors, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
const priceMetric = ratio.price;
|
||||
|
||||
const pctUsdMap = percentileUsdMap(colors, ratio);
|
||||
@@ -85,42 +86,54 @@ export function createPriceWithRatioOptions(
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: ratio.ratio, name: "Ratio", color, unit: Unit.ratio }),
|
||||
baseline({
|
||||
metric: ratio.ratio,
|
||||
name: "Ratio",
|
||||
base: 1,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1wSma,
|
||||
name: "1w SMA",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1mSma,
|
||||
name: "1m SMA",
|
||||
color: colors.teal,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1ySd.sma,
|
||||
name: "1y SMA",
|
||||
color: colors.sky,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio2ySd.sma,
|
||||
name: "2y SMA",
|
||||
color: colors.indigo,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio4ySd.sma,
|
||||
name: "4y SMA",
|
||||
color: colors.purple,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratioSd.sma,
|
||||
name: "All SMA",
|
||||
color: colors.rose,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...pctMap.map(({ name: pctName, prop, color: pctColor }) =>
|
||||
line({
|
||||
@@ -140,13 +153,14 @@ export function createPriceWithRatioOptions(
|
||||
tree: sdPats.map(({ nameAddon, titleAddon, sd }) => ({
|
||||
name: nameAddon,
|
||||
title: `${title} ${titleAddon} Z-Score`,
|
||||
top: sdBands(colors, sd).map(({ name: bandName, prop, color: bandColor }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: bandName,
|
||||
color: bandColor,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
top: sdBands(colors, sd).map(
|
||||
({ name: bandName, prop, color: bandColor }) =>
|
||||
line({
|
||||
metric: prop,
|
||||
name: bandName,
|
||||
color: bandColor,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
bottom: [
|
||||
line({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
|
||||
@@ -163,8 +177,7 @@ export function createPriceWithRatioOptions(
|
||||
* @param {ReturnType<typeof buildAverages>} averages
|
||||
*/
|
||||
export function createAveragesSection(ctx, averages) {
|
||||
const { line } = ctx;
|
||||
|
||||
|
||||
return {
|
||||
name: "Averages",
|
||||
tree: [
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { localhost } from "../../utils/env.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line } from "../series.js";
|
||||
import { buildAverages, createAveragesSection } from "./averages.js";
|
||||
import { createPerformanceSection } from "./performance.js";
|
||||
import { createIndicatorsSection } from "./indicators/index.js";
|
||||
@@ -13,7 +14,7 @@ import { createInvestingSection } from "./investing.js";
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk, line } = ctx;
|
||||
const { colors, brk } = ctx;
|
||||
const { market, supply, price } = brk.metrics;
|
||||
const {
|
||||
movingAverage,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Bands indicators (MinMax, Mayer Multiple) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { line } from "../../series.js";
|
||||
|
||||
/**
|
||||
* Create Bands section
|
||||
@@ -10,7 +11,7 @@ import { Unit } from "../../../utils/units.js";
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
*/
|
||||
export function createBandsSection(ctx, { range, movingAverage }) {
|
||||
const { line, colors } = ctx;
|
||||
const { colors } = ctx;
|
||||
|
||||
return {
|
||||
name: "Bands",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { line, histogram } from "../../series.js";
|
||||
|
||||
/**
|
||||
* Create Momentum section
|
||||
@@ -8,7 +9,7 @@ import { Unit } from "../../../utils/units.js";
|
||||
* @param {Market["indicators"]} indicators
|
||||
*/
|
||||
export function createMomentumSection(ctx, indicators) {
|
||||
const { line, histogram, colors, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Momentum",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { line } from "../../series.js";
|
||||
|
||||
/**
|
||||
* Create On-chain section
|
||||
@@ -10,7 +11,7 @@ import { Unit } from "../../../utils/units.js";
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
*/
|
||||
export function createOnchainSection(ctx, { indicators, movingAverage }) {
|
||||
const { line, colors, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "On-chain",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
|
||||
|
||||
import { Unit } from "../../../utils/units.js";
|
||||
import { line } from "../../series.js";
|
||||
|
||||
/**
|
||||
* Create Volatility section
|
||||
@@ -10,7 +11,7 @@ import { Unit } from "../../../utils/units.js";
|
||||
* @param {Market["range"]} args.range
|
||||
*/
|
||||
export function createVolatilitySection(ctx, { volatility, range }) {
|
||||
const { line, colors, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Volatility",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Investing section (DCA) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
@@ -41,7 +42,7 @@ export function buildDcaClasses(colors, dca) {
|
||||
* @param {Market["returns"]} args.returns
|
||||
*/
|
||||
export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
const { line, baseline, colors, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
const dcaClasses = buildDcaClasses(colors, dca);
|
||||
|
||||
return {
|
||||
@@ -114,8 +115,8 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
name: "Stack",
|
||||
title: `${name} DCA vs Lump Sum Stack ($100/day)`,
|
||||
bottom: [
|
||||
...satsBtcUsd(ctx, dcaStack, "DCA", colors.green),
|
||||
...satsBtcUsd(ctx, lumpSumStack, "Lump sum", colors.orange),
|
||||
...satsBtcUsd( dcaStack, "DCA", colors.green),
|
||||
...satsBtcUsd( lumpSumStack, "Lump sum", colors.orange),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -164,7 +165,7 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
title: "DCA Stack by Year ($100/day)",
|
||||
bottom: dcaClasses.flatMap(
|
||||
({ year, color, defaultActive, stack }) =>
|
||||
satsBtcUsd(ctx, stack, `${year}`, color, { defaultActive }),
|
||||
satsBtcUsd( stack, `${year}`, color, { defaultActive }),
|
||||
),
|
||||
},
|
||||
],
|
||||
@@ -200,7 +201,7 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
{
|
||||
name: "Stack",
|
||||
title: `DCA Class ${year} Stack ($100/day)`,
|
||||
bottom: satsBtcUsd(ctx, stack, "Stack", color),
|
||||
bottom: satsBtcUsd( stack, "Stack", color),
|
||||
},
|
||||
],
|
||||
})),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** Performance section */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { baseline } from "../series.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
@@ -9,7 +10,7 @@ import { periodIdToName } from "./utils.js";
|
||||
* @param {Market["returns"]} returns
|
||||
*/
|
||||
export function createPerformanceSection(ctx, returns) {
|
||||
const { colors, baseline, createPriceLine } = ctx;
|
||||
const { colors, createPriceLine } = ctx;
|
||||
|
||||
return {
|
||||
name: "Performance",
|
||||
|
||||
@@ -85,6 +85,8 @@ export function candlestick({
|
||||
* @param {Unit} args.unit
|
||||
* @param {Color | [Color, Color]} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {number | undefined} [args.base]
|
||||
* @param {BaselineSeriesPartialOptions} [args.options]
|
||||
* @returns {FetchedBaselineSeriesBlueprint}
|
||||
*/
|
||||
@@ -94,6 +96,7 @@ export function baseline({
|
||||
color,
|
||||
defaultActive,
|
||||
unit,
|
||||
base,
|
||||
options,
|
||||
}) {
|
||||
const isTuple = Array.isArray(color);
|
||||
@@ -105,7 +108,12 @@ export function baseline({
|
||||
colors: isTuple ? color : undefined,
|
||||
unit,
|
||||
defaultActive,
|
||||
options,
|
||||
options: {
|
||||
baseValue: {
|
||||
price: base,
|
||||
},
|
||||
...options,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/** Shared helpers for options */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line } from "./series.js";
|
||||
|
||||
/**
|
||||
* Create sats/btc/usd line series from a pattern with .sats/.bitcoin/.dollars
|
||||
* @param {PartialContext} ctx
|
||||
* @param {{ sats: AnyMetricPattern, bitcoin: AnyMetricPattern, dollars: AnyMetricPattern }} pattern
|
||||
* @param {string} name
|
||||
* @param {Color} [color]
|
||||
* @param {{ defaultActive?: boolean }} [options]
|
||||
* @returns {FetchedLineSeriesBlueprint[]}
|
||||
*/
|
||||
export function satsBtcUsd(ctx, pattern, name, color, options) {
|
||||
export function satsBtcUsd(pattern, name, color, options) {
|
||||
const { defaultActive } = options || {};
|
||||
return [
|
||||
ctx.line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
|
||||
ctx.line({ metric: pattern.bitcoin, name, color, unit: Unit.btc, defaultActive }),
|
||||
ctx.line({ metric: pattern.dollars, name, color, unit: Unit.usd, defaultActive }),
|
||||
line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
|
||||
line({ metric: pattern.bitcoin, name, color, unit: Unit.btc, defaultActive }),
|
||||
line({ metric: pattern.dollars, name, color, unit: Unit.usd, defaultActive }),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -241,11 +241,6 @@
|
||||
* @typedef {Object} PartialContext
|
||||
* @property {Colors} colors
|
||||
* @property {BrkClient} brk
|
||||
* @property {LineSeriesFn} line
|
||||
* @property {DotsSeriesFn} dots
|
||||
* @property {CandlestickSeriesFn} candlestick
|
||||
* @property {BaselineSeriesFn} baseline
|
||||
* @property {HistogramSeriesFn} histogram
|
||||
* @property {(pattern: BlockCountPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCount
|
||||
* @property {(pattern: FullnessPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
|
||||
* @property {(pattern: AnyStatsPattern, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import signals from "../signals.js";
|
||||
import { readStored, removeStored, writeToStorage } from "./storage.js";
|
||||
|
||||
const preferredColorSchemeMatchMedia = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
const stored = readStored("theme");
|
||||
const initial = stored ? stored === "dark" : preferredColorSchemeMatchMedia.matches;
|
||||
|
||||
export const dark = signals.createSignal(initial);
|
||||
|
||||
/** @param {boolean} isDark */
|
||||
function apply(isDark) {
|
||||
document.documentElement.style.colorScheme = isDark ? "dark" : "light";
|
||||
}
|
||||
apply(initial);
|
||||
|
||||
preferredColorSchemeMatchMedia.addEventListener("change", ({ matches }) => {
|
||||
if (!readStored("theme")) {
|
||||
dark.set(matches);
|
||||
apply(matches);
|
||||
}
|
||||
});
|
||||
|
||||
function invert() {
|
||||
const newValue = !dark();
|
||||
dark.set(newValue);
|
||||
apply(newValue);
|
||||
if (newValue === preferredColorSchemeMatchMedia.matches) {
|
||||
removeStored("theme");
|
||||
} else {
|
||||
writeToStorage("theme", newValue ? "dark" : "light");
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("invert-button")?.addEventListener("click", invert);
|
||||
Reference in New Issue
Block a user