mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-29 17:19:57 -07:00
bindex: contained fjall code
This commit is contained in:
163
website/scripts/chart.js
Normal file
163
website/scripts/chart.js
Normal file
@@ -0,0 +1,163 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {LightweightCharts} args.lightweightCharts
|
||||
* @param {Accessor<ChartOption>} args.selected
|
||||
* @param {Signals} args.signals
|
||||
* @param {Utilities} args.utils
|
||||
* @param {Datasets} args.datasets
|
||||
* @param {WebSockets} args.webSockets
|
||||
* @param {Elements} args.elements
|
||||
*/
|
||||
export function init({
|
||||
colors,
|
||||
datasets,
|
||||
elements,
|
||||
lightweightCharts,
|
||||
selected,
|
||||
signals,
|
||||
utils,
|
||||
webSockets,
|
||||
}) {
|
||||
console.log("init chart state");
|
||||
|
||||
const scale = signals.createMemo(() => selected().scale);
|
||||
|
||||
elements.charts.append(utils.dom.createShadow("left"));
|
||||
elements.charts.append(utils.dom.createShadow("right"));
|
||||
|
||||
const { headerElement, titleElement, descriptionElement } =
|
||||
utils.dom.createHeader({});
|
||||
elements.charts.append(headerElement);
|
||||
signals.createEffect(selected, (option) => {
|
||||
titleElement.innerHTML = option.title;
|
||||
descriptionElement.innerHTML = option.serializedPath;
|
||||
});
|
||||
|
||||
const chart = lightweightCharts.createChart({
|
||||
parent: elements.charts,
|
||||
signals,
|
||||
colors,
|
||||
id: "chart",
|
||||
scale: scale(),
|
||||
kind: "moveable",
|
||||
utils,
|
||||
});
|
||||
|
||||
const activeDatasets = signals.createSignal(
|
||||
/** @type {Set<ResourceDataset<any, any>>} */ (new Set()),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
|
||||
function createFetchChunksOfVisibleDatasetsEffect() {
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
ids: chart.visibleDatasetIds(),
|
||||
activeDatasets: activeDatasets(),
|
||||
}),
|
||||
({ ids, activeDatasets }) => {
|
||||
const datasets = Array.from(activeDatasets);
|
||||
|
||||
if (ids.length === 0 || datasets.length === 0) return;
|
||||
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
for (let j = 0; j < datasets.length; j++) {
|
||||
datasets[j].fetch(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
createFetchChunksOfVisibleDatasetsEffect();
|
||||
|
||||
/**
|
||||
* @param {ChartOption} option
|
||||
*/
|
||||
function applyChartOption(option) {
|
||||
const scale = option.scale;
|
||||
chart.visibleTimeRange.set(chart.getInitialVisibleTimeRange());
|
||||
|
||||
activeDatasets.set((s) => {
|
||||
s.clear();
|
||||
return s;
|
||||
});
|
||||
|
||||
const chartsBlueprints = [option.top || [], option.bottom].flatMap(
|
||||
(list) => (list ? [list] : []),
|
||||
);
|
||||
|
||||
chartsBlueprints.map((seriesBlueprints, paneIndex) => {
|
||||
const chartPane = chart.createPane({
|
||||
paneIndex,
|
||||
unit: paneIndex ? option.unit : "US Dollars",
|
||||
});
|
||||
|
||||
if (!paneIndex) {
|
||||
/** @type {AnyDatasetPath} */
|
||||
const datasetPath = `${scale}-to-price`;
|
||||
|
||||
const dataset = datasets.getOrCreate(scale, datasetPath);
|
||||
|
||||
// Don't trigger reactivity by design
|
||||
activeDatasets().add(dataset);
|
||||
|
||||
const priceSeries = chartPane.createSplitSeries({
|
||||
blueprint: {
|
||||
datasetPath,
|
||||
title: "BTC Price",
|
||||
type: "Candlestick",
|
||||
},
|
||||
dataset,
|
||||
id: option.id,
|
||||
index: -1,
|
||||
});
|
||||
|
||||
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
|
||||
if (!latest) return;
|
||||
|
||||
const index = utils.chunkIdToIndex(scale, latest.year);
|
||||
|
||||
priceSeries.forEach((splitSeries) => {
|
||||
const series = splitSeries.chunks.at(index);
|
||||
if (series) {
|
||||
signals.createEffect(series, (series) => {
|
||||
series?.update(latest);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[...seriesBlueprints].reverse().forEach((blueprint, index) => {
|
||||
const dataset = datasets.getOrCreate(scale, blueprint.datasetPath);
|
||||
|
||||
// Don't trigger reactivity by design
|
||||
activeDatasets().add(dataset);
|
||||
|
||||
chartPane.createSplitSeries({
|
||||
index,
|
||||
blueprint,
|
||||
id: option.id,
|
||||
dataset,
|
||||
});
|
||||
});
|
||||
|
||||
activeDatasets.set((s) => s);
|
||||
|
||||
return chart;
|
||||
});
|
||||
}
|
||||
|
||||
function createApplyChartOptionEffect() {
|
||||
signals.createEffect(selected, (option) => {
|
||||
chart.reset({ scale: option.scale, owner: signals.getOwner() });
|
||||
applyChartOption(option);
|
||||
});
|
||||
}
|
||||
createApplyChartOptionEffect();
|
||||
}
|
||||
43
website/scripts/live-price.js
Normal file
43
website/scripts/live-price.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @import {Options} from './options';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {Consts} args.consts
|
||||
* @param {LightweightCharts} args.lightweightCharts
|
||||
* @param {Signals} args.signals
|
||||
* @param {Utilities} args.utils
|
||||
* @param {Options} args.options
|
||||
* @param {Datasets} args.datasets
|
||||
* @param {WebSockets} args.webSockets
|
||||
* @param {Elements} args.elements
|
||||
* @param {Ids} args.ids
|
||||
* @param {Accessor<boolean>} args.dark
|
||||
*/
|
||||
export function init({
|
||||
colors,
|
||||
consts,
|
||||
dark,
|
||||
datasets,
|
||||
elements,
|
||||
ids,
|
||||
lightweightCharts,
|
||||
options,
|
||||
signals,
|
||||
utils,
|
||||
webSockets,
|
||||
}) {
|
||||
const livePriceElement = elements.livePrice;
|
||||
|
||||
const price = window.document.createElement("h1");
|
||||
livePriceElement.append(price);
|
||||
|
||||
signals.createEffect(webSockets.kraken1dCandle.latest, (candle) => {
|
||||
if (!candle) return;
|
||||
price.innerHTML = utils.formatters.dollars.format(candle.close);
|
||||
});
|
||||
}
|
||||
2697
website/scripts/main.js
Normal file
2697
website/scripts/main.js
Normal file
File diff suppressed because it is too large
Load Diff
47
website/scripts/moscow-time.js
Normal file
47
website/scripts/moscow-time.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @import {Options} from './options';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {Consts} args.consts
|
||||
* @param {Signals} args.signals
|
||||
* @param {Utilities} args.utils
|
||||
* @param {Options} args.options
|
||||
* @param {Datasets} args.datasets
|
||||
* @param {WebSockets} args.webSockets
|
||||
* @param {Elements} args.elements
|
||||
* @param {Ids} args.ids
|
||||
* @param {Accessor<boolean>} args.dark
|
||||
*/
|
||||
export function init({
|
||||
colors,
|
||||
consts,
|
||||
dark,
|
||||
datasets,
|
||||
elements,
|
||||
ids,
|
||||
options,
|
||||
signals,
|
||||
utils,
|
||||
webSockets,
|
||||
}) {
|
||||
const moscowTimeElement = elements.moscowTime;
|
||||
|
||||
const satsPerDollar = signals.createMemo(
|
||||
() =>
|
||||
100_000_000 /
|
||||
// webSockets.kraken5mnCandle.latest()?.close ||
|
||||
(webSockets.kraken1dCandle.latest()?.close || 0),
|
||||
);
|
||||
|
||||
const p = window.document.createElement("h1");
|
||||
moscowTimeElement.append(p);
|
||||
|
||||
signals.createEffect(satsPerDollar, (satsPerDollar) => {
|
||||
p.innerHTML = utils.formatters.dollars.format(satsPerDollar);
|
||||
});
|
||||
}
|
||||
5659
website/scripts/options.js
Normal file
5659
website/scripts/options.js
Normal file
File diff suppressed because it is too large
Load Diff
89
website/scripts/service-worker.js
Normal file
89
website/scripts/service-worker.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// @ts-check
|
||||
|
||||
const version = "v1";
|
||||
|
||||
self.addEventListener("install", (_event) => {
|
||||
console.log("service-worker: install");
|
||||
|
||||
const event = /** @type {any} */ (_event);
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(version).then((cache) => {
|
||||
return cache.addAll([
|
||||
"/",
|
||||
"/index.html",
|
||||
"/assets/fonts/satoshi/2024-09/font.var.woff2",
|
||||
"/scripts/main.js",
|
||||
"/scripts/options.js",
|
||||
"/scripts/chart.js",
|
||||
"/styles/chart.css",
|
||||
"/scripts/simulation.js",
|
||||
"/styles/simulation.css",
|
||||
"/packages/lean-qr/v2.3.4/script.js",
|
||||
"/packages/lightweight-charts/v4.2.2/script.js",
|
||||
"/packages/solid-signals/2024-11-02/script.js",
|
||||
"/packages/ufuzzy/v1.0.14/script.js",
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (_event) => {
|
||||
const event = /** @type {any} */ (_event);
|
||||
|
||||
/** @type {Request} */
|
||||
let request = event.request;
|
||||
const method = request.method;
|
||||
let url = request.url;
|
||||
|
||||
const { pathname, origin } = new URL(url);
|
||||
|
||||
const slashMatches = url.match(/\//g);
|
||||
const dotMatches = pathname.split("/").at(-1)?.match(/./g);
|
||||
const endsWithDotHtml = pathname.endsWith(".html");
|
||||
const slashApiSlashMatches = url.match(/\/api\//g);
|
||||
|
||||
if (
|
||||
slashMatches &&
|
||||
slashMatches.length <= 3 &&
|
||||
!slashApiSlashMatches &&
|
||||
(!dotMatches || endsWithDotHtml)
|
||||
) {
|
||||
url = `${origin}/`;
|
||||
}
|
||||
request = new Request(url, request.mode !== "navigate" ? request : undefined);
|
||||
|
||||
console.log(`service-worker: fetching: ${url}`);
|
||||
|
||||
event.respondWith(
|
||||
caches.match(request).then(async (cachedResponse) => {
|
||||
return fetch(request)
|
||||
.then((response) => {
|
||||
const { status } = response;
|
||||
|
||||
if (method !== "GET" || slashApiSlashMatches) {
|
||||
// API calls are cached in script.js
|
||||
return response;
|
||||
} else if (status === 200 || status === 304) {
|
||||
if (status === 200) {
|
||||
const clonedResponse = response.clone();
|
||||
caches.open(version).then((cache) => {
|
||||
cache.put(request, clonedResponse);
|
||||
});
|
||||
}
|
||||
return response;
|
||||
} else {
|
||||
return cachedResponse || response;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("service-worker: offline");
|
||||
|
||||
return cachedResponse;
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
995
website/scripts/simulation.js
Normal file
995
website/scripts/simulation.js
Normal file
@@ -0,0 +1,995 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @import { Options } from './options';
|
||||
* @import { ColorName, Frequencies, Frequency } from './types/self';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {LightweightCharts} args.lightweightCharts
|
||||
* @param {Signals} args.signals
|
||||
* @param {Utilities} args.utils
|
||||
* @param {Datasets} args.datasets
|
||||
* @param {Elements} args.elements
|
||||
* @param {Signal<LastValues>} args.lastValues
|
||||
*/
|
||||
export function init({
|
||||
colors,
|
||||
datasets,
|
||||
elements,
|
||||
lightweightCharts,
|
||||
signals,
|
||||
utils,
|
||||
lastValues,
|
||||
}) {
|
||||
const simulationElement = elements.simulation;
|
||||
|
||||
const parametersElement = window.document.createElement("div");
|
||||
simulationElement.append(parametersElement);
|
||||
const resultsElement = window.document.createElement("div");
|
||||
simulationElement.append(resultsElement);
|
||||
|
||||
const frequencies = computeFrequencies();
|
||||
|
||||
const keyPrefix = "save-in-bitcoin";
|
||||
const settings = {
|
||||
dollars: {
|
||||
initial: {
|
||||
amount: signals.createSignal(/** @type {number | null} */ (1000), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
keyPrefix,
|
||||
key: "initial-amount",
|
||||
},
|
||||
}),
|
||||
},
|
||||
topUp: {
|
||||
amount: signals.createSignal(/** @type {number | null} */ (150), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
keyPrefix,
|
||||
key: "top-up-amount",
|
||||
},
|
||||
}),
|
||||
frenquency: signals.createSignal(
|
||||
/** @type {Frequency} */ (frequencies.list[3].list[0]),
|
||||
{
|
||||
save: {
|
||||
...frequencies.serde,
|
||||
keyPrefix,
|
||||
key: "top-up-freq",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
bitcoin: {
|
||||
investment: {
|
||||
initial: signals.createSignal(/** @type {number | null} */ (1000), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
keyPrefix,
|
||||
key: "initial-swap",
|
||||
},
|
||||
}),
|
||||
recurrent: signals.createSignal(/** @type {number | null} */ (5), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
keyPrefix,
|
||||
key: "recurrent-swap",
|
||||
},
|
||||
}),
|
||||
frequency: signals.createSignal(
|
||||
/** @type {Frequency} */ (frequencies.list[0]),
|
||||
{
|
||||
save: {
|
||||
...frequencies.serde,
|
||||
keyPrefix,
|
||||
key: "swap-freq",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
interval: {
|
||||
start: signals.createSignal(
|
||||
/** @type {Date | null} */ (new Date("2021-04-15")),
|
||||
{
|
||||
save: {
|
||||
...utils.serde.date,
|
||||
keyPrefix,
|
||||
key: "interval-start",
|
||||
},
|
||||
},
|
||||
),
|
||||
end: signals.createSignal(/** @type {Date | null} */ (new Date()), {
|
||||
save: {
|
||||
...utils.serde.date,
|
||||
keyPrefix,
|
||||
key: "interval-end",
|
||||
},
|
||||
}),
|
||||
},
|
||||
fees: {
|
||||
percentage: signals.createSignal(/** @type {number | null} */ (0.25), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
keyPrefix,
|
||||
key: "percentage",
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createHeader({
|
||||
title: "Save in Bitcoin",
|
||||
description: "What if you bought Bitcoin in the past ?",
|
||||
}).headerElement,
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {ColorName} param0.color
|
||||
* @param {string} param0.type
|
||||
* @param {string} param0.text
|
||||
*/
|
||||
function createColoredTypeHTML({ color, type, text }) {
|
||||
return `${createColoredSpan({ color, text: `${type}:` })} ${text}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {ColorName} param0.color
|
||||
* @param {string} param0.text
|
||||
*/
|
||||
function createColoredSpan({ color, text }) {
|
||||
return `<span style="color: ${colors[color]()}; font-weight: var(--font-weight-bold)">${text}</span>`;
|
||||
}
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "green",
|
||||
type: "Dollars",
|
||||
text: "Initial Amount",
|
||||
}),
|
||||
description:
|
||||
"The amount of dollars you have ready on the exchange on day one.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputDollar({
|
||||
id: "simulation-dollars-initial",
|
||||
title: "Initial Dollar Amount",
|
||||
signal: settings.dollars.initial.amount,
|
||||
signals,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "green",
|
||||
type: "Dollars",
|
||||
text: "Top Up Frequency",
|
||||
}),
|
||||
description:
|
||||
"The frequency at which you'll top up your account at the exchange.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createSelect({
|
||||
id: "top-up-frequency",
|
||||
list: frequencies.list,
|
||||
signal: settings.dollars.topUp.frenquency,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "green",
|
||||
type: "Dollars",
|
||||
text: "Top Up Amount",
|
||||
}),
|
||||
description:
|
||||
"The recurrent amount of dollars you'll be transfering to said exchange.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputDollar({
|
||||
id: "simulation-dollars-top-up-amount",
|
||||
title: "Top Up Dollar Amount",
|
||||
signal: settings.dollars.topUp.amount,
|
||||
signals,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "orange",
|
||||
type: "Bitcoin",
|
||||
text: "Initial Investment",
|
||||
}),
|
||||
description:
|
||||
"The amount, if available, of dollars that will be used to buy Bitcoin on day one.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputDollar({
|
||||
id: "simulation-bitcoin-initial-investment",
|
||||
title: "Initial Swap Amount",
|
||||
signal: settings.bitcoin.investment.initial,
|
||||
signals,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "orange",
|
||||
type: "Bitcoin",
|
||||
text: "Investment Frequency",
|
||||
}),
|
||||
description: "The frequency at which you'll be buying Bitcoin.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createSelect({
|
||||
id: "investment-frequency",
|
||||
list: frequencies.list,
|
||||
signal: settings.bitcoin.investment.frequency,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "orange",
|
||||
type: "Bitcoin",
|
||||
text: "Recurrent Investment",
|
||||
}),
|
||||
description:
|
||||
"The recurrent amount, if available, of dollars that will be used to buy Bitcoin.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputDollar({
|
||||
id: "simulation-bitcoin-recurrent-investment",
|
||||
title: "Bitcoin Recurrent Investment",
|
||||
signal: settings.bitcoin.investment.recurrent,
|
||||
signals,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "sky",
|
||||
type: "Interval",
|
||||
text: "Start",
|
||||
}),
|
||||
description: "The first day of the simulation.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputDate({
|
||||
id: "simulation-inverval-start",
|
||||
title: "First Simulation Date",
|
||||
signal: settings.interval.start,
|
||||
signals,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "sky",
|
||||
type: "Interval",
|
||||
text: "End",
|
||||
}),
|
||||
description: "The last day of the simulation.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputDate({
|
||||
id: "simulation-inverval-end",
|
||||
title: "Last Simulation Day",
|
||||
signal: settings.interval.end,
|
||||
signals,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(
|
||||
utils.dom.createFieldElement({
|
||||
title: createColoredTypeHTML({
|
||||
color: "red",
|
||||
type: "Fees",
|
||||
text: "Exchange",
|
||||
}),
|
||||
description: "The amount of trading fees (in %) at the exchange.",
|
||||
input: utils.dom.createResetableInput(
|
||||
utils.dom.createInputNumberElement({
|
||||
id: "simulation-fees",
|
||||
title: "Exchange Fees",
|
||||
signal: settings.fees.percentage,
|
||||
min: 0,
|
||||
max: 50,
|
||||
step: 0.01,
|
||||
signals,
|
||||
placeholder: "Fees",
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
const p1 = window.document.createElement("p");
|
||||
resultsElement.append(p1);
|
||||
const p2 = window.document.createElement("p");
|
||||
resultsElement.append(p2);
|
||||
const p3 = window.document.createElement("p");
|
||||
resultsElement.append(p3);
|
||||
const p4 = window.document.createElement("p");
|
||||
resultsElement.append(p4);
|
||||
|
||||
const owner = signals.getOwner();
|
||||
|
||||
const totalInvestedAmountData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const bitcoinValueData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const bitcoinData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const resultData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const dollarsLeftData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const totalValueData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const investmentData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const bitcoinAddedData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const averagePricePaidData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const bitcoinPriceData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const buyCountData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const totalFeesPaidData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const daysCountData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const profitableDaysRatioData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
const unprofitableDaysRatioData = signals.createSignal(
|
||||
/** @type {LineData<Time>[]} */ ([]),
|
||||
{
|
||||
equals: false,
|
||||
},
|
||||
);
|
||||
|
||||
lightweightCharts.createChart({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-0`,
|
||||
kind: "static",
|
||||
scale: "date",
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
{
|
||||
title: "Fees Paid",
|
||||
type: "Line",
|
||||
color: colors.rose,
|
||||
data: totalFeesPaidData,
|
||||
},
|
||||
{
|
||||
title: "Dollars Left",
|
||||
type: "Line",
|
||||
color: colors.offDollars,
|
||||
data: dollarsLeftData,
|
||||
},
|
||||
{
|
||||
title: "Dollars Converted",
|
||||
type: "Line",
|
||||
color: colors.dollars,
|
||||
data: totalInvestedAmountData,
|
||||
},
|
||||
{
|
||||
title: "Bitcoin Value",
|
||||
type: "Line",
|
||||
color: colors.amber,
|
||||
data: bitcoinValueData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
lightweightCharts.createChart({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-1`,
|
||||
scale: "date",
|
||||
kind: "static",
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
{
|
||||
title: "Bitcoin Stack",
|
||||
type: "Line",
|
||||
color: colors.bitcoin,
|
||||
data: bitcoinData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
lightweightCharts.createChart({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-average-price`,
|
||||
scale: "date",
|
||||
kind: "static",
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
{
|
||||
title: "Bitcoin Price",
|
||||
type: "Line",
|
||||
color: colors.default,
|
||||
data: bitcoinPriceData,
|
||||
},
|
||||
{
|
||||
title: "Average Price Paid",
|
||||
type: "Line",
|
||||
color: colors.lightDollars,
|
||||
data: averagePricePaidData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
lightweightCharts.createChart({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-return-ratio`,
|
||||
scale: "date",
|
||||
kind: "static",
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
{
|
||||
title: "Return Of Investment",
|
||||
type: "Baseline",
|
||||
data: resultData,
|
||||
// TODO: Doesn't work for some reason
|
||||
// options: {
|
||||
// baseLineColor: "#888",
|
||||
// baseLineVisible: true,
|
||||
// baseLineWidth: 1,
|
||||
// baseValue: {
|
||||
// price: 0,
|
||||
// type: "price",
|
||||
// },
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
lightweightCharts.createChart({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-profitability-ratios`,
|
||||
kind: "static",
|
||||
scale: "date",
|
||||
utils,
|
||||
owner,
|
||||
config: [
|
||||
{
|
||||
unit: "Percentage",
|
||||
config: [
|
||||
{
|
||||
title: "Unprofitable Days Ratio",
|
||||
type: "Line",
|
||||
color: colors.red,
|
||||
data: unprofitableDaysRatioData,
|
||||
},
|
||||
{
|
||||
title: "Profitable Days Ratio",
|
||||
type: "Line",
|
||||
color: colors.green,
|
||||
data: profitableDaysRatioData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const closes = datasets.getOrCreate("date", "date-to-close");
|
||||
closes.fetchRange(2009, new Date().getUTCFullYear()).then(() => {
|
||||
signals.runWithOwner(owner, () => {
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
initialDollarAmount: settings.dollars.initial.amount() || 0,
|
||||
topUpAmount: settings.dollars.topUp.amount() || 0,
|
||||
topUpFrequency: settings.dollars.topUp.frenquency(),
|
||||
initialSwap: settings.bitcoin.investment.initial() || 0,
|
||||
recurrentSwap: settings.bitcoin.investment.recurrent() || 0,
|
||||
swapFrequency: settings.bitcoin.investment.frequency(),
|
||||
start: settings.interval.start(),
|
||||
end: settings.interval.end(),
|
||||
fees: settings.fees.percentage(),
|
||||
}),
|
||||
({
|
||||
initialDollarAmount,
|
||||
topUpAmount,
|
||||
topUpFrequency,
|
||||
initialSwap,
|
||||
recurrentSwap,
|
||||
swapFrequency,
|
||||
start,
|
||||
end,
|
||||
fees,
|
||||
}) => {
|
||||
if (!start || !end || start > end) return;
|
||||
|
||||
const range = utils.date.getRange(start, end);
|
||||
|
||||
totalInvestedAmountData().length = 0;
|
||||
bitcoinValueData().length = 0;
|
||||
bitcoinData().length = 0;
|
||||
resultData().length = 0;
|
||||
dollarsLeftData().length = 0;
|
||||
totalValueData().length = 0;
|
||||
investmentData().length = 0;
|
||||
bitcoinAddedData().length = 0;
|
||||
averagePricePaidData().length = 0;
|
||||
bitcoinPriceData().length = 0;
|
||||
buyCountData().length = 0;
|
||||
totalFeesPaidData().length = 0;
|
||||
daysCountData().length = 0;
|
||||
profitableDaysRatioData().length = 0;
|
||||
unprofitableDaysRatioData().length = 0;
|
||||
|
||||
let bitcoin = 0;
|
||||
let sats = 0;
|
||||
let dollars = initialDollarAmount;
|
||||
let investedAmount = 0;
|
||||
let postFeesInvestedAmount = 0;
|
||||
let buyCount = 0;
|
||||
let averagePricePaid = 0;
|
||||
let bitcoinValue = 0;
|
||||
let roi = 0;
|
||||
let totalValue = 0;
|
||||
let totalFeesPaid = 0;
|
||||
let daysCount = range.length;
|
||||
let profitableDays = 0;
|
||||
let unprofitableDays = 0;
|
||||
let profitableDaysRatio = 0;
|
||||
let unprofitableDaysRatio = 0;
|
||||
let lastInvestDay = range[0];
|
||||
let dailyInvestment = 0;
|
||||
let bitcoinAdded = 0;
|
||||
let satsAdded = 0;
|
||||
let lastSatsAdded = 0;
|
||||
|
||||
range.forEach((date, index) => {
|
||||
const year = date.getUTCFullYear();
|
||||
const time = utils.date.toString(date);
|
||||
|
||||
if (topUpFrequency.isTriggerDay(date)) {
|
||||
dollars += topUpAmount;
|
||||
}
|
||||
|
||||
const close = closes.fetchedJSONs
|
||||
.at(utils.chunkIdToIndex("date", year))
|
||||
?.json()?.dataset.map[utils.date.toString(date)];
|
||||
|
||||
if (!close) return;
|
||||
|
||||
dailyInvestment = 0;
|
||||
/** @param {number} value */
|
||||
function invest(value) {
|
||||
value = Math.min(dollars, value);
|
||||
dailyInvestment += value;
|
||||
dollars -= value;
|
||||
buyCount += 1;
|
||||
lastInvestDay = date;
|
||||
}
|
||||
if (!index) {
|
||||
invest(initialSwap);
|
||||
}
|
||||
if (swapFrequency.isTriggerDay(date) && dollars > 0) {
|
||||
invest(recurrentSwap);
|
||||
}
|
||||
|
||||
investedAmount += dailyInvestment;
|
||||
|
||||
let dailyInvestmentPostFees =
|
||||
dailyInvestment * (1 - (fees || 0) / 100);
|
||||
|
||||
totalFeesPaid += dailyInvestment - dailyInvestmentPostFees;
|
||||
|
||||
bitcoinAdded = dailyInvestmentPostFees / close;
|
||||
bitcoin += bitcoinAdded;
|
||||
satsAdded = Math.floor(bitcoinAdded * 100_000_000);
|
||||
if (satsAdded > 0) {
|
||||
lastSatsAdded = satsAdded;
|
||||
}
|
||||
sats += satsAdded;
|
||||
|
||||
postFeesInvestedAmount += dailyInvestmentPostFees;
|
||||
|
||||
bitcoinValue = close * bitcoin;
|
||||
|
||||
totalValue = dollars + bitcoinValue;
|
||||
|
||||
averagePricePaid = postFeesInvestedAmount / bitcoin;
|
||||
|
||||
roi = (bitcoinValue / postFeesInvestedAmount - 1) * 100;
|
||||
|
||||
const daysCount = index + 1;
|
||||
profitableDaysRatio = profitableDays / daysCount;
|
||||
unprofitableDaysRatio = unprofitableDays / daysCount;
|
||||
|
||||
if (roi >= 0) {
|
||||
profitableDays += 1;
|
||||
} else {
|
||||
unprofitableDays += 1;
|
||||
}
|
||||
|
||||
bitcoinPriceData().push({
|
||||
time,
|
||||
value: close,
|
||||
});
|
||||
|
||||
bitcoinData().push({
|
||||
time,
|
||||
value: bitcoin,
|
||||
});
|
||||
|
||||
totalInvestedAmountData().push({
|
||||
time,
|
||||
value: investedAmount,
|
||||
});
|
||||
|
||||
bitcoinValueData().push({
|
||||
time,
|
||||
value: bitcoinValue,
|
||||
});
|
||||
|
||||
resultData().push({
|
||||
time,
|
||||
value: roi,
|
||||
});
|
||||
|
||||
dollarsLeftData().push({
|
||||
time,
|
||||
value: dollars,
|
||||
});
|
||||
|
||||
totalValueData().push({
|
||||
time,
|
||||
value: totalValue,
|
||||
});
|
||||
|
||||
investmentData().push({
|
||||
time,
|
||||
value: dailyInvestment,
|
||||
});
|
||||
|
||||
bitcoinAddedData().push({
|
||||
time,
|
||||
value: bitcoinAdded,
|
||||
});
|
||||
|
||||
averagePricePaidData().push({
|
||||
time,
|
||||
value: averagePricePaid,
|
||||
});
|
||||
|
||||
buyCountData().push({
|
||||
time,
|
||||
value: buyCount,
|
||||
});
|
||||
|
||||
totalFeesPaidData().push({
|
||||
time,
|
||||
value: totalFeesPaid,
|
||||
});
|
||||
|
||||
daysCountData().push({
|
||||
time,
|
||||
value: daysCount,
|
||||
});
|
||||
|
||||
profitableDaysRatioData().push({
|
||||
time,
|
||||
value: profitableDaysRatio * 100,
|
||||
});
|
||||
|
||||
unprofitableDaysRatioData().push({
|
||||
time,
|
||||
value: unprofitableDaysRatio * 100,
|
||||
});
|
||||
});
|
||||
|
||||
const f = utils.locale.numberToUSFormat;
|
||||
/** @param {number} v */
|
||||
const fd = (v) => utils.formatters.dollars.format(v);
|
||||
/** @param {number} v */
|
||||
const fp = (v) => utils.formatters.percentage.format(v);
|
||||
/**
|
||||
* @param {ColorName} c
|
||||
* @param {string} t
|
||||
*/
|
||||
const c = (c, t) => createColoredSpan({ color: c, text: t });
|
||||
|
||||
const serInvestedAmount = c("dollars", fd(investedAmount));
|
||||
const serDaysCount = c("sky", f(daysCount));
|
||||
const serSats = c("orange", f(sats));
|
||||
const serBitcoin = c("orange", `~${f(bitcoin)}`);
|
||||
const serBitcoinValue = c("amber", fd(bitcoinValue));
|
||||
const serAveragePricePaid = c("lightDollars", fd(averagePricePaid));
|
||||
const serRoi = c("yellow", fp(roi / 100));
|
||||
const serDollars = c("offDollars", fd(dollars));
|
||||
const serTotalFeesPaid = c("rose", fd(totalFeesPaid));
|
||||
|
||||
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
|
||||
|
||||
const dayDiff = Math.floor(
|
||||
utils.date.differenceBetween(new Date(), lastInvestDay),
|
||||
);
|
||||
const serDailyInvestment = c("offDollars", fd(dailyInvestment));
|
||||
const setLastSatsAdded = c("bitcoin", f(lastSatsAdded));
|
||||
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
|
||||
|
||||
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
|
||||
const serUnprofitableDaysRatio = c("red", fp(unprofitableDaysRatio));
|
||||
|
||||
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
|
||||
|
||||
signals.createEffect(lastValues, (lastValues) => {
|
||||
const lowestAnnual4YReturn = 0.2368;
|
||||
// const lowestAnnual4YReturn = lastValues?.["price-4y-compound-return"] || 0
|
||||
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);
|
||||
resultData.set((a) => a);
|
||||
dollarsLeftData.set((a) => a);
|
||||
totalValueData.set((a) => a);
|
||||
investmentData.set((a) => a);
|
||||
bitcoinAddedData.set((a) => a);
|
||||
averagePricePaidData.set((a) => a);
|
||||
bitcoinPriceData.set((a) => a);
|
||||
buyCountData.set((a) => a);
|
||||
totalFeesPaidData.set((a) => a);
|
||||
daysCountData.set((a) => a);
|
||||
profitableDaysRatioData.set((a) => a);
|
||||
unprofitableDaysRatioData.set((a) => a);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {number} day */
|
||||
function getOrdinalDay(day) {
|
||||
const rest = (day % 30) % 20;
|
||||
|
||||
return `${day}${
|
||||
rest === 1 ? "st" : rest === 2 ? "nd" : rest === 3 ? "rd" : "th"
|
||||
}`;
|
||||
}
|
||||
|
||||
function computeFrequencies() {
|
||||
const weekDays = [
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
];
|
||||
const maxDays = 28;
|
||||
|
||||
/** @satisfies {([Frequency, Frequencies, Frequencies, Frequencies])} */
|
||||
const list = [
|
||||
{
|
||||
name: "Every day",
|
||||
value: "every-day",
|
||||
/** @param {Date} _ */
|
||||
isTriggerDay(_) {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Once a week",
|
||||
list: weekDays.map((day, index) => ({
|
||||
name: day,
|
||||
value: day.toLowerCase(),
|
||||
/** @param {Date} date */
|
||||
isTriggerDay(date) {
|
||||
let day = date.getUTCDay() - 1;
|
||||
if (day === -1) {
|
||||
day = 6;
|
||||
}
|
||||
return day === index;
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Every two weeks",
|
||||
list: [...Array(Math.round(maxDays / 2)).keys()].map((day) => {
|
||||
const day1 = day + 1;
|
||||
const day2 = day + 15;
|
||||
|
||||
return {
|
||||
value: `${day1}+${day2}`,
|
||||
name: `The ${getOrdinalDay(day1)} and the ${getOrdinalDay(day2)}`,
|
||||
/** @param {Date} date */
|
||||
isTriggerDay(date) {
|
||||
const d = date.getUTCDate();
|
||||
return d === day1 || d === day2;
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Once a month",
|
||||
list: [...Array(maxDays).keys()].map((day) => {
|
||||
day++;
|
||||
|
||||
return {
|
||||
name: `The ${getOrdinalDay(day)}`,
|
||||
value: String(day),
|
||||
/** @param {Date} date */
|
||||
isTriggerDay(date) {
|
||||
const d = date.getUTCDate();
|
||||
return d === day;
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {Record<string, Frequency>} */
|
||||
const idToFrequency = {};
|
||||
|
||||
list.forEach((anyFreq, index) => {
|
||||
if ("list" in anyFreq) {
|
||||
anyFreq.list?.forEach((freq) => {
|
||||
idToFrequency[freq.value] = freq;
|
||||
});
|
||||
} else {
|
||||
idToFrequency[anyFreq.value] = anyFreq;
|
||||
}
|
||||
});
|
||||
|
||||
const serde = {
|
||||
/**
|
||||
* @param {Frequency} v
|
||||
*/
|
||||
serialize(v) {
|
||||
return v.value;
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
const freq = idToFrequency[v];
|
||||
if (!freq) throw "Freq not found";
|
||||
return freq;
|
||||
},
|
||||
};
|
||||
|
||||
return { list, serde };
|
||||
}
|
||||
302
website/scripts/types/self.d.ts
vendored
Normal file
302
website/scripts/types/self.d.ts
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
import {
|
||||
Accessor,
|
||||
Setter,
|
||||
} from "../../packages/solid-signals/2024-11-02/types/signals";
|
||||
import {
|
||||
DeepPartial,
|
||||
BaselineStyleOptions,
|
||||
CandlestickStyleOptions,
|
||||
LineStyleOptions,
|
||||
SeriesOptionsCommon,
|
||||
Range,
|
||||
Time,
|
||||
SingleValueData,
|
||||
CandlestickData,
|
||||
SeriesType,
|
||||
ISeriesApi,
|
||||
BaselineData,
|
||||
} from "../../packages/lightweight-charts/v4.2.2/types";
|
||||
import { DatePath, HeightPath, LastPath } from "./paths";
|
||||
import { AnyPossibleCohortId } from "../options";
|
||||
import { Signal } from "../../packages/solid-signals/types";
|
||||
|
||||
type GrowToSize<T, N extends number, A extends T[]> = A["length"] extends N
|
||||
? A
|
||||
: GrowToSize<T, N, [...A, T]>;
|
||||
|
||||
type FixedArray<T, N extends number> = GrowToSize<T, N, []>;
|
||||
|
||||
type TimeScale = "date" | "height";
|
||||
|
||||
type TimeRange = Range<Time | number>;
|
||||
|
||||
type DatasetPath<Scale extends TimeScale> = Scale extends "date"
|
||||
? DatePath
|
||||
: HeightPath;
|
||||
|
||||
type AnyDatasetPath = import("./paths").DatePath | import("./paths").HeightPath;
|
||||
|
||||
type AnyPath = AnyDatasetPath | LastPath;
|
||||
|
||||
type Color = () => string;
|
||||
type ColorName = keyof Colors;
|
||||
|
||||
type Unit =
|
||||
| ""
|
||||
| "Bitcoin"
|
||||
| "Coinblocks"
|
||||
| "Count"
|
||||
| "Date"
|
||||
| "Dollars / (PetaHash / Second)"
|
||||
| "ExaHash / Second"
|
||||
| "Height"
|
||||
| "Gigabytes"
|
||||
| "Megabytes"
|
||||
| "Percentage"
|
||||
| "Ratio"
|
||||
| "Satoshis"
|
||||
| "Seconds"
|
||||
| "Transactions"
|
||||
| "US Dollars"
|
||||
| "Virtual Bytes"
|
||||
| "Weight";
|
||||
|
||||
interface PartialOption {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PartialHomeOption extends PartialOption {
|
||||
kind: "home";
|
||||
title: "Home";
|
||||
name: "Home";
|
||||
}
|
||||
|
||||
interface PartialLivePriceOption extends PartialOption {
|
||||
kind: "live-price";
|
||||
}
|
||||
|
||||
interface PartialMoscowTimeOption extends PartialOption {
|
||||
kind: "moscow-time";
|
||||
}
|
||||
|
||||
interface PartialConverterOption extends PartialOption {
|
||||
kind: "converter";
|
||||
}
|
||||
|
||||
interface PartialChartOption extends PartialOption {
|
||||
scale: TimeScale;
|
||||
title: string;
|
||||
shortTitle?: string;
|
||||
unit: Unit;
|
||||
description: string;
|
||||
top?: SplitSeriesBlueprint[];
|
||||
bottom?: SplitSeriesBlueprint[];
|
||||
dashboard?: {
|
||||
ignoreName?: boolean;
|
||||
skip?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface PartialSimulationOption extends PartialOption {
|
||||
kind: "simulation";
|
||||
title: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PartialPdfOption extends PartialOption {
|
||||
pdf: string;
|
||||
}
|
||||
|
||||
interface PartialUrlOption extends PartialOption {
|
||||
qrcode?: true;
|
||||
url: () => string;
|
||||
}
|
||||
|
||||
interface PartialOptionsGroup {
|
||||
name: string;
|
||||
tree: PartialOptionsTree;
|
||||
}
|
||||
|
||||
type AnyPartialOption =
|
||||
| PartialHomeOption
|
||||
| PartialLivePriceOption
|
||||
| PartialMoscowTimeOption
|
||||
| PartialConverterOption
|
||||
| PartialChartOption
|
||||
| PartialSimulationOption
|
||||
| PartialPdfOption
|
||||
| PartialUrlOption;
|
||||
|
||||
type PartialOptionsTree = (AnyPartialOption | PartialOptionsGroup)[];
|
||||
|
||||
interface ProcessedOptionAddons {
|
||||
id: string;
|
||||
path: OptionPath;
|
||||
serializedPath: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
type OptionPath = {
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
|
||||
type HomeOption = PartialHomeOption & ProcessedOptionAddons;
|
||||
type SimulationOption = PartialSimulationOption & ProcessedOptionAddons;
|
||||
type LivePriceOption = PartialLivePriceOption & ProcessedOptionAddons;
|
||||
type MoscowTimeOption = PartialMoscowTimeOption & ProcessedOptionAddons;
|
||||
type ConverterOption = PartialConverterOption & ProcessedOptionAddons;
|
||||
|
||||
interface PdfOption extends PartialPdfOption, ProcessedOptionAddons {
|
||||
kind: "pdf";
|
||||
}
|
||||
|
||||
interface UrlOption extends PartialUrlOption, ProcessedOptionAddons {
|
||||
kind: "url";
|
||||
}
|
||||
|
||||
interface ChartOption extends PartialChartOption, ProcessedOptionAddons {
|
||||
kind: "chart";
|
||||
}
|
||||
|
||||
type Option =
|
||||
| HomeOption
|
||||
| LivePriceOption
|
||||
| MoscowTimeOption
|
||||
| ConverterOption
|
||||
| PdfOption
|
||||
| UrlOption
|
||||
| ChartOption
|
||||
| SimulationOption;
|
||||
|
||||
type OptionsTree = (Option | OptionsGroup)[];
|
||||
|
||||
interface OptionsGroup extends PartialOptionsGroup {
|
||||
id: string;
|
||||
tree: OptionsTree;
|
||||
}
|
||||
|
||||
interface OHLC {
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
}
|
||||
|
||||
interface ResourceDataset<
|
||||
Scale extends TimeScale,
|
||||
Type extends OHLC | number = number,
|
||||
> {
|
||||
scale: Scale;
|
||||
url: string;
|
||||
fetch: (id: number) => Promise<void>;
|
||||
fetchRange: (start: number, end: number) => Promise<void[]>;
|
||||
fetchedJSONs: FetchedResult<Scale, Type>[];
|
||||
}
|
||||
|
||||
type ValuedCandlestickData = CandlestickData & Valued;
|
||||
|
||||
interface FetchedResult<
|
||||
Scale extends TimeScale,
|
||||
Type extends number | OHLC,
|
||||
Value extends SingleValueData | ValuedCandlestickData = Type extends number
|
||||
? SingleValueData
|
||||
: ValuedCandlestickData,
|
||||
> {
|
||||
at: Date | null;
|
||||
json: Signal<FetchedJSON<Scale, Type> | null>;
|
||||
vec: Accessor<Value[] | null>;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface Valued {
|
||||
value: number;
|
||||
}
|
||||
|
||||
type DatasetValue<T> = T & Valued;
|
||||
|
||||
interface FetchedJSON<Scale extends TimeScale, Type extends number | OHLC> {
|
||||
source: FetchedSource;
|
||||
chunk: FetchedChunk;
|
||||
dataset: FetchedDataset<Scale, Type>;
|
||||
}
|
||||
|
||||
type FetchedSource = string;
|
||||
|
||||
interface FetchedChunk {
|
||||
id: number;
|
||||
previous: string | null;
|
||||
next: string | null;
|
||||
}
|
||||
|
||||
type FetchedDataset<
|
||||
Scale extends TimeScale,
|
||||
Type extends number | OHLC,
|
||||
> = Scale extends "date"
|
||||
? FetchedDateDataset<Type>
|
||||
: FetchedHeightDataset<Type>;
|
||||
|
||||
interface Versioned {
|
||||
version: number;
|
||||
}
|
||||
|
||||
interface FetchedDateDataset<Type> extends Versioned {
|
||||
map: Record<string, Type>;
|
||||
}
|
||||
|
||||
interface FetchedHeightDataset<Type> extends Versioned {
|
||||
map: Type[];
|
||||
}
|
||||
|
||||
interface Weighted {
|
||||
weight: number;
|
||||
}
|
||||
|
||||
type DatasetCandlestickData = DatasetValue<CandlestickData> & { year: number };
|
||||
|
||||
type NotFunction<T> = T extends Function ? never : T;
|
||||
|
||||
type Groups = import("../options").Groups;
|
||||
|
||||
type DefaultCohortOption = CohortOption<AnyPossibleCohortId>;
|
||||
|
||||
interface CohortOption<Id extends AnyPossibleCohortId> {
|
||||
scale: TimeScale;
|
||||
name: string;
|
||||
title: string;
|
||||
datasetId: Id;
|
||||
color: Color;
|
||||
filenameAddon?: string;
|
||||
}
|
||||
|
||||
type DefaultCohortOptions = CohortOptions<AnyPossibleCohortId>;
|
||||
|
||||
interface CohortOptions<Id extends AnyPossibleCohortId> {
|
||||
scale: TimeScale;
|
||||
name: string;
|
||||
title: string;
|
||||
list: CohortOption<Id>[];
|
||||
}
|
||||
|
||||
interface RatioOption {
|
||||
scale: TimeScale;
|
||||
color: Color;
|
||||
valueDatasetPath: AnyDatasetPath;
|
||||
ratioDatasetPath: AnyDatasetPath;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface RatioOptions {
|
||||
scale: TimeScale;
|
||||
title: string;
|
||||
list: RatioOption[];
|
||||
}
|
||||
|
||||
interface Frequency {
|
||||
name: string;
|
||||
value: string;
|
||||
isTriggerDay: (date: Date) => boolean;
|
||||
}
|
||||
type Frequencies = { name: string; list: Frequency[] };
|
||||
|
||||
type LastValues = Record<LastPath, number> | null;
|
||||
Reference in New Issue
Block a user