website: update

This commit is contained in:
k
2024-11-27 12:56:04 +01:00
parent 4e9c5612ca
commit d39e7584c0
9 changed files with 666 additions and 517 deletions
+3
View File
@@ -54,3 +54,6 @@ docker/kibo
# Types
website/scripts/types/paths.d.ts
# Misc
OPENSATS.md
+1 -1
View File
@@ -8,7 +8,7 @@
## 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)). The goal is to empower people with information that is often hard to come by and/or very pricey.
[**kibō**](https://kibo.money) (_hope_ in japanese) is primarily an open source Bitcoin Core data extractor and visualizer (similar to [Glassnode](https://glassnode.com)) which goal is to empower anybody with data about Bitcoin for free.
The project is split in 3 parts:
+27 -26
View File
@@ -253,6 +253,10 @@
--pink: oklch(0.624 0.245 357.444);
--rose: oklch(0.6155 0.2495 17.012);
--dollar: var(--green);
--background-color: light-dark(var(--white), var(--black));
--color: light-dark(var(--black), var(--white));
--off-color: light-dark(var(--light-gray), var(--dark-gray));
--border-color: light-dark(var(--lighter-gray), var(--darker-gray));
--font-size-2xs: 0.625rem;
--line-height-2xs: 1rem;
@@ -282,11 +286,6 @@
--default-main-width: 25rem;
--background-color: light-dark(var(--white), var(--black));
--color: light-dark(var(--black), var(--white));
--off-color: light-dark(var(--light-gray), var(--dark-gray));
--border-color: light-dark(var(--lighter-gray), var(--darker-gray));
--emoji-filter: grayscale(1) contrast(5) invert(1);
@media (prefers-color-scheme: dark) {
--emoji-filter: grayscale(1) contrast(5);
@@ -388,14 +387,16 @@
aside {
min-width: 0px;
position: relative;
display: flex;
height: 100%;
width: 100%;
overflow-y: auto;
flex: 1;
margin-bottom: calc(var(--main-padding) + 1.5rem);
@media (max-width: 767px) {
padding-bottom: calc(var(--main-padding) + 1.5rem);
html[data-display="standalone"] & {
margin-bottom: calc(var(--main-padding) + 2rem);
padding-bottom: calc(var(--main-padding) + 2rem);
}
}
@@ -601,7 +602,7 @@
display: flex;
margin: var(--main-padding);
margin-bottom: var(--main-padding);
z-index: 20;
z-index: 100;
pointer-events: none;
justify-content: center;
@@ -649,9 +650,16 @@
background-color: white;
}
section {
flex: 1;
min-width: 0;
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: url('data:image/svg+xml;utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M7 10l5 5 5-5z" fill="gray"/><path d="M0 0h24v24H0z" fill="none"/></svg>')
100% 50% no-repeat transparent;
&:focus-visible {
border: none;
}
}
nav,
@@ -811,10 +819,7 @@
}
}
#selected-frame {
flex-direction: column;
display: flex;
aside {
> header {
button {
margin: -0.5rem 0;
@@ -975,8 +980,6 @@
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;
@@ -986,7 +989,7 @@
align-items: center;
font-size: var(--font-size-xs);
line-height: var(--line-height-xs);
gap: 1rem;
gap: 0.5rem;
> legend,
> div {
@@ -1010,8 +1013,8 @@
}
> .chart-div {
width: 100%;
height: 100%;
margin-right: calc(var(--negative-main-padding) - 0.5rem);
}
}
@@ -1528,12 +1531,10 @@
</footer>
</main>
<aside id="aside">
<section id="selected-frame">
<div id="charts" hidden></div>
<div id="simulation" hidden></div>
<div id="live-price" hidden></div>
<div id="moscow-time" hidden></div>
</section>
<div id="charts" hidden></div>
<div id="simulation" hidden></div>
<div id="live-price" hidden></div>
<div id="moscow-time" hidden></div>
</aside>
<div id="share-div" hidden>
<div id="share-content-div">
+28 -105
View File
@@ -34,7 +34,7 @@ export function init({
}) {
console.log("init chart state");
/** @type {Array<(IChartApi & {whitespace: ISeriesApi<"Line">})>} */
/** @type {ChartPane[]} */
let charts = [];
const scale = signals.createMemo(() => selected().scale);
@@ -53,15 +53,26 @@ export function init({
descriptionElement.innerHTML = option.serializedPath;
});
const div = window.document.createElement("div");
elements.charts.append(div);
// const div = window.document.createElement("div");
// elements.charts.append(div);
const legendElement = window.document.createElement("legend");
div.append(legendElement);
// const legendElement = window.document.createElement("legend");
// div.append(legendElement);
const chartListElement = window.document.createElement("div");
chartListElement.classList.add("chart-list");
div.append(chartListElement);
// const chartListElement = window.document.createElement("div");
// chartListElement.classList.add("chart-list");
// div.append(chartListElement);
//
const {
chartListElement,
legendElement,
createPane: addChart,
} = lightweightCharts.createChart({
parent: elements.charts,
signals,
colors,
id: "chart",
});
/**
* @returns {TimeRange}
@@ -248,59 +259,6 @@ export function init({
}
createFetchChunksOfVisibleDatasetsEffect();
/**
* @param {HTMLElement} parent
* @param {number} chartIndex
*/
function createChartDiv(parent, chartIndex) {
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);
function createUnitAndModeElements() {
const fieldset = window.document.createElement("fieldset");
fieldset.dataset.size = "sm";
chartWrapper.append(fieldset);
const unitName = signals.createSignal("");
const id = `chart-${chartIndex}-mode`;
const chartModes = /** @type {const} */ (["Linear", "Log"]);
const chartMode = signals.createSignal(
/** @type {Lowercase<typeof chartModes[number]>} */ (
localStorage.getItem(id) ||
chartModes[chartIndex ? 0 : 1].toLowerCase()
),
);
const field = utils.dom.createHorizontalChoiceField({
choices: chartModes,
selected: chartMode(),
id,
title: unitName,
signals,
});
fieldset.append(field);
field.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
localStorage.setItem(id, value);
chartMode.set(value);
});
return { unitName, chartMode };
}
const { unitName, chartMode } = createUnitAndModeElements();
return { chartDiv, unitName, chartMode };
}
/**
* @param {IChartApi} chart
*/
@@ -526,7 +484,7 @@ export function init({
* @param {ResourceDataset<S>} args.dataset
* @param {SeriesBlueprint} args.seriesBlueprint
* @param {Option} args.option
* @param {IChartApi} args.chart
* @param {ChartPane} args.chart
* @param {number} args.index
* @param {Series[]} args.chartSeries
* @param {Accessor<number | undefined>} args.lastVisibleDatasetIndex
@@ -630,42 +588,26 @@ export function init({
if (!s) {
switch (type) {
case "Baseline": {
s = lightweightCharts.createBaseLineSeries({
chart,
s = chart.createBaseLineSeries({
color,
options: seriesOptions,
owner,
signals,
colors,
});
break;
}
case "Candlestick": {
s = lightweightCharts.createCandlesticksSeries({
chart,
s = chart.createCandlesticksSeries({
options: seriesOptions,
owner,
signals,
colors,
});
break;
}
// case "Histogram": {
// s = createHistogramSeries({
// chart,
// options,
// });
// break;
// }
default:
case "Line": {
s = lightweightCharts.createLineSeries({
chart,
s = chart.createLineSeries({
color,
options: seriesOptions,
owner,
signals,
colors,
});
break;
}
@@ -766,7 +708,7 @@ export function init({
* @param {PriceSeriesType} args.type
* @param {VoidFunction} args.setMinMaxMarkersWhenIdle
* @param {Option} args.option
* @param {IChartApi} args.chart
* @param {ChartPane} args.chart
* @param {Series[]} args.chartSeries
* @param {Accessor<number | undefined>} args.lastVisibleDatasetIndex
*/
@@ -864,16 +806,11 @@ export function init({
const allSeries = [];
charts = chartsBlueprints.map((seriesBlueprints, chartIndex) => {
const { chartDiv, unitName, chartMode } = createChartDiv(
chartListElement,
const chart = addChart({
chartIndex,
);
const chart = lightweightCharts.createChartWithWhitespace({
scale,
element: chartDiv,
signals,
colors,
unit: option.unit || "US Dollars",
whitespace: true,
});
setInitialVisibleTimeRange(chart);
@@ -1063,12 +1000,6 @@ export function init({
});
}
createLinkPriceSeriesEffect();
/** @type {Unit} */
const unit = "US Dollars";
unitName.set(unit);
} else {
unitName.set(option.unit);
}
[...seriesBlueprints].reverse().forEach((seriesBlueprint, index) => {
@@ -1110,9 +1041,7 @@ export function init({
function createChartVisibilityEffect() {
signals.createEffect(chartVisible, (chartVisible) => {
const chartWrapper = chartDiv.parentElement;
if (!chartWrapper) throw "Should exist";
chartWrapper.hidden = !chartVisible;
chart.setHidden(!chartVisible);
});
}
createChartVisibilityEffect();
@@ -1134,12 +1063,6 @@ export function init({
}
createTimeScaleVisibilityEffect();
signals.createEffect(chartMode, (chartMode) =>
chart.priceScale("right").applyOptions({
mode: chartMode === "linear" ? 0 : 1,
}),
);
chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => {
if (!logicalRange) return;
+280 -148
View File
@@ -1,7 +1,7 @@
// @ts-check
/**
* @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, Frequency } from "./types/self"
* @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, Frequency, CreatePaneParameters, CreateBaselineSeriesParams, CreateCandlestickSeriesParams, CreateLineSeriesParams } 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, LineData, LogicalRange, SeriesMarker, CandlestickData, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v4.2.0/types"
@@ -252,7 +252,7 @@ function initPackages() {
* @param {Colors} args.colors
* @param {DeepPartial<ChartOptions>} [args.options]
*/
function createChart({
function createLightweightChart({
scale,
element,
signals,
@@ -282,9 +282,6 @@ function initPackages() {
time: false,
},
},
crosshair: {
mode: 0,
},
localization: {
priceFormatter: utils.locale.numberToShortUSFormat,
locale: "en-us",
@@ -360,127 +357,6 @@ function initPackages() {
baseLineColor: "",
};
/**
* @param {SpecificSeriesBlueprintWithChart<BaselineSpecificSeriesBlueprint> & {colors: Colors, signals: Signals}} args
*/
function createBaseLineSeries({
chart,
color,
options,
owner,
colors,
signals,
}) {
const topLineColor = color || colors.profit;
const bottomLineColor = color || colors.loss;
function computeColors() {
return {
topLineColor: topLineColor(),
bottomLineColor: bottomLineColor(),
};
}
const transparent = "transparent";
/** @type {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon>} */
const seriesOptions = {
priceScaleId: "right",
...defaultSeriesOptions,
...options,
topFillColor1: transparent,
topFillColor2: transparent,
bottomFillColor1: transparent,
bottomFillColor2: transparent,
...computeColors(),
};
const series = chart.addBaselineSeries(seriesOptions);
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
series.applyOptions(computeColors);
});
});
return series;
}
/**
* @param {SpecificSeriesBlueprintWithChart<CandlestickSpecificSeriesBlueprint> & {colors: Colors, signals: Signals}} args
*/
function createCandlesticksSeries({
chart,
options,
owner,
signals,
colors,
}) {
function computeColors() {
const upColor = colors.profit();
const downColor = colors.loss();
return {
upColor,
wickUpColor: upColor,
downColor,
wickDownColor: downColor,
};
}
const candlestickSeries = chart.addCandlestickSeries({
baseLineVisible: false,
borderVisible: false,
priceLineVisible: false,
baseLineColor: "",
borderColor: "",
borderDownColor: "",
borderUpColor: "",
...options,
...computeColors(),
});
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
candlestickSeries.applyOptions(computeColors);
});
});
return candlestickSeries;
}
/**
* @param {SpecificSeriesBlueprintWithChart<LineSpecificSeriesBlueprint> & {colors: Colors, signals: Signals}} args
*/
function createLineSeries({
chart,
color,
options,
owner,
signals,
colors,
}) {
function computeColors() {
return {
color: color(),
};
}
const series = chart.addLineSeries({
...defaultSeriesOptions,
...options,
...computeColors(),
});
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
series.applyOptions(computeColors);
});
});
return series;
}
function initWhitespace() {
const whitespaceStartDate = new Date("1970-01-01");
const whitespaceStartDateYear =
@@ -576,34 +452,285 @@ function initPackages() {
const { setWhitespace } = initWhitespace();
/**
*
* @param {Parameters<typeof createChart>[0]} args
* @typeof {Object} PaneParameters
* @property {Unit} param.unit
* @param {TimeScale} param.scale
* @param {number} [param.chartIndex]
* @param {true} [param.whitespace]
* @param {DeepPartial<ChartOptions>} [param.options]
*/
function createChartWithWhitespace({
element,
scale,
colors,
/**
* @param {Object} param0
* @param {string} param0.id
* @param {HTMLElement} param0.parent
* @param {Signals} param0.signals
* @param {Colors} param0.colors
* @param {"static" | "dynamic"} [param0.kind]
* @param {CreatePaneParameters[]} [param0.config]
*/
function createChart({
parent,
signals,
colors,
id: chartId,
kind,
config,
}) {
const chart =
/** @type {IChartApi & {whitespace: ISeriesApi<"Line">}} */ (
createChart({
colors,
element,
scale,
const div = window.document.createElement("div");
div.classList.add("charts");
parent.append(div);
const legendElement = window.document.createElement("legend");
div.append(legendElement);
const chartListElement = window.document.createElement("div");
chartListElement.classList.add("chart-list");
div.append(chartListElement);
/**
* @param {CreatePaneParameters} param
*/
function createPane({
chartIndex,
whitespace,
scale,
unit,
options,
config,
}) {
const chartWrapper = window.document.createElement("div");
chartWrapper.classList.add("chart-wrapper");
chartListElement.append(chartWrapper);
const chartDiv = window.document.createElement("div");
chartDiv.classList.add("chart-div");
chartWrapper.append(chartDiv);
options = { ...options };
if (kind === "static") {
options.handleScale = false;
options.handleScroll = false;
} else {
options.crosshair = {
...options.crosshair,
mode: 0,
};
}
const _chart = createLightweightChart({
scale,
element: chartDiv,
signals,
colors,
options,
});
/**
* @param {CreateBaselineSeriesParams} args
*/
function createBaseLineSeries({ color, options, owner, data }) {
const topLineColor = color || colors.profit;
const bottomLineColor = color || colors.loss;
function computeColors() {
return {
topLineColor: topLineColor(),
bottomLineColor: bottomLineColor(),
};
}
const transparent = "transparent";
/** @type {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon>} */
const seriesOptions = {
priceScaleId: "right",
...defaultSeriesOptions,
...options,
topFillColor1: transparent,
topFillColor2: transparent,
bottomFillColor1: transparent,
bottomFillColor2: transparent,
...computeColors(),
};
const series = _chart.addBaselineSeries(seriesOptions);
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
series.applyOptions(computeColors);
});
});
if (data) {
series.setData(data);
}
return series;
}
/**
* @param {CreateCandlestickSeriesParams} args
*/
function createCandlestickSeries({ options, owner, data }) {
function computeColors() {
const upColor = colors.profit();
const downColor = colors.loss();
return {
upColor,
wickUpColor: upColor,
downColor,
wickDownColor: downColor,
};
}
const series = _chart.addCandlestickSeries({
baseLineVisible: false,
borderVisible: false,
priceLineVisible: false,
baseLineColor: "",
borderColor: "",
borderDownColor: "",
borderUpColor: "",
...options,
...computeColors(),
});
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
series.applyOptions(computeColors);
});
});
if (data) {
series.setData(data);
}
return series;
}
/**
* @param {CreateLineSeriesParams} args
*/
function createLineSeries({ color, options, owner, data }) {
function computeColors() {
return {
color: color(),
};
}
const series = _chart.addLineSeries({
...defaultSeriesOptions,
...options,
...computeColors(),
});
if (data) {
series.setData(data);
}
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
series.applyOptions(computeColors);
});
});
return series;
}
const chart =
/** @type {IChartApi & { whitespace: ISeriesApi<"Line">, createBaseLineSeries: typeof createBaseLineSeries, createCandlesticksSeries: typeof createCandlestickSeries, createLineSeries: typeof createLineSeries; setHidden: (b: boolean) => void }} */ (
_chart
);
if (whitespace) {
chart.whitespace = setWhitespace(_chart, scale);
}
chart.createBaseLineSeries = createBaseLineSeries;
chart.createCandlesticksSeries = createCandlestickSeries;
chart.createLineSeries = createLineSeries;
chart.setHidden = (b) => {
chartWrapper.hidden = b;
};
function createUnitAndModeElements() {
const fieldset = window.document.createElement("fieldset");
fieldset.dataset.size = "sm";
chartWrapper.append(fieldset);
const id = `chart-${chartId}-${chartIndex}-mode`;
const chartModes = /** @type {const} */ (["Linear", "Log"]);
const chartMode = signals.createSignal(
/** @type {Lowercase<typeof chartModes[number]>} */ (
localStorage.getItem(id) ||
chartModes[chartIndex ? 0 : 1].toLowerCase()
),
);
const field = utils.dom.createHorizontalChoiceField({
choices: chartModes,
selected: chartMode(),
id,
title: unit,
signals,
})
);
chart.whitespace = setWhitespace(chart, scale);
return chart;
});
fieldset.append(field);
field.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
localStorage.setItem(id, value);
chartMode.set(value);
});
signals.createEffect(chartMode, (chartMode) =>
_chart.priceScale("right").applyOptions({
mode: chartMode === "linear" ? 0 : 1,
}),
);
}
createUnitAndModeElements();
config?.forEach((params) => {
switch (params.kind) {
case "line": {
chart.createLineSeries(params);
break;
}
case "candle": {
chart.createCandlesticksSeries(params);
break;
}
case "baseline": {
chart.createBaseLineSeries(params);
break;
}
}
});
if (kind === "static") {
chart.timeScale().fitContent();
}
return chart;
}
config?.forEach((params) => {
createPane(params);
});
return {
legendElement,
chartListElement,
createPane,
};
}
return {
createChart,
createChartWithWhitespace,
createBaseLineSeries,
createCandlesticksSeries,
createLineSeries,
};
},
),
@@ -654,6 +781,7 @@ const packages = initPackages();
/**
* @typedef {Awaited<ReturnType<typeof packages.signals>>} Signals
* @typedef {Awaited<ReturnType<typeof packages.lightweightCharts>>} LightweightCharts
* @typedef {ReturnType<ReturnType<Awaited<ReturnType<typeof packages.lightweightCharts>>['createChart']>['createPane']>} ChartPane
*/
const options = import("./options.js");
@@ -1700,7 +1828,6 @@ const elements = {
search: utils.dom.getElementById("search"),
nav: utils.dom.getElementById("nav"),
navHeader: utils.dom.getElementById("nav-header"),
selectedFrame: utils.dom.getElementById("selected-frame"),
searchInput: /** @type {HTMLInputElement} */ (
utils.dom.getElementById("search-input")
),
@@ -1914,8 +2041,13 @@ function createColors(dark) {
offDollars: emerald,
yellow,
lime,
orange,
red,
sky,
blue,
rose,
pink,
_1d: pink,
_1w: red,
@@ -2813,7 +2945,7 @@ packages.signals().then((signals) =>
}
createMobileSwitchEffect();
utils.dom.onFirstIntersection(elements.selectedFrame, initSelectedFrame);
utils.dom.onFirstIntersection(elements.aside, initSelectedFrame);
}
initSelected();
+237 -191
View File
@@ -292,14 +292,8 @@ export function init({
);
const firstParagraph = window.document.createElement("p");
resultsElement.append(firstParagraph);
const secondParagraph = window.document.createElement("p");
resultsElement.append(secondParagraph);
const parent = window.document.createElement("div");
parent.classList.add("chart-list");
resultsElement.append(parent);
const owner = signals.getOwner();
@@ -330,24 +324,27 @@ export function init({
fees,
}) => {
console.log({ start, end });
parent.innerHTML = "";
resultsElement.innerHTML = "";
resultsElement.append(firstParagraph);
resultsElement.append(secondParagraph);
if (!start || !end || start > end) return;
const range = utils.date.getRange(start, end);
/** @type {LineData<Time>[]} */
const investedData = [];
const totalInvestedAmountData = [];
/** @type {LineData<Time>[]} */
const returnData = [];
const bitcoinValueData = [];
/** @type {LineData<Time>[]} */
const bitcoinData = [];
/** @type {LineData<Time>[]} */
const resultData = [];
/** @type {LineData<Time>[]} */
const dollarsData = [];
const dollarsLeftData = [];
/** @type {LineData<Time>[]} */
const totalData = [];
const totalValueData = [];
/** @type {LineData<Time>[]} */
const investmentData = [];
/** @type {LineData<Time>[]} */
@@ -357,17 +354,26 @@ export function init({
/** @type {LineData<Time>[]} */
const bitcoinPriceData = [];
/** @type {LineData<Time>[]} */
const investmentsData = [];
const buyCountData = [];
/** @type {LineData<Time>[]} */
const totalFeesPaidData = [];
/** @type {LineData<Time>[]} */
const daysCountData = [];
/** @type {LineData<Time>[]} */
const profitableDaysRatioData = [];
let bitcoin = 0;
let dollars = initialDollarAmount;
let investedAmount = 0;
let investmentsCount = 0;
let buyCount = 0;
let averagePricePaid = 0;
let _return = 0;
let bitcoinValue = 0;
let roi = 0;
let feesPaid = 0;
let totalValue = 0;
let totalFeesPaid = 0;
let daysCount = range.length;
let profitableDays = 0;
let unprofitableDays = 0;
range.forEach((date, index) => {
const year = date.getUTCFullYear();
@@ -383,13 +389,13 @@ export function init({
if (!close) return;
let investmentPreFees = 0;
let dailyInvestment = 0;
/** @param {number} value */
function invest(value) {
value = Math.min(dollars, value);
investmentPreFees += value;
dailyInvestment += value;
dollars -= value;
investmentsCount += 1;
buyCount += 1;
}
if (!index) {
invest(initialSwap);
@@ -398,19 +404,31 @@ export function init({
invest(recurrentSwap);
}
let investment = investmentPreFees * (1 - (fees || 0) / 100);
feesPaid += investmentPreFees - investment;
let dailyInvestmentPostFees =
dailyInvestment * (1 - (fees || 0) / 100);
const bitcoinAdded = investment / close;
totalFeesPaid += dailyInvestment - dailyInvestmentPostFees;
const bitcoinAdded = dailyInvestmentPostFees / close;
bitcoin += bitcoinAdded;
investedAmount += investment;
investedAmount += dailyInvestmentPostFees;
_return = close * bitcoin;
bitcoinValue = close * bitcoin;
totalValue = dollars + bitcoinValue;
averagePricePaid = investedAmount / bitcoin;
roi = (_return / investedAmount - 1) * 100;
roi = (bitcoinValue / investedAmount - 1) * 100;
const daysCount = index + 1;
if (roi >= 0) {
profitableDays += 1;
} else {
unprofitableDays += 1;
}
bitcoinPriceData.push({
time,
@@ -422,14 +440,14 @@ export function init({
value: bitcoin,
});
investedData.push({
totalInvestedAmountData.push({
time,
value: investedAmount,
});
returnData.push({
bitcoinValueData.push({
time,
value: _return,
value: bitcoinValue,
});
resultData.push({
@@ -437,19 +455,19 @@ export function init({
value: roi,
});
dollarsData.push({
dollarsLeftData.push({
time,
value: dollars,
});
totalData.push({
totalValueData.push({
time,
value: dollars + _return,
value: totalValue,
});
investmentData.push({
time,
value: investment,
value: dailyInvestment,
});
bitcoinAddedData.push({
@@ -462,19 +480,27 @@ export function init({
value: averagePricePaid,
});
investmentsData.push({
buyCountData.push({
time,
value: investmentsCount,
value: buyCount,
});
totalFeesPaidData.push({
time,
value: totalFeesPaid,
});
daysCountData.push({
time,
value: daysCount,
});
profitableDaysRatioData.push({
time,
value: profitableDays / daysCount,
});
});
// const { headerElement } = utils.dom.createHeader({
// title: "TItle",
// description: "Description",
// });
// parent.append(headerElement);
const f = utils.locale.numberToUSFormat;
/**
* @param {string} c
@@ -482,168 +508,188 @@ export function init({
*/
const c = (c, t) => createColoredSpan({ color: c, text: t });
firstParagraph.innerHTML = `After exchanging ${c("dollar", `$${f(investedAmount)}`)} in the span of ${c("sky", f(range.length))} days, you would've accumulated ${c("orange", f(bitcoin))} Bitcoin worth ${c("dollar", `$${f(_return)}`)} at an average price of ${c("dollar", `$${f(averagePricePaid)}`)} per Bitcoin with a return of investment of ${c("yellow", `${f(roi)}%`)}.`;
firstParagraph.innerHTML = `After exchanging ${c("dollar", `$${f(investedAmount)}`)} in the span of ${c("sky", f(daysCount))} days, you would've accumulated ${c("orange", f(bitcoin))} Bitcoin worth ${c("dollar", `$${f(bitcoinValue)}`)} at an average price of ${c("dollar", `$${f(averagePricePaid)}`)} per Bitcoin with a return of investment of ${c("yellow", `${f(roi)}%`)}.`;
secondParagraph.innerHTML = `After exchanging ${c("dollar", `$${f(investedAmount)}`)} in the span of ${c("sky", f(range.length))} days, you would've accumulated ${c("orange", f(bitcoin))} Bitcoin worth ${c("dollar", `$${f(_return)}`)} at an average price of ${c("dollar", `$${f(averagePricePaid)}`)} per Bitcoin with a return of investment of ${c("yellow", `${f(roi)}%`)}.`;
secondParagraph.innerHTML = `Work in progress`;
(() => {
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,
lightweightCharts.createChart({
parent: resultsElement,
signals,
colors,
id: `simulation-0`,
kind: "static",
config: [
{
unit: "US Dollars",
scale: "date",
config: [
{
kind: "line",
color: colors.dollars,
owner,
data: totalInvestedAmountData,
},
{
kind: "line",
color: colors.bitcoin,
owner,
data: bitcoinValueData,
},
{
kind: "line",
color: colors.lime,
owner,
data: dollarsLeftData,
},
{
kind: "line",
color: colors.default,
owner,
data: totalValueData,
},
{
kind: "line",
color: colors.yellow,
owner,
data: investmentData,
},
{
kind: "line",
color: colors.red,
owner,
data: totalFeesPaidData,
},
],
},
});
],
});
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,
lightweightCharts.createChart({
parent: resultsElement,
signals,
colors,
id: `simulation-1`,
kind: "static",
config: [
{
unit: "US Dollars",
scale: "date",
config: [
{
kind: "line",
color: colors.bitcoin,
owner,
data: bitcoinData,
},
{
kind: "line",
color: colors.lightBitcoin,
owner,
data: bitcoinAddedData,
},
],
},
});
],
});
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,
lightweightCharts.createChart({
parent: resultsElement,
signals,
colors,
id: `simulation-2`,
kind: "static",
config: [
{
unit: "US Dollars",
scale: "date",
config: [
{
kind: "baseline",
owner,
data: resultData,
},
],
},
});
],
});
const line = chart.addLineSeries();
line.setData(resultData);
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,
lightweightCharts.createChart({
parent: resultsElement,
signals,
colors,
id: `simulation-3`,
kind: "static",
config: [
{
unit: "US Dollars",
scale: "date",
config: [
{
kind: "line",
owner,
color: colors.bitcoin,
data: bitcoinPriceData,
},
{
kind: "line",
owner,
color: colors.default,
data: averagePricePaidData,
},
],
},
});
],
});
const line = chart.addLineSeries();
line.setData(bitcoinPriceData);
const line2 = chart.addLineSeries();
line2.setData(averagePricePaidData);
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,
lightweightCharts.createChart({
parent: resultsElement,
signals,
colors,
id: `simulation-4`,
kind: "static",
config: [
{
unit: "US Dollars",
scale: "date",
config: [
{
kind: "line",
owner,
color: colors.blue,
data: buyCountData,
},
{
kind: "line",
owner,
color: colors.sky,
data: daysCountData,
},
],
},
});
],
});
const line = chart.addLineSeries();
line.setData(investmentsData);
chart.timeScale().fitContent();
})();
lightweightCharts.createChart({
parent: resultsElement,
signals,
colors,
id: `simulation-5`,
kind: "static",
config: [
{
unit: "Percentage",
scale: "date",
config: [
{
kind: "line",
owner,
color: colors.pink,
data: profitableDaysRatioData,
},
],
},
],
});
},
);
});
+27 -4
View File
@@ -15,6 +15,7 @@ import {
SeriesType,
IChartApi,
ISeriesApi,
BaselineData,
} from "../../packages/lightweight-charts/v4.2.0/types";
import { DatePath, HeightPath, LastPath } from "./paths";
import { Owner } from "../../packages/solid-signals/2024-11-02/types/core/owner";
@@ -46,18 +47,21 @@ interface BaselineSpecificSeriesBlueprint {
type: "Baseline";
color?: Color;
options?: DeepPartial<BaselineStyleOptions & SeriesOptionsCommon>;
data?: BaselineData<Time>[];
}
interface CandlestickSpecificSeriesBlueprint {
type: "Candlestick";
color?: Color;
options?: DeepPartial<CandlestickStyleOptions & SeriesOptionsCommon>;
data?: CandlestickData<Time>[];
}
interface LineSpecificSeriesBlueprint {
type?: "Line";
color: Color;
options?: DeepPartial<LineStyleOptions & SeriesOptionsCommon>;
data?: LineData<Time>[];
}
type AnySpecificSeriesBlueprint =
@@ -66,10 +70,16 @@ type AnySpecificSeriesBlueprint =
| LineSpecificSeriesBlueprint;
type SpecificSeriesBlueprintWithChart<A extends AnySpecificSeriesBlueprint> = {
chart: IChartApi;
owner: Owner | null;
} & Omit<A, "type">;
type CreateBaselineSeriesParams =
SpecificSeriesBlueprintWithChart<BaselineSpecificSeriesBlueprint>;
type CreateLineSeriesParams =
SpecificSeriesBlueprintWithChart<LineSpecificSeriesBlueprint>;
type CreateCandlestickSeriesParams =
SpecificSeriesBlueprintWithChart<CandlestickSpecificSeriesBlueprint>;
type SeriesBlueprint = {
datasetPath: AnyDatasetPath;
title: string;
@@ -224,7 +234,7 @@ interface OHLC {
interface ResourceDataset<
Scale extends TimeScale,
Type extends OHLC | number = number
Type extends OHLC | number = number,
> {
scale: Scale;
url: string;
@@ -243,7 +253,7 @@ interface FetchedResult<
SingleValueData | ValuedCandlestickData
> = DatasetValue<
Type extends number ? SingleValueData : ValuedCandlestickData
>
>,
> {
at: Date | null;
json: Signal<FetchedJSON<Scale, Type> | null>;
@@ -273,7 +283,7 @@ interface FetchedChunk {
type FetchedDataset<
Scale extends TimeScale,
Type extends number | OHLC
Type extends number | OHLC,
> = Scale extends "date"
? FetchedDateDataset<Type>
: FetchedHeightDataset<Type>;
@@ -376,3 +386,16 @@ interface Frequency {
value: string;
isTriggerDay: (date: Date) => boolean;
}
interface CreatePaneParameters {
unit: Unit;
scale: TimeScale;
chartIndex?: number;
whitespace?: true;
options?: DeepPartial<ChartOptions>;
config?: (
| ({ kind: "line" } & CreateLineSeriesParams)
| ({ kind: "candle" } & CreateCandlestickSeriesParams)
| ({ kind: "baseline" } & CreateBaselineSeriesParams)
)[];
}
+6 -5
View File
@@ -1,7 +1,8 @@
#charts {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
width: 100%;
min-height: 0;
padding: var(--main-padding);
@@ -23,11 +24,12 @@
}
}
> div {
.charts {
flex: 1;
margin-top: 2rem;
display: flex;
flex-direction: column;
margin-top: 2rem;
min-height: 0;
> legend {
@@ -80,9 +82,8 @@
}
}
.chart-list {
.chart-div {
margin-left: var(--negative-main-padding);
margin-right: calc(var(--negative-main-padding) - 0.5rem);
}
}
}
+57 -37
View File
@@ -1,42 +1,8 @@
#simulation {
min-height: 0;
display: flex;
height: 100%;
width: 100%;
header {
margin-bottom: 0.5rem;
}
> div:first-child {
max-width: var(--default-main-width);
border-right: 1px;
padding-bottom: var(--bottom-area);
}
> div:last-child {
display: flex;
flex-direction: column;
gap: 1rem;
p {
font-size: var(--font-size-lg);
text-wrap: pretty;
}
}
label {
> span {
display: block;
}
small {
font-size: var(--font-size-base);
}
}
> div {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1.5rem;
@@ -60,8 +26,62 @@
}
}
.chart-list {
max-height: 2000px;
margin-right: calc(var(--negative-main-padding) - 0.5rem);
@media (max-width: 767px) {
overflow-y: auto;
> div:first-child {
border-bottom: 1px;
}
}
@media (min-width: 768px) {
display: flex;
flex-direction: column;
height: 100%;
flex-direction: row;
> div {
flex: 1;
overflow-y: auto;
padding-bottom: var(--bottom-area);
}
> div:first-child {
max-width: var(--default-main-width);
border-right: 1px;
}
}
header {
margin-bottom: 0.5rem;
}
> div:last-child {
display: flex;
flex-direction: column;
gap: 1.5rem;
overflow-x: hidden;
p {
text-wrap: pretty;
}
}
label {
> span {
display: block;
}
small {
font-size: var(--font-size-base);
}
}
.charts {
flex-shrink: 0;
height: 400px;
.chart-div {
margin-left: calc(var(--negative-main-padding) / 2);
}
}
}