global: snapshot

This commit is contained in:
k
2024-11-20 10:50:14 +01:00
parent 9a73ee6952
commit d01ea13de4
61 changed files with 1907 additions and 950 deletions

View File

@@ -7,7 +7,7 @@
* @param {Colors} args.colors
* @param {Consts} args.consts
* @param {LightweightCharts} args.lightweightCharts
* @param {Accessor<ChartOption>} 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<Time>[]} */
const investedData = [];
/** @type {LineData<Time>[]} */
const returnData = [];
/** @type {LineData<Time>[]} */
const bitcoinData = [];
/** @type {LineData<Time>[]} */
const resultData = [];
/** @type {LineData<Time>[]} */
const dollarsData = [];
/** @type {LineData<Time>[]} */
const totalData = [];
/** @type {LineData<Time>[]} */
const investmentData = [];
/** @type {LineData<Time>[]} */
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<number | null>} args.signal
*
* @param {Object} arg
* @param {Signal<Date | null>} 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<Date>} 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;
}