diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62fcf8c1b..0110897a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@
- Split `index.html` and `script.js` into multiple js and css files to load only what's necessary at a given time
- Added `Compare` section to all groups, to compare all datasets within a group
- Updated `Solid Signals` library, which had an important breaking change on the `createEffect` function which might bring some bugs
+- Fixed some datasets paths
## Parser
diff --git a/README.md b/README.md
index f98c3f579..36de80bf0 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,14 @@
-
-
-
-
+
+
+
## Description
-[**kibō**](https://kibo.money) (_hope_ in japanese) is primarily an open source Bitcoin Core data extractor and visualizer (similar to [Glassnode](https://glassnode.com)).
+[**kibō**](https://kibo.money) (_hope_ in japanese) is primarily an open source Bitcoin Core data extractor and visualizer (similar to [Glassnode](https://glassnode.com)). The goal is to empower people with information that is often hard to come by and/or very pricey.
The project is split in 3 parts:
@@ -20,7 +19,7 @@ The project is split in 3 parts:
Whether you're an enthusiast, a researcher, a miner, an analyst, a trader, a skeptic or just curious, there is something for everyone !
-This project was created out of frustration by all the alternatives that were either very expensive and thus discriminatory and against bitcoin values or just very limited and none were open-source and verifiable. So while it's not the first tool trying to solve these problems, it's the first that is completely free, open-source and self-hostable.
+This project was created out of frustration by all the alternatives that were either very expensive and thus discriminatory and against bitcoin values or just very limited and none were open-source and verifiable. So while it's not the first tool trying to solve these problems, it's the first that is completely free, open-source and self-hostable.
If you are a user of [mempool.space](https://mempool.space), you'll find this to be very complimentary, as it offers a macro view of the chain over time instead of a detailed one.
diff --git a/assets/dove-orange.svg b/assets/dove-orange.svg
new file mode 100644
index 000000000..725a1b086
--- /dev/null
+++ b/assets/dove-orange.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/assets/dove-white.svg b/assets/dove-white.svg
new file mode 100644
index 000000000..4b4e23701
--- /dev/null
+++ b/assets/dove-white.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/assets/logo-long-text-dark.svg b/assets/logo-long-text-dark.svg
index 3aada5871..63c9b24bd 100644
--- a/assets/logo-long-text-dark.svg
+++ b/assets/logo-long-text-dark.svg
@@ -2,7 +2,7 @@
-
-
+
+
-
\ No newline at end of file
+
diff --git a/assets/logo-stamp-orange.svg b/assets/logo-stamp-orange.svg
new file mode 100644
index 000000000..dc69696c5
--- /dev/null
+++ b/assets/logo-stamp-orange.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/assets/logo-stamp.svg b/assets/logo-stamp.svg
new file mode 100644
index 000000000..7ba4f7cc9
--- /dev/null
+++ b/assets/logo-stamp.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/parser/src/datasets/price/mod.rs b/parser/src/datasets/price.rs
similarity index 100%
rename from parser/src/datasets/price/mod.rs
rename to parser/src/datasets/price.rs
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png b/website/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png
new file mode 100644
index 000000000..f8097234c
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-icon-180.png differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg
new file mode 100644
index 000000000..f9a6c9c35
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1125-2436.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg
new file mode 100644
index 000000000..49ec476c1
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1136-640.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg
new file mode 100644
index 000000000..88c45819f
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1170-2532.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg
new file mode 100644
index 000000000..beed1b88d
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1179-2556.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg
new file mode 100644
index 000000000..3b88083b1
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2208.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg
new file mode 100644
index 000000000..9e347e448
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1242-2688.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg
new file mode 100644
index 000000000..17693b3ce
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1284-2778.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg
new file mode 100644
index 000000000..edae7dc00
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1290-2796.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg
new file mode 100644
index 000000000..4536d1b7f
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1334-750.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg
new file mode 100644
index 000000000..fe68a6536
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1488-2266.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg
new file mode 100644
index 000000000..06f26fa31
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1536-2048.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg
new file mode 100644
index 000000000..71dc363de
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1620-2160.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg
new file mode 100644
index 000000000..5b6b22d72
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1640-2360.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg
new file mode 100644
index 000000000..16de30647
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2224.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg
new file mode 100644
index 000000000..68467639e
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1668-2388.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg
new file mode 100644
index 000000000..e11c0d8b8
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-1792-828.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg
new file mode 100644
index 000000000..238355f96
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-1536.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg
new file mode 100644
index 000000000..c269d3564
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2048-2732.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg
new file mode 100644
index 000000000..3b0fd467a
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2160-1620.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg
new file mode 100644
index 000000000..cbb943128
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2208-1242.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg
new file mode 100644
index 000000000..11cc13afe
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2224-1668.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg
new file mode 100644
index 000000000..9845b9b49
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2266-1488.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg
new file mode 100644
index 000000000..3dbe26e7a
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2360-1640.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg
new file mode 100644
index 000000000..66200a766
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2388-1668.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg
new file mode 100644
index 000000000..4358322a6
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2436-1125.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg
new file mode 100644
index 000000000..9208568ed
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2532-1170.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg
new file mode 100644
index 000000000..c7d246aa1
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2556-1179.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg
new file mode 100644
index 000000000..21ec5e387
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2688-1242.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg
new file mode 100644
index 000000000..d860c7f1e
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2732-2048.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg
new file mode 100644
index 000000000..0c9c2e177
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2778-1284.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg
new file mode 100644
index 000000000..69c340c6e
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-2796-1290.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg
new file mode 100644
index 000000000..71af8e200
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-640-1136.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg
new file mode 100644
index 000000000..69745687e
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-750-1334.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg
new file mode 100644
index 000000000..0f8155029
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/apple-splash-828-1792.jpg differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/favicon-196.png b/website/assets/pwa/2024-11-20_09-41-25/favicon-196.png
new file mode 100644
index 000000000..8fd56e3b1
Binary files /dev/null and b/website/assets/pwa/2024-11-20_09-41-25/favicon-196.png differ
diff --git a/website/assets/pwa/2024-11-20_09-41-25/index.html b/website/assets/pwa/2024-11-20_09-41-25/index.html
new file mode 100644
index 000000000..39ede9825
--- /dev/null
+++ b/website/assets/pwa/2024-11-20_09-41-25/index.html
@@ -0,0 +1,44 @@
+
+
+
+
-
+
+
diff --git a/website/manifest.webmanifest b/website/manifest.webmanifest
index e71abaff1..0b6e69d90 100644
--- a/website/manifest.webmanifest
+++ b/website/manifest.webmanifest
@@ -2,7 +2,11 @@
"name": "kibō",
"short_name": "kibō",
"description": "A better, FOSS, Bitcoin-only, self-hostable Glassnode",
- "categories": ["bitcoin", "on-chain", "data"],
+ "categories": [
+ "bitcoin",
+ "on-chain",
+ "data"
+ ],
"start_url": "/",
"scope": "/",
"display": "standalone",
@@ -12,28 +16,28 @@
"lang": "en",
"icons": [
{
- "src": "/assets/pwa/2024-09-17_09-06-03/manifest-icon-192.maskable.png",
+ "src": "/assets/pwa/2024-11-20_09-41-25/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
- "src": "/assets/pwa/2024-09-17_09-06-03/manifest-icon-192.maskable.png",
+ "src": "/assets/pwa/2024-11-20_09-41-25/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
- "src": "/assets/pwa/2024-09-17_09-06-03/manifest-icon-512.maskable.png",
+ "src": "/assets/pwa/2024-11-20_09-41-25/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
- "src": "/assets/pwa/2024-09-17_09-06-03/manifest-icon-512.maskable.png",
+ "src": "/assets/pwa/2024-11-20_09-41-25/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
-}
+}
\ No newline at end of file
diff --git a/website/scripts/chart.js b/website/scripts/chart.js
index 7d3e8d363..244b4d2db 100644
--- a/website/scripts/chart.js
+++ b/website/scripts/chart.js
@@ -224,14 +224,6 @@ export function init({
}
createFetchChunksOfVisibleDatasetsEffect();
- function resetChartListElement() {
- while (
- elements.chartList.lastElementChild?.classList.contains("chart-wrapper")
- ) {
- elements.chartList.lastElementChild?.remove();
- }
- }
-
/**
* @param {HTMLElement} parent
* @param {number} chartIndex
@@ -767,7 +759,7 @@ export function init({
/** @type {AnyDatasetPath} */
const datasetPath = `${s}-to-price`;
- const dataset = datasets.getOrImport(s, datasetPath);
+ const dataset = datasets.getOrCreate(s, datasetPath);
// Don't trigger reactivity by design
activeDatasets().add(dataset);
@@ -806,7 +798,7 @@ export function init({
});
function createLiveCandleUpdateEffect() {
- signals.createEffect(webSockets.krakenCandle.latest, (latest) => {
+ signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return;
const index = utils.chunkIdToIndex(s, latest.year);
@@ -825,95 +817,6 @@ export function init({
return priceSeries;
}
- function resetLegendElement() {
- elements.legend.innerHTML = "";
- }
-
- function initTimeScaleElement() {
- const GENESIS_DAY = "2009-01-03";
-
- /**
- * @param {HTMLButtonElement} button
- * @param {ChartOption} option
- */
- function setTimeScale(button, option) {
- const chart = charts.at(-1);
- if (!chart) return;
- const timeScale = chart.timeScale();
-
- const year = button.dataset.year;
- let days = button.dataset.days;
- let toHeight = button.dataset.to;
-
- switch (option.scale) {
- case "date": {
- let from = new Date();
- let to = new Date();
- to.setUTCHours(0, 0, 0, 0);
-
- if (!days && typeof button.dataset.yearToDate === "string") {
- days = String(
- Math.ceil(
- (to.getTime() -
- new Date(`${to.getUTCFullYear()}-01-01`).getTime()) /
- consts.ONE_DAY_IN_MS,
- ),
- );
- }
-
- if (year) {
- from = new Date(`${year}-01-01`);
- to = new Date(`${year}-12-31`);
- } else if (days) {
- from.setDate(from.getUTCDate() - Number(days));
- } else {
- from = new Date(GENESIS_DAY);
- }
-
- timeScale.setVisibleRange({
- from: /** @type {Time} */ (from.getTime() / 1000),
- to: /** @type {Time} */ (to.getTime() / 1000),
- });
- break;
- }
- case "height": {
- timeScale.setVisibleRange({
- from: /** @type {Time} */ (0),
- to: /** @type {Time} */ (Number(toHeight?.slice(0, -1)) * 1_000),
- });
- break;
- }
- }
- }
-
- /**
- * @param {HTMLElement} timeScaleButtons
- */
- function initGoToButtons(timeScaleButtons) {
- Array.from(timeScaleButtons.children).forEach((button) => {
- if (button.tagName !== "BUTTON") throw "Expect a button";
- button.addEventListener("click", () => {
- const option = options.selected();
- if (option.kind === "chart") {
- setTimeScale(/** @type {HTMLButtonElement} */ (button), option);
- }
- });
- });
- }
- // initGoToButtons(elements.timeScaleDateButtons);
- // initGoToButtons(elements.timeScaleHeightButtons);
-
- // function createScaleButtonsToggleEffect() {
- // const isDate = signals.createMemo(() => scale() === "date");
- // signals.createEffect(isDate, (isDate) => {
- // elements.timeScaleDateButtons.hidden = !isDate;
- // elements.timeScaleHeightButtons.hidden = isDate;
- // });
- // }
- // createScaleButtonsToggleEffect();
- }
- initTimeScaleElement();
-
/**
* @param {ChartOption} option
*/
@@ -933,15 +836,12 @@ export function init({
(list) => (list ? [list] : []),
);
- resetLegendElement();
- resetChartListElement();
-
/** @type {Series[]} */
const allSeries = [];
charts = chartsBlueprints.map((seriesBlueprints, chartIndex) => {
const { chartDiv, unitName, chartMode } = createChartDiv(
- elements.chartList,
+ elements.chartsChartList,
chartIndex,
);
@@ -1148,7 +1048,7 @@ export function init({
}
[...seriesBlueprints].reverse().forEach((seriesBlueprint, index) => {
- const dataset = datasets.getOrImport(
+ const dataset = datasets.getOrCreate(
scale,
seriesBlueprint.datasetPath,
);
@@ -1266,11 +1166,31 @@ export function init({
});
}
+ function resetLegendElement() {
+ elements.legend.innerHTML = "";
+ }
+
+ function resetChartListElement() {
+ while (
+ elements.chartsChartList.lastElementChild?.classList.contains(
+ "chart-wrapper",
+ )
+ ) {
+ elements.chartsChartList.lastElementChild?.remove();
+ }
+ }
+
+ function reset() {
+ charts.forEach((chart) => chart.remove());
+ charts = [];
+ resetLegendElement();
+ resetChartListElement();
+ }
+
function createApplyChartOptionEffect() {
signals.createEffect(selected, (option) => {
- signals.createRoot(() => {
- applyChartOption(option);
- });
+ reset();
+ applyChartOption(option);
});
}
createApplyChartOptionEffect();
diff --git a/website/scripts/live-price.js b/website/scripts/live-price.js
new file mode 100644
index 000000000..b45fe93d7
--- /dev/null
+++ b/website/scripts/live-price.js
@@ -0,0 +1,41 @@
+/**
+ * @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} 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);
+ });
+}
diff --git a/website/scripts/main.js b/website/scripts/main.js
index 8baa0abd3..902eb0e32 100644
--- a/website/scripts/main.js
+++ b/website/scripts/main.js
@@ -4,7 +4,7 @@
* @import { Option, ResourceDataset, TimeScale, TimeRange, Unit, Marker, Weighted, DatasetPath, OHLC, FetchedJSON, DatasetValue, FetchedResult, AnyDatasetPath, SeriesBlueprint, BaselineSpecificSeriesBlueprint, CandlestickSpecificSeriesBlueprint, LineSpecificSeriesBlueprint, SpecificSeriesBlueprintWithChart, Signal, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, AnyPath, SimulationOption } from "./types/self"
* @import {createChart as CreateClassicChart, createChartEx as CreateCustomChart, LineStyleOptions} from "../packages/lightweight-charts/v4.2.0/types";
* @import * as _ from "../packages/ufuzzy/v1.0.14/types"
- * @import { DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, SingleValueData, ISeriesApi, Time, LogicalRange, SeriesMarker, CandlestickData, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v4.2.0/types"
+ * @import { DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, SingleValueData, ISeriesApi, Time, LineData, LogicalRange, SeriesMarker, CandlestickData, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v4.2.0/types"
* @import { DatePath, HeightPath, LastPath } from "./types/paths";
* @import { SignalOptions } from "../packages/solid-signals/2024-11-02/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/2024-11-02/types/core/owner"
@@ -19,7 +19,22 @@ function initPackages() {
createSolidSignal: /** @type {CreateSignal} */ (
_signals.createSignal
),
- createEffect: /** @type {CreateEffect} */ (_signals.createEffect),
+ createSolidEffect: /** @type {CreateEffect} */ (
+ _signals.createEffect
+ ),
+ createEffect: /** @type {CreateEffect} */ (compute, effect) => {
+ let dispose = /** @type {VoidFunction | null} */ (null);
+ // @ts-ignore
+ _signals.createEffect(compute, (v) => {
+ dispose?.();
+ signals.createRoot((_dispose) => {
+ dispose = _dispose;
+ effect(v);
+ });
+ signals.onCleanup(() => dispose?.());
+ });
+ signals.onCleanup(() => dispose?.());
+ },
createMemo: /** @type {CreateMemo} */ (_signals.createMemo),
createRoot: /** @type {CreateRoot} */ (_signals.createRoot),
getOwner: /** @type {GetOwner} */ (_signals.getOwner),
@@ -220,8 +235,15 @@ function initPackages() {
* @param {HTMLElement} args.element
* @param {Signals} args.signals
* @param {Colors} args.colors
+ * @param {DeepPartial} [args.options]
*/
- function createChart({ scale, element, signals, colors }) {
+ function createChart({
+ scale,
+ element,
+ signals,
+ colors,
+ options: _options = {},
+ }) {
/** @satisfies {DeepPartial} */
const options = {
autoSize: true,
@@ -257,6 +279,7 @@ function initPackages() {
}
: {}),
},
+ ..._options,
};
/** @type {IChartApi} */
@@ -597,7 +620,6 @@ function initPackages() {
let packagePromise = null;
return function () {
- let p = null;
if (!packagePromise) {
// @ts-ignore
packagePromise = imports[key]();
@@ -640,6 +662,20 @@ const utils = {
yield() {
return this.sleep(0);
},
+ array: {
+ /**
+ * @param {number} start
+ * @param {number} end
+ */
+ range(start, end) {
+ const range = [];
+ while (start <= end) {
+ range.push(start);
+ start += 1;
+ }
+ return range;
+ },
+ },
dom: {
/**
* @param {string} id
@@ -743,13 +779,16 @@ const utils = {
/**
* @param {Object} arg
* @param {string} arg.text
+ * @param {string} arg.title
* @param {VoidFunction} arg.onClick
*/
- createButtonElement({ text, onClick }) {
+ createButtonElement({ text, onClick, title }) {
const button = window.document.createElement("button");
button.innerHTML = text;
+ button.title = title;
+
button.addEventListener("click", onClick);
return button;
@@ -773,6 +812,8 @@ const utils = {
}) {
const label = window.document.createElement("label");
+ inputId = inputId.toLowerCase();
+
const input = window.document.createElement("input");
input.type = "radio";
input.name = inputName;
@@ -898,6 +939,130 @@ const utils = {
return field;
},
+ createUlElement() {
+ return window.document.createElement("ul");
+ },
+ createLiElement() {
+ return window.document.createElement("li");
+ },
+ /**
+ * @param {Object} args
+ * @param {string} args.id
+ * @param {string} args.title
+ * @param {Signal} args.signal
+ * @param {number} args.min
+ * @param {number} args.max
+ * @param {number} args.step
+ * @param {{createEffect: typeof CreateEffect}} args.signals
+ */
+ createInputNumberElement({ id, title, signal, min, max, step, signals }) {
+ const input = window.document.createElement("input");
+ input.id = id;
+ input.title = title;
+ input.type = "number";
+ input.min = String(min);
+ input.max = String(max);
+ input.step = String(step);
+
+ let stateValue = /** @type {string | null} */ (null);
+
+ signals.createEffect(
+ () => {
+ const value = signal();
+ return value ? String(value) : "";
+ },
+ (value) => {
+ if (stateValue !== value) {
+ input.value = value;
+ stateValue = value;
+ }
+ },
+ );
+
+ input.addEventListener("input", () => {
+ const valueSer = input.value;
+ const value = Number(valueSer);
+ if (value >= min && value <= max) {
+ stateValue = valueSer;
+ signal.set(value);
+ }
+ });
+
+ return input;
+ },
+ /**
+ * @param {Object} args
+ * @param {string} args.id
+ * @param {string} args.title
+ * @param {Signal} args.signal
+ * @param {{createEffect: typeof CreateEffect}} args.signals
+ */
+ createInputDate({ id, title, signal, signals }) {
+ const input = window.document.createElement("input");
+ input.id = id;
+ input.title = title;
+ input.type = "date";
+ const min = "2011-01-01";
+ const minDate = new Date(min);
+ const maxDate = new Date();
+ const max = utils.date.toString(maxDate);
+ input.min = min;
+ input.max = max;
+
+ let stateValue = /** @type {string | null} */ (null);
+
+ signals.createEffect(
+ () => {
+ const date = signal();
+ return date ? utils.date.toString(date) : "";
+ },
+ (value) => {
+ if (stateValue !== value) {
+ input.value = value;
+ stateValue = value;
+ }
+ },
+ );
+
+ input.addEventListener("change", () => {
+ const value = input.value;
+ const date = new Date(value);
+ if (date >= minDate && date <= maxDate) {
+ stateValue = value;
+ signal.set(value ? date : null);
+ }
+ });
+
+ return input;
+ },
+ /**
+ * @param {Object} param0
+ * @param {string} param0.title
+ * @param {string} param0.description
+ */
+ createHeader({ title, description }) {
+ const headerElement = window.document.createElement("header");
+
+ const div = window.document.createElement("div");
+ headerElement.append(div);
+
+ const h1 = window.document.createElement("h1");
+ div.append(h1);
+
+ const titleElement = window.document.createElement("span");
+ titleElement.append(title);
+ h1.append(titleElement);
+
+ const descriptionElement = window.document.createElement("small");
+ descriptionElement.append(description);
+ h1.append(descriptionElement);
+
+ return {
+ headerElement,
+ titleElement,
+ descriptionElement,
+ };
+ },
},
url: {
chartParamsWhitelist: ["from", "to"],
@@ -1124,6 +1289,20 @@ const utils = {
return Number(v);
},
},
+ date: {
+ /**
+ * @param {Date} v
+ */
+ serialize(v) {
+ return utils.date.toString(v);
+ },
+ /**
+ * @param {string} v
+ */
+ deserialize(v) {
+ return new Date(v);
+ },
+ },
},
formatters: {
dollars: new Intl.NumberFormat("en-US", {
@@ -1168,6 +1347,25 @@ const utils = {
: // @ts-ignore
new Date(time.year, time.month, time.day);
},
+ /**
+ * @param {Date} start
+ */
+ getRangeUpToToday(start) {
+ return this.getRange(start, new Date());
+ },
+ /**
+ * @param {Date} start
+ * @param {Date} end
+ */
+ getRange(start, end) {
+ const dates = /** @type {Date[]} */ ([]);
+ let currentDate = new Date(start);
+ while (currentDate <= end) {
+ dates.push(new Date(currentDate));
+ currentDate.setUTCDate(currentDate.getUTCDate() + 1);
+ }
+ return dates;
+ },
},
/**
*
@@ -1328,14 +1526,14 @@ const elements = {
selectedTitle: utils.dom.getElementById("selected-title"),
selectedDescription: utils.dom.getElementById("selected-description"),
selectors: utils.dom.getElementById("frame-selectors"),
- chartList: utils.dom.getElementById("chart-list"),
+ chartsChartList: utils.dom.getElementById("charts-chart-list"),
legend: utils.dom.getElementById("legend"),
style: getComputedStyle(window.document.documentElement),
- // timeScaleDateButtons: utils.dom.getElementById("timescale-date-buttons"),
- // timeScaleHeightButtons: utils.dom.getElementById("timescale-height-buttons"),
selectedHeader: utils.dom.getElementById("selected-header"),
charts: utils.dom.getElementById("charts"),
simulation: utils.dom.getElementById("simulation"),
+ livePrice: utils.dom.getElementById("live-price"),
+ moscowTime: utils.dom.getElementById("moscow-time"),
};
/** @typedef {typeof elements} Elements */
@@ -1951,6 +2149,21 @@ function createDatasets(signals) {
scale,
url: baseURL,
fetch: _fetch,
+ fetchRange(start, end) {
+ const promises = /** @type {Promise[]} */ ([]);
+ switch (scale) {
+ case "date": {
+ utils.array.range(start, end).forEach((year) => {
+ promises.push(this.fetch(year));
+ });
+ break;
+ }
+ default: {
+ throw "Unsupported";
+ }
+ }
+ return Promise.all(promises);
+ },
fetchedJSONs,
// drop() {
// dispose();
@@ -1985,7 +2198,7 @@ function createDatasets(signals) {
* @param {DatasetPath} path
* @returns {ResourceDataset}
*/
- function getOrImport(scale, path) {
+ function getOrCreate(scale, path) {
if (scale === "date") {
const found = date.get(/** @type {DatePath} */ (path));
if (found) return /** @type {ResourceDataset} */ (found);
@@ -2018,7 +2231,7 @@ function createDatasets(signals) {
}
return {
- getOrImport,
+ getOrCreate,
};
}
/** @typedef {ReturnType} Datasets */
@@ -2088,18 +2301,18 @@ function initWebSockets(signals) {
/**
* @param {(candle: DatasetCandlestickData) => void} callback
- * @returns
+ * @param {number} interval
*/
- function krakenCandleWebSocketCreator(callback) {
- const ws = new WebSocket("wss://ws.kraken.com");
+ function krakenCandleWebSocketCreator(callback, interval) {
+ const ws = new WebSocket("wss://ws.kraken.com/v2");
ws.addEventListener("open", () => {
ws.send(
JSON.stringify({
- event: "subscribe",
- pair: ["XBT/USD"],
- subscription: {
- name: "ohlc",
+ method: "subscribe",
+ params: {
+ channel: "ohlc",
+ symbol: ["BTC/USD"],
interval: 1440,
},
}),
@@ -2109,11 +2322,11 @@ function initWebSockets(signals) {
ws.addEventListener("message", (message) => {
const result = JSON.parse(message.data);
- if (!Array.isArray(result)) return;
+ if (result.channel !== "ohlc") return;
- const [timestamp, _, open, high, low, close, __, volume] = result[1];
+ const { timestamp, open, high, low, close } = result.data.at(-1);
- const date = new Date(Number(timestamp) * 1000);
+ const date = new Date(timestamp);
const dateStr = utils.date.toString(date);
@@ -2134,12 +2347,18 @@ function initWebSockets(signals) {
return ws;
}
- const krakenCandle = createWebsocket(krakenCandleWebSocketCreator);
+ const kraken1dCandle = createWebsocket((callback) =>
+ krakenCandleWebSocketCreator(callback, 1440),
+ );
+ const kraken5mnCandle = createWebsocket((callback) =>
+ krakenCandleWebSocketCreator(callback, 5),
+ );
- krakenCandle.open();
+ kraken1dCandle.open();
+ kraken5mnCandle.open();
function createDocumentTitleEffect() {
- signals.createEffect(krakenCandle.latest, (latest) => {
+ signals.createEffect(kraken5mnCandle.latest, (latest) => {
if (latest) {
const close = latest.close;
console.log("close:", close);
@@ -2153,7 +2372,8 @@ function initWebSockets(signals) {
createDocumentTitleEffect();
return {
- krakenCandle,
+ kraken1dCandle,
+ // kraken5mnCandle,
};
}
/** @typedef {ReturnType} WebSockets */
@@ -2252,6 +2472,8 @@ packages.signals().then((signals) =>
);
let firstChartOption = true;
let firstSimulationOption = true;
+ let firstLivePriceOption = true;
+ let firstMoscowTimeOption = true;
signals.createEffect(options.selected, (option) => {
if (previousElement) {
@@ -2262,7 +2484,13 @@ packages.signals().then((signals) =>
utils.url.replaceHistory({ pathname: option.id });
}
- const hideTop = option.kind === "home" || option.kind === "pdf";
+ const hideTop =
+ option.kind === "home" ||
+ option.kind === "pdf" ||
+ option.kind === "live-price" ||
+ option.kind === "converter" ||
+ option.kind === "moscow-time";
+
elements.selectedHeader.hidden = hideTop;
elements.selectedTitle.innerHTML = option.title;
@@ -2277,6 +2505,8 @@ packages.signals().then((signals) =>
// break;
// }
case "chart": {
+ console.log("chart", option);
+
element = elements.charts;
lastChartOption.set(option);
@@ -2333,7 +2563,7 @@ packages.signals().then((signals) =>
ids,
lightweightCharts,
options,
- selected: /** @type {any} */ (lastChartOption),
+ selected: option,
signals,
utils,
webSockets,
@@ -2347,7 +2577,77 @@ packages.signals().then((signals) =>
break;
}
- default: {
+ case "live-price": {
+ console.log("live-price");
+
+ element = elements.livePrice;
+
+ if (firstLivePriceOption) {
+ const lightweightCharts = packages.lightweightCharts();
+ const script = import("./live-price.js");
+
+ utils.dom.importStyleAndThen("/styles/live-price.css", () =>
+ script.then(({ init }) =>
+ lightweightCharts.then((lightweightCharts) =>
+ signals.runWithOwner(owner, () =>
+ init({
+ colors,
+ consts,
+ dark,
+ datasets,
+ elements,
+ ids,
+ lightweightCharts,
+ options,
+ signals,
+ utils,
+ webSockets,
+ }),
+ ),
+ ),
+ ),
+ );
+ }
+ firstLivePriceOption = false;
+
+ break;
+ }
+ case "moscow-time": {
+ console.log("moscow-time");
+
+ element = elements.moscowTime;
+
+ if (firstLivePriceOption) {
+ const lightweightCharts = packages.lightweightCharts();
+ const script = import("./moscow-time.js");
+
+ utils.dom.importStyleAndThen("/styles/moscow-time.css", () =>
+ script.then(({ init }) =>
+ signals.runWithOwner(owner, () =>
+ init({
+ colors,
+ consts,
+ dark,
+ datasets,
+ elements,
+ ids,
+ options,
+ signals,
+ utils,
+ webSockets,
+ }),
+ ),
+ ),
+ );
+ }
+ firstLivePriceOption = false;
+
+ break;
+ }
+ case "converter":
+ case "home":
+ case "pdf":
+ case "url": {
return;
}
}
diff --git a/website/scripts/moscow-time.js b/website/scripts/moscow-time.js
new file mode 100644
index 000000000..0d40137ba
--- /dev/null
+++ b/website/scripts/moscow-time.js
@@ -0,0 +1,45 @@
+/**
+ * @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} 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);
+ });
+}
diff --git a/website/scripts/options.js b/website/scripts/options.js
index 3e5e78ae3..ed603aaf9 100644
--- a/website/scripts/options.js
+++ b/website/scripts/options.js
@@ -1853,436 +1853,528 @@ function createPartialOptions(colors) {
{
name: "Coinbases",
tree: [
- ...(scale === "date"
- ? /** @satisfies {PartialOptionsTree} */ ([
- {
- scale,
- name: "Daily Sum",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Daily Sum Of Coinbases In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Sum",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-coinbase-1d-sum`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Daily Sum Of Coinbases In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Sum",
- color: colors.dollars,
- datasetPath: `${scale}-to-coinbase-in-dollars-1d-sum`,
- },
- ],
- },
- ],
- },
- {
- scale,
- name: "Yearly Sum",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Yearly Sum Of Coinbases In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Sum",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-coinbase-1y-sum`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Yearly Sum Of Coinbases In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Sum",
- color: colors.dollars,
- datasetPath: `${scale}-to-coinbase-in-dollars-1y-sum`,
- },
- ],
- },
- ],
- },
- {
- scale,
- name: "Cumulative",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Cumulative Coinbases In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Coinbases",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-cumulative-coinbase`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Cumulative Coinbases In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Coinbases",
- color: colors.dollars,
- datasetPath: `${scale}-to-cumulative-coinbase-in-dollars`,
- },
- ],
- },
- ],
- },
- {
- name: "Last Block",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Last Coinbase In Bitcoin",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Last",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-last-coinbase`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Last Coinbase In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Last",
- color: colors.dollars,
- datasetPath: `${scale}-to-last-coinbase-in-dollars`,
- },
- ],
- },
- ],
- },
- ])
- : []),
+ .../** @satisfies {PartialOptionsTree} */ (
+ scale === "date"
+ ? [
+ {
+ scale,
+ name: "Daily Sum",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Daily Sum Of Coinbases In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-coinbase-1d-sum`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Daily Sum Of Coinbases In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-coinbase-in-dollars-1d-sum`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ scale,
+ name: "Yearly Sum",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Yearly Sum Of Coinbases In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-coinbase-1y-sum`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Yearly Sum Of Coinbases In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-coinbase-in-dollars-1y-sum`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ scale,
+ name: "Cumulative",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Cumulative Coinbases In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Coinbases",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-cumulative-coinbase`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Cumulative Coinbases In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Coinbases",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-cumulative-coinbase-in-dollars`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: "Last Block",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Last Coinbase In Bitcoin",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Last",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-last-coinbase`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Last Coinbase In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Last",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-last-coinbase-in-dollars`,
+ },
+ ],
+ },
+ ],
+ },
+ ]
+ : [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Coinbases In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Coinbase",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-coinbase`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Coinbases In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Coinbase",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-coinbase-in-dollars`,
+ },
+ ],
+ },
+ ]
+ ),
],
},
{
name: "Subsidies",
tree: [
- ...(scale === "date"
- ? /** @type {PartialOptionsTree} */ ([
- {
- scale,
- name: "Daily Sum",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Daily Sum Of Subsidies In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Sum",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-subsidy`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Daily Sum Of Subsidies In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Sum",
- color: colors.dollars,
- datasetPath: `${scale}-to-subsidy-in-dollars`,
- },
- ],
- },
- ],
- },
- {
- scale,
- name: "Yearly Sum",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Yearly Sum Of Subsidies In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Sum",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-subsidy-1y-sum`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Yearly Sum Of Subsidies In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Sum",
- color: colors.dollars,
- datasetPath: `${scale}-to-subsidy-in-dollars-1y-sum`,
- },
- ],
- },
- ],
- },
- {
- scale,
- name: "Cumulative",
-
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Cumulative Subsidies In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Subsidies",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-cumulative-subsidy`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Cumulative Subsidies In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Subsidies",
- color: colors.dollars,
- datasetPath: `${scale}-to-cumulative-subsidy-in-dollars`,
- },
- ],
- },
- ],
- },
- {
- name: "Last Block",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Last Subsidy In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Last",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-last-subsidy`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Last Subsidy In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Last",
- color: colors.dollars,
- datasetPath: `${scale}-to-last-subsidy-in-dollars`,
- },
- ],
- },
- ],
- },
- ])
- : []),
+ .../** @satisfies {PartialOptionsTree} */ (
+ scale === "date"
+ ? [
+ {
+ scale,
+ name: "Daily Sum",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Daily Sum Of Subsidies In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-subsidy-1d-sum`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Daily Sum Of Subsidies In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-subsidy-in-dollars-1d-sum`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ scale,
+ name: "Yearly Sum",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Yearly Sum Of Subsidies In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-subsidy-1y-sum`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Yearly Sum Of Subsidies In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-subsidy-in-dollars-1y-sum`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ scale,
+ name: "Cumulative",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Cumulative Subsidies In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Subsidies",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-cumulative-subsidy`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Cumulative Subsidies In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Subsidies",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-cumulative-subsidy-in-dollars`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: "Last Block",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Last Subsidy In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Last",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-last-subsidy`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Last Subsidy In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Last",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-last-subsidy-in-dollars`,
+ },
+ ],
+ },
+ ],
+ },
+ ]
+ : [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Subsidies In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-subsidy`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Subsidies In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-subsidy-in-dollars`,
+ },
+ ],
+ },
+ ]
+ ),
],
},
{
name: "Fees",
tree: [
- ...(scale === "date"
- ? /** @type {PartialOptionsTree} */ ([
- {
- scale,
- name: "Daily Sum",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Daily Sum Of Fees In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Sum",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-fees`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Daily Sum Of Fees In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Sum",
- color: colors.dollars,
- datasetPath: `${scale}-to-fees-in-dollars-1d-sum`,
- },
- ],
- },
- ],
- },
- {
- scale,
- name: "Yearly Sum",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Yearly Sum Of Fees In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Sum",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-fees-1y-sum`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Yearly Sum Of Fees In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Sum",
- color: colors.dollars,
- datasetPath: `${scale}-to-fees-in-dollars-1y-sum`,
- },
- ],
- },
- ],
- },
- {
- scale,
- name: "Cumulative",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Cumulative Fees In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Fees",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-cumulative-fees`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Cumulative Fees In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Fees",
- color: colors.dollars,
- datasetPath: `${scale}-to-cumulative-fees-in-dollars`,
- },
- ],
- },
- ],
- },
- {
- name: "Last Block",
- tree: [
- {
- scale,
- name: "In Bitcoin",
- title: "Last Fees In Bitcoin",
- description: "",
- unit: "Bitcoin",
- bottom: [
- {
- title: "Last",
- color: colors.bitcoin,
- datasetPath: `${scale}-to-last-fees`,
- },
- ],
- },
- {
- scale,
- name: "In Dollars",
- title: "Last Fees In Dollars",
- description: "",
- unit: "US Dollars",
- bottom: [
- {
- title: "Last",
- color: colors.dollars,
- datasetPath: `${scale}-to-last-fees-in-dollars`,
- },
- ],
- },
- ],
- },
- ])
- : []),
+ .../** @satisfies {PartialOptionsTree} */ (
+ scale === "date"
+ ? [
+ {
+ scale,
+ name: "Daily Sum",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Daily Sum Of Fees In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-fees-1d-sum`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Daily Sum Of Fees In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-fees-in-dollars-1d-sum`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ scale,
+ name: "Yearly Sum",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Yearly Sum Of Fees In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-fees-1y-sum`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Yearly Sum Of Fees In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-fees-in-dollars-1y-sum`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ scale,
+ name: "Cumulative",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Cumulative Fees In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Fees",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-cumulative-fees`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Cumulative Fees In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Fees",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-cumulative-fees-in-dollars`,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ name: "Last Block",
+ tree: [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Last Fees In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Last",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-last-fees`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Last Fees In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Last",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-last-fees-in-dollars`,
+ },
+ ],
+ },
+ ],
+ },
+ ]
+ : [
+ {
+ scale,
+ name: "In Bitcoin",
+ title: "Fees In Bitcoin",
+ description: "",
+ unit: "Bitcoin",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.bitcoin,
+ datasetPath: `${scale}-to-fees`,
+ },
+ ],
+ },
+ {
+ scale,
+ name: "In Dollars",
+ title: "Fees In Dollars",
+ description: "",
+ unit: "US Dollars",
+ bottom: [
+ {
+ title: "Sum",
+ color: colors.dollars,
+ datasetPath: `${scale}-to-fees-in-dollars`,
+ },
+ ],
+ },
+ ]
+ ),
],
},
@@ -4835,6 +4927,23 @@ function createPartialOptions(colors) {
}
return [
+ // {
+ // name: "Live",
+ // tree: [
+ // {
+ // kind: "live-price",
+ // name: "Price",
+ // },
+ // {
+ // kind: "moscow-time",
+ // name: "Moscow Time",
+ // },
+ // {
+ // kind: "converter",
+ // name: "Converter",
+ // },
+ // ],
+ // },
{
name: "Charts",
tree: [
@@ -4965,21 +5074,25 @@ function createPartialOptions(colors) {
url: () => "bitcoin:bc1q950q4ukpxxm6wjjkv6cpq8jzpazaxrrwftctkt",
},
{
- name: "Donate lightning",
+ name: "Donate via lightning",
qrcode: true,
url: () =>
"lightning:lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkxmmww3jkuar8d35kgetj8yuq363hv4",
},
{
- name: "Fundraiser",
+ name: "Donate via Geyser",
+ url: () => "https://geyser.fund/project/kibo/funding",
+ },
+ {
+ name: "Geyser Fundraiser",
url: () => "https://geyser.fund/project/kibo",
},
{
- name: "Goals",
+ name: "Geyser Goals",
url: () => "https://geyser.fund/project/kibo/goals",
},
{
- name: "Leaderboard",
+ name: "Geyser Leaderboard",
url: () => "https://geyser.fund/project/kibo/leaderboard",
},
],
@@ -4995,12 +5108,17 @@ function createPartialOptions(colors) {
"https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44",
},
{
- name: "Source",
- url: () => "https://github.com/kibo-money/kibo",
- },
- {
- name: "API",
- url: () => "/api",
+ name: "Developers",
+ tree: [
+ {
+ name: "API",
+ url: () => "/api",
+ },
+ {
+ name: "Source",
+ url: () => "https://github.com/kibo-money/kibo",
+ },
+ ],
},
];
}
@@ -5142,6 +5260,7 @@ export function initOptions({
if (option.qrcode) {
return utils.dom.createButtonElement({
text: option.name,
+ title: option.title,
onClick: () => {
qrcode.set(option.url);
},
@@ -5189,7 +5308,8 @@ export function initOptions({
signals.createEffect(
() =>
- webSockets.krakenCandle.latest()?.close ?? lastValues()?.close,
+ webSockets.kraken1dCandle.latest()?.close ??
+ lastValues()?.close,
(close) => {
if (close) {
valueElement.innerHTML = formatValue(close, "US Dollars");
@@ -5248,11 +5368,11 @@ export function initOptions({
} else {
createCheckEffect();
}
-
- return label;
} else {
- console.log(option);
+ console.log("else", option);
}
+
+ return label;
}
}
}
@@ -5416,10 +5536,9 @@ export function initOptions({
title = anyPartial.title;
} else {
kind = anyPartial.kind;
- title = anyPartial.title;
+ title = "title" in anyPartial ? anyPartial.title : anyPartial.name;
console.log("Unprocessed", anyPartial);
- id = `${kind}-${ids.fromString(anyPartial.title)}`;
- // return;
+ id = `${kind}-${ids.fromString(title)}`;
}
/** @type {ProcessedOptionAddons} */
@@ -5465,9 +5584,7 @@ export function initOptions({
qrcode,
});
- if (element) {
- li.append(element);
- }
+ li.append(element);
});
listForSum.push(() => 1);
diff --git a/website/scripts/simulation.js b/website/scripts/simulation.js
index 6affaa992..ae2e81145 100644
--- a/website/scripts/simulation.js
+++ b/website/scripts/simulation.js
@@ -7,7 +7,7 @@
* @param {Colors} args.colors
* @param {Consts} args.consts
* @param {LightweightCharts} args.lightweightCharts
- * @param {Accessor} args.selected
+ * @param {SimulationOption} args.selected
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Options} args.options
@@ -38,22 +38,27 @@ export function init({
const resultsElement = window.document.createElement("div");
simulationElement.append(resultsElement);
+ const getDefaultIntervalStart = () => new Date("2021-04-15");
+ const getDefaultIntervalEnd = () => new Date();
+
const storagePrefix = "save-in-bitcoin";
const settings = {
- initial: signals.createSignal(/** @type {number | null} */ (1000), {
- save: {
- ...utils.serde.number,
- id: `${storagePrefix}-initial-amount`,
- param: "initial-amount",
- },
- }),
- later: signals.createSignal(/** @type {number | null} */ (0), {
- save: {
- ...utils.serde.number,
- id: `${storagePrefix}-later-amount`,
- param: "later-amount",
- },
- }),
+ initial: {
+ firstDay: signals.createSignal(/** @type {number | null} */ (1000), {
+ save: {
+ ...utils.serde.number,
+ id: `${storagePrefix}-initial-amount`,
+ param: "initial-amount",
+ },
+ }),
+ overTime: signals.createSignal(/** @type {number | null} */ (0), {
+ save: {
+ ...utils.serde.number,
+ id: `${storagePrefix}-later-amount`,
+ param: "later-amount",
+ },
+ }),
+ },
recurrent: {
amount: signals.createSignal(/** @type {number | null} */ (100), {
save: {
@@ -63,8 +68,45 @@ export function init({
},
}),
},
+ interval: {
+ start: signals.createSignal(
+ /** @type {Date | null} */ (getDefaultIntervalStart()),
+ {
+ save: {
+ ...utils.serde.date,
+ id: `${storagePrefix}-interval-start`,
+ param: "interval-start",
+ },
+ },
+ ),
+ end: signals.createSignal(
+ /** @type {Date | null} */ (getDefaultIntervalEnd()),
+ {
+ save: {
+ ...utils.serde.date,
+ id: `${storagePrefix}-interval-end`,
+ param: "interval-end",
+ },
+ },
+ ),
+ },
+ fees: {
+ percentage: signals.createSignal(/** @type {number | null} */ (0.25), {
+ save: {
+ ...utils.serde.number,
+ id: `${storagePrefix}-percentage`,
+ param: "percentage",
+ },
+ }),
+ },
};
+ const { headerElement } = utils.dom.createHeader({
+ title: selected.title,
+ description: selected.serializedPath,
+ });
+ parametersElement.append(headerElement);
+
const initialGroup = createParameterGroup({
title: "Initial",
description:
@@ -78,7 +120,7 @@ export function init({
input: createInputDollar({
id: "simulation-dollars-initial",
title: "Initial amount of dollars converted",
- signal: settings.initial,
+ signal: settings.initial.firstDay,
}),
}),
);
@@ -89,12 +131,17 @@ export function init({
input: createInputDollar({
id: "simulation-dollars-later",
title: "Dollars to spread over time",
- signal: settings.later,
+ signal: settings.initial.overTime,
}),
}),
);
- parametersElement.append(createHrElement());
+ const topUpGroup = createParameterGroup({
+ title: "Top Up",
+ description:
+ "The topUp amount of dollars you're willing to eventually save in Bitcoin.",
+ });
+ parametersElement.append(topUpGroup);
const recurrentGroup = createParameterGroup({
title: "Recurrent",
@@ -105,7 +152,7 @@ export function init({
recurrentGroup.append(
createInputField({
- name: "Amount",
+ name: "Maximum Amount",
input: createInputDollar({
id: "simulation-dollars-recurrent",
title: "Recurrent dollar amount",
@@ -114,34 +161,92 @@ export function init({
}),
);
- const frequencyUL = appendUl({ parent: recurrentGroup });
-
- [{ name: "Daily" }, { name: "Weekly" }, { name: "Monthly" }].forEach(
- ({ name }) => {
- const li = appendLi({ name, parent: frequencyUL });
- },
- );
-
- const frequencyChoiceUL = appendUl({ parent: recurrentGroup });
+ const frequencyUL = utils.dom.createUlElement();
+ recurrentGroup.append(frequencyUL);
[
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday",
- "Sunday",
- ].forEach((name) => {
- const li = appendLi({ name, parent: frequencyChoiceUL });
+ { name: "Daily" },
+ {
+ name: "Weekly",
+ sub: [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ],
+ },
+ {
+ name: "Monthly",
+ sub: [
+ "The 1st",
+ "The 2nd",
+ "The 3rd",
+ "The 4th",
+ "The 5th",
+ "The 6th",
+ "The 7th",
+ "The 8th",
+ "The 9th",
+ "The 10th",
+ "The 11th",
+ "The 12th",
+ "The 13th",
+ "The 14th",
+ "The 15th",
+ "The 16th",
+ "The 17th",
+ "The 18th",
+ "The 19th",
+ "The 20th",
+ "The 21st",
+ "The 22nd",
+ "The 23rd",
+ "The 24th",
+ "The 25th",
+ "The 26th",
+ "The 27th",
+ "The 28th",
+ ],
+ },
+ ].forEach(({ name, sub }, index) => {
+ const li = utils.dom.createLiElement();
+ const { label, input } = utils.dom.createLabeledInput({
+ inputId: `frequency-${name}`,
+ inputName: "frequency",
+ inputValue: name.toLowerCase(),
+ labelTitle: name,
+ inputChecked: !index,
+ onClick: () => {},
+ });
+ label.append(name);
+ li.append(label);
+ if (sub) {
+ const parentName = name;
+ const ul = utils.dom.createUlElement();
+ li.append(ul);
+ sub.forEach((name) => {
+ const li = utils.dom.createLiElement();
+ const { label, input } = utils.dom.createLabeledInput({
+ inputId: `frequency-${parentName}-${name}`,
+ inputName: `frequency-${parentName}`,
+ inputValue: name.toLowerCase(),
+ labelTitle: name,
+ inputChecked: !index,
+ onClick: () => {},
+ });
+ label.append(name);
+ li.append(label);
+ ul.append(li);
+ });
+ }
+ frequencyUL.append(li);
});
- parametersElement.append(createHrElement());
-
- const today = signals.createSignal(utils.date.todayUTC());
- setInterval(() => {
- today.set(utils.date.todayUTC());
- }, consts.FIVE_SECONDS_IN_MS);
+ const frequencyChoiceUL = utils.dom.createUlElement();
+ recurrentGroup.append(frequencyChoiceUL);
const intervalGroup = createParameterGroup({
title: "Interval",
@@ -149,42 +254,22 @@ export function init({
});
parametersElement.append(intervalGroup);
- /**
- * @param {Object} args
- * @param {HTMLElement} args.parent
- */
- function appendDiv({ parent }) {
- const div = window.document.createElement("div");
- parent.append(div);
- return div;
- }
+ console.log("weofpwklfpwkofwepokf");
- function createInputDateField() {
- const div = appendDiv({ parent: intervalGroup });
-
- appendInputDate({
- id: "",
- title: "",
- value: "2021-04-15",
- parent: div,
- signals,
- today,
- });
-
- appendButton({
- onClick: () => {},
- text: "Reset",
- title: "",
- parent: div,
- });
-
- return div;
- }
-
- createInputDateField();
- createInputDateField();
-
- parametersElement.append(createHrElement());
+ createInputDateField({
+ signal: settings.interval.start,
+ getDefault: getDefaultIntervalStart,
+ parent: intervalGroup,
+ signals,
+ utils,
+ });
+ createInputDateField({
+ signal: settings.interval.end,
+ getDefault: getDefaultIntervalEnd,
+ parent: intervalGroup,
+ signals,
+ utils,
+ });
const feesGroup = createParameterGroup({
title: "Fees",
@@ -193,39 +278,275 @@ export function init({
});
parametersElement.append(feesGroup);
- createInputNumber({
+ const input = utils.dom.createInputNumberElement({
id: "",
title: "",
- value: 0.25,
- parent: feesGroup,
+ signal: settings.fees.percentage,
min: 0,
- max: 10,
+ max: 50,
+ step: 0.01,
+ signals,
+ });
+ feesGroup.append(input);
+
+ // parametersElement.append(utils.dom.createHrElement());
+
+ // const strategyGroup = createParameterGroup({
+ // title: "Strategy",
+ // description: "The strategy used to convert your fiat into Bitcoin",
+ // });
+ // parametersElement.append(strategyGroup);
+
+ // const ulStrategies = utils.dom.createUlElement();
+ // strategyGroup.append(ulStrategies);
+
+ // ["All in", "Weighted Local", "Weighted Cycle"].forEach((strategy) => {
+ // const li = utils.dom.createLiElement();
+ // li.append(strategy);
+ // ulStrategies.append(li);
+ // });
+
+ const parent = window.document.createElement("div");
+ parent.classList.add("chart-list");
+ resultsElement.append(parent);
+
+ signals.createEffect(settings.interval.start, (start) => {
+ console.log("start", start);
});
- parametersElement.append(createHrElement());
-
- const strategyGroup = createParameterGroup({
- title: "Strategy",
- description: "The strategy used to convert your fiat into Bitcoin",
+ signals.createEffect(settings.interval.end, (end) => {
+ console.log("end", end);
});
- parametersElement.append(strategyGroup);
- const ulStrategies = appendUl({ parent: strategyGroup });
+ const owner = signals.getOwner();
- ["All in", "Weighted Local", "Weighted Cycle"].forEach((strategy) => {
- appendLi({
- name: strategy,
- parent: ulStrategies,
+ const closes = datasets.getOrCreate("date", "date-to-close");
+ closes.fetchRange(2009, new Date().getUTCFullYear()).then(() => {
+ signals.runWithOwner(owner, () => {
+ signals.createEffect(
+ () => ({
+ initialAmount: settings.initial.firstDay() || 0,
+ recurrentAmount: settings.recurrent.amount() || 0,
+ dollarsLeft: settings.initial.overTime() || 0,
+ start: settings.interval.start(),
+ end: settings.interval.end(),
+ fees: settings.fees.percentage(),
+ }),
+ // ({ initialAmount, recurrentAmount, dollarsLeft, start, end }) => {
+ // console.log({
+ // start,
+ // end,
+ // });
+ // },
+ ({ initialAmount, recurrentAmount, dollarsLeft, start, end, fees }) => {
+ console.log({ start, end });
+ parent.innerHTML = "";
+
+ if (!start || !end || start > end) return;
+
+ const range = utils.date.getRange(start, end);
+
+ let investedAmount = 0;
+
+ /** @type {LineData[]} */
+ const investedData = [];
+ /** @type {LineData[]} */
+ const returnData = [];
+ /** @type {LineData[]} */
+ const bitcoinData = [];
+ /** @type {LineData[]} */
+ const resultData = [];
+ /** @type {LineData[]} */
+ const dollarsData = [];
+ /** @type {LineData[]} */
+ const totalData = [];
+ /** @type {LineData[]} */
+ const investmentData = [];
+ /** @type {LineData[]} */
+ const bitcoinAddedData = [];
+
+ let bitcoin = 0;
+
+ let feesPaid = 0;
+
+ range.forEach((date, index) => {
+ const year = date.getUTCFullYear();
+ const time = utils.date.toString(date);
+
+ const close = closes.fetchedJSONs
+ .at(utils.chunkIdToIndex("date", year))
+ ?.json()?.dataset.map[utils.date.toString(date)];
+
+ if (!close) return;
+
+ let investmentPreFees =
+ (!index ? initialAmount : 0) + recurrentAmount;
+
+ if (dollarsLeft > 0) {
+ if (dollarsLeft >= recurrentAmount) {
+ investmentPreFees += recurrentAmount;
+ dollarsLeft -= recurrentAmount;
+ } else {
+ investmentPreFees += dollarsLeft;
+ dollarsLeft = 0;
+ }
+ }
+
+ let investment = investmentPreFees * (1 - (fees || 0) / 100);
+ feesPaid += investmentPreFees - investment;
+
+ const bitcoinAdded = investment / close;
+ bitcoin += bitcoinAdded;
+
+ investedAmount += investment;
+
+ const _return = close * bitcoin;
+
+ bitcoinData.push({
+ time,
+ value: bitcoin,
+ });
+
+ investedData.push({
+ time,
+ value: investedAmount,
+ });
+
+ returnData.push({
+ time,
+ value: _return,
+ });
+
+ resultData.push({
+ time,
+ value: (_return / investedAmount - 1) * 100,
+ });
+
+ dollarsData.push({
+ time,
+ value: dollarsLeft,
+ });
+
+ totalData.push({
+ time,
+ value: dollarsLeft + _return,
+ });
+
+ investmentData.push({
+ time,
+ value: investment,
+ });
+
+ bitcoinAddedData.push({
+ time,
+ value: bitcoinAdded,
+ });
+ });
+
+ (() => {
+ const chartWrapper = window.document.createElement("div");
+ chartWrapper.classList.add("chart-wrapper");
+ parent.append(chartWrapper);
+
+ const chartDiv = window.document.createElement("div");
+ chartDiv.classList.add("chart-div");
+ chartWrapper.append(chartDiv);
+
+ const chart = lightweightCharts.createChart({
+ scale: "date",
+ element: chartDiv,
+ signals,
+ colors,
+ options: {
+ handleScale: false,
+ handleScroll: false,
+ },
+ });
+
+ const line = chart.addLineSeries();
+
+ line.setData(investedData);
+
+ const line2 = chart.addLineSeries();
+
+ line2.setData(returnData);
+
+ const line3 = chart.addLineSeries();
+
+ line3.setData(dollarsData);
+
+ const line4 = chart.addLineSeries();
+
+ line4.setData(totalData);
+
+ const line5 = chart.addLineSeries();
+
+ line5.setData(investmentData);
+
+ chart.timeScale().fitContent();
+ })();
+
+ (() => {
+ const chartWrapper = window.document.createElement("div");
+ chartWrapper.classList.add("chart-wrapper");
+ parent.append(chartWrapper);
+
+ const chartDiv = window.document.createElement("div");
+ chartDiv.classList.add("chart-div");
+ chartWrapper.append(chartDiv);
+
+ const chart = lightweightCharts.createChart({
+ scale: "date",
+ element: chartDiv,
+ signals,
+ colors,
+ options: {
+ handleScale: false,
+ handleScroll: false,
+ },
+ });
+
+ const line = chart.addLineSeries();
+
+ line.setData(bitcoinData);
+
+ const line2 = chart.addLineSeries();
+
+ line2.setData(bitcoinAddedData);
+
+ chart.timeScale().fitContent();
+ })();
+
+ (() => {
+ const chartWrapper = window.document.createElement("div");
+ chartWrapper.classList.add("chart-wrapper");
+ parent.append(chartWrapper);
+
+ const chartDiv = window.document.createElement("div");
+ chartDiv.classList.add("chart-div");
+ chartWrapper.append(chartDiv);
+
+ const chart = lightweightCharts.createChart({
+ scale: "date",
+ element: chartDiv,
+ signals,
+ colors,
+ options: {
+ handleScale: false,
+ handleScroll: false,
+ },
+ });
+
+ const line = chart.addLineSeries();
+
+ line.setData(resultData);
+
+ chart.timeScale().fitContent();
+ })();
+ },
+ );
});
});
-
- //
- // On the side
- // Value in Bitcoin
- // Value in Dollars + total converted
- //
- // Value min estimated value in 4 years
- //
}
/**
@@ -269,31 +590,6 @@ function createParameterGroup({ title, description }) {
return div;
}
-function createHrElement() {
- return window.document.createElement("hr");
-}
-
-/**
- *@param {Object} args
- *@param {string} args.id
- *@param {string} args.title
- *@param {number} args.value
- *@param {HTMLElement} args.parent
- *@param {number} args.min
- *@param {number} args.max
- */
-function createInputNumber({ id, title, value, parent, min, max }) {
- const input = window.document.createElement("input");
- input.id = id;
- input.title = title;
- input.type = "number";
- input.value = String(value);
- input.min = String(min);
- input.max = String(max);
- parent.append(input);
- return input;
-}
-
/**
* @param {Object} args
* @param {string} args.id
@@ -320,90 +616,36 @@ function createInputDollar({ id, title, signal }) {
}
/**
- * @param {Object} args
- * @param {string} args.id
- * @param {string} args.title
- * @param {Signal} args.signal
+ *
+ * @param {Object} arg
+ * @param {Signal} arg.signal
+ * @param {() => Date | null} arg.getDefault
+ * @param {HTMLElement} arg.parent
+ * @param {Utilities} arg.utils
+ * @param {Signals} arg.signals
*/
-function createInputRangePercentage({ id, title, signal }) {
- const input = window.document.createElement("input");
- input.id = id;
- input.title = title;
- input.type = "range";
- input.min = "0";
- input.max = "100";
+function createInputDateField({ signal, getDefault, parent, signals, utils }) {
+ const div = window.document.createElement("div");
+ parent.append(div);
- const value = signal();
- input.value = value !== null ? String(value) : "";
+ div.append(
+ utils.dom.createInputDate({
+ id: "",
+ title: "",
+ signal,
+ signals,
+ }),
+ );
- input.addEventListener("input", () => {
- const value = input.value;
- signal.set(value ? Number(value) : null);
+ const button = utils.dom.createButtonElement({
+ onClick: () => {
+ signal.set(getDefault());
+ },
+ text: "Reset",
+ title: "Reset field",
});
- return input;
-}
-
-/**
- * @param {Object} args
- * @param {HTMLElement} args.parent
- */
-function appendUl({ parent }) {
- const ul = window.document.createElement("ul");
- parent.append(ul);
- return ul;
-}
-
-/**
- * @param {Object} args
- * @param {string} args.name
- * @param {HTMLUListElement} args.parent
- */
-function appendLi({ name, parent }) {
- const li = window.document.createElement("li");
- li.innerHTML = name;
- parent.append(li);
- return li;
-}
-
-/**
- *@param {Object} args
- *@param {string} args.id
- *@param {string} args.title
- *@param {string} args.value
- *@param {HTMLElement} args.parent
- *@param {Accessor} args.today
- *@param {Signals} args.signals
- */
-function appendInputDate({ id, title, value, parent, today, signals }) {
- const input = window.document.createElement("input");
- input.id = id;
- input.title = title;
- input.type = "date";
- input.value = value;
-
- input.min = "2011-01-01";
-
- signals.createEffect(today, (today) => {
- input.max = today.toJSON().split("T")[0];
- });
-
- parent.append(input);
- return input;
-}
-
-/**
- *@param {Object} args
- *@param {string} args.title
- *@param {string} args.text
- *@param {HTMLElement} args.parent
- *@param {VoidFunction} args.onClick
- */
-function appendButton({ title, text, onClick, parent }) {
- const button = window.document.createElement("button");
- button.title = title;
- button.innerHTML = text;
- button.addEventListener("click", onClick);
- parent.append(button);
- return button;
+ div.append(button);
+
+ return div;
}
diff --git a/website/scripts/types/self.d.ts b/website/scripts/types/self.d.ts
index 93be593e6..443888ebf 100644
--- a/website/scripts/types/self.d.ts
+++ b/website/scripts/types/self.d.ts
@@ -101,7 +101,6 @@ type Unit =
| "Weight";
interface PartialOption {
- // icon: string;
name: string;
}
@@ -111,6 +110,18 @@ interface PartialHomeOption extends PartialOption {
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;
@@ -147,6 +158,9 @@ interface PartialOptionsGroup {
type AnyPartialOption =
| PartialHomeOption
+ | PartialLivePriceOption
+ | PartialMoscowTimeOption
+ | PartialConverterOption
| PartialChartOption
| PartialSimulationOption
| PartialPdfOption
@@ -168,6 +182,9 @@ type OptionPath = {
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";
@@ -183,6 +200,9 @@ interface ChartOption extends PartialChartOption, ProcessedOptionAddons {
type Option =
| HomeOption
+ | LivePriceOption
+ | MoscowTimeOption
+ | ConverterOption
| PdfOption
| UrlOption
| ChartOption
@@ -208,7 +228,8 @@ interface ResourceDataset<
> {
scale: Scale;
url: string;
- fetch: (id: number) => void;
+ fetch: (id: number) => Promise;
+ fetchRange: (start: number, end: number) => Promise;
fetchedJSONs: FetchedResult[];
// drop: VoidFunction;
}
diff --git a/website/styles/chart.css b/website/styles/chart.css
index e63f7db6c..6320074ef 100644
--- a/website/styles/chart.css
+++ b/website/styles/chart.css
@@ -57,75 +57,8 @@
}
}
- > #chart-list {
- margin-top: 1rem;
- position: relative;
+ .chart-list {
margin-left: var(--negative-main-padding);
margin-right: calc(var(--negative-main-padding) - 0.5rem);
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 0;
- z-index: 20;
-
- > .chart-wrapper {
- height: 100%;
- position: relative;
- min-height: 0px;
- width: 100%;
- cursor: crosshair;
-
- &:has(+ .chart-wrapper:not([hidden])) {
- height: calc(100% - 62px);
- }
-
- > fieldset {
- pointer-events: none;
- position: absolute;
- left: 0px;
- top: 0px;
- z-index: 10;
- display: flex;
- align-items: center;
- padding-left: var(--main-padding);
- padding-right: var(--main-padding);
- font-size: var(--font-size-xs);
- line-height: var(--line-height-xs);
- gap: 0.5rem;
- color: var(--off-color);
-
- > div.field {
- display: flex;
- align-items: center;
- font-size: var(--font-size-xs);
- line-height: var(--line-height-xs);
- gap: 1rem;
-
- > legend,
- > div {
- flex-shrink: 0;
- }
-
- > hr {
- min-width: 1rem;
- }
-
- label {
- padding: 0.5rem;
- margin: -0.5rem;
- }
-
- > div {
- display: flex;
- gap: 0.5rem;
- }
- }
- }
-
- > .chart-div {
- width: 100%;
- height: 100%;
- }
- }
}
}
diff --git a/website/styles/live-price.css b/website/styles/live-price.css
new file mode 100644
index 000000000..08d3b68a3
--- /dev/null
+++ b/website/styles/live-price.css
@@ -0,0 +1,7 @@
+#live-price {
+ > h1 {
+ font-size: 2.5rem;
+ line-height: 4rem;
+ text-align: center;
+ }
+}
diff --git a/website/styles/moscow-time.css b/website/styles/moscow-time.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/website/styles/simulation.css b/website/styles/simulation.css
index 823c87aa8..569bbd25f 100644
--- a/website/styles/simulation.css
+++ b/website/styles/simulation.css
@@ -4,14 +4,17 @@
height: 100%;
width: 100%;
+ > div:first-child {
+ max-width: 20rem;
+ border-right: 1px;x
+ }
+
> div {
flex: 1;
- /* min-height: 0; */
overflow-y: auto;
display: flex;
flex-direction: column;
- width: 32rem;
- gap: 1rem;
+ gap: 3rem;
> div {
display: flex;
@@ -26,4 +29,29 @@
gap: 0.5rem;
align-items: center;
}
+
+ .chart-list {
+ max-height: 500px;
+ /* margin-left: var(--negative-main-padding);*/
+ margin-right: calc(var(--negative-main-padding) - 0.5rem);
+ }
+
+ li {
+ label:has(input:not(:checked)) + ul {
+ display: none;
+ }
+
+ ul {
+ padding-left: 0.75rem;
+ margin-left:0.25rem;
+ border-left: 1px;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+
+ li {
+ display: inline;
+ }
+ }
+ }
}