website: moved more code to lc wrapper

This commit is contained in:
nym21
2024-12-04 10:28:30 +01:00
parent 62f6d9a413
commit 8af1ddd10d
6 changed files with 178 additions and 215 deletions
+6 -1
View File
@@ -1547,7 +1547,12 @@
<small style="display: block">
<strong>Bitcoin</strong> is
<b style="color: var(--color)">hope</b>
<span style="filter: var(--emoji-filter)">🕊️</span>
<button
style="filter: var(--emoji-filter)"
onclick="window.document.documentElement.style.colorScheme = window.document.documentElement.style.colorScheme === 'light' ? 'dark' : 'light'"
>
🕊️
</button>
</small>
</header>
</nav>
+4 -4
View File
@@ -12,7 +12,7 @@ import {
ISeriesApi,
BaselineData,
} from "./v4.2.0/types";
import { Color, ValuedCandlestickData } from "../../scripts/types/self";
import { Color, Valued, ValuedCandlestickData } from "../../scripts/types/self";
interface BaseSeriesBlueprint {
title: string;
@@ -28,7 +28,7 @@ interface CandlestickSeriesBlueprint extends BaseSeriesBlueprint {
type: "Candlestick";
color?: Color;
options?: DeepPartial<CandlestickStyleOptions & SeriesOptionsCommon>;
data?: Accessor<CandlestickData<Time>[]>;
data?: Accessor<(CandlestickData<Time> & Valued)[]>;
}
interface LineSeriesBlueprint extends BaseSeriesBlueprint {
type?: "Line";
@@ -98,16 +98,16 @@ type ChartPane = IChartApi & {
createSingleSeries: (a: CreateSingleSeriesParameters) => SingleSeries;
createSplitSeries: <S extends TimeScale>(
a: CreateSplitSeriesParameters<S>,
) => SplitSeries;
) => SplitSeries[];
anySeries: AnySeries[];
singleSeries: SingleSeries[];
splitSeries: SplitSeries[];
remove: VoidFunction;
};
interface CreatePaneParameters {
unit: Unit;
paneIndex?: number;
whitespace?: true;
options?: DeepPartial<ChartOptions>;
config?: SingleSeriesBlueprint[];
}
+87 -18
View File
@@ -721,7 +721,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
/**
* @param {CreatePaneParameters} param
*/
function createPane({ paneIndex, whitespace, unit, options, config }) {
function createPane({ paneIndex, unit, options, config }) {
const chartWrapper = window.document.createElement("div");
chartWrapper.classList.add("pane");
panesElement.append(chartWrapper);
@@ -797,10 +797,11 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
return series;
}
let hasCandleSeries = false;
/**
* @param {RemoveSeriesBlueprintFluff<CandlestickSeriesBlueprint>} args
*/
function createCandlestickSeries({ options, data }) {
function createCandlestickSeries({ color, options, data }) {
function computeColors() {
const upColor = colors.profit();
const downColor = colors.loss();
@@ -825,6 +826,8 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
...computeColors(),
});
hasCandleSeries = true;
signals.runWithOwner(owner, () => {
signals.createEffect(computeColors, (computeColors) => {
series.applyOptions(computeColors);
@@ -839,6 +842,8 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
});
}
updateVisiblePriceSeriesType();
return series;
}
@@ -980,7 +985,12 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
return series;
};
pane.createSplitSeries = function ({
/**
* @template {TimeScale} T
* @param {CreateSplitSeriesParameters<T>} param0
* @returns
*/
function createSplitSeries({
id,
index: seriesIndex,
disabled,
@@ -1141,11 +1151,42 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
createLegend({ series, extraName: blueprint.type });
return series;
}
pane.createSplitSeries = function (a) {
if (a.blueprint.type === "Candlestick") {
const candleSeries = createSplitSeries({
disabled: signals.createMemo(
() => priceSeriesType() !== "Candlestick",
),
...a,
});
const lineSeries = createSplitSeries({
disabled: signals.createMemo(() => priceSeriesType() !== "Line"),
...a,
blueprint: {
color: colors.default,
...a.blueprint,
type: "Line",
},
});
signals.createEffect(candleSeries.active, (active) => {
lineSeries.active.set(active);
});
signals.createEffect(lineSeries.active, (active) => {
candleSeries.active.set(active);
});
return [candleSeries, lineSeries];
} else {
return [createSplitSeries(a)];
}
};
pane.hidden = () => {
pane.hidden = function () {
return chartWrapper.hidden;
};
pane.setHidden = (b) => {
pane.setHidden = function (b) {
chartWrapper.hidden = b;
};
pane.splitSeries = [];
@@ -1165,14 +1206,15 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
}, 50);
}
};
const remove = _chart.remove;
pane.remove = function () {
_chart.remove();
remove.call(this);
pane.splitSeries.length = 0;
pane.singleSeries.length = 0;
pane.anySeries.length = 0;
};
if (whitespace) {
if (kind === "moveable") {
pane.whitespace = setWhitespace({ chart: _chart, scale, utils });
}
@@ -1479,6 +1521,12 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
setMinMaxMarkersWhenIdle,
);
pane.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => {
if (!logicalRange || !hasCandleSeries) return;
// Must be the chart with the visible timeScale
debouncedUpdateVisiblePriceSeriesType(logicalRange);
});
return pane;
}
@@ -1503,11 +1551,9 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
}
/**
* @param {Object} args
* @param {LogicalRange} [args.visibleLogicalRange]
* @param {TimeRange} [args.visibleTimeRange]
* @param {LogicalRange} [visibleLogicalRange]
*/
function getTicksToWidthRatio({ visibleLogicalRange, visibleTimeRange }) {
function getTicksToWidthRatio(visibleLogicalRange) {
try {
const chartPane = panes.find((pane) => !pane.hidden());
if (!chartPane) return;
@@ -1518,10 +1564,12 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
if (visibleLogicalRange) {
ratio = (visibleLogicalRange.to - visibleLogicalRange.from) / width;
} else if (visibleTimeRange) {
} else {
let range = visibleTimeRange();
if (scale === "date") {
const to = /** @type {Time} */ (visibleTimeRange.to);
const from = /** @type {Time} */ (visibleTimeRange.from);
const to = /** @type {Time} */ (range.to);
const from = /** @type {Time} */ (range.from);
ratio =
utils.getNumberOfDaysBetweenTwoDates(
@@ -1529,19 +1577,39 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
utils.date.fromTime(to),
) / width;
} else {
const to = /** @type {number} */ (visibleTimeRange.to);
const from = /** @type {number} */ (visibleTimeRange.from);
const to = /** @type {number} */ (range.to);
const from = /** @type {number} */ (range.from);
ratio = (to - from) / width;
}
} else {
throw Error();
}
return ratio;
} catch {}
}
const priceSeriesType = signals.createSignal(
/** @type {PriceSeriesType} */ ("Candlestick"),
);
/**
* @param {Parameters<typeof getTicksToWidthRatio>[0]} [args]
*/
function updateVisiblePriceSeriesType(args) {
const ratio = getTicksToWidthRatio(args);
if (ratio) {
if (ratio <= 0.5) {
priceSeriesType.set("Candlestick");
} else {
priceSeriesType.set("Line");
}
}
}
const debouncedUpdateVisiblePriceSeriesType = utils.debounce(
updateVisiblePriceSeriesType,
50,
);
return {
createPane,
reset,
@@ -1549,6 +1617,7 @@ export default import("./v4.2.0/script.js").then((lightweightCharts) => {
visibleDatasetIds,
getInitialVisibleTimeRange,
getTicksToWidthRatio,
priceSeriesType,
};
}
+29 -128
View File
@@ -1,9 +1,5 @@
// @ts-check
/**
* @import { PriceSeriesType } from '../packages/lightweight-charts/types';
*/
/**
* @param {Object} args
* @param {Colors} args.colors
@@ -14,11 +10,9 @@
* @param {Datasets} args.datasets
* @param {WebSockets} args.webSockets
* @param {Elements} args.elements
* @param {Accessor<boolean>} args.dark
*/
export function init({
colors,
dark,
datasets,
elements,
lightweightCharts,
@@ -59,10 +53,6 @@ export function init({
},
);
const priceSeriesType = signals.createSignal(
/** @type {PriceSeriesType} */ ("Candlestick"),
);
function createFetchChunksOfVisibleDatasetsEffect() {
signals.createEffect(
() => ({
@@ -85,90 +75,6 @@ export function init({
}
createFetchChunksOfVisibleDatasetsEffect();
/**
* @param {Parameters<Chart['getTicksToWidthRatio']>[0]} args
*/
function updateVisiblePriceSeriesType(args) {
const ratio = chart.getTicksToWidthRatio(args);
if (ratio) {
if (ratio <= 0.5) {
priceSeriesType.set("Candlestick");
} else {
priceSeriesType.set("Line");
}
}
}
const debouncedUpdateVisiblePriceSeriesType = utils.debounce(
updateVisiblePriceSeriesType,
50,
);
/**
* @param {Object} args
* @param {PriceSeriesType} args.type
* @param {Option} args.option
* @param {ChartPane} args.chartPane
*/
function createPriceSeries({ type, option, chartPane }) {
const s = scale();
/** @type {AnyDatasetPath} */
const datasetPath = `${s}-to-price`;
const dataset = datasets.getOrCreate(s, datasetPath);
// Don't trigger reactivity by design
activeDatasets().add(dataset);
const title = "BTC Price";
/** @type {SplitSeriesBlueprint} */
let blueprint;
if (type === "Candlestick") {
blueprint = {
datasetPath,
title,
type: "Candlestick",
};
} else {
blueprint = {
datasetPath,
title,
color: colors.default,
};
}
const disabled = signals.createMemo(() => priceSeriesType() !== type);
const priceSeries = chartPane.createSplitSeries({
blueprint,
dataset,
id: option.id,
index: -1,
disabled,
});
function createLiveCandleUpdateEffect() {
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return;
const index = utils.chunkIdToIndex(s, latest.year);
const series = priceSeries.chunks.at(index);
if (series) {
signals.createEffect(series, (series) => {
series?.update(latest);
});
}
});
}
createLiveCandleUpdateEffect();
return priceSeries;
}
/**
* @param {ChartOption} option
*/
@@ -189,47 +95,42 @@ export function init({
const chartPane = chart.createPane({
paneIndex,
unit: paneIndex ? option.unit : "US Dollars",
whitespace: true,
});
if (!paneIndex) {
updateVisiblePriceSeriesType({
visibleTimeRange: chart.visibleTimeRange(),
/** @type {AnyDatasetPath} */
const datasetPath = `${scale}-to-price`;
const dataset = datasets.getOrCreate(scale, datasetPath);
// Don't trigger reactivity by design
activeDatasets().add(dataset);
const priceSeries = chartPane.createSplitSeries({
blueprint: {
datasetPath,
title: "BTC Price",
type: "Candlestick",
},
dataset,
id: option.id,
index: -1,
});
chartPane
.timeScale()
.subscribeVisibleLogicalRangeChange((logicalRange) => {
if (!logicalRange) return;
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return;
// Must be the chart with the visible timeScale
debouncedUpdateVisiblePriceSeriesType({
visibleLogicalRange: logicalRange,
});
const index = utils.chunkIdToIndex(scale, latest.year);
priceSeries.forEach((splitSeries) => {
const series = splitSeries.chunks.at(index);
if (series) {
signals.createEffect(series, (series) => {
series?.update(latest);
});
}
});
/** @param {PriceSeriesType} type */
function _createPriceSeries(type) {
return createPriceSeries({
chartPane,
option,
type,
});
}
const priceCandlestickSeries = _createPriceSeries("Candlestick");
const priceLineSeries = _createPriceSeries("Line");
function createLinkPriceSeriesEffect() {
signals.createEffect(priceLineSeries.active, (active) => {
priceCandlestickSeries.active.set(active);
});
signals.createEffect(priceCandlestickSeries.active, (active) => {
priceLineSeries.active.set(active);
});
}
createLinkPriceSeriesEffect();
});
}
[...seriesBlueprints].reverse().forEach((blueprint, index) => {
+25 -2
View File
@@ -3,7 +3,7 @@
/**
* @import { Option, ResourceDataset, TimeScale, TimeRange, Unit, Weighted, DatasetPath, OHLC, FetchedJSON, DatasetValue, FetchedResult, AnyDatasetPath, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, AnyPath, SimulationOption, Frequency, LastValues } from "./types/self"
* @import {createChart as CreateClassicChart, createChartEx as CreateCustomChart, LineStyleOptions } from "../packages/lightweight-charts/v4.2.0/types";
* @import { Marker, CreatePaneParameters, HoveredLegend, ChartPane, SplitSeries, SingleSeries, CreateSplitSeriesParameters, LineSeriesBlueprint, CandlestickSeriesBlueprint, BaselineSeriesBlueprint, CreateBaseSeriesParameters, BaseSeries, RemoveSeriesBlueprintFluff, SplitSeriesBlueprint, AnySeries } from "../packages/lightweight-charts/types";
* @import { Marker, CreatePaneParameters, HoveredLegend, ChartPane, SplitSeries, SingleSeries, CreateSplitSeriesParameters, LineSeriesBlueprint, CandlestickSeriesBlueprint, BaselineSeriesBlueprint, CreateBaseSeriesParameters, BaseSeries, RemoveSeriesBlueprintFluff, SplitSeriesBlueprint, AnySeries, PriceSeriesType } from "../packages/lightweight-charts/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"
* @import { DatePath, HeightPath, LastPath } from "./types/paths";
@@ -598,6 +598,30 @@ const utils = {
return { select, signal };
},
/**
* @param {Object} param0
* @param {Signal<any>} 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 = this.createButtonElement({
onClick: signal.reset,
text: "Reset",
title: "Reset field",
});
button.type = "reset";
div.append(button);
return div;
},
/**
* @param {'left' | 'bottom' | 'top' | 'right'} position
*/
@@ -2154,7 +2178,6 @@ packages.signals().then((signals) =>
signals.runWithOwner(owner, () =>
initChartsElement({
colors,
dark,
datasets,
elements,
lightweightCharts,
+27 -62
View File
@@ -158,15 +158,14 @@ export function init({
}),
description:
"The amount of dollars you have ready on the exchange on day one.",
input: createResetableInput({
...utils.dom.createInputDollar({
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
id: "simulation-dollars-initial",
title: "Initial Dollar Amount",
signal: settings.dollars.initial.amount,
signals,
}),
utils,
}),
),
}),
);
@@ -179,14 +178,13 @@ export function init({
}),
description:
"The frequency at which you'll top up your account at the exchange.",
input: createResetableInput({
...utils.dom.createSelect({
input: utils.dom.createResetableInput(
utils.dom.createSelect({
id: "top-up-frequency",
list: frequencies.list,
signal: settings.dollars.topUp.frenquency,
}),
utils,
}),
),
}),
);
@@ -199,15 +197,14 @@ export function init({
}),
description:
"The recurrent amount of dollars you'll be transfering to said exchange.",
input: createResetableInput({
...utils.dom.createInputDollar({
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
id: "simulation-dollars-top-up-amount",
title: "Top Up Dollar Amount",
signal: settings.dollars.topUp.amount,
signals,
}),
utils,
}),
),
}),
);
@@ -220,15 +217,14 @@ export function init({
}),
description:
"The amount, if available, of dollars that will be used to buy Bitcoin on day one.",
input: createResetableInput({
...utils.dom.createInputDollar({
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
id: "simulation-bitcoin-initial-investment",
title: "Initial Swap Amount",
signal: settings.bitcoin.investment.initial,
signals,
}),
utils,
}),
),
}),
);
@@ -240,14 +236,13 @@ export function init({
text: "Investment Frequency",
}),
description: "The frequency at which you'll be buying Bitcoin.",
input: createResetableInput({
...utils.dom.createSelect({
input: utils.dom.createResetableInput(
utils.dom.createSelect({
id: "investment-frequency",
list: frequencies.list,
signal: settings.bitcoin.investment.frequency,
}),
utils,
}),
),
}),
);
@@ -260,15 +255,14 @@ export function init({
}),
description:
"The recurrent amount, if available, of dollars that will be used to buy Bitcoin.",
input: createResetableInput({
...utils.dom.createInputDollar({
input: utils.dom.createResetableInput(
utils.dom.createInputDollar({
id: "simulation-bitcoin-recurrent-investment",
title: "Bitcoin Recurrent Investment",
signal: settings.bitcoin.investment.recurrent,
signals,
}),
utils,
}),
),
}),
);
@@ -280,15 +274,14 @@ export function init({
text: "Start",
}),
description: "The first day of the simulation.",
input: createResetableInput({
...utils.dom.createInputDate({
input: utils.dom.createResetableInput(
utils.dom.createInputDate({
id: "simulation-inverval-start",
title: "First Simulation Date",
signal: settings.interval.start,
signals,
}),
utils,
}),
),
}),
);
@@ -300,15 +293,14 @@ export function init({
text: "End",
}),
description: "The last day of the simulation.",
input: createResetableInput({
...utils.dom.createInputDate({
input: utils.dom.createResetableInput(
utils.dom.createInputDate({
id: "simulation-inverval-end",
title: "Last Simulation Day",
signal: settings.interval.end,
signals,
}),
utils,
}),
),
}),
);
@@ -320,8 +312,8 @@ export function init({
text: "Exchange",
}),
description: "The amount of trading fees (in %) at the exchange.",
input: createResetableInput({
...utils.dom.createInputNumberElement({
input: utils.dom.createResetableInput(
utils.dom.createInputNumberElement({
id: "simulation-fees",
title: "Exchange Fees",
signal: settings.fees.percentage,
@@ -331,8 +323,7 @@ export function init({
signals,
placeholder: "Fees",
}),
utils,
}),
),
}),
);
@@ -851,32 +842,6 @@ function createFieldElement({ title, description, input }) {
return div;
}
/**
* @param {Object} param0
* @param {Signal<any>} param0.signal
* @param {HTMLInputElement} [param0.input]
* @param {HTMLSelectElement} [param0.select]
* @param {Utilities} param0.utils
*/
function createResetableInput({ input, select, signal, utils }) {
const div = window.document.createElement("div");
const element = input || select;
if (!element) throw "createResetableField element missing";
div.append(element);
const button = utils.dom.createButtonElement({
onClick: signal.reset,
text: "Reset",
title: "Reset field",
});
button.type = "reset";
div.append(button);
return div;
}
/** @param {number} day */
function getOrdinalDay(day) {
const rest = (day % 30) % 20;