bitview: reorg part 9

This commit is contained in:
nym21
2025-10-01 23:17:48 +02:00
parent 62d4b35c93
commit c4ce718bb2
102 changed files with 1654 additions and 1798 deletions
@@ -2,9 +2,11 @@ import {
createShadow,
createHorizontalChoiceField,
createHeader,
} from "../core/dom";
import { serdeChartableIndex, serdeOptNumber } from "../core/serde";
import { throttle } from "../core/timing";
} from "../../core/dom";
import { chartElement } from "../../core/elements";
import { ios, canShare } from "../../core/env";
import { serdeChartableIndex, serdeOptNumber } from "../../core/serde";
import { throttle } from "../../core/timing";
const keyPrefix = "chart";
const ONE_BTC_IN_SATS = 100_000_000;
@@ -13,7 +15,7 @@ const LINE = "line";
const CANDLE = "candle";
/**
* @typedef {"timestamp" | "date" | "week" | "epoch" | "month" | "quarter" | "semester" | "year" | "decade" } SerializedChartableIndex
* @typedef {"timestamp" | "date" | "week" | "epoch" | "month" | "quarter" | "semester" | "year" | "decade" } ChartableIndexName
*/
/**
@@ -22,38 +24,29 @@ const CANDLE = "candle";
* @param {CreateChartElement} args.createChartElement
* @param {Accessor<ChartOption>} args.option
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {WebSockets} args.webSockets
* @param {Elements} args.elements
* @param {Env} args.env
* @param {VecsResources} args.vecsResources
* @param {MetricToIndexes} args.metricToIndexes
* @param {Packages} args.packages
* @param {Resources} args.resources
* @param {BRK} args.brk
*/
export function init({
colors,
elements,
createChartElement,
option,
signals,
utils,
env,
webSockets,
vecsResources,
metricToIndexes,
packages,
resources,
brk,
}) {
elements.charts.append(createShadow("left"));
elements.charts.append(createShadow("right"));
chartElement.append(createShadow("left"));
chartElement.append(createShadow("right"));
const { headerElement, headingElement } = createHeader();
elements.charts.append(headerElement);
chartElement.append(headerElement);
const { index, fieldset } = createIndexSelector({
option,
metricToIndexes,
brk,
signals,
utils,
});
const TIMERANGE_LS_KEY = signals.createMemo(
@@ -80,13 +73,11 @@ export function init({
});
const chart = createChartElement({
parent: elements.charts,
parent: chartElement,
signals,
colors,
id: "charts",
utils,
vecsResources,
elements,
resources,
index,
timeScaleSetCallback: (unknownTimeScaleCallback) => {
// TODO: Although it mostly works in practice, need to make it more robust, there is no guarantee that this runs in order and wait for `from` and `to` to update when `index` and thus `TIMERANGE_LS_KEY` is updated
@@ -105,12 +96,11 @@ export function init({
},
});
if (!(env.ios && !("canShare" in navigator))) {
if (!(ios && !canShare)) {
const chartBottomRightCanvas = Array.from(
chart.inner.chartElement().getElementsByTagName("tr"),
).at(-1)?.lastChild?.firstChild?.firstChild;
if (chartBottomRightCanvas) {
const charts = elements.charts;
const domain = window.document.createElement("p");
domain.innerText = `${window.location.host}`;
domain.id = "domain";
@@ -121,20 +111,20 @@ export function init({
screenshotButton.title = "Screenshot";
chartBottomRightCanvas.replaceWith(screenshotButton);
screenshotButton.addEventListener("click", () => {
packages.modernScreenshot().then(async ({ screenshot }) => {
charts.dataset.screenshot = "true";
charts.append(domain);
import("./screenshot").then(async ({ screenshot }) => {
chartElement.dataset.screenshot = "true";
chartElement.append(domain);
seriesTypeField.hidden = true;
try {
await screenshot({
element: charts,
element: chartElement,
name: option().path.join("-"),
title: option().title,
});
} catch {}
charts.removeChild(domain);
chartElement.removeChild(domain);
seriesTypeField.hidden = false;
charts.dataset.screenshot = "false";
chartElement.dataset.screenshot = "false";
});
});
}
@@ -148,7 +138,7 @@ export function init({
}, 250),
);
elements.charts.append(fieldset);
chartElement.append(fieldset);
const { field: seriesTypeField, selected: topSeriesType_ } =
createHorizontalChoiceField({
@@ -205,7 +195,7 @@ export function init({
* @param {Object} params
* @param {ISeries} params.iseries
* @param {Unit} params.unit
* @param {Index} params.index
* @param {IndexName} params.index
*/
function printLatest({ iseries, unit, index }) {
const _latest = webSockets.kraken1dCandle.latest();
@@ -234,9 +224,9 @@ export function init({
const date = new Date(latest.time * 1000);
switch (index) {
case /** @satisfies {Height} */ (5):
case /** @satisfies {DifficultyEpoch} */ (2):
case /** @satisfies {HalvingEpoch} */ (4): {
case "height":
case "difficultyepoch":
case "halvingepoch": {
if ("close" in last) {
last.low = Math.min(last.low, latest.close);
last.high = Math.max(last.high, latest.close);
@@ -244,24 +234,24 @@ export function init({
iseries.update(last);
break;
}
case /** @satisfies {DateIndex} */ (0): {
case "dateindex": {
iseries.update(last);
break;
}
default: {
if (index === /** @satisfies {WeekIndex} */ (23)) {
if (index === "weekindex") {
date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 6) % 7));
} else if (index === /** @satisfies {MonthIndex} */ (7)) {
} else if (index === "monthindex") {
date.setUTCDate(1);
} else if (index === /** @satisfies {QuarterIndex} */ (19)) {
} else if (index === "quarterindex") {
const month = date.getUTCMonth();
date.setUTCMonth(month - (month % 3), 1);
} else if (index === /** @satisfies {SemesterIndex} */ (20)) {
} else if (index === "semesterindex") {
const month = date.getUTCMonth();
date.setUTCMonth(month - (month % 6), 1);
} else if (index === /** @satisfies {YearIndex} */ (24)) {
} else if (index === "yearindex") {
date.setUTCMonth(0, 1);
} else if (index === /** @satisfies {DecadeIndex} */ (1)) {
} else if (index === "decadeindex") {
date.setUTCFullYear(
Math.floor(date.getUTCFullYear() / 10) * 10,
0,
@@ -446,9 +436,7 @@ export function init({
blueprints[unit]?.forEach((blueprint, order) => {
order += orderStart;
const indexes = /** @type {readonly number[]} */ (
metricToIndexes[blueprint.metric]
);
const indexes = brk.getIndexesFromMetric(blueprint.metric);
if (indexes.includes(index)) {
switch (blueprint.type) {
@@ -519,12 +507,11 @@ export function init({
/**
* @param {Object} args
* @param {Accessor<ChartOption>} args.option
* @param {MetricToIndexes} args.metricToIndexes
* @param {BRK} args.brk
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/
function createIndexSelector({ option, metricToIndexes, signals, utils }) {
const choices_ = /** @satisfies {SerializedChartableIndex[]} */ ([
function createIndexSelector({ option, brk, signals }) {
const choices_ = /** @satisfies {ChartableIndexName[]} */ ([
"timestamp",
"date",
"week",
@@ -548,7 +535,7 @@ function createIndexSelector({ option, metricToIndexes, signals, utils }) {
[Object.values(o.top), Object.values(o.bottom)]
.flat(2)
.filter((blueprint) => !blueprint.metric.startsWith("constant_"))
.map((blueprint) => metricToIndexes[blueprint.metric])
.map((blueprint) => brk.getIndexesFromMetric(blueprint.metric))
.flat(),
);
@@ -0,0 +1,38 @@
import { ios } from "../../core/env";
import { domToBlob } from "../../modules/modern-screenshot/4.6.6/dist/index.mjs";
/**
* @param {Object} args
* @param {Element} args.element
* @param {string} args.name
* @param {string} args.title
*/
export async function screenshot({ element, name, title }) {
const blob = await domToBlob(element, {
scale: 2,
});
if (ios) {
const file = new File(
[blob],
`bitview-${name}-${new Date().toJSON().split(".")[0]}.png`,
{
type: "image/png",
},
);
try {
await navigator.share({
files: [file],
title: `${title} on ${window.document.location.hostname}`,
});
return;
} catch (err) {
console.log(err);
}
}
const url = URL.createObjectURL(blob);
window.open(url, "_blank");
setTimeout(() => URL.revokeObjectURL(url), 100);
}
+6 -9
View File
@@ -1,4 +1,5 @@
import { randomFromArray } from "../core/array";
import { explorerElement } from "../core/elements";
/**
* @param {Object} args
@@ -6,26 +7,22 @@ import { randomFromArray } from "../core/array";
* @param {CreateChartElement} args.createChartElement
* @param {Accessor<ChartOption>} args.option
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {WebSockets} args.webSockets
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {MetricToIndexes} args.metricToIndexes
* @param {Resources} args.resources
* @param {BRK} args.brk
*/
export function init({
colors,
elements,
createChartElement,
option,
signals,
utils,
webSockets,
vecsResources,
metricToIndexes,
resources,
brk,
}) {
const chain = window.document.createElement("div");
chain.id = "chain";
elements.explorer.append(chain);
explorerElement.append(chain);
// vecsResources.getOrCreate(/** @satisfies {Height}*/ (5), "height");
//
+20 -73
View File
@@ -9,6 +9,12 @@ import {
createHeader,
createSelect,
} from "../core/dom";
import { simulationElement } from "../core/elements";
import {
numberToDollars,
numberToPercentage,
numberToUSNumber,
} from "../core/format";
import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde";
/**
@@ -16,18 +22,9 @@ import { serdeDate, serdeOptDate, serdeOptNumber } from "../core/serde";
* @param {Colors} args.colors
* @param {CreateChartElement} args.createChartElement
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {Resources} args.resources
*/
export function init({
colors,
elements,
createChartElement,
signals,
utils,
vecsResources,
}) {
export function init({ colors, createChartElement, signals, resources }) {
/**
* @typedef {Object} Frequency
* @property {string} name
@@ -39,8 +36,6 @@ export function init({
* @property {Frequency[]} list
*/
const simulationElement = elements.simulation;
const domExtended = {
/**
* @param {Object} args
@@ -686,7 +681,8 @@ export function init({
},
);
const index = () => /** @type {DateIndex} */ (0);
/** @type {() => IndexName} */
const index = () => "dateindex";
createChartElement({
index,
@@ -695,9 +691,7 @@ export function init({
colors,
id: `result`,
fitContent: true,
vecsResources,
utils,
elements,
resources,
config: [
{
unit: "usd",
@@ -740,9 +734,7 @@ export function init({
colors,
id: `bitcoin`,
fitContent: true,
vecsResources,
elements,
utils,
resources,
config: [
{
unit: "btc",
@@ -765,9 +757,7 @@ export function init({
colors,
id: `average-price`,
fitContent: true,
vecsResources,
utils,
elements,
resources,
config: [
{
unit: "usd",
@@ -794,11 +784,9 @@ export function init({
parent: resultsElement,
signals,
colors,
vecsResources,
resources,
id: `return-ratio`,
fitContent: true,
utils,
elements,
config: [
{
unit: "usd",
@@ -820,9 +808,7 @@ export function init({
colors,
id: `simulation-profitability-ratios`,
fitContent: true,
vecsResources,
utils,
elements,
resources,
config: [
{
unit: "percentage",
@@ -844,8 +830,8 @@ export function init({
],
});
vecsResources
.getOrCreate(/** @satisfies {DateIndex} */ (0), "price_close")
resources.metrics
.getOrCreate("price_close", "dateindex")
.fetch()
.then((_closes) => {
if (!_closes) return;
@@ -1055,11 +1041,11 @@ export function init({
});
});
const f = utils.locale.numberToUSFormat;
const f = numberToUSNumber;
/** @param {number} v */
const fd = (v) => utils.formatters.dollars.format(v);
const fd = (v) => numberToDollars.format(v);
/** @param {number} v */
const fp = (v) => utils.formatters.percentage.format(v);
const fp = (v) => numberToPercentage.format(v);
/**
* @param {ColorName} c
* @param {string} t
@@ -1098,45 +1084,6 @@ export function init({
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
signals.createEffect(
() => 0.073,
(lowestAnnual4YReturn) => {
const serLowestAnnual4YReturn = c(
"cyan",
`${fp(lowestAnnual4YReturn)}`,
);
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
/**
* @param {number} power
*/
function bitcoinValueReturn(power) {
return (
bitcoinValue *
Math.pow(lowestAnnual4YReturnPercentage, power)
);
}
const bitcoinValueAfter4y = bitcoinValueReturn(4);
const serBitcoinValueAfter4y = c(
"purple",
fd(bitcoinValueAfter4y),
);
const bitcoinValueAfter10y = bitcoinValueReturn(10);
const serBitcoinValueAfter10y = c(
"fuchsia",
fd(bitcoinValueAfter10y),
);
const bitcoinValueAfter21y = bitcoinValueReturn(21);
const serBitcoinValueAfter21y = c(
"pink",
fd(bitcoinValueAfter21y),
);
/** @param {number} v */
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
},
);
totalInvestedAmountData.set((a) => a);
bitcoinValueData.set((a) => a);
bitcoinData.set((a) => a);
+13 -126
View File
@@ -1,23 +1,17 @@
import { randomFromArray } from "../core/array";
import { createButtonElement, createHeader, createSelect } from "../core/dom";
import { tableElement } from "../core/elements";
import { serdeMetrics, serdeString, serdeUnit } from "../core/serde";
import { resetParams } from "../core/url";
/**
* @param {Object} args
* @param {MetricToIndexes} args.metricToIndexes
* @param {Option} args.option
* @param {Utilities} args.utils
* @param {Signals} args.signals
* @param {VecsResources} args.vecsResources
* @param {BRK} args.brk
* @param {Resources} args.resources
*/
function createTable({
utils,
metricToIndexes,
signals,
option,
vecsResources,
}) {
function createTable({ brk, signals, option, resources }) {
const indexToMetrics = createIndexToMetrics(metricToIndexes);
const serializedIndexes = createSerializedIndexes();
@@ -150,7 +144,7 @@ function createTable({
let from = 0;
let to = 0;
vecsResources
resources
.getOrCreate(index, serializedIndex())
.fetch()
.then((vec) => {
@@ -292,11 +286,11 @@ function createTable({
const unit = serdeUnit.deserialize(metric);
th.setUnit(unit);
const vec = vecsResources.getOrCreate(index, metric);
const vec = resources.getOrCreate(index, metric);
vec.fetch({ from, to });
const fetchedKey = vecsResources.genFetchedKey({ from, to });
const fetchedKey = resources.genFetchedKey({ from, to });
columns.set((l) => {
const i = l.indexOf(prevMetric ?? metric);
@@ -355,21 +349,12 @@ function createTable({
/**
* @param {Object} args
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Option} args.option
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
* @param {MetricToIndexes} args.metricToIndexes
* @param {Resources} args.resources
* @param {BRK} args.brk
*/
export function init({
elements,
signals,
option,
utils,
vecsResources,
metricToIndexes,
}) {
const parent = elements.table;
export function init({ signals, option, resources, brk }) {
const parent = tableElement;
const { headerElement } = createHeader("Table");
parent.append(headerElement);
@@ -378,9 +363,8 @@ export function init({
const table = createTable({
signals,
utils,
metricToIndexes,
vecsResources,
brk,
resources,
option,
});
div.append(table.element);
@@ -398,103 +382,6 @@ export function init({
);
}
function createSerializedIndexes() {
return /** @type {const} */ ([
/** @satisfies {Metric} */ ("dateindex"),
/** @satisfies {Metric} */ ("decadeindex"),
/** @satisfies {Metric} */ ("difficultyepoch"),
/** @satisfies {Metric} */ ("emptyoutputindex"),
/** @satisfies {Metric} */ ("halvingepoch"),
/** @satisfies {Metric} */ ("height"),
/** @satisfies {Metric} */ ("inputindex"),
/** @satisfies {Metric} */ ("monthindex"),
/** @satisfies {Metric} */ ("opreturnindex"),
/** @satisfies {Metric} */ ("semesterindex"),
/** @satisfies {Metric} */ ("outputindex"),
/** @satisfies {Metric} */ ("p2aaddressindex"),
/** @satisfies {Metric} */ ("p2msoutputindex"),
/** @satisfies {Metric} */ ("p2pk33addressindex"),
/** @satisfies {Metric} */ ("p2pk65addressindex"),
/** @satisfies {Metric} */ ("p2pkhaddressindex"),
/** @satisfies {Metric} */ ("p2shaddressindex"),
/** @satisfies {Metric} */ ("p2traddressindex"),
/** @satisfies {Metric} */ ("p2wpkhaddressindex"),
/** @satisfies {Metric} */ ("p2wshaddressindex"),
/** @satisfies {Metric} */ ("quarterindex"),
/** @satisfies {Metric} */ ("txindex"),
/** @satisfies {Metric} */ ("unknownoutputindex"),
/** @satisfies {Metric} */ ("weekindex"),
/** @satisfies {Metric} */ ("yearindex"),
/** @satisfies {Metric} */ ("loadedaddressindex"),
/** @satisfies {Metric} */ ("emptyaddressindex"),
]);
}
/** @typedef {ReturnType<typeof createSerializedIndexes>} SerializedIndexes */
/** @typedef {SerializedIndexes[number]} SerializedIndex */
/**
* @param {SerializedIndex} serializedIndex
* @returns {Index}
*/
function serializedIndexToIndex(serializedIndex) {
switch (serializedIndex) {
case "height":
return /** @satisfies {Height} */ (5);
case "dateindex":
return /** @satisfies {DateIndex} */ (0);
case "weekindex":
return /** @satisfies {WeekIndex} */ (23);
case "difficultyepoch":
return /** @satisfies {DifficultyEpoch} */ (2);
case "monthindex":
return /** @satisfies {MonthIndex} */ (7);
case "quarterindex":
return /** @satisfies {QuarterIndex} */ (19);
case "semesterindex":
return /** @satisfies {SemesterIndex} */ (20);
case "yearindex":
return /** @satisfies {YearIndex} */ (24);
case "decadeindex":
return /** @satisfies {DecadeIndex} */ (1);
case "halvingepoch":
return /** @satisfies {HalvingEpoch} */ (4);
case "txindex":
return /** @satisfies {TxIndex} */ (21);
case "inputindex":
return /** @satisfies {InputIndex} */ (6);
case "outputindex":
return /** @satisfies {OutputIndex} */ (9);
case "p2pk33addressindex":
return /** @satisfies {P2PK33AddressIndex} */ (12);
case "p2pk65addressindex":
return /** @satisfies {P2PK65AddressIndex} */ (13);
case "p2pkhaddressindex":
return /** @satisfies {P2PKHAddressIndex} */ (14);
case "p2shaddressindex":
return /** @satisfies {P2SHAddressIndex} */ (15);
case "p2traddressindex":
return /** @satisfies {P2TRAddressIndex} */ (16);
case "p2wpkhaddressindex":
return /** @satisfies {P2WPKHAddressIndex} */ (17);
case "p2wshaddressindex":
return /** @satisfies {P2WSHAddressIndex} */ (18);
case "p2aaddressindex":
return /** @satisfies {P2AAddressIndex} */ (10);
case "p2msoutputindex":
return /** @satisfies {P2MSOutputIndex} */ (11);
case "opreturnindex":
return /** @satisfies {OpReturnIndex} */ (8);
case "emptyoutputindex":
return /** @satisfies {EmptyOutputIndex} */ (3);
case "unknownoutputindex":
return /** @satisfies {UnknownOutputIndex} */ (22);
case "emptyaddressindex":
return /** @satisfies {EmptyAddressIndex} */ (26);
case "loadedaddressindex":
return /** @satisfies {LoadedAddressIndex} */ (25);
}
}
/**
* @param {MetricToIndexes} metricToIndexes
*/