// // @ts-nocheck // import { // createDateRange, // dateToDateIndex, // differenceBetweenDates, // } from "../utils/date.js"; // import { // createButtonElement, // createFieldElement, // createHeader, // createSelect, // } from "../utils/dom.js"; // import { simulationElement } from "../utils/elements.js"; // import { // numberToDollars, // numberToPercentage, // numberToUSNumber, // } from "../utils/format.js"; // import { serdeDate, serdeOptDate, serdeOptNumber } from "../utils/serde.js"; // import signals from "../signals.js"; // import { createChart } from "../chart/index.js"; // import { resources } from "../resources.js"; // import { colors } from "../chart/colors.js"; // export function init() { // /** // * @typedef {Object} Frequency // * @property {string} name // * @property {string} value // * @property {(date: Date) => boolean} isTriggerDay // * // * @typedef {Object} Frequencies // * @property {string} name // * @property {Frequency[]} list // */ // const domExtended = { // /** // * @param {Object} args // * @param {string} args.id // * @param {string} args.title // * @param {string} args.placeholder // * @param {Signal} args.signal // * @param {number} args.min // * @param {number} args.step // * @param {number} [args.max] // * @param {Signals} args.signals // */ // createInputNumberElement({ // id, // title, // signal, // min, // max, // step, // placeholder, // signals, // }) { // const input = window.document.createElement("input"); // if (!id || !title || !placeholder) throw Error("input attribute missing"); // input.id = id; // input.title = title; // input.placeholder = placeholder; // input.type = "number"; // input.min = String(min); // if (max) { // input.max = String(max); // } // input.step = String(step); // let stateValue = /** @type {string | null} */ (null); // signals.createScopedEffect( // () => { // const value = signal(); // return value ? String(value) : ""; // }, // (value) => { // if (stateValue !== value) { // input.value = value; // stateValue = value; // } // }, // ); // input.addEventListener("input", () => { // const valueSer = input.value; // stateValue = valueSer; // const value = Number(valueSer); // if (value >= min && (max ? value <= max : true)) { // signal.set(value); // } // }); // return { input, signal }; // }, // /** // * @param {Object} args // * @param {string} args.id // * @param {string} args.title // * @param {Signal} args.signal // * @param {Signals} args.signals // */ // createInputDollar({ id, title, signal, signals }) { // return this.createInputNumberElement({ // id, // placeholder: "USD", // min: 0, // title, // signal, // signals, // step: 1, // }); // }, // /** // * @param {Object} args // * @param {string} args.id // * @param {string} args.title // * @param {Signal} args.signal // * @param {Signals} 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 = serdeDate.serialize(maxDate); // input.min = min; // input.max = max; // let stateValue = /** @type {string | null} */ (null); // signals.createScopedEffect( // () => { // const dateSignal = signal(); // return dateSignal ? serdeDate.serialize(dateSignal) : ""; // }, // (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, signal }; // }, // /** // * @param {Object} param0 // * @param {Signal} param0.signal // * @param {HTMLInputElement} [param0.input] // * @param {HTMLSelectElement} [param0.select] // */ // createResetableInput({ input, select, signal }) { // const div = window.document.createElement("div"); // const element = input || select; // if (!element) throw "createResetableField element missing"; // div.append(element); // const button = createButtonElement({ // onClick: signal.reset, // inside: "Reset", // title: "Reset field", // }); // button.type = "reset"; // div.append(button); // return div; // }, // }; // const parametersElement = window.document.createElement("div"); // simulationElement.append(parametersElement); // const resultsElement = window.document.createElement("div"); // simulationElement.append(resultsElement); // function computeFrequencies() { // const weekDays = [ // "Monday", // "Tuesday", // "Wednesday", // "Thursday", // "Friday", // "Saturday", // "Sunday", // ]; // const maxDays = 28; // /** @param {number} day */ // function getOrdinalDay(day) { // const rest = (day % 30) % 20; // return `${day}${ // rest === 1 ? "st" : rest === 2 ? "nd" : rest === 3 ? "rd" : "th" // }`; // } // /** @satisfies {([Frequency, Frequencies, Frequencies, Frequencies])} */ // const list = [ // { // name: "Every day", // value: "every-day", // /** @param {Date} _ */ // isTriggerDay(_) { // return true; // }, // }, // { // name: "Once a week", // list: weekDays.map((day, index) => ({ // name: day, // value: day.toLowerCase(), // /** @param {Date} date */ // isTriggerDay(date) { // let day = date.getUTCDay() - 1; // if (day === -1) { // day = 6; // } // return day === index; // }, // })), // }, // { // name: "Every two weeks", // list: [...Array(Math.round(maxDays / 2)).keys()].map((day) => { // const day1 = day + 1; // const day2 = day + 15; // return { // value: `${day1}+${day2}`, // name: `The ${getOrdinalDay(day1)} and the ${getOrdinalDay(day2)}`, // /** @param {Date} date */ // isTriggerDay(date) { // const d = date.getUTCDate(); // return d === day1 || d === day2; // }, // }; // }), // }, // { // name: "Once a month", // list: [...Array(maxDays).keys()].map((day) => { // day++; // return { // name: `The ${getOrdinalDay(day)}`, // value: String(day), // /** @param {Date} date */ // isTriggerDay(date) { // const d = date.getUTCDate(); // return d === day; // }, // }; // }), // }, // ]; // /** @type {Record} */ // const idToFrequency = {}; // list.forEach((anyFreq) => { // if ("list" in anyFreq) { // anyFreq.list?.forEach((freq) => { // idToFrequency[freq.value] = freq; // }); // } else { // idToFrequency[anyFreq.value] = anyFreq; // } // }); // const serde = { // /** // * @param {Frequency} v // */ // serialize(v) { // return v.value; // }, // /** // * @param {string} v // */ // deserialize(v) { // const freq = idToFrequency[v]; // if (!freq) throw "Freq not found"; // return freq; // }, // }; // return { list, serde }; // } // const frequencies = computeFrequencies(); // const keyPrefix = "save-in-bitcoin"; // const settings = { // dollars: { // initial: { // amount: signals.createSignal(/** @type {number | null} */ (1000), { // save: { // ...serdeOptNumber, // keyPrefix, // key: "initial-amount", // }, // }), // }, // topUp: { // amount: signals.createSignal(/** @type {number | null} */ (150), { // save: { // ...serdeOptNumber, // keyPrefix, // key: "top-up-amount", // }, // }), // frenquency: signals.createSignal( // /** @type {Frequency} */ (frequencies.list[3].list[0]), // { // save: { // ...frequencies.serde, // keyPrefix, // key: "top-up-freq", // }, // }, // ), // }, // }, // bitcoin: { // investment: { // initial: signals.createSignal(/** @type {number | null} */ (1000), { // save: { // ...serdeOptNumber, // keyPrefix, // key: "initial-swap", // }, // }), // recurrent: signals.createSignal(/** @type {number | null} */ (5), { // save: { // ...serdeOptNumber, // keyPrefix, // key: "recurrent-swap", // }, // }), // frequency: signals.createSignal( // /** @type {Frequency} */ (frequencies.list[0]), // { // save: { // ...frequencies.serde, // keyPrefix, // key: "swap-freq", // }, // }, // ), // }, // }, // interval: { // start: signals.createSignal( // /** @type {Date | null} */ (new Date("2021-04-15")), // { // save: { // ...serdeOptDate, // keyPrefix, // key: "interval-start", // }, // }, // ), // end: signals.createSignal(/** @type {Date | null} */ (new Date()), { // save: { // ...serdeOptDate, // keyPrefix, // key: "interval-end", // }, // }), // }, // fees: { // percentage: signals.createSignal(/** @type {number | null} */ (1), { // save: { // ...serdeOptNumber, // keyPrefix, // key: "percentage", // }, // }), // }, // }; // parametersElement.append(createHeader("Save in Bitcoin").headerElement); // /** // * @param {Object} param0 // * @param {ColorName} param0.color // * @param {string} param0.type // * @param {string} param0.text // */ // function createColoredTypeHTML({ color, type, text }) { // return `${createColoredSpan({ color, text: `${type}:` })} ${text}`; // } // /** // * @param {Object} param0 // * @param {ColorName} param0.color // * @param {string} param0.text // */ // function createColoredSpan({ color, text }) { // return `${text}`; // } // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "green", // type: "Dollars", // text: "Initial Amount", // }), // description: // "The amount of dollars you have ready on the exchange on day one.", // input: domExtended.createResetableInput( // domExtended.createInputDollar({ // id: "simulation-dollars-initial", // title: "Initial Dollar Amount", // signal: settings.dollars.initial.amount, // signals, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "green", // type: "Dollars", // text: "Top Up Frequency", // }), // description: // "The frequency at which you'll top up your account at the exchange.", // input: domExtended.createResetableInput( // createSelect({ // id: "top-up-frequency", // list: frequencies.list, // signal: settings.dollars.topUp.frenquency, // deep: true, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "green", // type: "Dollars", // text: "Top Up Amount", // }), // description: // "The recurrent amount of dollars you'll be transfering to said exchange.", // input: domExtended.createResetableInput( // domExtended.createInputDollar({ // id: "simulation-dollars-top-up-amount", // title: "Top Up Dollar Amount", // signal: settings.dollars.topUp.amount, // signals, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "orange", // type: "Bitcoin", // text: "Initial Investment", // }), // description: // "The amount, if available, of dollars that will be used to buy Bitcoin on day one.", // input: domExtended.createResetableInput( // domExtended.createInputDollar({ // id: "simulation-bitcoin-initial-investment", // title: "Initial Swap Amount", // signal: settings.bitcoin.investment.initial, // signals, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "orange", // type: "Bitcoin", // text: "Investment Frequency", // }), // description: "The frequency at which you'll be buying Bitcoin.", // input: domExtended.createResetableInput( // createSelect({ // id: "investment-frequency", // list: frequencies.list, // signal: settings.bitcoin.investment.frequency, // deep: true, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "orange", // type: "Bitcoin", // text: "Recurrent Investment", // }), // description: // "The recurrent amount, if available, of dollars that will be used to buy Bitcoin.", // input: domExtended.createResetableInput( // domExtended.createInputDollar({ // id: "simulation-bitcoin-recurrent-investment", // title: "Bitcoin Recurrent Investment", // signal: settings.bitcoin.investment.recurrent, // signals, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "sky", // type: "Interval", // text: "Start", // }), // description: "The first day of the simulation.", // input: domExtended.createResetableInput( // domExtended.createInputDate({ // id: "simulation-inverval-start", // title: "First Simulation Date", // signal: settings.interval.start, // signals, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "sky", // type: "Interval", // text: "End", // }), // description: "The last day of the simulation.", // input: domExtended.createResetableInput( // domExtended.createInputDate({ // id: "simulation-inverval-end", // title: "Last Simulation Day", // signal: settings.interval.end, // signals, // }), // ), // }), // ); // parametersElement.append( // createFieldElement({ // title: createColoredTypeHTML({ // color: "red", // type: "Fees", // text: "Exchange", // }), // description: "The amount of trading fees (in %) at the exchange.", // input: domExtended.createResetableInput( // domExtended.createInputNumberElement({ // id: "simulation-fees", // title: "Exchange Fees", // signal: settings.fees.percentage, // min: 0, // max: 50, // step: 0.01, // signals, // placeholder: "Fees", // }), // ), // }), // ); // const p1 = window.document.createElement("p"); // resultsElement.append(p1); // const p2 = window.document.createElement("p"); // resultsElement.append(p2); // const p3 = window.document.createElement("p"); // resultsElement.append(p3); // const owner = signals.getOwner(); // const totalInvestedAmountData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const bitcoinValueData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const bitcoinData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const resultData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const dollarsLeftData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const totalValueData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const investmentData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const bitcoinAddedData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const averageCostBasisData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const bitcoinPriceData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const buyCountData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const totalFeesPaidData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const daysCountData = signals.createSignal(/** @type {LineData[]} */ ([]), { // equals: false, // }); // const profitableDaysRatioData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // const unprofitableDaysRatioData = signals.createSignal( // /** @type {LineData[]} */ ([]), // { // equals: false, // }, // ); // /** @type {() => IndexName} */ // const index = () => "dateindex"; // createChart({ // index, // parent: resultsElement, // signals, // colors, // id: `result`, // fitContent: true, // resources, // config: [ // { // unit: "usd", // blueprints: [ // { // title: "Bitcoin Value", // type: "Line", // color: colors.amber, // data: bitcoinValueData, // }, // { // title: "Dollars Converted", // type: "Line", // color: colors.green, // data: totalInvestedAmountData, // }, // { // title: "Dollars Left", // type: "Line", // color: colors.emerald, // data: dollarsLeftData, // defaultActive: false, // }, // { // title: "Fees Paid", // type: "Line", // color: colors.rose, // data: totalFeesPaidData, // defaultActive: false, // }, // ], // }, // ], // }); // createChart({ // index, // parent: resultsElement, // signals, // colors, // id: `bitcoin`, // fitContent: true, // resources, // config: [ // { // unit: "btc", // blueprints: [ // { // title: "Bitcoin Stack", // type: "Line", // color: colors.orange, // data: bitcoinData, // }, // ], // }, // ], // }); // createChart({ // index, // parent: resultsElement, // signals, // colors, // id: `average-price`, // fitContent: true, // resources, // config: [ // { // unit: "usd", // blueprints: [ // { // title: "Bitcoin Price", // type: "Line", // color: colors.default, // data: bitcoinPriceData, // }, // { // title: "Average Cost Basis", // type: "Line", // color: colors.lime, // data: averageCostBasisData, // }, // ], // }, // ], // }); // createChart({ // index, // parent: resultsElement, // signals, // colors, // resources, // id: `return-ratio`, // fitContent: true, // config: [ // { // unit: "usd", // blueprints: [ // { // title: "Return Of Investment", // type: "Baseline", // data: resultData, // }, // ], // }, // ], // }); // createChart({ // index, // parent: resultsElement, // signals, // colors, // id: `simulation-profitability-ratios`, // fitContent: true, // resources, // config: [ // { // unit: "percentage", // blueprints: [ // { // title: "Profitable Days Ratio", // type: "Line", // color: colors.green, // data: profitableDaysRatioData, // }, // { // title: "Unprofitable Days Ratio", // type: "Line", // color: colors.red, // data: unprofitableDaysRatioData, // }, // ], // }, // ], // }); // resources.metrics // .getOrCreate("price_close", "dateindex") // .fetch() // .then((_closes) => { // if (!_closes) return; // const closes = /** @type {number[]} */ (_closes); // signals.runWithOwner(owner, () => { // signals.createScopedEffect( // () => ({ // initialDollarAmount: settings.dollars.initial.amount() || 0, // topUpAmount: settings.dollars.topUp.amount() || 0, // topUpFrequency: settings.dollars.topUp.frenquency(), // initialSwap: settings.bitcoin.investment.initial() || 0, // recurrentSwap: settings.bitcoin.investment.recurrent() || 0, // swapFrequency: settings.bitcoin.investment.frequency(), // start: settings.interval.start(), // end: settings.interval.end(), // fees: settings.fees.percentage(), // }), // ({ // initialDollarAmount, // topUpAmount, // topUpFrequency, // initialSwap, // recurrentSwap, // swapFrequency, // start, // end, // fees, // }) => { // if (!start || !end || start > end) return; // const range = createDateRange(start, end); // totalInvestedAmountData().length = 0; // bitcoinValueData().length = 0; // bitcoinData().length = 0; // resultData().length = 0; // dollarsLeftData().length = 0; // totalValueData().length = 0; // investmentData().length = 0; // bitcoinAddedData().length = 0; // averageCostBasisData().length = 0; // bitcoinPriceData().length = 0; // buyCountData().length = 0; // totalFeesPaidData().length = 0; // daysCountData().length = 0; // profitableDaysRatioData().length = 0; // unprofitableDaysRatioData().length = 0; // let bitcoin = 0; // let sats = 0; // let dollars = initialDollarAmount; // let investedAmount = 0; // let postFeesInvestedAmount = 0; // let buyCount = 0; // let averageCostBasis = 0; // let bitcoinValue = 0; // let roi = 0; // let totalValue = 0; // let totalFeesPaid = 0; // let daysCount = range.length; // let profitableDays = 0; // let unprofitableDays = 0; // let profitableDaysRatio = 0; // let unprofitableDaysRatio = 0; // let lastInvestDay = range[0]; // let dailyInvestment = 0; // let bitcoinAdded = 0; // let satsAdded = 0; // let lastSatsAdded = 0; // range.forEach((date, index) => { // const time = date.valueOf() / 1000; // if (topUpFrequency.isTriggerDay(date)) { // dollars += topUpAmount; // } // const close = closes[dateToDateIndex(date)]; // if (!close) return; // dailyInvestment = 0; // /** @param {number} value */ // function invest(value) { // value = Math.min(dollars, value); // dailyInvestment += value; // dollars -= value; // buyCount += 1; // lastInvestDay = date; // } // if (!index) { // invest(initialSwap); // } // if (swapFrequency.isTriggerDay(date) && dollars > 0) { // invest(recurrentSwap); // } // investedAmount += dailyInvestment; // let dailyInvestmentPostFees = // dailyInvestment * (1 - (fees || 0) / 100); // totalFeesPaid += dailyInvestment - dailyInvestmentPostFees; // bitcoinAdded = dailyInvestmentPostFees / close; // bitcoin += bitcoinAdded; // satsAdded = Math.floor(bitcoinAdded * 100_000_000); // if (satsAdded > 0) { // lastSatsAdded = satsAdded; // } // sats += satsAdded; // postFeesInvestedAmount += dailyInvestmentPostFees; // bitcoinValue = close * bitcoin; // totalValue = dollars + bitcoinValue; // averageCostBasis = postFeesInvestedAmount / bitcoin; // roi = (bitcoinValue / postFeesInvestedAmount - 1) * 100; // const daysCount = index + 1; // profitableDaysRatio = profitableDays / daysCount; // unprofitableDaysRatio = unprofitableDays / daysCount; // if (roi >= 0) { // profitableDays += 1; // } else { // unprofitableDays += 1; // } // bitcoinPriceData().push({ // time, // value: close, // }); // bitcoinData().push({ // time, // value: bitcoin, // }); // totalInvestedAmountData().push({ // time, // value: investedAmount, // }); // bitcoinValueData().push({ // time, // value: bitcoinValue, // }); // resultData().push({ // time, // value: roi, // }); // dollarsLeftData().push({ // time, // value: dollars, // }); // totalValueData().push({ // time, // value: totalValue, // }); // investmentData().push({ // time, // value: dailyInvestment, // }); // bitcoinAddedData().push({ // time, // value: bitcoinAdded, // }); // averageCostBasisData().push({ // time, // value: averageCostBasis, // }); // buyCountData().push({ // time, // value: buyCount, // }); // totalFeesPaidData().push({ // time, // value: totalFeesPaid, // }); // daysCountData().push({ // time, // value: daysCount, // }); // profitableDaysRatioData().push({ // time, // value: profitableDaysRatio * 100, // }); // unprofitableDaysRatioData().push({ // time, // value: unprofitableDaysRatio * 100, // }); // }); // const f = numberToUSNumber; // /** @param {number} v */ // const fd = (v) => numberToDollars.format(v); // /** @param {number} v */ // const fp = (v) => numberToPercentage.format(v); // /** // * @param {ColorName} c // * @param {string} t // */ // const c = (c, t) => createColoredSpan({ color: c, text: t }); // const serInvestedAmount = c("green", fd(investedAmount)); // const serDaysCount = c("sky", f(daysCount)); // const serSats = c("orange", f(sats)); // const serBitcoin = c("orange", `~${f(bitcoin)}`); // const serBitcoinValue = c("amber", fd(bitcoinValue)); // const serAverageCostBasis = c("lime", fd(averageCostBasis)); // const serRoi = c("yellow", fp(roi / 100)); // const serDollars = c("emerald", fd(dollars)); // const serTotalFeesPaid = c("rose", fd(totalFeesPaid)); // p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average cost basis of ${serAverageCostBasis} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`; // const dayDiff = Math.floor( // differenceBetweenDates(new Date(), lastInvestDay), // ); // const serDailyInvestment = c("emerald", fd(dailyInvestment)); // const setLastSatsAdded = c("orange", f(lastSatsAdded)); // p2.innerHTML = `You would've last bought ${c( // "blue", // dayDiff // ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` // : "today", // )} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`; // const serProfitableDaysRatio = c("green", fp(profitableDaysRatio)); // const serUnprofitableDaysRatio = c( // "red", // fp(unprofitableDaysRatio), // ); // p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`; // totalInvestedAmountData.set((a) => a); // bitcoinValueData.set((a) => a); // bitcoinData.set((a) => a); // resultData.set((a) => a); // dollarsLeftData.set((a) => a); // totalValueData.set((a) => a); // investmentData.set((a) => a); // bitcoinAddedData.set((a) => a); // averageCostBasisData.set((a) => a); // bitcoinPriceData.set((a) => a); // buyCountData.set((a) => a); // totalFeesPaidData.set((a) => a); // daysCountData.set((a) => a); // profitableDaysRatioData.set((a) => a); // unprofitableDaysRatioData.set((a) => a); // }, // ); // }); // }); // }