mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-30 17:40:00 -07:00
kibo: fix simulation
This commit is contained in:
@@ -920,10 +920,15 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&[data-size="sm"] {
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-sm);
|
||||
}
|
||||
|
||||
&[data-size="xs"] {
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: var(--line-height-xs);
|
||||
font-weight: 500;
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
> div.field {
|
||||
@@ -940,6 +945,10 @@
|
||||
> hr {
|
||||
min-width: 2rem;
|
||||
|
||||
fieldset[data-size="sm"] & {
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
|
||||
fieldset[data-size="xs"] & {
|
||||
min-width: 1rem;
|
||||
}
|
||||
@@ -954,6 +963,10 @@
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
|
||||
fieldset[data-size="xs"] & {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
fieldset[data-size="xs"] & {
|
||||
gap: 1rem;
|
||||
}
|
||||
@@ -983,6 +996,9 @@
|
||||
padding-bottom: 1rem;
|
||||
overflow-x: auto;
|
||||
min-width: 0;
|
||||
font-size: var(--font-size-sm);
|
||||
line-height: var(--line-height-sm);
|
||||
height: 2rem;
|
||||
|
||||
> div {
|
||||
flex: 0;
|
||||
@@ -1031,6 +1047,17 @@
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
margin-right: var(--negative-main-padding);
|
||||
margin-left: var(--negative-main-padding);
|
||||
|
||||
fieldset {
|
||||
padding-left: var(--main-padding);
|
||||
padding-top: 0.5rem;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .panes {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,6 +3,7 @@
|
||||
// import { CanvasRenderingTarget2D } from "fancy-canvas";
|
||||
type CanvasRenderingTarget2D = any;
|
||||
|
||||
declare const baselineSeries: SeriesDefinition<"Baseline">;
|
||||
declare const candlestickSeries: SeriesDefinition<"Candlestick">;
|
||||
declare const lineSeries: SeriesDefinition<"Line">;
|
||||
export declare const customSeriesDefaultOptions: CustomSeriesOptions;
|
||||
@@ -4032,6 +4033,10 @@ export type UTCTimestamp = Nominal<number, "UTCTimestamp">;
|
||||
*/
|
||||
export type VisiblePriceScaleOptions = PriceScaleOptions;
|
||||
|
||||
export { candlestickSeries as CandlestickSeries, lineSeries as LineSeries };
|
||||
export {
|
||||
baselineSeries as BaselineSeries,
|
||||
candlestickSeries as CandlestickSeries,
|
||||
lineSeries as LineSeries,
|
||||
};
|
||||
|
||||
export {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-check
|
||||
|
||||
/** @import {IChartApi, ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData} from './v5.0.5-treeshaked/types' */
|
||||
/** @import {IChartApi, ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData, SeriesType} from './v5.0.5-treeshaked/types' */
|
||||
|
||||
/**
|
||||
* @typedef {[number, number, number, number]} OHLCTuple
|
||||
@@ -47,7 +47,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
autoSize: true,
|
||||
layout: {
|
||||
fontFamily: "Geist mono",
|
||||
fontSize: 14,
|
||||
fontSize: 13,
|
||||
background: { color: "transparent" },
|
||||
attributionLogo: false,
|
||||
colorSpace: "display-p3",
|
||||
@@ -130,15 +130,18 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
* @param {VecsResources} args.vecsResources
|
||||
* @param {Owner | null} [args.owner]
|
||||
* @param {true} [args.fitContentOnResize]
|
||||
* @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config]
|
||||
*/
|
||||
function createChartElement({
|
||||
parent,
|
||||
signals,
|
||||
colors,
|
||||
utils,
|
||||
id,
|
||||
vecsResources,
|
||||
owner: _owner,
|
||||
fitContentOnResize,
|
||||
config,
|
||||
}) {
|
||||
let owner = _owner || signals.getOwner();
|
||||
|
||||
@@ -179,7 +182,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
* @param {ISeriesApi<SeriesType>} series
|
||||
* @param {VecResource} valuesResource
|
||||
*/
|
||||
function createSetDataEffect(series, valuesResource) {
|
||||
function createSetFetchedDataEffect(series, valuesResource) {
|
||||
signals.runWithOwner(owner, () =>
|
||||
signals.createEffect(
|
||||
() => [timeResource?.fetched(), valuesResource.fetched()],
|
||||
@@ -244,7 +247,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
const chart = {
|
||||
inner: () => ichart,
|
||||
/**
|
||||
* @param {Object} args
|
||||
@@ -272,12 +275,23 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
colors,
|
||||
utils,
|
||||
});
|
||||
|
||||
if (fitContentOnResize) {
|
||||
ichart.applyOptions({
|
||||
handleScroll: false,
|
||||
handleScale: false,
|
||||
timeScale: {
|
||||
minBarSpacing: 0.001,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {VecId} args.vecId
|
||||
* @param {string} args.name
|
||||
* @param {Unit} args.unit
|
||||
* @param {VecId} [args.vecId]
|
||||
* @param {Accessor<CandlestickData[]>} [args.data]
|
||||
* @param {number} [args.paneIndex]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
*/
|
||||
@@ -287,15 +301,12 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
unit,
|
||||
paneIndex: _paneIndex,
|
||||
defaultActive,
|
||||
data,
|
||||
}) {
|
||||
const paneIndex = _paneIndex ?? 0;
|
||||
|
||||
if (!ichart || !timeResource) throw Error("Chart not fully set");
|
||||
|
||||
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
|
||||
valuesResource.fetch();
|
||||
activeResources.push(valuesResource);
|
||||
|
||||
const green = colors.green();
|
||||
const red = colors.red();
|
||||
const series = ichart.addSeries(
|
||||
@@ -311,31 +322,56 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
paneIndex,
|
||||
);
|
||||
|
||||
let url = /** @type {string | undefined} */ (undefined);
|
||||
|
||||
if (vecId) {
|
||||
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
|
||||
valuesResource.fetch();
|
||||
activeResources.push(valuesResource);
|
||||
createSetFetchedDataEffect(series, valuesResource);
|
||||
|
||||
url = valuesResource.url;
|
||||
} else if (data) {
|
||||
signals.runWithOwner(owner, () =>
|
||||
signals.createEffect(data, (data) => {
|
||||
series.setData(data);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
legend.add({
|
||||
series,
|
||||
name,
|
||||
defaultActive,
|
||||
colors: [colors.green, colors.red],
|
||||
url: valuesResource.url,
|
||||
url,
|
||||
});
|
||||
|
||||
createPaneHeightObserver({
|
||||
ichart,
|
||||
paneIndex,
|
||||
signals,
|
||||
utils,
|
||||
});
|
||||
|
||||
createPriceScaleSelectorIfNeeded({
|
||||
ichart,
|
||||
paneIndex,
|
||||
seriesType: "Candlestick",
|
||||
signals,
|
||||
id,
|
||||
unit,
|
||||
utils,
|
||||
});
|
||||
|
||||
createSetDataEffect(series, valuesResource);
|
||||
|
||||
return series;
|
||||
},
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {VecId} args.vecId
|
||||
* @param {string} args.name
|
||||
* @param {Unit} args.unit
|
||||
* @param {Accessor<LineData[]>} [args.data]
|
||||
* @param {VecId} [args.vecId]
|
||||
* @param {Color} [args.color]
|
||||
* @param {number} [args.paneIndex]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
@@ -347,15 +383,12 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
color,
|
||||
paneIndex: _paneIndex,
|
||||
defaultActive,
|
||||
data,
|
||||
}) {
|
||||
if (!ichart || !timeResource) throw Error("Chart not fully set");
|
||||
|
||||
const paneIndex = _paneIndex ?? 0;
|
||||
|
||||
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
|
||||
valuesResource.fetch();
|
||||
activeResources.push(valuesResource);
|
||||
|
||||
color ||= colors.orange;
|
||||
|
||||
const series = ichart.addSeries(
|
||||
@@ -369,16 +402,34 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
paneIndex,
|
||||
);
|
||||
|
||||
let url = /** @type {string | undefined} */ (undefined);
|
||||
|
||||
if (vecId) {
|
||||
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
|
||||
valuesResource.fetch();
|
||||
activeResources.push(valuesResource);
|
||||
createSetFetchedDataEffect(series, valuesResource);
|
||||
|
||||
url = valuesResource.url;
|
||||
} else if (data) {
|
||||
signals.runWithOwner(owner, () =>
|
||||
signals.createEffect(data, (data) => {
|
||||
series.setData(data);
|
||||
ichart
|
||||
?.timeScale()
|
||||
.setVisibleLogicalRange({ from: -1, to: data.length });
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
legend.add({
|
||||
series,
|
||||
colors: [color],
|
||||
name,
|
||||
defaultActive,
|
||||
url: valuesResource.url,
|
||||
url,
|
||||
});
|
||||
|
||||
createSetDataEffect(series, valuesResource);
|
||||
|
||||
createPaneHeightObserver({
|
||||
ichart,
|
||||
paneIndex,
|
||||
@@ -390,6 +441,94 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
ichart,
|
||||
paneIndex,
|
||||
signals,
|
||||
seriesType: "Line",
|
||||
id,
|
||||
unit,
|
||||
utils,
|
||||
});
|
||||
|
||||
return series;
|
||||
},
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {string} args.name
|
||||
* @param {Unit} args.unit
|
||||
* @param {Accessor<BaselineData[]>} [args.data]
|
||||
* @param {VecId} [args.vecId]
|
||||
* @param {number} [args.paneIndex]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
*/
|
||||
addBaselineSeries({
|
||||
vecId,
|
||||
name,
|
||||
unit,
|
||||
paneIndex: _paneIndex,
|
||||
defaultActive,
|
||||
data,
|
||||
}) {
|
||||
if (!ichart || !timeResource) throw Error("Chart not fully set");
|
||||
|
||||
const paneIndex = _paneIndex ?? 0;
|
||||
|
||||
const series = ichart.addSeries(
|
||||
/** @type {SeriesDefinition<'Baseline'>} */ (lc.BaselineSeries),
|
||||
{
|
||||
lineWidth: /** @type {any} */ (1.5),
|
||||
visible: defaultActive !== false,
|
||||
topLineColor: colors.green(),
|
||||
bottomLineColor: colors.red(),
|
||||
baseValue: {
|
||||
price: 0,
|
||||
},
|
||||
baseLineStyle: 0,
|
||||
baseLineWidth: 1,
|
||||
baseLineVisible: true,
|
||||
lineVisible: true,
|
||||
},
|
||||
paneIndex,
|
||||
);
|
||||
|
||||
let url = /** @type {string | undefined} */ (undefined);
|
||||
|
||||
if (vecId) {
|
||||
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
|
||||
valuesResource.fetch();
|
||||
activeResources.push(valuesResource);
|
||||
createSetFetchedDataEffect(series, valuesResource);
|
||||
|
||||
url = valuesResource.url;
|
||||
} else if (data) {
|
||||
signals.runWithOwner(owner, () =>
|
||||
signals.createEffect(data, (data) => {
|
||||
series.setData(data);
|
||||
ichart
|
||||
?.timeScale()
|
||||
.setVisibleLogicalRange({ from: -1, to: data.length });
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
legend.add({
|
||||
series,
|
||||
colors: [colors.green, colors.red],
|
||||
name,
|
||||
defaultActive,
|
||||
url,
|
||||
});
|
||||
|
||||
createPaneHeightObserver({
|
||||
ichart,
|
||||
paneIndex,
|
||||
signals,
|
||||
utils,
|
||||
});
|
||||
|
||||
createPriceScaleSelectorIfNeeded({
|
||||
ichart,
|
||||
paneIndex,
|
||||
signals,
|
||||
seriesType: "Baseline",
|
||||
id,
|
||||
unit,
|
||||
utils,
|
||||
});
|
||||
@@ -410,6 +549,41 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
|
||||
legend.reset();
|
||||
},
|
||||
};
|
||||
|
||||
config?.forEach(({ unit, blueprints }, paneIndex) => {
|
||||
chart.create({ index: /** @satisfies {Dateindex} */ (1) });
|
||||
|
||||
blueprints.forEach((blueprint) => {
|
||||
if (blueprint.type === "Candlestick") {
|
||||
chart.addCandlestickSeries({
|
||||
name: blueprint.title,
|
||||
unit,
|
||||
data: blueprint.data,
|
||||
defaultActive: blueprint.defaultActive,
|
||||
paneIndex,
|
||||
});
|
||||
} else if (blueprint.type === "Baseline") {
|
||||
chart.addBaselineSeries({
|
||||
name: blueprint.title,
|
||||
unit,
|
||||
data: blueprint.data,
|
||||
defaultActive: blueprint.defaultActive,
|
||||
paneIndex,
|
||||
});
|
||||
} else {
|
||||
chart.addLineSeries({
|
||||
name: blueprint.title,
|
||||
unit,
|
||||
data: blueprint.data,
|
||||
defaultActive: blueprint.defaultActive,
|
||||
paneIndex,
|
||||
color: blueprint.color,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -678,7 +852,6 @@ function createPaneHeightObserver({ ichart, paneIndex, signals, utils }) {
|
||||
if (!paneIndex) return;
|
||||
|
||||
const owner = signals.getOwner();
|
||||
if (!owner) throw Error("Expect owner");
|
||||
|
||||
const one = "1";
|
||||
|
||||
@@ -729,6 +902,8 @@ function createPaneHeightObserver({ ichart, paneIndex, signals, utils }) {
|
||||
* @param {Object} args
|
||||
* @param {IChartApi} args.ichart
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} args.id
|
||||
* @param {SeriesType} args.seriesType
|
||||
* @param {number} args.paneIndex
|
||||
* @param {Signals} args.signals
|
||||
* @param {Utilities} args.utils
|
||||
@@ -737,11 +912,12 @@ function createPriceScaleSelectorIfNeeded({
|
||||
ichart,
|
||||
unit,
|
||||
paneIndex,
|
||||
id,
|
||||
seriesType,
|
||||
signals,
|
||||
utils,
|
||||
}) {
|
||||
const owner = signals.getOwner();
|
||||
if (!owner) throw Error("Expect owner");
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
@@ -759,16 +935,20 @@ function createPriceScaleSelectorIfNeeded({
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(id);
|
||||
|
||||
const choices = /**@type {const} */ (["lin", "log"]);
|
||||
|
||||
/** @typedef {(typeof choices)[number]} Choices */
|
||||
const serializedValue = signals.createSignal(
|
||||
/** @satisfies {Choices} */ (paneIndex ? "lin" : "log"),
|
||||
/** @satisfies {Choices} */ (
|
||||
unit === "US Dollars" && seriesType !== "Baseline" ? "log" : "lin"
|
||||
),
|
||||
{
|
||||
save: {
|
||||
...utils.serde.string,
|
||||
keyPrefix: "charts",
|
||||
key: `price-scale-${paneIndex}`,
|
||||
keyPrefix: "",
|
||||
key: `${id}-price-scale-${paneIndex}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -777,7 +957,7 @@ function createPriceScaleSelectorIfNeeded({
|
||||
title: unit,
|
||||
selected: serializedValue(),
|
||||
choices: choices,
|
||||
id: unit,
|
||||
id: `${id}-${unit.replace(" ", "-")}`,
|
||||
signals,
|
||||
});
|
||||
|
||||
@@ -789,6 +969,7 @@ function createPriceScaleSelectorIfNeeded({
|
||||
|
||||
const element = window.document.createElement(tagName);
|
||||
element.dataset.size = "xs";
|
||||
element.id = `${id}-price-scale-${paneIndex}`;
|
||||
element.append(field);
|
||||
|
||||
const mode = signals.createMemo(() => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export function init({
|
||||
parent: elements.charts,
|
||||
signals,
|
||||
colors,
|
||||
id: "chart",
|
||||
id: "charts",
|
||||
utils,
|
||||
vecsResources,
|
||||
});
|
||||
@@ -174,6 +174,7 @@ function createIndexSelector({ elements, signals, utils }) {
|
||||
|
||||
const fieldset = window.document.createElement("fieldset");
|
||||
fieldset.append(indexesField);
|
||||
fieldset.dataset.size = "sm";
|
||||
elements.charts.append(fieldset);
|
||||
|
||||
const index = signals.createMemo(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Unit } from "./options"
|
||||
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Unit, AnySeriesBlueprint } from "./options"
|
||||
* @import {Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple} from "../packages/lightweight-charts/wrapper"
|
||||
* @import * as _ from "../packages/ufuzzy/v1.0.14/types"
|
||||
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.5-treeshaked/types"
|
||||
@@ -952,11 +952,22 @@ function createUtils() {
|
||||
},
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*/
|
||||
toString(date) {
|
||||
return date.toJSON().split("T")[0];
|
||||
},
|
||||
/**
|
||||
* @param {Date} date
|
||||
*/
|
||||
toDateIndex(date) {
|
||||
if (
|
||||
date.getUTCFullYear() === 2009 &&
|
||||
date.getUTCMonth() === 0 &&
|
||||
date.getUTCDate() === 3
|
||||
)
|
||||
return 0;
|
||||
return this.differenceBetween(date, new Date("2009-01-09"));
|
||||
},
|
||||
/**
|
||||
* @param {Time} time
|
||||
*/
|
||||
@@ -988,7 +999,6 @@ function createUtils() {
|
||||
/**
|
||||
* @param {Date} date1
|
||||
* @param {Date} date2
|
||||
* @returns
|
||||
*/
|
||||
differenceBetween(date1, date2) {
|
||||
return Math.abs(date1.valueOf() - date2.valueOf()) / this.ONE_DAY_IN_MS;
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
*
|
||||
* @typedef {Object} BaseSeriesBlueprint
|
||||
* @property {string} title
|
||||
* @property {VecId} key
|
||||
* @property {boolean} [defaultActive]
|
||||
*
|
||||
* @typedef {Object} BaselineSeriesBlueprintSpecific
|
||||
@@ -49,6 +48,8 @@
|
||||
*
|
||||
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
|
||||
*
|
||||
* @typedef {AnySeriesBlueprint & {key: VecId}} AnyFetchedSeriesBlueprint
|
||||
*
|
||||
* @typedef {Object} PartialOption
|
||||
* @property {string} name
|
||||
*
|
||||
@@ -61,8 +62,8 @@
|
||||
* @property {"chart"} [kind]
|
||||
* @property {Unit} [unit]
|
||||
* @property {string} [title]
|
||||
* @property {AnySeriesBlueprint[]} [top]
|
||||
* @property {AnySeriesBlueprint[]} [bottom]
|
||||
* @property {AnyFetchedSeriesBlueprint[]} [top]
|
||||
* @property {AnyFetchedSeriesBlueprint[]} [bottom]
|
||||
* @typedef {PartialOption & PartialChartOptionSpecific} PartialChartOption
|
||||
* @typedef {Required<PartialChartOption> & ProcessedOptionAddons} ChartOption
|
||||
*
|
||||
|
||||
@@ -263,7 +263,8 @@ export function init({
|
||||
* @param {string} param0.text
|
||||
*/
|
||||
function createColoredSpan({ color, text }) {
|
||||
return `<span style="color: ${colors[color]()}; font-weight: 500">${text}</span>`;
|
||||
return `<span style="color: ${colors[color]()}; font-weight: 500; text-transform: uppercase;
|
||||
font-size: var(--font-size-sm);">${text}</span>`;
|
||||
}
|
||||
|
||||
parametersElement.append(
|
||||
@@ -550,24 +551,19 @@ export function init({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-0`,
|
||||
id: `result`,
|
||||
fitContentOnResize: true,
|
||||
vecsResources,
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
blueprints: [
|
||||
{
|
||||
title: "Fees Paid",
|
||||
title: "Bitcoin Value",
|
||||
type: "Line",
|
||||
color: colors.rose,
|
||||
data: totalFeesPaidData,
|
||||
},
|
||||
{
|
||||
title: "Dollars Left",
|
||||
type: "Line",
|
||||
color: colors.offDollars,
|
||||
data: dollarsLeftData,
|
||||
color: colors.amber,
|
||||
data: bitcoinValueData,
|
||||
},
|
||||
{
|
||||
title: "Dollars Converted",
|
||||
@@ -576,10 +572,18 @@ export function init({
|
||||
data: totalInvestedAmountData,
|
||||
},
|
||||
{
|
||||
title: "Bitcoin Value",
|
||||
title: "Dollars Left",
|
||||
type: "Line",
|
||||
color: colors.amber,
|
||||
data: bitcoinValueData,
|
||||
color: colors.offDollars,
|
||||
data: dollarsLeftData,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
title: "Fees Paid",
|
||||
type: "Line",
|
||||
color: colors.rose,
|
||||
data: totalFeesPaidData,
|
||||
defaultActive: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -590,13 +594,14 @@ export function init({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-1`,
|
||||
id: `bitcoin`,
|
||||
fitContentOnResize: true,
|
||||
vecsResources,
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
unit: "Bitcoin",
|
||||
blueprints: [
|
||||
{
|
||||
title: "Bitcoin Stack",
|
||||
type: "Line",
|
||||
@@ -612,13 +617,14 @@ export function init({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-average-price`,
|
||||
id: `average-price`,
|
||||
fitContentOnResize: true,
|
||||
vecsResources,
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
blueprints: [
|
||||
{
|
||||
title: "Bitcoin Price",
|
||||
type: "Line",
|
||||
@@ -640,27 +646,18 @@ export function init({
|
||||
parent: resultsElement,
|
||||
signals,
|
||||
colors,
|
||||
id: `simulation-return-ratio`,
|
||||
vecsResources,
|
||||
id: `return-ratio`,
|
||||
fitContentOnResize: true,
|
||||
utils,
|
||||
config: [
|
||||
{
|
||||
unit: "US Dollars",
|
||||
config: [
|
||||
blueprints: [
|
||||
{
|
||||
title: "Return Of Investment",
|
||||
type: "Baseline",
|
||||
data: resultData,
|
||||
// TODO: Doesn't work for some reason
|
||||
// options: {
|
||||
// baseLineColor: "#888",
|
||||
// baseLineVisible: true,
|
||||
// baseLineWidth: 1,
|
||||
// baseValue: {
|
||||
// price: 0,
|
||||
// type: "price",
|
||||
// },
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -673,333 +670,336 @@ export function init({
|
||||
colors,
|
||||
id: `simulation-profitability-ratios`,
|
||||
fitContentOnResize: true,
|
||||
vecsResources,
|
||||
utils,
|
||||
owner,
|
||||
config: [
|
||||
{
|
||||
unit: "Percentage",
|
||||
config: [
|
||||
{
|
||||
title: "Unprofitable Days Ratio",
|
||||
type: "Line",
|
||||
color: colors.red,
|
||||
data: unprofitableDaysRatioData,
|
||||
},
|
||||
blueprints: [
|
||||
{
|
||||
title: "Profitable Days Ratio",
|
||||
type: "Line",
|
||||
color: colors.green,
|
||||
data: profitableDaysRatioData,
|
||||
},
|
||||
{
|
||||
title: "Unprofitable Days Ratio",
|
||||
type: "Line",
|
||||
color: colors.red,
|
||||
data: unprofitableDaysRatioData,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const closes = vecsResources.getOrCreate(
|
||||
/** @satisfies {Dateindex} */ (1),
|
||||
"close",
|
||||
);
|
||||
vecsResources
|
||||
.getOrCreate(/** @satisfies {Dateindex} */ (1), "close")
|
||||
.fetch()
|
||||
.then((_closes) => {
|
||||
if (!_closes) return;
|
||||
const closes = /** @type {number[]} */ (_closes);
|
||||
|
||||
closes.fetch().then((_closes) => {
|
||||
const closes = /** @type {OHLCTuple[] | null} */ (_closes);
|
||||
signals.runWithOwner(owner, () => {
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
initialDollarAmount: settings.dollars.initial.amount() || 0,
|
||||
topUpAmount: settings.dollars.topUp.amount() || 0,
|
||||
topUpFrequency: settings.dollars.topUp.frenquency(),
|
||||
initialSwap: settings.bitcoin.investment.initial() || 0,
|
||||
recurrentSwap: settings.bitcoin.investment.recurrent() || 0,
|
||||
swapFrequency: settings.bitcoin.investment.frequency(),
|
||||
start: settings.interval.start(),
|
||||
end: settings.interval.end(),
|
||||
fees: settings.fees.percentage(),
|
||||
}),
|
||||
({
|
||||
initialDollarAmount,
|
||||
topUpAmount,
|
||||
topUpFrequency,
|
||||
initialSwap,
|
||||
recurrentSwap,
|
||||
swapFrequency,
|
||||
start,
|
||||
end,
|
||||
fees,
|
||||
}) => {
|
||||
if (!start || !end || start > end) return;
|
||||
signals.runWithOwner(owner, () => {
|
||||
signals.createEffect(
|
||||
() => ({
|
||||
initialDollarAmount: settings.dollars.initial.amount() || 0,
|
||||
topUpAmount: settings.dollars.topUp.amount() || 0,
|
||||
topUpFrequency: settings.dollars.topUp.frenquency(),
|
||||
initialSwap: settings.bitcoin.investment.initial() || 0,
|
||||
recurrentSwap: settings.bitcoin.investment.recurrent() || 0,
|
||||
swapFrequency: settings.bitcoin.investment.frequency(),
|
||||
start: settings.interval.start(),
|
||||
end: settings.interval.end(),
|
||||
fees: settings.fees.percentage(),
|
||||
}),
|
||||
({
|
||||
initialDollarAmount,
|
||||
topUpAmount,
|
||||
topUpFrequency,
|
||||
initialSwap,
|
||||
recurrentSwap,
|
||||
swapFrequency,
|
||||
start,
|
||||
end,
|
||||
fees,
|
||||
}) => {
|
||||
if (!start || !end || start > end) return;
|
||||
|
||||
const range = utils.date.getRange(start, end);
|
||||
const range = utils.date.getRange(start, end);
|
||||
|
||||
totalInvestedAmountData().length = 0;
|
||||
bitcoinValueData().length = 0;
|
||||
bitcoinData().length = 0;
|
||||
resultData().length = 0;
|
||||
dollarsLeftData().length = 0;
|
||||
totalValueData().length = 0;
|
||||
investmentData().length = 0;
|
||||
bitcoinAddedData().length = 0;
|
||||
averagePricePaidData().length = 0;
|
||||
bitcoinPriceData().length = 0;
|
||||
buyCountData().length = 0;
|
||||
totalFeesPaidData().length = 0;
|
||||
daysCountData().length = 0;
|
||||
profitableDaysRatioData().length = 0;
|
||||
unprofitableDaysRatioData().length = 0;
|
||||
totalInvestedAmountData().length = 0;
|
||||
bitcoinValueData().length = 0;
|
||||
bitcoinData().length = 0;
|
||||
resultData().length = 0;
|
||||
dollarsLeftData().length = 0;
|
||||
totalValueData().length = 0;
|
||||
investmentData().length = 0;
|
||||
bitcoinAddedData().length = 0;
|
||||
averagePricePaidData().length = 0;
|
||||
bitcoinPriceData().length = 0;
|
||||
buyCountData().length = 0;
|
||||
totalFeesPaidData().length = 0;
|
||||
daysCountData().length = 0;
|
||||
profitableDaysRatioData().length = 0;
|
||||
unprofitableDaysRatioData().length = 0;
|
||||
|
||||
let bitcoin = 0;
|
||||
let sats = 0;
|
||||
let dollars = initialDollarAmount;
|
||||
let investedAmount = 0;
|
||||
let postFeesInvestedAmount = 0;
|
||||
let buyCount = 0;
|
||||
let averagePricePaid = 0;
|
||||
let bitcoinValue = 0;
|
||||
let roi = 0;
|
||||
let totalValue = 0;
|
||||
let totalFeesPaid = 0;
|
||||
let daysCount = range.length;
|
||||
let profitableDays = 0;
|
||||
let unprofitableDays = 0;
|
||||
let profitableDaysRatio = 0;
|
||||
let unprofitableDaysRatio = 0;
|
||||
let lastInvestDay = range[0];
|
||||
let dailyInvestment = 0;
|
||||
let bitcoinAdded = 0;
|
||||
let satsAdded = 0;
|
||||
let lastSatsAdded = 0;
|
||||
let bitcoin = 0;
|
||||
let sats = 0;
|
||||
let dollars = initialDollarAmount;
|
||||
let investedAmount = 0;
|
||||
let postFeesInvestedAmount = 0;
|
||||
let buyCount = 0;
|
||||
let averagePricePaid = 0;
|
||||
let bitcoinValue = 0;
|
||||
let roi = 0;
|
||||
let totalValue = 0;
|
||||
let totalFeesPaid = 0;
|
||||
let daysCount = range.length;
|
||||
let profitableDays = 0;
|
||||
let unprofitableDays = 0;
|
||||
let profitableDaysRatio = 0;
|
||||
let unprofitableDaysRatio = 0;
|
||||
let lastInvestDay = range[0];
|
||||
let dailyInvestment = 0;
|
||||
let bitcoinAdded = 0;
|
||||
let satsAdded = 0;
|
||||
let lastSatsAdded = 0;
|
||||
|
||||
range.forEach((date, index) => {
|
||||
const year = date.getUTCFullYear();
|
||||
const time = utils.date.toString(date);
|
||||
range.forEach((date, index) => {
|
||||
const year = date.getUTCFullYear();
|
||||
const time = utils.date.toString(date);
|
||||
|
||||
if (topUpFrequency.isTriggerDay(date)) {
|
||||
dollars += topUpAmount;
|
||||
}
|
||||
|
||||
const close = closes.ranges
|
||||
.at(utils.chunkIdToIndex("date", year))
|
||||
?.json()?.dataset.map[utils.date.toString(date)];
|
||||
|
||||
if (!close) return;
|
||||
|
||||
dailyInvestment = 0;
|
||||
/** @param {number} value */
|
||||
function invest(value) {
|
||||
value = Math.min(dollars, value);
|
||||
dailyInvestment += value;
|
||||
dollars -= value;
|
||||
buyCount += 1;
|
||||
lastInvestDay = date;
|
||||
}
|
||||
if (!index) {
|
||||
invest(initialSwap);
|
||||
}
|
||||
if (swapFrequency.isTriggerDay(date) && dollars > 0) {
|
||||
invest(recurrentSwap);
|
||||
}
|
||||
|
||||
investedAmount += dailyInvestment;
|
||||
|
||||
let dailyInvestmentPostFees =
|
||||
dailyInvestment * (1 - (fees || 0) / 100);
|
||||
|
||||
totalFeesPaid += dailyInvestment - dailyInvestmentPostFees;
|
||||
|
||||
bitcoinAdded = dailyInvestmentPostFees / close;
|
||||
bitcoin += bitcoinAdded;
|
||||
satsAdded = Math.floor(bitcoinAdded * 100_000_000);
|
||||
if (satsAdded > 0) {
|
||||
lastSatsAdded = satsAdded;
|
||||
}
|
||||
sats += satsAdded;
|
||||
|
||||
postFeesInvestedAmount += dailyInvestmentPostFees;
|
||||
|
||||
bitcoinValue = close * bitcoin;
|
||||
|
||||
totalValue = dollars + bitcoinValue;
|
||||
|
||||
averagePricePaid = postFeesInvestedAmount / bitcoin;
|
||||
|
||||
roi = (bitcoinValue / postFeesInvestedAmount - 1) * 100;
|
||||
|
||||
const daysCount = index + 1;
|
||||
profitableDaysRatio = profitableDays / daysCount;
|
||||
unprofitableDaysRatio = unprofitableDays / daysCount;
|
||||
|
||||
if (roi >= 0) {
|
||||
profitableDays += 1;
|
||||
} else {
|
||||
unprofitableDays += 1;
|
||||
}
|
||||
|
||||
bitcoinPriceData().push({
|
||||
time,
|
||||
value: close,
|
||||
});
|
||||
|
||||
bitcoinData().push({
|
||||
time,
|
||||
value: bitcoin,
|
||||
});
|
||||
|
||||
totalInvestedAmountData().push({
|
||||
time,
|
||||
value: investedAmount,
|
||||
});
|
||||
|
||||
bitcoinValueData().push({
|
||||
time,
|
||||
value: bitcoinValue,
|
||||
});
|
||||
|
||||
resultData().push({
|
||||
time,
|
||||
value: roi,
|
||||
});
|
||||
|
||||
dollarsLeftData().push({
|
||||
time,
|
||||
value: dollars,
|
||||
});
|
||||
|
||||
totalValueData().push({
|
||||
time,
|
||||
value: totalValue,
|
||||
});
|
||||
|
||||
investmentData().push({
|
||||
time,
|
||||
value: dailyInvestment,
|
||||
});
|
||||
|
||||
bitcoinAddedData().push({
|
||||
time,
|
||||
value: bitcoinAdded,
|
||||
});
|
||||
|
||||
averagePricePaidData().push({
|
||||
time,
|
||||
value: averagePricePaid,
|
||||
});
|
||||
|
||||
buyCountData().push({
|
||||
time,
|
||||
value: buyCount,
|
||||
});
|
||||
|
||||
totalFeesPaidData().push({
|
||||
time,
|
||||
value: totalFeesPaid,
|
||||
});
|
||||
|
||||
daysCountData().push({
|
||||
time,
|
||||
value: daysCount,
|
||||
});
|
||||
|
||||
profitableDaysRatioData().push({
|
||||
time,
|
||||
value: profitableDaysRatio * 100,
|
||||
});
|
||||
|
||||
unprofitableDaysRatioData().push({
|
||||
time,
|
||||
value: unprofitableDaysRatio * 100,
|
||||
});
|
||||
});
|
||||
|
||||
const f = utils.locale.numberToUSFormat;
|
||||
/** @param {number} v */
|
||||
const fd = (v) => utils.formatters.dollars.format(v);
|
||||
/** @param {number} v */
|
||||
const fp = (v) => utils.formatters.percentage.format(v);
|
||||
/**
|
||||
* @param {ColorName} c
|
||||
* @param {string} t
|
||||
*/
|
||||
const c = (c, t) => createColoredSpan({ color: c, text: t });
|
||||
|
||||
const serInvestedAmount = c("dollars", fd(investedAmount));
|
||||
const serDaysCount = c("sky", f(daysCount));
|
||||
const serSats = c("orange", f(sats));
|
||||
const serBitcoin = c("orange", `~${f(bitcoin)}`);
|
||||
const serBitcoinValue = c("amber", fd(bitcoinValue));
|
||||
const serAveragePricePaid = c("lightDollars", fd(averagePricePaid));
|
||||
const serRoi = c("yellow", fp(roi / 100));
|
||||
const serDollars = c("offDollars", fd(dollars));
|
||||
const serTotalFeesPaid = c("rose", fd(totalFeesPaid));
|
||||
|
||||
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
|
||||
|
||||
const dayDiff = Math.floor(
|
||||
utils.date.differenceBetween(new Date(), lastInvestDay),
|
||||
);
|
||||
const serDailyInvestment = c("offDollars", fd(dailyInvestment));
|
||||
const setLastSatsAdded = c("bitcoin", f(lastSatsAdded));
|
||||
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
|
||||
|
||||
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
|
||||
const serUnprofitableDaysRatio = c("red", fp(unprofitableDaysRatio));
|
||||
|
||||
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
|
||||
|
||||
signals.createEffect(
|
||||
() => 0.2368,
|
||||
(lowestAnnual4YReturn) => {
|
||||
const serLowestAnnual4YReturn = c(
|
||||
"cyan",
|
||||
`${fp(lowestAnnual4YReturn)}`,
|
||||
);
|
||||
|
||||
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
|
||||
/**
|
||||
* @param {number} power
|
||||
*/
|
||||
function bitcoinValueReturn(power) {
|
||||
return (
|
||||
bitcoinValue * Math.pow(lowestAnnual4YReturnPercentage, power)
|
||||
);
|
||||
if (topUpFrequency.isTriggerDay(date)) {
|
||||
dollars += topUpAmount;
|
||||
}
|
||||
const bitcoinValueAfter4y = bitcoinValueReturn(4);
|
||||
const serBitcoinValueAfter4y = c(
|
||||
"purple",
|
||||
fd(bitcoinValueAfter4y),
|
||||
);
|
||||
const bitcoinValueAfter10y = bitcoinValueReturn(10);
|
||||
const serBitcoinValueAfter10y = c(
|
||||
"fuchsia",
|
||||
fd(bitcoinValueAfter10y),
|
||||
);
|
||||
const bitcoinValueAfter21y = bitcoinValueReturn(21);
|
||||
const serBitcoinValueAfter21y = c(
|
||||
"pink",
|
||||
fd(bitcoinValueAfter21y),
|
||||
);
|
||||
|
||||
/** @param {number} v */
|
||||
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
|
||||
},
|
||||
);
|
||||
const close = closes[utils.date.toDateIndex(date)];
|
||||
|
||||
totalInvestedAmountData.set((a) => a);
|
||||
bitcoinValueData.set((a) => a);
|
||||
bitcoinData.set((a) => a);
|
||||
resultData.set((a) => a);
|
||||
dollarsLeftData.set((a) => a);
|
||||
totalValueData.set((a) => a);
|
||||
investmentData.set((a) => a);
|
||||
bitcoinAddedData.set((a) => a);
|
||||
averagePricePaidData.set((a) => a);
|
||||
bitcoinPriceData.set((a) => a);
|
||||
buyCountData.set((a) => a);
|
||||
totalFeesPaidData.set((a) => a);
|
||||
daysCountData.set((a) => a);
|
||||
profitableDaysRatioData.set((a) => a);
|
||||
unprofitableDaysRatioData.set((a) => a);
|
||||
},
|
||||
);
|
||||
if (!close) return;
|
||||
|
||||
dailyInvestment = 0;
|
||||
/** @param {number} value */
|
||||
function invest(value) {
|
||||
value = Math.min(dollars, value);
|
||||
dailyInvestment += value;
|
||||
dollars -= value;
|
||||
buyCount += 1;
|
||||
lastInvestDay = date;
|
||||
}
|
||||
if (!index) {
|
||||
invest(initialSwap);
|
||||
}
|
||||
if (swapFrequency.isTriggerDay(date) && dollars > 0) {
|
||||
invest(recurrentSwap);
|
||||
}
|
||||
|
||||
investedAmount += dailyInvestment;
|
||||
|
||||
let dailyInvestmentPostFees =
|
||||
dailyInvestment * (1 - (fees || 0) / 100);
|
||||
|
||||
totalFeesPaid += dailyInvestment - dailyInvestmentPostFees;
|
||||
|
||||
bitcoinAdded = dailyInvestmentPostFees / close;
|
||||
bitcoin += bitcoinAdded;
|
||||
satsAdded = Math.floor(bitcoinAdded * 100_000_000);
|
||||
if (satsAdded > 0) {
|
||||
lastSatsAdded = satsAdded;
|
||||
}
|
||||
sats += satsAdded;
|
||||
|
||||
postFeesInvestedAmount += dailyInvestmentPostFees;
|
||||
|
||||
bitcoinValue = close * bitcoin;
|
||||
|
||||
totalValue = dollars + bitcoinValue;
|
||||
|
||||
averagePricePaid = postFeesInvestedAmount / bitcoin;
|
||||
|
||||
roi = (bitcoinValue / postFeesInvestedAmount - 1) * 100;
|
||||
|
||||
const daysCount = index + 1;
|
||||
profitableDaysRatio = profitableDays / daysCount;
|
||||
unprofitableDaysRatio = unprofitableDays / daysCount;
|
||||
|
||||
if (roi >= 0) {
|
||||
profitableDays += 1;
|
||||
} else {
|
||||
unprofitableDays += 1;
|
||||
}
|
||||
|
||||
bitcoinPriceData().push({
|
||||
time,
|
||||
value: close,
|
||||
});
|
||||
|
||||
bitcoinData().push({
|
||||
time,
|
||||
value: bitcoin,
|
||||
});
|
||||
|
||||
totalInvestedAmountData().push({
|
||||
time,
|
||||
value: investedAmount,
|
||||
});
|
||||
|
||||
bitcoinValueData().push({
|
||||
time,
|
||||
value: bitcoinValue,
|
||||
});
|
||||
|
||||
resultData().push({
|
||||
time,
|
||||
value: roi,
|
||||
});
|
||||
|
||||
dollarsLeftData().push({
|
||||
time,
|
||||
value: dollars,
|
||||
});
|
||||
|
||||
totalValueData().push({
|
||||
time,
|
||||
value: totalValue,
|
||||
});
|
||||
|
||||
investmentData().push({
|
||||
time,
|
||||
value: dailyInvestment,
|
||||
});
|
||||
|
||||
bitcoinAddedData().push({
|
||||
time,
|
||||
value: bitcoinAdded,
|
||||
});
|
||||
|
||||
averagePricePaidData().push({
|
||||
time,
|
||||
value: averagePricePaid,
|
||||
});
|
||||
|
||||
buyCountData().push({
|
||||
time,
|
||||
value: buyCount,
|
||||
});
|
||||
|
||||
totalFeesPaidData().push({
|
||||
time,
|
||||
value: totalFeesPaid,
|
||||
});
|
||||
|
||||
daysCountData().push({
|
||||
time,
|
||||
value: daysCount,
|
||||
});
|
||||
|
||||
profitableDaysRatioData().push({
|
||||
time,
|
||||
value: profitableDaysRatio * 100,
|
||||
});
|
||||
|
||||
unprofitableDaysRatioData().push({
|
||||
time,
|
||||
value: unprofitableDaysRatio * 100,
|
||||
});
|
||||
});
|
||||
|
||||
const f = utils.locale.numberToUSFormat;
|
||||
/** @param {number} v */
|
||||
const fd = (v) => utils.formatters.dollars.format(v);
|
||||
/** @param {number} v */
|
||||
const fp = (v) => utils.formatters.percentage.format(v);
|
||||
/**
|
||||
* @param {ColorName} c
|
||||
* @param {string} t
|
||||
*/
|
||||
const c = (c, t) => createColoredSpan({ color: c, text: t });
|
||||
|
||||
const serInvestedAmount = c("dollars", fd(investedAmount));
|
||||
const serDaysCount = c("sky", f(daysCount));
|
||||
const serSats = c("orange", f(sats));
|
||||
const serBitcoin = c("orange", `~${f(bitcoin)}`);
|
||||
const serBitcoinValue = c("amber", fd(bitcoinValue));
|
||||
const serAveragePricePaid = c("lightDollars", fd(averagePricePaid));
|
||||
const serRoi = c("yellow", fp(roi / 100));
|
||||
const serDollars = c("offDollars", fd(dollars));
|
||||
const serTotalFeesPaid = c("rose", fd(totalFeesPaid));
|
||||
|
||||
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
|
||||
|
||||
const dayDiff = Math.floor(
|
||||
utils.date.differenceBetween(new Date(), lastInvestDay),
|
||||
);
|
||||
const serDailyInvestment = c("offDollars", fd(dailyInvestment));
|
||||
const setLastSatsAdded = c("bitcoin", f(lastSatsAdded));
|
||||
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
|
||||
|
||||
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
|
||||
const serUnprofitableDaysRatio = c(
|
||||
"red",
|
||||
fp(unprofitableDaysRatio),
|
||||
);
|
||||
|
||||
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
|
||||
|
||||
signals.createEffect(
|
||||
() => 0.2368,
|
||||
(lowestAnnual4YReturn) => {
|
||||
const serLowestAnnual4YReturn = c(
|
||||
"cyan",
|
||||
`${fp(lowestAnnual4YReturn)}`,
|
||||
);
|
||||
|
||||
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
|
||||
/**
|
||||
* @param {number} power
|
||||
*/
|
||||
function bitcoinValueReturn(power) {
|
||||
return (
|
||||
bitcoinValue *
|
||||
Math.pow(lowestAnnual4YReturnPercentage, power)
|
||||
);
|
||||
}
|
||||
const bitcoinValueAfter4y = bitcoinValueReturn(4);
|
||||
const serBitcoinValueAfter4y = c(
|
||||
"purple",
|
||||
fd(bitcoinValueAfter4y),
|
||||
);
|
||||
const bitcoinValueAfter10y = bitcoinValueReturn(10);
|
||||
const serBitcoinValueAfter10y = c(
|
||||
"fuchsia",
|
||||
fd(bitcoinValueAfter10y),
|
||||
);
|
||||
const bitcoinValueAfter21y = bitcoinValueReturn(21);
|
||||
const serBitcoinValueAfter21y = c(
|
||||
"pink",
|
||||
fd(bitcoinValueAfter21y),
|
||||
);
|
||||
|
||||
/** @param {number} v */
|
||||
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
|
||||
},
|
||||
);
|
||||
|
||||
totalInvestedAmountData.set((a) => a);
|
||||
bitcoinValueData.set((a) => a);
|
||||
bitcoinData.set((a) => a);
|
||||
resultData.set((a) => a);
|
||||
dollarsLeftData.set((a) => a);
|
||||
totalValueData.set((a) => a);
|
||||
investmentData.set((a) => a);
|
||||
bitcoinAddedData.set((a) => a);
|
||||
averagePricePaidData.set((a) => a);
|
||||
bitcoinPriceData.set((a) => a);
|
||||
buyCountData.set((a) => a);
|
||||
totalFeesPaidData.set((a) => a);
|
||||
daysCountData.set((a) => a);
|
||||
profitableDaysRatioData.set((a) => a);
|
||||
unprofitableDaysRatioData.set((a) => a);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,19 +26,5 @@
|
||||
|
||||
.chart {
|
||||
flex: 1;
|
||||
|
||||
.lightweight-chart {
|
||||
margin-left: var(--negative-main-padding);
|
||||
|
||||
fieldset {
|
||||
padding-left: var(--main-padding);
|
||||
padding-top: 0.5rem;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,8 @@
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
gap: 2rem;
|
||||
padding: var(--main-padding);
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@@ -67,11 +62,16 @@
|
||||
}
|
||||
|
||||
.chart {
|
||||
flex-shrink: 0;
|
||||
flex: none;
|
||||
height: 400px;
|
||||
|
||||
.lightweight-chart {
|
||||
margin-left: calc(var(--negative-main-padding) / 2);
|
||||
margin-left: calc(var(--negative-main-padding) * 0.75);
|
||||
|
||||
fieldset {
|
||||
margin-left: -0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user