diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03d7f26..50ec683 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: name: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1810d6d..95c44ba 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index acdbb99..a840da3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 50bc26c..7a2cbaf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: should_publish: ${{ steps.check-pypi.outputs.should_publish }} repository: ${{ steps.set-repo.outputs.repository }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 @@ -102,7 +102,7 @@ jobs: if: needs.version-check.outputs.should_publish == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce31fcb..52987f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.gitignore b/.gitignore index b879bc2..bddb0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ pyrightconfig.json working/ .zed/ uv.lock +/.cursor/agents +/.cursor/reference +/.cursor/skills /.github/agents /.github/reference /.github/skills diff --git a/CHANGELOG.md b/CHANGELOG.md index 2454822..bc69737 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [Unreleased] + +--- + +## [1.4.0] — 2026-07-19 + +### Added + +- **Responsive chart chrome** — legend, top bar, subchart tabs, and HTML tables scale typography (and table padding) with the embedded window size via shared CSS `clamp()` tokens (`--chrome-font-*`). Designed for roughly 480×320–2560×1440 windows at 1x/2x HiDPI. No new Python sizing knobs. +- **`StreamChart.show_async(...)`** — asyncio-friendly server start that mirrors `Chart.show_async` (non-blocking `show` under the hood, then runs until cancelled). +- **`Window.run_script(..., transient=False)`** — additive keyword used by `StreamChart` to classify per-bar data scripts; ignored by the pywebview `Chart` path. + +### Changed + +- **`legend(..., font_size=...)` removed** (**breaking**) — both `AbstractChart.legend` and `SeriesCommon.legend` no longer accept `font_size`. Legend type size is owned by CSS; pass content and optional `color` / `font_family` only. `layout(font_size=...)` and `watermark(font_size=...)` are unchanged (canvas/LWC, not chrome). +- **Top bar and subchart tab overflow** — when widgets/tabs no longer fit at the responsive type-scale floor, the top bar and subchart tab bar scroll horizontally instead of clipping or compressing labels. +- **`stream-shim.js` reconnect** — after an established session closes, the page `location.reload()`s for a fresh JS context (avoids duplicate `Lib.Handler` construction). Close code `4002` uses a short backoff retry instead of reload; reload attempts are bounded via `sessionStorage`. + +### Fixed + +- **StreamChart reconnect flood** — while disconnected, per-bar `series.update` / `setData` / marker scripts are no longer buffered. On (re)connect the server replays a bounded structural script log, then emits a data snapshot from authoritative Python state (`candle_data` / series `data` / markers / tracked whitespace), so reopen-after-hours no longer replays thousands of updates and crash the page. +- **Candlestick snapshot** — main-series reconnect data is taken from `candle_data` (full OHLC), not the empty inherited `data` frame. +- **Snapshot/live cutover** — `_ws` is set only after the snapshot is fully sent under the data guard, so bars streamed during handshake are not lost. +- **`bulk_run` tagging** — transient vs structural tags are preserved through batched flushes; purely transient batches are dropped while disconnected. +- **StreamChart CSP** — Content-Security-Policy now allows WebSocket connections (`connect-src 'self' ws: wss:`) and inline styles (`style-src 'self' 'unsafe-inline'`), so live streaming and chrome styling work under the default CSP header. +- **StreamChart WebSocket scheme** — the browser shim picks `wss://` on HTTPS pages and `ws://` otherwise, instead of always using `ws://`. +- **`StreamWindow` inheritance** — subclasses `Window` so shared helpers (`_id_gen`, tables, style) stay in sync with `AbstractChart`. +- **Volume profile data helper** — internal data path aligned between Python and the TypeScript plugin. + +### Notes + +- **Snapshot boundary** — series data, markers, and whitespace from `append_whitespace` are snapshot-backed on reconnect. Other per-bar mutations (table cells, price lines, legend, PositionTool P&L) still buffer as structural and can grow if updated every bar. + +--- + ## [1.3.0] — 2026-06-19 ### Added diff --git a/README.md b/README.md index 98000fe..681f6ad 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ if __name__ == '__main__': chart.crosshair(mode='normal', vert_color='#FFFFFF', vert_style='dotted', horz_color='#FFFFFF', horz_style='dotted') - chart.legend(visible=True, font_size=14) + chart.legend(visible=True) chart.set(df) @@ -247,7 +247,7 @@ ___ ### 7. StreamChart — browser-based chart over HTTP/WebSocket: -`StreamChart` spins up a local FastAPI/Uvicorn server and streams data to any browser tab. No desktop window is required — ideal for headless servers, notebooks, or remote development. +`StreamChart` spins up a local FastAPI/Uvicorn server and streams data to any browser tab. No desktop window is required — ideal for headless servers, notebooks, or remote development. On (re)connect it rebuilds from a data snapshot (not a backlog of every `update()` while the tab was closed). ```python import pandas as pd diff --git a/docs/source/examples/subchart.md b/docs/source/examples/subchart.md index c657e11..03aeb1e 100644 --- a/docs/source/examples/subchart.md +++ b/docs/source/examples/subchart.md @@ -2,7 +2,8 @@ Tabbed subcharts let you switch between multiple independent charts inside a single desktop window. Call `create_subchart()` on the main chart; a tab bar -appears automatically above the charts on the first call. +appears automatically above the charts on the first call. When there are more +tabs than fit the window width, the tab bar scrolls horizontally. > **Note:** This is separate from the **Panes API** (`pane_index`, `add_pane()`), > which stacks multiple series vertically in one chart view. See diff --git a/docs/source/reference/abstract_chart.md b/docs/source/reference/abstract_chart.md index e09471f..faad7ce 100644 --- a/docs/source/reference/abstract_chart.md +++ b/docs/source/reference/abstract_chart.md @@ -319,9 +319,11 @@ ___ -```{py:method} legend(visible: bool, ohlc: bool, percent: bool, lines: bool, color: COLOR, font_size: int, font_family: str, text: str, color_based_on_candle: bool) +```{py:method} legend(visible: bool, ohlc: bool, percent: bool, lines: bool, color: COLOR, font_family: str, text: str, color_based_on_candle: bool) Configures the legend of the chart. + +Legend type size is CSS-responsive (`clamp()` via shared chrome tokens). There is no Python `font_size` parameter — pass content and color/family only. ``` ___ @@ -446,7 +448,7 @@ ___ Creates a tabbed sub-chart inside the same webview window and returns a {py:class}`SubChart` instance. -On the **first call**, a tab bar is injected above the charts and the main chart is assigned a tab labelled `main_label`. On subsequent calls `main_label` is ignored. +On the **first call**, a tab bar is injected above the charts and the main chart is assigned a tab labelled `main_label`. On subsequent calls `main_label` is ignored. When tab labels exceed the available width, the tab bar scrolls horizontally. See the [Subcharts example](../examples/subchart.md). ``` diff --git a/docs/source/reference/series.md b/docs/source/reference/series.md index 09a2837..dd14d9a 100644 --- a/docs/source/reference/series.md +++ b/docs/source/reference/series.md @@ -191,10 +191,11 @@ Shows a previously hidden series. ``` ___ -```{py:method} legend(visible, lines, color, font_size, font_family, text, pane_index) +```{py:method} legend(visible, lines, color, font_family, text, pane_index) Configures the legend for the pane this series lives on. +Legend type size is CSS-responsive. There is no Python `font_size` parameter. ``` ___ diff --git a/docs/source/reference/stream_chart.md b/docs/source/reference/stream_chart.md index 48ab03f..75e8212 100644 --- a/docs/source/reference/stream_chart.md +++ b/docs/source/reference/stream_chart.md @@ -25,18 +25,35 @@ Open the URL in any browser. The token is a one-time secret — keep it private. > **Tip:** the printed URL is a one-time CSRF-like token; do not expose it publicly. > To open the browser automatically, pass `open_browser=True` to `show()`. +### Reconnect model + +While disconnected, per-bar data scripts are dropped (not buffered). On connect the +server replays structural setup scripts, then a data snapshot from Python state +(series OHLC/values, markers, tracked whitespace). Same-page reconnects reload the +tab for a clean JavaScript context. See the [StreamChart tutorial](../tutorials/stream_chart.md) +for the snapshot boundary. + ___ -```{py:method} show(port: int, host: str, open_browser: bool, block: bool, cors_origins: list[str]) +```{py:method} show(port: int, host: str, open_browser: bool, block: bool, cors_origins: list[str], debug: bool) Starts the FastAPI/Uvicorn server and optionally opens the system browser. +Returns the chart URL once the server is accepting connections. * `port` *(int, default 8080)*: TCP port to listen on. * `host` *(str, default "127.0.0.1")*: Bind address. Use `"0.0.0.0"` for LAN access (a security reminder is printed). * `open_browser` *(bool, default False)*: Open the default browser to the chart URL. * `block` *(bool, default True)*: Block the calling thread until Ctrl+C is received. * `cors_origins` *(list[str], optional)*: Additional allowed CORS origins (e.g. `"http://192.168.1.10:8080"`). `http://127.0.0.1` and `http://localhost` are always permitted. +* `debug` *(bool, default False)*: Enable FastAPI debug mode. + +``` + +```{py:method} show_async(port: int, host: str, open_browser: bool, cors_origins: list[str], debug: bool) + +Asyncio variant of `show`. Starts the server in a worker thread, returns the URL once +ready, then awaits until the task is cancelled or interrupted. ``` diff --git a/docs/source/reference/tables.md b/docs/source/reference/tables.md index 76d2245..ac4602f 100644 --- a/docs/source/reference/tables.md +++ b/docs/source/reference/tables.md @@ -6,6 +6,8 @@ Tables are panes that can be used to gain further functionality from charts. The The `Table` and `Row` objects act as dictionaries, and can be manipulated as such. +Table font size and header/cell padding scale responsively with the window via CSS (no Python `font_size` parameter). Colors, borders, text alignment, column widths, and sticky header positioning remain instance-driven as before. + `width`/`height` : Either given as a percentage (a `float` between 0 and 1) or as an integer representing pixel size. diff --git a/docs/source/reference/topbar.md b/docs/source/reference/topbar.md index 947d680..898d888 100644 --- a/docs/source/reference/topbar.md +++ b/docs/source/reference/topbar.md @@ -22,6 +22,8 @@ Topbar widgets share common parameters: * `name`: The name of the widget which can be used to access it from the `topbar` dictionary. * `align`: The alignment of the widget (either `'left'` or `'right'` which determines which side of the topbar the widget will be placed upon. +Chrome typography (button, switcher, textbox, menu labels) scales responsively with the embedded window size via CSS `clamp()` tokens. There is no Python font-size API for the top bar. When left and right widgets exceed the available width, the top bar scrolls horizontally (widgets do not shrink or wrap). + ___ diff --git a/docs/source/tutorials/stream_chart.md b/docs/source/tutorials/stream_chart.md index 588b3f7..37c4b71 100644 --- a/docs/source/tutorials/stream_chart.md +++ b/docs/source/tutorials/stream_chart.md @@ -94,6 +94,39 @@ if __name__ == '__main__': time.sleep(0.1) ``` +## Reconnect behavior + +While no browser is connected, per-bar data scripts (`setData` / `update` / markers) +are **not** buffered. On (re)connect the server: + +1. Replays a bounded **structural** script log (series creation, styling, drawings, …). +2. Emits a **data snapshot** from Python state (OHLC/volume, line series, markers, + and whitespace from `append_whitespace`). + +Reopening a tab after hours of disconnected streaming therefore loads from current +state instead of replaying every bar update. Same-page reconnects reload the page +so structural replay always targets a fresh JavaScript context. + +**Snapshot boundary:** table cells, price lines, legend text, and PositionTool P&L +updated every bar still append to the structural log and can grow if you mutate +them per bar while disconnected. + +## Asyncio + +Use `show_async` when the chart shares an event loop with other coroutines: + +```python +import asyncio +from lightweight_charts import StreamChart + +async def main(): + chart = StreamChart() + chart.set(df) + await chart.show_async(port=8080, open_browser=True) + +asyncio.run(main()) +``` + ## Using plugins with StreamChart All plugins work identically with `StreamChart`: @@ -117,6 +150,8 @@ chart.show(block=True) | Window | pywebview desktop window | Any browser via HTTP | | Display | Requires display server | Headless compatible | | Access URL | n/a | Printed one-time token URL | -| `show()` signature | `show(block)` | `show(port, host, open_browser, block)` | +| `show()` signature | `show(block)` | `show(port, host, open_browser, block, …)` | +| `show_async()` | Yes | Yes | +| Reconnect | n/a | Structural replay + data snapshot | | Screenshot | `chart.screenshot()` | Not supported | | `JupyterChart` | Yes | No | diff --git a/examples/5_styling/styling.py b/examples/5_styling/styling.py index 5f1bf15..3165068 100644 --- a/examples/5_styling/styling.py +++ b/examples/5_styling/styling.py @@ -32,7 +32,7 @@ horz_style="dotted", ) - chart.legend(visible=True, font_size=14) + chart.legend(visible=True) chart.set(df) diff --git a/lightweight_charts/__init__.py b/lightweight_charts/__init__.py index 08f0944..a8830fc 100644 --- a/lightweight_charts/__init__.py +++ b/lightweight_charts/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.3.0" +__version__ = "1.4.0" from .abstract import ( AbstractChart, diff --git a/lightweight_charts/abstract.py b/lightweight_charts/abstract.py index c6fa06f..90f98df 100644 --- a/lightweight_charts/abstract.py +++ b/lightweight_charts/abstract.py @@ -4,6 +4,7 @@ import numbers import os from base64 import b64decode +from contextlib import nullcontext from datetime import datetime from typing import Literal from collections.abc import Callable @@ -99,16 +100,29 @@ def on_js_load(self): parts.append(stripped if stripped.endswith(";") else f"{stripped};") self.script_func("\n".join(parts)) - def run_script(self, script: str, run_last: bool = False): + def data_guard(self): + """ + Context manager serializing data mutations against snapshot reads. + + Base Window is a no-op (``nullcontext``). ``StreamWindow`` overrides + with a real lock. Must never wrap blocking round-trips such as + ``run_script_and_get``. + """ + return nullcontext() + + def run_script(self, script: str, run_last: bool = False, transient: bool = False): """ For advanced users; evaluates JavaScript within the Webview. + + ``transient`` is accepted for API compatibility with ``StreamWindow`` + and ignored here (pywebview always evaluates / buffers every script). """ if self.script_func is None: raise AttributeError("script_func has not been set") if self.loaded: if self.bulk_run.enabled: - self.bulk_run.add_script(script) + self.bulk_run.add_script(script, transient=transient) else: self.script_func(script) elif run_last: @@ -170,6 +184,8 @@ def __init__(self, chart: "AbstractChart", name: str = "", pane_index: int | Non self.positions = {} self.pane_index = pane_index self._data_changed_handler_id: str | None = None + self._whitespace_end: int | None = None + self._whitespace_step: float | None = None if hasattr(chart, "_series_registry"): chart._series_registry.append(self) @@ -178,7 +194,6 @@ def legend( visible: bool = True, lines: bool = True, color: str = "rgb(191, 195, 203)", - font_size: int = 11, font_family: str = "Monaco", text: str = "", pane_index: int | None = None, @@ -186,6 +201,7 @@ def legend( """ Configures the legend for the pane this series lives on. OHLC and percent are intentionally omitted — use chart.legend() on pane 0 for those. + Legend type size is CSS-responsive (no Python font_size). """ pane_idx = ( pane_index @@ -200,7 +216,6 @@ def legend( percent=False, lines=lines, color=color, - font_size=font_size, font_family=font_family, text=text, pane_index=pane_idx, @@ -293,29 +308,34 @@ def _single_datetime_format(self, arg) -> float: return arg def set(self, df: pd.DataFrame | None = None, format_cols: bool = True): - if df is None or df.empty: - self.run_script(f"{self.id}.series.setData([])") - self.data = pd.DataFrame() - return - if format_cols: - df = self._df_datetime_format(df, exclude_lowercase=self.name) - if self.name: - if self.name not in df: - raise NameError(f'No column named "{self.name}".') - df = df.rename(columns={self.name: "value"}) - self.data = df.copy() - self._last_bar = df.iloc[-1] - self.run_script(f"{self.id}.series.setData({js_data(df)}); ") + with self.win.data_guard(): + if df is None or df.empty: + self.run_script(f"{self.id}.series.setData([])", transient=True) + self.data = pd.DataFrame() + return + if format_cols: + df = self._df_datetime_format(df, exclude_lowercase=self.name) + if self.name: + if self.name not in df: + raise NameError(f'No column named "{self.name}".') + df = df.rename(columns={self.name: "value"}) + self.data = df.copy() + self._last_bar = df.iloc[-1] + self.run_script(f"{self.id}.series.setData({js_data(df)}); ", transient=True) def update(self, series: pd.Series, historical_update: bool = False): - series = self._series_datetime_format(series, exclude_lowercase=self.name) - if self.name in series.index: - series.rename({self.name: "value"}, inplace=True) - if self._last_bar is not None and series["time"] != self._last_bar["time"]: - self.data.loc[self.data.index[-1]] = self._last_bar - self.data = pd.concat([self.data, series.to_frame().T], ignore_index=True) - self._last_bar = series - self.run_script(f"{self.id}.series.update({js_data(series)}, {jbool(historical_update)});") + with self.win.data_guard(): + series = self._series_datetime_format(series, exclude_lowercase=self.name) + if self.name in series.index: + series.rename({self.name: "value"}, inplace=True) + if self._last_bar is not None and series["time"] != self._last_bar["time"]: + self.data.loc[self.data.index[-1]] = self._last_bar + self.data = pd.concat([self.data, series.to_frame().T], ignore_index=True) + self._last_bar = series + self.run_script( + f"{self.id}.series.update({js_data(series)}, {jbool(historical_update)});", + transient=True, + ) def append_whitespace(self, count: int, bar_seconds: float | None = None) -> int: """Append future whitespace bars after the last point. Returns end time (epoch s).""" @@ -326,16 +346,64 @@ def append_whitespace(self, count: int, bar_seconds: float | None = None) -> int else: start = float(self._last_bar["time"]) step = bar_seconds or self._interval + self._whitespace_step = float(step) end_t = int(start) for i in range(count): end_t = int(start + step * (i + 1)) - self.run_script(f"{self.id}.series.update({{time:{end_t}}}, false);") + self.run_script(f"{self.id}.series.update({{time:{end_t}}}, false);", transient=True) + self._whitespace_end = end_t return end_t + def note_whitespace_end(self, end_t: int, bar_seconds: float | None = None) -> None: + """Track whitespace horizon (e.g. after buffer extend) for snapshot restore.""" + self._whitespace_end = int(end_t) + if bar_seconds is not None: + self._whitespace_step = float(bar_seconds) + + def _whitespace_snapshot_scripts(self, last_real_time: float | None) -> list[str]: + """Emit trailing whitespace ``update({time})`` after snapshot ``setData``.""" + if self._whitespace_end is None or last_real_time is None: + return [] + step = float(self._whitespace_step or self._interval or 1) + if step <= 0: + return [] + scripts: list[str] = [] + t = int(last_real_time) + end = int(self._whitespace_end) + while t < end: + t = int(t + step) + scripts.append(f"{self.id}.series.update({{time:{t}}}, false);") + return scripts + def _update_markers(self): - self.run_script( - f"{self.id}.seriesMarkers.setMarkers({json.dumps(list(self.markers.values()))})" - ) + with self.win.data_guard(): + self.run_script( + f"{self.id}.seriesMarkers.setMarkers({json.dumps(list(self.markers.values()))})", + transient=True, + ) + + def _series_data_snapshot_scripts(self) -> list[str]: + """ + Side-effect-free JS for reconnect snapshot from ``self.data`` + markers. + + Does not reformat frames or emit ``clearDrawings`` / ``autoScale``. + Caller should hold ``win.data_guard()`` when racing live updates. + """ + scripts: list[str] = [] + last_real: float | None = None + if self.data is not None and not self.data.empty: + scripts.append(f"{self.id}.series.setData({js_data(self.data)});") + last_real = float(self.data.iloc[-1]["time"]) + if self.markers: + scripts.append( + f"{self.id}.seriesMarkers.setMarkers({json.dumps(list(self.markers.values()))})" + ) + scripts.extend(self._whitespace_snapshot_scripts(last_real)) + return scripts + + def data_snapshot_scripts(self) -> list[str]: + """Public per-series snapshot scripts (see ``_series_data_snapshot_scripts``).""" + return self._series_data_snapshot_scripts() def marker_list(self, markers: list): """ @@ -1168,29 +1236,32 @@ def set(self, df: pd.DataFrame | None = None, keep_drawings=False): :param df: columns: date/time, open, high, low, close, volume (if volume enabled). :param keep_drawings: keeps any drawings made through the toolbox. Otherwise, they will be deleted. """ - if df is None or df.empty: - self.run_script(f"{self.id}.series.setData([])") - self.run_script(f"{self.id}.volumeSeries.setData([])") - self.candle_data = pd.DataFrame() - return - df = self._df_datetime_format(df) - self.candle_data = df.copy() - self._last_bar = df.iloc[-1] - self.run_script(f"{self.id}.series.setData({js_data(df)})") - - if "volume" not in df: - return - volume = df.drop(columns=["open", "high", "low", "close"]).rename( - columns={"volume": "value"} - ) - volume["color"] = self._volume_down_color - volume.loc[df["close"] > df["open"], "color"] = self._volume_up_color - self.run_script(f"{self.id}.volumeSeries.setData({js_data(volume)})") + with self.win.data_guard(): + if df is None or df.empty: + self.run_script(f"{self.id}.series.setData([])", transient=True) + self.run_script(f"{self.id}.volumeSeries.setData([])", transient=True) + self.candle_data = pd.DataFrame() + return + df = self._df_datetime_format(df) + self.candle_data = df.copy() + self._last_bar = df.iloc[-1] + self.run_script(f"{self.id}.series.setData({js_data(df)})", transient=True) + + if "volume" in df: + volume = df.drop(columns=["open", "high", "low", "close"]).rename( + columns={"volume": "value"} + ) + volume["color"] = self._volume_down_color + volume.loc[df["close"] > df["open"], "color"] = self._volume_up_color + self.run_script( + f"{self.id}.volumeSeries.setData({js_data(volume)})", transient=True + ) - for line in self._lines: - if line.name not in df.columns: - continue - line.set(df[["time", line.name]], format_cols=False) + for line in self._lines: + if line.name not in df.columns: + continue + line.set(df[["time", line.name]], format_cols=False) + # Structural side effects (not part of the data snapshot). # set autoScale to true in case the user has dragged the price scale self.run_script( f""" @@ -1212,23 +1283,32 @@ def update(self, series: pd.Series, _from_tick=False, historical_update: bool = :param historical_update: When True, update an existing bar by time (required when future whitespace bars extend past the last real bar). """ - series = self._series_datetime_format(series) if not _from_tick else series - if series["time"] != self._last_bar["time"]: - self.candle_data.loc[self.candle_data.index[-1]] = self._last_bar - self.candle_data = pd.concat([self.candle_data, series.to_frame().T], ignore_index=True) - self._chart.events.new_bar._emit(self) + with self.win.data_guard(): + series = self._series_datetime_format(series) if not _from_tick else series + if series["time"] != self._last_bar["time"]: + self.candle_data.loc[self.candle_data.index[-1]] = self._last_bar + self.candle_data = pd.concat( + [self.candle_data, series.to_frame().T], ignore_index=True + ) + self._chart.events.new_bar._emit(self) - self._last_bar = series - self.run_script(f"{self.id}.series.update({js_data(series)}, {jbool(historical_update)});") - if "volume" not in series: - return - volume = series.drop(["open", "high", "low", "close"]).rename({"volume": "value"}) - volume["color"] = ( - self._volume_up_color if series["close"] > series["open"] else self._volume_down_color - ) - self.run_script( - f"{self.id}.volumeSeries.update({js_data(volume)}, {jbool(historical_update)});" - ) + self._last_bar = series + self.run_script( + f"{self.id}.series.update({js_data(series)}, {jbool(historical_update)});", + transient=True, + ) + if "volume" not in series: + return + volume = series.drop(["open", "high", "low", "close"]).rename({"volume": "value"}) + volume["color"] = ( + self._volume_up_color + if series["close"] > series["open"] + else self._volume_down_color + ) + self.run_script( + f"{self.id}.volumeSeries.update({js_data(volume)}, {jbool(historical_update)});", + transient=True, + ) def append_whitespace(self, count: int, bar_seconds: float | None = None) -> int: """Append future whitespace candle bars. Returns end time (epoch s).""" @@ -1239,12 +1319,45 @@ def append_whitespace(self, count: int, bar_seconds: float | None = None) -> int else: start = float(self._last_bar["time"]) step = bar_seconds or self._interval + self._whitespace_step = float(step) end_t = int(start) for i in range(count): end_t = int(start + step * (i + 1)) - self.run_script(f"{self.id}.series.update({{time:{end_t}}}, false);") + self.run_script(f"{self.id}.series.update({{time:{end_t}}}, false);", transient=True) + self._whitespace_end = end_t return end_t + def _series_data_snapshot_scripts(self) -> list[str]: + """ + Snapshot from ``self.candle_data`` (OHLC), not empty ``self.data`` (H-01). + + Also emits ``volumeSeries`` with the same up/down color rule as :meth:`set`. + Restores trailing whitespace tracked via ``append_whitespace`` / ``note_whitespace_end``. + """ + scripts: list[str] = [] + if self.candle_data is None or self.candle_data.empty: + if self.markers: + scripts.append( + f"{self.id}.seriesMarkers.setMarkers({json.dumps(list(self.markers.values()))})" + ) + return scripts + + df = self.candle_data + scripts.append(f"{self.id}.series.setData({js_data(df)})") + if "volume" in df.columns: + volume = df.drop(columns=["open", "high", "low", "close"]).rename( + columns={"volume": "value"} + ) + volume["color"] = self._volume_down_color + volume.loc[df["close"] > df["open"], "color"] = self._volume_up_color + scripts.append(f"{self.id}.volumeSeries.setData({js_data(volume)})") + if self.markers: + scripts.append( + f"{self.id}.seriesMarkers.setMarkers({json.dumps(list(self.markers.values()))})" + ) + scripts.extend(self._whitespace_snapshot_scripts(float(df.iloc[-1]["time"]))) + return scripts + def update_from_tick(self, series: pd.Series, cumulative_volume: bool = False): """ Updates the data from a tick.\n @@ -1389,6 +1502,25 @@ def __init__( if toolbox: self.toolbox: ToolBox = ToolBox(self) + def data_snapshot_scripts(self) -> list[str]: + """ + Collect reconnect data-snapshot scripts for every registered series. + + Only series/marker (and candlestick volume) data is snapshot-backed. + Other per-bar mutations (table cells, price lines, legend text, + PositionTool P&L) remain structural and still buffer in the stream log. + + Also walks ``_subcharts`` so tabbed SubChart series (set while the + browser was disconnected) are restored on connect — otherwise + create_line structural scripts replay empty and dataLens stays 0. + """ + scripts: list[str] = [] + for series in self._series_registry: + scripts.extend(series._series_data_snapshot_scripts()) + for sub in getattr(self, "_subcharts", None) or []: + scripts.extend(sub.data_snapshot_scripts()) + return scripts + def fit(self): """ Fits the maximum amount of the chart data within the viewport. @@ -1629,7 +1761,6 @@ def legend( percent: bool = True, lines: bool = True, color: str = "rgb(191, 195, 203)", - font_size: int = 11, font_family: str = "Monaco", text: str = "", color_based_on_candle: bool = False, @@ -1638,6 +1769,7 @@ def legend( """ Configures the legend of the chart. :param pane_index: Which pane's legend to configure (default 0). + Legend type size is CSS-responsive (no Python font_size). """ l_id = f"{self.id}.getOrCreateLegend({int(pane_index)})" if not visible: @@ -1658,9 +1790,8 @@ def legend( {l_id}.linesEnabled = {jbool(lines)} {l_id}.colorBasedOnCandle = {jbool(color_based_on_candle)} {l_id}.div.style.color = '{color}' - {l_id}.div.style.fontSize = '{font_size}px' {l_id}.div.style.fontFamily = '{font_family}' - {l_id}.text.innerText = '{text}'; + {l_id}.text.innerText = '{text}' """ ) diff --git a/lightweight_charts/js/bundle.js b/lightweight_charts/js/bundle.js index 6aefb73..c40fae9 100644 --- a/lightweight_charts/js/bundle.js +++ b/lightweight_charts/js/bundle.js @@ -1 +1 @@ -var Lib=function(t,e){"use strict";const i={backgroundColor:"#0c0d0f",hoverBackgroundColor:"#3c434c",clickBackgroundColor:"#50565E",activeBackgroundColor:"rgba(0, 122, 255, 0.7)",mutedBackgroundColor:"rgba(0, 122, 255, 0.3)",borderColor:"#3C434C",color:"#d8d9db",activeColor:"#ececed"};function s(){window.pane={...i},window.containerDiv=document.getElementById("container")||document.createElement("div");const t=window.containerDiv;t.style.display="flex",t.style.flexDirection="column",t.style.width="100%",t.style.height="100%",t.style.minHeight="0",t.style.overflow="hidden",window.setCursor=t=>{t&&(window.cursor=t),document.body.style.cursor=window.cursor},window.cursor="default",window.textBoxFocused=!1}class o{handler;_paneIndex;_anchored=!1;div;seriesContainer;ohlcEnabled=!1;percentEnabled=!1;linesEnabled=!1;colorBasedOnCandle=!1;text;candle;_lines=[];constructor(t,e){this.legendHandler=this.legendHandler.bind(this),this._paneIndex=t,this.handler=e,this.div=document.createElement("div"),this.div.classList.add("legend"),this.div.style.display="none";const i=document.createElement("div");i.style.display="flex",i.style.flexDirection="row",this.seriesContainer=document.createElement("div"),this.seriesContainer.classList.add("series-container"),this.text=document.createElement("span"),this.text.style.lineHeight="1.8",this.candle=document.createElement("div"),i.appendChild(this.seriesContainer),this.div.appendChild(this.text),this.div.appendChild(this.candle),this.div.appendChild(i)}tryAnchorToPane(){if(this._anchored)return;const t=this.handler.chart.panes();if(this._paneIndex>=t.length)return;const e=t[this._paneIndex].getHTMLElement();if(!e)return;const i=e.children[1];if(!i)return;const s=i.children[0];s&&(s.appendChild(this.div),this._anchored=!0)}toJSON(){const{_lines:t,handler:e,...i}=this;return i}makeSeriesRow(t,e){this.tryAnchorToPane();const i="#FFF";let s=`\n \n \`\n `,o=`\n \n `,n=document.createElement("div");n.style.display="flex",n.style.alignItems="center";let a=document.createElement("div"),r=document.createElement("div");r.classList.add("legend-toggle-switch");let l=document.createElementNS("http://www.w3.org/2000/svg","svg");l.setAttribute("width","22"),l.setAttribute("height","16");let h=document.createElementNS("http://www.w3.org/2000/svg","g");h.innerHTML=s;let c=!0;r.addEventListener("click",(()=>{c?(c=!1,h.innerHTML=o,e.applyOptions({visible:!1})):(c=!0,e.applyOptions({visible:!0}),h.innerHTML=s)})),l.appendChild(h),r.appendChild(l);const d=document.createElement("div");d.classList.add("legend-settings-btn"),d.innerHTML='',d.title="Series settings",d.addEventListener("click",(()=>this.openSettingsModal(t,e)));const p=document.createElement("div");p.classList.add("legend-cross-btn"),p.innerHTML="✕",p.title="Remove series",p.addEventListener("click",(()=>{this.handler.chart.removeSeries(e),n.remove();const t=this._lines.findIndex((t=>t.series===e));-1!==t&&this._lines.splice(t,1)})),n.appendChild(a),n.appendChild(r),n.appendChild(d),n.appendChild(p),this.seriesContainer.appendChild(n),this._lines.push({name:t,div:a,row:n,toggle:r,series:e,solid:this.getSeriesDisplayColor(e)})}getSeriesDisplayColor(t){const e=t.seriesType();let i;return i="Area"===e?t.options().lineColor:"Baseline"===e?t.options().topLineColor:"Bar"===e||"Candlestick"===e?t.options().upColor:"Line"===e||"Histogram"===e?t.options().color:t.options().baseLineColor,i=i||"#2196f3",i.startsWith("rgba")?i.replace(/[^,]+(?=\))/,"1"):i}colorToHex(t){if(!t)return"#000000";if(t.startsWith("#"))return t.length>=7?t.slice(0,7):t;const e=t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);return e?"#"+parseInt(e[1]).toString(16).padStart(2,"0")+parseInt(e[2]).toString(16).padStart(2,"0")+parseInt(e[3]).toString(16).padStart(2,"0"):"#000000"}getFieldGroups(t){const i=[{value:e.LineStyle.Solid,label:"Solid"},{value:e.LineStyle.Dotted,label:"Dotted"},{value:e.LineStyle.Dashed,label:"Dashed"},{value:e.LineStyle.LargeDashed,label:"Large Dashed"},{value:e.LineStyle.SparseDotted,label:"Sparse Dotted"}],s=[{value:e.LineType.Simple,label:"Simple"},{value:e.LineType.WithSteps,label:"With Steps"},{value:e.LineType.Curved,label:"Curved"}],o=[{value:e.LastPriceAnimationMode.Disabled,label:"Disabled"},{value:e.LastPriceAnimationMode.Continuous,label:"Continuous"},{value:e.LastPriceAnimationMode.OnDataUpdate,label:"On Data Update"}],n=[{key:"title",label:"Label",type:"text"},{key:"lastValueVisible",label:"Last Value Label",type:"boolean"},{key:"priceLineVisible",label:"Price Line",type:"boolean"},{key:"priceLineColor",label:"Price Line Color",type:"color"},{key:"priceLineWidth",label:"Price Line Width",type:"number",min:1,max:4,step:1},{key:"priceLineStyle",label:"Price Line Style",type:"select",options:i}],a=[{key:"crosshairMarkerVisible",label:"Visible",type:"boolean"},{key:"crosshairMarkerRadius",label:"Radius (px)",type:"number",min:1,max:20,step:1},{key:"crosshairMarkerBorderColor",label:"Border Color",type:"color"},{key:"crosshairMarkerBackgroundColor",label:"BG Color",type:"color"},{key:"crosshairMarkerBorderWidth",label:"Border Width",type:"number",min:1,max:6,step:1}];switch(t.seriesType()){case"Line":return[{title:"Line",fields:[{key:"color",label:"Color",type:"color"},{key:"lineWidth",label:"Width",type:"number",min:1,max:4,step:1},{key:"lineStyle",label:"Style",type:"select",options:i},{key:"lineType",label:"Type",type:"select",options:s},{key:"lineVisible",label:"Line Visible",type:"boolean"},{key:"pointMarkersVisible",label:"Point Markers",type:"boolean"},{key:"pointMarkersRadius",label:"Marker Radius (px)",type:"number",min:1,max:20,step:1},{key:"lastPriceAnimation",label:"Price Animation",type:"select",options:o}]},{title:"Crosshair Marker",fields:a},{title:"General",fields:n}];case"Area":return[{title:"Area",fields:[{key:"lineColor",label:"Line Color",type:"color"},{key:"topColor",label:"Top Fill",type:"color"},{key:"bottomColor",label:"Bottom Fill",type:"color"},{key:"lineWidth",label:"Line Width",type:"number",min:1,max:4,step:1},{key:"lineStyle",label:"Line Style",type:"select",options:i},{key:"lineType",label:"Line Type",type:"select",options:s},{key:"lineVisible",label:"Line Visible",type:"boolean"},{key:"invertFilledArea",label:"Invert Fill",type:"boolean"},{key:"relativeGradient",label:"Relative Gradient",type:"boolean"},{key:"pointMarkersVisible",label:"Point Markers",type:"boolean"},{key:"pointMarkersRadius",label:"Marker Radius (px)",type:"number",min:1,max:20,step:1},{key:"lastPriceAnimation",label:"Price Animation",type:"select",options:o}]},{title:"Crosshair Marker",fields:a},{title:"General",fields:n}];case"Bar":return[{title:"Bar",fields:[{key:"upColor",label:"Up Color",type:"color"},{key:"downColor",label:"Down Color",type:"color"},{key:"openVisible",label:"Show Open Tick",type:"boolean"},{key:"thinBars",label:"Thin Bars",type:"boolean"}]},{title:"General",fields:n}];case"Candlestick":return[{title:"Body",fields:[{key:"upColor",label:"Up Color",type:"color"},{key:"downColor",label:"Down Color",type:"color"}]},{title:"Borders",fields:[{key:"borderVisible",label:"Visible",type:"boolean"},{key:"borderColor",label:"Color",type:"color"},{key:"borderUpColor",label:"Up Color",type:"color"},{key:"borderDownColor",label:"Down Color",type:"color"}]},{title:"Wicks",fields:[{key:"wickVisible",label:"Visible",type:"boolean"},{key:"wickColor",label:"Color",type:"color"},{key:"wickUpColor",label:"Up Color",type:"color"},{key:"wickDownColor",label:"Down Color",type:"color"}]},{title:"General",fields:n}];case"Histogram":return[{title:"Histogram",fields:[{key:"color",label:"Color",type:"color"},{key:"base",label:"Base Level",type:"number",step:.01}]},{title:"General",fields:n}];case"Baseline":return[{title:"Top Area",fields:[{key:"topLineColor",label:"Line Color",type:"color"},{key:"topFillColor1",label:"Fill Color 1",type:"color"},{key:"topFillColor2",label:"Fill Color 2",type:"color"}]},{title:"Bottom Area",fields:[{key:"bottomLineColor",label:"Line Color",type:"color"},{key:"bottomFillColor1",label:"Fill Color 1",type:"color"},{key:"bottomFillColor2",label:"Fill Color 2",type:"color"}]},{title:"Line",fields:[{key:"lineWidth",label:"Width",type:"number",min:1,max:4,step:1},{key:"lineStyle",label:"Style",type:"select",options:i},{key:"lineType",label:"Type",type:"select",options:s},{key:"lineVisible",label:"Visible",type:"boolean"},{key:"relativeGradient",label:"Relative Gradient",type:"boolean"},{key:"pointMarkersVisible",label:"Point Markers",type:"boolean"},{key:"pointMarkersRadius",label:"Marker Radius (px)",type:"number",min:1,max:20,step:1},{key:"lastPriceAnimation",label:"Price Animation",type:"select",options:o}]},{title:"Crosshair Marker",fields:a},{title:"General",fields:n}];default:return[{title:"General",fields:n}]}}buildFieldRow(t,e,i){const s=document.createElement("div");s.classList.add("legend-modal-field");const o=document.createElement("label");let n;if(o.classList.add("legend-modal-field-label"),o.textContent=t.label,"color"===t.type){const t=document.createElement("input");t.type="color",t.value=this.colorToHex("string"==typeof e?e:""),t.classList.add("legend-modal-color"),t.addEventListener("input",(()=>i(t.value))),n=t}else if("number"===t.type){const s=document.createElement("input");s.type="number",void 0!==t.min&&(s.min=String(t.min)),void 0!==t.max&&(s.max=String(t.max)),void 0!==t.step&&(s.step=String(t.step)),s.value=void 0!==e?String(e):"",s.classList.add("legend-modal-input"),s.addEventListener("input",(()=>{const t=parseFloat(s.value);isNaN(t)||i(t)})),n=s}else if("boolean"===t.type){const t=document.createElement("input");t.type="checkbox",t.checked=!!e,t.classList.add("legend-modal-checkbox"),t.addEventListener("change",(()=>i(t.checked))),n=t}else if("select"===t.type){const s=document.createElement("select");s.classList.add("legend-modal-input","legend-modal-select");for(const i of t.options??[]){const t=document.createElement("option");t.value=String(i.value),t.textContent=i.label,String(e)===String(i.value)&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",(()=>i(Number(s.value)))),n=s}else{const t=document.createElement("input");t.type="text",t.value=String(e??""),t.classList.add("legend-modal-input"),t.addEventListener("input",(()=>i(t.value))),n=t}return s.appendChild(o),s.appendChild(n),s}openSettingsModal(t,e){const i=document.createElement("div");i.classList.add("legend-modal-backdrop"),i.addEventListener("click",(t=>{t.target===i&&i.remove()}));const s=document.createElement("div");s.classList.add("legend-modal"),s.addEventListener("click",(t=>t.stopPropagation()));const o=document.createElement("div");o.classList.add("legend-modal-header");const n=document.createElement("span");n.classList.add("legend-modal-title"),n.textContent=`${t} · ${e.seriesType()}`;const a=document.createElement("button");a.classList.add("legend-modal-close"),a.innerHTML="✕",a.title="Close",a.addEventListener("click",(()=>i.remove())),o.appendChild(n),o.appendChild(a);const r=document.createElement("div");r.classList.add("legend-modal-body");const l=this.getFieldGroups(e),h=e.options();for(const t of l){const i=document.createElement("div");i.classList.add("legend-modal-section");const s=document.createElement("div");s.classList.add("legend-modal-section-title"),s.textContent=t.title,i.appendChild(s);for(const s of t.fields){const t=this.buildFieldRow(s,h[s.key],(t=>{e.applyOptions({[s.key]:t});const i=this._lines.find((t=>t.series===e));i&&(i.solid=this.getSeriesDisplayColor(e))}));i.appendChild(t)}r.appendChild(i)}s.appendChild(o),s.appendChild(r),i.appendChild(s),document.body.appendChild(i)}legendItemFormat(t,e){return t.toFixed(e).toString().padStart(8," ")}shorthandFormat(t){const e=Math.abs(t);return e>=1e6?(t/1e6).toFixed(1)+"M":e>=1e3?(t/1e3).toFixed(1)+"K":t.toString().padStart(8," ")}legendHandler(t,e=!1){if(this.tryAnchorToPane(),!this.ohlcEnabled&&!this.linesEnabled&&!this.percentEnabled)return;const i=0===this._paneIndex;if(!t.time){if(i){const t=this.handler.series.options();this.candle.style.color="transparent",this.candle.innerHTML=this.candle.innerHTML.replace(t.upColor,"").replace(t.downColor,"")}if(!this.linesEnabled)return}let s,o=null;if(i&&t.time){const i=this.handler.series.options();if(e){const e=this.handler.chart.timeScale();let i=e.timeToCoordinate(t.time);i&&(o=e.coordinateToLogical(i.valueOf())),o&&(s=this.handler.series.dataByIndex(o.valueOf()))}else s=t.seriesData.get(this.handler.series);this.candle.style.color="";let n='';if(s){if(this.ohlcEnabled&&(n+=`O ${this.legendItemFormat(s.open,this.handler.precision)} `,n+=`| H ${this.legendItemFormat(s.high,this.handler.precision)} `,n+=`| L ${this.legendItemFormat(s.low,this.handler.precision)} `,n+=`| C ${this.legendItemFormat(s.close,this.handler.precision)} `),this.percentEnabled){let t=(s.close-s.open)/s.open*100,e=t>0?i.upColor:i.downColor,o=`${t>=0?"+":""}${t.toFixed(2)} %`;this.colorBasedOnCandle?n+=`| ${o}`:n+="| "+o}if(this.handler.volumeSeries){let e;e=o?this.handler.volumeSeries.dataByIndex(o):t.seriesData.get(this.handler.volumeSeries),e&&(n+=this.ohlcEnabled?`
V ${this.shorthandFormat(e.value)}`:"")}}this.candle.innerHTML=n+"
"}this._lines.forEach((i=>{if(!this.linesEnabled)return void(i.row.style.display="none");let s,n;if(i.row.style.display="flex",s=e&&o?i.series.dataByIndex(o):t.seriesData.get(i.series),s?.value){if("Histogram"==i.series.seriesType())n=this.shorthandFormat(s.value);else{const t=i.series.options().priceFormat;n=this.legendItemFormat(s.value,t.precision)}i.div.innerHTML=`▨ ${i.name} : ${n}`}}))}}function n(t){if(void 0===t)throw new Error("Value is undefined");return t}class a{_chart=void 0;_series=void 0;requestUpdate(){this._requestUpdate&&this._requestUpdate()}_requestUpdate;attached({chart:t,series:e,requestUpdate:i}){this._chart=t,this._series=e,this._series.subscribeDataChanged(this._fireDataUpdated),this._requestUpdate=i,this.requestUpdate()}detached(){this._series?.unsubscribeDataChanged(this._fireDataUpdated),this._chart=void 0,this._series=void 0,this._requestUpdate=void 0}get chart(){return n(this._chart)}get series(){return n(this._series)}_fireDataUpdated=t=>{this.dataUpdated&&this.dataUpdated(t)}}const r={lineColor:"#1E80F0",lineStyle:e.LineStyle.Solid,width:4,text:"",textColor:"",axisLabelVisible:!0,textPosition:"center",textHAlign:"center",textVAlign:"center"};var l;!function(t){t[t.NONE=0]="NONE",t[t.HOVERING=1]="HOVERING",t[t.DRAGGING=2]="DRAGGING",t[t.DRAGGINGP1=3]="DRAGGINGP1",t[t.DRAGGINGP2=4]="DRAGGINGP2",t[t.DRAGGINGP3=5]="DRAGGINGP3",t[t.DRAGGINGP4=6]="DRAGGINGP4"}(l||(l={}));class h extends a{_paneViews=[];_options;_points=[];_state=l.NONE;_startDragPoint=null;_latestHoverPoint=null;static _mouseIsDown=!1;static hoveredObject=null;static lastHoveredObject=null;_listeners=[];constructor(t){super(),this._options={...r,...t}}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}applyOptions(t){this._options={...this._options,...t},this.requestUpdate()}updatePoints(...t){for(let e=0;ei.name===t&&i.listener===e));this._listeners.splice(this._listeners.indexOf(i),1)}_handleHoverInteraction(t){if(this._latestHoverPoint=t.point,h._mouseIsDown)this._handleDragInteraction(t);else if(this._mouseIsOverDrawing(t)){if(this._state!=l.NONE)return;this._moveToState(l.HOVERING),h.hoveredObject=h.lastHoveredObject=this}else{if(this._state==l.NONE)return;this._moveToState(l.NONE),h.hoveredObject===this&&(h.hoveredObject=null)}}static _eventToPoint(t,e){if(!e||!t.point||!t.logical)return null;const i=e.coordinateToPrice(t.point.y);return null==i?null:{time:t.time||null,logical:t.logical,price:i.valueOf()}}static _getDiff(t,e){return{logical:t.logical-e.logical,price:t.price-e.price}}_addDiffToPoint(t,e,i){t&&(t.logical=t.logical+e,t.price=t.price+i,t.time=this.series.dataByIndex(t.logical)?.time||null)}_handleMouseDownInteraction=()=>{h._mouseIsDown=!0,this._onMouseDown()};_handleMouseUpInteraction=()=>{h._mouseIsDown=!1,this._moveToState(l.HOVERING)};_handleDragInteraction(t){if(this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP1&&this._state!=l.DRAGGINGP2&&this._state!=l.DRAGGINGP3&&this._state!=l.DRAGGINGP4)return;const e=h._eventToPoint(t,this.series);if(!e)return;this._startDragPoint=this._startDragPoint||e;const i=h._getDiff(e,this._startDragPoint);this._onDrag(i),this.requestUpdate(),this._startDragPoint=e}}class c{_options;constructor(t){this._options=t}}class d extends c{_p1;_p2;_hovered;constructor(t,e,i,s){super(i),this._p1=t,this._p2=e,this._hovered=s}_getScaledCoordinates(t){return null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y?null:{x1:Math.round(this._p1.x*t.horizontalPixelRatio),y1:Math.round(this._p1.y*t.verticalPixelRatio),x2:Math.round(this._p2.x*t.horizontalPixelRatio),y2:Math.round(this._p2.y*t.verticalPixelRatio)}}_drawEndCircle(t,e,i){t.context.fillStyle="#000",t.context.beginPath(),t.context.arc(e,i,9,0,2*Math.PI),t.context.stroke(),t.context.fill()}}function p(t,i){const s={[e.LineStyle.Solid]:[],[e.LineStyle.Dotted]:[t.lineWidth,t.lineWidth],[e.LineStyle.Dashed]:[2*t.lineWidth,2*t.lineWidth],[e.LineStyle.LargeDashed]:[6*t.lineWidth,6*t.lineWidth],[e.LineStyle.SparseDotted]:[t.lineWidth,4*t.lineWidth]}[i];t.setLineDash(s)}function u(t,e){return t||e}function _(t,e,i,s,o,n,a,r){s&&(t.save(),t.font=`${n}px -apple-system, BlinkMacSystemFont, sans-serif`,t.fillStyle=o,t.textAlign=a,t.textBaseline=r,t.fillText(s,e,i),t.restore())}class m extends c{_point={x:null,y:null};constructor(t,e){super(e),this._point=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null==this._point.y)return;const e=t.context,i=Math.round(this._point.y*t.verticalPixelRatio),s=this._point.x?this._point.x*t.horizontalPixelRatio:0;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.beginPath(),e.moveTo(s,i),e.lineTo(t.bitmapSize.width,i),e.stroke();const o=6*t.verticalPixelRatio,n=Math.round(12*t.verticalPixelRatio),a=u(this._options.textColor,this._options.lineColor),r=(s+t.bitmapSize.width)/2,l="below"!==this._options.textPosition;_(e,r,l?i-o:i+o,this._options.text,a,n,"center",l?"bottom":"top")}))}}class b{_source;constructor(t){this._source=t}}class g extends b{_p1={x:null,y:null};_p2={x:null,y:null};_source;constructor(t){super(t),this._source=t}update(){if(!this._source.p1||!this._source.p2)return;const t=this._source.series,e=t.priceToCoordinate(this._source.p1.price),i=t.priceToCoordinate(this._source.p2.price),s=this._getX(this._source.p1),o=this._getX(this._source.p2);this._p1={x:s,y:e},this._p2={x:o,y:i}}_getX(t){return this._source.chart.timeScale().logicalToCoordinate(t.logical)}}class y extends b{_source;_point={x:null,y:null};constructor(t){super(t),this._source=t}update(){const t=this._source._point,e=this._source.chart.timeScale(),i=this._source.series;"RayLine"==this._source._type&&(this._point.x=t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical)),this._point.y=i.priceToCoordinate(t.price)}renderer(){return new m(this._point,this._source._options)}}class v{_source;_y=null;_price=null;constructor(t){this._source=t}update(){if(!this._source.series||!this._source._point)return;this._y=this._source.series.priceToCoordinate(this._source._point.price);const t=this._source.series.options().priceFormat.precision;this._price=this._source._point.price.toFixed(t).toString()}visible(){return this._source._options.axisLabelVisible??!0}tickVisible(){return!0}coordinate(){return this._y??0}text(){return this._price||""}textColor(){return"white"}backColor(){return this._source._options.lineColor}}class w extends h{_type="HorizontalLine";_paneViews;_point;_callbackName;_priceAxisViews;_startDragPoint=null;constructor(t,e,i=null){super(e),this._point=t,this._point.time=null,this._paneViews=[new y(this)],this._priceAxisViews=[new v(this)],this._callbackName=i}get points(){return[this._point]}updatePoints(...t){for(const e of t)e&&(this._point.price=e.price);this.requestUpdate()}updateAllViews(){this._paneViews.forEach((t=>t.update())),this._priceAxisViews.forEach((t=>t.update()))}priceAxisViews(){return this._priceAxisViews}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._unsubscribe("mouseup",this._childHandleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._childHandleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._addDiffToPoint(this._point,0,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price);return!!i&&Math.abs(i-t.point.y){this._handleMouseUpInteraction(),this._callbackName&&window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`)}}class x{_chart;_series;_finishDrawingCallback=null;_drawings=[];_activeDrawing=null;_isDrawing=!1;_drawingType=null;constructor(t,e,i=null){this._chart=t,this._series=e,this._finishDrawingCallback=i,this._chart.subscribeClick(this._clickHandler),this._chart.subscribeCrosshairMove(this._moveHandler)}_clickHandler=t=>this._onClick(t);_moveHandler=t=>this._onMouseMove(t);beginDrawing(t){this._drawingType=t,this._isDrawing=!0}stopDrawing(){this._isDrawing=!1,this._activeDrawing=null}get drawings(){return this._drawings}addNewDrawing(t){this._series.attachPrimitive(t),this._drawings.push(t)}delete(t){if(null==t)return;const e=this._drawings.indexOf(t);-1!=e&&(this._drawings.splice(e,1),t.detach())}clearDrawings(){for(const t of this._drawings)t.detach();this._drawings=[]}repositionOnTime(){for(const t of this.drawings){const e=[];for(const i of t.points){if(!i){e.push(i);continue}const t=i.time?this._chart.timeScale().coordinateToLogical(this._chart.timeScale().timeToCoordinate(i.time)||0):i.logical;e.push({time:i.time,logical:t,price:i.price})}t.updatePoints(...e)}}_onClick(t){if(!this._isDrawing)return;const e=h._eventToPoint(t,this._series);if(e)if(null==this._activeDrawing){if(null==this._drawingType)return;this._activeDrawing=new this._drawingType(e,e),this._series.attachPrimitive(this._activeDrawing),this._drawingType==w&&this._onClick(t)}else{if(this._drawings.push(this._activeDrawing),this.stopDrawing(),!this._finishDrawingCallback)return;this._finishDrawingCallback()}}_onMouseMove(t){if(!t)return;for(const e of this._drawings)e._handleHoverInteraction(t);if(!this._isDrawing||!this._activeDrawing)return;const e=h._eventToPoint(t,this._series);e&&this._activeDrawing.updatePoints(null,e)}}class f extends d{constructor(t,e,i,s){super(t,e,i,s)}draw(t){t.useBitmapCoordinateSpace((t=>{if(null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y)return;const e=t.context,i=this._getScaledCoordinates(t);if(!i)return;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.beginPath(),e.moveTo(i.x1,i.y1),e.lineTo(i.x2,i.y2),e.stroke();const s=6*t.horizontalPixelRatio,o=Math.round(12*t.verticalPixelRatio),n=u(this._options.textColor,this._options.lineColor),a=i.x2-i.x1,r=i.y2-i.y1,l=Math.sqrt(a*a+r*r)||1,h=-r/l,c=a/l;let d,m;switch(this._options.textPosition){case"start":d=i.x1+h*s,m=i.y1+c*s;break;case"end":d=i.x2+h*s,m=i.y2+c*s;break;default:d=(i.x1+i.x2)/2+h*s,m=(i.y1+i.y2)/2+c*s}_(e,d,m,this._options.text,n,o,"center","middle"),this._hovered&&(this._drawEndCircle(t,i.x1,i.y1),this._drawEndCircle(t,i.x2,i.y2))}))}}class C extends g{constructor(t){super(t)}renderer(){return new f(this._p1,this._p2,this._source._options,this._source.hovered)}}class k extends h{_paneViews=[];_hovered=!1;constructor(t,e,i){super(),this.points.push(t),this.points.push(e),this._options={...r,...i}}setFirstPoint(t){this.updatePoints(t)}setSecondPoint(t){this.updatePoints(null,t)}get p1(){return this.points[0]}get p2(){return this.points[1]}get hovered(){return this._hovered}}class S extends k{_type="TrendLine";constructor(t,e,i){super(t,e,i),this._paneViews=[new C(this)]}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._hovered=!1,this.requestUpdate(),this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._hovered=!0,this.requestUpdate(),this._subscribe("mousedown",this._handleMouseDownInteraction),this._unsubscribe("mouseup",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGINGP1:case l.DRAGGINGP2:case l.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP1||this._addDiffToPoint(this.p1,t.logical,t.price),this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP2||this._addDiffToPoint(this.p2,t.logical,t.price)}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint;if(!t)return;const e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(l.DRAGGING);Math.abs(t.x-e.x)<10&&Math.abs(t.y-e.y)<10?this._moveToState(l.DRAGGINGP1):Math.abs(t.x-i.x)<10&&Math.abs(t.y-i.y)<10?this._moveToState(l.DRAGGINGP2):this._moveToState(l.DRAGGING)}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this._paneViews[0]._p1.x,s=this._paneViews[0]._p1.y,o=this._paneViews[0]._p2.x,n=this._paneViews[0]._p2.y;if(!(i&&o&&s&&n))return!1;const a=t.point.x,r=t.point.y;if(a<=Math.min(i,o)-e||a>=Math.max(i,o)+e)return!1;return Math.abs((n-s)*a-(o-i)*r+o*s-n*i)/Math.sqrt((n-s)**2+(o-i)**2)<=e}}class L extends d{constructor(t,e,i,s){super(t,e,i,s)}draw(t){t.useBitmapCoordinateSpace((t=>{const e=t.context,i=this._getScaledCoordinates(t);if(!i)return;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.fillStyle=this._options.fillColor;const s=Math.min(i.x1,i.x2),o=Math.min(i.y1,i.y2),n=Math.abs(i.x1-i.x2),a=Math.abs(i.y1-i.y2);e.strokeRect(s,o,n,a),e.fillRect(s,o,n,a);const r=6*t.horizontalPixelRatio,l=Math.round(12*t.verticalPixelRatio),h=u(this._options.textColor,this._options.lineColor),c=s+n/2,d=o+a/2;if("outside"===(this._options.textPlacement??"outside"))switch(this._options.textPosition){case"left":_(e,s-r,d,this._options.text,h,l,"right","middle");break;case"right":_(e,s+n+r,d,this._options.text,h,l,"left","middle");break;case"top":default:_(e,c,o-r,this._options.text,h,l,"center","bottom");break;case"bottom":_(e,c,o+a+r,this._options.text,h,l,"center","top")}else switch(this._options.textPosition){case"left":_(e,s+r,d,this._options.text,h,l,"left","middle");break;case"right":_(e,s+n-r,d,this._options.text,h,l,"right","middle");break;case"top":_(e,c,o+r,this._options.text,h,l,"center","top");break;case"bottom":_(e,c,o+a-r,this._options.text,h,l,"center","bottom");break;default:_(e,c,d,this._options.text,h,l,"center","middle")}this._hovered&&(this._drawEndCircle(t,s,o),this._drawEndCircle(t,s+n,o),this._drawEndCircle(t,s+n,o+a),this._drawEndCircle(t,s,o+a))}))}}class D extends g{constructor(t){super(t)}renderer(){return new L(this._p1,this._p2,this._source._options,this._source.hovered)}}const E={fillEnabled:!0,fillColor:"rgba(255, 255, 255, 0.2)",textPlacement:"outside",...r};class M extends k{_type="Box";constructor(t,e,i){super(t,e,i),this._options={...E,...i},this._paneViews=[new D(this)]}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._hovered=!1,this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._hovered=!0,this._unsubscribe("mouseup",this._handleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGINGP1:case l.DRAGGINGP2:case l.DRAGGINGP3:case l.DRAGGINGP4:case l.DRAGGING:document.body.style.cursor="grabbing",document.body.addEventListener("mouseup",this._handleMouseUpInteraction),this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP1||this._addDiffToPoint(this.p1,t.logical,t.price),this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP2||this._addDiffToPoint(this.p2,t.logical,t.price),this._state!=l.DRAGGING&&(this._state==l.DRAGGINGP3&&(this._addDiffToPoint(this.p1,t.logical,0),this._addDiffToPoint(this.p2,0,t.price)),this._state==l.DRAGGINGP4&&(this._addDiffToPoint(this.p1,0,t.price),this._addDiffToPoint(this.p2,t.logical,0)))}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint,e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(l.DRAGGING);const s=10;Math.abs(t.x-e.x)l-p&&ah-p&&ri.appendChild(this.makeColorBox(t))));let s=document.createElement("div");s.style.backgroundColor=window.pane.borderColor,s.style.height="1px",s.style.width="130px";let o=document.createElement("div");o.style.margin="10px";let n=document.createElement("div");n.style.color="lightgray",n.style.fontSize="12px",n.innerText="Opacity",this._opacityLabel=document.createElement("div"),this._opacityLabel.style.color="lightgray",this._opacityLabel.style.fontSize="12px",this._opacitySlider=document.createElement("input"),this._opacitySlider.type="range",this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%",this._opacitySlider.oninput=()=>{this._opacityLabel.innerText=this._opacitySlider.value+"%",this.opacity=parseInt(this._opacitySlider.value)/100,this.updateColor()},o.appendChild(n),o.appendChild(this._opacitySlider),o.appendChild(this._opacityLabel),this._div.appendChild(i),this._div.appendChild(s),this._div.appendChild(o),window.containerDiv.appendChild(this._div)}_updateOpacitySlider(){this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%"}makeColorBox(t){const e=document.createElement("div");e.style.width="18px",e.style.height="18px",e.style.borderRadius="3px",e.style.margin="3px",e.style.boxSizing="border-box",e.style.backgroundColor=t,e.addEventListener("mouseover",(()=>e.style.border="2px solid lightgray")),e.addEventListener("mouseout",(()=>e.style.border="none"));const i=T.extractRGBA(t);return e.addEventListener("click",(()=>{this.rgba=i,this.updateColor()})),e}static extractRGBA(t){const e=document.createElement("div");e.style.color=t,document.body.appendChild(e);const i=getComputedStyle(e).color;document.body.removeChild(e);const s=i.match(/\d+/g)?.map(Number);if(!s)return[];let o=i.includes("rgba")?parseFloat(i.split(",")[3]):1;return[s[0],s[1],s[2],o]}updateColor(){if(!h.lastHoveredObject||!this.rgba)return;const t=`rgba(${this.rgba[0]}, ${this.rgba[1]}, ${this.rgba[2]}, ${this.opacity})`;h.lastHoveredObject.applyOptions({[this.colorOption]:t}),this.saveDrawings()}openMenu(t){h.lastHoveredObject&&(this.rgba=T.extractRGBA(h.lastHoveredObject._options[this.colorOption]),this.opacity=this.rgba[3],this._updateOpacitySlider(),this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="flex",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10))}closeMenu(){document.body.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}class P{static _styles=[{name:"Solid",var:e.LineStyle.Solid},{name:"Dotted",var:e.LineStyle.Dotted},{name:"Dashed",var:e.LineStyle.Dashed},{name:"Large Dashed",var:e.LineStyle.LargeDashed},{name:"Sparse Dotted",var:e.LineStyle.SparseDotted}];_div;_saveDrawings;constructor(t){this._saveDrawings=t,this._div=document.createElement("div"),this._div.classList.add("context-menu"),P._styles.forEach((t=>{this._div.appendChild(this._makeTextBox(t.name,t.var))})),window.containerDiv.appendChild(this._div)}_makeTextBox(t,e){const i=document.createElement("span");return i.classList.add("context-menu-item"),i.innerText=t,i.addEventListener("click",(()=>{h.lastHoveredObject?.applyOptions({lineStyle:e}),this._saveDrawings()})),i}openMenu(t){this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="block",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10)}closeMenu(){document.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}function G(t){const e=[];for(const i of t)0==e.length?e.push(i.toUpperCase()):i==i.toUpperCase()?e.push(" "+i):e.push(i);return e.join("")}class I{saveDrawings;drawingTool;div;hoverItem;items=[];constructor(t,e){this.saveDrawings=t,this.drawingTool=e,this._onRightClick=this._onRightClick.bind(this),this.div=document.createElement("div"),this.div.classList.add("context-menu"),document.body.appendChild(this.div),this.hoverItem=null,document.body.addEventListener("contextmenu",this._onRightClick)}_handleClick=t=>this._onClick(t);_onClick(t){t.target&&(this.div.contains(t.target)||(this.div.style.display="none",document.body.removeEventListener("click",this._handleClick)))}_onRightClick(t){if(!h.hoveredObject)return;for(const t of this.items)this.div.removeChild(t);this.items=[];for(const t of Object.keys(h.hoveredObject._options)){let e;if(t.toLowerCase().includes("color"))e=new T(this.saveDrawings,t);else{if("lineStyle"!==t)continue;e=new P(this.saveDrawings)}let i=t=>e.openMenu(t);this.menuItem(G(t),i,(()=>{document.removeEventListener("click",e.closeMenu),e._div.style.display="none"}))}this.separator(),this.menuItem("Delete Drawing",(()=>this.drawingTool.delete(h.lastHoveredObject))),t.preventDefault(),this.div.style.left=t.clientX+"px",this.div.style.top=t.clientY+"px",this.div.style.display="block",document.body.addEventListener("click",this._handleClick)}menuItem(t,e,i=null){const s=document.createElement("span");s.classList.add("context-menu-item"),this.div.appendChild(s);const o=document.createElement("span");if(o.innerText=t,o.style.pointerEvents="none",s.appendChild(o),i){let t=document.createElement("span");t.innerText="►",t.style.fontSize="8px",t.style.pointerEvents="none",s.appendChild(t)}if(s.addEventListener("mouseover",(()=>{this.hoverItem&&this.hoverItem.closeAction&&this.hoverItem.closeAction(),this.hoverItem={elem:o,action:e,closeAction:i}})),i){let t;s.addEventListener("mouseover",(()=>t=setTimeout((()=>e(s.getBoundingClientRect())),100))),s.addEventListener("mouseout",(()=>clearTimeout(t)))}else s.addEventListener("click",(t=>{e(t),this.div.style.display="none"}));this.items.push(s)}separator(){const t=document.createElement("div");t.style.width="90%",t.style.height="1px",t.style.margin="3px 0px",t.style.backgroundColor=window.pane.borderColor,this.div.appendChild(t),this.items.push(t)}}class R extends w{_type="RayLine";constructor(t,e){super({...t},e),this._point.time=t.time}updatePoints(...t){for(const e of t)e&&(this._point=e);this.requestUpdate()}_onDrag(t){this._addDiffToPoint(this._point,t.logical,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price),s=this._point.time?this.chart.timeScale().timeToCoordinate(this._point.time):null;return!(!i||!s)&&(Math.abs(i-t.point.y)s-e)}}class B extends c{_point={x:null,y:null};constructor(t,e){super(e),this._point=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null==this._point.x)return;const e=t.context,i=this._point.x*t.horizontalPixelRatio;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.beginPath(),e.moveTo(i,0),e.lineTo(i,t.bitmapSize.height),e.stroke();const s=6*t.horizontalPixelRatio,o=Math.round(12*t.verticalPixelRatio),n=u(this._options.textColor,this._options.lineColor),a=t.bitmapSize.height;let r,l,h,c;switch(this._options.textVAlign){case"top":r=s,l="top";break;case"bottom":r=a-s,l="bottom";break;default:r=a/2,l="middle"}switch(this._options.textHAlign){case"left":h=i-s,c="right";break;case"right":h=i+s,c="left";break;default:h=i,c="center"}_(e,h,r,this._options.text,n,o,c,l)}))}}class V extends b{_source;_point={x:null,y:null};constructor(t){super(t),this._source=t}update(){const t=this._source._point,e=this._source.chart.timeScale(),i=this._source.series;this._point.x=t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical),this._point.y=i.priceToCoordinate(t.price)}renderer(){return new B(this._point,this._source._options)}}class A{_source;_x=null;constructor(t){this._source=t}update(){if(!this._source.chart||!this._source._point)return;const t=this._source._point,e=this._source.chart.timeScale();this._x=t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical)}visible(){return!0}tickVisible(){return!0}coordinate(){return this._x??0}text(){return""}textColor(){return"white"}backColor(){return this._source._options.lineColor}}class N extends h{_type="VerticalLine";_paneViews;_timeAxisViews;_point;_callbackName;_startDragPoint=null;constructor(t,e,i=null){super(e),this._point=t,this._paneViews=[new V(this)],this._callbackName=i,this._timeAxisViews=[new A(this)]}updateAllViews(){this._paneViews.forEach((t=>t.update())),this._timeAxisViews.forEach((t=>t.update()))}timeAxisViews(){return this._timeAxisViews}updatePoints(...t){for(const e of t)e&&(!e.time&&e.logical&&(e.time=this.series.dataByIndex(e.logical)?.time||null),this._point=e);this.requestUpdate()}get points(){return[this._point]}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._unsubscribe("mouseup",this._childHandleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._childHandleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._addDiffToPoint(this._point,t.logical,0),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.chart.timeScale();let s;return s=this._point.time?i.timeToCoordinate(this._point.time):i.logicalToCoordinate(this._point.logical),!!s&&Math.abs(s-t.point.x){this._handleMouseUpInteraction(),this._callbackName&&window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`)}}class O{static TREND_SVG='';static HORZ_SVG='';static RAY_SVG='';static BOX_SVG='';static VERT_SVG=O.RAY_SVG;div;activeIcon=null;buttons=[];_commandFunctions;_handlerID;_drawingTool;constructor(t,e,i,s){this._handlerID=t,this._commandFunctions=s,this._drawingTool=new x(e,i,(()=>this.removeActiveAndSave())),this.div=this._makeToolBox(),new I(this.saveDrawings,this._drawingTool),s.push((t=>{if((t.metaKey||t.ctrlKey)&&"KeyZ"===t.code){const t=this._drawingTool.drawings.pop();return t&&this._drawingTool.delete(t),!0}return!1}))}toJSON(){const{...t}=this;return t}_makeToolBox(){let t=document.createElement("div");t.classList.add("toolbox"),this.buttons.push(this._makeToolBoxElement(S,"KeyT",O.TREND_SVG)),this.buttons.push(this._makeToolBoxElement(w,"KeyH",O.HORZ_SVG)),this.buttons.push(this._makeToolBoxElement(R,"KeyR",O.RAY_SVG)),this.buttons.push(this._makeToolBoxElement(M,"KeyB",O.BOX_SVG)),this.buttons.push(this._makeToolBoxElement(N,"KeyV",O.VERT_SVG,!0));for(const e of this.buttons)t.appendChild(e);return t}_makeToolBoxElement(t,e,i,s=!1){const o=document.createElement("div");o.classList.add("toolbox-button");const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","29"),n.setAttribute("height","29");const a=document.createElementNS("http://www.w3.org/2000/svg","g");a.innerHTML=i,a.setAttribute("fill",window.pane.color),n.appendChild(a),o.appendChild(n);const r={div:o,group:a,type:t};return o.addEventListener("click",(()=>this._onIconClick(r))),this._commandFunctions.push((t=>this._handlerID===window.handlerInFocus&&(!(!t.altKey||t.code!==e)&&(t.preventDefault(),this._onIconClick(r),!0)))),1==s&&(n.style.transform="rotate(90deg)",n.style.transformBox="fill-box",n.style.transformOrigin="center"),o}_onIconClick(t){this.activeIcon&&(this.activeIcon.div.classList.remove("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.stopDrawing(),this.activeIcon===t)?this.activeIcon=null:(this.activeIcon=t,this.activeIcon.div.classList.add("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.beginDrawing(this.activeIcon.type))}removeActiveAndSave=()=>{window.setCursor("default"),this.activeIcon&&this.activeIcon.div.classList.remove("active-toolbox-button"),this.activeIcon=null,this.saveDrawings()};addNewDrawing(t){this._drawingTool.addNewDrawing(t)}clearDrawings(){this._drawingTool.clearDrawings()}saveDrawings=()=>{const t=[];for(const e of this._drawingTool.drawings)t.push({type:e._type,points:e.points,options:e._options});const e=JSON.stringify(t);window.callbackFunction(`save_drawings${this._handlerID}_~_${e}`)};loadDrawings(t){t.forEach((t=>{switch(t.type){case"Box":this._drawingTool.addNewDrawing(new M(t.points[0],t.points[1],t.options));break;case"TrendLine":this._drawingTool.addNewDrawing(new S(t.points[0],t.points[1],t.options));break;case"HorizontalLine":this._drawingTool.addNewDrawing(new w(t.points[0],t.options));break;case"RayLine":this._drawingTool.addNewDrawing(new R(t.points[0],t.options));break;case"VerticalLine":this._drawingTool.addNewDrawing(new N(t.points[0],t.options))}}))}}class H{makeButton;callbackName;div;isOpen=!1;widget;constructor(t,e,i,s,o,n){this.makeButton=t,this.callbackName=e,this.div=document.createElement("div"),this.div.classList.add("topbar-menu"),this.widget=this.makeButton(s,null,o,!0,n),this.widget.elem.classList.add("menu-button"),this.updateMenuItems(i),this.widget.elem.addEventListener("click",(()=>{if(this.isOpen=!this.isOpen,!this.isOpen)return void(this.div.style.display="none");let t=this.widget.elem.getBoundingClientRect();this.div.style.display="flex",this.div.style.flexDirection="column";let e=t.x+t.width/2;this.div.style.left=e-this.div.clientWidth/2+"px",this.div.style.top=t.y+t.height+"px"})),document.body.appendChild(this.div)}updateMenuItems(t){this.div.innerHTML="",t.forEach((t=>{let e=this.makeButton(t,null,!1,!1);e.elem.addEventListener("click",(()=>{this._clickHandler(e.elem.innerText)})),this.div.appendChild(e.elem)})),this.widget.elem.innerText=t[0]}_clickHandler(t){this.widget.elem.innerText=t,window.callbackFunction(`${this.callbackName}_~_${t}`),this.div.style.display="none",this.isOpen=!1}}class F{_handler;_div;left;right;constructor(t){this._handler=t,this._div=document.createElement("div"),this._div.classList.add("topbar");const e=t=>{const e=document.createElement("div");return e.classList.add("topbar-container"),e.style.justifyContent=t,this._div.appendChild(e),e};this.left=e("flex-start"),this.right=e("flex-end")}makeSwitcher(t,e,i,s="left"){const o=document.createElement("div");let n;o.classList.add("topbar-switcher");const a={elem:o,callbackName:i,intervalElements:t.map((t=>{const i=document.createElement("button");i.classList.add("topbar-button"),i.classList.add("switcher-button"),i.style.margin="0px 2px",i.innerText=t,t==e&&(n=i,i.classList.add("active-switcher-button"));const s=F.getClientWidth(i);return i.style.minWidth=s+1+"px",i.addEventListener("click",(()=>a.onItemClicked(i))),o.appendChild(i),i})),setActive:t=>{const e=Array.from(o.children).find((e=>e.textContent===t));e&&e!=n&&(n.classList.remove("active-switcher-button"),e.classList.add("active-switcher-button"),n=e)},onItemClicked:t=>{t!=n&&(n.classList.remove("active-switcher-button"),t.classList.add("active-switcher-button"),n=t,window.callbackFunction(`${a.callbackName}_~_${t.innerText}`))}};return this.appendWidget(o,s,!0),a}makeTextBoxWidget(t,e="left",i=null){if(i){const s=document.createElement("input");return s.classList.add("topbar-textbox-input"),s.value=t,s.style.width=`${s.value.length+2}ch`,s.addEventListener("focus",(()=>{window.textBoxFocused=!0})),s.addEventListener("input",(t=>{t.preventDefault(),s.style.width=`${s.value.length+2}ch`})),s.addEventListener("keydown",(t=>{"Enter"==t.key&&(t.preventDefault(),s.blur())})),s.addEventListener("blur",(()=>{window.callbackFunction(`${i}_~_${s.value}`),window.textBoxFocused=!1})),this.appendWidget(s,e,!0),s}{const i=document.createElement("div");return i.classList.add("topbar-textbox"),i.innerText=t,this.appendWidget(i,e,!0),i}}makeMenu(t,e,i,s,o){return new H(this.makeButton.bind(this),s,t,e,i,o)}makeButton(t,e,i,s=!0,o="left",n=!1){let a=document.createElement("button");a.classList.add("topbar-button"),a.innerText=t,document.body.appendChild(a),a.style.minWidth=a.clientWidth+1+"px",document.body.removeChild(a);let r={elem:a,callbackName:e};if(e){let t;if(n){let e=!1;t=()=>{e=!e,window.callbackFunction(`${r.callbackName}_~_${e}`),a.style.backgroundColor=e?"var(--active-bg-color)":"",a.style.color=e?"var(--active-color)":""}}else t=()=>window.callbackFunction(`${r.callbackName}_~_${a.innerText}`);a.addEventListener("click",t)}return s&&this.appendWidget(a,o,i),r}makeSeparator(t="left"){const e=document.createElement("div");e.classList.add("topbar-seperator");("left"==t?this.left:this.right).appendChild(e)}appendWidget(t,e,i){const s="left"==e?this.left:this.right;i?("left"==e&&s.appendChild(t),this.makeSeparator(e),"right"==e&&s.appendChild(t)):s.appendChild(t),this._handler.reSize()}static getClientWidth(t){document.body.appendChild(t);const e=t.clientWidth;return document.body.removeChild(t),e}}s();class z{numbers;cache;constructor(t){this.numbers=t,this.cache=new Map}findClosestIndex(t,e){const i=`${t}:${e}`;if(this.cache.has(i))return this.cache.get(i);const s=this._performSearch(t,e);return this.cache.set(i,s),s}_performSearch(t,e){let i=0,s=this.numbers.length-1;if(t<=this.numbers[0].time)return 0;if(t>=this.numbers[s].time)return s;for(;i<=s;){const e=Math.floor((i+s)/2),o=this.numbers[e].time;if(o===t)return e;o>t?s=e-1:i=e+1}return"left"===e?i:s}}class ${_arr;_chunkSize;_cache;constructor(t,e=10){this._arr=t,this._chunkSize=e,this._cache=new Map}getMinMax(t,e){const i=`${t}:${e}`;if(i in this._cache)return this._cache.get(i);const s={lower:1/0,upper:-1/0},o=Math.floor(t/this._chunkSize),n=Math.floor(e/this._chunkSize);for(let t=o;t<=n;t++){const e=t*this._chunkSize,o=Math.min((t+1)*this._chunkSize-1,this._arr.length-1),n=`${e}:${o}`;if(n in this._cache.keys()){const t=this._cache.get(i);this._check(t,s)}else{const t={lower:1/0,upper:-1/0};for(let i=e;i<=o;i++)this._check(this._arr[i],t);this._cache.set(n,t),this._check(t,s)}}return this._cache.set(i,s),s}_check(t,e){t.lowere.upper&&(e.upper=t.upper)}}class W{_viewData;constructor(t){this._viewData=t}draw(){}drawBackground(t){const e=this._viewData.data;t.useBitmapCoordinateSpace((t=>{const i=t.context;i.scale(t.horizontalPixelRatio,t.verticalPixelRatio),i.strokeStyle=this._viewData.options.lineColor,i.lineWidth=this._viewData.options.lineWidth,i.beginPath();const s=new Path2D,o=new Path2D;s.moveTo(e[0].x,e[0].upper),o.moveTo(e[0].x,e[0].upper);for(const t of e)s.lineTo(t.x,t.upper),o.lineTo(t.x,t.upper);const n=e.length-1;s.lineTo(e[n].x,e[n].lower),o.moveTo(e[n].x,e[n].lower);for(let t=e.length-2;t>=0;t--)s.lineTo(e[t].x,e[t].lower),o.lineTo(e[t].x,e[t].lower);s.lineTo(e[0].x,e[0].upper),s.closePath(),i.stroke(o),i.fillStyle=this._viewData.options.fillColor,i.fill(s)}))}}class U{_source;_data;constructor(t){this._source=t,this._data={data:[],options:this._source._options}}update(){const t=this._source.series,e=this._source.chart.timeScale();this._data.data=this._source._bandsData.map((i=>({x:e.timeToCoordinate(i.time)??-100,upper:t.priceToCoordinate(i.upper)??-100,lower:t.priceToCoordinate(i.lower)??-100})))}renderer(){return new W(this._data)}}const q={lineColor:"rgb(25, 200, 100)",fillColor:"rgba(25, 200, 100, 0.25)",lineWidth:0};const Y={...e.customSeriesDefaultOptions,lastValueVisible:!1,priceLineVisible:!1,cellShader:t=>{const e=Math.min(Math.max(0,t),100);return`rgba(0, ${100+1.55*e}, ${0+e}, ${.2+.8*e})`},cellBorderWidth:1,cellBorderColor:"transparent"};function j(t,e,i){const s=t-e,o=t+e,n=Math.round(s*i);return{position:n,length:Math.round(o*i)-n}}function X(t,e,i=1,s){const o=Math.round(e*t),n=s?i:Math.round(i*e),a=function(t){return Math.floor(.5*t)}(n);return{position:o-a,length:n}}function K(t,e,i){const s=Math.round(i*t),o=Math.round(i*e);return{position:Math.min(s,o),length:Math.abs(o-s)+1}}class Z{_data=null;_options=null;draw(t,e){t.useBitmapCoordinateSpace((t=>this._drawImpl(t,e)))}update(t,e){this._data=t,this._options=e}_drawImpl(t,e){if(null===this._data||0===this._data.bars.length||null===this._data.visibleRange||null===this._options)return;const i=this._options,s=this._data.bars.map((t=>({x:t.x,cells:t.originalData.cells.map((t=>({amount:t.amount,color:t.color,side:t.side,low:e(t.low),high:e(t.high)})))}))),o=this._data.barSpacing>3*i.cellBorderWidth;for(let e=this._data.visibleRange.from;e{if(null===this._price||null===this._x)return;const e=Math.round(this._x*t.horizontalPixelRatio),i=X(this._price,t.verticalPixelRatio,t.verticalPixelRatio),s=i.position+i.length/2,o=t.context;o.beginPath(),o.setLineDash([4*t.verticalPixelRatio,2*t.verticalPixelRatio]),o.moveTo(e,s),o.lineTo(t.bitmapSize.width,s),o.strokeStyle=this._color,o.lineWidth=t.verticalPixelRatio,o.stroke()}))}}class Q{_renderer;constructor(){this._renderer=new J}renderer(){return this._renderer}update(t,e,i){this._renderer.update(t,e,i)}}class tt{_viewData;constructor(t){this._viewData=t}draw(t){const e=this._viewData.data;t.useBitmapCoordinateSpace((t=>{const i=t.context,s=t.bitmapSize.height,o=t.horizontalPixelRatio*this._viewData.barWidth/2,n=-1*(o+1),a=t.bitmapSize.width;e.forEach((e=>{const r=e.x*t.horizontalPixelRatio;if(r({x:t.timeToCoordinate(e.time)??-100,color:e.color}))),this._data.data.length>1?this._data.barWidth=this._data.data[1].x-this._data.data[0].x:this._data.barWidth=6}renderer(){return new tt(this._data)}zOrder(){return"bottom"}}const it={};const st={title:"",followMode:"tracking",horizontalDeadzoneWidth:45,verticalDeadzoneHeight:100,verticalSpacing:20,topOffset:20};class ot{_chart;_element;_titleElement;_priceElement;_dateElement;_timeElement;_options;_lastTooltipWidth=null;constructor(t,e){this._options={...st,...e},this._chart=t;const i=document.createElement("div");at(i,{display:"flex","flex-direction":"column","align-items":"center",position:"absolute",transform:"translate(calc(0px - 50%), 0px)",opacity:"0",left:"0%",top:"0","z-index":"100","background-color":"white","border-radius":"4px",padding:"5px 10px","font-family":"-apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif","font-size":"12px","font-weight":"400","box-shadow":"0px 2px 4px rgba(0, 0, 0, 0.2)","line-height":"16px","pointer-events":"none",color:"#131722"});const s=document.createElement("div");at(s,{"font-size":"16px","line-height":"24px","font-weight":"590"}),nt(s,this._options.title),i.appendChild(s);const o=document.createElement("div");at(o,{"font-size":"14px","line-height":"18px","font-weight":"590"}),nt(o,""),i.appendChild(o);const n=document.createElement("div");at(n,{color:"#787B86"}),nt(n,""),i.appendChild(n);const a=document.createElement("div");at(a,{color:"#787B86"}),nt(a,""),i.appendChild(a),this._element=i,this._titleElement=s,this._priceElement=o,this._dateElement=n,this._timeElement=a;const r=this._chart.chartElement();r.appendChild(this._element);const l=r.parentElement;if(!l)return void console.error("Chart Element is not attached to the page.");const h=getComputedStyle(l).position;"relative"!==h&&"absolute"!==h&&console.error("Chart Element position is expected be `relative` or `absolute`.")}destroy(){this._chart&&this._element&&this._chart.chartElement().removeChild(this._element)}applyOptions(t){this._options={...this._options,...t}}options(){return this._options}updateTooltipContent(t){if(!this._element)return;const e=this._element.getBoundingClientRect();this._lastTooltipWidth=e.width,void 0!==t.title&&this._titleElement&&nt(this._titleElement,t.title),nt(this._priceElement,t.price),nt(this._dateElement,t.date),nt(this._timeElement,t.time)}updatePosition(t){if(!this._chart||!this._element)return;if(this._element.style.opacity=t.visible?"1":"0",!t.visible)return;const e=this._calculateXPosition(t,this._chart),i=this._calculateYPosition(t);this._element.style.transform=`translate(${e}, ${i})`}_calculateXPosition(t,e){const i=t.paneX+e.priceScale("left").width(),s=this._lastTooltipWidth?Math.ceil(this._lastTooltipWidth/2):this._options.horizontalDeadzoneWidth;return`calc(${Math.min(Math.max(s,i),e.timeScale().width()-s)}px - 50%)`}_calculateYPosition(t){if("top"==this._options.followMode)return`${this._options.topOffset}px`;const e=t.paneY,i=e<=this._options.verticalSpacing+this._options.verticalDeadzoneHeight;return`calc(${e+(i?1:-1)*this._options.verticalSpacing}px${i?"":" - 100%"})`}}function nt(t,e){t&&e!==t.innerText&&(t.innerText=e,t.style.display=e?"block":"none")}function at(t,e){for(const[i,s]of Object.entries(e))t.style.setProperty(i,s)}class rt{_data;constructor(t){this._data=t}draw(t){this._data.visible&&t.useBitmapCoordinateSpace((t=>{const e=t.context,i=X(this._data.x,t.horizontalPixelRatio,1);e.fillStyle=this._data.color,e.fillRect(i.position,this._data.topMargin*t.verticalPixelRatio,i.length,t.bitmapSize.height)}))}}class lt{_data;constructor(t){this._data=t}update(t){this._data=t}renderer(){return new rt(this._data)}zOrder(){return"bottom"}}const ht={lineColor:"rgba(0, 0, 0, 0.2)",priceExtractor:t=>void 0!==t.value?t.value.toFixed(2):void 0!==t.close?t.close.toFixed(2):""};class ct{_data;constructor(t){this._data=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null===this._data.x||null===this._data.top)return;const e=t.context,i=K(this._data.x,this._data.x+this._data.width,t.horizontalPixelRatio),s=K(this._data.top,this._data.top-this._data.columnHeight*this._data.items.length,t.verticalPixelRatio);e.fillStyle="rgba(0, 0, 255, 0.2)",e.fillRect(i.position,s.position,i.length,s.length),e.fillStyle="rgba(80, 80, 255, 0.8)",this._data.items.forEach((i=>{if(null===i.y)return;const s=K(i.y,i.y-this._data.columnHeight,t.verticalPixelRatio),o=K(this._data.x,this._data.x+i.width,t.horizontalPixelRatio);e.fillRect(o.position,s.position,o.length,s.length-2)}))}))}}class dt{_source;_x=null;_width=6;_columnHeight=0;_top=null;_items=[];constructor(t){this._source=t}update(){const t=this._source._vpData,e=this._source._series,i=this._source._chart.timeScale();this._x=i.timeToCoordinate(t.time),this._width=i.options().barSpacing*t.width;const s=e.priceToCoordinate(t.profile[0].price)??0,o=e.priceToCoordinate(t.profile[1].price)??i.height();this._columnHeight=Math.max(1,s-o);const n=t.profile.reduce(((t,e)=>Math.max(t,e.vol)),0);this._top=s,this._items=t.profile.map((t=>({y:e.priceToCoordinate(t.price),width:this._width*t.vol/n})))}renderer(){return new ct({x:this._x,top:this._top,columnHeight:this._columnHeight,width:this._width,items:this._items})}}const pt={endTime:null,stopColor:"rgba(239, 83, 80, 0.25)",targetColor:"rgba(38, 166, 154, 0.25)",quantity:null};function ut(t,e,i,s,o,n,a,r){t.save(),t.font=`bold ${n}px -apple-system, BlinkMacSystemFont, sans-serif`,t.fillStyle=o,t.textAlign=a,t.textBaseline=r,t.fillText(s,e,i),t.restore()}class _t{_data=null;update(t){this._data=t}drawBackground(t){if(!this._data)return;const e=this._data;t.useBitmapCoordinateSpace((t=>{const i=t.context,s=t.horizontalPixelRatio,o=t.verticalPixelRatio,n=Math.round(e.entryX*s),a=Math.round(e.endX*s)-n;if(a<=0)return;const r=Math.round(e.entryY*o),l=Math.round(e.stopY*o),h=Math.round(e.targetY*o);i.fillStyle=e.stopColor,i.fillRect(n,Math.min(r,l),a,Math.abs(l-r)),i.fillStyle=e.targetColor,i.fillRect(n,Math.min(r,h),a,Math.abs(h-r))}))}draw(t){if(!this._data?.hovered)return;const e=this._data;t.useBitmapCoordinateSpace((t=>{const i=t.context,s=t.horizontalPixelRatio,o=t.verticalPixelRatio,n=Math.round(e.entryX*s),a=Math.round(e.endX*s);if(a-n<=0)return;const r=Math.round(e.entryY*o),l=Math.round(e.stopY*o),h=Math.round(e.targetY*o),c=Math.round((n+a)/2),d=Math.round(11*s),p=Math.round(4*s);i.save(),i.strokeStyle="#2962FF",i.lineWidth=Math.round(1*o),i.beginPath(),i.moveTo(n,r),i.lineTo(a,r),i.stroke(),ut(i,n+p,r-p,e.entryPriceLabel,"#2962FF",d,"left","bottom"),ut(i,a-p,r-p,`${e.barCount} bars`,"rgba(255,255,255,0.9)",d,"right","bottom"),ut(i,c,r+p,e.rrRatio,"rgba(255,255,255,0.9)",d,"center","top");const u=e.isLong?h+p:h-p,_=e.isLong?l-p:l+p,m=e.isLong?"top":"bottom",b=e.isLong?"bottom":"top";ut(i,n+p,u,e.tpPriceLabel,"rgba(38,166,154,1)",d,"left",m),ut(i,a-p,u,e.winAmount,"rgba(38,166,154,1)",d,"right",m),ut(i,n+p,_,e.slPriceLabel,"rgba(239,83,80,1)",d,"left",b),ut(i,a-p,_,e.loseAmount,"rgba(239,83,80,1)",d,"right",b),i.restore()}))}}class mt{_source;_renderer;constructor(t){this._source=t,this._renderer=new _t}update(){const t=this._source._options,e=this._source.chart,i=this._source.series,s=e.timeScale(),o=s.timeToCoordinate(t.entryTime);if(null===o)return void this._renderer.update(null);const n=t.endTime??this._source._autoEndTime,a=s.coordinateToLogical(o)??0,r=a+2;let l;if(null!==n){const t=s.timeToCoordinate(n);if(null!==t){l=(s.coordinateToLogical(t)??0)0?`1:${Math.round(u/p)}`:"1:∞",m=s.coordinateToLogical(l)??r,b=Math.max(0,m-a),g=t=>`$${t.toFixed(2)}`,y=(t,e)=>`${e}${Math.abs(t).toFixed(2)}`,v=(e,i)=>`${i}$${Math.abs(e*t.quantity).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2})}`,w=null!==t.quantity?v(u,"+"):y(u,"+"),x=null!==t.quantity?v(p,"-"):y(p,"-");this._source._lastBounds={entryX:o,endX:l,minY:Math.min(c,d),maxY:Math.max(c,d)},this._renderer.update({entryX:o,endX:l,entryY:h,stopY:c,targetY:d,stopColor:t.stopColor,targetColor:t.targetColor,hovered:this._source._hovered,isLong:t.target>t.entry,rrRatio:_,barCount:b,winAmount:w,loseAmount:x,entryPriceLabel:g(t.entry),tpPriceLabel:g(t.target),slPriceLabel:g(t.stop)})}renderer(){return this._renderer}}return t.BandsIndicator=class extends a{_paneViews;_bandsData=[];_options;_timeIndices;_upperLower;_upperSeries;_lowerSeries;constructor(t,e,i={}){super(),this._upperSeries=t,this._lowerSeries=e,this._options={...q,...i},this._paneViews=[new U(this)],this._timeIndices=new z([]),this._upperLower=new $([])}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}attached(t){super.attached(t),this._lowerSeries.subscribeDataChanged(this._onLowerDataChanged),this.dataUpdated("full")}detached(){this._lowerSeries.unsubscribeDataChanged(this._onLowerDataChanged),super.detached()}_onLowerDataChanged=t=>{this.dataUpdated(t),this.requestUpdate()};dataUpdated(t){this.calculateBands(),"full"===t&&(this._timeIndices=new z(this._bandsData))}calculateBands(){const t=this._upperSeries.data(),e=this._lowerSeries.data(),i=new Map;for(const t of e){const e=t.value;void 0!==e&&i.set(t.time,e)}const s=[];for(const e of t){const t=e.value;if(void 0===t)continue;const o=i.get(e.time);void 0!==o&&s.push({time:e.time,upper:t,lower:o})}this._bandsData=s,this._upperLower=new $(this._bandsData,4)}autoscaleInfo(t,e){const i=this.chart.timeScale(),s=i.coordinateToTime(i.logicalToCoordinate(t)??0)??0,o=i.coordinateToTime(i.logicalToCoordinate(e)??5e9)??5e9,n=this._timeIndices.findClosestIndex(s,"left"),a=this._timeIndices.findClosestIndex(o,"right"),r=this._upperLower.getMinMax(n,a);return{priceRange:{minValue:r.lower,maxValue:r.upper}}}},t.Box=M,t.Handler=class{id;commandFunctions=[];wrapper;div;chart;scale;precision=2;series;volumeSeries;legends=new Map;get legend(){return this.legends.get(0)}_topBar;toolBox;spinner;_seriesList=[];watermark;seriesMarkers;constructor(t,i,s,n,a){this.reSize=this.reSize.bind(this),this.id=t,this.scale={width:i,height:s},this.wrapper=document.createElement("div"),this.wrapper.classList.add("handler"),this.div=document.createElement("div"),this.div.classList.add("chart-root"),this.wrapper.appendChild(this.div),window.containerDiv.append(this.wrapper),this.scale.width<1?(this.wrapper.style.width=100*this.scale.width+"%",this.wrapper.style.flex="0 0 auto",this.wrapper.style.float=n):(this.wrapper.style.width="100%",this.wrapper.style.flex="1 1 auto",this.wrapper.style.minHeight="0"),this.chart=this._createChart(),this.series=this.createCandlestickSeries(0),this.volumeSeries=this.createVolumeSeries(0),this.seriesMarkers=e.createSeriesMarkers(this.series,[]),this.legends.set(0,new o(0,this)),this.chart.subscribeCrosshairMove((t=>{this.legends.forEach((e=>e.legendHandler(t)))})),document.addEventListener("keydown",(t=>{for(let e=0;ewindow.handlerInFocus=this.id)),this.reSize(),a&&window.addEventListener("resize",(()=>this.reSize()))}reSize(){const t=window.containerDiv,e=0!==this.scale.height&&this._topBar?._div.offsetHeight||0,i=Math.max(Math.floor(t.clientWidth*this.scale.width),0),s=this.wrapper.clientHeight>0?this.wrapper.clientHeight:Math.floor(t.clientHeight*this.scale.height),o=Math.max(s-e,0);this.chart.resize(i,o),this.scale.width<1?this.wrapper.style.width=100*this.scale.width+"%":this.wrapper.style.width="100%",0===this.scale.height||0===this.scale.width?this.toolBox&&(this.toolBox.div.style.display="none"):this.toolBox&&(this.toolBox.div.style.display="flex")}_createChart(){const t=window.containerDiv,i=Math.max(Math.floor(t.clientWidth*this.scale.width)||Math.floor(window.innerWidth*this.scale.width),0),s=Math.max(Math.floor(t.clientHeight*this.scale.height)||Math.floor(window.innerHeight*this.scale.height),0);return e.createChart(this.div,{width:i,height:s,layout:{textColor:window.pane.color,background:{color:"#000000",type:e.ColorType.Solid},fontSize:12},rightPriceScale:{scaleMargins:{top:.3,bottom:.25}},timeScale:{timeVisible:!0,secondsVisible:!1},crosshair:{mode:e.CrosshairMode.Normal,vertLine:{labelBackgroundColor:"rgb(46, 46, 46)"},horzLine:{labelBackgroundColor:"rgb(55, 55, 55)"}},grid:{vertLines:{color:"rgba(29, 30, 38, 5)"},horzLines:{color:"rgba(29, 30, 58, 5)"}},handleScroll:{vertTouchDrag:!0}})}createCandlestickSeries(t){const i="rgba(39, 157, 130, 100)",s="rgba(200, 97, 100, 100)",o=this.chart.addSeries(e.CandlestickSeries,{upColor:i,borderUpColor:i,wickUpColor:i,downColor:s,borderDownColor:s,wickDownColor:s},t);return o.priceScale().applyOptions({scaleMargins:{top:.2,bottom:.2}}),o}createVolumeSeries(t){const i=this.chart.addSeries(e.HistogramSeries,{color:"#26a69a",priceFormat:{type:"volume"},priceScaleId:"volume_scale"},t);return i.priceScale().applyOptions({scaleMargins:{top:.8,bottom:0}}),i}getOrCreateLegend(t){return this.legends.has(t)||this.legends.set(t,new o(t,this)),this.legends.get(t)}createLineSeries(t,i,s){const o=this.chart.addSeries(e.LineSeries,{...i},s);return this._seriesList.push(o),this.getOrCreateLegend(s??0).makeSeriesRow(t,o),{name:t,series:o}}createHistogramSeries(t,i,s){const o=this.chart.addSeries(e.HistogramSeries,{...i},s);return this._seriesList.push(o),this.getOrCreateLegend(s??0).makeSeriesRow(t,o),{name:t,series:o}}createAreaSeries(t,i,s){const o=this.chart.addSeries(e.AreaSeries,{...i},s);return this._seriesList.push(o),this.getOrCreateLegend(s??0).makeSeriesRow(t,o),{name:t,series:o}}createToolBox(){this.toolBox=new O(this.id,this.chart,this.series,this.commandFunctions),this.div.appendChild(this.toolBox.div)}createTopBar(){return this._topBar=new F(this),this.wrapper.prepend(this._topBar._div),this._topBar}toJSON(){const{chart:t,...e}=this;return e}static makeSearchBox(t){const e=document.createElement("div");e.classList.add("searchbox"),e.style.display="none";const i=document.createElement("div");i.innerHTML='';const s=document.createElement("input");return s.type="text",e.appendChild(i),e.appendChild(s),t.div.appendChild(e),t.commandFunctions.push((i=>window.handlerInFocus===t.id&&!window.textBoxFocused&&("none"===e.style.display?!!/^[a-zA-Z0-9]$/.test(i.key)&&(e.style.display="flex",s.focus(),!0):("Enter"===i.key||"Escape"===i.key)&&("Enter"===i.key&&window.callbackFunction(`search${t.id}_~_${s.value}`),e.style.display="none",s.value="",!0)))),s.addEventListener("input",(()=>s.value=s.value.toUpperCase())),{window:e,box:s}}static makeSpinner(t){t.spinner=document.createElement("div"),t.spinner.classList.add("spinner"),t.wrapper.appendChild(t.spinner);let e=0;!function i(){t.spinner&&(e+=10,t.spinner.style.transform=`translate(-50%, -50%) rotate(${e}deg)`,requestAnimationFrame(i))}()}static _styleMap={"--bg-color":"backgroundColor","--hover-bg-color":"hoverBackgroundColor","--click-bg-color":"clickBackgroundColor","--active-bg-color":"activeBackgroundColor","--muted-bg-color":"mutedBackgroundColor","--border-color":"borderColor","--color":"color","--active-color":"activeColor"};static setRootStyles(t){const e=document.documentElement.style;for(const[i,s]of Object.entries(this._styleMap))e.setProperty(i,t[s])}createWatermark(t,i,s){this.watermark?this.watermark.applyOptions({lines:[{text:t,color:s,fontSize:i}]}):this.watermark=e.createTextWatermark(this.chart.panes()[0],{horzAlign:"center",vertAlign:"center",lines:[{text:t,color:s,fontSize:i}]})}},t.HeatMapSeries=class{_renderer;constructor(){this._renderer=new Z}priceValueBuilder(t){if(t.cells.length<1)return[NaN];let e=1/0,i=-1/0;t.cells.forEach((t=>{t.lowi&&(i=t.high)}));return[e,i,e+(i-e)/2]}isWhitespace(t){return void 0===t.cells||t.cells.length<1}renderer(){return this._renderer}update(t,e){this._renderer.update(t,e)}defaultOptions(){return Y}},t.HorizontalLine=w,t.Legend=o,t.PartialPriceLine=class{_paneViews;_chart=null;_series=null;constructor(){this._paneViews=[new Q]}attached({chart:t,series:e}){this._chart=t,this._series=e,this._series.applyOptions({priceLineVisible:!1})}detached(){this._chart=null,this._series=null}updateAllViews(){if(!this._series||!this._chart)return;const t=this._series.options();let i=t.priceLineColor||t.color||"#000000";const s=this._series.dataByIndex(1e5,e.MismatchDirection.NearestLeft);let o=null,n=null;var a;s&&(void 0!==s.color&&(i=s.color),o=void 0!==(a=s).value?a.value:void 0!==a.close?a.close:null,n=this._chart.timeScale().timeToCoordinate(s.time));const r=null!==o?this._series.priceToCoordinate(o):null;this._paneViews.forEach((t=>t.update(r,i,n)))}paneViews(){return this._paneViews}},t.PositionTool=class extends a{_options;_autoEndTime=null;_hovered=!1;_lastBounds=null;_paneViews;_subscribed=!1;constructor(t){super(),this._options={...pt,...t,endTime:t.endTime??null},this._paneViews=[new mt(this)]}attached(t){super.attached(t),this._subscribed||(this.chart.subscribeCrosshairMove(this._crosshairMoveHandler),this._subscribed=!0)}detached(){this._subscribed&&(this.chart.unsubscribeCrosshairMove(this._crosshairMoveHandler),this._subscribed=!1),super.detached()}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}autoscaleInfo(t,e){const{entry:i,stop:s,target:o}=this._options;return{priceRange:{minValue:Math.min(i,s,o),maxValue:Math.max(i,s,o)}}}applyOptions(t){this._options={...this._options,...t,endTime:void 0!==t.endTime?t.endTime??null:this._options.endTime},this.requestUpdate()}dataUpdated(t){const e=this.series.data();e.length>0&&(this._autoEndTime=e[e.length-1].time),this.requestUpdate()}_crosshairMoveHandler=t=>{const e=this._lastBounds;if(!e||!t.point)return void(this._hovered&&(this._hovered=!1,this.requestUpdate()));const{x:i,y:s}=t.point,o=i>=e.entryX&&i<=e.endX&&s>=e.minY&&s<=e.maxY;o!==this._hovered&&(this._hovered=o,this.requestUpdate())}},t.RayLine=R,t.SessionHighlighting=class extends a{_paneViews;_seriesData=[];_backgroundColors=[];_options;_highlighter;constructor(t,e={}){super(),this._highlighter=t,this._options={...it,...e},this._paneViews=[new et(this)]}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}attached(t){super.attached(t),this.dataUpdated("full")}dataUpdated(t){this._backgroundColors=this.series.data().map((t=>({time:t.time,color:this._highlighter(t.time)}))),this.requestUpdate()}},t.SubChartManager=class{_tabBar;_tabs=new Map;_activeId="";constructor(){this._tabBar=document.createElement("div"),this._tabBar.classList.add("subchart-tab-bar"),window.containerDiv.prepend(this._tabBar)}register(t,e,i){const s=document.createElement("button");if(s.classList.add("subchart-tab"),s.textContent=e,s.addEventListener("click",(()=>this.showTab(t))),this._tabBar.appendChild(s),this._tabs.set(t,{handler:i,tabEl:s}),1===this._tabs.size)s.classList.add("subchart-tab-active"),this._activeId=t,i.reSize();else{i.wrapper.style.display="none";const t=this._tabs.get(this._activeId);t?.handler.reSize()}}showTab(t){this._tabs.forEach((({handler:t,tabEl:e})=>{t.wrapper.style.display="none",e.classList.remove("subchart-tab-active")}));const e=this._tabs.get(t);e&&(e.handler.wrapper.style.display="flex",e.tabEl.classList.add("subchart-tab-active"),this._activeId=t,e.handler.reSize())}updateLabel(t,e){const i=this._tabs.get(t);i&&(i.tabEl.textContent=e)}get activeId(){return this._activeId}},t.Table=class{_div;callbackName;borderColor;borderWidth;table;rows={};headings;widths;alignments;footer;header;constructor(t,e,i,s,o,n,a=!1,r,l,h,c,d){this._div=document.createElement("div"),this.callbackName=null,this.borderColor=l,this.borderWidth=h,a?(this._div.style.position="absolute",this._div.style.cursor="move"):(this._div.style.position="absolute","right"===n?this._div.style.right="0px":this._div.style.left="0px",this._div.style.top="0px"),this._div.style.zIndex="2000",this.reSize(t,e),this._div.style.display="flex",this._div.style.flexDirection="column",this._div.style.borderRadius="5px",this._div.style.color="white",this._div.style.fontSize="12px",this._div.style.fontVariantNumeric="tabular-nums",this.table=document.createElement("table"),this.table.style.width="100%",this.table.style.borderCollapse="collapse",this._div.style.overflow="hidden",this.headings=i,this.widths=s.map((t=>100*t+"%")),this.alignments=o;let p=this.table.createTHead().insertRow();for(let t=0;t0?d[t]:r,e.style.color=c[t],p.appendChild(e)}let u,_,m=document.createElement("div");if(m.style.overflowY="auto",m.style.overflowX="hidden",m.style.backgroundColor=r,m.appendChild(this.table),this._div.appendChild(m),window.containerDiv.appendChild(this._div),!a)return;let b=t=>{this._div.style.left=t.clientX-u+"px",this._div.style.top=t.clientY-_+"px"},g=()=>{document.removeEventListener("mousemove",b),document.removeEventListener("mouseup",g)};this._div.addEventListener("mousedown",(t=>{u=t.clientX-this._div.offsetLeft,_=t.clientY-this._div.offsetTop,document.addEventListener("mousemove",b),document.addEventListener("mouseup",g)}))}divToButton(t,e){t.addEventListener("mouseover",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)")),t.addEventListener("mouseout",(()=>t.style.backgroundColor="transparent")),t.addEventListener("mousedown",(()=>t.style.backgroundColor="rgba(60, 60, 60)")),t.addEventListener("click",(()=>window.callbackFunction(e))),t.addEventListener("mouseup",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)"))}newRow(t,e=!1){let i=this.table.insertRow();i.style.cursor="default";for(let s=0;st.update(this._data)))}setData(t){this._data=t,this.updateAllViews(),this._attachedParams?.requestUpdate()}currentColor(){return this._options.lineColor}chart(){return this._attachedParams?.chart}series(){return this._attachedParams?.series}applyOptions(t){this._options={...this._options,...t},this._tooltip&&this._tooltip.applyOptions({...this._options.tooltip})}_setCrosshairMode(){const t=this.chart();if(!t)throw new Error("Unable to change crosshair mode because the chart instance is undefined");t.applyOptions({crosshair:{mode:e.CrosshairMode.Magnet,vertLine:{visible:!1,labelVisible:!1},horzLine:{visible:!1,labelVisible:!1}}})}_moveHandler=t=>this._onMouseMove(t);_hideTooltip(){this._tooltip&&(this._tooltip.updateTooltipContent({title:"",price:"",date:"",time:""}),this._tooltip.updatePosition({paneX:0,paneY:0,visible:!1}))}_hideCrosshair(){this._hideTooltip(),this.setData({x:0,visible:!1,color:this.currentColor(),topMargin:0})}_onMouseMove(t){const i=this.chart(),s=this.series(),o=t.logical;if(!o||!i||!s)return void this._hideCrosshair();const n=t.seriesData.get(s);if(!n)return void this._hideCrosshair();const a=this._options.priceExtractor(n),r=i.timeScale().logicalToCoordinate(o),[l,h]=function(t){if(!t)return["",""];const e=new Date(t),i=e.getFullYear(),s=e.toLocaleString("default",{month:"short"});return[`${e.getDate().toString().padStart(2,"0")} ${s} ${i}`,`${e.getHours().toString().padStart(2,"0")}:${e.getMinutes().toString().padStart(2,"0")}`]}(t.time?function(t){if(e.isUTCTimestamp(t))return 1e3*t;if(e.isBusinessDay(t))return new Date(t.year,t.month,t.day).valueOf();const[i,s,o]=t.split("-").map(parseInt);return new Date(i,s,o).valueOf()}(t.time):void 0);if(this._tooltip){const e=this._tooltip.options(),i="top"==e.followMode?e.topOffset+10:0;this.setData({x:r??0,visible:null!==r,color:this.currentColor(),topMargin:i}),this._tooltip.updateTooltipContent({price:a,date:l,time:h}),this._tooltip.updatePosition({paneX:t.point?.x??0,paneY:t.point?.y??0,visible:!0})}}_createTooltipElement(){const t=this.chart();if(!t)throw new Error("Unable to create Tooltip element. Chart not attached");this._tooltip=new ot(t,{...this._options.tooltip})}},t.TopBar=F,t.TrendLine=S,t.VerticalLine=N,t.VolumeProfile=class{_chart;_series;_vpData;_minPrice;_maxPrice;_paneViews;_vpIndex=null;constructor(t,e,i){this._chart=t,this._series=e,this._vpData=i,this._minPrice=1/0,this._maxPrice=-1/0,this._vpData.profile.forEach((t=>{t.pricethis._maxPrice&&(this._maxPrice=t.price)})),this._paneViews=[new dt(this)]}updateAllViews(){this._paneViews.forEach((t=>t.update()))}autoscaleInfo(t,e){const i=this._chart.timeScale().timeToCoordinate(this._vpData.time);if(null===i)return null;const s=this._chart.timeScale().coordinateToLogical(i);return null===s||es+this._vpData.width?null:{priceRange:{minValue:this._minPrice,maxValue:this._maxPrice}}}paneViews(){return this._paneViews}},t.globalParamInit=s,t.paneStyleDefault=i,t.setCursor=t=>{t&&(window.cursor=t),document.body.style.cursor=window.cursor},t}({},LightweightCharts); +var Lib=function(t,e){"use strict";const i={backgroundColor:"#0c0d0f",hoverBackgroundColor:"#3c434c",clickBackgroundColor:"#50565E",activeBackgroundColor:"rgba(0, 122, 255, 0.7)",mutedBackgroundColor:"rgba(0, 122, 255, 0.3)",borderColor:"#3C434C",color:"#d8d9db",activeColor:"#ececed"};function s(){window.pane={...i},window.containerDiv=document.getElementById("container")||document.createElement("div");const t=window.containerDiv;t.style.display="flex",t.style.flexDirection="column",t.style.width="100%",t.style.height="100%",t.style.minHeight="0",t.style.overflow="hidden",window.setCursor=t=>{t&&(window.cursor=t),document.body.style.cursor=window.cursor},window.cursor="default",window.textBoxFocused=!1}class o{handler;_paneIndex;_anchored=!1;div;seriesContainer;ohlcEnabled=!1;percentEnabled=!1;linesEnabled=!1;colorBasedOnCandle=!1;text;candle;_lines=[];constructor(t,e){this.legendHandler=this.legendHandler.bind(this),this._paneIndex=t,this.handler=e,this.div=document.createElement("div"),this.div.classList.add("legend"),this.div.style.display="none";const i=document.createElement("div");i.style.display="flex",i.style.flexDirection="row",this.seriesContainer=document.createElement("div"),this.seriesContainer.classList.add("series-container"),this.text=document.createElement("span"),this.text.style.lineHeight="1.8",this.candle=document.createElement("div"),i.appendChild(this.seriesContainer),this.div.appendChild(this.text),this.div.appendChild(this.candle),this.div.appendChild(i)}tryAnchorToPane(){if(this._anchored)return;const t=this.handler.chart.panes();if(this._paneIndex>=t.length)return;const e=t[this._paneIndex].getHTMLElement();if(!e)return;const i=e.children[1];if(!i)return;const s=i.children[0];s&&(s.appendChild(this.div),this._anchored=!0)}toJSON(){const{_lines:t,handler:e,...i}=this;return i}makeSeriesRow(t,e){this.tryAnchorToPane();const i="#FFF";let s=`\n \n \`\n `,o=`\n \n `,n=document.createElement("div");n.style.display="flex",n.style.alignItems="center";let a=document.createElement("div"),r=document.createElement("div");r.classList.add("legend-toggle-switch");let l=document.createElementNS("http://www.w3.org/2000/svg","svg");l.setAttribute("width","22"),l.setAttribute("height","16");let h=document.createElementNS("http://www.w3.org/2000/svg","g");h.innerHTML=s;let c=!0;r.addEventListener("click",(()=>{c?(c=!1,h.innerHTML=o,e.applyOptions({visible:!1})):(c=!0,e.applyOptions({visible:!0}),h.innerHTML=s)})),l.appendChild(h),r.appendChild(l);const d=document.createElement("div");d.classList.add("legend-settings-btn"),d.innerHTML='',d.title="Series settings",d.addEventListener("click",(()=>this.openSettingsModal(t,e)));const p=document.createElement("div");p.classList.add("legend-cross-btn"),p.innerHTML="✕",p.title="Remove series",p.addEventListener("click",(()=>{this.handler.chart.removeSeries(e),n.remove();const t=this._lines.findIndex((t=>t.series===e));-1!==t&&this._lines.splice(t,1)})),n.appendChild(a),n.appendChild(r),n.appendChild(d),n.appendChild(p),this.seriesContainer.appendChild(n),this._lines.push({name:t,div:a,row:n,toggle:r,series:e,solid:this.getSeriesDisplayColor(e)})}getSeriesDisplayColor(t){const e=t.seriesType();let i;return i="Area"===e?t.options().lineColor:"Baseline"===e?t.options().topLineColor:"Bar"===e||"Candlestick"===e?t.options().upColor:"Line"===e||"Histogram"===e?t.options().color:t.options().baseLineColor,i=i||"#2196f3",i.startsWith("rgba")?i.replace(/[^,]+(?=\))/,"1"):i}colorToHex(t){if(!t)return"#000000";if(t.startsWith("#"))return t.length>=7?t.slice(0,7):t;const e=t.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);return e?"#"+parseInt(e[1]).toString(16).padStart(2,"0")+parseInt(e[2]).toString(16).padStart(2,"0")+parseInt(e[3]).toString(16).padStart(2,"0"):"#000000"}getFieldGroups(t){const i=[{value:e.LineStyle.Solid,label:"Solid"},{value:e.LineStyle.Dotted,label:"Dotted"},{value:e.LineStyle.Dashed,label:"Dashed"},{value:e.LineStyle.LargeDashed,label:"Large Dashed"},{value:e.LineStyle.SparseDotted,label:"Sparse Dotted"}],s=[{value:e.LineType.Simple,label:"Simple"},{value:e.LineType.WithSteps,label:"With Steps"},{value:e.LineType.Curved,label:"Curved"}],o=[{value:e.LastPriceAnimationMode.Disabled,label:"Disabled"},{value:e.LastPriceAnimationMode.Continuous,label:"Continuous"},{value:e.LastPriceAnimationMode.OnDataUpdate,label:"On Data Update"}],n=[{key:"title",label:"Label",type:"text"},{key:"lastValueVisible",label:"Last Value Label",type:"boolean"},{key:"priceLineVisible",label:"Price Line",type:"boolean"},{key:"priceLineColor",label:"Price Line Color",type:"color"},{key:"priceLineWidth",label:"Price Line Width",type:"number",min:1,max:4,step:1},{key:"priceLineStyle",label:"Price Line Style",type:"select",options:i}],a=[{key:"crosshairMarkerVisible",label:"Visible",type:"boolean"},{key:"crosshairMarkerRadius",label:"Radius (px)",type:"number",min:1,max:20,step:1},{key:"crosshairMarkerBorderColor",label:"Border Color",type:"color"},{key:"crosshairMarkerBackgroundColor",label:"BG Color",type:"color"},{key:"crosshairMarkerBorderWidth",label:"Border Width",type:"number",min:1,max:6,step:1}];switch(t.seriesType()){case"Line":return[{title:"Line",fields:[{key:"color",label:"Color",type:"color"},{key:"lineWidth",label:"Width",type:"number",min:1,max:4,step:1},{key:"lineStyle",label:"Style",type:"select",options:i},{key:"lineType",label:"Type",type:"select",options:s},{key:"lineVisible",label:"Line Visible",type:"boolean"},{key:"pointMarkersVisible",label:"Point Markers",type:"boolean"},{key:"pointMarkersRadius",label:"Marker Radius (px)",type:"number",min:1,max:20,step:1},{key:"lastPriceAnimation",label:"Price Animation",type:"select",options:o}]},{title:"Crosshair Marker",fields:a},{title:"General",fields:n}];case"Area":return[{title:"Area",fields:[{key:"lineColor",label:"Line Color",type:"color"},{key:"topColor",label:"Top Fill",type:"color"},{key:"bottomColor",label:"Bottom Fill",type:"color"},{key:"lineWidth",label:"Line Width",type:"number",min:1,max:4,step:1},{key:"lineStyle",label:"Line Style",type:"select",options:i},{key:"lineType",label:"Line Type",type:"select",options:s},{key:"lineVisible",label:"Line Visible",type:"boolean"},{key:"invertFilledArea",label:"Invert Fill",type:"boolean"},{key:"relativeGradient",label:"Relative Gradient",type:"boolean"},{key:"pointMarkersVisible",label:"Point Markers",type:"boolean"},{key:"pointMarkersRadius",label:"Marker Radius (px)",type:"number",min:1,max:20,step:1},{key:"lastPriceAnimation",label:"Price Animation",type:"select",options:o}]},{title:"Crosshair Marker",fields:a},{title:"General",fields:n}];case"Bar":return[{title:"Bar",fields:[{key:"upColor",label:"Up Color",type:"color"},{key:"downColor",label:"Down Color",type:"color"},{key:"openVisible",label:"Show Open Tick",type:"boolean"},{key:"thinBars",label:"Thin Bars",type:"boolean"}]},{title:"General",fields:n}];case"Candlestick":return[{title:"Body",fields:[{key:"upColor",label:"Up Color",type:"color"},{key:"downColor",label:"Down Color",type:"color"}]},{title:"Borders",fields:[{key:"borderVisible",label:"Visible",type:"boolean"},{key:"borderColor",label:"Color",type:"color"},{key:"borderUpColor",label:"Up Color",type:"color"},{key:"borderDownColor",label:"Down Color",type:"color"}]},{title:"Wicks",fields:[{key:"wickVisible",label:"Visible",type:"boolean"},{key:"wickColor",label:"Color",type:"color"},{key:"wickUpColor",label:"Up Color",type:"color"},{key:"wickDownColor",label:"Down Color",type:"color"}]},{title:"General",fields:n}];case"Histogram":return[{title:"Histogram",fields:[{key:"color",label:"Color",type:"color"},{key:"base",label:"Base Level",type:"number",step:.01}]},{title:"General",fields:n}];case"Baseline":return[{title:"Top Area",fields:[{key:"topLineColor",label:"Line Color",type:"color"},{key:"topFillColor1",label:"Fill Color 1",type:"color"},{key:"topFillColor2",label:"Fill Color 2",type:"color"}]},{title:"Bottom Area",fields:[{key:"bottomLineColor",label:"Line Color",type:"color"},{key:"bottomFillColor1",label:"Fill Color 1",type:"color"},{key:"bottomFillColor2",label:"Fill Color 2",type:"color"}]},{title:"Line",fields:[{key:"lineWidth",label:"Width",type:"number",min:1,max:4,step:1},{key:"lineStyle",label:"Style",type:"select",options:i},{key:"lineType",label:"Type",type:"select",options:s},{key:"lineVisible",label:"Visible",type:"boolean"},{key:"relativeGradient",label:"Relative Gradient",type:"boolean"},{key:"pointMarkersVisible",label:"Point Markers",type:"boolean"},{key:"pointMarkersRadius",label:"Marker Radius (px)",type:"number",min:1,max:20,step:1},{key:"lastPriceAnimation",label:"Price Animation",type:"select",options:o}]},{title:"Crosshair Marker",fields:a},{title:"General",fields:n}];default:return[{title:"General",fields:n}]}}buildFieldRow(t,e,i){const s=document.createElement("div");s.classList.add("legend-modal-field");const o=document.createElement("label");let n;if(o.classList.add("legend-modal-field-label"),o.textContent=t.label,"color"===t.type){const t=document.createElement("input");t.type="color",t.value=this.colorToHex("string"==typeof e?e:""),t.classList.add("legend-modal-color"),t.addEventListener("input",(()=>i(t.value))),n=t}else if("number"===t.type){const s=document.createElement("input");s.type="number",void 0!==t.min&&(s.min=String(t.min)),void 0!==t.max&&(s.max=String(t.max)),void 0!==t.step&&(s.step=String(t.step)),s.value=void 0!==e?String(e):"",s.classList.add("legend-modal-input"),s.addEventListener("input",(()=>{const t=parseFloat(s.value);isNaN(t)||i(t)})),n=s}else if("boolean"===t.type){const t=document.createElement("input");t.type="checkbox",t.checked=!!e,t.classList.add("legend-modal-checkbox"),t.addEventListener("change",(()=>i(t.checked))),n=t}else if("select"===t.type){const s=document.createElement("select");s.classList.add("legend-modal-input","legend-modal-select");for(const i of t.options??[]){const t=document.createElement("option");t.value=String(i.value),t.textContent=i.label,String(e)===String(i.value)&&(t.selected=!0),s.appendChild(t)}s.addEventListener("change",(()=>i(Number(s.value)))),n=s}else{const t=document.createElement("input");t.type="text",t.value=String(e??""),t.classList.add("legend-modal-input"),t.addEventListener("input",(()=>i(t.value))),n=t}return s.appendChild(o),s.appendChild(n),s}openSettingsModal(t,e){const i=document.createElement("div");i.classList.add("legend-modal-backdrop"),i.addEventListener("click",(t=>{t.target===i&&i.remove()}));const s=document.createElement("div");s.classList.add("legend-modal"),s.addEventListener("click",(t=>t.stopPropagation()));const o=document.createElement("div");o.classList.add("legend-modal-header");const n=document.createElement("span");n.classList.add("legend-modal-title"),n.textContent=`${t} · ${e.seriesType()}`;const a=document.createElement("button");a.classList.add("legend-modal-close"),a.innerHTML="✕",a.title="Close",a.addEventListener("click",(()=>i.remove())),o.appendChild(n),o.appendChild(a);const r=document.createElement("div");r.classList.add("legend-modal-body");const l=this.getFieldGroups(e),h=e.options();for(const t of l){const i=document.createElement("div");i.classList.add("legend-modal-section");const s=document.createElement("div");s.classList.add("legend-modal-section-title"),s.textContent=t.title,i.appendChild(s);for(const s of t.fields){const t=this.buildFieldRow(s,h[s.key],(t=>{e.applyOptions({[s.key]:t});const i=this._lines.find((t=>t.series===e));i&&(i.solid=this.getSeriesDisplayColor(e))}));i.appendChild(t)}r.appendChild(i)}s.appendChild(o),s.appendChild(r),i.appendChild(s),document.body.appendChild(i)}legendItemFormat(t,e){return t.toFixed(e).toString().padStart(8," ")}shorthandFormat(t){const e=Math.abs(t);return e>=1e6?(t/1e6).toFixed(1)+"M":e>=1e3?(t/1e3).toFixed(1)+"K":t.toString().padStart(8," ")}legendHandler(t,e=!1){if(this.tryAnchorToPane(),!this.ohlcEnabled&&!this.linesEnabled&&!this.percentEnabled)return;const i=0===this._paneIndex;if(!t.time){if(i){const t=this.handler.series.options();this.candle.style.color="transparent",this.candle.innerHTML=this.candle.innerHTML.replace(t.upColor,"").replace(t.downColor,"")}if(!this.linesEnabled)return}let s,o=null;if(i&&t.time){const i=this.handler.series.options();if(e){const e=this.handler.chart.timeScale();let i=e.timeToCoordinate(t.time);i&&(o=e.coordinateToLogical(i.valueOf())),o&&(s=this.handler.series.dataByIndex(o.valueOf()))}else s=t.seriesData.get(this.handler.series);this.candle.style.color="";let n='';if(s){if(this.ohlcEnabled&&(n+=`O ${this.legendItemFormat(s.open,this.handler.precision)} `,n+=`| H ${this.legendItemFormat(s.high,this.handler.precision)} `,n+=`| L ${this.legendItemFormat(s.low,this.handler.precision)} `,n+=`| C ${this.legendItemFormat(s.close,this.handler.precision)} `),this.percentEnabled){let t=(s.close-s.open)/s.open*100,e=t>0?i.upColor:i.downColor,o=`${t>=0?"+":""}${t.toFixed(2)} %`;this.colorBasedOnCandle?n+=`| ${o}`:n+="| "+o}if(this.handler.volumeSeries){let e;e=o?this.handler.volumeSeries.dataByIndex(o):t.seriesData.get(this.handler.volumeSeries),e&&(n+=this.ohlcEnabled?`
V ${this.shorthandFormat(e.value)}`:"")}}this.candle.innerHTML=n+"
"}this._lines.forEach((i=>{if(!this.linesEnabled)return void(i.row.style.display="none");let s,n;if(i.row.style.display="flex",s=e&&o?i.series.dataByIndex(o):t.seriesData.get(i.series),s?.value){if("Histogram"==i.series.seriesType())n=this.shorthandFormat(s.value);else{const t=i.series.options().priceFormat;n=this.legendItemFormat(s.value,t.precision)}i.div.innerHTML=`▨ ${i.name} : ${n}`}}))}}function n(t){if(void 0===t)throw new Error("Value is undefined");return t}class a{_chart=void 0;_series=void 0;requestUpdate(){this._requestUpdate&&this._requestUpdate()}_requestUpdate;attached({chart:t,series:e,requestUpdate:i}){this._chart=t,this._series=e,this._series.subscribeDataChanged(this._fireDataUpdated),this._requestUpdate=i,this.requestUpdate()}detached(){this._series?.unsubscribeDataChanged(this._fireDataUpdated),this._chart=void 0,this._series=void 0,this._requestUpdate=void 0}get chart(){return n(this._chart)}get series(){return n(this._series)}_fireDataUpdated=t=>{this.dataUpdated&&this.dataUpdated(t)}}const r={lineColor:"#1E80F0",lineStyle:e.LineStyle.Solid,width:4,text:"",textColor:"",axisLabelVisible:!0,textPosition:"center",textHAlign:"center",textVAlign:"center"};var l;!function(t){t[t.NONE=0]="NONE",t[t.HOVERING=1]="HOVERING",t[t.DRAGGING=2]="DRAGGING",t[t.DRAGGINGP1=3]="DRAGGINGP1",t[t.DRAGGINGP2=4]="DRAGGINGP2",t[t.DRAGGINGP3=5]="DRAGGINGP3",t[t.DRAGGINGP4=6]="DRAGGINGP4"}(l||(l={}));class h extends a{_paneViews=[];_options;_points=[];_state=l.NONE;_startDragPoint=null;_latestHoverPoint=null;static _mouseIsDown=!1;static hoveredObject=null;static lastHoveredObject=null;_listeners=[];constructor(t){super(),this._options={...r,...t}}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}applyOptions(t){this._options={...this._options,...t},this.requestUpdate()}updatePoints(...t){for(let e=0;ei.name===t&&i.listener===e));this._listeners.splice(this._listeners.indexOf(i),1)}_handleHoverInteraction(t){if(this._latestHoverPoint=t.point,h._mouseIsDown)this._handleDragInteraction(t);else if(this._mouseIsOverDrawing(t)){if(this._state!=l.NONE)return;this._moveToState(l.HOVERING),h.hoveredObject=h.lastHoveredObject=this}else{if(this._state==l.NONE)return;this._moveToState(l.NONE),h.hoveredObject===this&&(h.hoveredObject=null)}}static _eventToPoint(t,e){if(!e||!t.point||!t.logical)return null;const i=e.coordinateToPrice(t.point.y);return null==i?null:{time:t.time||null,logical:t.logical,price:i.valueOf()}}static _getDiff(t,e){return{logical:t.logical-e.logical,price:t.price-e.price}}_addDiffToPoint(t,e,i){t&&(t.logical=t.logical+e,t.price=t.price+i,t.time=this.series.dataByIndex(t.logical)?.time||null)}_handleMouseDownInteraction=()=>{h._mouseIsDown=!0,this._onMouseDown()};_handleMouseUpInteraction=()=>{h._mouseIsDown=!1,this._moveToState(l.HOVERING)};_handleDragInteraction(t){if(this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP1&&this._state!=l.DRAGGINGP2&&this._state!=l.DRAGGINGP3&&this._state!=l.DRAGGINGP4)return;const e=h._eventToPoint(t,this.series);if(!e)return;this._startDragPoint=this._startDragPoint||e;const i=h._getDiff(e,this._startDragPoint);this._onDrag(i),this.requestUpdate(),this._startDragPoint=e}}class c{_options;constructor(t){this._options=t}}class d extends c{_p1;_p2;_hovered;constructor(t,e,i,s){super(i),this._p1=t,this._p2=e,this._hovered=s}_getScaledCoordinates(t){return null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y?null:{x1:Math.round(this._p1.x*t.horizontalPixelRatio),y1:Math.round(this._p1.y*t.verticalPixelRatio),x2:Math.round(this._p2.x*t.horizontalPixelRatio),y2:Math.round(this._p2.y*t.verticalPixelRatio)}}_drawEndCircle(t,e,i){t.context.fillStyle="#000",t.context.beginPath(),t.context.arc(e,i,9,0,2*Math.PI),t.context.stroke(),t.context.fill()}}function p(t,i){const s={[e.LineStyle.Solid]:[],[e.LineStyle.Dotted]:[t.lineWidth,t.lineWidth],[e.LineStyle.Dashed]:[2*t.lineWidth,2*t.lineWidth],[e.LineStyle.LargeDashed]:[6*t.lineWidth,6*t.lineWidth],[e.LineStyle.SparseDotted]:[t.lineWidth,4*t.lineWidth]}[i];t.setLineDash(s)}function u(t,e){return t||e}function _(t,e,i,s,o,n,a,r){s&&(t.save(),t.font=`${n}px -apple-system, BlinkMacSystemFont, sans-serif`,t.fillStyle=o,t.textAlign=a,t.textBaseline=r,t.fillText(s,e,i),t.restore())}class m extends c{_point={x:null,y:null};constructor(t,e){super(e),this._point=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null==this._point.y)return;const e=t.context,i=Math.round(this._point.y*t.verticalPixelRatio),s=this._point.x?this._point.x*t.horizontalPixelRatio:0;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.beginPath(),e.moveTo(s,i),e.lineTo(t.bitmapSize.width,i),e.stroke();const o=6*t.verticalPixelRatio,n=Math.round(12*t.verticalPixelRatio),a=u(this._options.textColor,this._options.lineColor),r=(s+t.bitmapSize.width)/2,l="below"!==this._options.textPosition;_(e,r,l?i-o:i+o,this._options.text,a,n,"center",l?"bottom":"top")}))}}class b{_source;constructor(t){this._source=t}}class g extends b{_p1={x:null,y:null};_p2={x:null,y:null};_source;constructor(t){super(t),this._source=t}update(){if(!this._source.p1||!this._source.p2)return;const t=this._source.series,e=t.priceToCoordinate(this._source.p1.price),i=t.priceToCoordinate(this._source.p2.price),s=this._getX(this._source.p1),o=this._getX(this._source.p2);this._p1={x:s,y:e},this._p2={x:o,y:i}}_getX(t){return this._source.chart.timeScale().logicalToCoordinate(t.logical)}}class y extends b{_source;_point={x:null,y:null};constructor(t){super(t),this._source=t}update(){const t=this._source._point,e=this._source.chart.timeScale(),i=this._source.series;"RayLine"==this._source._type&&(this._point.x=t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical)),this._point.y=i.priceToCoordinate(t.price)}renderer(){return new m(this._point,this._source._options)}}class v{_source;_y=null;_price=null;constructor(t){this._source=t}update(){if(!this._source.series||!this._source._point)return;this._y=this._source.series.priceToCoordinate(this._source._point.price);const t=this._source.series.options().priceFormat.precision;this._price=this._source._point.price.toFixed(t).toString()}visible(){return this._source._options.axisLabelVisible??!0}tickVisible(){return!0}coordinate(){return this._y??0}text(){return this._price||""}textColor(){return"white"}backColor(){return this._source._options.lineColor}}class w extends h{_type="HorizontalLine";_paneViews;_point;_callbackName;_priceAxisViews;_startDragPoint=null;constructor(t,e,i=null){super(e),this._point=t,this._point.time=null,this._paneViews=[new y(this)],this._priceAxisViews=[new v(this)],this._callbackName=i}get points(){return[this._point]}updatePoints(...t){for(const e of t)e&&(this._point.price=e.price);this.requestUpdate()}updateAllViews(){this._paneViews.forEach((t=>t.update())),this._priceAxisViews.forEach((t=>t.update()))}priceAxisViews(){return this._priceAxisViews}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._unsubscribe("mouseup",this._childHandleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._childHandleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._addDiffToPoint(this._point,0,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price);return!!i&&Math.abs(i-t.point.y){this._handleMouseUpInteraction(),this._callbackName&&window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`)}}class x{_chart;_series;_finishDrawingCallback=null;_drawings=[];_activeDrawing=null;_isDrawing=!1;_drawingType=null;constructor(t,e,i=null){this._chart=t,this._series=e,this._finishDrawingCallback=i,this._chart.subscribeClick(this._clickHandler),this._chart.subscribeCrosshairMove(this._moveHandler)}_clickHandler=t=>this._onClick(t);_moveHandler=t=>this._onMouseMove(t);beginDrawing(t){this._drawingType=t,this._isDrawing=!0}stopDrawing(){this._isDrawing=!1,this._activeDrawing=null}get drawings(){return this._drawings}addNewDrawing(t){this._series.attachPrimitive(t),this._drawings.push(t)}delete(t){if(null==t)return;const e=this._drawings.indexOf(t);-1!=e&&(this._drawings.splice(e,1),t.detach())}clearDrawings(){for(const t of this._drawings)t.detach();this._drawings=[]}repositionOnTime(){for(const t of this.drawings){const e=[];for(const i of t.points){if(!i){e.push(i);continue}const t=i.time?this._chart.timeScale().coordinateToLogical(this._chart.timeScale().timeToCoordinate(i.time)||0):i.logical;e.push({time:i.time,logical:t,price:i.price})}t.updatePoints(...e)}}_onClick(t){if(!this._isDrawing)return;const e=h._eventToPoint(t,this._series);if(e)if(null==this._activeDrawing){if(null==this._drawingType)return;this._activeDrawing=new this._drawingType(e,e),this._series.attachPrimitive(this._activeDrawing),this._drawingType==w&&this._onClick(t)}else{if(this._drawings.push(this._activeDrawing),this.stopDrawing(),!this._finishDrawingCallback)return;this._finishDrawingCallback()}}_onMouseMove(t){if(!t)return;for(const e of this._drawings)e._handleHoverInteraction(t);if(!this._isDrawing||!this._activeDrawing)return;const e=h._eventToPoint(t,this._series);e&&this._activeDrawing.updatePoints(null,e)}}class f extends d{constructor(t,e,i,s){super(t,e,i,s)}draw(t){t.useBitmapCoordinateSpace((t=>{if(null===this._p1.x||null===this._p1.y||null===this._p2.x||null===this._p2.y)return;const e=t.context,i=this._getScaledCoordinates(t);if(!i)return;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.beginPath(),e.moveTo(i.x1,i.y1),e.lineTo(i.x2,i.y2),e.stroke();const s=6*t.horizontalPixelRatio,o=Math.round(12*t.verticalPixelRatio),n=u(this._options.textColor,this._options.lineColor),a=i.x2-i.x1,r=i.y2-i.y1,l=Math.sqrt(a*a+r*r)||1,h=-r/l,c=a/l;let d,m;switch(this._options.textPosition){case"start":d=i.x1+h*s,m=i.y1+c*s;break;case"end":d=i.x2+h*s,m=i.y2+c*s;break;default:d=(i.x1+i.x2)/2+h*s,m=(i.y1+i.y2)/2+c*s}_(e,d,m,this._options.text,n,o,"center","middle"),this._hovered&&(this._drawEndCircle(t,i.x1,i.y1),this._drawEndCircle(t,i.x2,i.y2))}))}}class C extends g{constructor(t){super(t)}renderer(){return new f(this._p1,this._p2,this._source._options,this._source.hovered)}}class k extends h{_paneViews=[];_hovered=!1;constructor(t,e,i){super(),this.points.push(t),this.points.push(e),this._options={...r,...i}}setFirstPoint(t){this.updatePoints(t)}setSecondPoint(t){this.updatePoints(null,t)}get p1(){return this.points[0]}get p2(){return this.points[1]}get hovered(){return this._hovered}}class L extends k{_type="TrendLine";constructor(t,e,i){super(t,e,i),this._paneViews=[new C(this)]}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._hovered=!1,this.requestUpdate(),this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._hovered=!0,this.requestUpdate(),this._subscribe("mousedown",this._handleMouseDownInteraction),this._unsubscribe("mouseup",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGINGP1:case l.DRAGGINGP2:case l.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP1||this._addDiffToPoint(this.p1,t.logical,t.price),this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP2||this._addDiffToPoint(this.p2,t.logical,t.price)}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint;if(!t)return;const e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(l.DRAGGING);Math.abs(t.x-e.x)<10&&Math.abs(t.y-e.y)<10?this._moveToState(l.DRAGGINGP1):Math.abs(t.x-i.x)<10&&Math.abs(t.y-i.y)<10?this._moveToState(l.DRAGGINGP2):this._moveToState(l.DRAGGING)}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this._paneViews[0]._p1.x,s=this._paneViews[0]._p1.y,o=this._paneViews[0]._p2.x,n=this._paneViews[0]._p2.y;if(!(i&&o&&s&&n))return!1;const a=t.point.x,r=t.point.y;if(a<=Math.min(i,o)-e||a>=Math.max(i,o)+e)return!1;return Math.abs((n-s)*a-(o-i)*r+o*s-n*i)/Math.sqrt((n-s)**2+(o-i)**2)<=e}}class S extends d{constructor(t,e,i,s){super(t,e,i,s)}draw(t){t.useBitmapCoordinateSpace((t=>{const e=t.context,i=this._getScaledCoordinates(t);if(!i)return;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.fillStyle=this._options.fillColor;const s=Math.min(i.x1,i.x2),o=Math.min(i.y1,i.y2),n=Math.abs(i.x1-i.x2),a=Math.abs(i.y1-i.y2);e.strokeRect(s,o,n,a),e.fillRect(s,o,n,a);const r=6*t.horizontalPixelRatio,l=Math.round(12*t.verticalPixelRatio),h=u(this._options.textColor,this._options.lineColor),c=s+n/2,d=o+a/2;if("outside"===(this._options.textPlacement??"outside"))switch(this._options.textPosition){case"left":_(e,s-r,d,this._options.text,h,l,"right","middle");break;case"right":_(e,s+n+r,d,this._options.text,h,l,"left","middle");break;case"top":default:_(e,c,o-r,this._options.text,h,l,"center","bottom");break;case"bottom":_(e,c,o+a+r,this._options.text,h,l,"center","top")}else switch(this._options.textPosition){case"left":_(e,s+r,d,this._options.text,h,l,"left","middle");break;case"right":_(e,s+n-r,d,this._options.text,h,l,"right","middle");break;case"top":_(e,c,o+r,this._options.text,h,l,"center","top");break;case"bottom":_(e,c,o+a-r,this._options.text,h,l,"center","bottom");break;default:_(e,c,d,this._options.text,h,l,"center","middle")}this._hovered&&(this._drawEndCircle(t,s,o),this._drawEndCircle(t,s+n,o),this._drawEndCircle(t,s+n,o+a),this._drawEndCircle(t,s,o+a))}))}}class D extends g{constructor(t){super(t)}renderer(){return new S(this._p1,this._p2,this._source._options,this._source.hovered)}}const E={fillEnabled:!0,fillColor:"rgba(255, 255, 255, 0.2)",textPlacement:"outside",...r};class M extends k{_type="Box";constructor(t,e,i){super(t,e,i),this._options={...E,...i},this._paneViews=[new D(this)]}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._hovered=!1,this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._hovered=!0,this._unsubscribe("mouseup",this._handleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGINGP1:case l.DRAGGINGP2:case l.DRAGGINGP3:case l.DRAGGINGP4:case l.DRAGGING:document.body.style.cursor="grabbing",document.body.addEventListener("mouseup",this._handleMouseUpInteraction),this._subscribe("mouseup",this._handleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP1||this._addDiffToPoint(this.p1,t.logical,t.price),this._state!=l.DRAGGING&&this._state!=l.DRAGGINGP2||this._addDiffToPoint(this.p2,t.logical,t.price),this._state!=l.DRAGGING&&(this._state==l.DRAGGINGP3&&(this._addDiffToPoint(this.p1,t.logical,0),this._addDiffToPoint(this.p2,0,t.price)),this._state==l.DRAGGINGP4&&(this._addDiffToPoint(this.p1,0,t.price),this._addDiffToPoint(this.p2,t.logical,0)))}_onMouseDown(){this._startDragPoint=null;const t=this._latestHoverPoint,e=this._paneViews[0]._p1,i=this._paneViews[0]._p2;if(!(e.x&&i.x&&e.y&&i.y))return this._moveToState(l.DRAGGING);const s=10;Math.abs(t.x-e.x)l-p&&ah-p&&ri.appendChild(this.makeColorBox(t))));let s=document.createElement("div");s.style.backgroundColor=window.pane.borderColor,s.style.height="1px",s.style.width="130px";let o=document.createElement("div");o.style.margin="10px";let n=document.createElement("div");n.style.color="lightgray",n.style.fontSize="12px",n.innerText="Opacity",this._opacityLabel=document.createElement("div"),this._opacityLabel.style.color="lightgray",this._opacityLabel.style.fontSize="12px",this._opacitySlider=document.createElement("input"),this._opacitySlider.type="range",this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%",this._opacitySlider.oninput=()=>{this._opacityLabel.innerText=this._opacitySlider.value+"%",this.opacity=parseInt(this._opacitySlider.value)/100,this.updateColor()},o.appendChild(n),o.appendChild(this._opacitySlider),o.appendChild(this._opacityLabel),this._div.appendChild(i),this._div.appendChild(s),this._div.appendChild(o),window.containerDiv.appendChild(this._div)}_updateOpacitySlider(){this._opacitySlider.value=(100*this.opacity).toString(),this._opacityLabel.innerText=this._opacitySlider.value+"%"}makeColorBox(t){const e=document.createElement("div");e.style.width="18px",e.style.height="18px",e.style.borderRadius="3px",e.style.margin="3px",e.style.boxSizing="border-box",e.style.backgroundColor=t,e.addEventListener("mouseover",(()=>e.style.border="2px solid lightgray")),e.addEventListener("mouseout",(()=>e.style.border="none"));const i=T.extractRGBA(t);return e.addEventListener("click",(()=>{this.rgba=i,this.updateColor()})),e}static extractRGBA(t){const e=document.createElement("div");e.style.color=t,document.body.appendChild(e);const i=getComputedStyle(e).color;document.body.removeChild(e);const s=i.match(/\d+/g)?.map(Number);if(!s)return[];let o=i.includes("rgba")?parseFloat(i.split(",")[3]):1;return[s[0],s[1],s[2],o]}updateColor(){if(!h.lastHoveredObject||!this.rgba)return;const t=`rgba(${this.rgba[0]}, ${this.rgba[1]}, ${this.rgba[2]}, ${this.opacity})`;h.lastHoveredObject.applyOptions({[this.colorOption]:t}),this.saveDrawings()}openMenu(t){h.lastHoveredObject&&(this.rgba=T.extractRGBA(h.lastHoveredObject._options[this.colorOption]),this.opacity=this.rgba[3],this._updateOpacitySlider(),this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="flex",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10))}closeMenu(){document.body.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}class P{static _styles=[{name:"Solid",var:e.LineStyle.Solid},{name:"Dotted",var:e.LineStyle.Dotted},{name:"Dashed",var:e.LineStyle.Dashed},{name:"Large Dashed",var:e.LineStyle.LargeDashed},{name:"Sparse Dotted",var:e.LineStyle.SparseDotted}];_div;_saveDrawings;constructor(t){this._saveDrawings=t,this._div=document.createElement("div"),this._div.classList.add("context-menu"),P._styles.forEach((t=>{this._div.appendChild(this._makeTextBox(t.name,t.var))})),window.containerDiv.appendChild(this._div)}_makeTextBox(t,e){const i=document.createElement("span");return i.classList.add("context-menu-item"),i.innerText=t,i.addEventListener("click",(()=>{h.lastHoveredObject?.applyOptions({lineStyle:e}),this._saveDrawings()})),i}openMenu(t){this._div.style.top=t.top-30+"px",this._div.style.left=t.right+"px",this._div.style.display="block",setTimeout((()=>document.addEventListener("mousedown",(t=>{this._div.contains(t.target)||this.closeMenu()}))),10)}closeMenu(){document.removeEventListener("click",this.closeMenu),this._div.style.display="none"}}function I(t){const e=[];for(const i of t)0==e.length?e.push(i.toUpperCase()):i==i.toUpperCase()?e.push(" "+i):e.push(i);return e.join("")}class G{saveDrawings;drawingTool;div;hoverItem;items=[];constructor(t,e){this.saveDrawings=t,this.drawingTool=e,this._onRightClick=this._onRightClick.bind(this),this.div=document.createElement("div"),this.div.classList.add("context-menu"),document.body.appendChild(this.div),this.hoverItem=null,document.body.addEventListener("contextmenu",this._onRightClick)}_handleClick=t=>this._onClick(t);_onClick(t){t.target&&(this.div.contains(t.target)||(this.div.style.display="none",document.body.removeEventListener("click",this._handleClick)))}_onRightClick(t){if(!h.hoveredObject)return;for(const t of this.items)this.div.removeChild(t);this.items=[];for(const t of Object.keys(h.hoveredObject._options)){let e;if(t.toLowerCase().includes("color"))e=new T(this.saveDrawings,t);else{if("lineStyle"!==t)continue;e=new P(this.saveDrawings)}let i=t=>e.openMenu(t);this.menuItem(I(t),i,(()=>{document.removeEventListener("click",e.closeMenu),e._div.style.display="none"}))}this.separator(),this.menuItem("Delete Drawing",(()=>this.drawingTool.delete(h.lastHoveredObject))),t.preventDefault(),this.div.style.left=t.clientX+"px",this.div.style.top=t.clientY+"px",this.div.style.display="block",document.body.addEventListener("click",this._handleClick)}menuItem(t,e,i=null){const s=document.createElement("span");s.classList.add("context-menu-item"),this.div.appendChild(s);const o=document.createElement("span");if(o.innerText=t,o.style.pointerEvents="none",s.appendChild(o),i){let t=document.createElement("span");t.innerText="►",t.style.fontSize="8px",t.style.pointerEvents="none",s.appendChild(t)}if(s.addEventListener("mouseover",(()=>{this.hoverItem&&this.hoverItem.closeAction&&this.hoverItem.closeAction(),this.hoverItem={elem:o,action:e,closeAction:i}})),i){let t;s.addEventListener("mouseover",(()=>t=setTimeout((()=>e(s.getBoundingClientRect())),100))),s.addEventListener("mouseout",(()=>clearTimeout(t)))}else s.addEventListener("click",(t=>{e(t),this.div.style.display="none"}));this.items.push(s)}separator(){const t=document.createElement("div");t.style.width="90%",t.style.height="1px",t.style.margin="3px 0px",t.style.backgroundColor=window.pane.borderColor,this.div.appendChild(t),this.items.push(t)}}class R extends w{_type="RayLine";constructor(t,e){super({...t},e),this._point.time=t.time}updatePoints(...t){for(const e of t)e&&(this._point=e);this.requestUpdate()}_onDrag(t){this._addDiffToPoint(this._point,t.logical,t.price),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.series.priceToCoordinate(this._point.price),s=this._point.time?this.chart.timeScale().timeToCoordinate(this._point.time):null;return!(!i||!s)&&(Math.abs(i-t.point.y)s-e)}}class B extends c{_point={x:null,y:null};constructor(t,e){super(e),this._point=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null==this._point.x)return;const e=t.context,i=this._point.x*t.horizontalPixelRatio;e.lineWidth=this._options.width,e.strokeStyle=this._options.lineColor,p(e,this._options.lineStyle),e.beginPath(),e.moveTo(i,0),e.lineTo(i,t.bitmapSize.height),e.stroke();const s=6*t.horizontalPixelRatio,o=Math.round(12*t.verticalPixelRatio),n=u(this._options.textColor,this._options.lineColor),a=t.bitmapSize.height;let r,l,h,c;switch(this._options.textVAlign){case"top":r=s,l="top";break;case"bottom":r=a-s,l="bottom";break;default:r=a/2,l="middle"}switch(this._options.textHAlign){case"left":h=i-s,c="right";break;case"right":h=i+s,c="left";break;default:h=i,c="center"}_(e,h,r,this._options.text,n,o,c,l)}))}}class V extends b{_source;_point={x:null,y:null};constructor(t){super(t),this._source=t}update(){const t=this._source._point,e=this._source.chart.timeScale(),i=this._source.series;this._point.x=t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical),this._point.y=i.priceToCoordinate(t.price)}renderer(){return new B(this._point,this._source._options)}}class A{_source;_x=null;constructor(t){this._source=t}update(){if(!this._source.chart||!this._source._point)return;const t=this._source._point,e=this._source.chart.timeScale();this._x=t.time?e.timeToCoordinate(t.time):e.logicalToCoordinate(t.logical)}visible(){return!0}tickVisible(){return!0}coordinate(){return this._x??0}text(){return""}textColor(){return"white"}backColor(){return this._source._options.lineColor}}class N extends h{_type="VerticalLine";_paneViews;_timeAxisViews;_point;_callbackName;_startDragPoint=null;constructor(t,e,i=null){super(e),this._point=t,this._paneViews=[new V(this)],this._callbackName=i,this._timeAxisViews=[new A(this)]}updateAllViews(){this._paneViews.forEach((t=>t.update())),this._timeAxisViews.forEach((t=>t.update()))}timeAxisViews(){return this._timeAxisViews}updatePoints(...t){for(const e of t)e&&(!e.time&&e.logical&&(e.time=this.series.dataByIndex(e.logical)?.time||null),this._point=e);this.requestUpdate()}get points(){return[this._point]}_moveToState(t){switch(t){case l.NONE:document.body.style.cursor="default",this._unsubscribe("mousedown",this._handleMouseDownInteraction);break;case l.HOVERING:document.body.style.cursor="pointer",this._unsubscribe("mouseup",this._childHandleMouseUpInteraction),this._subscribe("mousedown",this._handleMouseDownInteraction),this.chart.applyOptions({handleScroll:!0});break;case l.DRAGGING:document.body.style.cursor="grabbing",this._subscribe("mouseup",this._childHandleMouseUpInteraction),this.chart.applyOptions({handleScroll:!1})}this._state=t}_onDrag(t){this._addDiffToPoint(this._point,t.logical,0),this.requestUpdate()}_mouseIsOverDrawing(t,e=4){if(!t.point)return!1;const i=this.chart.timeScale();let s;return s=this._point.time?i.timeToCoordinate(this._point.time):i.logicalToCoordinate(this._point.logical),!!s&&Math.abs(s-t.point.x){this._handleMouseUpInteraction(),this._callbackName&&window.callbackFunction(`${this._callbackName}_~_${this._point.price.toFixed(8)}`)}}class O{static TREND_SVG='';static HORZ_SVG='';static RAY_SVG='';static BOX_SVG='';static VERT_SVG=O.RAY_SVG;div;activeIcon=null;buttons=[];_commandFunctions;_handlerID;_drawingTool;constructor(t,e,i,s){this._handlerID=t,this._commandFunctions=s,this._drawingTool=new x(e,i,(()=>this.removeActiveAndSave())),this.div=this._makeToolBox(),new G(this.saveDrawings,this._drawingTool),s.push((t=>{if((t.metaKey||t.ctrlKey)&&"KeyZ"===t.code){const t=this._drawingTool.drawings.pop();return t&&this._drawingTool.delete(t),!0}return!1}))}toJSON(){const{...t}=this;return t}_makeToolBox(){let t=document.createElement("div");t.classList.add("toolbox"),this.buttons.push(this._makeToolBoxElement(L,"KeyT",O.TREND_SVG)),this.buttons.push(this._makeToolBoxElement(w,"KeyH",O.HORZ_SVG)),this.buttons.push(this._makeToolBoxElement(R,"KeyR",O.RAY_SVG)),this.buttons.push(this._makeToolBoxElement(M,"KeyB",O.BOX_SVG)),this.buttons.push(this._makeToolBoxElement(N,"KeyV",O.VERT_SVG,!0));for(const e of this.buttons)t.appendChild(e);return t}_makeToolBoxElement(t,e,i,s=!1){const o=document.createElement("div");o.classList.add("toolbox-button");const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","29"),n.setAttribute("height","29");const a=document.createElementNS("http://www.w3.org/2000/svg","g");a.innerHTML=i,a.setAttribute("fill",window.pane.color),n.appendChild(a),o.appendChild(n);const r={div:o,group:a,type:t};return o.addEventListener("click",(()=>this._onIconClick(r))),this._commandFunctions.push((t=>this._handlerID===window.handlerInFocus&&(!(!t.altKey||t.code!==e)&&(t.preventDefault(),this._onIconClick(r),!0)))),1==s&&(n.style.transform="rotate(90deg)",n.style.transformBox="fill-box",n.style.transformOrigin="center"),o}_onIconClick(t){this.activeIcon&&(this.activeIcon.div.classList.remove("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.stopDrawing(),this.activeIcon===t)?this.activeIcon=null:(this.activeIcon=t,this.activeIcon.div.classList.add("active-toolbox-button"),window.setCursor("crosshair"),this._drawingTool?.beginDrawing(this.activeIcon.type))}removeActiveAndSave=()=>{window.setCursor("default"),this.activeIcon&&this.activeIcon.div.classList.remove("active-toolbox-button"),this.activeIcon=null,this.saveDrawings()};addNewDrawing(t){this._drawingTool.addNewDrawing(t)}clearDrawings(){this._drawingTool.clearDrawings()}saveDrawings=()=>{const t=[];for(const e of this._drawingTool.drawings)t.push({type:e._type,points:e.points,options:e._options});const e=JSON.stringify(t);window.callbackFunction(`save_drawings${this._handlerID}_~_${e}`)};loadDrawings(t){t.forEach((t=>{switch(t.type){case"Box":this._drawingTool.addNewDrawing(new M(t.points[0],t.points[1],t.options));break;case"TrendLine":this._drawingTool.addNewDrawing(new L(t.points[0],t.points[1],t.options));break;case"HorizontalLine":this._drawingTool.addNewDrawing(new w(t.points[0],t.options));break;case"RayLine":this._drawingTool.addNewDrawing(new R(t.points[0],t.options));break;case"VerticalLine":this._drawingTool.addNewDrawing(new N(t.points[0],t.options))}}))}}class H{makeButton;callbackName;div;isOpen=!1;widget;constructor(t,e,i,s,o,n){this.makeButton=t,this.callbackName=e,this.div=document.createElement("div"),this.div.classList.add("topbar-menu"),this.widget=this.makeButton(s,null,o,!0,n),this.widget.elem.classList.add("menu-button"),this.updateMenuItems(i),this.widget.elem.addEventListener("click",(()=>{if(this.isOpen=!this.isOpen,!this.isOpen)return void(this.div.style.display="none");let t=this.widget.elem.getBoundingClientRect();this.div.style.display="flex",this.div.style.flexDirection="column";let e=t.x+t.width/2;this.div.style.left=e-this.div.clientWidth/2+"px",this.div.style.top=t.y+t.height+"px"})),document.body.appendChild(this.div)}updateMenuItems(t){this.div.innerHTML="",t.forEach((t=>{let e=this.makeButton(t,null,!1,!1);e.elem.addEventListener("click",(()=>{this._clickHandler(e.elem.innerText)})),this.div.appendChild(e.elem)})),this.widget.elem.innerText=t[0]}_clickHandler(t){this.widget.elem.innerText=t,window.callbackFunction(`${this.callbackName}_~_${t}`),this.div.style.display="none",this.isOpen=!1}}class F{_handler;_div;left;right;constructor(t){this._handler=t,this._div=document.createElement("div"),this._div.classList.add("topbar");const e=t=>{const e=document.createElement("div");return e.classList.add("topbar-container"),e.style.justifyContent=t,this._div.appendChild(e),e};this.left=e("flex-start"),this.right=e("flex-end")}makeSwitcher(t,e,i,s="left"){const o=document.createElement("div");let n;o.classList.add("topbar-switcher");const a={elem:o,callbackName:i,intervalElements:t.map((t=>{const i=document.createElement("button");i.classList.add("topbar-button"),i.classList.add("switcher-button"),i.style.margin="0px 2px",i.innerText=t,t==e&&(n=i,i.classList.add("active-switcher-button"));const s=F.getClientWidth(i);return i.style.minWidth=s+1+"px",i.addEventListener("click",(()=>a.onItemClicked(i))),o.appendChild(i),i})),setActive:t=>{const e=Array.from(o.children).find((e=>e.textContent===t));e&&e!=n&&(n.classList.remove("active-switcher-button"),e.classList.add("active-switcher-button"),n=e)},onItemClicked:t=>{t!=n&&(n.classList.remove("active-switcher-button"),t.classList.add("active-switcher-button"),n=t,window.callbackFunction(`${a.callbackName}_~_${t.innerText}`))}};return this.appendWidget(o,s,!0),a}makeTextBoxWidget(t,e="left",i=null){if(i){const s=document.createElement("input");return s.classList.add("topbar-textbox-input"),s.value=t,s.style.width=`${s.value.length+2}ch`,s.addEventListener("focus",(()=>{window.textBoxFocused=!0})),s.addEventListener("input",(t=>{t.preventDefault(),s.style.width=`${s.value.length+2}ch`})),s.addEventListener("keydown",(t=>{"Enter"==t.key&&(t.preventDefault(),s.blur())})),s.addEventListener("blur",(()=>{window.callbackFunction(`${i}_~_${s.value}`),window.textBoxFocused=!1})),this.appendWidget(s,e,!0),s}{const i=document.createElement("div");return i.classList.add("topbar-textbox"),i.innerText=t,this.appendWidget(i,e,!0),i}}makeMenu(t,e,i,s,o){return new H(this.makeButton.bind(this),s,t,e,i,o)}makeButton(t,e,i,s=!0,o="left",n=!1){let a=document.createElement("button");a.classList.add("topbar-button"),a.innerText=t,document.body.appendChild(a),a.style.minWidth=a.clientWidth+1+"px",document.body.removeChild(a);let r={elem:a,callbackName:e};if(e){let t;if(n){let e=!1;t=()=>{e=!e,window.callbackFunction(`${r.callbackName}_~_${e}`),a.style.backgroundColor=e?"var(--active-bg-color)":"",a.style.color=e?"var(--active-color)":""}}else t=()=>window.callbackFunction(`${r.callbackName}_~_${a.innerText}`);a.addEventListener("click",t)}return s&&this.appendWidget(a,o,i),r}makeSeparator(t="left"){const e=document.createElement("div");e.classList.add("topbar-seperator");("left"==t?this.left:this.right).appendChild(e)}appendWidget(t,e,i){const s="left"==e?this.left:this.right;i?("left"==e&&s.appendChild(t),this.makeSeparator(e),"right"==e&&s.appendChild(t)):s.appendChild(t),this._handler.reSize()}static getClientWidth(t){document.body.appendChild(t);const e=t.clientWidth;return document.body.removeChild(t),e}}s();class z{numbers;cache;constructor(t){this.numbers=t,this.cache=new Map}findClosestIndex(t,e){const i=`${t}:${e}`;if(this.cache.has(i))return this.cache.get(i);const s=this._performSearch(t,e);return this.cache.set(i,s),s}_performSearch(t,e){let i=0,s=this.numbers.length-1;if(0===this.numbers.length)return 0;if(t<=this.numbers[0].time)return 0;if(t>=this.numbers[s].time)return s;for(;i<=s;){const e=Math.floor((i+s)/2),o=this.numbers[e].time;if(o===t)return e;o>t?s=e-1:i=e+1}return"left"===e?i:s}}class ${_arr;_chunkSize;_cache;constructor(t,e=10){this._arr=t,this._chunkSize=e,this._cache=new Map}getMinMax(t,e){const i=`${t}:${e}`;if(i in this._cache)return this._cache.get(i);const s={lower:1/0,upper:-1/0},o=Math.floor(t/this._chunkSize),n=Math.floor(e/this._chunkSize);for(let t=o;t<=n;t++){const e=t*this._chunkSize,o=Math.min((t+1)*this._chunkSize-1,this._arr.length-1),n=`${e}:${o}`;if(n in this._cache.keys()){const t=this._cache.get(i);this._check(t,s)}else{const t={lower:1/0,upper:-1/0};for(let i=e;i<=o;i++)this._check(this._arr[i],t);this._cache.set(n,t),this._check(t,s)}}return this._cache.set(i,s),s}_check(t,e){t.lowere.upper&&(e.upper=t.upper)}}class W{_viewData;constructor(t){this._viewData=t}draw(){}drawBackground(t){const e=this._viewData.data;e.length&&t.useBitmapCoordinateSpace((t=>{const i=t.context;i.scale(t.horizontalPixelRatio,t.verticalPixelRatio),i.strokeStyle=this._viewData.options.lineColor,i.lineWidth=this._viewData.options.lineWidth,i.beginPath();const s=new Path2D,o=new Path2D;s.moveTo(e[0].x,e[0].upper),o.moveTo(e[0].x,e[0].upper);for(const t of e)s.lineTo(t.x,t.upper),o.lineTo(t.x,t.upper);const n=e.length-1;s.lineTo(e[n].x,e[n].lower),o.moveTo(e[n].x,e[n].lower);for(let t=e.length-2;t>=0;t--)s.lineTo(e[t].x,e[t].lower),o.lineTo(e[t].x,e[t].lower);s.lineTo(e[0].x,e[0].upper),s.closePath(),i.stroke(o),i.fillStyle=this._viewData.options.fillColor,i.fill(s)}))}}class U{_source;_data;constructor(t){this._source=t,this._data={data:[],options:this._source._options}}update(){const t=this._source.series,e=this._source.chart.timeScale();this._data.data=this._source._bandsData.map((i=>({x:e.timeToCoordinate(i.time)??-100,upper:t.priceToCoordinate(i.upper)??-100,lower:t.priceToCoordinate(i.lower)??-100})))}renderer(){return new W(this._data)}}const q={lineColor:"rgb(25, 200, 100)",fillColor:"rgba(25, 200, 100, 0.25)",lineWidth:0};const Y={...e.customSeriesDefaultOptions,lastValueVisible:!1,priceLineVisible:!1,cellShader:t=>{const e=Math.min(Math.max(0,t),100);return`rgba(0, ${100+1.55*e}, ${0+e}, ${.2+.8*e})`},cellBorderWidth:1,cellBorderColor:"transparent"};function j(t,e,i){const s=t-e,o=t+e,n=Math.round(s*i);return{position:n,length:Math.round(o*i)-n}}function X(t,e,i=1,s){const o=Math.round(e*t),n=s?i:Math.round(i*e),a=function(t){return Math.floor(.5*t)}(n);return{position:o-a,length:n}}function K(t,e,i){const s=Math.round(i*t),o=Math.round(i*e);return{position:Math.min(s,o),length:Math.abs(o-s)+1}}class Z{_data=null;_options=null;draw(t,e){t.useBitmapCoordinateSpace((t=>this._drawImpl(t,e)))}update(t,e){this._data=t,this._options=e}_drawImpl(t,e){if(null===this._data||0===this._data.bars.length||null===this._data.visibleRange||null===this._options)return;const i=this._options,s=this._data.bars.map((t=>({x:t.x,cells:t.originalData.cells.map((t=>({amount:t.amount,color:t.color,side:t.side,low:e(t.low),high:e(t.high)})))}))),o=this._data.barSpacing>3*i.cellBorderWidth;for(let e=this._data.visibleRange.from;e{if(null===this._price||null===this._x)return;const e=Math.round(this._x*t.horizontalPixelRatio),i=X(this._price,t.verticalPixelRatio,t.verticalPixelRatio),s=i.position+i.length/2,o=t.context;o.beginPath(),o.setLineDash([4*t.verticalPixelRatio,2*t.verticalPixelRatio]),o.moveTo(e,s),o.lineTo(t.bitmapSize.width,s),o.strokeStyle=this._color,o.lineWidth=t.verticalPixelRatio,o.stroke()}))}}class Q{_renderer;constructor(){this._renderer=new J}renderer(){return this._renderer}update(t,e,i){this._renderer.update(t,e,i)}}class tt{_viewData;constructor(t){this._viewData=t}draw(t){const e=this._viewData.data;t.useBitmapCoordinateSpace((t=>{const i=t.context,s=t.bitmapSize.height,o=t.horizontalPixelRatio*this._viewData.barWidth/2,n=-1*(o+1),a=t.bitmapSize.width;e.forEach((e=>{const r=e.x*t.horizontalPixelRatio;if(r({x:t.timeToCoordinate(e.time)??-100,color:e.color}))),this._data.data.length>1?this._data.barWidth=this._data.data[1].x-this._data.data[0].x:this._data.barWidth=6}renderer(){return new tt(this._data)}zOrder(){return"bottom"}}const it={};const st={title:"",followMode:"tracking",horizontalDeadzoneWidth:45,verticalDeadzoneHeight:100,verticalSpacing:20,topOffset:20};class ot{_chart;_element;_titleElement;_priceElement;_dateElement;_timeElement;_options;_lastTooltipWidth=null;constructor(t,e){this._options={...st,...e},this._chart=t;const i=document.createElement("div");at(i,{display:"flex","flex-direction":"column","align-items":"center",position:"absolute",transform:"translate(calc(0px - 50%), 0px)",opacity:"0",left:"0%",top:"0","z-index":"100","background-color":"white","border-radius":"4px",padding:"5px 10px","font-family":"-apple-system, BlinkMacSystemFont, 'Trebuchet MS', Roboto, Ubuntu, sans-serif","font-size":"12px","font-weight":"400","box-shadow":"0px 2px 4px rgba(0, 0, 0, 0.2)","line-height":"16px","pointer-events":"none",color:"#131722"});const s=document.createElement("div");at(s,{"font-size":"16px","line-height":"24px","font-weight":"590"}),nt(s,this._options.title),i.appendChild(s);const o=document.createElement("div");at(o,{"font-size":"14px","line-height":"18px","font-weight":"590"}),nt(o,""),i.appendChild(o);const n=document.createElement("div");at(n,{color:"#787B86"}),nt(n,""),i.appendChild(n);const a=document.createElement("div");at(a,{color:"#787B86"}),nt(a,""),i.appendChild(a),this._element=i,this._titleElement=s,this._priceElement=o,this._dateElement=n,this._timeElement=a;const r=this._chart.chartElement();r.appendChild(this._element);const l=r.parentElement;if(!l)return void console.error("Chart Element is not attached to the page.");const h=getComputedStyle(l).position;"relative"!==h&&"absolute"!==h&&console.error("Chart Element position is expected be `relative` or `absolute`.")}destroy(){this._chart&&this._element&&this._chart.chartElement().removeChild(this._element)}applyOptions(t){this._options={...this._options,...t}}options(){return this._options}updateTooltipContent(t){if(!this._element)return;const e=this._element.getBoundingClientRect();this._lastTooltipWidth=e.width,void 0!==t.title&&this._titleElement&&nt(this._titleElement,t.title),nt(this._priceElement,t.price),nt(this._dateElement,t.date),nt(this._timeElement,t.time)}updatePosition(t){if(!this._chart||!this._element)return;if(this._element.style.opacity=t.visible?"1":"0",!t.visible)return;const e=this._calculateXPosition(t,this._chart),i=this._calculateYPosition(t);this._element.style.transform=`translate(${e}, ${i})`}_calculateXPosition(t,e){const i=t.paneX+e.priceScale("left").width(),s=this._lastTooltipWidth?Math.ceil(this._lastTooltipWidth/2):this._options.horizontalDeadzoneWidth;return`calc(${Math.min(Math.max(s,i),e.timeScale().width()-s)}px - 50%)`}_calculateYPosition(t){if("top"==this._options.followMode)return`${this._options.topOffset}px`;const e=t.paneY,i=e<=this._options.verticalSpacing+this._options.verticalDeadzoneHeight;return`calc(${e+(i?1:-1)*this._options.verticalSpacing}px${i?"":" - 100%"})`}}function nt(t,e){t&&e!==t.innerText&&(t.innerText=e,t.style.display=e?"block":"none")}function at(t,e){for(const[i,s]of Object.entries(e))t.style.setProperty(i,s)}class rt{_data;constructor(t){this._data=t}draw(t){this._data.visible&&t.useBitmapCoordinateSpace((t=>{const e=t.context,i=X(this._data.x,t.horizontalPixelRatio,1);e.fillStyle=this._data.color,e.fillRect(i.position,this._data.topMargin*t.verticalPixelRatio,i.length,t.bitmapSize.height)}))}}class lt{_data;constructor(t){this._data=t}update(t){this._data=t}renderer(){return new rt(this._data)}zOrder(){return"bottom"}}const ht={lineColor:"rgba(0, 0, 0, 0.2)",priceExtractor:t=>void 0!==t.value?t.value.toFixed(2):void 0!==t.close?t.close.toFixed(2):""};class ct{_data;constructor(t){this._data=t}draw(t){t.useBitmapCoordinateSpace((t=>{if(null===this._data.x||null===this._data.top)return;const e=t.context;this._data.items.forEach((i=>{if(null===i.y)return;const s=K(i.y,i.y-this._data.columnHeight,t.verticalPixelRatio),o=K(this._data.x,this._data.x+i.width,t.horizontalPixelRatio);e.fillStyle=i.color??"rgba(80, 80, 255, 0.8)",e.fillRect(o.position,s.position,o.length,s.length-2)}))}))}}class dt{_source;_x=null;_width=6;_columnHeight=0;_top=null;_items=[];constructor(t){this._source=t}update(){const t=this._source._vpData,e=this._source._series,i=this._source._chart.timeScale();this._x=i.timeToCoordinate(t.time),this._width=i.options().barSpacing*t.width;const s=e.priceToCoordinate(t.profile[0].price)??0,o=e.priceToCoordinate(t.profile[1].price)??i.height();this._columnHeight=Math.max(1,s-o);const n=t.profile.reduce(((t,e)=>Math.max(t,e.vol)),0);this._top=s,this._items=t.profile.map((t=>({y:e.priceToCoordinate(t.price),width:this._width*t.vol/n,color:t.color})))}renderer(){return new ct({x:this._x,top:this._top,columnHeight:this._columnHeight,width:this._width,items:this._items})}}const pt={endTime:null,stopColor:"rgba(239, 83, 80, 0.25)",targetColor:"rgba(38, 166, 154, 0.25)",quantity:null};function ut(t,e,i,s,o,n,a,r){t.save(),t.font=`bold ${n}px -apple-system, BlinkMacSystemFont, sans-serif`,t.fillStyle=o,t.textAlign=a,t.textBaseline=r,t.fillText(s,e,i),t.restore()}class _t{_data=null;update(t){this._data=t}drawBackground(t){if(!this._data)return;const e=this._data;t.useBitmapCoordinateSpace((t=>{const i=t.context,s=t.horizontalPixelRatio,o=t.verticalPixelRatio,n=Math.round(e.entryX*s),a=Math.round(e.endX*s)-n;if(a<=0)return;const r=Math.round(e.entryY*o),l=Math.round(e.stopY*o),h=Math.round(e.targetY*o);i.fillStyle=e.stopColor,i.fillRect(n,Math.min(r,l),a,Math.abs(l-r)),i.fillStyle=e.targetColor,i.fillRect(n,Math.min(r,h),a,Math.abs(h-r))}))}draw(t){if(!this._data?.hovered)return;const e=this._data;t.useBitmapCoordinateSpace((t=>{const i=t.context,s=t.horizontalPixelRatio,o=t.verticalPixelRatio,n=Math.round(e.entryX*s),a=Math.round(e.endX*s);if(a-n<=0)return;const r=Math.round(e.entryY*o),l=Math.round(e.stopY*o),h=Math.round(e.targetY*o),c=Math.round((n+a)/2),d=Math.round(11*s),p=Math.round(4*s);i.save(),i.strokeStyle="#2962FF",i.lineWidth=Math.round(1*o),i.beginPath(),i.moveTo(n,r),i.lineTo(a,r),i.stroke(),ut(i,n+p,r-p,e.entryPriceLabel,"#2962FF",d,"left","bottom"),ut(i,a-p,r-p,`${e.barCount} bars`,"rgba(255,255,255,0.9)",d,"right","bottom"),ut(i,c,r+p,e.rrRatio,"rgba(255,255,255,0.9)",d,"center","top");const u=e.isLong?h+p:h-p,_=e.isLong?l-p:l+p,m=e.isLong?"top":"bottom",b=e.isLong?"bottom":"top";ut(i,n+p,u,e.tpPriceLabel,"rgba(38,166,154,1)",d,"left",m),ut(i,a-p,u,e.winAmount,"rgba(38,166,154,1)",d,"right",m),ut(i,n+p,_,e.slPriceLabel,"rgba(239,83,80,1)",d,"left",b),ut(i,a-p,_,e.loseAmount,"rgba(239,83,80,1)",d,"right",b),i.restore()}))}}class mt{_source;_renderer;constructor(t){this._source=t,this._renderer=new _t}update(){const t=this._source._options,e=this._source.chart,i=this._source.series,s=e.timeScale(),o=s.timeToCoordinate(t.entryTime);if(null===o)return void this._renderer.update(null);const n=t.endTime??this._source._autoEndTime,a=s.coordinateToLogical(o)??0,r=a+2;let l;if(null!==n){const t=s.timeToCoordinate(n);if(null!==t){l=(s.coordinateToLogical(t)??0)0?`1:${Math.round(u/p)}`:"1:∞",m=s.coordinateToLogical(l)??r,b=Math.max(0,m-a),g=t=>`$${t.toFixed(2)}`,y=(t,e)=>`${e}${Math.abs(t).toFixed(2)}`,v=(e,i)=>`${i}$${Math.abs(e*t.quantity).toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2})}`,w=null!==t.quantity?v(u,"+"):y(u,"+"),x=null!==t.quantity?v(p,"-"):y(p,"-");this._source._lastBounds={entryX:o,endX:l,minY:Math.min(c,d),maxY:Math.max(c,d)},this._renderer.update({entryX:o,endX:l,entryY:h,stopY:c,targetY:d,stopColor:t.stopColor,targetColor:t.targetColor,hovered:this._source._hovered,isLong:t.target>t.entry,rrRatio:_,barCount:b,winAmount:w,loseAmount:x,entryPriceLabel:g(t.entry),tpPriceLabel:g(t.target),slPriceLabel:g(t.stop)})}renderer(){return this._renderer}}return t.BandsIndicator=class extends a{_paneViews;_bandsData=[];_options;_timeIndices;_upperLower;_upperSeries;_lowerSeries;constructor(t,e,i={}){super(),this._upperSeries=t,this._lowerSeries=e,this._options={...q,...i},this._paneViews=[new U(this)],this._timeIndices=new z([]),this._upperLower=new $([])}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}attached(t){super.attached(t),this._lowerSeries.subscribeDataChanged(this._onBandDataChanged),this._upperSeries.subscribeDataChanged(this._onBandDataChanged),this.dataUpdated("full")}detached(){this._lowerSeries.unsubscribeDataChanged(this._onBandDataChanged),this._upperSeries.unsubscribeDataChanged(this._onBandDataChanged),super.detached()}_onBandDataChanged=t=>{this.dataUpdated(t),this.requestUpdate()};dataUpdated(t){this.calculateBands(),this._timeIndices=new z(this._bandsData)}calculateBands(){const t=this._upperSeries.data(),e=this._lowerSeries.data(),i=new Map;for(const t of e){const e=t.value;void 0!==e&&i.set(t.time,e)}const s=[];for(const e of t){const t=e.value;if(void 0===t)continue;const o=i.get(e.time);void 0!==o&&s.push({time:e.time,upper:t,lower:o})}this._bandsData=s,this._upperLower=new $(this._bandsData,4)}autoscaleInfo(t,e){if(!this._bandsData.length)return null;const i=this.chart.timeScale(),s=i.coordinateToTime(i.logicalToCoordinate(t)??0)??0,o=i.coordinateToTime(i.logicalToCoordinate(e)??5e9)??5e9,n=this._timeIndices.findClosestIndex(s,"left"),a=this._timeIndices.findClosestIndex(o,"right"),r=this._upperLower.getMinMax(n,a);return{priceRange:{minValue:r.lower,maxValue:r.upper}}}},t.Box=M,t.Handler=class{id;commandFunctions=[];wrapper;div;chart;scale;precision=2;series;volumeSeries;legends=new Map;get legend(){return this.legends.get(0)}_topBar;toolBox;spinner;_seriesList=[];watermark;seriesMarkers;constructor(t,i,s,n,a){this.reSize=this.reSize.bind(this),this.id=t,this.scale={width:i,height:s},this.wrapper=document.createElement("div"),this.wrapper.classList.add("handler"),this.div=document.createElement("div"),this.div.classList.add("chart-root"),this.wrapper.appendChild(this.div),window.containerDiv.append(this.wrapper),this.scale.width<1?(this.wrapper.style.width=100*this.scale.width+"%",this.wrapper.style.flex="0 0 auto",this.wrapper.style.float=n):(this.wrapper.style.width="100%",this.wrapper.style.flex="1 1 auto",this.wrapper.style.minHeight="0"),this.chart=this._createChart(),this.series=this.createCandlestickSeries(0),this.volumeSeries=this.createVolumeSeries(0),this.seriesMarkers=e.createSeriesMarkers(this.series,[]),this.legends.set(0,new o(0,this)),this.chart.subscribeCrosshairMove((t=>{this.legends.forEach((e=>e.legendHandler(t)))})),document.addEventListener("keydown",(t=>{for(let e=0;ewindow.handlerInFocus=this.id)),this.reSize(),a&&window.addEventListener("resize",(()=>this.reSize()))}reSize(){const t=window.containerDiv,e=0!==this.scale.height&&this._topBar?._div.offsetHeight||0,i=Math.max(Math.floor(t.clientWidth*this.scale.width),0),s=this.wrapper.clientHeight>0?this.wrapper.clientHeight:Math.floor(t.clientHeight*this.scale.height),o=Math.max(s-e,0);this.chart.resize(i,o),this.scale.width<1?this.wrapper.style.width=100*this.scale.width+"%":this.wrapper.style.width="100%",0===this.scale.height||0===this.scale.width?this.toolBox&&(this.toolBox.div.style.display="none"):this.toolBox&&(this.toolBox.div.style.display="flex")}_createChart(){const t=window.containerDiv,i=Math.max(Math.floor(t.clientWidth*this.scale.width)||Math.floor(window.innerWidth*this.scale.width),0),s=Math.max(Math.floor(t.clientHeight*this.scale.height)||Math.floor(window.innerHeight*this.scale.height),0);return e.createChart(this.div,{width:i,height:s,layout:{textColor:window.pane.color,background:{color:"#000000",type:e.ColorType.Solid},fontSize:12},rightPriceScale:{scaleMargins:{top:.3,bottom:.25}},timeScale:{timeVisible:!0,secondsVisible:!1},crosshair:{mode:e.CrosshairMode.Normal,vertLine:{labelBackgroundColor:"rgb(46, 46, 46)"},horzLine:{labelBackgroundColor:"rgb(55, 55, 55)"}},grid:{vertLines:{color:"rgba(29, 30, 38, 5)"},horzLines:{color:"rgba(29, 30, 58, 5)"}},handleScroll:{vertTouchDrag:!0}})}createCandlestickSeries(t){const i="rgba(39, 157, 130, 100)",s="rgba(200, 97, 100, 100)",o=this.chart.addSeries(e.CandlestickSeries,{upColor:i,borderUpColor:i,wickUpColor:i,downColor:s,borderDownColor:s,wickDownColor:s},t);return o.priceScale().applyOptions({scaleMargins:{top:.2,bottom:.2}}),o}createVolumeSeries(t){const i=this.chart.addSeries(e.HistogramSeries,{color:"#26a69a",priceFormat:{type:"volume"},priceScaleId:"volume_scale"},t);return i.priceScale().applyOptions({scaleMargins:{top:.8,bottom:0}}),i}getOrCreateLegend(t){return this.legends.has(t)||this.legends.set(t,new o(t,this)),this.legends.get(t)}createLineSeries(t,i,s){const o=this.chart.addSeries(e.LineSeries,{...i},s);return this._seriesList.push(o),this.getOrCreateLegend(s??0).makeSeriesRow(t,o),{name:t,series:o}}createHistogramSeries(t,i,s){const o=this.chart.addSeries(e.HistogramSeries,{...i},s);return this._seriesList.push(o),this.getOrCreateLegend(s??0).makeSeriesRow(t,o),{name:t,series:o}}createAreaSeries(t,i,s){const o=this.chart.addSeries(e.AreaSeries,{...i},s);return this._seriesList.push(o),this.getOrCreateLegend(s??0).makeSeriesRow(t,o),{name:t,series:o}}createToolBox(){this.toolBox=new O(this.id,this.chart,this.series,this.commandFunctions),this.div.appendChild(this.toolBox.div)}createTopBar(){return this._topBar=new F(this),this.wrapper.prepend(this._topBar._div),this._topBar}toJSON(){const{chart:t,...e}=this;return e}static makeSearchBox(t){const e=document.createElement("div");e.classList.add("searchbox"),e.style.display="none";const i=document.createElement("div");i.innerHTML='';const s=document.createElement("input");return s.type="text",e.appendChild(i),e.appendChild(s),t.div.appendChild(e),t.commandFunctions.push((i=>window.handlerInFocus===t.id&&!window.textBoxFocused&&("none"===e.style.display?!!/^[a-zA-Z0-9]$/.test(i.key)&&(e.style.display="flex",s.focus(),!0):("Enter"===i.key||"Escape"===i.key)&&("Enter"===i.key&&window.callbackFunction(`search${t.id}_~_${s.value}`),e.style.display="none",s.value="",!0)))),s.addEventListener("input",(()=>s.value=s.value.toUpperCase())),{window:e,box:s}}static makeSpinner(t){t.spinner=document.createElement("div"),t.spinner.classList.add("spinner"),t.wrapper.appendChild(t.spinner);let e=0;!function i(){t.spinner&&(e+=10,t.spinner.style.transform=`translate(-50%, -50%) rotate(${e}deg)`,requestAnimationFrame(i))}()}static _styleMap={"--bg-color":"backgroundColor","--hover-bg-color":"hoverBackgroundColor","--click-bg-color":"clickBackgroundColor","--active-bg-color":"activeBackgroundColor","--muted-bg-color":"mutedBackgroundColor","--border-color":"borderColor","--color":"color","--active-color":"activeColor"};static setRootStyles(t){const e=document.documentElement.style;for(const[i,s]of Object.entries(this._styleMap))e.setProperty(i,t[s])}createWatermark(t,i,s){this.watermark?this.watermark.applyOptions({lines:[{text:t,color:s,fontSize:i}]}):this.watermark=e.createTextWatermark(this.chart.panes()[0],{horzAlign:"center",vertAlign:"center",lines:[{text:t,color:s,fontSize:i}]})}},t.HeatMapSeries=class{_renderer;constructor(){this._renderer=new Z}priceValueBuilder(t){if(t.cells.length<1)return[NaN];let e=1/0,i=-1/0;t.cells.forEach((t=>{t.lowi&&(i=t.high)}));return[e,i,e+(i-e)/2]}isWhitespace(t){return void 0===t.cells||t.cells.length<1}renderer(){return this._renderer}update(t,e){this._renderer.update(t,e)}defaultOptions(){return Y}},t.HorizontalLine=w,t.Legend=o,t.PartialPriceLine=class{_paneViews;_chart=null;_series=null;constructor(){this._paneViews=[new Q]}attached({chart:t,series:e}){this._chart=t,this._series=e,this._series.applyOptions({priceLineVisible:!1})}detached(){this._chart=null,this._series=null}updateAllViews(){if(!this._series||!this._chart)return;const t=this._series.options();let i=t.priceLineColor||t.color||"#000000";const s=this._series.dataByIndex(1e5,e.MismatchDirection.NearestLeft);let o=null,n=null;var a;s&&(void 0!==s.color&&(i=s.color),o=void 0!==(a=s).value?a.value:void 0!==a.close?a.close:null,n=this._chart.timeScale().timeToCoordinate(s.time));const r=null!==o?this._series.priceToCoordinate(o):null;this._paneViews.forEach((t=>t.update(r,i,n)))}paneViews(){return this._paneViews}},t.PositionTool=class extends a{_options;_autoEndTime=null;_hovered=!1;_lastBounds=null;_paneViews;_subscribed=!1;constructor(t){super(),this._options={...pt,...t,endTime:t.endTime??null},this._paneViews=[new mt(this)]}attached(t){super.attached(t),this._subscribed||(this.chart.subscribeCrosshairMove(this._crosshairMoveHandler),this._subscribed=!0)}detached(){this._subscribed&&(this.chart.unsubscribeCrosshairMove(this._crosshairMoveHandler),this._subscribed=!1),super.detached()}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}autoscaleInfo(t,e){const{entry:i,stop:s,target:o}=this._options;return{priceRange:{minValue:Math.min(i,s,o),maxValue:Math.max(i,s,o)}}}applyOptions(t){this._options={...this._options,...t,endTime:void 0!==t.endTime?t.endTime??null:this._options.endTime},this.requestUpdate()}dataUpdated(t){const e=this.series.data();e.length>0&&(this._autoEndTime=e[e.length-1].time),this.requestUpdate()}_crosshairMoveHandler=t=>{const e=this._lastBounds;if(!e||!t.point)return void(this._hovered&&(this._hovered=!1,this.requestUpdate()));const{x:i,y:s}=t.point,o=i>=e.entryX&&i<=e.endX&&s>=e.minY&&s<=e.maxY;o!==this._hovered&&(this._hovered=o,this.requestUpdate())}},t.RayLine=R,t.SessionHighlighting=class extends a{_paneViews;_seriesData=[];_backgroundColors=[];_options;_highlighter;constructor(t,e={}){super(),this._highlighter=t,this._options={...it,...e},this._paneViews=[new et(this)]}updateAllViews(){this._paneViews.forEach((t=>t.update()))}paneViews(){return this._paneViews}attached(t){super.attached(t),this.dataUpdated("full")}dataUpdated(t){this._backgroundColors=this.series.data().map((t=>({time:t.time,color:this._highlighter(t.time)}))),this.requestUpdate()}},t.SubChartManager=class{_tabBar;_tabs=new Map;_activeId="";constructor(){this._tabBar=document.createElement("div"),this._tabBar.classList.add("subchart-tab-bar"),window.containerDiv.prepend(this._tabBar)}register(t,e,i){const s=document.createElement("button");if(s.classList.add("subchart-tab"),s.textContent=e,s.addEventListener("click",(()=>this.showTab(t))),this._tabBar.appendChild(s),this._tabs.set(t,{handler:i,tabEl:s}),1===this._tabs.size)s.classList.add("subchart-tab-active"),this._activeId=t,i.reSize();else{i.wrapper.style.display="none";const t=this._tabs.get(this._activeId);t?.handler.reSize()}}showTab(t){this._tabs.forEach((({handler:t,tabEl:e})=>{t.wrapper.style.display="none",e.classList.remove("subchart-tab-active")}));const e=this._tabs.get(t);e&&(e.handler.wrapper.style.display="flex",e.tabEl.classList.add("subchart-tab-active"),this._activeId=t,e.handler.reSize(),e.tabEl.scrollIntoView({inline:"nearest",block:"nearest"}))}updateLabel(t,e){const i=this._tabs.get(t);i&&(i.tabEl.textContent=e)}get activeId(){return this._activeId}},t.Table=class{_div;callbackName;borderColor;borderWidth;table;rows={};headings;widths;alignments;footer;header;constructor(t,e,i,s,o,n,a=!1,r,l,h,c,d){this._div=document.createElement("div"),this._div.classList.add("lw-table"),this.callbackName=null,this.borderColor=l,this.borderWidth=h,a?(this._div.style.position="absolute",this._div.style.cursor="move"):(this._div.style.position="absolute","right"===n?this._div.style.right="0px":this._div.style.left="0px",this._div.style.top="0px"),this._div.style.zIndex="2000",this.reSize(t,e),this._div.style.display="flex",this._div.style.flexDirection="column",this.table=document.createElement("table"),this._div.style.overflow="hidden",this.headings=i,this.widths=s.map((t=>100*t+"%")),this.alignments=o;let p=this.table.createTHead().insertRow();for(let t=0;t0?d[t]:r,e.style.color=c[t],p.appendChild(e)}let u,_,m=document.createElement("div");if(m.style.overflowY="auto",m.style.overflowX="hidden",m.style.backgroundColor=r,m.appendChild(this.table),this._div.appendChild(m),window.containerDiv.appendChild(this._div),!a)return;let b=t=>{this._div.style.left=t.clientX-u+"px",this._div.style.top=t.clientY-_+"px"},g=()=>{document.removeEventListener("mousemove",b),document.removeEventListener("mouseup",g)};this._div.addEventListener("mousedown",(t=>{u=t.clientX-this._div.offsetLeft,_=t.clientY-this._div.offsetTop,document.addEventListener("mousemove",b),document.addEventListener("mouseup",g)}))}divToButton(t,e){t.addEventListener("mouseover",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)")),t.addEventListener("mouseout",(()=>t.style.backgroundColor="transparent")),t.addEventListener("mousedown",(()=>t.style.backgroundColor="rgba(60, 60, 60)")),t.addEventListener("click",(()=>window.callbackFunction(e))),t.addEventListener("mouseup",(()=>t.style.backgroundColor="rgba(60, 60, 60, 0.6)"))}newRow(t,e=!1){let i=this.table.insertRow();i.style.cursor="default";for(let s=0;st.update(this._data)))}setData(t){this._data=t,this.updateAllViews(),this._attachedParams?.requestUpdate()}currentColor(){return this._options.lineColor}chart(){return this._attachedParams?.chart}series(){return this._attachedParams?.series}applyOptions(t){this._options={...this._options,...t},this._tooltip&&this._tooltip.applyOptions({...this._options.tooltip})}_setCrosshairMode(){const t=this.chart();if(!t)throw new Error("Unable to change crosshair mode because the chart instance is undefined");t.applyOptions({crosshair:{mode:e.CrosshairMode.Magnet,vertLine:{visible:!1,labelVisible:!1},horzLine:{visible:!1,labelVisible:!1}}})}_moveHandler=t=>this._onMouseMove(t);_hideTooltip(){this._tooltip&&(this._tooltip.updateTooltipContent({title:"",price:"",date:"",time:""}),this._tooltip.updatePosition({paneX:0,paneY:0,visible:!1}))}_hideCrosshair(){this._hideTooltip(),this.setData({x:0,visible:!1,color:this.currentColor(),topMargin:0})}_onMouseMove(t){const i=this.chart(),s=this.series(),o=t.logical;if(!o||!i||!s)return void this._hideCrosshair();const n=t.seriesData.get(s);if(!n)return void this._hideCrosshair();const a=this._options.priceExtractor(n),r=i.timeScale().logicalToCoordinate(o),[l,h]=function(t){if(!t)return["",""];const e=new Date(t),i=e.getFullYear(),s=e.toLocaleString("default",{month:"short"});return[`${e.getDate().toString().padStart(2,"0")} ${s} ${i}`,`${e.getHours().toString().padStart(2,"0")}:${e.getMinutes().toString().padStart(2,"0")}`]}(t.time?function(t){if(e.isUTCTimestamp(t))return 1e3*t;if(e.isBusinessDay(t))return new Date(t.year,t.month,t.day).valueOf();const[i,s,o]=t.split("-").map(parseInt);return new Date(i,s,o).valueOf()}(t.time):void 0);if(this._tooltip){const e=this._tooltip.options(),i="top"==e.followMode?e.topOffset+10:0;this.setData({x:r??0,visible:null!==r,color:this.currentColor(),topMargin:i}),this._tooltip.updateTooltipContent({price:a,date:l,time:h}),this._tooltip.updatePosition({paneX:t.point?.x??0,paneY:t.point?.y??0,visible:!0})}}_createTooltipElement(){const t=this.chart();if(!t)throw new Error("Unable to create Tooltip element. Chart not attached");this._tooltip=new ot(t,{...this._options.tooltip})}},t.TopBar=F,t.TrendLine=L,t.VerticalLine=N,t.VolumeProfile=class{_chart;_series;_vpData;_minPrice;_maxPrice;_paneViews;_vpIndex=null;constructor(t,e,i){this._chart=t,this._series=e,this._vpData=i,this._minPrice=1/0,this._maxPrice=-1/0,this._vpData.profile.forEach((t=>{t.pricethis._maxPrice&&(this._maxPrice=t.price)})),this._paneViews=[new dt(this)]}updateAllViews(){this._paneViews.forEach((t=>t.update()))}updateData(t){this._vpData=t,this._minPrice=1/0,this._maxPrice=-1/0,this._vpData.profile.forEach((t=>{t.pricethis._maxPrice&&(this._maxPrice=t.price)})),this.updateAllViews()}autoscaleInfo(t,e){const i=this._chart.timeScale().timeToCoordinate(this._vpData.time);if(null===i)return null;const s=this._chart.timeScale().coordinateToLogical(i);return null===s||es+this._vpData.width?null:{priceRange:{minValue:this._minPrice,maxValue:this._maxPrice}}}paneViews(){return this._paneViews}},t.globalParamInit=s,t.paneStyleDefault=i,t.setCursor=t=>{t&&(window.cursor=t),document.body.style.cursor=window.cursor},t}({},LightweightCharts); diff --git a/lightweight_charts/js/lightweight-charts.standalone.development.js b/lightweight_charts/js/lightweight-charts.standalone.development.js index e64476e..cb09cdf 100644 --- a/lightweight_charts/js/lightweight-charts.standalone.development.js +++ b/lightweight_charts/js/lightweight-charts.standalone.development.js @@ -8063,7 +8063,7 @@ } var ratio = (_b = (_a = this._devicePixelRatioObservable) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : win.devicePixelRatio; var canvasRects = this._canvasElement.getClientRects(); - var newSize = + var newSize = // eslint-disable-next-line no-negated-condition canvasRects[0] !== undefined ? predictedBitmapSize(canvasRects[0], ratio) : @@ -13759,7 +13759,7 @@ return HorzScaleBehaviorTime; } - function drawSeriesPointMarkers(renderingScope, items, pointMarkersRadius, visibleRange, + function drawSeriesPointMarkers(renderingScope, items, pointMarkersRadius, visibleRange, // the values returned by styleGetter are compared using the operator !==, // so if styleGetter returns objects, then styleGetter should return the same object for equal styles styleGetter) { @@ -13796,7 +13796,7 @@ return Math.hypot(p2x - p1x, p2y - p1y); } // eslint-disable-next-line max-params, complexity - function walkLine(renderingScope, items, lineType, visibleRange, barWidth, + function walkLine(renderingScope, items, lineType, visibleRange, barWidth, // the values returned by styleGetter are compared using the operator !==, // so if styleGetter returns objects, then styleGetter should return the same object for equal styles styleGetter, finishStyledArea, dashPatternLength = 0) { @@ -14311,7 +14311,7 @@ }; function generateWhitespaceData({ _internal_start: start, _internal_end: end, _internal_resolution: resolution, }) { - return Array.from({ length: Math.floor((end - start) / resolution) + 1 }, + return Array.from({ length: Math.floor((end - start) / resolution) + 1 }, // eslint-disable-next-line quote-props (item, i) => ({ 'time': start + i * resolution })); } diff --git a/lightweight_charts/js/stream-shim.js b/lightweight_charts/js/stream-shim.js index e56ebf7..cddea7d 100644 --- a/lightweight_charts/js/stream-shim.js +++ b/lightweight_charts/js/stream-shim.js @@ -1,15 +1,25 @@ (function () { var RETURN_PREFIX = '_~_~RETURN~_~_'; var token = '__STREAM_TOKEN__'; + var MAX_RELOADS = 5; + var RELOAD_KEY = 'lwcStreamReloadCount'; window.callbackFunction = function (msg) { if (ws && ws.readyState === 1) ws.send(msg); }; var ws; + var established = false; + function connect() { - ws = new WebSocket('ws://' + location.host + '/ws'); - ws.onopen = function () { ws.send(token); }; + established = false; + var scheme = location.protocol === 'https:' ? 'wss://' : 'ws://'; + ws = new WebSocket(scheme + location.host + '/ws'); + ws.onopen = function () { + established = true; + try { sessionStorage.removeItem(RELOAD_KEY); } catch (e) {} + ws.send(token); + }; ws.onmessage = function (e) { if (e.data.startsWith(RETURN_PREFIX)) { var result = eval(e.data.slice(RETURN_PREFIX.length)); @@ -18,7 +28,30 @@ eval(e.data); } }; - ws.onclose = function () { + ws.onclose = function (ev) { + // 4002 = single-client guard — short backoff + retry, not reload + // (avoids reload storms while the previous socket is clearing). + if (ev && ev.code === 4002) { + console.log('WebSocket rejected (4002), retrying in 500ms...'); + setTimeout(connect, 500); + return; + } + if (established) { + var count = 0; + try { + count = parseInt(sessionStorage.getItem(RELOAD_KEY) || '0', 10) || 0; + } catch (e) {} + if (count < MAX_RELOADS) { + try { sessionStorage.setItem(RELOAD_KEY, String(count + 1)); } catch (e) {} + console.log('WebSocket closed after session; reloading for fresh context...'); + location.reload(); + return; + } + try { sessionStorage.removeItem(RELOAD_KEY); } catch (e) {} + console.log('WebSocket closed; reload bound exceeded, retrying in 2s...'); + setTimeout(connect, 2000); + return; + } console.log('WebSocket closed, reconnecting in 1s...'); setTimeout(connect, 1000); }; diff --git a/lightweight_charts/js/styles.css b/lightweight_charts/js/styles.css index d784f07..3207c44 100644 --- a/lightweight_charts/js/styles.css +++ b/lightweight_charts/js/styles.css @@ -7,6 +7,22 @@ --border-color: #3C434C; --color: #d8d9db; --active-color: #ececed; + + /* Responsive chrome type scale (Plan 012 / Q2: 480×320–2560×1440, HiDPI 1x/2x). + Preferred formulas are vw-based so @media consumers and engines without + @container still scale; per-pane legend also overrides with cqw below. */ + --chrome-bp-sm: 480px; + --chrome-bp-md: 768px; + --chrome-bp-lg: 1280px; + --chrome-bp-xl: 1920px; + --chrome-font-xs: clamp(8px, 0.25vw + 6.5px, 11px); + --chrome-font-sm: clamp(9px, 0.3vw + 7.5px, 14px); + --chrome-font-md: clamp(10px, 0.35vw + 8px, 16px); + --chrome-font-lg: clamp(12px, 0.45vw + 9px, 20px); + --chrome-pad-y: clamp(1px, 0.08vw + 0.5px, 4px); + --chrome-pad-x: clamp(3px, 0.15vw + 2px, 8px); + --chrome-table-pad-y: clamp(0.1rem, 0.05vw + 0.08rem, 0.35rem); + --chrome-table-letter-spacing: clamp(0.02rem, 0.01vw + 0.015rem, 0.04rem); } html, @@ -41,6 +57,8 @@ body { min-height: 0; width: 100%; position: relative; + container-type: inline-size; + container-name: chart-handler; } .chart-root { @@ -127,27 +145,53 @@ body { display: flex; align-items: center; flex-shrink: 0; + overflow-x: auto; + overflow-y: hidden; + flex-wrap: nowrap; + scrollbar-width: thin; + scrollbar-color: var(--border-color) var(--bg-color); +} +.topbar::-webkit-scrollbar { + height: 6px; +} +.topbar::-webkit-scrollbar-track { + background: var(--bg-color); +} +.topbar::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 3px; } .topbar-container { display: flex; align-items: center; flex-grow: 1; + flex-shrink: 0; + min-width: max-content; } .topbar-button { border: none; - padding: 2px 5px; + padding: var(--chrome-pad-y) var(--chrome-pad-x); margin: 4px 10px; - font-size: 13px; + font-size: var(--chrome-font-md); border-radius: 4px; color: var(--color); background-color: transparent; + flex-shrink: 0; + white-space: nowrap; } .topbar-button:hover { background-color: var(--hover-bg-color) } +.topbar-switcher { + flex-shrink: 0; + display: flex; + align-items: center; + white-space: nowrap; +} + .topbar-button:active { background-color: var(--click-bg-color); color: var(--active-color); @@ -168,8 +212,10 @@ body { .topbar-textbox { margin: 0px 18px; - font-size: 16px; + font-size: var(--chrome-font-lg); color: var(--color); + flex-shrink: 0; + white-space: nowrap; } .topbar-textbox-input { @@ -195,6 +241,7 @@ body { width: 1px; height: 20px; background-color: var(--border-color); + flex-shrink: 0; } .searchbox { @@ -249,6 +296,7 @@ body { left: 10px; display: none; flex-direction: column; + font-size: var(--chrome-font-sm); } .series-container { display: flex; @@ -256,6 +304,7 @@ body { pointer-events: auto; overflow-y: auto; max-height: 80vh; + font-size: inherit; } .series-container::-webkit-scrollbar { width: 0px; @@ -289,7 +338,7 @@ body { border-radius: 4px; cursor: pointer; color: rgba(255, 255, 255, 0.45); - font-size: 11px; + font-size: var(--chrome-font-xs); line-height: 1; pointer-events: auto; } @@ -320,7 +369,7 @@ body { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55); overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - font-size: 12px; + font-size: var(--chrome-font-sm); } .legend-modal-header { display: flex; @@ -332,7 +381,7 @@ body { } .legend-modal-title { color: #d1d4dc; - font-size: 13px; + font-size: var(--chrome-font-md); font-weight: 600; letter-spacing: 0.2px; } @@ -340,7 +389,7 @@ body { background: none; border: none; color: rgba(255, 255, 255, 0.4); - font-size: 14px; + font-size: var(--chrome-font-md); cursor: pointer; padding: 2px 6px; border-radius: 4px; @@ -370,7 +419,7 @@ body { } .legend-modal-section-title { color: rgba(255, 255, 255, 0.3); - font-size: 10px; + font-size: var(--chrome-font-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.9px; @@ -385,7 +434,7 @@ body { } .legend-modal-field-label { color: #b2b5be; - font-size: 12px; + font-size: var(--chrome-font-sm); flex: 1; white-space: nowrap; } @@ -394,7 +443,7 @@ body { border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 4px; color: #d1d4dc; - font-size: 12px; + font-size: var(--chrome-font-sm); padding: 3px 6px; min-width: 80px; max-width: 130px; @@ -430,20 +479,37 @@ body { display: flex; flex-direction: row; flex-shrink: 0; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; background-color: var(--bg-color); border-bottom: 2px solid var(--border-color); padding: 0 4px; + scrollbar-width: thin; + scrollbar-color: var(--border-color) var(--bg-color); +} +.subchart-tab-bar::-webkit-scrollbar { + height: 6px; +} +.subchart-tab-bar::-webkit-scrollbar-track { + background: var(--bg-color); +} +.subchart-tab-bar::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 3px; } .subchart-tab { border: none; - padding: 2px 5px; + padding: var(--chrome-pad-y) var(--chrome-pad-x); margin: 4px 10px; - font-size: 13px; + font-size: var(--chrome-font-md); border-radius: 4px; color: var(--color); background-color: transparent; cursor: pointer; + flex-shrink: 0; + white-space: nowrap; } .subchart-tab:hover { @@ -455,3 +521,57 @@ body { color: var(--active-color) !important; font-weight: 500; } + +/* HTML table chrome (typography/spacing only; colors/borders/align stay inline) */ +.lw-table { + font-size: var(--chrome-font-sm); + font-variant-numeric: tabular-nums; + border-radius: 5px; + color: white; +} + +.lw-table table { + width: 100%; + border-collapse: collapse; +} + +.lw-table th { + letter-spacing: var(--chrome-table-letter-spacing); + padding: var(--chrome-table-pad-y) 0; + font-weight: 500; +} + +.lw-table-section { + display: flex; + width: 100%; + padding: var(--chrome-table-pad-y) 0; + background-color: rgb(30, 30, 30); +} + +.lw-table-section-cell { + flex: 1; + text-align: center; +} + +/* Per-pane legend: prefer container width when @container is supported */ +@container chart-handler (min-width: 0) { + .legend { + font-size: clamp(9px, 0.3cqw + 7.5px, 14px); + } + .legend-modal { + font-size: clamp(9px, 0.3cqw + 7.5px, 14px); + } + .legend-modal-title, + .legend-modal-close { + font-size: clamp(10px, 0.35cqw + 8px, 16px); + } + .legend-modal-section-title, + .legend-cross-btn { + font-size: clamp(8px, 0.25cqw + 6.5px, 11px); + } + .legend-modal-field-label, + .legend-modal-input { + font-size: clamp(9px, 0.3cqw + 7.5px, 14px); + } +} + diff --git a/lightweight_charts/plugins/volume_profile.py b/lightweight_charts/plugins/volume_profile.py index 779ed0d..e695b7f 100644 --- a/lightweight_charts/plugins/volume_profile.py +++ b/lightweight_charts/plugins/volume_profile.py @@ -15,8 +15,8 @@ class VolumeProfile(Pane): :param series: The series to attach to (also provides the chart reference). :param time: Anchor timestamp for the profile (Unix seconds, datetime, or string). :param profile: Price-level volume data — either a list of - ``{'price': float, 'vol': float}`` dicts or a DataFrame with - ``price`` and ``vol`` columns. + ``{'price': float, 'vol': float, 'color': str}`` dicts or a DataFrame with + ``price``, ``vol``, and optional ``color`` columns. :param width: Width of the profile in bar units (default 10). """ @@ -30,11 +30,9 @@ def __init__( super().__init__(series._chart.win) self._series = series ts = series._chart._format_time(time) - profile_list = ( - profile[["price", "vol"]].to_dict("records") - if isinstance(profile, pd.DataFrame) - else profile - ) + profile_list = self._profile_records(profile) + self._vpData_time = ts + self._vpData_width = width vp_data = {"time": ts, "profile": profile_list, "width": width} self.run_script(f""" {self.id} = new Lib.VolumeProfile( @@ -45,6 +43,30 @@ def __init__( {series.id}.series.attachPrimitive({self.id}); null""") + @staticmethod + def _profile_records(profile: list[dict] | pd.DataFrame) -> list[dict]: + if isinstance(profile, pd.DataFrame): + cols = [c for c in ("price", "vol", "color") if c in profile.columns] + return profile[cols].to_dict("records") + return profile + + def update_data( + self, + profile: list[dict] | pd.DataFrame, + time=None, + width: int | None = None, + ) -> None: + """Replace profile data in-place without detaching the primitive.""" + ts = self._series._chart._format_time(time) if time is not None else self._vpData_time + if time is not None: + self._vpData_time = ts + if width is not None: + self._vpData_width = width + profile_list = self._profile_records(profile) + w = width if width is not None else self._vpData_width + vp_data = {"time": ts, "profile": profile_list, "width": w} + self.run_script(f"{self.id}.updateData({json.dumps(vp_data)})") + def delete(self): """Detaches and removes the volume profile from the series.""" self.run_script(f"{self._series.id}.series.detachPrimitive({self.id})") diff --git a/lightweight_charts/stream.py b/lightweight_charts/stream.py index d12f363..29fb95b 100644 --- a/lightweight_charts/stream.py +++ b/lightweight_charts/stream.py @@ -9,23 +9,42 @@ chart.set(df) chart.show(port=8080, block=True) -The printed URL includes a one-time security token; share it only with trusted viewers. + # asyncio + await chart.show_async(port=8080) + +The logged URL includes a one-time security token; share it only with trusted viewers. + +Reconnect model (v1.4.0+) +------------------------- +While a browser is disconnected, per-bar **transient** scripts (``series.setData`` / +``update``, volume, markers) are **not** buffered. On (re)connect the server replays a +bounded **structural** script log, then emits a data snapshot regenerated from +authoritative Python state (``candle_data`` / ``data`` / ``markers``, plus tracked +whitespace from ``append_whitespace``). + +Series/marker/whitespace data is snapshot-backed. Other per-bar mutations (table +cells, price lines, legend, PositionTool P&L) still append to the structural log — +a soft size warning fires if that log grows large. """ +from __future__ import annotations + import asyncio +import logging import os import secrets import threading import time import webbrowser +from contextlib import asynccontextmanager import uvicorn from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles -from fastapi.middleware.cors import CORSMiddleware -from .abstract import AbstractChart +from .abstract import AbstractChart, Window from .util import BulkRunScript, parse_event_message # --------------------------------------------------------------------------- @@ -35,41 +54,99 @@ _JS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "js") +# Soft bound for structural script log (defense-in-depth; M-02). +_STRUCTURAL_LOG_WARN_THRESHOLD = 2000 + +# Library-safe logger: no handlers of our own; apps (or logging.basicConfig) +# decide where records go. NullHandler avoids "No handlers" noise. +logger = logging.getLogger(__name__) +logger.addHandler(logging.NullHandler()) -class StreamWindow: + +class StreamWindow(Window): """ Drop-in replacement for abstract.Window that communicates with a browser client over WebSocket instead of pywebview. + + Subclasses ``Window`` so ``_id_gen``, ``create_table``, ``style``, and + other shared window helpers stay in sync with ``AbstractChart``. + + Structural scripts are always retained in ``self.scripts`` for reconnect. + Transient (series/marker data) scripts are never buffered — they are either + sent live or dropped while disconnected and regenerated via snapshot. """ def __init__(self) -> None: - self.token: str = secrets.token_hex(32) - self.scripts: list = [] # replay buffer — never cleared + super().__init__() + # Per-instance handlers (``Window.handlers`` is a shared class dict). self.handlers: dict = {} + self.token: str = secrets.token_hex(32) + self._ws: WebSocket | None = None self._loop: asyncio.AbstractEventLoop | None = None self._return_event = threading.Event() self._return_value = None self._server: uvicorn.Server | None = None self._thread: threading.Thread | None = None - - # BulkRunScript batches JS calls; script_func is called on __exit__ + self._server_ready = threading.Event() + self._client_ready = threading.Event() + self._host: str | None = None + self._port: int | None = None + self._chart: AbstractChart | None = None + self._data_lock = threading.RLock() + self._structural_log_warned = False + + # Route batched JS through this window's WebSocket-aware run_script. self.bulk_run = BulkRunScript(self.run_script) + def data_guard(self): + """Real lock serializing live mutations against connect-time snapshot.""" + return self._data_lock + + def _append_structural(self, script: str) -> None: + self.scripts.append(script) + if not self._structural_log_warned and len(self.scripts) >= _STRUCTURAL_LOG_WARN_THRESHOLD: + self._structural_log_warned = True + logger.warning( + "StreamWindow structural script log has %d entries (threshold %d). " + "Only series/marker data is snapshot-backed; per-bar " + "table/price-line/legend/PositionTool updates still buffer as " + "structural and can grow unbounded.", + len(self.scripts), + _STRUCTURAL_LOG_WARN_THRESHOLD, + ) + + @property + def url(self) -> str | None: + """HTTP URL for this stream window, set after :meth:`show`.""" + if self._host is None or self._port is None: + return None + return f"http://{self._host}:{self._port}/" + # ------------------------------------------------------------------ # Public interface (mirrors abstract.Window) # ------------------------------------------------------------------ - def run_script(self, script: str, run_last: bool = False) -> None: - """Send *script* to the connected browser, or buffer it for later.""" + def run_script(self, script: str, run_last: bool = False, transient: bool = False) -> None: + """ + Send *script* to the connected browser, or buffer structural scripts. + + - Connected: send live; structural scripts also append to the log. + - Disconnected + transient: drop (state lives in Python). + - Disconnected + structural: append to ``self.scripts``. + """ if self.bulk_run.enabled: - self.bulk_run.add_script(script) + self.bulk_run.add_script(script, transient=transient) return if self._ws is not None and self._loop is not None: asyncio.run_coroutine_threadsafe(self._ws.send_text(script), self._loop) + if not transient: + self._append_structural(script) else: - self.scripts.append(script) + if transient: + return + self._append_structural(script) def run_script_and_get(self, script: str): """ @@ -85,15 +162,6 @@ def run_script_and_get(self, script: str): ) return self._return_value - def on_js_load(self, script: str) -> None: - """ - Buffer *script* permanently (for replay on reconnect) and send it - immediately if a client is already connected. - """ - self.scripts.append(script) - if self._ws is not None and self._loop is not None: - asyncio.run_coroutine_threadsafe(self._ws.send_text(script), self._loop) - def bulk_run_scripts(self, scripts) -> None: """Send each script in *scripts*.""" for s in scripts: @@ -119,8 +187,29 @@ def show( host: str = "127.0.0.1", debug: bool = False, cors_origins: list[str] | None = None, - ) -> None: - """Build the FastAPI app and start uvicorn in a daemon thread.""" + log_level: str = "error", + ) -> str: + """Build the FastAPI app and start uvicorn in a daemon thread. + + ``log_level`` is passed to uvicorn (default ``"error"`` keeps the + server quiet). Use ``"info"`` to surface startup and access logs via + the host app's logging handlers. ``log_config=None`` so uvicorn does + not call ``dictConfig`` and wipe existing handlers. + """ + self._host = host + self._port = port + self._server_ready.clear() + self._client_ready.clear() + + @asynccontextmanager + async def lifespan(app: FastAPI): + self._server_ready.set() + try: + yield + finally: + self._server_ready.clear() + self._client_ready.clear() + app = FastAPI( debug=debug, docs_url=None, @@ -128,6 +217,7 @@ def show( title=title, summary=summary, description=description, + lifespan=lifespan, ) allowed_origins = ["http://127.0.0.1", "http://localhost"] if cors_origins: @@ -144,7 +234,12 @@ def show( @app.get("/") async def serve_stream_html(): headers = { - "Content-Security-Policy": ("default-src 'self'; script-src 'self' 'unsafe-eval'") + "Content-Security-Policy": ( + "default-src 'self'; " + "connect-src 'self' ws: wss:; " + "script-src 'self' 'unsafe-eval'; " + "style-src 'self' 'unsafe-inline'" + ) } return FileResponse( os.path.join(_JS_DIR, "stream.html"), @@ -177,16 +272,26 @@ async def ws_endpoint(websocket: WebSocket): # --- single-client guard --- if self._ws is not None: - print("WARNING: A second client attempted to connect; rejected with code 4002.") + logger.warning("A second client attempted to connect; rejected with code 4002.") await websocket.close(code=4002) return - self._ws = websocket - - # --- replay buffered scripts --- + # H-02 / M-04: keep _ws=None during handshake so live transient + # emits are dropped; structural replay + snapshot use ordered + # await send_text on this event loop; flip live only after snapshot. for script in list(self.scripts): await websocket.send_text(script) + with self.data_guard(): + snapshot_scripts: list[str] = [] + if self._chart is not None: + snapshot_scripts = self._chart.data_snapshot_scripts() + for script in snapshot_scripts: + await websocket.send_text(script) + self._ws = websocket + + self._client_ready.set() + # --- message loop --- try: while True: @@ -205,12 +310,19 @@ async def ws_endpoint(websocket: WebSocket): pass finally: self._ws = None + self._client_ready.clear() # Static files (bundle.js, styles.css, etc.) — mounted AFTER the # explicit "/" route so that route takes priority. app.mount("/", StaticFiles(directory=_JS_DIR), name="static") - config = uvicorn.Config(app, host=host, port=port, log_level="error") + config = uvicorn.Config( + app, + host=host, + port=port, + log_level=log_level, + log_config=None, + ) self._server = uvicorn.Server(config) loop_ready = threading.Event() @@ -227,9 +339,10 @@ def _run(): 98, 10048, ): - print( - f"ERROR: Port {port} is already in use. " - "Choose a different port with chart.show(port=)." + logger.error( + "Port %d is already in use. " + "Choose a different port with chart.show(port=).", + port, ) else: raise @@ -237,14 +350,20 @@ def _run(): self._thread = threading.Thread(target=_run, daemon=True) self._thread.start() loop_ready.wait() # ensure self._loop is set before returning + if not self._server_ready.wait(timeout=30.0): + raise TimeoutError(f"Timed out waiting for chart server to start on {host}:{port}") + return self.url or f"http://{host}:{port}/" class StreamChart(AbstractChart): """ - A chart served over HTTP/WebSocket. Open the printed URL in any browser. + A chart served over HTTP/WebSocket. Open the logged URL in any browser. :: + import logging + logging.basicConfig(level=logging.INFO) + chart = StreamChart() chart.set(df) chart.show(port=8080, block=True) @@ -264,6 +383,12 @@ def __init__( # width/height are unused window-geometry params; pass proportions to # AbstractChart so autosize takes over in the browser. super().__init__(self.win, width=1.0, height=1.0, toolbox=toolbox) + self.win._chart = self + + @property + def url(self) -> str | None: + """HTTP URL for this chart, set after :meth:`show`.""" + return self.win.url def show( self, @@ -272,7 +397,9 @@ def show( open_browser: bool = False, block: bool = True, cors_origins: list[str] | None = None, - ) -> None: + debug: bool = False, + log_level: str = "error", + ) -> str: """ Start the chart server and optionally open a browser. @@ -283,18 +410,25 @@ def show( security warning below). open_browser: Automatically open the URL in the default browser. block: Block until Ctrl-C (suitable for scripts). + cors_origins: Extra allowed CORS origins. + debug: Enable FastAPI debug mode. + log_level: Uvicorn log level (default ``"error"``). Pass ``"info"`` + to include startup/access logs in the host logging config. """ - url = f"http://{host}:{port}/" - print(f"Chart server running at {url} — press Ctrl+C to stop") + url = self.win.show( + port=port, + host=host, + cors_origins=cors_origins, + debug=debug, + log_level=log_level, + ) + logger.info("Chart server running at %s — press Ctrl+C to stop", url) if host not in ("127.0.0.1", "::1", "localhost"): - print( - "WARNING: Chart server is accessible from the network. " - "Ensure the token URL is kept private." + logger.warning( + "Chart server is accessible from the network. Ensure the token URL is kept private." ) - self.win.show(port=port, host=host, cors_origins=cors_origins) - if open_browser: webbrowser.open(url) @@ -304,3 +438,43 @@ def show( time.sleep(1) except KeyboardInterrupt: self.win.stop() + return url + + async def show_async( + self, + port: int = 8080, + host: str = "127.0.0.1", + open_browser: bool = False, + cors_origins: list[str] | None = None, + debug: bool = False, + log_level: str = "error", + ) -> str: + """ + Start the chart server, await readiness, and run until cancelled. + + Returns the chart URL once the server is accepting connections. + Cancel the task or send ``KeyboardInterrupt`` to stop the server. + + Server startup (``show``) blocks the calling thread until the port is + ready, so it runs in a worker thread via ``asyncio.to_thread`` to keep + the caller's event loop free. + """ + url = await asyncio.to_thread( + self.show, + port, + host, + open_browser, + False, # block + cors_origins, + debug, + log_level, + ) + try: + while True: + await asyncio.sleep(0.1) + except KeyboardInterrupt: + self.win.stop() + return url + except asyncio.CancelledError: + self.win.stop() + raise diff --git a/lightweight_charts/util.py b/lightweight_charts/util.py index 8c04888..dbfb2b8 100644 --- a/lightweight_charts/util.py +++ b/lightweight_charts/util.py @@ -211,7 +211,8 @@ def __init__(self, chart): class BulkRunScript: def __init__(self, script_func): self.enabled = False - self.scripts = [] + # list of (script, transient) — tag preserved through batching (Plan 013 M-01) + self.scripts: list[tuple[str, bool]] = [] self.script_func = script_func def __enter__(self): @@ -219,8 +220,22 @@ def __enter__(self): def __exit__(self, *args): self.enabled = False - self.script_func("\n".join(self.scripts)) + items = self.scripts self.scripts = [] - - def add_script(self, script): - self.scripts.append(script) + structural = [s for s, transient in items if not transient] + transient_scripts = [s for s, transient in items if transient] + # Flush separately so mixed batches are never one untagged structural blob. + if structural: + self._flush("\n".join(structural), transient=False) + if transient_scripts: + self._flush("\n".join(transient_scripts), transient=True) + + def _flush(self, script: str, *, transient: bool) -> None: + try: + self.script_func(script, transient=transient) + except TypeError: + # pywebview Window.script_func accepts only the script string. + self.script_func(script) + + def add_script(self, script: str, transient: bool = False) -> None: + self.scripts.append((script, transient)) diff --git a/package-lock.json b/package-lock.json index 63282a3..519be40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "@rollup/rollup-win32-x64-msvc": "^4.60.0", "dts-bundle-generator": "^8.0.1", "fancy-canvas": "^2.1.0", - "lightweight-charts": "^5.1.0", + "lightweight-charts": "5.2.0", "tslib": "^2.6.2" }, "devDependencies": { diff --git a/pyproject.toml b/pyproject.toml index 3aa8022..472a3aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-lightweight-charts" -version = "1.3.0" +version = "1.4.0" description = "A maintained Python wrapper for TradingView's Lightweight Charts v5.x — featuring StreamChart, plugins, multi-pane layouts, and full IChartApi coverage." authors = [ { name = "Gopal Parashar", email = "gopal.parashar421@gmail.com" } diff --git a/src/bands-indicator/bands-indicator.ts b/src/bands-indicator/bands-indicator.ts index bb14fef..8668d3a 100644 --- a/src/bands-indicator/bands-indicator.ts +++ b/src/bands-indicator/bands-indicator.ts @@ -31,6 +31,9 @@ class BandsIndicatorPaneRenderer implements IPrimitivePaneRenderer { draw() {} drawBackground(target: CanvasRenderingTarget2D) { const points: BandRendererData[] = this._viewData.data; + if (!points.length) { + return; + } target.useBitmapCoordinateSpace(scope => { const ctx = scope.context; ctx.scale(scope.horizontalPixelRatio, scope.verticalPixelRatio); @@ -148,27 +151,28 @@ export class BandsIndicator extends PluginBase implements ISeriesPrimitive