pylightcharts 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. pylightcharts-0.1.0/LICENSE +26 -0
  2. pylightcharts-0.1.0/MANIFEST.in +10 -0
  3. pylightcharts-0.1.0/PKG-INFO +411 -0
  4. pylightcharts-0.1.0/README.md +370 -0
  5. pylightcharts-0.1.0/pylightcharts/__init__.py +54 -0
  6. pylightcharts-0.1.0/pylightcharts/abstract.py +1778 -0
  7. pylightcharts-0.1.0/pylightcharts/chart.py +265 -0
  8. pylightcharts-0.1.0/pylightcharts/compat.py +117 -0
  9. pylightcharts-0.1.0/pylightcharts/drawings.py +513 -0
  10. pylightcharts-0.1.0/pylightcharts/headless.py +164 -0
  11. pylightcharts-0.1.0/pylightcharts/indicators.py +228 -0
  12. pylightcharts-0.1.0/pylightcharts/indicators_api.py +309 -0
  13. pylightcharts-0.1.0/pylightcharts/js/bundle.js +1 -0
  14. pylightcharts-0.1.0/pylightcharts/js/index.html +17 -0
  15. pylightcharts-0.1.0/pylightcharts/js/lightweight-charts.js +7 -0
  16. pylightcharts-0.1.0/pylightcharts/js/styles.css +237 -0
  17. pylightcharts-0.1.0/pylightcharts/numeric.py +58 -0
  18. pylightcharts-0.1.0/pylightcharts/polygon.py +396 -0
  19. pylightcharts-0.1.0/pylightcharts/polygon_chart.py +93 -0
  20. pylightcharts-0.1.0/pylightcharts/price_scale.py +72 -0
  21. pylightcharts-0.1.0/pylightcharts/qt.py +65 -0
  22. pylightcharts-0.1.0/pylightcharts/shapes.py +146 -0
  23. pylightcharts-0.1.0/pylightcharts/table.py +138 -0
  24. pylightcharts-0.1.0/pylightcharts/toolbox.py +45 -0
  25. pylightcharts-0.1.0/pylightcharts/topbar.py +128 -0
  26. pylightcharts-0.1.0/pylightcharts/util.py +378 -0
  27. pylightcharts-0.1.0/pylightcharts/widgets.py +260 -0
  28. pylightcharts-0.1.0/pylightcharts.egg-info/PKG-INFO +411 -0
  29. pylightcharts-0.1.0/pylightcharts.egg-info/SOURCES.txt +32 -0
  30. pylightcharts-0.1.0/pylightcharts.egg-info/dependency_links.txt +1 -0
  31. pylightcharts-0.1.0/pylightcharts.egg-info/requires.txt +27 -0
  32. pylightcharts-0.1.0/pylightcharts.egg-info/top_level.txt +1 -0
  33. pylightcharts-0.1.0/pyproject.toml +50 -0
  34. pylightcharts-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 louisnw01 (lightweight-charts-python)
4
+ Copyright (c) 2025 pylightcharts contributors
5
+
6
+ This project is a derivative work based on lightweight-charts-python
7
+ (https://github.com/louisnw01/lightweight-charts-python, MIT).
8
+ It embeds TradingView Lightweight Charts™ (Apache-2.0), see NOTICE.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ # Keep dev-only / reference trees out of the sdist.
2
+ # (The wheel is already limited by `packages = ["pylightcharts"]`.)
3
+ prune reference
4
+ prune jslib
5
+ prune examples
6
+ prune tests
7
+ prune dist
8
+ prune build
9
+ global-exclude *.py[cod]
10
+ global-exclude __pycache__/*
@@ -0,0 +1,411 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylightcharts
3
+ Version: 0.1.0
4
+ Summary: TradingView Lightweight Charts for Python (bridged, near-full API coverage)
5
+ Author: pylightcharts contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/your-org/pylightcharts
8
+ Keywords: financial-charting,charting,candlestick,tradingview,lightweight-charts
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Office/Business :: Financial :: Investment
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: pandas>=1.5
20
+ Requires-Dist: numpy>=1.23
21
+ Requires-Dist: pywebview>=5.0.5
22
+ Provides-Extra: qt
23
+ Requires-Dist: PySide6; extra == "qt"
24
+ Provides-Extra: jupyter
25
+ Requires-Dist: ipython; extra == "jupyter"
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest; extra == "test"
28
+ Requires-Dist: pillow; extra == "test"
29
+ Provides-Extra: e2e
30
+ Requires-Dist: playwright; extra == "e2e"
31
+ Requires-Dist: pillow; extra == "e2e"
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs-material; extra == "docs"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest; extra == "dev"
36
+ Requires-Dist: build; extra == "dev"
37
+ Requires-Dist: pillow; extra == "dev"
38
+ Requires-Dist: playwright; extra == "dev"
39
+ Requires-Dist: mkdocs-material; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ # pylightcharts
43
+
44
+ Python bindings for [TradingView Lightweight Charts™](https://github.com/tradingview/lightweight-charts),
45
+ aiming at **near-full API coverage** with a pythonic interface.
46
+
47
+ > Status: development (P0/P1/P2 hardening — 17 indicators, 13 drawing tools, numeric axes, formatters, CI with JS + Python tests and docs).
48
+
49
+ ## How it works
50
+
51
+ This is **not** a re-implementation of the charting engine in Python.
52
+ It embeds the official, battle-tested JavaScript renderer in a WebView
53
+ (pywebview / Qt WebEngine / wx / Jupyter / Streamlit) and drives it from Python.
54
+
55
+ ```
56
+ Python API ──► JSON / JS bridge ──► Lib.Handler (TypeScript) ──► Lightweight Charts v5
57
+ ```
58
+
59
+ Because the pixels and interactions are produced by the upstream library,
60
+ visual fidelity and performance match the JS version.
61
+
62
+ ## Acknowledgements / License
63
+
64
+ - Python layer, GUI backends and drawing plugins are derived from
65
+ [lightweight-charts-python](https://github.com/louisnw01/lightweight-charts-python) (MIT).
66
+ - The charting engine is
67
+ [TradingView Lightweight Charts™](https://github.com/tradingview/lightweight-charts) (Apache-2.0),
68
+ see `NOTICE`.
69
+
70
+ ## Migrating from lightweight-charts-python
71
+ `pylightcharts` keeps every class, method and module-level name of the original
72
+ package (enforced by `tests/test_lwc_compat.py`), so existing code runs with a
73
+ one-line shim:
74
+
75
+ ```python
76
+ from pylightcharts.compat import install_alias
77
+ install_alias() # before any `import lightweight_charts`
78
+
79
+ import lightweight_charts # -> pylightcharts
80
+ lightweight_charts.widgets.QWebEngineView = MyPyQt6WebEngineView # patching still works
81
+ ```
82
+
83
+ Or change the imports directly:
84
+
85
+ ```diff
86
+ - from lightweight_charts.util import Events, JSEmitter
87
+ - from lightweight_charts.widgets import QtChart
88
+ + from pylightcharts.util import Events, JSEmitter
89
+ + from pylightcharts.widgets import QtChart
90
+ ```
91
+
92
+ ## Qt integration
93
+
94
+ Inside a desktop application, `QtChart` embeds the chart in a `QWebEngineView`:
95
+
96
+ ```python
97
+ from PyQt6.QtWidgets import QApplication
98
+ from pylightcharts.qt import prepare_qt
99
+
100
+ prepare_qt('PyQt6') # pick the binding *before* QApplication
101
+ app = QApplication([])
102
+
103
+ from pylightcharts.widgets import QtChart
104
+ chart = QtChart(toolbox=True)
105
+ chart.set(df)
106
+ chart.get_webview().show()
107
+ app.exec()
108
+ ```
109
+
110
+ `QtWebEngineWidgets` must be imported before the `QApplication` exists, which is
111
+ why `prepare_qt()` comes first; `$PYLIGHTCHARTS_QT` chooses the binding without it.
112
+ Full details - multi-chart `sync()`, screenshots, the Chromium network noise -
113
+ are in `docs/guide/qt.md`.
114
+
115
+ ## Examples
116
+
117
+ `examples/` in the repository holds ten self-contained scripts, each with its own
118
+ data: loading OHLCV, live bars, tick updates, indicator lines, styling,
119
+ callbacks, built-in indicators and panes, headless PNG rendering, custom series
120
+ and the PyQt6 window. Start with `examples/1_setting_data/setting_data.py`.
121
+
122
+ ## Development
123
+
124
+ ```bash
125
+ # build the JS bridge + vendor the engine into pylightcharts/js/
126
+ cd jslib && npm install && npm run build
127
+
128
+ # install the python package in editable mode
129
+ cd .. && pip install -e .
130
+
131
+ # ...or use the requirement files (pyproject.toml stays the source of truth)
132
+ pip install -r requirements.txt # runtime only
133
+ pip install -r requirements-dev.txt # + tests, e2e, docs, build
134
+ ```
135
+
136
+ ### Testing
137
+
138
+ ```bash
139
+ # unit tests: assert on the JS generated by the python bridge (no browser)
140
+ python -m pytest -m "not slow"
141
+
142
+ # e2e: run the built bundle in headless Chromium and check rendered pixels
143
+ python tests/e2e/smoke.py
144
+
145
+ # headless rendering tests (Playwright or a local Chrome/Edge)
146
+ python -m pytest tests/test_headless.py
147
+
148
+ # full stack: real pywebview window + real chart engine (needs a desktop session)
149
+ python tests/e2e/full_stack.py
150
+
151
+ # performance: JSON vs binary bulk data transfer
152
+ python tests/e2e/benchmark.py 100000
153
+ ```
154
+
155
+ CI (`.github/workflows/ci.yml`) builds the bundle, checks the committed artifacts
156
+ are up to date, runs the unit tests on Linux/Windows/macOS, renders in headless
157
+ Chromium and verifies the wheel ships the JS assets.
158
+
159
+ ### Packaging
160
+
161
+ ```bash
162
+ cd jslib && npm run build # regenerate pylightcharts/js/*
163
+ cd .. && python -m build # sdist + wheel (JS assets are package-data)
164
+ twine upload dist/*
165
+ ```
166
+
167
+ The built `pylightcharts/js/` files are committed on purpose so that
168
+ `pip install` works without Node. Always run `npm run build` before committing.
169
+
170
+ ### Bridge rules
171
+
172
+ - Fire-and-forget calls go through `Window.invoke` (the transport appends
173
+ `;undefined` so pywebview never serialises a live chart object).
174
+ - Methods that **return an object** must use `store_as=...` or, for primitives,
175
+ `invoke_get`. Returning a live object through `invoke_get` would explode.
176
+ - Round-trips are serialised and correlated by request id; JS errors raise
177
+ `pylightcharts.util.BridgeError` instead of returning `None`.
178
+ - Chart and series share method names (`apply_options`); they are deliberately
179
+ kept apart (`series_options`, `data_points`) to avoid MRO shadowing.
180
+ - Upgrading lightweight-charts: `npm pack lightweight-charts@X`, migrate the
181
+ breaking changes, `npm run build`, then run the e2e suite.
182
+
183
+ ### The generic bridge
184
+
185
+ New features should not require touching TypeScript. Any method on any live
186
+ chart object can be called from Python:
187
+
188
+ ```python
189
+ chart._invoke('addSeries', 'Area', 'my area', {'lineWidth': 2}, handle)
190
+ chart.win.invoke(f'{series.id}.series', 'applyOptions', {'lineWidth': 3})
191
+ ```
192
+
193
+ Handles are either registered via `Lib.register(...)` or dotted window paths
194
+ (`window.abcdefgh.chart`). A `{'$ref': handle}` argument is resolved to the live
195
+ JS object, so object-taking APIs work too:
196
+
197
+ ```python
198
+ series.attach_primitive('myPrimitive') # -> attachPrimitive({"$ref": "myPrimitive"})
199
+ ```
200
+
201
+ See `jslib/src/general/rpc.ts`.
202
+
203
+ ### Full option coverage
204
+
205
+ `apply_options(**kwargs)` accepts any option of the underlying API and converts
206
+ `snake_case` keys to `camelCase` automatically:
207
+
208
+ ```python
209
+ chart.apply_options(auto_size=True, localization={'price_format': {'precision': 4}})
210
+ chart.time_scale_options(right_offset=5, bar_spacing=8) # time scale
211
+ series.apply_options(line_width=3, crosshair_marker_visible=False)
212
+ get = chart.get_price_scale('left'); get.set_mode('logarithmic')
213
+ ```
214
+
215
+ ### Indicators
216
+
217
+ Pure-pandas implementations, no extra dependencies. Overlays go on the price
218
+ pane, oscillators get their own pane automatically:
219
+
220
+ ```python
221
+ chart.add_sma('close', 20)
222
+ chart.add_ema('close', 50, color='#2196F3')
223
+ chart.add_bollinger('close', 20, 2) # -> (upper, middle, lower)
224
+ chart.add_donchian(20)
225
+ chart.add_vwap()
226
+
227
+ chart.add_rsi(14) # own pane + 70/30 guide lines
228
+ chart.add_macd() # own pane -> (macd, signal, histogram)
229
+ chart.add_stochastic() # own pane -> (%K, %D)
230
+ chart.add_atr(14)
231
+ chart.add_adx(14) # own pane -> (adx, +DI, -DI)
232
+ chart.add_obv() # own pane (needs volume)
233
+ chart.add_cci(20)
234
+ chart.add_williams_r(14)
235
+ chart.add_mfi(14)
236
+ chart.add_roc(12)
237
+ chart.add_keltner(20, 2.0) # overlay -> (upper, middle, lower)
238
+ ```
239
+
240
+ Raw values: `from pylightcharts import indicators; indicators.rsi(close, 14)`.
241
+ RSI, OBV, CCI, %R, ROC, MFI and Bollinger match TA-Lib to floating-point
242
+ precision; ATR and ADX match within ~1e-4 and ~0.1 respectively (Wilder
243
+ accumulation rounding).
244
+
245
+ #### Indicators follow the data
246
+
247
+ Every indicator is recomputed automatically: a full re-send on `chart.set()`,
248
+ and a single-point update on `chart.update(bar)` (recomputed from a warm-up
249
+ window so recursive indicators stay numerically identical to a full run).
250
+ `add_computed_series(compute, name, ...)` adds your own.
251
+
252
+ #### Batching ticks
253
+
254
+ Each bridge call is one webview round-trip, so wrap bursts in `chart.batch()`:
255
+
256
+ ```python
257
+ with chart.batch():
258
+ for tick in ticks:
259
+ chart.update(tick) # flushed as a single script
260
+ ```
261
+
262
+ #### Conflation (very large data)
263
+
264
+ Conflation is a plain series option, so the bridge already exposes it:
265
+
266
+ ```python
267
+ chart.apply_options(enable_conflation=True, precompute_conflation_on_init=True)
268
+ series.apply_options(enable_conflation=True)
269
+ ```
270
+
271
+ ### Custom series (declarative rendering)
272
+
273
+ The tricky part of a custom series is `draw()`, which runs synchronously every
274
+ frame - python cannot take part in that. So the protocol splits the work:
275
+ **python computes the shapes once per data update, one generic JS renderer draws
276
+ them every frame.**
277
+
278
+ ```python
279
+ from pylightcharts import shapes
280
+
281
+ series = chart.add_custom_series('range bars', pane_index='new')
282
+ series.set(df, shapes=lambda row: shapes.range_bar(row['low'], row['high']))
283
+ ```
284
+
285
+ Shape builders: `rect` `band` `line` `circle` `text` `polyline`, plus presets
286
+ `range_bar` `box_plot` `error_bar`. Vertical anchors are prices, horizontal
287
+ offsets are in bar units (`-0.4..0.4` spans a bar).
288
+
289
+ ```python
290
+ shapes.box_plot(low, q1, median, q3, high)
291
+ shapes.polyline([(0.0, 1.0), (0.4, 2.0)], fill_color='rgba(0,0,0,0.2)')
292
+ shapes.text(price, 'label')
293
+ ```
294
+
295
+ Each data item may carry `value`, `low`, `high` (autoscale + last value),
296
+ `color`, and `shapes`.
297
+
298
+ ### Price lines, data readback, events
299
+
300
+ ```python
301
+ line = series.create_price_line(105.0, color='#00e676', title='entry')
302
+ line.apply_options(line_visible=False)
303
+ line.remove()
304
+
305
+ series.price_to_coordinate(105) # <-> series.coordinate_to_price(y)
306
+ series.data_by_index(10) # individual bar
307
+ series.data_points() # everything the chart holds
308
+ series.pop(2); series.last_value_data(); series.bars_in_logical_range(0, 50)
309
+ series.series_type(); series.get_pane_index()
310
+ series.move_to_pane(2); series.series_order(); series.set_series_order(3)
311
+
312
+ chart.scroll_to_real_time(); chart.scroll_to_position(3, animated=False)
313
+ chart.reset_time_scale(); chart.set_visible_logical_range(0, 60)
314
+ chart.get_visible_range(); chart.get_visible_logical_range()
315
+ chart.time_to_coordinate(t); chart.coordinate_to_time(x)
316
+ chart.logical_to_coordinate(l); chart.coordinate_to_logical(x)
317
+ chart.pane_height(0); chart.set_pane_height(300, 0)
318
+ chart.pane_stretch_factor(0); chart.pane_size(); chart.pane_series_count(0)
319
+ chart.version(); chart.auto_size_active(); chart.remove()
320
+ chart.set_crosshair_position(100.0, some_time); chart.clear_crosshair_position()
321
+
322
+ chart.get_price_scale('right').get_visible_range()
323
+ chart.get_price_scale('right').set_auto_scale(False)
324
+ chart.pane_price_scale(0, 'left')
325
+
326
+ chart.events.crosshair_move += handler # handler(chart, time, price)
327
+ chart.events.dblclick += handler
328
+ chart.events.click.unsubscribe()
329
+ ```
330
+
331
+ ### Multi-chart sync
332
+
333
+ ```python
334
+ price = Chart(); volume = Chart()
335
+ price.sync(volume) # volume follows price pan/zoom
336
+ price.sync(volume, crosshairs_only=True)
337
+ ```
338
+
339
+ ### Native panes (lightweight-charts v5)
340
+
341
+ ```python
342
+ chart.add_pane()
343
+ chart.create_area(name='close', pane_index=1)
344
+ chart.add_series('Line', 'ma20', pane_index=1, color='#0ff')
345
+ chart.set_pane_stretch(1, 1.5)
346
+ chart.pane_count()
347
+ ```
348
+
349
+ `pane_index='new'` creates a fresh pane, and passing an index beyond the
350
+ current pane count creates the panes in between.
351
+
352
+ ### Drawing tools
353
+
354
+ Trend line / horizontal line / vertical line / ray / box, plus:
355
+
356
+ ```python
357
+ chart.fibonacci(t1, p1, t2, p2, levels=(0, 0.382, 0.5, 0.618, 1))
358
+ chart.measure(t1, p1, t2, p2) # price %, bar count
359
+ chart.parallel_channel(t1, p1, t2, p2, offset=5) # second line 5 price units away
360
+ chart.position(t1, entry, t2, target, risk_ratio=1.5) # profit + stop zones
361
+ chart.long_position(t1, entry, t2, target) # aliases
362
+ chart.short_position(t1, entry, t2, target)
363
+
364
+ chart.andrews_pitchfork(t1, p1, t2, p2, t3, p3) # 3 points
365
+ chart.triangle(t1, p1, t2, p2, t3, p3) # 3 points
366
+ chart.fibonacci_extension(t1, p1, t2, p2, t3, p3) # impulse + retracement
367
+ chart.gann_fan(t1, p1, t2, p2) # 1x1 plus steeper/shallower rays
368
+ ```
369
+
370
+ With `toolbox=True` these are also available interactively
371
+ (Alt+F / M / C / P / A / G / X / N).
372
+
373
+ ### Numeric axes and formatters
374
+
375
+ ```python
376
+ from pylightcharts import YieldCurveChart, OptionsChart
377
+
378
+ curve = YieldCurveChart() # x axis = duration in months
379
+ series = curve.add_series('Line', 'rate')
380
+ series.set(pd.DataFrame({'time': [1, 3, 12], 'rate': [4.2, 4.0, 3.6]}))
381
+
382
+ surface = OptionsChart() # x axis = strike
383
+
384
+ chart.set_price_formatter(decimals=2, thousands=True, prefix='$')
385
+ chart.set_time_formatter('YYYY-MM-DD HH:mm')
386
+ chart.register_js_formatter('eur', "value => '\u20ac' + value.toFixed(2)")
387
+ chart.set_price_formatter(name='eur')
388
+ ```
389
+
390
+ ### Large data
391
+
392
+ DataFrames with 2000+ rows are transferred as a base64 column-major Float64
393
+ buffer and rebuilt in the browser (`Lib.decodeData`) instead of being shipped as
394
+ JavaScript source. Measured 100k bars: **1.56s -> 0.54s (~3x)**;
395
+ 500k bars: **8.9s -> 3.2s**. Tune with `pylightcharts.util.BINARY_DATA_THRESHOLD`.
396
+
397
+ ### Headless / server-side rendering
398
+
399
+ ```python
400
+ from pylightcharts.headless import HeadlessChart
401
+
402
+ chart = HeadlessChart(width=1200, height=700)
403
+ chart.set(df)
404
+ chart.add_sma('close', 20)
405
+ chart.add_rsi(14)
406
+ chart.render('report.png') # PNG bytes, no window required
407
+ chart.to_html() # or grab the standalone HTML
408
+ ```
409
+
410
+ Uses Playwright when installed, otherwise a local Chrome/Edge in headless mode
411
+ (`PYLIGHTCHARTS_CHROME` overrides the browser path).